StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.
| 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 | |
| 13 | use crate::vertex::Vertex; |
| 14 | use anyhow::{Context, Result}; |
| 15 | use parking_lot::RwLock; |
| 16 | use serde::{Deserialize, Serialize}; |
| 17 | use std::collections::HashMap; |
| 18 | use std::sync::Arc; |
| 19 | use std::time::Duration; |
| 20 | use 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)] |
| 31 | pub 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 | |
| 38 | impl 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 |
| 78 | pub struct UIPipeline { |
| 79 | pub pipeline: RenderPipeline, |
| 80 | pub bind_group_layout: BindGroupLayout, |
| 81 | pub uniform_buffer: Buffer, |
| 82 | pub bind_group: BindGroup, |
| 83 | } |
| 84 | |
| 85 | impl 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 |
| 276 | pub struct TextPipeline { |
| 277 | pub pipeline: RenderPipeline, |
| 278 | pub bind_group_layout: BindGroupLayout, |
| 279 | } |
| 280 | |
| 281 | impl 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)] |
| 374 | pub 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)] |
| 382 | pub enum RenderPassType { |
| 383 | UI, |
| 384 | Text, |
| 385 | PostProcess, |
| 386 | } |
| 387 | |
| 388 | /// Render graph for managing render pass dependencies |
| 389 | #[derive(Debug)] |
| 390 | pub struct RenderGraph { |
| 391 | nodes: Vec<RenderNode>, |
| 392 | execution_order: Vec<usize>, |
| 393 | } |
| 394 | |
| 395 | impl 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 |
| 419 | pub 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 | |
| 426 | impl 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)] |
| 458 | mod 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 |