StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.
| 1 | //! Render pipeline management |
| 2 | //! |
| 3 | //! BLOCCO 5: Pipeline Creation |
| 4 | //! Handles render pipeline, bind groups, and pipeline state |
| 5 | |
| 6 | use wgpu::{ |
| 7 | BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, |
| 8 | BindGroupLayoutEntry, BindingType, BlendState, BufferBindingType, ColorTargetState, |
| 9 | ColorWrites, Device, Face, FragmentState, FrontFace, MultisampleState, |
| 10 | PipelineLayoutDescriptor, PolygonMode, PrimitiveState, PrimitiveTopology, RenderPipeline, |
| 11 | RenderPipelineDescriptor, ShaderStages, TextureFormat, VertexState, |
| 12 | }; |
| 13 | |
| 14 | use super::{ |
| 15 | buffer_mgr::{BufferManager, SimpleVertex}, |
| 16 | shader_mgr::ShaderManager, |
| 17 | texture_mgr::TextureManager, |
| 18 | }; |
| 19 | |
| 20 | /// Manages render pipeline and bind groups |
| 21 | pub struct PipelineManager { |
| 22 | bind_group_layout: BindGroupLayout, |
| 23 | bind_group: BindGroup, |
| 24 | render_pipeline: RenderPipeline, |
| 25 | } |
| 26 | |
| 27 | impl PipelineManager { |
| 28 | /// Create new pipeline manager |
| 29 | /// |
| 30 | /// # Arguments |
| 31 | /// * `device` - GPU device |
| 32 | /// * `shader` - Compiled shader module |
| 33 | /// * `buffer_mgr` - Buffer manager (for uniform binding) |
| 34 | /// * `texture_mgr` - Texture manager (for texture binding) |
| 35 | /// * `surface_format` - Surface texture format |
| 36 | pub fn new( |
| 37 | device: &Device, |
| 38 | shader: &ShaderManager, |
| 39 | buffer_mgr: &BufferManager, |
| 40 | texture_mgr: &TextureManager, |
| 41 | surface_format: TextureFormat, |
| 42 | ) -> anyhow::Result<Self> { |
| 43 | println!("=== PIPELINE CREATION ==="); |
| 44 | |
| 45 | // Create bind group layout for uniform buffer + texture + sampler |
| 46 | let bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor { |
| 47 | label: Some("Bind Group Layout"), |
| 48 | entries: &[ |
| 49 | // Binding 0: Uniform buffer (projection matrix) |
| 50 | BindGroupLayoutEntry { |
| 51 | binding: 0, |
| 52 | visibility: ShaderStages::VERTEX, |
| 53 | ty: BindingType::Buffer { |
| 54 | ty: BufferBindingType::Uniform, |
| 55 | has_dynamic_offset: false, |
| 56 | min_binding_size: None, |
| 57 | }, |
| 58 | count: None, |
| 59 | }, |
| 60 | // Binding 1: Texture |
| 61 | BindGroupLayoutEntry { |
| 62 | binding: 1, |
| 63 | visibility: ShaderStages::FRAGMENT, |
| 64 | ty: BindingType::Texture { |
| 65 | sample_type: wgpu::TextureSampleType::Float { filterable: true }, |
| 66 | view_dimension: wgpu::TextureViewDimension::D2, |
| 67 | multisampled: false, |
| 68 | }, |
| 69 | count: None, |
| 70 | }, |
| 71 | // Binding 2: Sampler |
| 72 | BindGroupLayoutEntry { |
| 73 | binding: 2, |
| 74 | visibility: ShaderStages::FRAGMENT, |
| 75 | ty: BindingType::Sampler(wgpu::SamplerBindingType::Filtering), |
| 76 | count: None, |
| 77 | }, |
| 78 | ], |
| 79 | }); |
| 80 | |
| 81 | // Create bind group with actual resources |
| 82 | let bind_group = device.create_bind_group(&BindGroupDescriptor { |
| 83 | label: Some("Bind Group"), |
| 84 | layout: &bind_group_layout, |
| 85 | entries: &[ |
| 86 | // Binding 0: Uniform buffer |
| 87 | BindGroupEntry { |
| 88 | binding: 0, |
| 89 | resource: buffer_mgr.uniform_buffer().as_entire_binding(), |
| 90 | }, |
| 91 | // Binding 1: Texture view |
| 92 | BindGroupEntry { |
| 93 | binding: 1, |
| 94 | resource: wgpu::BindingResource::TextureView(texture_mgr.atlas().view()), |
| 95 | }, |
| 96 | // Binding 2: Sampler |
| 97 | BindGroupEntry { |
| 98 | binding: 2, |
| 99 | resource: wgpu::BindingResource::Sampler(texture_mgr.atlas().sampler()), |
| 100 | }, |
| 101 | ], |
| 102 | }); |
| 103 | |
| 104 | // Create pipeline layout |
| 105 | let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor { |
| 106 | label: Some("Render Pipeline Layout"), |
| 107 | bind_group_layouts: &[&bind_group_layout], |
| 108 | push_constant_ranges: &[], |
| 109 | }); |
| 110 | |
| 111 | // Create render pipeline |
| 112 | let render_pipeline = device.create_render_pipeline(&RenderPipelineDescriptor { |
| 113 | label: Some("Render Pipeline"), |
| 114 | layout: Some(&pipeline_layout), |
| 115 | vertex: VertexState { |
| 116 | module: shader.module(), |
| 117 | entry_point: shader.vertex_entry(), |
| 118 | buffers: &[SimpleVertex::desc()], |
| 119 | compilation_options: Default::default(), |
| 120 | }, |
| 121 | fragment: Some(FragmentState { |
| 122 | module: shader.module(), |
| 123 | entry_point: shader.fragment_entry(), |
| 124 | targets: &[Some(ColorTargetState { |
| 125 | format: surface_format, |
| 126 | blend: Some(BlendState::ALPHA_BLENDING), |
| 127 | write_mask: ColorWrites::ALL, |
| 128 | })], |
| 129 | compilation_options: Default::default(), |
| 130 | }), |
| 131 | primitive: PrimitiveState { |
| 132 | topology: PrimitiveTopology::TriangleList, |
| 133 | strip_index_format: None, |
| 134 | front_face: FrontFace::Ccw, |
| 135 | cull_mode: None, // No culling for debugging |
| 136 | unclipped_depth: false, |
| 137 | polygon_mode: PolygonMode::Fill, |
| 138 | conservative: false, |
| 139 | }, |
| 140 | depth_stencil: None, |
| 141 | multisample: MultisampleState { |
| 142 | count: 1, |
| 143 | mask: !0, |
| 144 | alpha_to_coverage_enabled: false, |
| 145 | }, |
| 146 | multiview: None, |
| 147 | }); |
| 148 | |
| 149 | println!("Bind group layout: created"); |
| 150 | println!("Bind group: created"); |
| 151 | println!("Render pipeline: created"); |
| 152 | println!("Surface format: {:?}", surface_format); |
| 153 | println!("Blend mode: ALPHA_BLENDING"); |
| 154 | println!("========================="); |
| 155 | |
| 156 | Ok(Self { |
| 157 | bind_group_layout, |
| 158 | bind_group, |
| 159 | render_pipeline, |
| 160 | }) |
| 161 | } |
| 162 | |
| 163 | /// Get bind group reference |
| 164 | pub fn bind_group(&self) -> &BindGroup { |
| 165 | &self.bind_group |
| 166 | } |
| 167 | |
| 168 | /// Get render pipeline reference |
| 169 | pub fn pipeline(&self) -> &RenderPipeline { |
| 170 | &self.render_pipeline |
| 171 | } |
| 172 | } |
| 173 | |
| 174 | #[cfg(test)] |
| 175 | mod tests { |
| 176 | use super::*; |
| 177 | use crate::gpu::{DeviceManager, SurfaceManager, TextureManager}; |
| 178 | use std::sync::Arc; |
| 179 | use wgpu::Backends; |
| 180 | use winit::dpi::PhysicalSize; |
| 181 | use winit::event_loop::EventLoop; |
| 182 | |
| 183 | #[tokio::test] |
| 184 | async fn test_pipeline_creation() { |
| 185 | let dm = DeviceManager::new(Backends::all()).await.unwrap(); |
| 186 | let shader = ShaderManager::from_wgsl( |
| 187 | dm.device(), |
| 188 | include_str!("../shaders/simple.wgsl"), |
| 189 | Some("Test Shader"), |
| 190 | ) |
| 191 | .unwrap(); |
| 192 | let buffer_mgr = BufferManager::new(dm.device()); |
| 193 | let texture_mgr = TextureManager::new(dm.device(), dm.queue()); |
| 194 | |
| 195 | // Use a common format for testing |
| 196 | let format = TextureFormat::Bgra8UnormSrgb; |
| 197 | |
| 198 | let pipeline_mgr = |
| 199 | PipelineManager::new(dm.device(), &shader, &buffer_mgr, &texture_mgr, format); |
| 200 | |
| 201 | assert!(pipeline_mgr.is_ok()); |
| 202 | } |
| 203 | |
| 204 | #[tokio::test] |
| 205 | async fn test_bind_group_setup() { |
| 206 | let dm = DeviceManager::new(Backends::all()).await.unwrap(); |
| 207 | let shader = ShaderManager::from_wgsl( |
| 208 | dm.device(), |
| 209 | include_str!("../shaders/simple.wgsl"), |
| 210 | Some("Test Shader"), |
| 211 | ) |
| 212 | .unwrap(); |
| 213 | let buffer_mgr = BufferManager::new(dm.device()); |
| 214 | let texture_mgr = TextureManager::new(dm.device(), dm.queue()); |
| 215 | |
| 216 | let format = TextureFormat::Bgra8UnormSrgb; |
| 217 | let pipeline_mgr = |
| 218 | PipelineManager::new(dm.device(), &shader, &buffer_mgr, &texture_mgr, format).unwrap(); |
| 219 | |
| 220 | // Verify bind group exists |
| 221 | let _bg = pipeline_mgr.bind_group(); |
| 222 | let _pipeline = pipeline_mgr.pipeline(); |
| 223 | } |
| 224 | |
| 225 | #[test] |
| 226 | fn test_blend_state_configuration() { |
| 227 | // Verify blend state is correct (ALPHA_BLENDING) |
| 228 | let blend = BlendState::ALPHA_BLENDING; |
| 229 | |
| 230 | // ALPHA_BLENDING should have: |
| 231 | // - color: src_alpha * src + (1 - src_alpha) * dst |
| 232 | // - alpha: 1 * src + (1 - src_alpha) * dst |
| 233 | assert_eq!(blend.color.src_factor, wgpu::BlendFactor::SrcAlpha); |
| 234 | assert_eq!(blend.color.dst_factor, wgpu::BlendFactor::OneMinusSrcAlpha); |
| 235 | assert_eq!(blend.alpha.src_factor, wgpu::BlendFactor::One); |
| 236 | assert_eq!(blend.alpha.dst_factor, wgpu::BlendFactor::OneMinusSrcAlpha); |
| 237 | } |
| 238 | } |
| 239 |