Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-renderer/src/gpu/pipeline_mgr.rs
StratoSDK / crates / strato-renderer / src / gpu / pipeline_mgr.rs
1//! Render pipeline management
2//!
3//! BLOCCO 5: Pipeline Creation
4//! Handles render pipeline, bind groups, and pipeline state
5 
6use 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 
14use super::{
15 buffer_mgr::{BufferManager, SimpleVertex},
16 shader_mgr::ShaderManager,
17 texture_mgr::TextureManager,
18};
19 
20/// Manages render pipeline and bind groups
21pub struct PipelineManager {
22 bind_group_layout: BindGroupLayout,
23 bind_group: BindGroup,
24 render_pipeline: RenderPipeline,
25}
26 
27impl 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)]
175mod 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