StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.
| 1 | use crate::rendering::get_best_dash_gap; |
| 2 | use crate::rendering::wgpu::shader_types::BorderWidth; |
| 3 | use crate::rendering::wgpu::{resources, shader_types}; |
| 4 | use crate::scene::Layer; |
| 5 | use crate::Scene; |
| 6 | use pathfinder_color::ColorU; |
| 7 | use pathfinder_geometry::rect::RectF; |
| 8 | use pathfinder_geometry::vector::vec2f; |
| 9 | use std::borrow::Cow; |
| 10 | use std::sync::{atomic::AtomicBool, Arc}; |
| 11 | use wgpu::util::BufferInitDescriptor; |
| 12 | use wgpu::{BindGroupLayout, ColorTargetState, Device, RenderPass, RenderPipeline}; |
| 13 | |
| 14 | use super::util::create_buffer_init; |
| 15 | |
| 16 | pub(super) struct Pipeline { |
| 17 | render_pipeline: RenderPipeline, |
| 18 | } |
| 19 | |
| 20 | #[derive(Default)] |
| 21 | pub(super) struct PerFrameState { |
| 22 | rect_data: Vec<shader_types::RectData>, |
| 23 | buffer: Option<wgpu::Buffer>, |
| 24 | } |
| 25 | |
| 26 | pub(super) struct LayerState { |
| 27 | start_offset: usize, |
| 28 | len: usize, |
| 29 | } |
| 30 | |
| 31 | impl Pipeline { |
| 32 | pub(super) fn new( |
| 33 | uniform_bind_group_layout: &BindGroupLayout, |
| 34 | device: &Device, |
| 35 | color_target: ColorTargetState, |
| 36 | ) -> Self { |
| 37 | let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { |
| 38 | label: Some("Rect Shader"), |
| 39 | source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!( |
| 40 | "../shaders/rect_shader.wgsl" |
| 41 | ))), |
| 42 | }); |
| 43 | |
| 44 | let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { |
| 45 | label: Some("Rect pipeline layout"), |
| 46 | bind_group_layouts: &[Some(uniform_bind_group_layout)], |
| 47 | immediate_size: 0, |
| 48 | }); |
| 49 | |
| 50 | let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { |
| 51 | label: Some("Rect render pipeline"), |
| 52 | layout: Some(&pipeline_layout), |
| 53 | vertex: wgpu::VertexState { |
| 54 | module: &shader, |
| 55 | entry_point: Some("vs_main"), |
| 56 | buffers: &[shader_types::Vertex::desc(), shader_types::RectData::desc()], |
| 57 | compilation_options: Default::default(), |
| 58 | }, |
| 59 | fragment: Some(wgpu::FragmentState { |
| 60 | module: &shader, |
| 61 | entry_point: Some("rect_fs_main"), |
| 62 | targets: &[Some(color_target)], |
| 63 | compilation_options: Default::default(), |
| 64 | }), |
| 65 | primitive: wgpu::PrimitiveState::default(), |
| 66 | depth_stencil: None, |
| 67 | multisample: wgpu::MultisampleState::default(), |
| 68 | multiview_mask: None, |
| 69 | // Don't use a pipeline cache. Most desktop GPU drivers have their own internal caches, |
| 70 | // so we are unlikely to get much value out of this for the platforms Warp supports. |
| 71 | cache: None, |
| 72 | }); |
| 73 | |
| 74 | Self { render_pipeline } |
| 75 | } |
| 76 | |
| 77 | pub(super) fn initialize_for_layer( |
| 78 | &self, |
| 79 | layer: &Layer, |
| 80 | scene: &Scene, |
| 81 | per_frame_state: &mut PerFrameState, |
| 82 | ) -> Option<LayerState> { |
| 83 | if layer.rects.is_empty() { |
| 84 | // It's a mac assertion error to create an empty metal buffer, so exit early |
| 85 | return None; |
| 86 | } |
| 87 | |
| 88 | let scale_factor = scene.scale_factor(); |
| 89 | let mut rect_instance_data = Vec::with_capacity(layer.rects.len()); |
| 90 | for rect in &layer.rects { |
| 91 | let bounds = rect.bounds * scale_factor; |
| 92 | |
| 93 | if let Some(drop_shadow) = rect.drop_shadow { |
| 94 | let sigma = drop_shadow.blur_radius * scale_factor; |
| 95 | let padding = drop_shadow.spread_radius * scale_factor; |
| 96 | let shadow_origin = bounds.origin() + drop_shadow.offset * scale_factor - padding; |
| 97 | let shadow_size = bounds.size() + vec2f(2. * padding, 2. * padding); |
| 98 | |
| 99 | let min_dimension = f32::min(shadow_size.x(), shadow_size.y()); |
| 100 | let corner_radius = crate::rendering::CornerRadius::from_ui_corner_radius( |
| 101 | rect.corner_radius, |
| 102 | scale_factor, |
| 103 | min_dimension, |
| 104 | ); |
| 105 | let bounds = RectF::new(shadow_origin, shadow_size); |
| 106 | let shadow_color = shader_types::Color { |
| 107 | start: vec2f(0., 0.).into(), |
| 108 | start_color: drop_shadow.color.into(), |
| 109 | end: vec2f(1., 0.).into(), |
| 110 | end_color: drop_shadow.color.into(), |
| 111 | }; |
| 112 | |
| 113 | let border_color = shader_types::Color { |
| 114 | start: vec2f(0., 0.).into(), |
| 115 | start_color: ColorU::transparent_black().into(), |
| 116 | end: vec2f(1., 0.).into(), |
| 117 | end_color: ColorU::transparent_black().into(), |
| 118 | }; |
| 119 | |
| 120 | rect_instance_data.push(shader_types::RectData::new( |
| 121 | bounds, |
| 122 | shadow_color, |
| 123 | border_color, |
| 124 | corner_radius.clone(), |
| 125 | BorderWidth::default(), |
| 126 | sigma, |
| 127 | padding, |
| 128 | 0., |
| 129 | vec2f(0., 0.), |
| 130 | )); |
| 131 | } |
| 132 | |
| 133 | let min_dimension = f32::min(bounds.height(), bounds.width()); |
| 134 | let corner_radius = crate::rendering::CornerRadius::from_ui_corner_radius( |
| 135 | rect.corner_radius, |
| 136 | scale_factor, |
| 137 | min_dimension, |
| 138 | ); |
| 139 | let background_color = shader_types::Color { |
| 140 | start: rect.background.start().into(), |
| 141 | start_color: (rect.background.start_color().into()), |
| 142 | end: rect.background.end().into(), |
| 143 | end_color: (rect.background.end_color().into()), |
| 144 | }; |
| 145 | |
| 146 | let border_color = shader_types::Color { |
| 147 | start: rect.border.color.start().into(), |
| 148 | start_color: (rect.border.color.start_color().into()), |
| 149 | end: rect.border.color.end().into(), |
| 150 | end_color: (rect.border.color.end_color().into()), |
| 151 | }; |
| 152 | |
| 153 | let border_width = shader_types::BorderWidth { |
| 154 | top: rect.border.top_width() * scale_factor, |
| 155 | right: rect.border.right_width() * scale_factor, |
| 156 | bottom: rect.border.bottom_width() * scale_factor, |
| 157 | left: rect.border.left_width() * scale_factor, |
| 158 | }; |
| 159 | |
| 160 | let dash = rect |
| 161 | .border |
| 162 | .dash |
| 163 | .map(|mut dash| { |
| 164 | dash.dash_length *= scale_factor; |
| 165 | dash.gap_length *= scale_factor; |
| 166 | dash |
| 167 | }) |
| 168 | .unwrap_or_default(); |
| 169 | let horizontal_gap = get_best_dash_gap(bounds.width(), dash); |
| 170 | let vertical_gap = get_best_dash_gap(bounds.height(), dash); |
| 171 | let gap_lengths = vec2f(horizontal_gap, vertical_gap); |
| 172 | |
| 173 | let rect_data = shader_types::RectData::new( |
| 174 | bounds, |
| 175 | background_color, |
| 176 | border_color, |
| 177 | corner_radius, |
| 178 | border_width, |
| 179 | 0., |
| 180 | 0., |
| 181 | dash.dash_length, |
| 182 | gap_lengths, |
| 183 | ); |
| 184 | rect_instance_data.push(rect_data); |
| 185 | } |
| 186 | |
| 187 | let start_offset = per_frame_state.rect_data.len(); |
| 188 | let len = rect_instance_data.len(); |
| 189 | per_frame_state.rect_data.append(&mut rect_instance_data); |
| 190 | |
| 191 | Some(LayerState { start_offset, len }) |
| 192 | } |
| 193 | |
| 194 | pub(super) fn finalize_per_frame_state( |
| 195 | per_frame_state: &mut PerFrameState, |
| 196 | device: &Device, |
| 197 | device_lost: &Arc<AtomicBool>, |
| 198 | ) { |
| 199 | per_frame_state.buffer = create_buffer_init( |
| 200 | device, |
| 201 | device_lost, |
| 202 | &BufferInitDescriptor { |
| 203 | label: Some("Rect instance buffer"), |
| 204 | contents: bytemuck::cast_slice(&per_frame_state.rect_data), |
| 205 | usage: wgpu::BufferUsages::VERTEX, |
| 206 | }, |
| 207 | ) |
| 208 | .ok(); |
| 209 | } |
| 210 | |
| 211 | pub(super) fn draw<'a>( |
| 212 | &'a self, |
| 213 | render_pass: &mut RenderPass<'a>, |
| 214 | layer_state: &LayerState, |
| 215 | per_frame_state: &'a PerFrameState, |
| 216 | ) { |
| 217 | let Some(buffer) = per_frame_state.buffer.as_ref() else { |
| 218 | return; |
| 219 | }; |
| 220 | |
| 221 | render_pass.set_pipeline(&self.render_pipeline); |
| 222 | render_pass.set_vertex_buffer(1, buffer.slice(..)); |
| 223 | |
| 224 | let end_offset = layer_state.start_offset + layer_state.len; |
| 225 | render_pass.draw_indexed( |
| 226 | 0..resources::quad::INDICES.len() as u32, |
| 227 | 0, |
| 228 | layer_state.start_offset as u32..end_offset as u32, |
| 229 | ); |
| 230 | } |
| 231 | } |
| 232 |