alex/beta-minecraft-rust
Minecraft Beta 1.7.3 Rewrite
BRANCH main
SNAPSHOT Draw terrain GPU-driven from packed per-section buffers with GPU occlusion culling, add a render distance slider (2-128) with a quad/disk shape, cut chunk bookkeeping at high render distances, and fix a light underflow at y=0
DOWNLOAD
root crates client src render mod.rs
//! Vulkan renderer: sky, terrain and animated textures.
pub mod animated;
mod capture;
pub mod item;
pub mod mesher;
pub mod scene;
mod sky;
mod staging;
mod terrain;
use std::ops::Range;
use std::ptr::null;
use std::sync::Arc;
use std::time::{Duration, Instant};
use crate::gpu::vk::*;
use crate::gpu::vk::{
VkAccessFlags2 as Access, VkImageLayout as Layout, VkPipelineStageFlags2 as Stage,
};
use crate::gpu::{
Binding, Blend, Buffer, ComputePipeline, Descriptors, Device, GraphicsPipeline, Image,
Instance, MemoryLocation, PipelineDesc, PresentMode, Presentation, Result, Surface, Swapchain,
Validation, check, subresource_range, zeroed,
};
use bytemuck::{Pod, Zeroable};
use common::world::ChunkPos;
use glam::{DVec3, Mat4, Vec3, Vec4};
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
use self::animated::TileUpdate;
use self::sky::{SkyGeometry, SkyParams, SkyPass, SkyVertex};
use self::staging::Staging;
use self::terrain::{CullMode, Terrain, TerrainCopies};
use crate::assets::{Assets, RgbaImage};
use crate::world::ClientWorld;
const FRAMES_IN_FLIGHT: usize = 2;
const STAGING_SIZE: u64 = 64 << 20;
/// Initial capacity of the per-frame translucent section lists, which grow to the frame's.
const TRANSLUCENT_SECTIONS: usize = 1 << 12;
const SKY_VERTICES: usize = 1 << 14;
const GUI_VERTICES: usize = 1 << 16;
const SCENE_VERTICES: usize = 1 << 18;
const DEPTH_FORMAT: VkFormat = VkFormat::D32Sfloat;
const TERRAIN_SPIRV: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/terrain.spv"));
const SKY_SPIRV: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/sky.spv"));
const GUI_SPIRV: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/gui.spv"));
const SCENE_SPIRV: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/scene.spv"));
const COLOR: VkImageAspectFlags = VkImageAspectFlags::COLOR;
type Scope = (Stage, Access);
const COPY_WRITE: Scope = (Stage::COPY, Access::TRANSFER_WRITE);
const COPY_READ: Scope = (Stage::COPY, Access::TRANSFER_READ);
const COLOR_WRITE: Scope = (
Stage::COLOR_ATTACHMENT_OUTPUT,
Access::COLOR_ATTACHMENT_WRITE,
);
const NO_ACCESS: Scope = (Stage::NONE, Access::NONE);
const TRANSFER_WRITE: Scope = (Stage::ALL_TRANSFER, Access::TRANSFER_WRITE);
/// Color and depth attachment access, ordering one rendering's stores before the next's loads.
const ATTACHMENTS: Scope = (
Stage::COLOR_ATTACHMENT_OUTPUT
.union(Stage::EARLY_FRAGMENT_TESTS)
.union(Stage::LATE_FRAGMENT_TESTS),
Access::COLOR_ATTACHMENT_READ
.union(Access::COLOR_ATTACHMENT_WRITE)
.union(Access::DEPTH_STENCIL_ATTACHMENT_READ)
.union(Access::DEPTH_STENCIL_ATTACHMENT_WRITE),
);
const COMPUTE_WRITE: Scope = (Stage::COMPUTE_SHADER, Access::SHADER_WRITE);
/// Everything that reads or writes the terrain tables and section meshes, and the frame's
/// sampled textures.
const CULLING: Scope = (
Stage::DRAW_INDIRECT
.union(Stage::VERTEX_SHADER)
.union(Stage::FRAGMENT_SHADER)
.union(Stage::COMPUTE_SHADER)
.union(Stage::ALL_TRANSFER),
Access::INDIRECT_COMMAND_READ
.union(Access::SHADER_READ)
.union(Access::SHADER_WRITE)
.union(Access::TRANSFER_READ),
);
const ATLAS_SLOT: u32 = 0;
const SUN_SLOT: u32 = 1;
const MOON_SLOT: u32 = 2;
/// Everything about the viewer and environment needed to draw one frame.
pub struct FrameParams<'a> {
pub eye: DVec3,
/// Camera rotation (and the original's small back offset); world positions are camera-relative.
pub view: Mat4,
pub fov_y_degrees: f32,
/// `farPlaneDistance`: fog ends here.
pub view_distance: f32,
pub fog_color: Vec3,
/// Exponential fog of this density under water or lava, instead of distance fog.
pub fog_density: Option<f32>,
pub sky_color: Vec3,
/// `getRainStrength`, which fades out the sun, moon and stars.
pub rain: f32,
pub celestial_angle: f32,
pub sunrise: Option<[f32; 4]>,
pub sky_darkness: u32,
/// Without a sky (the Nether), only fog fills the background and starts at the eye.
pub has_sky: bool,
pub brightness: [f32; 16],
pub tile_updates: &'a [TileUpdate],
pub gui: &'a crate::gui::Geometry,
pub scene: &'a scene::Geometry,
}
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
struct TerrainPush {
view_projection: [f32; 16],
fog_color: [f32; 4],
/// The camera relative to the eye, which fog distances are measured from.
camera: [f32; 4],
eye_fraction: [f32; 4],
eye_block: [i32; 4],
sections: u64,
fog_start: f32,
fog_end: f32,
sky_darkness: u32,
texture: u32,
/// Scales the output alpha. A depth-only pass draws with 0 through the pipeline of its color
/// pass: blending then keeps the color, and both passes compute bit-identical depths.
opacity: f32,
_padding: u32,
brightness: [f32; 16],
}
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
struct BoxPush {
view_projection: [f32; 16],
eye_fraction: [f32; 4],
eye_block: [i32; 4],
sections: u64,
tested: u64,
}
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
struct SkyPush {
view_projection: [f32; 16],
vertices: u64,
_padding: u64,
fog_color: [f32; 4],
/// The camera relative to the eye, which fog distances are measured from.
camera: [f32; 4],
fog_start: f32,
fog_end: f32,
fog: u32,
texture: i32,
}
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
struct ScenePush {
view_projection: [f32; 16],
vertices: u64,
fog: u32,
/// Fragments with less alpha are discarded (`glAlphaFunc(GL_GREATER, ...)`).
alpha_cutoff: f32,
fog_color: [f32; 4],
/// The camera relative to the eye, which fog distances are measured from.
camera: [f32; 4],
fog_start: f32,
fog_end: f32,
/// See [`TerrainPush::opacity`].
opacity: f32,
_padding: u32,
}
struct FrameResources {
pool: VkCommandPool,
command_buffer: VkCommandBuffer,
fence: VkFence,
image_available: VkSemaphore,
staging: Staging,
/// Slots of the translucent sections to draw, back to front.
translucent: Buffer,
/// The sections the frame drew, read back once it finished.
drawn: Buffer,
sky: Buffer,
gui: Buffer,
scene: Buffer,
capture: Option<capture::Capture>,
map_image: Image,
map_colors: Option<Box<[u8; 16384]>>,
}
/// Defines `Pipelines` from rows of shader, blend and changes to the default state: depth
/// tested, no depth writes, no culling and triangle lists.
macro_rules! pipelines {
($($name:ident: $spirv:ident, $blend:ident { $($field:ident: $value:expr),* };)*) => {
struct Pipelines {
$($name: GraphicsPipeline,)*
cull: ComputePipeline,
}
impl Pipelines {
fn new(device: &Arc<Device>, color_format: VkFormat, binding: Binding) -> Result<Self> {
Ok(Self {
cull: ComputePipeline::new(device, TERRAIN_SPIRV, c"cullMain", binding)?,
$($name: GraphicsPipeline::new(device, &PipelineDesc {
$($field: $value,)*
..PipelineDesc {
spirv: $spirv,
vertex_entry: c"vertexMain",
fragment_entry: c"fragmentMain",
color_format,
depth_format: Some(DEPTH_FORMAT),
depth_test: true,
topology: VkPrimitiveTopology::TriangleList,
depth_write: false,
cull: false,
blend: Blend::$blend,
color_write: true,
binding,
}
})?,)*
})
}
}
};
}
pipelines! {
terrain_solid: TERRAIN_SPIRV, Opaque {
depth_write: true, cull: true, fragment_entry: c"solidFragment"
};
terrain_cutout: TERRAIN_SPIRV, Opaque { depth_write: true, cull: true };
terrain_translucent: TERRAIN_SPIRV, Alpha { depth_write: true };
section_boxes: TERRAIN_SPIRV, Opaque {
vertex_entry: c"boxVertex", fragment_entry: c"boxFragment", color_write: false
};
sky_plane: SKY_SPIRV, Opaque {};
sky_alpha: SKY_SPIRV, Alpha {};
sky_additive: SKY_SPIRV, Additive {};
gui: GUI_SPIRV, Alpha {};
gui_invert: GUI_SPIRV, Invert {};
scene: SCENE_SPIRV, Alpha { depth_write: true };
scene_additive: SCENE_SPIRV, Additive {};
outline: SCENE_SPIRV, Alpha {};
lines: SCENE_SPIRV, Opaque { depth_write: true, topology: VkPrimitiveTopology::LineList };
nameplate: SCENE_SPIRV, Alpha { depth_test: false };
cracks: SCENE_SPIRV, Multiply {};
}
pub struct Renderer {
pipelines: Pipelines,
terrain: Terrain,
sky: SkyGeometry,
translucent: Vec<u32>,
/// Sections the GPU drew in the last finished frame.
drawn: u32,
fancy: bool,
/// `GameSettings.ambientOcclusion`, on by default.
smooth_lighting: bool,
frames: Vec<FrameResources>,
frame: u64,
resize: Option<VkExtent2D>,
capture_requested: bool,
captures: Vec<RgbaImage>,
descriptors: Descriptors,
/// The atlas, sun, moon and GUI textures, at their descriptor slots.
textures: Vec<Image>,
/// Whether `gui/items.png` has the original layout that compass and clock tiles target.
animated_items: bool,
depth: Image,
swapchain: Swapchain,
device: Arc<Device>,
}
impl Renderer {
/// # Safety
/// The window and display handles must outlive the renderer.
pub unsafe fn new(
display: RawDisplayHandle,
window: RawWindowHandle,
extent: VkExtent2D,
assets: &Assets,
validation: Option<Validation>,
descriptor_heaps: bool,
presentation: Presentation,
) -> Result<Self> {
let instance = Instance::new(display, validation)?;
let surface = unsafe { Surface::new(&instance, display, window)? };
let device = Device::new(&instance, &surface)?;
let swapchain = Swapchain::new(&device, surface, extent, presentation)?;
let depth = create_depth(&device, swapchain.extent())?;
let textures = load_textures(&device, assets)?;
mesher::set_atlas(&assets.terrain, &animated::TRANSPARENT_TILES);
let upload =
|size: usize, usage| Buffer::new(&device, size as u64, usage, MemoryLocation::CpuToGpu);
let vertices = |size: usize| upload(size, VkBufferUsageFlags2::SHADER_DEVICE_ADDRESS);
let frames = (0..FRAMES_IN_FLIGHT)
.map(|_| {
let pool = device.create_command_pool(true)?;
Ok(FrameResources {
capture: None,
map_image: upload_texture(&device, &blank_map())?,
map_colors: None,
pool,
command_buffer: device.allocate_command_buffer(pool)?,
fence: device.create_fence(true)?,
image_available: device.create_semaphore()?,
staging: Staging::new(&device, STAGING_SIZE)?,
translucent: vertices(TRANSLUCENT_SECTIONS * size_of::<u32>())?,
drawn: {
let size = size_of::<u32>() as u64;
let usage = VkBufferUsageFlags2::TRANSFER_DST;
let mut drawn =
Buffer::new(&device, size, usage, MemoryLocation::GpuToCpu)?;
drawn.write(0, &[0; 4])?;
drawn
},
sky: vertices(SKY_VERTICES * size_of::<SkyVertex>())?,
gui: vertices(GUI_VERTICES * size_of::<crate::gui::Vertex>())?,
scene: vertices(SCENE_VERTICES * size_of::<scene::Vertex>())?,
})
})
.collect::<Result<Vec<_>>>()?;
let heaps = descriptor_heaps && device.descriptor_heap;
let descriptors = describe(&device, heaps, &textures, &frames)?;
let pipelines = Pipelines::new(&device, swapchain.format(), descriptors.binding())?;
Ok(Self {
pipelines,
terrain: Terrain::new(&device)?,
sky: SkyGeometry::default(),
translucent: Vec::new(),
drawn: 0,
fancy: true,
smooth_lighting: true,
frames,
frame: 0,
resize: None,
capture_requested: false,
captures: Vec::new(),
descriptors,
textures,
animated_items: assets.gui[2].width == 256,
depth,
swapchain,
device,
})
}
pub fn device_name(&self) -> &str {
&self.device.name
}
pub fn supports_descriptor_heaps(&self) -> bool {
self.device.descriptor_heap
}
/// Whether shaders reach their textures through descriptor heaps rather than a descriptor set.
pub fn descriptor_heaps(&self) -> bool {
matches!(self.descriptors, Descriptors::Heaps(_))
}
pub fn present_modes(&self) -> &[PresentMode] {
self.swapchain.present_modes()
}
pub fn supports_frame_pacing(&self) -> bool {
self.swapchain.supports_pacing()
}
pub fn presentation(&self) -> Presentation {
self.swapchain.presentation()
}
/// When pacing, plans the frame about to be rendered and returns when it will be displayed.
pub fn begin_frame(&mut self, min_interval: Duration) -> Option<Instant> {
self.swapchain.begin_frame(min_interval)
}
/// When pacing, when to start rendering the next frame.
pub fn next_frame(&self) -> Option<Instant> {
self.swapchain.next_frame()
}
pub fn request_screenshot(&mut self) {
self.capture_requested = true;
}
pub fn finish_screenshots(&mut self) -> Result<()> {
self.device.wait_idle()?;
for frame in &mut self.frames {
if let Some(capture) = frame.capture.take() {
self.captures.push(capture.finish()?);
}
}
Ok(())
}
pub fn take_screenshots(&mut self) -> Vec<RgbaImage> {
std::mem::take(&mut self.captures)
}
pub fn resize(&mut self, extent: VkExtent2D) {
self.resize = Some(extent);
}
/// `getDebugInfoRenders`' counts: sections drawn last frame and with geometry, and meshes
/// uploaded since the last call.
pub fn debug_counts(&mut self) -> (usize, usize, u32) {
(
self.drawn as usize,
self.terrain.section_count(),
self.terrain.take_updates(),
)
}
pub fn column_ready(&self, pos: ChunkPos) -> bool {
self.terrain.column_ready(pos)
}
pub fn clear_world(&mut self) {
self.terrain.clear(self.frame);
}
pub fn set_options(
&mut self,
fancy: bool,
smooth_lighting: bool,
descriptor_heaps: bool,
presentation: Presentation,
) -> Result<()> {
if self.swapchain.configure(presentation) {
self.resize.get_or_insert(self.swapchain.extent());
}
if self.fancy != fancy || self.smooth_lighting != smooth_lighting {
self.fancy = fancy;
self.smooth_lighting = smooth_lighting;
self.terrain.rebuild();
}
let heaps = descriptor_heaps && self.device.descriptor_heap;
if heaps != self.descriptor_heaps() {
let descriptors = describe(&self.device, heaps, &self.textures, &self.frames)?;
self.replace_descriptors(descriptors)?;
}
Ok(())
}
pub fn reload_assets(&mut self, assets: &Assets) -> Result<()> {
let textures = load_textures(&self.device, assets)?;
mesher::set_atlas(&assets.terrain, &animated::TRANSPARENT_TILES);
let heaps = self.descriptor_heaps();
let descriptors = describe(&self.device, heaps, &textures, &self.frames)?;
self.replace_descriptors(descriptors)?;
self.textures = textures;
self.animated_items = assets.gui[2].width == 256;
self.terrain.rebuild();
Ok(())
}
/// Waits for the GPU, then switches to `descriptors` and to pipelines for their binding.
fn replace_descriptors(&mut self, descriptors: Descriptors) -> Result<()> {
let binding = descriptors.binding();
let pipelines = (binding != self.descriptors.binding())
.then(|| Pipelines::new(&self.device, self.swapchain.format(), binding))
.transpose()?;
self.device.wait_idle()?;
if let Some(pipelines) = pipelines {
self.pipelines = pipelines;
}
self.descriptors = descriptors;
Ok(())
}
/// Remeshes a filled column and its filled neighbors; columns that arrive later do the same.
pub fn column_loaded(&mut self, pos: ChunkPos, world: &ClientWorld) {
for dx in -1..=1 {
for dz in -1..=1 {
let column = pos.offset(dx, dz);
if world.contains(column) {
self.terrain.mark_column_dirty(column);
}
}
}
}
pub fn section_changed(&mut self, pos: ChunkPos, y: i32) {
self.terrain.mark_section_dirty(pos, y);
}
pub fn column_unloaded(&mut self, pos: ChunkPos) {
self.terrain.remove_column(pos, self.frame);
}
pub fn render(&mut self, world: Option<&ClientWorld>, params: &FrameParams) -> Result<()> {
if let Some(extent) = self.resize.take() {
if extent.width == 0 || extent.height == 0 {
self.resize = Some(extent);
return Ok(());
}
self.swapchain.recreate(extent)?;
self.depth = create_depth(&self.device, self.swapchain.extent())?;
}
let slot = (self.frame % FRAMES_IN_FLIGHT as u64) as usize;
let commands = &self.device.commands;
let device = self.device.handle;
let waiting = Instant::now();
{
let frame = &mut self.frames[slot];
check(unsafe { commands.vkWaitForFences(device, 1, &frame.fence, VK_TRUE, u64::MAX) })?;
check(unsafe {
commands.vkResetCommandPool(device, frame.pool, VkCommandPoolResetFlags::empty())
})?;
frame.staging.reset();
if let Some(capture) = frame.capture.take() {
self.captures.push(capture.finish()?);
}
frame.drawn.invalidate()?;
self.drawn = bytemuck::pod_read_unaligned(&frame.drawn.mapped()[..4]);
}
let Some(image) = self.swapchain.acquire(self.frames[slot].image_available)? else {
self.resize = Some(self.swapchain.extent());
return Ok(());
};
self.swapchain.waited(waiting.elapsed());
if let Some(world) = world {
self.terrain
.schedule(world, params.eye, self.fancy, self.smooth_lighting);
}
let frame = &mut self.frames[slot];
let mut terrain_copies = TerrainCopies::default();
let completed = self.frame as i64 - FRAMES_IN_FLIGHT as i64;
let camera = params.view.inverse().transform_point3(Vec3::ZERO);
self.terrain.upload(
&mut frame.staging,
&mut terrain_copies,
self.frame,
completed,
params.eye + camera.as_dvec3(),
)?;
// Copies into the atlas and into `gui/items.png`
let mut tile_copies = [Vec::new(), Vec::new()];
for update in params.tile_updates {
if update.texture != 0 && !self.animated_items {
continue;
}
if let Some(offset) = frame.staging.push(&update.pixels) {
let tile = i32::from(update.tile);
let copy = image_copy(offset, [tile % 16 * 16, tile / 16 * 16], 16, 16);
tile_copies[usize::from(update.texture != 0)].push(copy);
}
}
let extent = self.swapchain.extent();
if self.capture_requested {
let format = self.swapchain.format();
frame.capture = Some(capture::Capture::new(&self.device, extent, format)?);
self.capture_requested = false;
}
let aspect = extent.width as f32 / extent.height as f32;
let projection = glam::camera::rh::proj::directx::perspective_infinite_reverse(
params.fov_y_degrees.to_radians(),
aspect,
0.05,
);
let view_projection = projection * params.view;
let terrain_active = world.is_some() && self.terrain.order_len() > 0;
self.translucent.clear();
if terrain_active {
let slots = &mut self.translucent;
self.terrain.translucent(params.eye, view_projection, slots);
}
// Grown to the frame's sections; this slot's previous frame has finished with it
let translucent_bytes = size_of_val(self.translucent.as_slice()) as u64;
if translucent_bytes > frame.translucent.size() {
frame.translucent = Buffer::new(
&self.device,
translucent_bytes.next_power_of_two(),
VkBufferUsageFlags2::SHADER_DEVICE_ADDRESS,
MemoryLocation::CpuToGpu,
)?;
}
frame
.translucent
.write(0, bytemuck::cast_slice(&self.translucent))?;
let translucent_count = self.translucent.len() as u32;
let translucent = frame.translucent.address();
let cull_push = self
.terrain
.cull_push(params.eye, camera, view_projection, translucent);
let (eye_block, eye_fraction) = terrain::split_eye(params.eye);
let camera = camera.extend(0.0).to_array();
if world.is_some() && params.has_sky {
self.sky.build(&SkyParams {
sky_color: params.sky_color,
celestial_angle: params.celestial_angle,
sunrise: params.sunrise,
sun_texture: SUN_SLOT,
moon_texture: MOON_SLOT,
clear: 1.0 - params.rain,
});
} else {
self.sky.clear();
}
let sky_count = write_prefix(&mut frame.sky, &self.sky.vertices, SKY_VERTICES)?;
let cmd = frame.command_buffer;
let gui_count = write_prefix(&mut frame.gui, ¶ms.gui.vertices, GUI_VERTICES)? as u32;
// Grown to the frame's geometry; this slot's previous frame has finished with it
let scene_bytes = size_of_val(params.scene.vertices.as_slice()) as u64;
if scene_bytes > frame.scene.size() {
frame.scene = Buffer::new(
&self.device,
scene_bytes.next_power_of_two(),
VkBufferUsageFlags2::SHADER_DEVICE_ADDRESS,
MemoryLocation::CpuToGpu,
)?;
}
let scene_count = write_prefix(&mut frame.scene, ¶ms.scene.vertices, usize::MAX)?;
if params.scene.map.is_some() {
let bytes = scene_count * size_of::<scene::Vertex>();
let vertices: &mut [scene::Vertex] =
bytemuck::cast_slice_mut(&mut frame.scene.mapped()[..bytes]);
for vertex in vertices {
if vertex.texture == -2 {
vertex.texture = (self.textures.len() + slot) as i32;
}
}
frame.scene.flush(0, bytes)?;
}
let held_map = world.and_then(|world| params.scene.map.and_then(|id| world.maps.get(&id)));
let map_pixels = if let Some(map) = held_map {
if frame.map_colors.as_deref() == Some(&*map.colors) {
None
} else {
Some(map.rgba())
}
} else if params.scene.map.is_some() && frame.map_colors.is_some() {
Some(blank_map().pixels)
} else {
None
};
let map_copy = map_pixels
.and_then(|pixels| frame.staging.push(&pixels))
.map(|offset| image_copy(offset, [0, 0], 128, 128));
if map_copy.is_some() {
frame.map_colors = held_map.map(|map| map.colors.clone());
}
frame.staging.flush()?;
let begin = VkCommandBufferBeginInfo {
sType: VkStructureType::CommandBufferBeginInfo,
flags: VkCommandBufferUsageFlags::ONE_TIME_SUBMIT,
..zeroed()
};
check(unsafe { commands.vkBeginCommandBuffer(cmd, &begin) })?;
let staging = frame.staging.handle();
unsafe {
let device = &*self.device;
if let Some(copy) = map_copy {
let map = frame.map_image.handle();
let shader_read = (Stage::FRAGMENT_SHADER, Access::SHADER_READ);
let general = (Layout::General, Layout::General);
image_barrier(device, cmd, map, COLOR, general, shader_read, COPY_WRITE);
commands.vkCmdCopyBufferToImage(cmd, staging, map, Layout::General, 1, ©);
}
for (buffer, copy) in &terrain_copies.meshes {
commands.vkCmdCopyBuffer(cmd, staging, *buffer, 1, copy);
}
// The previous frame's culling and box test are done with the tables
memory_barrier(device, cmd, CULLING, TRANSFER_WRITE);
let (sections, order, work) = self.terrain.tables();
for (target, copies) in [
(sections, &terrain_copies.sections),
(order, &terrain_copies.order),
] {
if !copies.is_empty() {
let count = copies.len() as u32;
commands.vkCmdCopyBuffer(cmd, staging, target, count, copies.as_ptr());
}
}
let header = terrain::HEADER;
let header_size = terrain::HEADER_SIZE;
commands.vkCmdUpdateBuffer(cmd, work, 0, header_size, header.as_ptr().cast());
if tile_copies.iter().any(|copies| !copies.is_empty()) {
let fragment = (Stage::FRAGMENT_SHADER, Access::NONE);
memory_barrier(device, cmd, fragment, COPY_WRITE);
}
let items = &self.textures[crate::gui::ITEMS as usize];
for (image, copies) in [&self.textures[ATLAS_SLOT as usize], items]
.iter()
.zip(&tile_copies)
{
if !copies.is_empty() {
commands.vkCmdCopyBufferToImage(
cmd,
staging,
image.handle(),
Layout::General,
copies.len() as u32,
copies.as_ptr(),
);
}
}
memory_barrier(device, cmd, TRANSFER_WRITE, CULLING);
let descriptors = &self.descriptors;
descriptors.bind(cmd);
let pipelines = &self.pipelines;
let cull = |mode: CullMode, count: u32| {
if count == 0 {
return;
}
let push = cull_push.pass(mode, count);
let pipeline = pipelines.cull.handle();
commands.vkCmdBindPipeline(cmd, VkPipelineBindPoint::Compute, pipeline);
descriptors.push(cmd, bytemuck::bytes_of(&push));
commands.vkCmdDispatch(cmd, count.div_ceil(64), 1, 1);
};
let order_len = self.terrain.order_len();
if terrain_active {
cull(CullMode::Previous, order_len);
memory_barrier(device, cmd, COMPUTE_WRITE, CULLING);
}
let (target, depth) = (image.image, self.depth.handle());
let layouts = (Layout::Undefined, Layout::ColorAttachmentOptimal);
let output = (Stage::COLOR_ATTACHMENT_OUTPUT, Access::NONE);
image_barrier(device, cmd, target, COLOR, layouts, output, COLOR_WRITE);
let tests = Stage::EARLY_FRAGMENT_TESTS | Stage::LATE_FRAGMENT_TESTS;
let write = Access::DEPTH_STENCIL_ATTACHMENT_WRITE;
let read_write = (tests, write | Access::DEPTH_STENCIL_ATTACHMENT_READ);
let layouts = (Layout::Undefined, Layout::DepthAttachmentOptimal);
let aspect = VkImageAspectFlags::DEPTH;
image_barrier(
device,
cmd,
depth,
aspect,
layouts,
(tests, write),
read_write,
);
let clear_depth = VkClearValue {
depthStencil: VkClearDepthStencilValue {
depth: 0.0,
stencil: 0,
},
};
let color_attachment = VkRenderingAttachmentInfo {
sType: VkStructureType::RenderingAttachmentInfo,
imageView: image.view,
imageLayout: Layout::ColorAttachmentOptimal,
loadOp: VkAttachmentLoadOp::Clear,
storeOp: VkAttachmentStoreOp::Store,
clearValue: VkClearValue {
color: VkClearColorValue {
float32: params.fog_color.extend(1.0).to_array(),
},
},
..zeroed()
};
let depth_attachment = VkRenderingAttachmentInfo {
sType: VkStructureType::RenderingAttachmentInfo,
imageView: self.depth.view(),
imageLayout: Layout::DepthAttachmentOptimal,
loadOp: VkAttachmentLoadOp::Clear,
// Kept between the culling passes
storeOp: if terrain_active {
VkAttachmentStoreOp::Store
} else {
VkAttachmentStoreOp::DontCare
},
clearValue: clear_depth,
..zeroed()
};
let scissor = VkRect2D {
offset: VkOffset2D { x: 0, y: 0 },
extent,
};
let rendering = VkRenderingInfo {
sType: VkStructureType::RenderingInfo,
renderArea: scissor,
layerCount: 1,
colorAttachmentCount: 1,
pColorAttachments: &color_attachment,
pDepthAttachment: &depth_attachment,
..zeroed()
};
commands.vkCmdBeginRendering(cmd, &rendering);
let viewport = VkViewport {
x: 0.0,
y: extent.height as f32,
width: extent.width as f32,
height: -(extent.height as f32),
minDepth: 0.0,
maxDepth: 1.0,
};
commands.vkCmdSetViewport(cmd, 0, 1, &viewport);
commands.vkCmdSetScissor(cmd, 0, 1, &scissor);
let fog_color = params.fog_color.extend(1.0).to_array();
let fog_start = if params.has_sky {
params.view_distance * 0.25
} else {
0.0
};
for batch in &self.sky.batches {
let (pipeline, fog, texture) = match batch.pass {
SkyPass::Plane => (&pipelines.sky_plane, 1, -1),
SkyPass::Sunrise => (&pipelines.sky_alpha, 0, -1),
SkyPass::Body(slot) => (&pipelines.sky_additive, 0, slot as i32),
SkyPass::Stars => (&pipelines.sky_additive, 0, -1),
};
if batch.first + batch.count > sky_count as u32 {
continue;
}
bind(
device,
descriptors,
cmd,
pipeline,
&SkyPush {
view_projection: view_projection.to_cols_array(),
vertices: frame.sky.address(),
_padding: 0,
fog_color,
camera,
fog_start: params.fog_density.map_or(0.0, |density| -density),
fog_end: params.view_distance * 0.8,
fog,
texture,
},
);
commands.vkCmdDraw(cmd, batch.count, 1, batch.first, 0);
}
let fog_start = params.fog_density.map_or(fog_start, |density| -density);
let terrain_push = TerrainPush {
view_projection: view_projection.to_cols_array(),
fog_color,
camera,
eye_fraction,
eye_block,
sections: self.terrain.sections_address(),
fog_start,
fog_end: params.view_distance,
sky_darkness: params.sky_darkness,
texture: ATLAS_SLOT,
opacity: 1.0,
_padding: 0,
brightness: params.brightness,
};
let stride = size_of::<VkDrawIndexedIndirectCommand>() as u32;
let draw_opaque = || {
if !terrain_active {
return;
}
let index_type = VkIndexType::UINT32;
commands.vkCmdBindIndexBuffer(cmd, self.terrain.index_buffer(), 0, index_type);
for (layer, pipeline) in [&pipelines.terrain_solid, &pipelines.terrain_cutout]
.into_iter()
.enumerate()
{
bind(device, descriptors, cmd, pipeline, &terrain_push);
let (offset, max) = self.terrain.commands(layer as u64);
let count = layer as u64 * 4;
commands
.vkCmdDrawIndexedIndirectCount(cmd, work, offset, work, count, max, stride);
}
};
draw_opaque();
if terrain_active {
let boxes = BoxPush {
view_projection: view_projection.to_cols_array(),
eye_fraction,
eye_block,
sections: self.terrain.sections_address(),
tested: self.terrain.tested_address(),
};
bind(device, descriptors, cmd, &pipelines.section_boxes, &boxes);
commands.vkCmdDrawIndirect(cmd, work, terrain::BOX_DRAW_OFFSET, 1, 16);
commands.vkCmdEndRendering(cmd);
// Draw what the boxes revealed, then fill the translucent draws
memory_barrier(device, cmd, ATTACHMENTS, ATTACHMENTS);
memory_barrier(device, cmd, CULLING, TRANSFER_WRITE);
commands.vkCmdFillBuffer(cmd, work, 0, 8, 0);
memory_barrier(device, cmd, TRANSFER_WRITE, CULLING);
cull(CullMode::Revealed, order_len);
memory_barrier(device, cmd, COMPUTE_WRITE, CULLING);
cull(CullMode::Translucent, translucent_count);
memory_barrier(device, cmd, COMPUTE_WRITE, CULLING);
let drawn = VkBufferCopy {
srcOffset: terrain::DRAWN_OFFSET,
dstOffset: 0,
size: size_of::<u32>() as u64,
};
commands.vkCmdCopyBuffer(cmd, work, frame.drawn.handle(), 1, &drawn);
memory_barrier(
device,
cmd,
TRANSFER_WRITE,
(Stage::HOST, Access::HOST_READ),
);
let load = |attachment: &VkRenderingAttachmentInfo| VkRenderingAttachmentInfo {
loadOp: VkAttachmentLoadOp::Load,
storeOp: VkAttachmentStoreOp::DontCare,
..*attachment
};
let color_attachment = VkRenderingAttachmentInfo {
storeOp: VkAttachmentStoreOp::Store,
..load(&color_attachment)
};
let depth_attachment = load(&depth_attachment);
let rendering = VkRenderingInfo {
pColorAttachments: &color_attachment,
pDepthAttachment: &depth_attachment,
..rendering
};
commands.vkCmdBeginRendering(cmd, &rendering);
draw_opaque();
}
let draw_translucent = |opacity| {
if translucent_count == 0 {
return;
}
let push = TerrainPush {
opacity,
..terrain_push
};
bind(
device,
descriptors,
cmd,
&pipelines.terrain_translucent,
&push,
);
let offset = self.terrain.translucent_offset();
commands.vkCmdDrawIndexedIndirect(cmd, work, offset, translucent_count, stride);
};
let scene = params.scene;
let fogged = |alpha_cutoff| ScenePush {
view_projection: view_projection.to_cols_array(),
vertices: frame.scene.address(),
fog: 1,
alpha_cutoff,
fog_color,
camera,
fog_start,
fog_end: params.view_distance,
opacity: 1.0,
_padding: 0,
};
let unfogged = |view_projection: Mat4, camera, alpha_cutoff| ScenePush {
view_projection: view_projection.to_cols_array(),
vertices: frame.scene.address(),
fog: 0,
alpha_cutoff,
fog_color,
camera,
fog_start: 0.0,
fog_end: 0.0,
opacity: 1.0,
_padding: 0,
};
let draw =
|range: &Range<u32>| commands.vkCmdDraw(cmd, range.len() as u32, 1, range.start, 0);
let draw_scene = |pipeline: &GraphicsPipeline, range: &Range<u32>, push: ScenePush| {
let valid = !range.is_empty() && range.end <= scene_count as u32;
if valid {
bind(device, descriptors, cmd, pipeline, &push);
draw(range);
}
valid
};
draw_scene(&pipelines.scene, &scene.objects, fogged(0.1));
draw_scene(&pipelines.lines, &scene.lines, fogged(0.0));
draw_scene(&pipelines.scene, &scene.moving, fogged(0.1));
draw_scene(&pipelines.scene, &scene.signs, fogged(0.1));
draw_scene(&pipelines.scene_additive, &scene.flashes, fogged(0.0));
draw_scene(&pipelines.outline, &scene.shadows, fogged(0.1));
draw_scene(&pipelines.scene_additive, &scene.bolts, fogged(0.0));
if self.fancy {
draw_translucent(0.0);
}
draw_translucent(1.0);
let names = unfogged(view_projection, camera, 0.0);
draw_scene(&pipelines.nameplate, &scene.names_hidden, names);
draw_scene(&pipelines.outline, &scene.names, names);
draw_scene(&pipelines.outline, &scene.weather, fogged(0.01));
if scene.fancy_clouds {
let depth_only = ScenePush {
opacity: 0.0,
..fogged(0.1)
};
draw_scene(&pipelines.scene, &scene.clouds, depth_only);
draw_scene(&pipelines.scene, &scene.clouds, fogged(0.1));
} else {
draw_scene(&pipelines.outline, &scene.clouds, fogged(0.1));
}
draw_scene(&pipelines.outline, &scene.outline, fogged(0.1));
draw_scene(&pipelines.cracks, &scene.cracks, fogged(0.1));
let depth_range = Mat4::from_cols(
Vec4::X,
Vec4::Y,
Vec4::new(0.0, 0.0, 0.1, 0.0),
Vec4::new(0.0, 0.0, 0.9, 1.0),
);
let cutoff = if scene.map.is_some() { 0.0 } else { 0.1 };
let hand = unfogged(depth_range * projection, [0.0; 4], cutoff);
if draw_scene(&pipelines.scene, &scene.hand, hand)
&& !scene.map_text.is_empty()
&& scene.map_text.end <= scene_count as u32
{
draw(&scene.map_text);
}
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
struct GuiPush {
vertices: u64,
size: [f32; 2],
}
let gui_push = GuiPush {
vertices: frame.gui.address(),
size: params.gui.projection,
};
let draw_gui_with = |pipeline: &GraphicsPipeline, range: Range<u32>| {
if !range.is_empty() {
bind(device, descriptors, cmd, pipeline, &gui_push);
draw(&range);
}
};
// The crosshair keeps its place in the drawing order with its own blend
let crosshair =
params.gui.crosshair.start.min(gui_count)..params.gui.crosshair.end.min(gui_count);
let draw_gui = |range: Range<u32>| {
if crosshair.is_empty()
|| crosshair.start < range.start
|| crosshair.end > range.end
{
return draw_gui_with(&pipelines.gui, range);
}
draw_gui_with(&pipelines.gui, range.start..crosshair.start);
draw_gui_with(&pipelines.gui_invert, crosshair.clone());
draw_gui_with(&pipelines.gui, crosshair.end..range.end);
};
let portrait = params
.gui
.portrait
.filter(|_| !scene.portrait.is_empty() && scene.portrait.end <= scene_count as u32);
let split = portrait.map_or(gui_count, |portrait| portrait.split.min(gui_count));
draw_gui(0..split);
if portrait.is_some() {
let clear = VkClearAttachment {
aspectMask: VkImageAspectFlags::DEPTH,
colorAttachment: 0,
clearValue: clear_depth,
};
let rect = VkClearRect {
rect: scissor,
baseArrayLayer: 0,
layerCount: 1,
};
commands.vkCmdClearAttachments(cmd, 1, &clear, 1, &rect);
let projection = scene::portrait_projection(params.gui.projection);
let portrait = unfogged(projection, [0.0; 4], 0.1);
draw_scene(&pipelines.scene, &scene.portrait, portrait);
}
draw_gui(split..gui_count);
commands.vkCmdEndRendering(cmd);
if let Some(capture) = &frame.capture {
capture.record(device, cmd, target);
} else {
let layouts = (Layout::ColorAttachmentOptimal, Layout::PresentSrcKhr);
image_barrier(device, cmd, target, COLOR, layouts, COLOR_WRITE, NO_ACCESS);
}
}
check(unsafe { commands.vkEndCommandBuffer(cmd) })?;
let wait = VkSemaphoreSubmitInfo {
sType: VkStructureType::SemaphoreSubmitInfo,
semaphore: frame.image_available,
stageMask: Stage::COLOR_ATTACHMENT_OUTPUT,
..zeroed()
};
let signal = VkSemaphoreSubmitInfo {
sType: VkStructureType::SemaphoreSubmitInfo,
semaphore: image.render_finished,
stageMask: Stage::ALL_COMMANDS,
..zeroed()
};
let command_info = VkCommandBufferSubmitInfo {
sType: VkStructureType::CommandBufferSubmitInfo,
commandBuffer: cmd,
..zeroed()
};
let submit = VkSubmitInfo2 {
sType: VkStructureType::SubmitInfo2,
waitSemaphoreInfoCount: 1,
pWaitSemaphoreInfos: &wait,
commandBufferInfoCount: 1,
pCommandBufferInfos: &command_info,
signalSemaphoreInfoCount: 1,
pSignalSemaphoreInfos: &signal,
..zeroed()
};
check(unsafe { commands.vkResetFences(device, 1, &frame.fence) })?;
check(unsafe { commands.vkQueueSubmit2(self.device.queue, 1, &submit, frame.fence) })?;
if !self.swapchain.present(&image)? {
self.resize = Some(self.swapchain.extent());
}
self.frame += 1;
Ok(())
}
}
impl Drop for Renderer {
fn drop(&mut self) {
let _ = self.device.wait_idle();
let commands = &self.device.commands;
let device = self.device.handle;
for frame in &self.frames {
unsafe {
commands.vkDestroyFence(device, frame.fence, null());
commands.vkDestroySemaphore(device, frame.image_available, null());
commands.vkDestroyCommandPool(device, frame.pool, null());
}
}
}
}
/// Binds a graphics pipeline and pushes its data.
fn bind<T: Pod>(
device: &Device,
descriptors: &Descriptors,
cmd: VkCommandBuffer,
pipeline: &GraphicsPipeline,
data: &T,
) {
unsafe {
let commands = &device.commands;
commands.vkCmdBindPipeline(cmd, VkPipelineBindPoint::Graphics, pipeline.handle());
descriptors.push(cmd, bytemuck::bytes_of(data));
}
}
/// Writes up to `max` items to the start of `buffer` and returns how many.
fn write_prefix<T: Pod>(buffer: &mut Buffer, items: &[T], max: usize) -> Result<usize> {
let count = items.len().min(max);
buffer.write(0, bytemuck::cast_slice(&items[..count]))?;
Ok(count)
}
/// A copy between tightly packed pixels and a region of a color image.
fn image_copy(offset: u64, [x, y]: [i32; 2], width: u32, height: u32) -> VkBufferImageCopy {
VkBufferImageCopy {
bufferOffset: offset,
imageSubresource: VkImageSubresourceLayers {
aspectMask: COLOR,
layerCount: 1,
..zeroed()
},
imageOffset: VkOffset3D { x, y, z: 0 },
imageExtent: VkExtent3D {
width,
height,
depth: 1,
},
..zeroed()
}
}
fn memory_barrier(
device: &Device,
cmd: VkCommandBuffer,
(src_stage, src_access): Scope,
(dst_stage, dst_access): Scope,
) {
let barrier = VkMemoryBarrier2 {
sType: VkStructureType::MemoryBarrier2,
srcStageMask: src_stage,
srcAccessMask: src_access,
dstStageMask: dst_stage,
dstAccessMask: dst_access,
..zeroed()
};
let dependency = VkDependencyInfo {
sType: VkStructureType::DependencyInfo,
memoryBarrierCount: 1,
pMemoryBarriers: &barrier,
..zeroed()
};
unsafe { device.commands.vkCmdPipelineBarrier2(cmd, &dependency) };
}
fn image_barrier(
device: &Device,
cmd: VkCommandBuffer,
image: VkImage,
aspect: VkImageAspectFlags,
(old_layout, new_layout): (Layout, Layout),
(src_stage, src_access): Scope,
(dst_stage, dst_access): Scope,
) {
let barrier = VkImageMemoryBarrier2 {
sType: VkStructureType::ImageMemoryBarrier2,
srcStageMask: src_stage,
srcAccessMask: src_access,
dstStageMask: dst_stage,
dstAccessMask: dst_access,
oldLayout: old_layout,
newLayout: new_layout,
srcQueueFamilyIndex: VK_QUEUE_FAMILY_IGNORED,
dstQueueFamilyIndex: VK_QUEUE_FAMILY_IGNORED,
image,
subresourceRange: subresource_range(aspect),
..zeroed()
};
let dependency = VkDependencyInfo {
sType: VkStructureType::DependencyInfo,
imageMemoryBarrierCount: 1,
pImageMemoryBarriers: &barrier,
..zeroed()
};
unsafe { device.commands.vkCmdPipelineBarrier2(cmd, &dependency) };
}
/// Uploads the atlas, sun, moon and GUI textures.
fn load_textures(device: &Arc<Device>, assets: &Assets) -> Result<Vec<Image>> {
[&assets.terrain, &assets.sun, &assets.moon]
.into_iter()
.chain(&assets.gui)
.chain(&assets.entities)
.chain(&assets.armor)
.chain(&assets.menu)
.map(|image| upload_texture(device, image))
.collect()
}
/// Describes the textures, then each frame's map, at slots matching their indices.
fn describe(
device: &Arc<Device>,
heaps: bool,
textures: &[Image],
frames: &[FrameResources],
) -> Result<Descriptors> {
let images = textures
.iter()
.chain(frames.iter().map(|frame| &frame.map_image));
let mut descriptors =
Descriptors::new(device, heaps, (textures.len() + frames.len()) as u32, 2)?;
for (slot, image) in images.enumerate() {
descriptors.write_image(slot as u32, image, Layout::General)?;
}
write_samplers(&mut descriptors)?;
Ok(descriptors)
}
fn blank_map() -> RgbaImage {
RgbaImage {
width: 128,
height: 128,
pixels: common::map::Map::new([0, 0], 0).rgba(),
}
}
fn create_depth(device: &Arc<Device>, extent: VkExtent2D) -> Result<Image> {
Image::new(
device,
extent,
DEPTH_FORMAT,
VkImageUsageFlags::DEPTH_STENCIL_ATTACHMENT,
VkImageAspectFlags::DEPTH,
)
}
/// Creates a sampled RGBA texture in the `GENERAL` layout, so tiles can be updated in place.
fn upload_texture(device: &Arc<Device>, image: &RgbaImage) -> Result<Image> {
let texture = Image::new(
device,
VkExtent2D {
width: image.width,
height: image.height,
},
VkFormat::R8G8B8A8Unorm,
VkImageUsageFlags::SAMPLED | VkImageUsageFlags::TRANSFER_DST,
VkImageAspectFlags::COLOR,
)?;
let mut staging = Buffer::new(
device,
image.pixels.len() as u64,
VkBufferUsageFlags2::TRANSFER_SRC,
MemoryLocation::CpuToGpu,
)?;
staging.write(0, &image.pixels)?;
device.submit_now(|cmd| {
let layouts = (Layout::Undefined, Layout::General);
image_barrier(
device,
cmd,
texture.handle(),
COLOR,
layouts,
NO_ACCESS,
COPY_WRITE,
);
let region = image_copy(0, [0, 0], image.width, image.height);
unsafe {
device.commands.vkCmdCopyBufferToImage(
cmd,
staging.handle(),
texture.handle(),
Layout::General,
1,
®ion,
);
}
})?;
Ok(texture)
}
/// Sampler 0 keeps texels sharp and repeats; sampler 1 smooths and clamps, like the original's
/// `%blur%` textures.
fn write_samplers(descriptors: &mut Descriptors) -> Result<()> {
let sampler = |filter, address| VkSamplerCreateInfo {
sType: VkStructureType::SamplerCreateInfo,
magFilter: filter,
minFilter: filter,
mipmapMode: VkSamplerMipmapMode::Nearest,
addressModeU: address,
addressModeV: address,
addressModeW: address,
..zeroed()
};
descriptors.write_sampler(0, &sampler(VkFilter::Nearest, VkSamplerAddressMode::Repeat))?;
descriptors.write_sampler(
1,
&sampler(VkFilter::Linear, VkSamplerAddressMode::ClampToEdge),
)
}