avenger_wgpu/marks/
symbol.rs

1use crate::canvas::CanvasDimensions;
2use crate::error::AvengerWgpuError;
3use crate::marks::instanced_mark::{InstancedMarkBatch, InstancedMarkShader};
4use avenger::marks::path::PathTransform;
5use avenger::marks::symbol::SymbolMark;
6use itertools::izip;
7use lyon::lyon_tessellation::{
8    BuffersBuilder, FillVertex, FillVertexConstructor, StrokeVertex, StrokeVertexConstructor,
9};
10use lyon::tessellation::geometry_builder::VertexBuffers;
11use lyon::tessellation::{FillOptions, FillTessellator, StrokeOptions, StrokeTessellator};
12use wgpu::{Extent3d, VertexBufferLayout};
13
14const FILL_KIND: u32 = 0;
15const STROKE_KIND: u32 = 1;
16
17#[repr(C)]
18#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
19pub struct SymbolUniform {
20    pub size: [f32; 2],
21    pub origin: [f32; 2],
22    pub scale: f32,
23    _pad: [f32; 5],
24}
25
26impl SymbolUniform {
27    pub fn new(dimensions: CanvasDimensions, origin: [f32; 2]) -> Self {
28        Self {
29            size: dimensions.size,
30            scale: dimensions.scale,
31            origin,
32            _pad: [0.0, 0.0, 0.0, 0.0, 0.0],
33        }
34    }
35}
36
37#[repr(C)]
38#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
39pub struct SymbolVertex {
40    pub position: [f32; 2],
41    pub normal: [f32; 2],
42    pub kind: u32,
43    pub shape_index: u32,
44}
45
46const VERTEX_ATTRIBUTES: [wgpu::VertexAttribute; 4] = wgpu::vertex_attr_array![
47    0 => Float32x2,     // position
48    1 => Float32x2,     // normal
49    2 => Uint32,        // kind
50    3 => Uint32,        // shape_index
51];
52
53impl SymbolVertex {
54    pub fn desc() -> VertexBufferLayout<'static> {
55        VertexBufferLayout {
56            array_stride: std::mem::size_of::<SymbolVertex>() as wgpu::BufferAddress,
57            step_mode: wgpu::VertexStepMode::Vertex,
58            attributes: &VERTEX_ATTRIBUTES,
59        }
60    }
61}
62
63#[repr(C)]
64#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
65pub struct SymbolInstance {
66    pub position: [f32; 2],
67    pub fill_color: [f32; 4],
68    pub stroke_color: [f32; 4],
69    pub stroke_width: f32,
70    pub relative_scale: f32,
71    pub angle: f32,
72    pub shape_index: u32,
73}
74
75// First shader index (i.e. the 1 in `1 => Float...`) must be one greater than
76// the largest shader index used in VERTEX_ATTRIBUTES above
77const INSTANCE_ATTRIBUTES: [wgpu::VertexAttribute; 7] = wgpu::vertex_attr_array![
78    4 => Float32x2,     // position
79    5 => Float32x4,     // fill_color
80    6 => Float32x4,     // stroke_color
81    7 => Float32,       // stroke_width
82    8 => Float32,       // relative_scale
83    9 => Float32,       // angle
84    10 => Uint32,       // shape_index
85];
86
87impl SymbolInstance {
88    pub fn from_spec(
89        mark: &SymbolMark,
90        max_size: f32,
91    ) -> (Vec<SymbolInstance>, Option<image::DynamicImage>, Extent3d) {
92        let max_scale = max_size.sqrt();
93        let stroke_width = mark.stroke_width.unwrap_or(0.0);
94        let mut instances: Vec<SymbolInstance> = Vec::new();
95        for (x, y, fill, size, stroke, angle, shape_index) in izip!(
96            mark.x_iter(),
97            mark.y_iter(),
98            mark.fill_iter(),
99            mark.size_iter(),
100            mark.stroke_iter(),
101            mark.angle_iter(),
102            mark.shape_index_iter(),
103        ) {
104            instances.push(SymbolInstance {
105                position: [*x, *y],
106                fill_color: fill.color_or_transparent(),
107                stroke_color: stroke.color_or_transparent(),
108                stroke_width,
109                relative_scale: (*size).sqrt() / max_scale,
110                angle: *angle,
111                shape_index: (*shape_index) as u32,
112            });
113        }
114
115        (instances, None, Default::default())
116    }
117}
118
119pub struct SymbolShader {
120    verts: Vec<SymbolVertex>,
121    indices: Vec<u16>,
122    instances: Vec<SymbolInstance>,
123    uniform: SymbolUniform,
124    batches: Vec<InstancedMarkBatch>,
125    texture_size: Extent3d,
126    shader: String,
127    vertex_entry_point: String,
128    fragment_entry_point: String,
129}
130
131impl SymbolShader {
132    pub fn from_symbol_mark(
133        mark: &SymbolMark,
134        dimensions: CanvasDimensions,
135        origin: [f32; 2],
136    ) -> Result<Self, AvengerWgpuError> {
137        let shapes = &mark.shapes;
138        let max_size = mark.max_size();
139        let max_scale = max_size.sqrt();
140        let mut verts: Vec<SymbolVertex> = Vec::new();
141        let mut indices: Vec<u16> = Vec::new();
142        for (shape_index, shape) in shapes.iter().enumerate() {
143            let shape_index = shape_index as u32;
144            let path = shape.as_path();
145
146            // Scale path to match the size of the largest symbol so tesselation looks nice
147            let scaled_path = path
148                .as_ref()
149                .clone()
150                .transformed(&PathTransform::scale(max_scale, max_scale));
151
152            let mut buffers: VertexBuffers<SymbolVertex, u16> = VertexBuffers::new();
153            let mut builder = BuffersBuilder::new(&mut buffers, VertexPositions { shape_index });
154
155            // Tesselate fill
156            let mut fill_tessellator = FillTessellator::new();
157            let fill_options = FillOptions::default().with_tolerance(0.1);
158            fill_tessellator.tessellate_path(&scaled_path, &fill_options, &mut builder)?;
159
160            // Tesselate stroke
161            if mark.stroke_width.is_some() {
162                let mut stroke_tessellator = StrokeTessellator::new();
163                let stroke_options = StrokeOptions::default()
164                    .with_tolerance(0.1)
165                    .with_line_width(1.0);
166                stroke_tessellator.tessellate_path(&scaled_path, &stroke_options, &mut builder)?;
167            }
168
169            let index_offset = verts.len() as u16;
170            verts.extend(buffers.vertices);
171            indices.extend(buffers.indices.into_iter().map(|i| i + index_offset));
172        }
173        let (instances, img, texture_size) = SymbolInstance::from_spec(mark, max_size);
174        let batches = vec![InstancedMarkBatch {
175            instances_range: 0..instances.len() as u32,
176            image: img,
177        }];
178        Ok(Self {
179            verts,
180            indices,
181            instances,
182            uniform: SymbolUniform::new(dimensions, origin),
183            batches,
184            texture_size,
185            shader: include_str!("symbol.wgsl").into(),
186            vertex_entry_point: "vs_main".to_string(),
187            fragment_entry_point: "fs_main".to_string(),
188        })
189    }
190}
191
192impl InstancedMarkShader for SymbolShader {
193    type Instance = SymbolInstance;
194    type Vertex = SymbolVertex;
195    type Uniform = SymbolUniform;
196
197    fn verts(&self) -> &[Self::Vertex] {
198        self.verts.as_slice()
199    }
200
201    fn indices(&self) -> &[u16] {
202        self.indices.as_slice()
203    }
204
205    fn instances(&self) -> &[Self::Instance] {
206        self.instances.as_slice()
207    }
208
209    fn uniform(&self) -> Self::Uniform {
210        self.uniform
211    }
212
213    fn batches(&self) -> &[InstancedMarkBatch] {
214        self.batches.as_slice()
215    }
216
217    fn texture_size(&self) -> Extent3d {
218        self.texture_size
219    }
220
221    fn shader(&self) -> &str {
222        self.shader.as_str()
223    }
224
225    fn vertex_entry_point(&self) -> &str {
226        self.vertex_entry_point.as_str()
227    }
228
229    fn fragment_entry_point(&self) -> &str {
230        self.fragment_entry_point.as_str()
231    }
232
233    fn instance_desc(&self) -> wgpu::VertexBufferLayout<'static> {
234        wgpu::VertexBufferLayout {
235            array_stride: std::mem::size_of::<SymbolInstance>() as wgpu::BufferAddress,
236            step_mode: wgpu::VertexStepMode::Instance,
237            attributes: &INSTANCE_ATTRIBUTES,
238        }
239    }
240
241    fn vertex_desc(&self) -> VertexBufferLayout<'static> {
242        SymbolVertex::desc()
243    }
244}
245
246pub struct VertexPositions {
247    shape_index: u32,
248}
249
250impl FillVertexConstructor<SymbolVertex> for VertexPositions {
251    fn new_vertex(&mut self, vertex: FillVertex) -> SymbolVertex {
252        // - y-coordinate is negated to flip vertically from SVG coordinates (top-left)
253        // to canvas coordinates (bottom-left).
254        SymbolVertex {
255            position: [vertex.position().x, -vertex.position().y],
256            normal: [0.0, 0.0],
257            kind: FILL_KIND,
258            shape_index: self.shape_index,
259        }
260    }
261}
262
263impl StrokeVertexConstructor<SymbolVertex> for VertexPositions {
264    fn new_vertex(&mut self, vertex: StrokeVertex) -> SymbolVertex {
265        // - y-coordinate is negated to flip vertically from SVG coordinates (top-left)
266        // to canvas coordinates (bottom-left).
267        SymbolVertex {
268            position: [vertex.position().x, -vertex.position().y],
269            normal: [vertex.normal().x, -vertex.normal().y],
270            kind: STROKE_KIND,
271            shape_index: self.shape_index,
272        }
273    }
274}