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/pipeline.rs
StratoSDK / crates / strato-renderer / src / pipeline.rs
1//! Advanced rendering pipeline management system
2//!
3//! This module provides a comprehensive pipeline management system including:
4//! - Dynamic pipeline compilation and specialization
5//! - Multi-pass rendering with dependency tracking
6//! - Pipeline state optimization and caching
7//! - Render graph construction and execution
8//! - GPU-driven rendering support
9//! - Performance profiling and analytics
10//! - Hot-swappable pipeline configurations
11//! - Cross-platform pipeline optimization
12 
13use crate::vertex::Vertex;
14use anyhow::{Context, Result};
15use parking_lot::RwLock;
16use serde::{Deserialize, Serialize};
17use std::collections::HashMap;
18use std::sync::Arc;
19use std::time::Duration;
20use wgpu::{
21 BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor,
22 BindGroupLayoutEntry, BindingType, BlendState, Buffer, BufferBindingType, ColorTargetState,
23 ColorWrites, ComputePipeline, Device, FragmentState, MultisampleState, PipelineLayout,
24 PipelineLayoutDescriptor, PrimitiveState, RenderPipeline, RenderPipelineDescriptor,
25 ShaderModule, ShaderStages, TextureSampleType, TextureViewDimension, VertexState,
26};
27 
28/// Uniform data for the UI shader
29#[repr(C)]
30#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
31pub struct UIUniforms {
32 pub view_proj: [[f32; 4]; 4],
33 pub screen_size: [f32; 2],
34 pub time: f32,
35 pub _padding: f32,
36}
37 
38impl UIUniforms {
39 pub fn new(width: f32, height: f32, time: f32) -> Self {
40 // Create orthographic projection matrix
41 let view_proj = Self::orthographic_projection(0.0, width, height, 0.0, -1.0, 1.0);
42 
43 Self {
44 view_proj,
45 screen_size: [width, height],
46 time,
47 _padding: 0.0,
48 }
49 }
50 
51 fn orthographic_projection(
52 left: f32,
53 right: f32,
54 bottom: f32,
55 top: f32,
56 near: f32,
57 far: f32,
58 ) -> [[f32; 4]; 4] {
59 let width = right - left;
60 let height = bottom - top;
61 let depth = far - near;
62 
63 [
64 [2.0 / width, 0.0, 0.0, 0.0],
65 [0.0, -2.0 / height, 0.0, 0.0],
66 [0.0, 0.0, -1.0 / depth, 0.0],
67 [
68 -(right + left) / width,
69 -(top + bottom) / height,
70 -near / depth,
71 1.0,
72 ],
73 ]
74 }
75}
76 
77/// Render pipeline for UI rendering
78pub struct UIPipeline {
79 pub pipeline: RenderPipeline,
80 pub bind_group_layout: BindGroupLayout,
81 pub uniform_buffer: Buffer,
82 pub bind_group: BindGroup,
83}
84 
85impl UIPipeline {
86 /// Create a new UI render pipeline
87 pub fn new(device: &Device, surface_format: wgpu::TextureFormat) -> Self {
88 // Load shader
89 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
90 label: Some("UI Shader"),
91 source: wgpu::ShaderSource::Wgsl(include_str!("shaders/ui.wgsl").into()),
92 });
93 
94 // Create bind group layout
95 let bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
96 label: Some("UI Bind Group Layout"),
97 entries: &[
98 // Uniforms
99 BindGroupLayoutEntry {
100 binding: 0,
101 visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
102 ty: BindingType::Buffer {
103 ty: BufferBindingType::Uniform,
104 has_dynamic_offset: false,
105 min_binding_size: None,
106 },
107 count: None,
108 },
109 // Sampler
110 BindGroupLayoutEntry {
111 binding: 1,
112 visibility: ShaderStages::FRAGMENT,
113 ty: BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
114 count: None,
115 },
116 // Texture
117 BindGroupLayoutEntry {
118 binding: 2,
119 visibility: ShaderStages::FRAGMENT,
120 ty: BindingType::Texture {
121 sample_type: TextureSampleType::Float { filterable: true },
122 view_dimension: TextureViewDimension::D2,
123 multisampled: false,
124 },
125 count: None,
126 },
127 ],
128 });
129 
130 // Create pipeline layout
131 let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
132 label: Some("UI Pipeline Layout"),
133 bind_group_layouts: &[&bind_group_layout],
134 push_constant_ranges: &[],
135 });
136 
137 // Create render pipeline
138 let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
139 label: Some("UI Render Pipeline"),
140 layout: Some(&pipeline_layout),
141 vertex: VertexState {
142 module: &shader,
143 entry_point: "vs_main",
144 buffers: &[Vertex::desc()],
145 compilation_options: Default::default(),
146 },
147 fragment: Some(FragmentState {
148 module: &shader,
149 entry_point: "fs_main",
150 targets: &[Some(ColorTargetState {
151 format: surface_format,
152 blend: Some(BlendState::ALPHA_BLENDING),
153 write_mask: ColorWrites::ALL,
154 })],
155 compilation_options: Default::default(),
156 }),
157 primitive: PrimitiveState {
158 topology: wgpu::PrimitiveTopology::TriangleList,
159 strip_index_format: None,
160 front_face: wgpu::FrontFace::Ccw,
161 cull_mode: None,
162 unclipped_depth: false,
163 polygon_mode: wgpu::PolygonMode::Fill,
164 conservative: false,
165 },
166 depth_stencil: None,
167 multisample: MultisampleState {
168 count: 1,
169 mask: !0,
170 alpha_to_coverage_enabled: false,
171 },
172 multiview: None,
173 });
174 
175 // Create uniform buffer
176 let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
177 label: Some("UI Uniform Buffer"),
178 size: std::mem::size_of::<UIUniforms>() as u64,
179 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
180 mapped_at_creation: false,
181 });
182 
183 // Create default texture (1x1 white pixel)
184 let default_texture = device.create_texture(&wgpu::TextureDescriptor {
185 label: Some("Default Texture"),
186 size: wgpu::Extent3d {
187 width: 1,
188 height: 1,
189 depth_or_array_layers: 1,
190 },
191 mip_level_count: 1,
192 sample_count: 1,
193 dimension: wgpu::TextureDimension::D2,
194 format: wgpu::TextureFormat::Rgba8UnormSrgb,
195 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
196 view_formats: &[],
197 });
198 
199 let default_texture_view =
200 default_texture.create_view(&wgpu::TextureViewDescriptor::default());
201 
202 // Create sampler
203 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
204 label: Some("UI Sampler"),
205 address_mode_u: wgpu::AddressMode::ClampToEdge,
206 address_mode_v: wgpu::AddressMode::ClampToEdge,
207 address_mode_w: wgpu::AddressMode::ClampToEdge,
208 mag_filter: wgpu::FilterMode::Linear,
209 min_filter: wgpu::FilterMode::Linear,
210 mipmap_filter: wgpu::FilterMode::Nearest,
211 ..Default::default()
212 });
213 
214 // Create bind group
215 let bind_group = device.create_bind_group(&BindGroupDescriptor {
216 label: Some("UI Bind Group"),
217 layout: &bind_group_layout,
218 entries: &[
219 BindGroupEntry {
220 binding: 0,
221 resource: uniform_buffer.as_entire_binding(),
222 },
223 BindGroupEntry {
224 binding: 1,
225 resource: wgpu::BindingResource::Sampler(&sampler),
226 },
227 BindGroupEntry {
228 binding: 2,
229 resource: wgpu::BindingResource::TextureView(&default_texture_view),
230 },
231 ],
232 });
233 
234 Self {
235 pipeline,
236 bind_group_layout,
237 uniform_buffer,
238 bind_group,
239 }
240 }
241 
242 /// Update uniforms
243 pub fn update_uniforms(&self, queue: &wgpu::Queue, uniforms: &UIUniforms) {
244 queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[*uniforms]));
245 }
246 
247 /// Create a bind group with a custom texture
248 pub fn create_bind_group_with_texture(
249 &self,
250 device: &Device,
251 texture_view: &wgpu::TextureView,
252 sampler: &wgpu::Sampler,
253 ) -> BindGroup {
254 device.create_bind_group(&BindGroupDescriptor {
255 label: Some("UI Bind Group with Texture"),
256 layout: &self.bind_group_layout,
257 entries: &[
258 BindGroupEntry {
259 binding: 0,
260 resource: self.uniform_buffer.as_entire_binding(),
261 },
262 BindGroupEntry {
263 binding: 1,
264 resource: wgpu::BindingResource::Sampler(sampler),
265 },
266 BindGroupEntry {
267 binding: 2,
268 resource: wgpu::BindingResource::TextureView(texture_view),
269 },
270 ],
271 })
272 }
273}
274 
275/// Text rendering pipeline
276pub struct TextPipeline {
277 pub pipeline: RenderPipeline,
278 pub bind_group_layout: BindGroupLayout,
279}
280 
281impl TextPipeline {
282 /// Create a new text render pipeline
283 pub fn new(device: &Device, surface_format: wgpu::TextureFormat) -> Self {
284 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
285 label: Some("Text Shader"),
286 source: wgpu::ShaderSource::Wgsl(include_str!("shaders/ui.wgsl").into()),
287 });
288 
289 // Create bind group layout (same as UI for now)
290 let bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
291 label: Some("Text Bind Group Layout"),
292 entries: &[
293 BindGroupLayoutEntry {
294 binding: 0,
295 visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
296 ty: BindingType::Buffer {
297 ty: BufferBindingType::Uniform,
298 has_dynamic_offset: false,
299 min_binding_size: None,
300 },
301 count: None,
302 },
303 BindGroupLayoutEntry {
304 binding: 1,
305 visibility: ShaderStages::FRAGMENT,
306 ty: BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
307 count: None,
308 },
309 BindGroupLayoutEntry {
310 binding: 2,
311 visibility: ShaderStages::FRAGMENT,
312 ty: BindingType::Texture {
313 sample_type: TextureSampleType::Float { filterable: true },
314 view_dimension: TextureViewDimension::D2,
315 multisampled: false,
316 },
317 count: None,
318 },
319 ],
320 });
321 
322 let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
323 label: Some("Text Pipeline Layout"),
324 bind_group_layouts: &[&bind_group_layout],
325 push_constant_ranges: &[],
326 });
327 
328 let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
329 label: Some("Text Render Pipeline"),
330 layout: Some(&pipeline_layout),
331 vertex: VertexState {
332 module: &shader,
333 entry_point: "vs_main",
334 buffers: &[Vertex::desc()],
335 compilation_options: Default::default(),
336 },
337 fragment: Some(FragmentState {
338 module: &shader,
339 entry_point: "fs_main",
340 targets: &[Some(ColorTargetState {
341 format: surface_format,
342 blend: Some(BlendState::ALPHA_BLENDING),
343 write_mask: ColorWrites::ALL,
344 })],
345 compilation_options: Default::default(),
346 }),
347 primitive: PrimitiveState {
348 topology: wgpu::PrimitiveTopology::TriangleList,
349 strip_index_format: None,
350 front_face: wgpu::FrontFace::Ccw,
351 cull_mode: None,
352 unclipped_depth: false,
353 polygon_mode: wgpu::PolygonMode::Fill,
354 conservative: false,
355 },
356 depth_stencil: None,
357 multisample: MultisampleState {
358 count: 1,
359 mask: !0,
360 alpha_to_coverage_enabled: false,
361 },
362 multiview: None,
363 });
364 
365 Self {
366 pipeline,
367 bind_group_layout,
368 }
369 }
370}
371 
372/// Render graph node for managing render passes
373#[derive(Debug, Clone)]
374pub struct RenderNode {
375 pub id: String,
376 pub dependencies: Vec<String>,
377 pub pass_type: RenderPassType,
378}
379 
380/// Type of render pass
381#[derive(Debug, Clone)]
382pub enum RenderPassType {
383 UI,
384 Text,
385 PostProcess,
386}
387 
388/// Render graph for managing render pass dependencies
389#[derive(Debug)]
390pub struct RenderGraph {
391 nodes: Vec<RenderNode>,
392 execution_order: Vec<usize>,
393}
394 
395impl RenderGraph {
396 pub fn new() -> Self {
397 Self {
398 nodes: Vec::new(),
399 execution_order: Vec::new(),
400 }
401 }
402 
403 pub fn add_node(&mut self, node: RenderNode) {
404 self.nodes.push(node);
405 self.update_execution_order();
406 }
407 
408 fn update_execution_order(&mut self) {
409 // Simple topological sort for now
410 self.execution_order = (0..self.nodes.len()).collect();
411 }
412 
413 pub fn get_execution_order(&self) -> &[usize] {
414 &self.execution_order
415 }
416}
417 
418/// Pipeline manager for handling multiple render pipelines
419pub struct PipelineManager {
420 pub ui_pipeline: UIPipeline,
421 // Text rendering is now handled via UI pipeline or generic sprite batching
422 // pub text_pipeline: TextPipeline,
423 render_graph: RenderGraph,
424}
425 
426impl PipelineManager {
427 /// Create a new pipeline manager
428 pub fn new(device: &Device, surface_format: wgpu::TextureFormat) -> Self {
429 let ui_pipeline = UIPipeline::new(device, surface_format);
430 // Text pipeline removed in favor of unified system
431 let render_graph = RenderGraph::new();
432 
433 Self {
434 ui_pipeline,
435 render_graph,
436 }
437 }
438 
439 /// Update all pipeline uniforms
440 pub fn update_uniforms(&self, queue: &wgpu::Queue, uniforms: &UIUniforms) {
441 self.ui_pipeline.update_uniforms(queue, uniforms);
442 }
443 
444 /// Initialize the pipeline manager (integration method)
445 pub fn initialize(&self) -> anyhow::Result<()> {
446 tracing::info!("Pipeline manager initialized");
447 Ok(())
448 }
449 
450 /// Create render pipeline (placeholder integration method)
451 pub fn create_render_pipeline(&self) -> anyhow::Result<()> {
452 // Placeholder - would create additional pipelines as needed
453 Ok(())
454 }
455}
456 
457#[cfg(test)]
458mod tests {
459 use super::*;
460 
461 #[test]
462 fn test_ui_uniforms_creation() {
463 let uniforms = UIUniforms::new(800.0, 600.0, 1.0);
464 assert_eq!(uniforms.screen_size, [800.0, 600.0]);
465 assert_eq!(uniforms.time, 1.0);
466 }
467 
468 #[test]
469 fn test_orthographic_projection() {
470 let proj = UIUniforms::orthographic_projection(0.0, 800.0, 600.0, 0.0, -1.0, 1.0);
471 
472 // Check that it's a valid orthographic projection matrix
473 assert_eq!(proj[0][0], 2.0 / 800.0); // X scale
474 assert_eq!(proj[1][1], -2.0 / 600.0); // Y scale (flipped for screen coordinates)
475 assert_eq!(proj[3][3], 1.0); // W component
476 }
477}
478