avenger_wgpu/marks/
multi.rs

1use crate::canvas::{CanvasDimensions, TextBuildCtor};
2use crate::error::AvengerWgpuError;
3
4use crate::marks::gradient::{to_color_or_gradient_coord, GradientAtlasBuilder};
5use crate::marks::image::ImageAtlasBuilder;
6use avenger::marks::area::{AreaMark, AreaOrientation};
7use avenger::marks::image::ImageMark;
8use avenger::marks::line::LineMark;
9use avenger::marks::path::{PathMark, PathTransform};
10use avenger::marks::rect::RectMark;
11use avenger::marks::rule::RuleMark;
12use avenger::marks::symbol::SymbolMark;
13use avenger::marks::trail::TrailMark;
14use avenger::marks::value::{ColorOrGradient, ImageAlign, ImageBaseline, StrokeCap, StrokeJoin};
15use etagere::euclid::UnknownUnit;
16use image::DynamicImage;
17use itertools::izip;
18use lyon::algorithms::aabb::bounding_box;
19use lyon::algorithms::measure::{PathMeasurements, PathSampler, SampleType};
20use lyon::geom::euclid::{Point2D, Vector2D};
21use lyon::geom::{Angle, Box2D, Point};
22use lyon::lyon_tessellation::{
23    AttributeIndex, BuffersBuilder, FillOptions, FillTessellator, FillVertex,
24    FillVertexConstructor, LineCap, LineJoin, StrokeOptions, StrokeTessellator, StrokeVertex,
25    StrokeVertexConstructor, VertexBuffers,
26};
27use lyon::path::builder::SvgPathBuilder;
28use lyon::path::builder::{BorderRadii, WithSvg};
29use lyon::path::path::BuilderImpl;
30use lyon::path::{Path, Winding};
31use std::ops::{Mul, Neg, Range};
32
33use wgpu::util::DeviceExt;
34use wgpu::{
35    BindGroup, BindGroupLayout, CommandBuffer, Device, Extent3d, Queue, TextureFormat, TextureView,
36    VertexBufferLayout,
37};
38
39// Import rayon prelude as required by par_izip.
40use crate::marks::text::{TextAtlasBuilderTrait, TextInstance};
41
42use avenger::marks::arc::ArcMark;
43use avenger::marks::group::Clip;
44use avenger::marks::text::TextMark;
45
46#[cfg(feature = "rayon")]
47use {crate::par_izip, rayon::prelude::*};
48
49pub const GRADIENT_TEXTURE_CODE: f32 = -1.0;
50pub const IMAGE_TEXTURE_CODE: f32 = -2.0;
51pub const TEXT_TEXTURE_CODE: f32 = -3.0;
52
53const NORMALIZED_SYMBOL_STROKE_WIDTH: f32 = 0.1;
54
55#[repr(C)]
56#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
57pub struct MultiUniform {
58    pub size: [f32; 2],
59    pub scale: f32,
60    _pad: [f32; 1],
61}
62
63#[repr(C)]
64#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
65pub struct MultiVertex {
66    pub position: [f32; 2],
67    pub color: [f32; 4],
68    pub top_left: [f32; 2],
69    pub bottom_right: [f32; 2],
70}
71
72const VERTEX_ATTRIBUTES: [wgpu::VertexAttribute; 4] = wgpu::vertex_attr_array![
73    0 => Float32x2,     // position
74    1 => Float32x4,     // color
75    2 => Float32x2,     // top_left
76    3 => Float32x2,     // bottom_right
77];
78
79impl MultiVertex {
80    pub fn desc() -> VertexBufferLayout<'static> {
81        VertexBufferLayout {
82            array_stride: std::mem::size_of::<MultiVertex>() as wgpu::BufferAddress,
83            step_mode: wgpu::VertexStepMode::Vertex,
84            attributes: &VERTEX_ATTRIBUTES,
85        }
86    }
87}
88
89#[derive(Clone)]
90pub struct MultiMarkBatch {
91    pub indices_range: Range<u32>,
92    pub clip: Clip,
93    pub clip_indices_range: Option<Range<u32>>,
94    pub image_atlas_index: Option<usize>,
95    pub gradient_atlas_index: Option<usize>,
96    pub text_atlas_index: Option<usize>,
97}
98
99pub struct MultiMarkRenderer {
100    verts_inds: Vec<(Vec<MultiVertex>, Vec<u32>)>,
101    clip_verts_inds: Vec<(Vec<MultiVertex>, Vec<u32>)>,
102    batches: Vec<MultiMarkBatch>,
103    uniform: MultiUniform,
104    gradient_atlas_builder: GradientAtlasBuilder,
105    image_atlas_builder: ImageAtlasBuilder,
106    text_atlas_builder: Box<dyn TextAtlasBuilderTrait>,
107    dimensions: CanvasDimensions,
108}
109
110impl MultiMarkRenderer {
111    pub fn new(
112        dimensions: CanvasDimensions,
113        text_atlas_builder_ctor: Option<TextBuildCtor>,
114    ) -> Self {
115        let text_atlas_builder = if let Some(text_atlas_builder_ctor) = text_atlas_builder_ctor {
116            text_atlas_builder_ctor()
117        } else {
118            cfg_if::cfg_if! {
119                if #[cfg(feature = "cosmic-text")] {
120                    use crate::marks::cosmic::CosmicTextRasterizer;
121                    use crate::marks::text::TextAtlasBuilder;
122                    use std::sync::Arc;
123                    let inner_text_atlas_builder: Box<dyn TextAtlasBuilderTrait> = Box::new(TextAtlasBuilder::new(Arc::new(CosmicTextRasterizer)));
124                } else {
125                    use crate::marks::text::NullTextAtlasBuilder;
126                    let inner_text_atlas_builder: Box<dyn TextAtlasBuilderTrait> = Box::new(NullTextAtlasBuilder);
127                }
128            };
129            inner_text_atlas_builder
130        };
131
132        Self {
133            verts_inds: vec![],
134            clip_verts_inds: vec![],
135            batches: vec![],
136            dimensions,
137            uniform: MultiUniform {
138                size: dimensions.size,
139                scale: dimensions.scale,
140                _pad: [0.0],
141            },
142            gradient_atlas_builder: GradientAtlasBuilder::new(),
143            image_atlas_builder: ImageAtlasBuilder::new(),
144            text_atlas_builder,
145        }
146    }
147
148    fn add_clip_path(
149        &mut self,
150        clip: &Clip,
151        should_clip: bool,
152    ) -> Result<Option<Range<u32>>, AvengerWgpuError> {
153        if !should_clip {
154            return Ok(None);
155        }
156
157        if let Clip::Path(path) = &clip {
158            // Tesselate path
159            let bbox = bounding_box(path);
160
161            // Create vertex/index buffer builder
162            let mut buffers: VertexBuffers<MultiVertex, u32> = VertexBuffers::new();
163            let mut builder = BuffersBuilder::new(
164                &mut buffers,
165                crate::marks::multi::VertexPositions {
166                    fill: [0.0, 0.0, 0.0, 1.0],
167                    stroke: [0.0, 0.0, 0.0, 0.0],
168                    top_left: bbox.min.to_array(),
169                    bottom_right: bbox.max.to_array(),
170                },
171            );
172
173            // Tesselate fill
174            let mut fill_tessellator = FillTessellator::new();
175            let fill_options = FillOptions::default().with_tolerance(0.05);
176            fill_tessellator.tessellate_path(path, &fill_options, &mut builder)?;
177
178            let start_index = self.num_clip_indices() as u32;
179            self.clip_verts_inds
180                .push((buffers.vertices, buffers.indices));
181            let end_index = self.num_clip_indices() as u32;
182            Ok(Some(start_index..end_index))
183        } else {
184            Ok(None)
185        }
186    }
187
188    #[tracing::instrument(skip_all)]
189    pub fn add_rule_mark(
190        &mut self,
191        mark: &RuleMark,
192        origin: [f32; 2],
193        clip: &Clip,
194    ) -> Result<(), AvengerWgpuError> {
195        let (gradient_atlas_index, grad_coords) = self
196            .gradient_atlas_builder
197            .register_gradients(&mark.gradients);
198
199        let verts_inds = if let Some(stroke_dash_iter) = mark.stroke_dash_iter() {
200            izip!(
201                stroke_dash_iter,
202                mark.x0_iter(),
203                mark.y0_iter(),
204                mark.x1_iter(),
205                mark.y1_iter(),
206                mark.stroke_iter(),
207                mark.stroke_width_iter(),
208                mark.stroke_cap_iter(),
209            ).map(|(stroke_dash, x0, y0, x1, y1, stroke, stroke_width, cap)| -> Result<(Vec<MultiVertex>, Vec<u32>), AvengerWgpuError> {
210                // Next index into stroke_dash array
211                let mut dash_idx = 0;
212
213                // Distance along line from (x0,y0) to (x1,y1) where the next dash will start
214                let mut start_dash_dist: f32 = 0.0;
215
216                // Length of the line from (x0,y0) to (x1,y1)
217                let rule_len = ((x1 - x0).powi(2) + (y1 - y0).powi(2)).sqrt();
218
219                // Coponents of unit vector along (x0,y0) to (x1,y1)
220                let xhat = (x1 - x0) / rule_len;
221                let yhat = (y1 - y0) / rule_len;
222
223                // Whether the next dash length represents a drawn dash (draw == true)
224                // or a gap (draw == false)
225                let mut draw = true;
226
227                // Init path builder
228                let mut path_builder = lyon::path::Path::builder().with_svg();
229
230                while start_dash_dist < rule_len {
231                    let end_dash_dist = if start_dash_dist + stroke_dash[dash_idx] >= rule_len {
232                        // The final dash/gap should be truncated to the end of the rule
233                        rule_len
234                    } else {
235                        // The dash/gap fits entirely in the rule
236                        start_dash_dist + stroke_dash[dash_idx]
237                    };
238
239                    if draw {
240                        let dash_x0 = x0 + xhat * start_dash_dist;
241                        let dash_y0 = y0 + yhat * start_dash_dist;
242                        let dash_x1 = x0 + xhat * end_dash_dist;
243                        let dash_y1 = y0 + yhat * end_dash_dist;
244
245                        path_builder.move_to(Point::new(dash_x0 + origin[0], dash_y0 + origin[1]));
246                        path_builder.line_to(Point::new(dash_x1 + origin[0], dash_y1 + origin[1]));
247                    }
248
249                    // update start dist for next dash/gap
250                    start_dash_dist = end_dash_dist;
251
252                    // increment index and cycle back to start of start of dash array
253                    dash_idx = (dash_idx + 1) % stroke_dash.len();
254
255                    // Alternate between drawn dash and gap
256                    draw = !draw;
257                }
258
259                let path = path_builder.build();
260                let bbox = bounding_box(&path);
261
262                // Create vertex/index buffer builder
263                let mut buffers: VertexBuffers<MultiVertex, u32> = VertexBuffers::new();
264                let mut builder = BuffersBuilder::new(
265                    &mut buffers,
266                    VertexPositions {
267                        fill: [0.0, 0.0, 0.0, 0.0],
268                        stroke: to_color_or_gradient_coord(stroke, &grad_coords),
269                        top_left: bbox.min.to_array(),
270                        bottom_right: bbox.max.to_array(),
271                    },
272                );
273
274                // Tesselate stroke
275                let mut stroke_tessellator = StrokeTessellator::new();
276                let stroke_options = StrokeOptions::default()
277                    .with_tolerance(0.05)
278                    .with_line_join(LineJoin::Miter)
279                    .with_line_cap(match cap {
280                        StrokeCap::Butt => LineCap::Butt,
281                        StrokeCap::Round => LineCap::Round,
282                        StrokeCap::Square => LineCap::Square,
283                    })
284                    .with_line_width(*stroke_width);
285                stroke_tessellator.tessellate_path(&path, &stroke_options, &mut builder)?;
286                Ok((buffers.vertices, buffers.indices))
287            }).collect::<Result<Vec<_>, AvengerWgpuError>>()?
288        } else {
289            izip!(
290                mark.x0_iter(),
291                mark.y0_iter(),
292                mark.x1_iter(),
293                mark.y1_iter(),
294                mark.stroke_iter(),
295                mark.stroke_width_iter(),
296                mark.stroke_cap_iter(),
297            ).map(|(x0, y0, x1, y1, stroke, stroke_width, cap)| -> Result<(Vec<MultiVertex>, Vec<u32>), AvengerWgpuError> {
298                let mut path_builder = lyon::path::Path::builder().with_svg();
299                path_builder.move_to(Point::new(*x0 + origin[0], *y0 + origin[1]));
300                path_builder.line_to(Point::new(*x1 + origin[0], *y1 + origin[1]));
301                let path = path_builder.build();
302                let bbox = bounding_box(&path);
303
304                // Create vertex/index buffer builder
305                let mut buffers: VertexBuffers<MultiVertex, u32> = VertexBuffers::new();
306                let mut builder = BuffersBuilder::new(
307                    &mut buffers,
308                    VertexPositions {
309                        fill: [0.0, 0.0, 0.0, 0.0],
310                        stroke: to_color_or_gradient_coord(stroke, &grad_coords),
311                        top_left: bbox.min.to_array(),
312                        bottom_right: bbox.max.to_array(),
313                    },
314                );
315
316                // Tesselate stroke
317                let mut stroke_tessellator = StrokeTessellator::new();
318                let stroke_options = StrokeOptions::default()
319                    .with_tolerance(0.05)
320                    .with_line_join(LineJoin::Miter)
321                    .with_line_cap(match cap {
322                        StrokeCap::Butt => LineCap::Butt,
323                        StrokeCap::Round => LineCap::Round,
324                        StrokeCap::Square => LineCap::Square,
325                    })
326                    .with_line_width(*stroke_width);
327                stroke_tessellator.tessellate_path(&path, &stroke_options, &mut builder)?;
328                Ok((buffers.vertices, buffers.indices))
329
330            }).collect::<Result<Vec<_>, AvengerWgpuError>>()?
331        };
332
333        let start_ind = self.num_indices();
334        let inds_len: usize = verts_inds.iter().map(|(_, i)| i.len()).sum();
335        let indices_range = (start_ind as u32)..((start_ind + inds_len) as u32);
336
337        let batch = MultiMarkBatch {
338            indices_range,
339            clip: clip.maybe_clip(mark.clip),
340            clip_indices_range: self.add_clip_path(clip, mark.clip)?,
341            image_atlas_index: None,
342            gradient_atlas_index,
343            text_atlas_index: None,
344        };
345
346        self.verts_inds.extend(verts_inds);
347        self.batches.push(batch);
348        Ok(())
349    }
350
351    #[tracing::instrument(skip_all)]
352    pub fn add_rect_mark(
353        &mut self,
354        mark: &RectMark,
355        origin: [f32; 2],
356        clip: &Clip,
357    ) -> Result<(), AvengerWgpuError> {
358        let (gradient_atlas_index, grad_coords) = self
359            .gradient_atlas_builder
360            .register_gradients(&mark.gradients);
361
362        let verts_inds = if mark.gradients.is_empty()
363            && mark.stroke_width.equals_scalar(0.0)
364            && mark.corner_radius.equals_scalar(0.0)
365        {
366            // Handle simple case of no stroke, rounded corners, or gradient. In this case we don't need
367            // lyon to perform the tesselation, which saves a bit of time. The contents of this loop are so
368            // fast that parallelization doesn't help.
369            let mut verts: Vec<MultiVertex> = Vec::with_capacity((mark.len * 4) as usize);
370            let mut indicies: Vec<u32> = Vec::with_capacity((mark.len * 6) as usize);
371
372            for (i, x, y, width, height, fill) in izip!(
373                0..mark.len,
374                mark.x_iter(),
375                mark.y_iter(),
376                mark.width_iter(),
377                mark.height_iter(),
378                mark.fill_iter()
379            ) {
380                let x0 = *x + origin[0];
381                let y0 = *y + origin[1];
382                let x1 = x0 + width;
383                let y1 = y0 + height;
384                let top_left = [x0, y0];
385                let bottom_right = [x1, y1];
386                let color = fill.color_or_transparent();
387                verts.push(MultiVertex {
388                    position: [x0, y0],
389                    color,
390                    top_left,
391                    bottom_right,
392                });
393                verts.push(MultiVertex {
394                    position: [x0, y1],
395                    color,
396                    top_left,
397                    bottom_right,
398                });
399                verts.push(MultiVertex {
400                    position: [x1, y1],
401                    color,
402                    top_left,
403                    bottom_right,
404                });
405                verts.push(MultiVertex {
406                    position: [x1, y0],
407                    color,
408                    top_left,
409                    bottom_right,
410                });
411                let offset = i * 4;
412                indicies.extend([
413                    offset,
414                    offset + 1,
415                    offset + 2,
416                    offset,
417                    offset + 2,
418                    offset + 3,
419                ])
420            }
421
422            vec![(verts, indicies)]
423        } else {
424            // General rects
425            let build_verts_inds =
426                |x: &f32,
427                 y: &f32,
428                 width: &f32,
429                 height: &f32,
430                 fill: &ColorOrGradient,
431                 stroke: &ColorOrGradient,
432                 stroke_width: &f32,
433                 corner_radius: &f32|
434                 -> Result<(Vec<MultiVertex>, Vec<u32>), AvengerWgpuError> {
435                    // Create rect path
436                    let mut path_builder = lyon::path::Path::builder();
437                    let x0 = *x + origin[0];
438                    let y0 = *y + origin[1];
439                    let x1 = x0 + width;
440                    let y1 = y0 + height;
441                    if *corner_radius > 0.0 {
442                        path_builder.add_rounded_rectangle(
443                            &Box2D::new(Point2D::new(x0, y0), Point2D::new(x1, y1)),
444                            &BorderRadii {
445                                top_left: *corner_radius,
446                                top_right: *corner_radius,
447                                bottom_left: *corner_radius,
448                                bottom_right: *corner_radius,
449                            },
450                            Winding::Positive,
451                        );
452                    } else {
453                        path_builder.add_rectangle(
454                            &Box2D::new(Point2D::new(x0, y0), Point2D::new(x1, y1)),
455                            Winding::Positive,
456                        );
457                    }
458
459                    // Apply transform to path
460                    let path = path_builder.build();
461                    let bbox = bounding_box(&path);
462
463                    // Create vertex/index buffer builder
464                    let mut buffers: VertexBuffers<MultiVertex, u32> = VertexBuffers::new();
465                    let mut builder = BuffersBuilder::new(
466                        &mut buffers,
467                        VertexPositions {
468                            fill: to_color_or_gradient_coord(fill, &grad_coords),
469                            stroke: to_color_or_gradient_coord(stroke, &grad_coords),
470                            top_left: bbox.min.to_array(),
471                            bottom_right: bbox.max.to_array(),
472                        },
473                    );
474
475                    // Tesselate fill
476                    let mut fill_tessellator = FillTessellator::new();
477                    let fill_options = FillOptions::default().with_tolerance(0.05);
478
479                    fill_tessellator.tessellate_path(&path, &fill_options, &mut builder)?;
480
481                    // Tesselate stroke
482                    if *stroke_width > 0.0 {
483                        let mut stroke_tessellator = StrokeTessellator::new();
484                        let stroke_options = StrokeOptions::default()
485                            .with_tolerance(0.05)
486                            .with_line_width(*stroke_width);
487                        stroke_tessellator.tessellate_path(&path, &stroke_options, &mut builder)?;
488                    }
489
490                    Ok((buffers.vertices, buffers.indices))
491                };
492
493            cfg_if::cfg_if! {
494                if #[cfg(feature = "rayon")] {
495                    par_izip!(
496                        mark.x_vec(),
497                        mark.y_vec(),
498                        mark.width_vec(),
499                        mark.height_vec(),
500                        mark.fill_vec(),
501                        mark.stroke_vec(),
502                        mark.stroke_width_vec(),
503                        mark.corner_radius_vec(),
504                    ).map(|(x, y, width, height, fill, stroke, stroke_width, corner_radius)| -> Result<(Vec<MultiVertex>, Vec<u32>), AvengerWgpuError> {
505                        build_verts_inds(x, &y, &width, &height, &fill, &stroke, &stroke_width, &corner_radius)
506                    }).collect::<Result<Vec<_>, AvengerWgpuError>>()?
507                } else {
508                    izip!(
509                        mark.x_iter(),
510                        mark.y_iter(),
511                        mark.width_iter(),
512                        mark.height_iter(),
513                        mark.fill_iter(),
514                        mark.stroke_iter(),
515                        mark.stroke_width_iter(),
516                        mark.corner_radius_iter(),
517                    ).map(|(x, y, width, height, fill, stroke, stroke_width, corner_radius)| -> Result<(Vec<MultiVertex>, Vec<u32>), AvengerWgpuError> {
518                            build_verts_inds(x, y, width, height, fill, stroke, stroke_width, corner_radius)
519                        }).collect::<Result<Vec<_>, AvengerWgpuError>>()?
520                }
521            }
522        };
523
524        let start_ind = self.num_indices();
525        let inds_len: usize = verts_inds.iter().map(|(_, i)| i.len()).sum();
526        let indices_range = (start_ind as u32)..((start_ind + inds_len) as u32);
527
528        let batch = MultiMarkBatch {
529            indices_range,
530            clip: clip.maybe_clip(mark.clip),
531            clip_indices_range: self.add_clip_path(clip, mark.clip)?,
532            image_atlas_index: None,
533            gradient_atlas_index,
534            text_atlas_index: None,
535        };
536
537        self.verts_inds.extend(verts_inds);
538        self.batches.push(batch);
539        Ok(())
540    }
541
542    #[tracing::instrument(skip_all)]
543    pub fn add_path_mark(
544        &mut self,
545        mark: &PathMark,
546        origin: [f32; 2],
547        clip: &Clip,
548    ) -> Result<(), AvengerWgpuError> {
549        let (gradient_atlas_index, grad_coords) = self
550            .gradient_atlas_builder
551            .register_gradients(&mark.gradients);
552
553        let build_verts_inds = |path: &lyon::path::Path,
554                                fill: &ColorOrGradient,
555                                stroke: &ColorOrGradient,
556                                transform: &PathTransform|
557         -> Result<(Vec<MultiVertex>, Vec<u32>), AvengerWgpuError> {
558            // Apply transform to path
559            let path = path
560                .clone()
561                .transformed(&transform.then_translate(Vector2D::new(origin[0], origin[1])));
562            let bbox = bounding_box(&path);
563
564            // Create vertex/index buffer builder
565            let mut buffers: VertexBuffers<MultiVertex, u32> = VertexBuffers::new();
566            let mut builder = BuffersBuilder::new(
567                &mut buffers,
568                crate::marks::multi::VertexPositions {
569                    fill: to_color_or_gradient_coord(fill, &grad_coords),
570                    stroke: to_color_or_gradient_coord(stroke, &grad_coords),
571                    top_left: bbox.min.to_array(),
572                    bottom_right: bbox.max.to_array(),
573                },
574            );
575
576            // Tesselate fill
577            let mut fill_tessellator = FillTessellator::new();
578            let fill_options = FillOptions::default().with_tolerance(0.05);
579
580            fill_tessellator.tessellate_path(&path, &fill_options, &mut builder)?;
581
582            // Tesselate stroke
583            if let Some(stroke_width) = mark.stroke_width {
584                let mut stroke_tessellator = StrokeTessellator::new();
585                let stroke_options = StrokeOptions::default()
586                    .with_tolerance(0.05)
587                    .with_line_join(match mark.stroke_join {
588                        StrokeJoin::Miter => LineJoin::Miter,
589                        StrokeJoin::Round => LineJoin::Round,
590                        StrokeJoin::Bevel => LineJoin::Bevel,
591                    })
592                    .with_line_cap(match mark.stroke_cap {
593                        StrokeCap::Butt => LineCap::Butt,
594                        StrokeCap::Round => LineCap::Round,
595                        StrokeCap::Square => LineCap::Square,
596                    })
597                    .with_line_width(stroke_width);
598                stroke_tessellator.tessellate_path(&path, &stroke_options, &mut builder)?;
599            }
600
601            Ok((buffers.vertices, buffers.indices))
602        };
603
604        cfg_if::cfg_if! {
605            if #[cfg(feature = "rayon")] {
606                let verts_inds = par_izip!(
607                    mark.path_vec(),
608                    mark.fill_vec(),
609                    mark.stroke_vec(),
610                    mark.transform_vec(),
611                ).map(|(path, fill, stroke, transform)| -> Result<(Vec<MultiVertex>, Vec<u32>), AvengerWgpuError> {
612                    build_verts_inds(path, &fill, &stroke, &transform)
613                }).collect::<Result<Vec<_>, AvengerWgpuError>>()?;
614            } else {
615                let verts_inds = izip!(
616                    mark.path_iter(),
617                    mark.fill_iter(),
618                    mark.stroke_iter(),
619                    mark.transform_iter(),
620                ).map(|(path, fill, stroke, transform)| -> Result<(Vec<MultiVertex>, Vec<u32>), AvengerWgpuError> {
621                    build_verts_inds(path, fill, stroke, transform)
622                }).collect::<Result<Vec<_>, AvengerWgpuError>>()?;
623            }
624        }
625
626        let start_ind = self.num_indices();
627        let inds_len: usize = verts_inds.iter().map(|(_, i)| i.len()).sum();
628        let indices_range = (start_ind as u32)..((start_ind + inds_len) as u32);
629
630        let batch = MultiMarkBatch {
631            indices_range,
632            clip: clip.maybe_clip(mark.clip),
633            clip_indices_range: self.add_clip_path(clip, mark.clip)?,
634            image_atlas_index: None,
635            gradient_atlas_index,
636            text_atlas_index: None,
637        };
638
639        self.verts_inds.extend(verts_inds);
640        self.batches.push(batch);
641        Ok(())
642    }
643
644    #[tracing::instrument(skip_all)]
645    pub fn add_symbol_mark(
646        &mut self,
647        mark: &SymbolMark,
648        origin: [f32; 2],
649        clip: &Clip,
650    ) -> Result<(), AvengerWgpuError> {
651        let paths = mark.shapes.iter().map(|s| s.as_path()).collect::<Vec<_>>();
652
653        // Compute cradients
654        let (gradient_atlas_index, grad_coords) = self
655            .gradient_atlas_builder
656            .register_gradients(&mark.gradients);
657
658        // Find max size
659        let max_scale = mark.max_size().sqrt();
660
661        // Compute stroke_width
662        let stroke_width = mark.stroke_width.unwrap_or(0.0);
663
664        // Tesselate paths
665        let mut shape_verts_inds: Vec<(Vec<SymbolVertex>, Vec<u32>)> = Vec::new();
666        for path in paths {
667            // Scale path to max size
668            let path = path
669                .as_ref()
670                .clone()
671                .transformed(&PathTransform::scale(max_scale, max_scale));
672
673            // Create vertex/index buffer builder
674            let mut buffers: VertexBuffers<SymbolVertex, u32> = VertexBuffers::new();
675            let mut builder =
676                BuffersBuilder::new(&mut buffers, SymbolVertexPositions { scale: max_scale });
677
678            // Tesselate fill
679            let mut fill_tessellator = FillTessellator::new();
680            let fill_options = FillOptions::default().with_tolerance(0.1);
681
682            fill_tessellator.tessellate_path(&path, &fill_options, &mut builder)?;
683
684            // Tesselate stroke
685            if stroke_width > 0.0 {
686                let mut stroke_tessellator = StrokeTessellator::new();
687                let stroke_options = StrokeOptions::default()
688                    .with_tolerance(0.1)
689                    .with_line_join(LineJoin::Miter)
690                    .with_line_cap(LineCap::Butt)
691                    .with_line_width(NORMALIZED_SYMBOL_STROKE_WIDTH);
692                stroke_tessellator.tessellate_path(&path, &stroke_options, &mut builder)?;
693            }
694
695            shape_verts_inds.push((buffers.vertices, buffers.indices));
696        }
697
698        // Builder function that we'll call from either single-threaded or parallel iterations paths
699        let build_verts_inds = |x: &f32,
700                                y: &f32,
701                                fill: &ColorOrGradient,
702                                size: &f32,
703                                stroke: &ColorOrGradient,
704                                angle: &f32,
705                                shape_index: &usize|
706         -> Result<(Vec<MultiVertex>, Vec<u32>), AvengerWgpuError> {
707            let (symbol_verts, indices) = &shape_verts_inds[*shape_index];
708            let fill = to_color_or_gradient_coord(fill, &grad_coords);
709            let stroke = to_color_or_gradient_coord(stroke, &grad_coords);
710
711            let multi_verts = symbol_verts
712                .iter()
713                .map(|sv| {
714                    sv.as_multi_vertex(
715                        *size,
716                        *x + origin[0],
717                        *y + origin[1],
718                        *angle,
719                        fill,
720                        stroke,
721                        stroke_width,
722                    )
723                })
724                .collect::<Vec<_>>();
725            Ok((multi_verts, indices.clone()))
726        };
727
728        cfg_if::cfg_if! {
729            if #[cfg(feature = "rayon")] {
730                let verts_inds = par_izip!(
731                    mark.x_vec(),
732                    mark.y_vec(),
733                    mark.fill_vec(),
734                    mark.size_vec(),
735                    mark.stroke_vec(),
736                    mark.angle_vec(),
737                    mark.shape_index_vec(),
738                ).map(|(x, y, fill, size, stroke, angle, shape_index)| -> Result<(Vec<MultiVertex>, Vec<u32>), AvengerWgpuError> {
739                    build_verts_inds(x, &y, &fill, &size, &stroke, &angle, &shape_index)
740                }).collect::<Result<Vec<_>, AvengerWgpuError>>()?;
741            } else {
742                let verts_inds = izip!(
743                    mark.x_iter(),
744                    mark.y_iter(),
745                    mark.fill_iter(),
746                    mark.size_iter(),
747                    mark.stroke_iter(),
748                    mark.angle_iter(),
749                    mark.shape_index_iter(),
750                ).map(|(x, y, fill, size, stroke, angle, shape_index)| -> Result<(Vec<MultiVertex>, Vec<u32>), AvengerWgpuError> {
751                    build_verts_inds(x, y, fill, size, stroke, angle, shape_index)
752                }).collect::<Result<Vec<_>, AvengerWgpuError>>()?;
753            }
754        };
755
756        let start_ind = self.num_indices();
757        let inds_len: usize = verts_inds.iter().map(|(_, i)| i.len()).sum();
758        let indices_range = (start_ind as u32)..((start_ind + inds_len) as u32);
759
760        let batch = MultiMarkBatch {
761            indices_range,
762            clip: clip.maybe_clip(mark.clip),
763            clip_indices_range: self.add_clip_path(clip, mark.clip)?,
764            image_atlas_index: None,
765            gradient_atlas_index,
766            text_atlas_index: None,
767        };
768
769        self.verts_inds.extend(verts_inds);
770        self.batches.push(batch);
771        Ok(())
772    }
773
774    #[tracing::instrument(skip_all)]
775    pub fn add_line_mark(
776        &mut self,
777        mark: &LineMark,
778        origin: [f32; 2],
779        clip: &Clip,
780    ) -> Result<(), AvengerWgpuError> {
781        let (gradient_atlas_index, grad_coords) = self
782            .gradient_atlas_builder
783            .register_gradients(&mark.gradients);
784
785        let mut defined_paths: Vec<Path> = Vec::new();
786
787        // Build path for each defined line segment
788        let mut path_builder = lyon::path::Path::builder().with_svg();
789        let mut path_len = 0;
790        for (x, y, defined) in izip!(mark.x_iter(), mark.y_iter(), mark.defined_iter()) {
791            if *defined {
792                if path_len > 0 {
793                    // Continue path
794                    path_builder.line_to(lyon::geom::point(*x + origin[0], *y + origin[1]));
795                } else {
796                    // New path
797                    path_builder.move_to(lyon::geom::point(*x + origin[0], *y + origin[1]));
798                }
799                path_len += 1;
800            } else {
801                if path_len == 1 {
802                    // Finishing single point line. Add extra point at the same location
803                    // so that stroke caps are drawn
804                    path_builder.close();
805                }
806                defined_paths.push(path_builder.build());
807                path_builder = lyon::path::Path::builder().with_svg();
808                path_len = 0;
809            }
810        }
811        defined_paths.push(path_builder.build());
812
813        let defined_paths = if let Some(stroke_dash) = &mark.stroke_dash {
814            // Create new paths with dashing
815            let mut dashed_paths: Vec<Path> = Vec::new();
816            for path in defined_paths.iter() {
817                let mut dash_path_builder = lyon::path::Path::builder();
818                let path_measurements = PathMeasurements::from_path(path, 0.1);
819                let mut sampler =
820                    PathSampler::new(&path_measurements, path, &(), SampleType::Distance);
821
822                // Next index into stroke_dash array
823                let mut dash_idx = 0;
824
825                // Distance along line from (x0,y0) to (x1,y1) where the next dash will start
826                let mut start_dash_dist: f32 = 0.0;
827
828                // Total length of line
829                let line_len = sampler.length();
830
831                // Whether the next dash length represents a drawn dash (draw == true)
832                // or a gap (draw == false)
833                let mut draw = true;
834
835                while start_dash_dist < line_len {
836                    let end_dash_dist = if start_dash_dist + stroke_dash[dash_idx] >= line_len {
837                        // The final dash/gap should be truncated to the end of the line
838                        line_len
839                    } else {
840                        // The dash/gap fits entirely in the rule
841                        start_dash_dist + stroke_dash[dash_idx]
842                    };
843
844                    if draw {
845                        sampler.split_range(start_dash_dist..end_dash_dist, &mut dash_path_builder);
846                    }
847
848                    // update start dist for next dash/gap
849                    start_dash_dist = end_dash_dist;
850
851                    // increment index and cycle back to start of start of dash array
852                    dash_idx = (dash_idx + 1) % stroke_dash.len();
853
854                    // Alternate between drawn dash and gap
855                    draw = !draw;
856                }
857                dashed_paths.push(dash_path_builder.build())
858            }
859            dashed_paths
860        } else {
861            defined_paths
862        };
863
864        let mut verts: Vec<MultiVertex> = Vec::new();
865        let mut indices: Vec<u32> = Vec::new();
866
867        for path in &defined_paths {
868            let bbox = bounding_box(path);
869            // Create vertex/index buffer builder
870            let mut buffers: VertexBuffers<MultiVertex, u32> = VertexBuffers::new();
871            let mut buffers_builder = BuffersBuilder::new(
872                &mut buffers,
873                VertexPositions {
874                    fill: [0.0, 0.0, 0.0, 0.0],
875                    stroke: to_color_or_gradient_coord(&mark.stroke, &grad_coords),
876                    top_left: bbox.min.to_array(),
877                    bottom_right: bbox.max.to_array(),
878                },
879            );
880
881            // Tesselate path
882            let mut stroke_tessellator = StrokeTessellator::new();
883            let stroke_options = StrokeOptions::default()
884                .with_tolerance(0.05)
885                .with_line_join(match mark.stroke_join {
886                    StrokeJoin::Miter => LineJoin::Miter,
887                    StrokeJoin::Round => LineJoin::Round,
888                    StrokeJoin::Bevel => LineJoin::Bevel,
889                })
890                .with_line_cap(match mark.stroke_cap {
891                    StrokeCap::Butt => LineCap::Butt,
892                    StrokeCap::Round => LineCap::Round,
893                    StrokeCap::Square => LineCap::Square,
894                })
895                .with_line_width(mark.stroke_width);
896            stroke_tessellator.tessellate_path(path, &stroke_options, &mut buffers_builder)?;
897
898            let index_offset = verts.len() as u32;
899            verts.extend(buffers.vertices);
900            indices.extend(buffers.indices.into_iter().map(|i| i + index_offset));
901        }
902
903        let start_ind = self.num_indices();
904        let indices_range = (start_ind as u32)..((start_ind + indices.len()) as u32);
905
906        let batch = MultiMarkBatch {
907            indices_range,
908            clip: clip.maybe_clip(mark.clip),
909            clip_indices_range: self.add_clip_path(clip, mark.clip)?,
910            image_atlas_index: None,
911            gradient_atlas_index,
912            text_atlas_index: None,
913        };
914
915        self.verts_inds.push((verts, indices));
916        self.batches.push(batch);
917        Ok(())
918    }
919
920    #[tracing::instrument(skip_all)]
921    pub fn add_area_mark(
922        &mut self,
923        mark: &AreaMark,
924        origin: [f32; 2],
925        clip: &Clip,
926    ) -> Result<(), AvengerWgpuError> {
927        let (gradient_atlas_index, grad_coords) = self
928            .gradient_atlas_builder
929            .register_gradients(&mark.gradients);
930
931        let mut path_builder = lyon::path::Path::builder().with_svg();
932        let mut tail: Vec<(f32, f32)> = Vec::new();
933
934        fn close_area(b: &mut WithSvg<BuilderImpl>, tail: &mut Vec<(f32, f32)>) {
935            if tail.is_empty() {
936                return;
937            }
938            for (x, y) in tail.iter().rev() {
939                b.line_to(lyon::geom::point(*x, *y));
940            }
941
942            tail.clear();
943            b.close();
944        }
945
946        if mark.orientation == AreaOrientation::Vertical {
947            for (x, y, y2, defined) in izip!(
948                mark.x_iter(),
949                mark.y_iter(),
950                mark.y2_iter(),
951                mark.defined_iter(),
952            ) {
953                if *defined {
954                    if !tail.is_empty() {
955                        // Continue path
956                        path_builder.line_to(lyon::geom::point(*x + origin[0], *y + origin[1]));
957                    } else {
958                        // New path
959                        path_builder.move_to(lyon::geom::point(*x + origin[0], *y + origin[1]));
960                    }
961                    tail.push((*x + origin[0], *y2 + origin[1]));
962                } else {
963                    close_area(&mut path_builder, &mut tail);
964                }
965            }
966        } else {
967            for (y, x, x2, defined) in izip!(
968                mark.y_iter(),
969                mark.x_iter(),
970                mark.x2_iter(),
971                mark.defined_iter(),
972            ) {
973                if *defined {
974                    if !tail.is_empty() {
975                        // Continue path
976                        path_builder.line_to(lyon::geom::point(*x + origin[0], *y + origin[1]));
977                    } else {
978                        // New path
979                        path_builder.move_to(lyon::geom::point(*x + origin[0], *y + origin[1]));
980                    }
981                    tail.push((*x2 + origin[0], *y + origin[1]));
982                } else {
983                    close_area(&mut path_builder, &mut tail);
984                }
985            }
986        }
987
988        close_area(&mut path_builder, &mut tail);
989        let path = path_builder.build();
990        let bbox = bounding_box(&path);
991
992        // Create vertex/index buffer builder
993        let mut buffers: VertexBuffers<MultiVertex, u32> = VertexBuffers::new();
994        let mut buffers_builder = BuffersBuilder::new(
995            &mut buffers,
996            VertexPositions {
997                fill: to_color_or_gradient_coord(&mark.fill, &grad_coords),
998                stroke: to_color_or_gradient_coord(&mark.stroke, &grad_coords),
999                top_left: bbox.min.to_array(),
1000                bottom_right: bbox.max.to_array(),
1001            },
1002        );
1003
1004        // Tessellate fill
1005        let mut fill_tessellator = FillTessellator::new();
1006        let fill_options = FillOptions::default().with_tolerance(0.05);
1007        fill_tessellator.tessellate_path(&path, &fill_options, &mut buffers_builder)?;
1008
1009        // Tessellate path
1010        if mark.stroke_width > 0.0 {
1011            let mut stroke_tessellator = StrokeTessellator::new();
1012            let stroke_options = StrokeOptions::default()
1013                .with_tolerance(0.05)
1014                .with_line_join(match mark.stroke_join {
1015                    StrokeJoin::Miter => LineJoin::Miter,
1016                    StrokeJoin::Round => LineJoin::Round,
1017                    StrokeJoin::Bevel => LineJoin::Bevel,
1018                })
1019                .with_line_cap(match mark.stroke_cap {
1020                    StrokeCap::Butt => LineCap::Butt,
1021                    StrokeCap::Round => LineCap::Round,
1022                    StrokeCap::Square => LineCap::Square,
1023                })
1024                .with_line_width(mark.stroke_width);
1025            stroke_tessellator.tessellate_path(&path, &stroke_options, &mut buffers_builder)?;
1026        }
1027
1028        let start_ind = self.num_indices();
1029        let indices_range = (start_ind as u32)..((start_ind + buffers.indices.len()) as u32);
1030
1031        let batch = MultiMarkBatch {
1032            indices_range,
1033            clip: clip.maybe_clip(mark.clip),
1034            clip_indices_range: self.add_clip_path(clip, mark.clip)?,
1035            image_atlas_index: None,
1036            gradient_atlas_index,
1037            text_atlas_index: None,
1038        };
1039
1040        self.verts_inds.push((buffers.vertices, buffers.indices));
1041        self.batches.push(batch);
1042        Ok(())
1043    }
1044
1045    #[tracing::instrument(skip_all)]
1046    pub fn add_trail_mark(
1047        &mut self,
1048        mark: &TrailMark,
1049        origin: [f32; 2],
1050        clip: &Clip,
1051    ) -> Result<(), AvengerWgpuError> {
1052        let (gradient_atlas_index, grad_coords) = self
1053            .gradient_atlas_builder
1054            .register_gradients(&mark.gradients);
1055
1056        let size_idx: AttributeIndex = 0;
1057        let mut path_builder = lyon::path::Path::builder_with_attributes(1);
1058        let mut path_len = 0;
1059        for (x, y, size, defined) in izip!(
1060            mark.x_iter(),
1061            mark.y_iter(),
1062            mark.size_iter(),
1063            mark.defined_iter()
1064        ) {
1065            if *defined {
1066                if path_len > 0 {
1067                    // Continue path
1068                    path_builder
1069                        .line_to(lyon::geom::point(*x + origin[0], *y + origin[1]), &[*size]);
1070                } else {
1071                    // New path
1072                    path_builder.begin(lyon::geom::point(*x + origin[0], *y + origin[1]), &[*size]);
1073                }
1074                path_len += 1;
1075            } else {
1076                if path_len == 1 {
1077                    // Finishing single point line. Add extra point at the same location
1078                    // so that stroke caps are drawn
1079                    path_builder.end(true);
1080                } else {
1081                    path_builder.end(false);
1082                }
1083                path_len = 0;
1084            }
1085        }
1086        path_builder.end(false);
1087
1088        // let bbox = bounding_box(&path);
1089
1090        let path = path_builder.build();
1091        let bbox = bounding_box(&path);
1092
1093        // Create vertex/index buffer builder
1094        let mut buffers: VertexBuffers<MultiVertex, u32> = VertexBuffers::new();
1095        let mut buffers_builder = BuffersBuilder::new(
1096            &mut buffers,
1097            VertexPositions {
1098                fill: [0.0, 0.0, 0.0, 0.0],
1099                stroke: to_color_or_gradient_coord(&mark.stroke, &grad_coords),
1100                top_left: bbox.min.to_array(),
1101                bottom_right: bbox.max.to_array(),
1102            },
1103        );
1104
1105        // Tesselate path
1106        let mut stroke_tessellator = StrokeTessellator::new();
1107        let stroke_options = StrokeOptions::default()
1108            .with_tolerance(0.05)
1109            .with_line_join(LineJoin::Round)
1110            .with_line_cap(LineCap::Round)
1111            .with_variable_line_width(size_idx);
1112        stroke_tessellator.tessellate_path(&path, &stroke_options, &mut buffers_builder)?;
1113
1114        let start_ind = self.num_indices();
1115        let indices_range = (start_ind as u32)..((start_ind + buffers.indices.len()) as u32);
1116
1117        let batch = MultiMarkBatch {
1118            indices_range,
1119            clip: clip.maybe_clip(mark.clip),
1120            clip_indices_range: self.add_clip_path(clip, mark.clip)?,
1121            image_atlas_index: None,
1122            gradient_atlas_index,
1123            text_atlas_index: None,
1124        };
1125
1126        self.verts_inds.push((buffers.vertices, buffers.indices));
1127        self.batches.push(batch);
1128        Ok(())
1129    }
1130
1131    #[tracing::instrument(skip_all)]
1132    pub fn add_arc_mark(
1133        &mut self,
1134        mark: &ArcMark,
1135        origin: [f32; 2],
1136        clip: &Clip,
1137    ) -> Result<(), AvengerWgpuError> {
1138        let (gradient_atlas_index, grad_coords) = self
1139            .gradient_atlas_builder
1140            .register_gradients(&mark.gradients);
1141
1142        let verts_inds = izip!(
1143            mark.x_iter(),
1144            mark.y_iter(),
1145            mark.start_angle_iter(),
1146            mark.end_angle_iter(),
1147            mark.outer_radius_iter(),
1148            mark.inner_radius_iter(),
1149            mark.fill_iter(),
1150            mark.stroke_iter(),
1151            mark.stroke_width_iter(),
1152        )
1153        .map(
1154            |(
1155                x,
1156                y,
1157                start_angle,
1158                end_angle,
1159                outer_radius,
1160                inner_radius,
1161                fill,
1162                stroke,
1163                stroke_width,
1164            )|
1165             -> Result<(Vec<MultiVertex>, Vec<u32>), AvengerWgpuError> {
1166                // Compute angle
1167                let total_angle = end_angle - start_angle;
1168
1169                // Normalize inner/outer radius
1170                let (inner_radius, outer_radius) = if *inner_radius > *outer_radius {
1171                    (*outer_radius, *inner_radius)
1172                } else {
1173                    (*inner_radius, *outer_radius)
1174                };
1175
1176                let mut path_builder = lyon::path::Path::builder().with_svg();
1177
1178                // Orient arc starting along vertical y-axis
1179                path_builder.move_to(lyon::geom::Point::new(0.0, -inner_radius));
1180                path_builder.line_to(lyon::geom::Point::new(0.0, -outer_radius));
1181
1182                // Draw outer arc
1183                path_builder.arc(
1184                    lyon::geom::Point::new(0.0, 0.0),
1185                    lyon::math::Vector::new(outer_radius, outer_radius),
1186                    lyon::geom::Angle::radians(total_angle),
1187                    lyon::geom::Angle::radians(0.0),
1188                );
1189
1190                if inner_radius != 0.0 {
1191                    // Compute vector from outer arc corner to arc corner
1192                    let inner_radius_vec = path_builder
1193                        .current_position()
1194                        .to_vector()
1195                        .neg()
1196                        .normalize()
1197                        .mul(outer_radius - inner_radius);
1198                    path_builder.relative_line_to(inner_radius_vec);
1199
1200                    // Draw inner
1201                    path_builder.arc(
1202                        lyon::geom::Point::new(0.0, 0.0),
1203                        lyon::math::Vector::new(inner_radius, inner_radius),
1204                        lyon::geom::Angle::radians(-total_angle),
1205                        lyon::geom::Angle::radians(0.0),
1206                    );
1207                } else {
1208                    // Draw line back to origin
1209                    path_builder.line_to(lyon::geom::Point::new(0.0, 0.0));
1210                }
1211
1212                path_builder.close();
1213                let path = path_builder.build();
1214
1215                // Transform path to account for start angle and position
1216                let path = path.transformed(
1217                    &PathTransform::rotation(lyon::geom::Angle::radians(*start_angle))
1218                        .then_translate(Vector2D::new(*x + origin[0], *y + origin[1])),
1219                );
1220
1221                // Compute bounding box
1222                let bbox = bounding_box(&path);
1223
1224                // Create vertex/index buffer builder
1225                let mut buffers: VertexBuffers<MultiVertex, u32> = VertexBuffers::new();
1226                let mut builder = BuffersBuilder::new(
1227                    &mut buffers,
1228                    VertexPositions {
1229                        fill: to_color_or_gradient_coord(fill, &grad_coords),
1230                        stroke: to_color_or_gradient_coord(stroke, &grad_coords),
1231                        top_left: bbox.min.to_array(),
1232                        bottom_right: bbox.max.to_array(),
1233                    },
1234                );
1235
1236                // Tesselate fill
1237                let mut fill_tessellator = FillTessellator::new();
1238                let fill_options = FillOptions::default().with_tolerance(0.05);
1239                fill_tessellator.tessellate_path(&path, &fill_options, &mut builder)?;
1240
1241                // Tesselate stroke
1242                if *stroke_width > 0.0 {
1243                    let mut stroke_tessellator = StrokeTessellator::new();
1244                    let stroke_options = StrokeOptions::default()
1245                        .with_tolerance(0.05)
1246                        .with_line_join(LineJoin::Miter)
1247                        .with_line_cap(LineCap::Butt)
1248                        .with_line_width(*stroke_width);
1249                    stroke_tessellator.tessellate_path(&path, &stroke_options, &mut builder)?;
1250                }
1251
1252                Ok((buffers.vertices, buffers.indices))
1253            },
1254        )
1255        .collect::<Result<Vec<_>, AvengerWgpuError>>()?;
1256
1257        let start_ind = self.num_indices();
1258        let inds_len: usize = verts_inds.iter().map(|(_, i)| i.len()).sum();
1259        let indices_range = (start_ind as u32)..((start_ind + inds_len) as u32);
1260
1261        let batch = MultiMarkBatch {
1262            indices_range,
1263            clip: clip.maybe_clip(mark.clip),
1264            clip_indices_range: self.add_clip_path(clip, mark.clip)?,
1265            image_atlas_index: None,
1266            gradient_atlas_index,
1267            text_atlas_index: None,
1268        };
1269
1270        self.verts_inds.extend(verts_inds);
1271        self.batches.push(batch);
1272        Ok(())
1273    }
1274
1275    #[tracing::instrument(skip_all)]
1276    pub fn add_image_mark(
1277        &mut self,
1278        mark: &ImageMark,
1279        origin: [f32; 2],
1280        clip: &Clip,
1281    ) -> Result<(), AvengerWgpuError> {
1282        let verts_inds = izip!(
1283            mark.image_iter(),
1284            mark.x_iter(),
1285            mark.y_iter(),
1286            mark.width_iter(),
1287            mark.height_iter(),
1288            mark.baseline_iter(),
1289            mark.align_iter(),
1290        ).map(|(img, x, y, width, height, baseline, align)| -> Result<(usize, Vec<MultiVertex>, Vec<u32>), AvengerWgpuError> {
1291            let x = *x + origin[0];
1292            let y = *y + origin[1];
1293
1294            let Some(rgba_image) = img.to_image() else {
1295                return Err(AvengerWgpuError::ConversionError("Failed to convert raw image to rgba image".to_string()))
1296            };
1297
1298            let (atlas_index, tex_coords) = self.image_atlas_builder.register_image(&rgba_image)?;
1299
1300            // Compute image left
1301            let left = match *align {
1302                ImageAlign::Left => x,
1303                ImageAlign::Center => x - *width / 2.0,
1304                ImageAlign::Right => x - *width,
1305            };
1306
1307            // Compute image top
1308            let top = match *baseline {
1309                ImageBaseline::Top => y,
1310                ImageBaseline::Middle => y - *height / 2.0,
1311                ImageBaseline::Bottom => y - *height,
1312            };
1313
1314            // Adjust position and dimensions if aspect ratio should be preserved
1315            let (left, top, width, height) = if mark.aspect {
1316                let img_aspect = img.width as f32 / img.height as f32;
1317                let outline_aspect = *width / *height;
1318                if img_aspect > outline_aspect {
1319                    // image is wider than the box, so we scale
1320                    // image to box width and center vertically
1321                    let aspect_height = *width / img_aspect;
1322                    let aspect_top = top + (*height - aspect_height) / 2.0;
1323                    (left, aspect_top, *width, aspect_height)
1324                } else if img_aspect < outline_aspect {
1325                    // image is taller than the box, so we scale
1326                    // image to box height an center horizontally
1327                    let aspect_width = *height * img_aspect;
1328                    let aspect_left = left + (*width - aspect_width) / 2.0;
1329                    (aspect_left, top, aspect_width, *height)
1330                } else {
1331                    (left, top, *width, *height)
1332                }
1333            } else {
1334                (left, top, *width, *height)
1335            };
1336
1337            let top_left = [top, left];
1338            let bottom_right = [top + height, left + width];
1339            let verts = vec![
1340                // Upper left
1341                MultiVertex {
1342                    color: [IMAGE_TEXTURE_CODE, tex_coords.x0, tex_coords.y0, 0.0],
1343                    position: [left, top],
1344                    top_left,
1345                    bottom_right,
1346                },
1347                // Lower left
1348                MultiVertex {
1349                    color: [IMAGE_TEXTURE_CODE, tex_coords.x0, tex_coords.y1, 0.0],
1350                    position: [left, top + height],
1351                    top_left,
1352                    bottom_right,
1353                },
1354                // Lower right
1355                MultiVertex {
1356                    color: [IMAGE_TEXTURE_CODE, tex_coords.x1, tex_coords.y1, 0.0],
1357                    position: [left + width, top + height],
1358                    top_left,
1359                    bottom_right,
1360                },
1361                // Upper right
1362                MultiVertex {
1363                    color: [IMAGE_TEXTURE_CODE, tex_coords.x1, tex_coords.y0, 0.0],
1364                    position: [left + width, top],
1365                    top_left,
1366                    bottom_right,
1367                },
1368            ];
1369            let indices: Vec<u32> = vec![0, 1, 2, 0, 2, 3];
1370            Ok((atlas_index, verts, indices))
1371        }).collect::<Result<Vec<_>, AvengerWgpuError>>()?;
1372
1373        // Construct batches, one batch per image atlas index
1374        let start_ind = self.num_indices() as u32;
1375        let mut next_batch = MultiMarkBatch {
1376            indices_range: start_ind..start_ind,
1377            clip: clip.maybe_clip(mark.clip),
1378            clip_indices_range: self.add_clip_path(clip, mark.clip)?,
1379            image_atlas_index: None,
1380            gradient_atlas_index: None,
1381            text_atlas_index: None,
1382        };
1383
1384        for (atlas_index, verts, inds) in verts_inds {
1385            if next_batch.image_atlas_index.unwrap_or(atlas_index) == atlas_index {
1386                // update next batch with atlas index and inds range
1387                next_batch.image_atlas_index = Some(atlas_index);
1388                next_batch.indices_range = next_batch.indices_range.start
1389                    ..(next_batch.indices_range.end + inds.len() as u32);
1390            } else {
1391                // create new batch
1392                let start_ind = next_batch.indices_range.end;
1393                // Initialize new next_batch and swap to avoid extra mem copy
1394                let mut full_batch = MultiMarkBatch {
1395                    indices_range: start_ind..(start_ind + inds.len() as u32),
1396                    clip: clip.maybe_clip(mark.clip),
1397                    clip_indices_range: self.add_clip_path(clip, mark.clip)?,
1398                    image_atlas_index: Some(atlas_index),
1399                    gradient_atlas_index: None,
1400                    text_atlas_index: None,
1401                };
1402                std::mem::swap(&mut full_batch, &mut next_batch);
1403                self.batches.push(full_batch);
1404            }
1405
1406            // Add verts and indices
1407            self.verts_inds.push((verts, inds))
1408        }
1409
1410        self.batches.push(next_batch);
1411        Ok(())
1412    }
1413
1414    #[tracing::instrument(skip_all)]
1415    pub fn add_text_mark(
1416        &mut self,
1417        mark: &TextMark,
1418        origin: [f32; 2],
1419        clip: &Clip,
1420    ) -> Result<(), AvengerWgpuError> {
1421        let registrations = izip!(
1422            mark.text_iter(),
1423            mark.x_iter(),
1424            mark.y_iter(),
1425            mark.color_iter(),
1426            mark.align_iter(),
1427            mark.angle_iter(),
1428            mark.baseline_iter(),
1429            mark.font_iter(),
1430            mark.font_size_iter(),
1431            mark.font_weight_iter(),
1432            mark.font_style_iter(),
1433            mark.limit_iter(),
1434        )
1435        .map(
1436            |(
1437                text,
1438                x,
1439                y,
1440                color,
1441                align,
1442                angle,
1443                baseline,
1444                font,
1445                font_size,
1446                font_weight,
1447                font_style,
1448                limit,
1449            )| {
1450                let instance = TextInstance {
1451                    text,
1452                    position: [*x + origin[0], *y + origin[1]],
1453                    color,
1454                    align,
1455                    angle: *angle,
1456                    baseline,
1457                    font,
1458                    font_size: *font_size,
1459                    font_weight,
1460                    font_style,
1461                    limit: *limit,
1462                };
1463                self.text_atlas_builder
1464                    .register_text(instance, self.dimensions)
1465            },
1466        )
1467        .collect::<Result<Vec<_>, AvengerWgpuError>>()?
1468        .into_iter()
1469        .flatten()
1470        .collect::<Vec<_>>();
1471
1472        // Construct batches, one batch per text atlas index
1473        let start_ind = self.num_indices() as u32;
1474        let mut next_batch = MultiMarkBatch {
1475            indices_range: start_ind..start_ind,
1476            clip: clip.maybe_clip(mark.clip),
1477            clip_indices_range: self.add_clip_path(clip, mark.clip)?,
1478            image_atlas_index: None,
1479            gradient_atlas_index: None,
1480            text_atlas_index: None,
1481        };
1482
1483        for registration in registrations {
1484            let atlas_index = registration.atlas_index;
1485            let verts = registration.verts;
1486            let inds = registration.indices;
1487
1488            // (atlas_index, verts, inds)
1489            if next_batch.text_atlas_index.unwrap_or(atlas_index) == atlas_index {
1490                // update next batch with atlas index and inds range
1491                next_batch.text_atlas_index = Some(atlas_index);
1492                next_batch.indices_range = next_batch.indices_range.start
1493                    ..(next_batch.indices_range.end + inds.len() as u32);
1494            } else {
1495                // create new batch
1496                let start_ind = next_batch.indices_range.end;
1497                // Initialize new next_batch and swap to avoid extra mem copy
1498                let mut full_batch = MultiMarkBatch {
1499                    indices_range: start_ind..(start_ind + inds.len() as u32),
1500                    clip: clip.maybe_clip(mark.clip),
1501                    clip_indices_range: self.add_clip_path(clip, mark.clip)?,
1502                    image_atlas_index: None,
1503                    gradient_atlas_index: None,
1504                    text_atlas_index: Some(atlas_index),
1505                };
1506                std::mem::swap(&mut full_batch, &mut next_batch);
1507                self.batches.push(full_batch);
1508            }
1509
1510            // Add verts and indices
1511            self.verts_inds.push((verts, inds))
1512        }
1513
1514        self.batches.push(next_batch);
1515        Ok(())
1516    }
1517
1518    fn num_indices(&self) -> usize {
1519        self.verts_inds.iter().map(|(_, inds)| inds.len()).sum()
1520    }
1521
1522    fn num_clip_indices(&self) -> usize {
1523        self.clip_verts_inds
1524            .iter()
1525            .map(|(_, inds)| inds.len())
1526            .sum()
1527    }
1528
1529    #[tracing::instrument(skip_all)]
1530    pub fn render(
1531        &self,
1532        device: &Device,
1533        queue: &Queue,
1534        texture_format: TextureFormat,
1535        sample_count: u32,
1536        texture_view: &TextureView,
1537        resolve_target: Option<&TextureView>,
1538    ) -> CommandBuffer {
1539        // Uniforms
1540        let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1541            label: Some("Multi Uniform Buffer"),
1542            contents: bytemuck::cast_slice(&[self.uniform]),
1543            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1544        });
1545
1546        let uniform_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1547            entries: &[wgpu::BindGroupLayoutEntry {
1548                binding: 0,
1549                visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
1550                ty: wgpu::BindingType::Buffer {
1551                    ty: wgpu::BufferBindingType::Uniform,
1552                    has_dynamic_offset: false,
1553                    min_binding_size: None,
1554                },
1555                count: None,
1556            }],
1557            label: Some("chart_uniform_layout"),
1558        });
1559
1560        let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1561            layout: &uniform_layout,
1562            entries: &[wgpu::BindGroupEntry {
1563                binding: 0,
1564                resource: uniform_buffer.as_entire_binding(),
1565            }],
1566            label: Some("uniform_bind_group"),
1567        });
1568
1569        // Gradient Textures
1570        let (grad_texture_size, grad_images) = self.gradient_atlas_builder.build();
1571        let (gradient_layout, gradient_texture_bind_groups) = Self::make_texture_bind_groups(
1572            device,
1573            queue,
1574            grad_texture_size,
1575            &grad_images,
1576            wgpu::FilterMode::Nearest,
1577            wgpu::FilterMode::Nearest,
1578        );
1579
1580        // Image Textures
1581        let (image_texture_size, image_images) = self.image_atlas_builder.build();
1582        let (image_layout, image_texture_bind_groups) = Self::make_texture_bind_groups(
1583            device,
1584            queue,
1585            image_texture_size,
1586            &image_images,
1587            wgpu::FilterMode::Linear,
1588            wgpu::FilterMode::Linear,
1589        );
1590
1591        // Text Textures
1592        let (text_texture_size, text_images) = self.text_atlas_builder.build();
1593        let (text_layout, text_texture_bind_groups) = Self::make_texture_bind_groups(
1594            device,
1595            queue,
1596            text_texture_size,
1597            &text_images,
1598            wgpu::FilterMode::Linear,
1599            wgpu::FilterMode::Linear,
1600        );
1601
1602        // Shaders
1603        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1604            label: Some("Shader"),
1605            source: wgpu::ShaderSource::Wgsl(include_str!("multi.wgsl").into()),
1606        });
1607
1608        let render_pipeline_layout =
1609            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1610                label: Some("Render Pipeline Layout"),
1611                bind_group_layouts: &[
1612                    &uniform_layout,
1613                    &gradient_layout,
1614                    &image_layout,
1615                    &text_layout,
1616                ],
1617                push_constant_ranges: &[],
1618            });
1619
1620        let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1621            label: Some("Render Pipeline"),
1622            layout: Some(&render_pipeline_layout),
1623            vertex: wgpu::VertexState {
1624                module: &shader,
1625                entry_point: "vs_main",
1626                compilation_options: Default::default(),
1627                buffers: &[MultiVertex::desc()],
1628            },
1629            fragment: Some(wgpu::FragmentState {
1630                module: &shader,
1631                entry_point: "fs_main",
1632                compilation_options: Default::default(),
1633                targets: &[Some(wgpu::ColorTargetState {
1634                    format: texture_format,
1635                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1636                    write_mask: wgpu::ColorWrites::ALL,
1637                })],
1638            }),
1639            primitive: wgpu::PrimitiveState {
1640                topology: wgpu::PrimitiveTopology::TriangleList,
1641                strip_index_format: None,
1642                front_face: wgpu::FrontFace::Ccw,
1643                cull_mode: Some(wgpu::Face::Back),
1644                polygon_mode: wgpu::PolygonMode::Fill,
1645                unclipped_depth: false,
1646                conservative: false,
1647            },
1648            depth_stencil: Some(wgpu::DepthStencilState {
1649                format: wgpu::TextureFormat::Stencil8,
1650                depth_write_enabled: false,
1651                depth_compare: wgpu::CompareFunction::Always,
1652                stencil: wgpu::StencilState {
1653                    front: wgpu::StencilFaceState {
1654                        // Draw pixel if stencil reference value is less than or equal to stencil value
1655                        compare: wgpu::CompareFunction::LessEqual,
1656                        ..Default::default()
1657                    },
1658                    back: wgpu::StencilFaceState::IGNORE,
1659                    read_mask: !0,
1660                    write_mask: !0,
1661                },
1662                bias: Default::default(),
1663            }),
1664            multisample: wgpu::MultisampleState {
1665                count: sample_count,
1666                mask: !0,
1667                alpha_to_coverage_enabled: false,
1668            },
1669            multiview: None,
1670        });
1671
1672        let stencil_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1673            label: None,
1674            layout: Some(&render_pipeline_layout),
1675            vertex: wgpu::VertexState {
1676                module: &shader,
1677                entry_point: "vs_main",
1678                compilation_options: Default::default(),
1679                buffers: &[MultiVertex::desc()],
1680            },
1681            fragment: Some(wgpu::FragmentState {
1682                module: &shader,
1683                entry_point: "fs_main",
1684                compilation_options: Default::default(),
1685                targets: &[Some(wgpu::ColorTargetState {
1686                    format: texture_format,
1687                    blend: None,
1688                    write_mask: wgpu::ColorWrites::empty(),
1689                })],
1690            }),
1691            primitive: Default::default(),
1692            depth_stencil: Some(wgpu::DepthStencilState {
1693                format: wgpu::TextureFormat::Stencil8,
1694                depth_write_enabled: false,
1695                depth_compare: wgpu::CompareFunction::Always,
1696                stencil: wgpu::StencilState {
1697                    front: wgpu::StencilFaceState {
1698                        compare: wgpu::CompareFunction::Always,
1699                        pass_op: wgpu::StencilOperation::Replace,
1700                        ..Default::default()
1701                    },
1702                    back: wgpu::StencilFaceState::IGNORE,
1703                    read_mask: !0,
1704                    write_mask: !0,
1705                },
1706                bias: Default::default(),
1707            }),
1708            multisample: wgpu::MultisampleState {
1709                count: sample_count,
1710                mask: !0,
1711                alpha_to_coverage_enabled: false,
1712            },
1713            multiview: None,
1714        });
1715
1716        let stencil_buffer = device.create_texture(&wgpu::TextureDescriptor {
1717            label: Some("Stencil buffer"),
1718            size: Extent3d {
1719                width: self.dimensions.to_physical_width(),
1720                height: self.dimensions.to_physical_height(),
1721                depth_or_array_layers: 1,
1722            },
1723            mip_level_count: 1,
1724            sample_count,
1725            dimension: wgpu::TextureDimension::D2,
1726            format: wgpu::TextureFormat::Stencil8,
1727            view_formats: &[],
1728            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1729        });
1730
1731        // flatten verts and inds
1732        let num_verts: usize = self.verts_inds.iter().map(|(v, _)| v.len()).sum();
1733        let num_inds: usize = self.verts_inds.iter().map(|(_, inds)| inds.len()).sum();
1734        let mut verticies: Vec<MultiVertex> = Vec::with_capacity(num_verts);
1735        let mut indices: Vec<u32> = Vec::with_capacity(num_inds);
1736
1737        for (vs, inds) in &self.verts_inds {
1738            let offset = verticies.len() as u32;
1739            indices.extend(inds.iter().map(|i| *i + offset));
1740            verticies.extend(vs);
1741        }
1742
1743        let num_clip_verts = self.clip_verts_inds.iter().map(|(v, _)| v.len()).sum();
1744        let num_clip_inds = self
1745            .clip_verts_inds
1746            .iter()
1747            .map(|(_, inds)| inds.len())
1748            .sum();
1749        let mut clip_verticies: Vec<MultiVertex> = Vec::with_capacity(num_clip_verts);
1750        let mut clip_indices: Vec<u32> = Vec::with_capacity(num_clip_inds);
1751
1752        for (vs, inds) in &self.clip_verts_inds {
1753            let offset = clip_verticies.len() as u32;
1754            clip_indices.extend(inds.iter().map(|i| *i + offset));
1755            clip_verticies.extend(vs);
1756        }
1757
1758        // Create vertex and index buffers
1759        let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1760            label: Some("Vertex Buffer"),
1761            contents: bytemuck::cast_slice(verticies.as_slice()),
1762            usage: wgpu::BufferUsages::VERTEX,
1763        });
1764
1765        let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1766            label: Some("Index Buffer"),
1767            contents: bytemuck::cast_slice(indices.as_slice()),
1768            usage: wgpu::BufferUsages::INDEX,
1769        });
1770
1771        let clip_vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1772            label: Some("Clip Vertex Buffer"),
1773            contents: bytemuck::cast_slice(clip_verticies.as_slice()),
1774            usage: wgpu::BufferUsages::VERTEX,
1775        });
1776
1777        let clip_index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1778            label: Some("Clip Index Buffer"),
1779            contents: bytemuck::cast_slice(clip_indices.as_slice()),
1780            usage: wgpu::BufferUsages::INDEX,
1781        });
1782
1783        // Create command encoder for marks
1784        let mut mark_encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
1785            label: Some("Multi Mark Render Encoder"),
1786        });
1787
1788        // Render batches
1789        {
1790            let depth_view = stencil_buffer.create_view(&Default::default());
1791            let mut render_pass = mark_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1792                label: Some("Multi Mark Render Pass"),
1793                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1794                    view: texture_view,
1795                    resolve_target,
1796                    ops: wgpu::Operations {
1797                        load: wgpu::LoadOp::Load,
1798                        store: wgpu::StoreOp::Store,
1799                    },
1800                })],
1801                depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1802                    view: &depth_view,
1803                    depth_ops: if cfg!(feature = "deno") {
1804                        // depth_ops shouldn't be needed, but setting to None results in validation
1805                        // error in Deno. However, setting it to the below causes a validation error
1806                        // in Chrome.
1807                        Some(wgpu::Operations {
1808                            load: wgpu::LoadOp::Clear(0.0),
1809                            store: wgpu::StoreOp::Discard,
1810                        })
1811                    } else {
1812                        None
1813                    },
1814                    stencil_ops: Some(wgpu::Operations {
1815                        load: wgpu::LoadOp::Clear(0),
1816                        store: wgpu::StoreOp::Store,
1817                    }),
1818                }),
1819                occlusion_query_set: None,
1820                timestamp_writes: None,
1821            });
1822
1823            render_pass.set_pipeline(&render_pipeline);
1824            render_pass.set_bind_group(0, &uniform_bind_group, &[]);
1825            let mut last_grad_ind = 0;
1826            let mut last_img_ind = 0;
1827            let mut last_text_ind = 0;
1828            let mut stencil_index: u32 = 1;
1829            render_pass.set_bind_group(1, &gradient_texture_bind_groups[last_grad_ind], &[]);
1830            render_pass.set_bind_group(2, &image_texture_bind_groups[last_img_ind], &[]);
1831            render_pass.set_bind_group(3, &text_texture_bind_groups[last_img_ind], &[]);
1832            render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
1833            render_pass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint32);
1834
1835            // Initialze textures with first entry
1836            for batch in &self.batches {
1837                if let Some(clip_inds_range) = &batch.clip_indices_range {
1838                    render_pass.set_stencil_reference(stencil_index);
1839                    render_pass.set_pipeline(&stencil_pipeline);
1840                    render_pass.set_vertex_buffer(0, clip_vertex_buffer.slice(..));
1841                    render_pass
1842                        .set_index_buffer(clip_index_buffer.slice(..), wgpu::IndexFormat::Uint32);
1843                    render_pass.draw_indexed(clip_inds_range.clone(), 0, 0..1);
1844
1845                    // Restore buffers
1846                    render_pass.set_pipeline(&render_pipeline);
1847                    render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
1848                    render_pass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint32);
1849
1850                    // increment stencil index for next draw
1851                    stencil_index += 1;
1852                } else {
1853                    // Set stencil reference back to zero so that everything is drawn
1854                    render_pass.set_stencil_reference(0);
1855                }
1856
1857                // Update scissors
1858                if let Clip::Rect {
1859                    x,
1860                    y,
1861                    width,
1862                    height,
1863                } = batch.clip
1864                {
1865                    // Set scissors rect
1866                    render_pass.set_scissor_rect(
1867                        (x * self.uniform.scale) as u32,
1868                        (y * self.uniform.scale) as u32,
1869                        (width * self.uniform.scale) as u32,
1870                        (height * self.uniform.scale) as u32,
1871                    );
1872                } else {
1873                    // Clear scissors rect
1874                    render_pass.set_scissor_rect(
1875                        0,
1876                        0,
1877                        self.dimensions.to_physical_width(),
1878                        self.dimensions.to_physical_height(),
1879                    );
1880                }
1881
1882                // Update bind groups
1883                if let Some(grad_ind) = batch.gradient_atlas_index {
1884                    if grad_ind != last_grad_ind {
1885                        render_pass.set_bind_group(1, &gradient_texture_bind_groups[grad_ind], &[]);
1886                        last_grad_ind = grad_ind;
1887                    }
1888                }
1889
1890                if let Some(img_ind) = batch.image_atlas_index {
1891                    if img_ind != last_img_ind {
1892                        render_pass.set_bind_group(2, &image_texture_bind_groups[img_ind], &[]);
1893                    }
1894                    last_img_ind = img_ind;
1895                }
1896
1897                if let Some(text_ind) = batch.text_atlas_index {
1898                    if text_ind != last_text_ind {
1899                        render_pass.set_bind_group(3, &text_texture_bind_groups[text_ind], &[]);
1900                    }
1901                    last_text_ind = text_ind;
1902                }
1903
1904                // draw inds
1905                render_pass.draw_indexed(batch.indices_range.clone(), 0, 0..1);
1906            }
1907        }
1908
1909        mark_encoder.finish()
1910    }
1911
1912    fn make_texture_bind_groups(
1913        device: &Device,
1914        queue: &Queue,
1915        size: Extent3d,
1916        images: &[DynamicImage],
1917        mag_filter: wgpu::FilterMode,
1918        min_filter: wgpu::FilterMode,
1919    ) -> (BindGroupLayout, Vec<BindGroup>) {
1920        // Create texture for each image
1921        let mut texture_bind_groups: Vec<BindGroup> = Vec::new();
1922
1923        // Create texture/sampler bind grous
1924        let texture_bind_group_layout =
1925            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1926                entries: &[
1927                    wgpu::BindGroupLayoutEntry {
1928                        binding: 0,
1929                        visibility: wgpu::ShaderStages::FRAGMENT,
1930                        ty: wgpu::BindingType::Texture {
1931                            multisampled: false,
1932                            view_dimension: wgpu::TextureViewDimension::D2,
1933                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
1934                        },
1935                        count: None,
1936                    },
1937                    wgpu::BindGroupLayoutEntry {
1938                        binding: 1,
1939                        visibility: wgpu::ShaderStages::FRAGMENT,
1940                        // This should match the filterable field of the
1941                        // corresponding Texture entry above.
1942                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1943                        count: None,
1944                    },
1945                ],
1946                label: Some("texture_bind_group_layout"),
1947            });
1948
1949        for image in images {
1950            // Create Texture
1951            let texture = device.create_texture(&wgpu::TextureDescriptor {
1952                size,
1953                mip_level_count: 1,
1954                sample_count: 1,
1955                dimension: wgpu::TextureDimension::D2,
1956                format: wgpu::TextureFormat::Rgba8Unorm,
1957                usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1958                label: Some("diffuse_texture"),
1959                view_formats: &[],
1960            });
1961            let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
1962
1963            // Create sampler
1964            let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1965                address_mode_u: wgpu::AddressMode::ClampToEdge,
1966                address_mode_v: wgpu::AddressMode::ClampToEdge,
1967                address_mode_w: wgpu::AddressMode::ClampToEdge,
1968                mag_filter,
1969                min_filter,
1970                mipmap_filter: wgpu::FilterMode::Nearest,
1971                ..Default::default()
1972            });
1973
1974            let texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1975                layout: &texture_bind_group_layout,
1976                entries: &[
1977                    wgpu::BindGroupEntry {
1978                        binding: 0,
1979                        resource: wgpu::BindingResource::TextureView(&texture_view),
1980                    },
1981                    wgpu::BindGroupEntry {
1982                        binding: 1,
1983                        resource: wgpu::BindingResource::Sampler(&sampler),
1984                    },
1985                ],
1986                label: Some("texture_bind_group"),
1987            });
1988
1989            queue.write_texture(
1990                // Tells wgpu where to copy the pixel data
1991                wgpu::ImageCopyTexture {
1992                    texture: &texture,
1993                    mip_level: 0,
1994                    origin: wgpu::Origin3d::ZERO,
1995                    aspect: wgpu::TextureAspect::All,
1996                },
1997                // The actual pixel data
1998                image.to_rgba8().as_raw(),
1999                // The layout of the texture
2000                wgpu::ImageDataLayout {
2001                    offset: 0,
2002                    bytes_per_row: Some(4 * image.width()),
2003                    rows_per_image: Some(image.height()),
2004                },
2005                size,
2006            );
2007
2008            texture_bind_groups.push(texture_bind_group);
2009        }
2010
2011        (texture_bind_group_layout, texture_bind_groups)
2012    }
2013}
2014
2015pub struct VertexPositions {
2016    fill: [f32; 4],
2017    stroke: [f32; 4],
2018    top_left: [f32; 2],
2019    bottom_right: [f32; 2],
2020}
2021
2022impl FillVertexConstructor<MultiVertex> for VertexPositions {
2023    fn new_vertex(&mut self, vertex: FillVertex) -> MultiVertex {
2024        MultiVertex {
2025            position: [vertex.position().x, vertex.position().y],
2026            color: self.fill,
2027            top_left: self.top_left,
2028            bottom_right: self.bottom_right,
2029        }
2030    }
2031}
2032
2033impl StrokeVertexConstructor<MultiVertex> for VertexPositions {
2034    fn new_vertex(&mut self, vertex: StrokeVertex) -> MultiVertex {
2035        MultiVertex {
2036            position: [vertex.position().x, vertex.position().y],
2037            color: self.stroke,
2038            top_left: self.top_left,
2039            bottom_right: self.bottom_right,
2040        }
2041    }
2042}
2043
2044// Symbol vertex construction that takes line width into account
2045pub struct SymbolVertexPositions {
2046    scale: f32,
2047}
2048
2049impl FillVertexConstructor<SymbolVertex> for SymbolVertexPositions {
2050    fn new_vertex(&mut self, vertex: FillVertex) -> SymbolVertex {
2051        SymbolVertex {
2052            position: [vertex.position().x, vertex.position().y].into(),
2053            normal: None,
2054            scale: self.scale,
2055        }
2056    }
2057}
2058
2059impl StrokeVertexConstructor<SymbolVertex> for SymbolVertexPositions {
2060    fn new_vertex(&mut self, vertex: StrokeVertex) -> SymbolVertex {
2061        SymbolVertex {
2062            position: [vertex.position().x, vertex.position().y].into(),
2063            normal: Some(vertex.normal()),
2064            scale: self.scale,
2065        }
2066    }
2067}
2068
2069pub struct SymbolVertex {
2070    position: Point2D<f32, UnknownUnit>,
2071    normal: Option<Vector2D<f32, UnknownUnit>>,
2072    scale: f32,
2073}
2074
2075impl SymbolVertex {
2076    #[allow(clippy::too_many_arguments)]
2077    pub fn as_multi_vertex(
2078        &self,
2079        size: f32,
2080        x: f32,
2081        y: f32,
2082        angle: f32,
2083        fill: [f32; 4],
2084        stroke: [f32; 4],
2085        line_width: f32,
2086    ) -> MultiVertex {
2087        let angle = Angle::degrees(angle);
2088        let absolue_scale = size.sqrt();
2089        let relative_scale = absolue_scale / self.scale;
2090
2091        // Scale
2092        let mut transform = PathTransform::scale(relative_scale, relative_scale);
2093
2094        // Compute adjustment factor for stroke vertices to compensate for scaling and
2095        // achieve the correct final line width.
2096        let color = if let Some(normal) = self.normal {
2097            let scaled_line_width = relative_scale * NORMALIZED_SYMBOL_STROKE_WIDTH;
2098            let line_width_adjustment = normal.mul((line_width - scaled_line_width) / 2.0);
2099            transform = transform.then_translate(line_width_adjustment);
2100            stroke
2101        } else {
2102            fill
2103        };
2104
2105        // Rotate then Translate
2106        transform = transform
2107            .then_rotate(angle)
2108            .then_translate(Vector2D::new(x, y));
2109        let position = transform.transform_point(self.position);
2110
2111        MultiVertex {
2112            position: position.to_array(),
2113            color,
2114            top_left: [x - absolue_scale / 2.0, y - absolue_scale / 2.0],
2115            bottom_right: [x + absolue_scale / 2.0, y + absolue_scale / 2.0],
2116        }
2117    }
2118}