From 17d06a0423209f3c3c274714a1560a8875107f6b Mon Sep 17 00:00:00 2001 From: Serkyo Date: Sun, 10 May 2026 22:02:43 +0200 Subject: [PATCH] feat(renderer): add pipeline module for graphics pipeline creation --- crates/renderer/src/pipeline.rs | 152 ++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 crates/renderer/src/pipeline.rs diff --git a/crates/renderer/src/pipeline.rs b/crates/renderer/src/pipeline.rs new file mode 100644 index 0000000..8dd7e83 --- /dev/null +++ b/crates/renderer/src/pipeline.rs @@ -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::() 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 +}