161 lines
6.3 KiB
Rust
161 lines
6.3 KiB
Rust
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
//! Graphics pipeline creation and shader management.
|
|
|
|
use crate::error::RendererError;
|
|
use crate::mesh::Vertex;
|
|
use ash::{Device, vk};
|
|
use std::io::Cursor;
|
|
|
|
/// Helper to load SPIR-V bytes and create a Vulkan Shader Module.
|
|
///
|
|
/// Vulkan expects shader code to be 32-bit aligned; `ash::util::read_spv` is used to correctly interpret the raw bytes as a slice of `u32`.
|
|
pub fn create_shader_module(
|
|
device: &Device,
|
|
bytes: &[u8],
|
|
) -> Result<vk::ShaderModule, RendererError> {
|
|
let mut cursor = Cursor::new(bytes);
|
|
|
|
let code = ash::util::read_spv(&mut cursor)?;
|
|
|
|
let create_info = vk::ShaderModuleCreateInfo::default().code(&code);
|
|
|
|
let module = unsafe { device.create_shader_module(&create_info, None)? };
|
|
Ok(module)
|
|
}
|
|
|
|
/// Defines the 'interface' of the pipeline (what data we can pass to the shaders).
|
|
///
|
|
/// This layout defines any push constants or descriptor sets (textures/UBOs) accessed by the shaders during execution.
|
|
pub fn create_pipeline_layout(device: &Device) -> Result<vk::PipelineLayout, RendererError> {
|
|
// A single push constant range is defined for the MVP matrix, allowing it to be updated for every draw call with high efficiency.
|
|
#[expect(clippy::expect_used)]
|
|
let push_constant_range = vk::PushConstantRange::default()
|
|
.stage_flags(vk::ShaderStageFlags::VERTEX)
|
|
.offset(0)
|
|
.size(
|
|
u32::try_from(std::mem::size_of::<glam::Mat4>()).expect("Mat4 size exceeds u32 range"),
|
|
);
|
|
|
|
let layout_create_info = vk::PipelineLayoutCreateInfo::default()
|
|
.push_constant_ranges(std::slice::from_ref(&push_constant_range));
|
|
|
|
let layout = unsafe { device.create_pipeline_layout(&layout_create_info, None)? };
|
|
Ok(layout)
|
|
}
|
|
|
|
/// Creates a Graphics Pipeline for voxel rendering using Vulkan 1.3 Dynamic Rendering.
|
|
///
|
|
/// The pipeline encapsulates the entire state of the GPU for a specific draw operation, including shader stages, vertex input layout, rasterization settings, and blending.
|
|
pub fn create_graphics_pipeline(
|
|
device: &Device,
|
|
layout: vk::PipelineLayout,
|
|
color_format: vk::Format,
|
|
) -> Result<vk::Pipeline, RendererError> {
|
|
// 1. Load and compile shader modules
|
|
let (vert_module, frag_module) = load_shader_modules(device)?;
|
|
let entry_point = std::ffi::CString::new("main").map_err(|_| RendererError::InvalidString)?;
|
|
|
|
let shader_stages = [
|
|
vk::PipelineShaderStageCreateInfo::default()
|
|
.stage(vk::ShaderStageFlags::VERTEX)
|
|
.module(vert_module)
|
|
.name(&entry_point),
|
|
vk::PipelineShaderStageCreateInfo::default()
|
|
.stage(vk::ShaderStageFlags::FRAGMENT)
|
|
.module(frag_module)
|
|
.name(&entry_point),
|
|
];
|
|
|
|
// 2. Configure Fixed-Function States
|
|
let binding_descriptions = [Vertex::get_binding_description()];
|
|
let attribute_descriptions = Vertex::get_attribute_descriptions();
|
|
let vertex_input_info = vk::PipelineVertexInputStateCreateInfo::default()
|
|
.vertex_binding_descriptions(&binding_descriptions)
|
|
.vertex_attribute_descriptions(&attribute_descriptions);
|
|
|
|
let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
|
|
.topology(vk::PrimitiveTopology::TRIANGLE_LIST)
|
|
.primitive_restart_enable(false);
|
|
|
|
let viewport_state = vk::PipelineViewportStateCreateInfo::default()
|
|
.viewport_count(1)
|
|
.scissor_count(1);
|
|
|
|
let rasterizer = vk::PipelineRasterizationStateCreateInfo::default()
|
|
.depth_clamp_enable(false)
|
|
.rasterizer_discard_enable(false)
|
|
.polygon_mode(vk::PolygonMode::FILL)
|
|
.line_width(1.0)
|
|
.cull_mode(vk::CullModeFlags::BACK)
|
|
.front_face(vk::FrontFace::COUNTER_CLOCKWISE)
|
|
.depth_bias_enable(false);
|
|
|
|
let multisampling = vk::PipelineMultisampleStateCreateInfo::default()
|
|
.sample_shading_enable(false)
|
|
.rasterization_samples(vk::SampleCountFlags::TYPE_1);
|
|
|
|
let color_blend_attachment = vk::PipelineColorBlendAttachmentState::default()
|
|
.color_write_mask(vk::ColorComponentFlags::RGBA)
|
|
.blend_enable(false);
|
|
|
|
let color_blending = vk::PipelineColorBlendStateCreateInfo::default()
|
|
.logic_op_enable(false)
|
|
.attachments(std::slice::from_ref(&color_blend_attachment));
|
|
|
|
let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
|
|
let dynamic_state_info =
|
|
vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);
|
|
|
|
let color_formats = [color_format];
|
|
let mut rendering_info = vk::PipelineRenderingCreateInfo::default()
|
|
.color_attachment_formats(&color_formats)
|
|
.depth_attachment_format(vk::Format::D32_SFLOAT);
|
|
|
|
let depth_stencil_state = &vk::PipelineDepthStencilStateCreateInfo::default()
|
|
.depth_test_enable(true)
|
|
.depth_write_enable(true)
|
|
.depth_compare_op(vk::CompareOp::LESS)
|
|
.depth_bounds_test_enable(false)
|
|
.stencil_test_enable(false);
|
|
|
|
// 3. Finalize Pipeline Creation
|
|
let pipeline_info = vk::GraphicsPipelineCreateInfo::default()
|
|
.push_next(&mut rendering_info)
|
|
.stages(&shader_stages)
|
|
.vertex_input_state(&vertex_input_info)
|
|
.input_assembly_state(&input_assembly)
|
|
.viewport_state(&viewport_state)
|
|
.rasterization_state(&rasterizer)
|
|
.multisample_state(&multisampling)
|
|
.color_blend_state(&color_blending)
|
|
.dynamic_state(&dynamic_state_info)
|
|
.layout(layout)
|
|
.depth_stencil_state(depth_stencil_state);
|
|
|
|
let result = unsafe {
|
|
device.create_graphics_pipelines(vk::PipelineCache::null(), &[pipeline_info], None)
|
|
};
|
|
|
|
unsafe {
|
|
device.destroy_shader_module(vert_module, None);
|
|
device.destroy_shader_module(frag_module, None);
|
|
}
|
|
|
|
let pipeline = result.map_err(|(_, e)| e)?[0];
|
|
Ok(pipeline)
|
|
}
|
|
|
|
/// Loads the vertex and fragment shader modules from embedded bytes.
|
|
fn load_shader_modules(
|
|
device: &Device,
|
|
) -> Result<(vk::ShaderModule, vk::ShaderModule), RendererError> {
|
|
let vert_bytes = include_bytes!("../../../assets/shaders/cube.vert.spv");
|
|
let frag_bytes = include_bytes!("../../../assets/shaders/cube.frag.spv");
|
|
|
|
let vert_module = create_shader_module(device, vert_bytes)?;
|
|
let frag_module = create_shader_module(device, frag_bytes)?;
|
|
|
|
Ok((vert_module, frag_module))
|
|
}
|