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-ui-renderer/src/rendering/wgpu/renderer/rect.rs
1use crate::rendering::get_best_dash_gap;
2use crate::rendering::wgpu::shader_types::BorderWidth;
3use crate::rendering::wgpu::{resources, shader_types};
4use crate::scene::Layer;
5use crate::Scene;
6use pathfinder_color::ColorU;
7use pathfinder_geometry::rect::RectF;
8use pathfinder_geometry::vector::vec2f;
9use std::borrow::Cow;
10use std::sync::{atomic::AtomicBool, Arc};
11use wgpu::util::BufferInitDescriptor;
12use wgpu::{BindGroupLayout, ColorTargetState, Device, RenderPass, RenderPipeline};
13 
14use super::util::create_buffer_init;
15 
16pub(super) struct Pipeline {
17 render_pipeline: RenderPipeline,
18}
19 
20#[derive(Default)]
21pub(super) struct PerFrameState {
22 rect_data: Vec<shader_types::RectData>,
23 buffer: Option<wgpu::Buffer>,
24}
25 
26pub(super) struct LayerState {
27 start_offset: usize,
28 len: usize,
29}
30 
31impl 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