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