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: [f32; 3],
29 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 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
77impl 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 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 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 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 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 self.update_uniforms(gpu_queue, &uniforms);
291
292 self.update_vertices_for(gpu_queue, ndc_pos, ndc_size);
294
295 render_pass.draw_indexed(0..6, 0, 0..1);
297 }
298 }
299}