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: [f32; 3],
28 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 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 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 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 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 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 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 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 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 {
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 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 gpu_queue.write_buffer(&self.vertex_buffer, 0, bytemuck::cast_slice(&vertices));
239
240 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}