StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.
| 1 | use anyhow::Result; |
| 2 | use std::sync::Arc; |
| 3 | use strato_renderer::{ |
| 4 | AllocationStrategy, IntegratedRenderer, RenderContext, RendererBuilder, RendererConfig, |
| 5 | }; |
| 6 | use tracing::{error, info}; |
| 7 | use wgpu::*; |
| 8 | use winit::{ |
| 9 | event::{Event, WindowEvent}, |
| 10 | event_loop::{ControlFlow, EventLoop}, |
| 11 | window::{Window, WindowBuilder}, |
| 12 | }; |
| 13 | |
| 14 | /// Advanced renderer example demonstrating the complete wgpu system |
| 15 | struct AdvancedRendererExample { |
| 16 | window: Arc<Window>, |
| 17 | surface: Surface<'static>, |
| 18 | surface_config: SurfaceConfiguration, |
| 19 | renderer: IntegratedRenderer, |
| 20 | |
| 21 | // Example resources |
| 22 | vertex_buffer: Option<strato_renderer::ResourceHandle>, |
| 23 | index_buffer: Option<strato_renderer::ResourceHandle>, |
| 24 | render_pipeline: Option<RenderPipeline>, |
| 25 | |
| 26 | // State |
| 27 | frame_count: u64, |
| 28 | } |
| 29 | |
| 30 | impl AdvancedRendererExample { |
| 31 | async fn new(window: Arc<Window>) -> Result<Self> { |
| 32 | info!("Initializing advanced renderer example"); |
| 33 | |
| 34 | // Create surface |
| 35 | let instance = Instance::new(InstanceDescriptor { |
| 36 | backends: Backends::PRIMARY, |
| 37 | ..Default::default() |
| 38 | }); |
| 39 | |
| 40 | let surface = instance.create_surface(window.clone())?; |
| 41 | |
| 42 | // Create renderer with performance configuration |
| 43 | let mut renderer = RendererBuilder::new() |
| 44 | .with_instance(instance) |
| 45 | .with_surface(&surface) |
| 46 | .with_profiling(true) |
| 47 | .with_detailed_profiling(true) |
| 48 | .with_memory_strategy(AllocationStrategy::Balanced) |
| 49 | .with_max_memory_pool_size(256 * 1024 * 1024) // 256MB |
| 50 | .with_preferred_adapter(PowerPreference::HighPerformance) |
| 51 | .with_validation(cfg!(debug_assertions)) |
| 52 | .build() |
| 53 | .await?; |
| 54 | |
| 55 | // Configure surface |
| 56 | let adapter = renderer |
| 57 | .get_active_adapter() |
| 58 | .expect("Current adapter not found"); |
| 59 | |
| 60 | let size = window.inner_size(); |
| 61 | let surface_config = SurfaceConfiguration { |
| 62 | usage: TextureUsages::RENDER_ATTACHMENT, |
| 63 | format: surface.get_capabilities(adapter).formats[0], |
| 64 | width: size.width, |
| 65 | height: size.height, |
| 66 | present_mode: PresentMode::Fifo, |
| 67 | alpha_mode: CompositeAlphaMode::Auto, |
| 68 | view_formats: vec![], |
| 69 | desired_maximum_frame_latency: 2, |
| 70 | }; |
| 71 | |
| 72 | surface.configure(&renderer.device().device, &surface_config); |
| 73 | |
| 74 | // Initialize renderer |
| 75 | renderer.initialize().await?; |
| 76 | |
| 77 | info!("Renderer initialized successfully"); |
| 78 | info!("GPU: {}", renderer.get_device_info().device_name); |
| 79 | // info!("Backend: {:?}", renderer.get_device_info().backend); |
| 80 | |
| 81 | let mut example = Self { |
| 82 | window, |
| 83 | surface, |
| 84 | surface_config, |
| 85 | renderer, |
| 86 | vertex_buffer: None, |
| 87 | index_buffer: None, |
| 88 | render_pipeline: None, |
| 89 | frame_count: 0, |
| 90 | }; |
| 91 | |
| 92 | // Create example resources |
| 93 | example.create_resources().await?; |
| 94 | |
| 95 | Ok(example) |
| 96 | } |
| 97 | |
| 98 | async fn create_resources(&mut self) -> Result<()> { |
| 99 | info!("Creating example resources"); |
| 100 | |
| 101 | // Create vertex buffer |
| 102 | let vertices: &[f32] = &[ |
| 103 | // Triangle vertices (position + color) |
| 104 | -0.5, -0.5, 1.0, 0.0, 0.0, // Bottom left - Red |
| 105 | 0.5, -0.5, 0.0, 1.0, 0.0, // Bottom right - Green |
| 106 | 0.0, 0.5, 0.0, 0.0, 1.0, // Top - Blue |
| 107 | ]; |
| 108 | |
| 109 | let vertex_buffer = self.renderer.create_buffer( |
| 110 | (vertices.len() * std::mem::size_of::<f32>()) as u64, |
| 111 | BufferUsages::VERTEX | BufferUsages::COPY_DST, |
| 112 | )?; |
| 113 | |
| 114 | // Create index buffer |
| 115 | let indices: &[u16] = &[0, 1, 2]; |
| 116 | let index_buffer = self.renderer.create_buffer( |
| 117 | (indices.len() * std::mem::size_of::<u16>()) as u64, |
| 118 | BufferUsages::INDEX | BufferUsages::COPY_DST, |
| 119 | )?; |
| 120 | |
| 121 | // Load shader |
| 122 | let path = std::path::PathBuf::from("examples/advanced_renderer/shaders/triangle.wgsl"); |
| 123 | let stage = strato_renderer::shader::ShaderStage::Vertex; |
| 124 | let variant = strato_renderer::shader::ShaderVariant { |
| 125 | macros: vec![], |
| 126 | features: vec![], |
| 127 | optimization_level: 0, |
| 128 | }; |
| 129 | |
| 130 | let shader = self.renderer.load_shader(&path, stage, variant)?; |
| 131 | |
| 132 | // Create render pipeline |
| 133 | let render_pipeline_desc = RenderPipelineDescriptor { |
| 134 | label: Some("Triangle Pipeline"), |
| 135 | layout: None, |
| 136 | vertex: VertexState { |
| 137 | module: &shader.module, |
| 138 | entry_point: "vs_main", |
| 139 | buffers: &[VertexBufferLayout { |
| 140 | array_stride: 5 * std::mem::size_of::<f32>() as BufferAddress, |
| 141 | step_mode: VertexStepMode::Vertex, |
| 142 | attributes: &[ |
| 143 | VertexAttribute { |
| 144 | offset: 0, |
| 145 | shader_location: 0, |
| 146 | format: VertexFormat::Float32x2, |
| 147 | }, |
| 148 | VertexAttribute { |
| 149 | offset: 2 * std::mem::size_of::<f32>() as BufferAddress, |
| 150 | shader_location: 1, |
| 151 | format: VertexFormat::Float32x3, |
| 152 | }, |
| 153 | ], |
| 154 | }], |
| 155 | compilation_options: Default::default(), |
| 156 | }, |
| 157 | fragment: Some(FragmentState { |
| 158 | module: &shader.module, |
| 159 | entry_point: "fs_main", |
| 160 | targets: &[Some(ColorTargetState { |
| 161 | format: self.surface_config.format, |
| 162 | blend: Some(BlendState::REPLACE), |
| 163 | write_mask: ColorWrites::ALL, |
| 164 | })], |
| 165 | compilation_options: Default::default(), |
| 166 | }), |
| 167 | primitive: PrimitiveState { |
| 168 | topology: PrimitiveTopology::TriangleList, |
| 169 | strip_index_format: None, |
| 170 | front_face: FrontFace::Ccw, |
| 171 | cull_mode: Some(Face::Back), |
| 172 | unclipped_depth: false, |
| 173 | polygon_mode: PolygonMode::Fill, |
| 174 | conservative: false, |
| 175 | }, |
| 176 | depth_stencil: None, |
| 177 | multisample: MultisampleState { |
| 178 | count: 1, |
| 179 | mask: !0, |
| 180 | alpha_to_coverage_enabled: false, |
| 181 | }, |
| 182 | multiview: None, |
| 183 | }; |
| 184 | |
| 185 | let render_pipeline = self |
| 186 | .renderer |
| 187 | .device() |
| 188 | .device |
| 189 | .create_render_pipeline(&render_pipeline_desc); |
| 190 | self.render_pipeline = Some(render_pipeline); |
| 191 | |
| 192 | self.vertex_buffer = Some(vertex_buffer); |
| 193 | self.index_buffer = Some(index_buffer); |
| 194 | |
| 195 | info!("Resources created successfully"); |
| 196 | Ok(()) |
| 197 | } |
| 198 | |
| 199 | fn render(&mut self) -> Result<()> { |
| 200 | // Resolve resources first to ensure they live long enough for the render pass |
| 201 | let vertex_buffer_res = if let Some(handle) = self.vertex_buffer { |
| 202 | self.renderer.get_buffer(handle) |
| 203 | } else { |
| 204 | None |
| 205 | }; |
| 206 | |
| 207 | let index_buffer_res = if let Some(handle) = self.index_buffer { |
| 208 | self.renderer.get_buffer(handle) |
| 209 | } else { |
| 210 | None |
| 211 | }; |
| 212 | |
| 213 | // Begin frame |
| 214 | let mut render_context = self.renderer.begin_frame()?; |
| 215 | |
| 216 | // Get surface texture |
| 217 | let output = self.surface.get_current_texture()?; |
| 218 | let view = output |
| 219 | .texture |
| 220 | .create_view(&TextureViewDescriptor::default()); |
| 221 | |
| 222 | // Begin render pass |
| 223 | let mut render_pass = render_context.begin_render_pass(&RenderPassDescriptor { |
| 224 | label: Some("Main Render Pass"), |
| 225 | color_attachments: &[Some(RenderPassColorAttachment { |
| 226 | view: &view, |
| 227 | resolve_target: None, |
| 228 | ops: Operations { |
| 229 | load: LoadOp::Clear(Color { |
| 230 | r: 0.1, |
| 231 | g: 0.2, |
| 232 | b: 0.3, |
| 233 | a: 1.0, |
| 234 | }), |
| 235 | store: StoreOp::Store, |
| 236 | }, |
| 237 | })], |
| 238 | depth_stencil_attachment: None, |
| 239 | occlusion_query_set: None, |
| 240 | timestamp_writes: None, |
| 241 | }); |
| 242 | |
| 243 | // Draw triangle |
| 244 | if let (Some(pipeline), Some(vb), Some(ib)) = |
| 245 | (&self.render_pipeline, &vertex_buffer_res, &index_buffer_res) |
| 246 | { |
| 247 | render_pass.set_pipeline(pipeline); |
| 248 | render_pass.set_vertex_buffer(0, vb.slice(..)); |
| 249 | render_pass.set_index_buffer(ib.slice(..), IndexFormat::Uint16); |
| 250 | render_pass.draw_indexed(0..3, 0, 0..1); |
| 251 | } |
| 252 | |
| 253 | drop(render_pass); |
| 254 | render_context.end_render_pass(); |
| 255 | |
| 256 | // End frame |
| 257 | self.renderer.end_frame(render_context)?; |
| 258 | |
| 259 | // Present |
| 260 | output.present(); |
| 261 | |
| 262 | self.frame_count += 1; |
| 263 | |
| 264 | // Print stats every 60 frames |
| 265 | if self.frame_count % 60 == 0 { |
| 266 | let stats = self.renderer.get_stats(); |
| 267 | info!( |
| 268 | "Frame {}: {:.2}ms avg, {}MB memory, {} resources", |
| 269 | stats.frame_count, |
| 270 | stats.average_frame_time * 1000.0, |
| 271 | stats.memory_usage / (1024 * 1024), |
| 272 | stats.active_resources |
| 273 | ); |
| 274 | |
| 275 | if let Some(report) = self.renderer.get_performance_report() { |
| 276 | info!("Performance report: {:#?}", report); |
| 277 | } |
| 278 | } |
| 279 | |
| 280 | Ok(()) |
| 281 | } |
| 282 | |
| 283 | fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) -> Result<()> { |
| 284 | if new_size.width > 0 && new_size.height > 0 { |
| 285 | self.surface_config.width = new_size.width; |
| 286 | self.surface_config.height = new_size.height; |
| 287 | self.surface |
| 288 | .configure(&self.renderer.device().device, &self.surface_config); |
| 289 | self.renderer.resize((new_size.width, new_size.height))?; |
| 290 | } |
| 291 | Ok(()) |
| 292 | } |
| 293 | } |
| 294 | |
| 295 | async fn run() -> Result<()> { |
| 296 | // Initialize tracing |
| 297 | tracing_subscriber::fmt::init(); |
| 298 | |
| 299 | info!("Starting advanced renderer example"); |
| 300 | |
| 301 | // Create event loop and window |
| 302 | let event_loop = EventLoop::new()?; |
| 303 | let window = Arc::new( |
| 304 | WindowBuilder::new() |
| 305 | .with_title("Advanced wgpu Renderer Example") |
| 306 | .with_inner_size(winit::dpi::LogicalSize::new(800, 600)) |
| 307 | .build(&event_loop)?, |
| 308 | ); |
| 309 | |
| 310 | // Create example |
| 311 | let mut example = AdvancedRendererExample::new(window.clone()).await?; |
| 312 | |
| 313 | info!("Starting event loop"); |
| 314 | |
| 315 | event_loop.run(move |event, elwt| { |
| 316 | elwt.set_control_flow(ControlFlow::Poll); |
| 317 | |
| 318 | match event { |
| 319 | Event::WindowEvent { |
| 320 | ref event, |
| 321 | window_id, |
| 322 | } if window_id == window.id() => match event { |
| 323 | WindowEvent::CloseRequested => { |
| 324 | info!("Close requested"); |
| 325 | elwt.exit(); |
| 326 | } |
| 327 | WindowEvent::Resized(physical_size) => { |
| 328 | if let Err(e) = example.resize(*physical_size) { |
| 329 | error!("Resize error: {}", e); |
| 330 | } |
| 331 | } |
| 332 | WindowEvent::RedrawRequested => { |
| 333 | if let Err(e) = example.render() { |
| 334 | error!("Render error: {}", e); |
| 335 | } |
| 336 | } |
| 337 | _ => {} |
| 338 | }, |
| 339 | Event::AboutToWait => { |
| 340 | window.request_redraw(); |
| 341 | } |
| 342 | _ => {} |
| 343 | } |
| 344 | })?; |
| 345 | |
| 346 | Ok(()) |
| 347 | } |
| 348 | |
| 349 | #[tokio::main] |
| 350 | async fn main() { |
| 351 | if let Err(e) = run().await { |
| 352 | error!("Application error: {}", e); |
| 353 | std::process::exit(1); |
| 354 | } |
| 355 | } |
| 356 |