tessera_ui_basic_components/pipelines/checkmark/
pipeline.rs

1use bytemuck::{Pod, Zeroable};
2use encase::{ShaderType, UniformBuffer};
3use glam::{Vec2, Vec4};
4use tessera_ui::{
5    PxPosition, PxSize,
6    renderer::DrawablePipeline,
7    wgpu::{self, include_wgsl, util::DeviceExt},
8};
9
10use crate::pipelines::pos_misc::pixel_to_ndc;
11
12use super::command::CheckmarkCommand;
13
14#[derive(ShaderType)]
15pub struct CheckmarkUniforms {
16    pub size: Vec2,
17    pub color: Vec4,
18    pub stroke_width: f32,
19    pub progress: f32,
20    pub padding: Vec2,
21}
22
23#[repr(C)]
24#[derive(Copy, Clone, Debug, Pod, Zeroable)]
25struct CheckmarkVertex {
26    /// Position of the vertex (x, y, z)
27    position: [f32; 3],
28    /// UV coordinates for the vertex
29    uv: [f32; 2],
30}
31
32impl CheckmarkVertex {
33    const ATTRIBUTES: [wgpu::VertexAttribute; 2] =
34        wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x2];
35
36    fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
37        wgpu::VertexBufferLayout {
38            array_stride: std::mem::size_of::<CheckmarkVertex>() as wgpu::BufferAddress,
39            step_mode: wgpu::VertexStepMode::Vertex,
40            attributes: &Self::ATTRIBUTES,
41        }
42    }
43}
44
45pub struct CheckmarkPipeline {
46    pipeline: wgpu::RenderPipeline,
47    uniform_buffer: wgpu::Buffer,
48    bind_group: wgpu::BindGroup,
49    vertex_buffer: wgpu::Buffer,
50    index_buffer: wgpu::Buffer,
51    uniform_staging_buffer: Vec<u8>,
52}
53
54impl CheckmarkPipeline {
55    pub fn new(gpu: &wgpu::Device, config: &wgpu::SurfaceConfiguration, sample_count: u32) -> Self {
56        let shader = gpu.create_shader_module(include_wgsl!("checkmark.wgsl"));
57
58        // Create uniform buffer
59        let uniform_buffer = gpu.create_buffer(&wgpu::BufferDescriptor {
60            label: Some("Checkmark Uniform Buffer"),
61            size: CheckmarkUniforms::min_size().get(),
62            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
63            mapped_at_creation: false,
64        });
65
66        // Create bind group layout
67        let bind_group_layout = gpu.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
68            label: Some("Checkmark Bind Group Layout"),
69            entries: &[wgpu::BindGroupLayoutEntry {
70                binding: 0,
71                visibility: wgpu::ShaderStages::FRAGMENT,
72                ty: wgpu::BindingType::Buffer {
73                    ty: wgpu::BufferBindingType::Uniform,
74                    has_dynamic_offset: false,
75                    min_binding_size: None,
76                },
77                count: None,
78            }],
79        });
80
81        // Create bind group
82        let bind_group = gpu.create_bind_group(&wgpu::BindGroupDescriptor {
83            label: Some("Checkmark Bind Group"),
84            layout: &bind_group_layout,
85            entries: &[wgpu::BindGroupEntry {
86                binding: 0,
87                resource: uniform_buffer.as_entire_binding(),
88            }],
89        });
90
91        // Create render pipeline layout
92        let pipeline_layout = gpu.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
93            label: Some("Checkmark Pipeline Layout"),
94            bind_group_layouts: &[&bind_group_layout],
95            push_constant_ranges: &[],
96        });
97
98        // Create render pipeline
99        let pipeline = gpu.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
100            label: Some("Checkmark Pipeline"),
101            layout: Some(&pipeline_layout),
102            vertex: wgpu::VertexState {
103                module: &shader,
104                entry_point: Some("vs_main"),
105                buffers: &[CheckmarkVertex::desc()],
106                compilation_options: wgpu::PipelineCompilationOptions::default(),
107            },
108            fragment: Some(wgpu::FragmentState {
109                module: &shader,
110                entry_point: Some("fs_main"),
111                targets: &[Some(wgpu::ColorTargetState {
112                    format: config.format,
113                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
114                    write_mask: wgpu::ColorWrites::ALL,
115                })],
116                compilation_options: wgpu::PipelineCompilationOptions::default(),
117            }),
118            primitive: wgpu::PrimitiveState {
119                topology: wgpu::PrimitiveTopology::TriangleList,
120                strip_index_format: None,
121                front_face: wgpu::FrontFace::Ccw,
122                cull_mode: Some(wgpu::Face::Back),
123                unclipped_depth: false,
124                polygon_mode: wgpu::PolygonMode::Fill,
125                conservative: false,
126            },
127            depth_stencil: None,
128            multisample: wgpu::MultisampleState {
129                count: sample_count,
130                mask: !0,
131                alpha_to_coverage_enabled: false,
132            },
133            multiview: None,
134            cache: None,
135        });
136
137        // Create quad vertices (two triangles forming a rectangle)
138        let vertices = [
139            CheckmarkVertex {
140                position: [-1.0, -1.0, 0.0],
141                uv: [0.0, 1.0],
142            },
143            CheckmarkVertex {
144                position: [1.0, -1.0, 0.0],
145                uv: [1.0, 1.0],
146            },
147            CheckmarkVertex {
148                position: [1.0, 1.0, 0.0],
149                uv: [1.0, 0.0],
150            },
151            CheckmarkVertex {
152                position: [-1.0, 1.0, 0.0],
153                uv: [0.0, 0.0],
154            },
155        ];
156
157        let indices: [u16; 6] = [0, 1, 2, 2, 3, 0];
158
159        let vertex_buffer = gpu.create_buffer_init(&wgpu::util::BufferInitDescriptor {
160            label: Some("Checkmark Vertex Buffer"),
161            contents: bytemuck::cast_slice(&vertices),
162            usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
163        });
164
165        let index_buffer = gpu.create_buffer_init(&wgpu::util::BufferInitDescriptor {
166            label: Some("Checkmark Index Buffer"),
167            contents: bytemuck::cast_slice(&indices),
168            usage: wgpu::BufferUsages::INDEX,
169        });
170
171        Self {
172            pipeline,
173            uniform_buffer,
174            bind_group,
175            vertex_buffer,
176            index_buffer,
177            uniform_staging_buffer: vec![0; CheckmarkUniforms::min_size().get() as usize],
178        }
179    }
180}
181
182impl DrawablePipeline<CheckmarkCommand> for CheckmarkPipeline {
183    fn draw(
184        &mut self,
185        _gpu: &wgpu::Device,
186        gpu_queue: &wgpu::Queue,
187        config: &wgpu::SurfaceConfiguration,
188        render_pass: &mut wgpu::RenderPass<'_>,
189        command: &CheckmarkCommand,
190        size: PxSize,
191        start_pos: PxPosition,
192        _scene_texture_view: &wgpu::TextureView,
193    ) {
194        // Convert position and size to NDC coordinates
195        let ndc_pos = pixel_to_ndc(start_pos, [config.width, config.height]);
196        let ndc_size = [
197            size.width.to_f32() / config.width as f32 * 2.0,
198            size.height.to_f32() / config.height as f32 * 2.0,
199        ];
200
201        // Create uniforms
202        let uniforms = CheckmarkUniforms {
203            size: [size.width.to_f32(), size.height.to_f32()].into(),
204            color: command.color.to_array().into(),
205            stroke_width: command.stroke_width,
206            progress: command.progress,
207            padding: command.padding.into(),
208        };
209
210        // Update uniform buffer using the staging buffer
211        {
212            let mut buffer = UniformBuffer::new(&mut self.uniform_staging_buffer);
213            buffer.write(&uniforms).unwrap();
214        }
215        gpu_queue.write_buffer(&self.uniform_buffer, 0, &self.uniform_staging_buffer);
216
217        // Update vertex positions to match the actual position and size
218        let vertices = [
219            CheckmarkVertex {
220                position: [ndc_pos[0], ndc_pos[1] - ndc_size[1], 0.0],
221                uv: [0.0, 1.0],
222            },
223            CheckmarkVertex {
224                position: [ndc_pos[0] + ndc_size[0], ndc_pos[1] - ndc_size[1], 0.0],
225                uv: [1.0, 1.0],
226            },
227            CheckmarkVertex {
228                position: [ndc_pos[0] + ndc_size[0], ndc_pos[1], 0.0],
229                uv: [1.0, 0.0],
230            },
231            CheckmarkVertex {
232                position: [ndc_pos[0], ndc_pos[1], 0.0],
233                uv: [0.0, 0.0],
234            },
235        ];
236
237        // Update vertex buffer
238        gpu_queue.write_buffer(&self.vertex_buffer, 0, bytemuck::cast_slice(&vertices));
239
240        // Set pipeline and draw
241        render_pass.set_pipeline(&self.pipeline);
242        render_pass.set_bind_group(0, &self.bind_group, &[]);
243        render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
244        render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
245        render_pass.draw_indexed(0..6, 0, 0..1);
246    }
247}