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