StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.
| 1 | use crate::rendering::wgpu::renderer::{glyph, image, rect, WGPUContext}; |
| 2 | |
| 3 | use crate::rendering::wgpu::Resources; |
| 4 | use crate::scene::Layer; |
| 5 | use crate::Scene; |
| 6 | |
| 7 | use pathfinder_geometry::rect::RectF; |
| 8 | use pathfinder_geometry::vector::Vector2F; |
| 9 | use wgpu::{CommandEncoder, RenderPass, SurfaceTexture}; |
| 10 | |
| 11 | #[derive(Default)] |
| 12 | struct PerFrameState { |
| 13 | rect: rect::PerFrameState, |
| 14 | glyph: glyph::PerFrameState, |
| 15 | image: image::PerFrameState, |
| 16 | } |
| 17 | |
| 18 | /// Struct responsible for rendering a frame by issuing draw calls. |
| 19 | pub(super) struct Frame<'a> { |
| 20 | scene: &'a Scene, |
| 21 | layer_state: Vec<LayerState<'a>>, |
| 22 | per_frame_state: PerFrameState, |
| 23 | rect_pipeline: &'a rect::Pipeline, |
| 24 | glyph_pipeline: &'a mut glyph::Pipeline, |
| 25 | image_pipeline: &'a mut image::Pipeline, |
| 26 | } |
| 27 | |
| 28 | impl<'a> Frame<'a> { |
| 29 | pub(super) fn new( |
| 30 | scene: &'a Scene, |
| 31 | ctx: &'a mut WGPUContext<'a>, |
| 32 | rect_pipeline: &'a rect::Pipeline, |
| 33 | glyph_pipeline: &'a mut glyph::Pipeline, |
| 34 | image_pipeline: &'a mut image::Pipeline, |
| 35 | ) -> Self { |
| 36 | glyph_pipeline.update_config(&scene.rendering_config().glyphs); |
| 37 | |
| 38 | let mut layer_state = vec![]; |
| 39 | let mut per_frame_state = PerFrameState::default(); |
| 40 | |
| 41 | for layer in scene.layers() { |
| 42 | let rect_layer_state = |
| 43 | rect_pipeline.initialize_for_layer(layer, scene, &mut per_frame_state.rect); |
| 44 | let glyph_layer_state = |
| 45 | glyph_pipeline.initialize_for_layer(layer, scene, &mut per_frame_state.glyph, ctx); |
| 46 | let image_layer_state = |
| 47 | image_pipeline.initialize_for_layer(layer, scene, &mut per_frame_state.image, ctx); |
| 48 | layer_state.push(LayerState { |
| 49 | layer, |
| 50 | rect_layer_state, |
| 51 | glyph_layer_state, |
| 52 | image_layer_state, |
| 53 | }); |
| 54 | } |
| 55 | |
| 56 | rect::Pipeline::finalize_per_frame_state( |
| 57 | &mut per_frame_state.rect, |
| 58 | &ctx.resources.device, |
| 59 | &ctx.resources.device_lost, |
| 60 | ); |
| 61 | glyph::Pipeline::finalize_per_frame_state( |
| 62 | &mut per_frame_state.glyph, |
| 63 | &ctx.resources.device, |
| 64 | &ctx.resources.device_lost, |
| 65 | ); |
| 66 | image::Pipeline::finalize_per_frame_state( |
| 67 | &mut per_frame_state.image, |
| 68 | &ctx.resources.device, |
| 69 | &ctx.resources.device_lost, |
| 70 | ); |
| 71 | |
| 72 | Self { |
| 73 | scene, |
| 74 | layer_state, |
| 75 | per_frame_state, |
| 76 | rect_pipeline, |
| 77 | glyph_pipeline, |
| 78 | image_pipeline, |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | /// Encodes draw calls into the [`wgpu::CommandEncoder`] to render the [`Scene`]. Callers are |
| 83 | /// responsible for finishing the [`wgpu::CommandEncoder`] and actually presenting the current |
| 84 | /// drawable on the screen. |
| 85 | pub(super) fn draw( |
| 86 | self, |
| 87 | resources: &Resources, |
| 88 | encoder: &mut CommandEncoder, |
| 89 | surface_texture: &SurfaceTexture, |
| 90 | ) { |
| 91 | let surface_size = Vector2F::new( |
| 92 | surface_texture.texture.width() as f32, |
| 93 | surface_texture.texture.height() as f32, |
| 94 | ); |
| 95 | |
| 96 | let view = surface_texture |
| 97 | .texture |
| 98 | .create_view(&wgpu::TextureViewDescriptor { |
| 99 | format: Some(surface_texture.texture.format()), |
| 100 | ..Default::default() |
| 101 | }); |
| 102 | |
| 103 | let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { |
| 104 | color_attachments: &[Some(wgpu::RenderPassColorAttachment { |
| 105 | view: &view, |
| 106 | depth_slice: None, |
| 107 | resolve_target: None, |
| 108 | ops: wgpu::Operations { |
| 109 | load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), |
| 110 | store: wgpu::StoreOp::Store, |
| 111 | }, |
| 112 | })], |
| 113 | ..Default::default() |
| 114 | }); |
| 115 | resources.configure_render_pass(&mut render_pass, surface_size); |
| 116 | |
| 117 | let device_bounds = RectF::new(Vector2F::zero(), surface_size); |
| 118 | |
| 119 | for layer_state in &self.layer_state { |
| 120 | if let Some(bounds) = layer_state.layer.clip_bounds { |
| 121 | // Make sure the scissor rect doesn't extend beyond the boundaries |
| 122 | // of the window. |
| 123 | let bounds = (bounds * self.scene.scale_factor()).intersection(device_bounds); |
| 124 | let Some(intersection) = bounds else { |
| 125 | // The layer's clip bounds don't intersect the window bounds |
| 126 | // at all; we can skip drawing anything in this layer. |
| 127 | continue; |
| 128 | }; |
| 129 | |
| 130 | Self::set_scissor_rect(&mut render_pass, intersection); |
| 131 | } else { |
| 132 | Self::set_scissor_rect(&mut render_pass, device_bounds); |
| 133 | } |
| 134 | |
| 135 | if let Some(rect_layer_state) = &layer_state.rect_layer_state { |
| 136 | self.rect_pipeline.draw( |
| 137 | &mut render_pass, |
| 138 | rect_layer_state, |
| 139 | &self.per_frame_state.rect, |
| 140 | ); |
| 141 | } |
| 142 | |
| 143 | if let Some(image_layer_state) = &layer_state.image_layer_state { |
| 144 | self.image_pipeline.draw( |
| 145 | &mut render_pass, |
| 146 | image_layer_state, |
| 147 | &self.per_frame_state.image, |
| 148 | ); |
| 149 | } |
| 150 | |
| 151 | if let Some(glyph_layer_state) = &layer_state.glyph_layer_state { |
| 152 | self.glyph_pipeline.draw( |
| 153 | &mut render_pass, |
| 154 | glyph_layer_state, |
| 155 | &self.per_frame_state.glyph, |
| 156 | ); |
| 157 | } |
| 158 | } |
| 159 | } |
| 160 | |
| 161 | fn set_scissor_rect(render_pass: &mut RenderPass<'_>, scissor_rect_bounds: RectF) { |
| 162 | // Round the corners independently and derive width/height from those. Rounding origin and |
| 163 | // size independently can produce a rect that extends beyond the surface when the origin |
| 164 | // rounds up and the size also rounds up. |
| 165 | let origin_x = scissor_rect_bounds.origin_x().round() as u32; |
| 166 | let origin_y = scissor_rect_bounds.origin_y().round() as u32; |
| 167 | let max_x = scissor_rect_bounds.max_x().round() as u32; |
| 168 | let max_y = scissor_rect_bounds.max_y().round() as u32; |
| 169 | let width = max_x.saturating_sub(origin_x); |
| 170 | let height = max_y.saturating_sub(origin_y); |
| 171 | |
| 172 | // wgpu runtime assertions will fail if a scissor rect is set with a 0 width or height. See |
| 173 | // https://github.com/gfx-rs/wgpu/issues/1750 |
| 174 | if height != 0 && width != 0 { |
| 175 | render_pass.set_scissor_rect(origin_x, origin_y, width, height); |
| 176 | } |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | impl Drop for Frame<'_> { |
| 181 | fn drop(&mut self) { |
| 182 | // Let the image pipeline know that we've finished the frame so it can |
| 183 | // perform cache cleanup. |
| 184 | self.image_pipeline.end_frame(); |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | /// State for rendering a given [`Layer`] onto the screen. |
| 189 | struct LayerState<'a> { |
| 190 | layer: &'a Layer, |
| 191 | rect_layer_state: Option<rect::LayerState>, |
| 192 | glyph_layer_state: Option<glyph::LayerState>, |
| 193 | image_layer_state: Option<image::LayerState>, |
| 194 | } |
| 195 |