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 gpu pipeline.rs
use std::ffi::{CStr, c_void};
use std::ptr::{self, null};
use std::sync::Arc;
use volcanic::*;
use super::{Binding, Device, Error, Result, create, zeroed};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Blend {
Opaque,
Alpha,
Additive,
Multiply,
/// `GL_ONE_MINUS_DST_COLOR`, `GL_ONE_MINUS_SRC_COLOR`.
Invert,
}
/// Fixed-function state for a graphics pipeline that pulls its vertices from buffer addresses.
///
/// Shaders see sampled images at `[[vk::binding(0, 0)]]` and samplers at `[[vk::binding(1, 0)]]`,
/// both as unbounded arrays mapped onto the descriptor heaps or in the descriptor set.
pub struct PipelineDesc<'a> {
pub spirv: &'a [u8],
pub vertex_entry: &'a CStr,
pub fragment_entry: &'a CStr,
pub color_format: VkFormat,
pub depth_format: Option<VkFormat>,
pub depth_test: bool,
pub topology: VkPrimitiveTopology,
pub depth_write: bool,
pub cull: bool,
pub blend: Blend,
/// Without it, fragments only test depth and run their side effects.
pub color_write: bool,
pub binding: Binding,
}
pub struct GraphicsPipeline {
handle: VkPipeline,
device: Arc<Device>,
}
impl GraphicsPipeline {
pub fn new(device: &Arc<Device>, desc: &PipelineDesc) -> Result<Self> {
with_shader(desc.spirv, desc.binding, |shader, layout, flags| {
Self::create(device, desc, shader, layout, flags)
})?
}
fn create(
device: &Arc<Device>,
desc: &PipelineDesc,
shader: *const c_void,
layout: VkPipelineLayout,
pipeline_flags: VkPipelineCreateFlags2,
) -> Result<Self> {
let stage = |stage, entry: &CStr| VkPipelineShaderStageCreateInfo {
sType: VkStructureType::PipelineShaderStageCreateInfo,
pNext: shader,
stage,
pName: entry.as_ptr(),
..zeroed()
};
let stages = [
stage(VkShaderStageFlags::VERTEX, desc.vertex_entry),
stage(VkShaderStageFlags::FRAGMENT, desc.fragment_entry),
];
let vertex_input = VkPipelineVertexInputStateCreateInfo {
sType: VkStructureType::PipelineVertexInputStateCreateInfo,
..zeroed()
};
let input_assembly = VkPipelineInputAssemblyStateCreateInfo {
sType: VkStructureType::PipelineInputAssemblyStateCreateInfo,
topology: desc.topology,
..zeroed()
};
let viewport = VkPipelineViewportStateCreateInfo {
sType: VkStructureType::PipelineViewportStateCreateInfo,
viewportCount: 1,
scissorCount: 1,
..zeroed()
};
let rasterization = VkPipelineRasterizationStateCreateInfo {
sType: VkStructureType::PipelineRasterizationStateCreateInfo,
polygonMode: VkPolygonMode::Fill,
cullMode: if desc.cull {
VkCullModeFlags::BACK
} else {
VkCullModeFlags::NONE
},
frontFace: VkFrontFace::CounterClockwise,
lineWidth: 1.0,
..zeroed()
};
let multisample = VkPipelineMultisampleStateCreateInfo {
sType: VkStructureType::PipelineMultisampleStateCreateInfo,
rasterizationSamples: VkSampleCountFlags::_1,
..zeroed()
};
let depth_stencil = VkPipelineDepthStencilStateCreateInfo {
sType: VkStructureType::PipelineDepthStencilStateCreateInfo,
depthTestEnable: VkBool32::from(desc.depth_test),
depthWriteEnable: VkBool32::from(desc.depth_write),
depthCompareOp: VkCompareOp::GreaterOrEqual,
..zeroed()
};
let blend_attachment = VkPipelineColorBlendAttachmentState {
blendEnable: VkBool32::from(desc.blend != Blend::Opaque),
srcColorBlendFactor: match desc.blend {
Blend::Multiply => VkBlendFactor::DstColor,
Blend::Invert => VkBlendFactor::OneMinusDstColor,
_ => VkBlendFactor::SrcAlpha,
},
dstColorBlendFactor: match desc.blend {
Blend::Additive => VkBlendFactor::One,
Blend::Multiply => VkBlendFactor::SrcColor,
Blend::Invert => VkBlendFactor::OneMinusSrcColor,
_ => VkBlendFactor::OneMinusSrcAlpha,
},
colorBlendOp: VkBlendOp::Add,
srcAlphaBlendFactor: VkBlendFactor::One,
dstAlphaBlendFactor: VkBlendFactor::OneMinusSrcAlpha,
alphaBlendOp: VkBlendOp::Add,
// Alpha keeps the clear value of 1, so the window is opaque in every composite mode
colorWriteMask: if desc.color_write {
VkColorComponentFlags::R | VkColorComponentFlags::G | VkColorComponentFlags::B
} else {
VkColorComponentFlags::empty()
},
};
let color_blend = VkPipelineColorBlendStateCreateInfo {
sType: VkStructureType::PipelineColorBlendStateCreateInfo,
attachmentCount: 1,
pAttachments: &blend_attachment,
..zeroed()
};
let dynamic_states = [VkDynamicState::Viewport, VkDynamicState::Scissor];
let dynamic = VkPipelineDynamicStateCreateInfo {
sType: VkStructureType::PipelineDynamicStateCreateInfo,
dynamicStateCount: dynamic_states.len() as u32,
pDynamicStates: dynamic_states.as_ptr(),
..zeroed()
};
let rendering = VkPipelineRenderingCreateInfo {
sType: VkStructureType::PipelineRenderingCreateInfo,
colorAttachmentCount: 1,
pColorAttachmentFormats: &desc.color_format,
depthAttachmentFormat: desc.depth_format.unwrap_or(VkFormat::Undefined),
..zeroed()
};
let flags = VkPipelineCreateFlags2CreateInfo {
sType: VkStructureType::PipelineCreateFlags2CreateInfo,
pNext: (&raw const rendering).cast(),
flags: pipeline_flags,
};
let info = VkGraphicsPipelineCreateInfo {
sType: VkStructureType::GraphicsPipelineCreateInfo,
pNext: (&raw const flags).cast(),
stageCount: stages.len() as u32,
pStages: stages.as_ptr(),
pVertexInputState: &vertex_input,
pInputAssemblyState: &input_assembly,
pViewportState: &viewport,
pRasterizationState: &rasterization,
pMultisampleState: &multisample,
pDepthStencilState: &depth_stencil,
pColorBlendState: &color_blend,
pDynamicState: &dynamic,
layout,
..zeroed()
};
let commands = &device.commands;
let handle = create(|handle| unsafe {
commands.vkCreateGraphicsPipelines(
device.handle,
VK_NULL_HANDLE,
1,
&info,
null(),
handle,
)
})?;
Ok(Self {
handle,
device: device.clone(),
})
}
pub fn handle(&self) -> VkPipeline {
self.handle
}
}
impl Drop for GraphicsPipeline {
fn drop(&mut self) {
let commands = &self.device.commands;
unsafe { commands.vkDestroyPipeline(self.device.handle, self.handle, null()) };
}
}
/// A compute pipeline that reaches its data through push constants and buffer addresses.
pub struct ComputePipeline {
handle: VkPipeline,
device: Arc<Device>,
}
impl ComputePipeline {
pub fn new(device: &Arc<Device>, spirv: &[u8], entry: &CStr, binding: Binding) -> Result<Self> {
let handle = with_shader(spirv, binding, |shader, layout, flags| {
let flags = VkPipelineCreateFlags2CreateInfo {
sType: VkStructureType::PipelineCreateFlags2CreateInfo,
flags,
..zeroed()
};
let info = VkComputePipelineCreateInfo {
sType: VkStructureType::ComputePipelineCreateInfo,
pNext: (&raw const flags).cast(),
stage: VkPipelineShaderStageCreateInfo {
sType: VkStructureType::PipelineShaderStageCreateInfo,
pNext: shader,
stage: VkShaderStageFlags::COMPUTE,
pName: entry.as_ptr(),
..zeroed()
},
layout,
..zeroed()
};
let commands = &device.commands;
create(|handle| unsafe {
commands.vkCreateComputePipelines(
device.handle,
VK_NULL_HANDLE,
1,
&info,
null(),
handle,
)
})
})??;
Ok(Self {
handle,
device: device.clone(),
})
}
pub fn handle(&self) -> VkPipeline {
self.handle
}
}
impl Drop for ComputePipeline {
fn drop(&mut self) {
let commands = &self.device.commands;
unsafe { commands.vkDestroyPipeline(self.device.handle, self.handle, null()) };
}
}
/// Calls `create` with the `pNext` chain of a shader stage for `spirv`, which maps the shared
/// bindings onto the descriptor heaps with a heap binding, and the pipeline layout and flags.
fn with_shader<R>(
spirv: &[u8],
binding: Binding,
create: impl FnOnce(*const c_void, VkPipelineLayout, VkPipelineCreateFlags2) -> R,
) -> Result<R> {
if !spirv.len().is_multiple_of(4) {
return Err(Error::InvalidSpirv(spirv.len()));
}
let code: Vec<u32> = spirv
.as_chunks::<4>()
.0
.iter()
.map(|&word| u32::from_le_bytes(word))
.collect();
let module = VkShaderModuleCreateInfo {
sType: VkStructureType::ShaderModuleCreateInfo,
codeSize: spirv.len(),
pCode: code.as_ptr(),
..zeroed()
};
let mapping = |binding, resource, stride| VkDescriptorSetAndBindingMappingEXT {
sType: VkStructureType::DescriptorSetAndBindingMappingExt,
firstBinding: binding,
bindingCount: 1,
resourceMask: resource,
source: VkDescriptorMappingSourceEXT::HeapWithConstantOffset,
sourceData: VkDescriptorMappingSourceDataEXT {
constantOffset: VkDescriptorMappingSourceConstantOffsetEXT {
heapArrayStride: stride,
..zeroed()
},
},
..zeroed()
};
let (heap, layout) = match binding {
Binding::Heap(heap) => (Some(heap), VK_NULL_HANDLE),
Binding::Layout(layout) => (None, layout),
};
let mappings = heap.map(|heap| {
[
mapping(
0,
VkSpirvResourceTypeFlagsEXT::SAMPLED_IMAGE_EXT,
heap.image_stride,
),
mapping(
1,
VkSpirvResourceTypeFlagsEXT::SAMPLER_EXT,
heap.sampler_stride,
),
]
});
let mapping_info =
mappings
.as_ref()
.map(|mappings| VkShaderDescriptorSetAndBindingMappingInfoEXT {
sType: VkStructureType::ShaderDescriptorSetAndBindingMappingInfoExt,
pNext: (&raw const module).cast(),
mappingCount: mappings.len() as u32,
pMappings: mappings.as_ptr(),
});
let shader: *const c_void = mapping_info
.as_ref()
.map_or((&raw const module).cast(), |info| {
ptr::from_ref(info).cast()
});
let flags = if heap.is_some() {
VkPipelineCreateFlags2::DESCRIPTOR_HEAP_EXT
} else {
VkPipelineCreateFlags2::empty()
};
Ok(create(shader, layout, flags))
}