synvael/crates/renderer/src/pipeline.rs

156 lines
6.2 KiB
Rust

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