Theme

alex/beta-minecraft-rust

Minecraft Beta 1.7.3 Rewrite

BRANCH main
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
Download file

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))
}