feat(renderer): add pipeline module for graphics pipeline creation
This commit is contained in:
parent
560b1e4d0f
commit
17d06a0423
152
crates/renderer/src/pipeline.rs
Normal file
152
crates/renderer/src/pipeline.rs
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
use ash::{vk, Device};
|
||||
use std::io::Cursor;
|
||||
use crate::mesh::Vertex;
|
||||
|
||||
/// 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]) -> vk::ShaderModule {
|
||||
let mut cursor = Cursor::new(bytes);
|
||||
|
||||
let code = ash::util::read_spv(&mut cursor)
|
||||
.expect("Failed to read SPIR-V binary; check if the file is valid");
|
||||
|
||||
let create_info = vk::ShaderModuleCreateInfo::default().code(&code);
|
||||
|
||||
unsafe {
|
||||
device.create_shader_module(&create_info, None)
|
||||
.expect("Failed to create Vulkan shader 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) -> vk::PipelineLayout {
|
||||
// A single push constant range is defined for the Model-View-Projection matrix.
|
||||
// This allows the matrix to be updated for every draw call with high efficiency.
|
||||
let push_constant_range = vk::PushConstantRange::default()
|
||||
.stage_flags(vk::ShaderStageFlags::VERTEX)
|
||||
.offset(0)
|
||||
.size(std::mem::size_of::<glam::Mat4>() as u32);
|
||||
|
||||
let layout_create_info = vk::PipelineLayoutCreateInfo::default()
|
||||
.push_constant_ranges(std::slice::from_ref(&push_constant_range));
|
||||
|
||||
unsafe {
|
||||
device.create_pipeline_layout(&layout_create_info, None)
|
||||
.expect("Failed to create pipeline 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
|
||||
) -> vk::Pipeline {
|
||||
// 1. Load and compile shader modules
|
||||
// Using include_bytes! embeds the shaders directly into the engine binary.
|
||||
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);
|
||||
|
||||
let entry_point = std::ffi::CString::new("main").unwrap();
|
||||
|
||||
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 Vertex Input
|
||||
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);
|
||||
|
||||
// 3. Configure Input Assembly (Drawing mode)
|
||||
let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
|
||||
.topology(vk::PrimitiveTopology::TRIANGLE_LIST)
|
||||
.primitive_restart_enable(false);
|
||||
|
||||
// 4. Viewport and Scissor (Static declarations, dynamic values set at runtime)
|
||||
let viewport_state = vk::PipelineViewportStateCreateInfo::default()
|
||||
.viewport_count(1)
|
||||
.scissor_count(1);
|
||||
|
||||
// 5. Configure Rasterizer (Triangles to Pixels)
|
||||
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) // Back-face culling for performance
|
||||
.front_face(vk::FrontFace::COUNTER_CLOCKWISE)
|
||||
.depth_bias_enable(false);
|
||||
|
||||
// 6. Configure Multisampling (Anti-aliasing)
|
||||
let multisampling = vk::PipelineMultisampleStateCreateInfo::default()
|
||||
.sample_shading_enable(false)
|
||||
.rasterization_samples(vk::SampleCountFlags::TYPE_1);
|
||||
|
||||
// 7. Configure Color Blending
|
||||
let color_blend_attachment = vk::PipelineColorBlendAttachmentState::default()
|
||||
.color_write_mask(vk::ColorComponentFlags::RGBA)
|
||||
.blend_enable(false); // Transparency is not required for the initial implementation
|
||||
|
||||
let color_blending = vk::PipelineColorBlendStateCreateInfo::default()
|
||||
.logic_op_enable(false)
|
||||
.attachments(std::slice::from_ref(&color_blend_attachment));
|
||||
|
||||
// 8. Define Dynamic States
|
||||
// This allows the window to be resized without recreating the entire pipeline.
|
||||
let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
|
||||
let dynamic_state_info = vk::PipelineDynamicStateCreateInfo::default()
|
||||
.dynamic_states(&dynamic_states);
|
||||
|
||||
// 9. Configure Dynamic Rendering (Vulkan 1.3)
|
||||
let color_formats = [color_format];
|
||||
let mut rendering_info = vk::PipelineRenderingCreateInfo::default()
|
||||
.color_attachment_formats(&color_formats);
|
||||
|
||||
// 10. 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);
|
||||
|
||||
let pipeline = unsafe {
|
||||
device.create_graphics_pipelines(vk::PipelineCache::null(), &[pipeline_info], None)
|
||||
.expect("Failed to create graphics pipeline")[0]
|
||||
};
|
||||
|
||||
// Cleanup temporary shader modules (they are baked into the pipeline now)
|
||||
unsafe {
|
||||
device.destroy_shader_module(vert_module, None);
|
||||
device.destroy_shader_module(frag_module, None);
|
||||
}
|
||||
|
||||
pipeline
|
||||
}
|
||||
Loading…
Reference in a new issue