1mod command;
20use bytemuck::{Pod, Zeroable};
21use earcutr::earcut;
22use encase::ShaderType;
23use glam::Vec4;
24use log::error;
25use tessera_ui::{
26 PxPosition, PxSize,
27 renderer::DrawablePipeline,
28 wgpu::{self, include_wgsl, util::DeviceExt},
29};
30
31use crate::pipelines::pos_misc::pixel_to_ndc;
32
33use command::ShapeCommandComputed;
34
35pub use command::{RippleProps, ShadowProps, ShapeCommand};
36
37#[derive(ShaderType, Clone, Copy, Debug, PartialEq)]
62pub struct ShapeUniforms {
63 pub size_cr_border_width: Vec4,
64 pub primary_color: Vec4,
65 pub shadow_color: Vec4,
66 pub render_params: Vec4,
67 pub ripple_params: Vec4,
68 pub ripple_color: Vec4,
69 pub g2_k_value: f32,
70}
71
72#[repr(C)]
85#[derive(Copy, Clone, Debug, Pod, Zeroable, PartialEq)]
86pub struct ShapeVertex {
87 pub position: [f32; 3],
89 pub color: [f32; 3],
91 pub local_pos: [f32; 2],
93}
94
95impl ShapeVertex {
96 const ATTR: [wgpu::VertexAttribute; 3] =
102 wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Float32x2];
103
104 fn new(pos: [f32; 2], color: [f32; 3], local_pos: [f32; 2]) -> Self {
106 Self {
107 position: [pos[0], pos[1], 0.0],
108 color,
109 local_pos,
110 }
111 }
112
113 fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
115 wgpu::VertexBufferLayout {
116 array_stride: core::mem::size_of::<ShapeVertex>() as wgpu::BufferAddress,
117 step_mode: wgpu::VertexStepMode::Vertex,
118 attributes: &Self::ATTR,
119 }
120 }
121}
122
123pub struct ShapeVertexData<'a> {
130 pub polygon_vertices: &'a [[f32; 2]],
131 pub vertex_colors: &'a [[f32; 3]],
132 pub vertex_local_pos: &'a [[f32; 2]],
133}
134
135pub struct ShapePipeline {
145 pipeline: wgpu::RenderPipeline,
146 uniform_buffer: wgpu::Buffer,
147 #[allow(unused)]
148 bind_group_layout: wgpu::BindGroupLayout,
149 bind_group: wgpu::BindGroup,
150 shape_uniform_alignment: u32,
151 current_shape_uniform_offset: u32,
152 max_shape_uniform_buffer_offset: u32,
153}
154
155pub const MAX_CONCURRENT_SHAPES: wgpu::BufferAddress = 256;
157
158impl ShapePipeline {
159 pub fn new(gpu: &wgpu::Device, config: &wgpu::SurfaceConfiguration, sample_count: u32) -> Self {
160 let shader = gpu.create_shader_module(include_wgsl!("shape/shape.wgsl"));
161
162 let uniform_alignment =
163 gpu.limits().min_uniform_buffer_offset_alignment as wgpu::BufferAddress;
164 let size_of_shape_uniforms = std::mem::size_of::<ShapeUniforms>() as wgpu::BufferAddress;
165 let aligned_size_of_shape_uniforms =
166 wgpu::util::align_to(size_of_shape_uniforms, uniform_alignment);
167
168 let uniform_buffer = gpu.create_buffer(&wgpu::BufferDescriptor {
169 label: Some("Shape Uniform Buffer"),
170 size: MAX_CONCURRENT_SHAPES * aligned_size_of_shape_uniforms,
171 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
172 mapped_at_creation: false,
173 });
174
175 let bind_group_layout = gpu.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
176 entries: &[wgpu::BindGroupLayoutEntry {
177 binding: 0,
178 visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
179 ty: wgpu::BindingType::Buffer {
180 ty: wgpu::BufferBindingType::Uniform,
181 has_dynamic_offset: true, min_binding_size: wgpu::BufferSize::new(
183 std::mem::size_of::<ShapeUniforms>() as _
184 ),
185 },
186 count: None,
187 }],
188 label: Some("shape_bind_group_layout"),
189 });
190
191 let bind_group = gpu.create_bind_group(&wgpu::BindGroupDescriptor {
192 layout: &bind_group_layout,
193 entries: &[wgpu::BindGroupEntry {
194 binding: 0,
195 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
196 buffer: &uniform_buffer,
197 offset: 0, size: wgpu::BufferSize::new(std::mem::size_of::<ShapeUniforms>() as _),
199 }),
200 }],
201 label: Some("shape_bind_group"),
202 });
203
204 let pipeline_layout = gpu.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
205 label: Some("Shape Pipeline Layout"),
206 bind_group_layouts: &[&bind_group_layout],
207 push_constant_ranges: &[],
208 });
209
210 let pipeline = gpu.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
211 label: Some("Shape Pipeline"),
212 layout: Some(&pipeline_layout),
213 vertex: wgpu::VertexState {
214 module: &shader,
215 entry_point: Some("vs_main"),
216 buffers: &[ShapeVertex::desc()],
217 compilation_options: Default::default(),
218 },
219 primitive: wgpu::PrimitiveState {
220 topology: wgpu::PrimitiveTopology::TriangleList,
221 strip_index_format: None,
222 front_face: wgpu::FrontFace::Ccw,
223 cull_mode: Some(wgpu::Face::Back),
224 unclipped_depth: false,
225 polygon_mode: wgpu::PolygonMode::Fill,
226 conservative: false,
227 },
228 depth_stencil: None,
229 multisample: wgpu::MultisampleState {
230 count: sample_count,
231 mask: !0,
232 alpha_to_coverage_enabled: false,
233 },
234 fragment: Some(wgpu::FragmentState {
235 module: &shader,
236 entry_point: Some("fs_main"),
237 compilation_options: Default::default(),
238 targets: &[Some(wgpu::ColorTargetState {
239 format: config.format,
240 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
241 write_mask: wgpu::ColorWrites::ALL,
242 })],
243 }),
244 multiview: None,
245 cache: None,
246 });
247
248 let size_of_shape_uniforms = std::mem::size_of::<ShapeUniforms>() as u32;
249 let alignment = gpu.limits().min_uniform_buffer_offset_alignment;
250 let shape_uniform_alignment =
251 wgpu::util::align_to(size_of_shape_uniforms, alignment) as u32;
252
253 let max_shape_uniform_buffer_offset =
254 (MAX_CONCURRENT_SHAPES as u32 - 1) * shape_uniform_alignment;
255
256 Self {
257 pipeline,
258 uniform_buffer,
259 bind_group_layout,
260 bind_group,
261 shape_uniform_alignment,
262 current_shape_uniform_offset: 0,
263 max_shape_uniform_buffer_offset,
264 }
265 }
266
267 fn draw_to_pass(
268 &self,
269 gpu: &wgpu::Device,
270 gpu_queue: &wgpu::Queue,
271 render_pass: &mut wgpu::RenderPass<'_>,
272 vertex_data_in: &ShapeVertexData,
273 uniforms: &ShapeUniforms,
274 dynamic_offset: u32,
275 ) {
276 let flat_polygon_vertices: Vec<f64> = vertex_data_in
277 .polygon_vertices
278 .iter()
279 .flat_map(|[x, y]| vec![*x as f64, *y as f64])
280 .collect();
281
282 let indices = earcut(&flat_polygon_vertices, &[], 2).unwrap_or_else(|e| {
283 error!("Earcut error: {e:?}");
284 Vec::new()
285 });
286
287 if indices.is_empty() && !vertex_data_in.polygon_vertices.is_empty() {
288 return;
289 }
290
291 let vertex_data: Vec<ShapeVertex> = indices
292 .iter()
293 .map(|&i| {
294 if i < vertex_data_in.polygon_vertices.len()
295 && i < vertex_data_in.vertex_colors.len()
296 && i < vertex_data_in.vertex_local_pos.len()
297 {
298 ShapeVertex::new(
299 vertex_data_in.polygon_vertices[i],
300 vertex_data_in.vertex_colors[i],
301 vertex_data_in.vertex_local_pos[i],
302 )
303 } else {
304 error!("Warning: Earcut index {i} out of bounds for input arrays.");
305 if !vertex_data_in.polygon_vertices.is_empty()
307 && !vertex_data_in.vertex_colors.is_empty()
308 && !vertex_data_in.vertex_local_pos.is_empty()
309 {
310 ShapeVertex::new(
311 vertex_data_in.polygon_vertices[0],
312 vertex_data_in.vertex_colors[0],
313 vertex_data_in.vertex_local_pos[0],
314 )
315 } else {
316 ShapeVertex::new([0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0])
319 }
321 }
322 })
323 .collect();
324
325 if vertex_data.is_empty() {
326 return;
327 }
328
329 let vertex_buffer = gpu.create_buffer_init(&wgpu::util::BufferInitDescriptor {
330 label: Some("Triangulated Vertex Buffer"),
331 contents: bytemuck::cast_slice(&vertex_data),
332 usage: wgpu::BufferUsages::VERTEX,
333 });
334
335 let mut buffer = encase::UniformBuffer::new(Vec::<u8>::new());
336 buffer.write(uniforms).unwrap();
337 let inner = buffer.into_inner();
338 gpu_queue.write_buffer(
339 &self.uniform_buffer,
340 dynamic_offset as wgpu::BufferAddress,
341 &inner,
342 );
343
344 render_pass.set_pipeline(&self.pipeline);
345 render_pass.set_bind_group(0, &self.bind_group, &[dynamic_offset]);
346 render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
347 render_pass.draw(0..vertex_data.len() as u32, 0..1);
348 }
349}
350
351#[allow(unused_variables)]
352impl DrawablePipeline<ShapeCommand> for ShapePipeline {
353 fn begin_frame(
354 &mut self,
355 _gpu: &wgpu::Device,
356 _gpu_queue: &wgpu::Queue,
357 _config: &wgpu::SurfaceConfiguration,
358 ) {
359 self.current_shape_uniform_offset = 0;
360 }
361
362 fn draw(
363 &mut self,
364 gpu: &wgpu::Device,
365 gpu_queue: &wgpu::Queue,
366 config: &wgpu::SurfaceConfiguration,
367 render_pass: &mut wgpu::RenderPass<'_>,
368 command: &ShapeCommand,
369 size: PxSize,
370 start_pos: PxPosition,
371 _scene_texture_view: &wgpu::TextureView,
372 ) {
373 let computed_command = ShapeCommandComputed::from_command(command.clone(), size, start_pos);
375 let positions: Vec<[f32; 2]> = computed_command
376 .vertices
377 .iter()
378 .map(|v| {
379 pixel_to_ndc(
380 PxPosition::from_f32_arr3(v.position),
381 [config.width, config.height],
382 )
383 })
384 .collect();
385 let colors: Vec<[f32; 3]> = computed_command.vertices.iter().map(|v| v.color).collect();
386 let local_positions: Vec<[f32; 2]> = computed_command
387 .vertices
388 .iter()
389 .map(|v| v.local_pos)
390 .collect();
391
392 let has_shadow = computed_command.uniforms.shadow_color[3] > 0.0
394 && computed_command.uniforms.render_params[2] > 0.0;
395
396 if has_shadow {
397 let dynamic_offset = self.current_shape_uniform_offset;
398 if dynamic_offset > self.max_shape_uniform_buffer_offset {
399 panic!(
400 "Shape uniform buffer overflow for shadow: offset {} > max {}",
401 dynamic_offset, self.max_shape_uniform_buffer_offset
402 );
403 }
404
405 let mut uniforms_for_shadow = computed_command.uniforms;
406 uniforms_for_shadow.render_params[3] = 2.0;
407
408 let vertex_data_for_shadow = ShapeVertexData {
409 polygon_vertices: &positions,
410 vertex_colors: &colors,
411 vertex_local_pos: &local_positions,
412 };
413
414 self.draw_to_pass(
415 gpu,
416 gpu_queue,
417 render_pass,
418 &vertex_data_for_shadow,
419 &uniforms_for_shadow,
420 dynamic_offset,
421 );
422 self.current_shape_uniform_offset += self.shape_uniform_alignment;
423 }
424
425 let dynamic_offset = self.current_shape_uniform_offset;
426 if dynamic_offset > self.max_shape_uniform_buffer_offset {
427 panic!(
428 "Shape uniform buffer overflow for object: offset {} > max {}",
429 dynamic_offset, self.max_shape_uniform_buffer_offset
430 );
431 }
432
433 let vertex_data_for_object = ShapeVertexData {
434 polygon_vertices: &positions,
435 vertex_colors: &colors,
436 vertex_local_pos: &local_positions,
437 };
438
439 self.draw_to_pass(
440 gpu,
441 gpu_queue,
442 render_pass,
443 &vertex_data_for_object,
444 &computed_command.uniforms,
445 dynamic_offset,
446 );
447 self.current_shape_uniform_offset += self.shape_uniform_alignment;
448 }
449}