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