Skip to main content

euv_chart/backend/webgl/
impl.rs

1use super::*;
2
3/// Inherent implementation of [`WebGl2ChartBackend`].
4impl WebGl2ChartBackend {
5    /// Returns `true` when the host can give out a WebGL2 context.
6    ///
7    /// The check opens a throw-away canvas and asks it for a context, so
8    /// it costs one context creation. It is cheap enough to run while
9    /// the backend is being picked, and it never throws.
10    ///
11    /// # Returns
12    ///
13    /// - `bool` - A boolean.
14    pub fn is_supported() -> bool {
15        is_supported()
16    }
17
18    /// Creates a backend around the given canvas.
19    ///
20    /// Returns `None` when the canvas cannot give out a WebGL2 context —
21    /// in practice, when the canvas already has a WebGPU context, or when
22    /// the browser has no WebGL2 at all — and when the shaders cannot be
23    /// built. The factory then falls back to the Canvas2D backend.
24    ///
25    /// # Arguments
26    ///
27    /// - `&HtmlCanvasElement` - Shared reference to a `HtmlCanvasElement`.
28    ///
29    /// # Returns
30    ///
31    /// - `Option<Self>` - `Some(...)` on success, `None` otherwise.
32    pub fn create(canvas: &HtmlCanvasElement) -> Option<Self> {
33        let attributes: WebGlContextAttributes = WebGlContextAttributes::new();
34        attributes.set_alpha(true);
35        attributes.set_premultiplied_alpha(true);
36        attributes.set_antialias(true);
37        attributes.set_depth(false);
38        attributes.set_stencil(false);
39        attributes.set_preserve_drawing_buffer(false);
40        let options: &JsValue = attributes.as_ref();
41        let context: WebGl2RenderingContext = canvas
42            .get_context_with_context_options("webgl2", options)
43            .ok()
44            .flatten()?
45            .dyn_into::<WebGl2RenderingContext>()
46            .ok()?;
47        let mut backend: WebGl2ChartBackend = Self {
48            canvas: canvas.clone(),
49            context,
50            program: None,
51            vertex_array: None,
52            vertex_buffer: None,
53            index_buffer: None,
54            atlas_texture: None,
55            ramp_texture: None,
56            parameter_texture: None,
57            images: Vec::new(),
58            tessellator: Tessellator::new(),
59            vertices: Vec::new(),
60            indices: Vec::new(),
61            batches: Vec::new(),
62            gradients: Vec::new(),
63            fingerprint: 0,
64            built: false,
65            width: 0.0,
66            height: 0.0,
67            dpr: 1.0,
68            scissor: None,
69            scissor_dirty: true,
70            primary_texture: None,
71        };
72        if !backend.ensure_resources() {
73            return None;
74        }
75        Some(backend)
76    }
77
78    /// Returns the canvas the backend draws into.
79    ///
80    /// # Returns
81    ///
82    /// - `&HtmlCanvasElement` - A `HtmlCanvasElement` value.
83    pub fn canvas(&self) -> &HtmlCanvasElement {
84        self.get_canvas()
85    }
86
87    /// Returns the number of draw calls the last frame used.
88    ///
89    /// The number is a debugging aid: it is the number of batches the
90    /// last frame was split into, which is what a chart with no images
91    /// and no clip paths reports as one.
92    ///
93    /// # Returns
94    ///
95    /// - `usize` - A non-negative integer.
96    pub fn draw_call_count(&self) -> usize {
97        self.get_batches().len()
98    }
99
100    /// Builds the GPU resources the backend needs, if they are missing.
101    ///
102    /// The resources are created once and then kept: the backend only
103    /// comes back here after [`ChartBackend::dispose`] has released
104    /// them.
105    ///
106    /// # Returns
107    ///
108    /// - `bool` - A boolean.
109    fn ensure_resources(&mut self) -> bool {
110        if self.try_get_program().is_some()
111            && self.try_get_vertex_array().is_some()
112            && self.try_get_vertex_buffer().is_some()
113            && self.try_get_index_buffer().is_some()
114        {
115            return true;
116        }
117        let Some(program) = WebGl2Program::create(self.get_context()) else {
118            return false;
119        };
120        let Some(vertex_array) = self.get_context().create_vertex_array() else {
121            return false;
122        };
123        let Some(vertex_buffer) = self.get_context().create_buffer() else {
124            return false;
125        };
126        let Some(index_buffer) = self.get_context().create_buffer() else {
127            return false;
128        };
129        // The vertex layout is recorded in the vertex array object, so
130        // it is set up once and every later frame inherits it.
131        self.get_context().bind_vertex_array(Some(&vertex_array));
132        self.get_context()
133            .bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&vertex_buffer));
134        self.get_context().bind_buffer(
135            WebGl2RenderingContext::ELEMENT_ARRAY_BUFFER,
136            Some(&index_buffer),
137        );
138        for (location, size, offset) in [
139            (GL_ATTRIBUTE_POSITION, 2, 0),
140            (GL_ATTRIBUTE_UV, 2, 8),
141            (GL_ATTRIBUTE_COLOR, 4, 16),
142            (GL_ATTRIBUTE_META, 2, 24),
143        ] {
144            self.get_context().enable_vertex_attrib_array(location);
145            self.get_context().vertex_attrib_pointer_with_i32(
146                location,
147                size,
148                WebGl2RenderingContext::FLOAT,
149                false,
150                GL_VERTEX_STRIDE,
151                offset,
152            );
153        }
154        // The chart draws in the order the display list asks for, with
155        // alpha blending and without depth or face culling: the
156        // tessellator emits triangles of both windings, and a label has
157        // to stay visible whatever the shapes around it do.
158        self.get_context().enable(WebGl2RenderingContext::BLEND);
159        self.get_context().blend_func_separate(
160            WebGl2RenderingContext::ONE,
161            WebGl2RenderingContext::ONE_MINUS_SRC_ALPHA,
162            WebGl2RenderingContext::ONE,
163            WebGl2RenderingContext::ONE_MINUS_SRC_ALPHA,
164        );
165        self.get_context()
166            .disable(WebGl2RenderingContext::DEPTH_TEST);
167        self.get_context()
168            .disable(WebGl2RenderingContext::CULL_FACE);
169        program.set_samplers(self.get_context());
170        self.set_program(Some(program));
171        self.set_vertex_array(Some(vertex_array));
172        self.set_vertex_buffer(Some(vertex_buffer));
173        self.set_index_buffer(Some(index_buffer));
174        true
175    }
176
177    /// Rebuilds the geometry of the frame from the display list.
178    ///
179    /// Everything the display list holds ends up in one vertex buffer,
180    /// in paint order: the fills, the strokes, the area strips, the
181    /// labels, and the images. The images are the only exception — they
182    /// need a texture of their own, so they end the batch they are in.
183    ///
184    /// # Arguments
185    ///
186    /// - `&DisplayList` - Shared reference to a `DisplayList`.
187    /// - `&mut TextShaper` - Mutable reference to a `TextShaper` (mutated in place).
188    fn rebuild(&mut self, list: &DisplayList, shaper: &mut TextShaper) {
189        self.get_mut_vertices().clear();
190        self.get_mut_indices().clear();
191        self.get_mut_batches().clear();
192        self.get_mut_gradients().clear();
193        let mut clip_stack: Vec<Option<WebGl2Scissor>> = Vec::new();
194        let mut clip: Option<WebGl2Scissor> = None;
195        let mut batch_start: usize = 0_usize;
196        let mut batch_kind: WebGl2BatchKind = WebGl2BatchKind::Atlas;
197        let mut batch_image: usize = 0_usize;
198        for item in list.items() {
199            match &item.cmd {
200                DrawCmd::Fill {
201                    path,
202                    paint,
203                    opacity,
204                } => {
205                    self.append_fill(path, paint, *opacity);
206                }
207                DrawCmd::Stroke {
208                    path,
209                    paint,
210                    style,
211                    opacity,
212                } => {
213                    self.append_stroke(path, paint, style, *opacity);
214                }
215                DrawCmd::Strip {
216                    points,
217                    baseline,
218                    paint,
219                    opacity,
220                } => {
221                    self.append_strip(points, *baseline, paint, *opacity);
222                }
223                DrawCmd::Text { text, x, y, style } => {
224                    self.append_text(text, *x, *y, style, shaper);
225                }
226                DrawCmd::Image {
227                    image,
228                    x,
229                    y,
230                    width,
231                    height,
232                } => {
233                    // An image is sampled from a texture of its own, so
234                    // it closes the batch it follows and forms one of
235                    // its own.
236                    self.flush_batch(&mut batch_start, batch_kind, batch_image, clip);
237                    let image_index: usize = self.image_index(image);
238                    self.append_image(*x, *y, *width, *height);
239                    self.flush_batch(&mut batch_start, WebGl2BatchKind::Image, image_index, clip);
240                    batch_kind = WebGl2BatchKind::Atlas;
241                    batch_image = 0;
242                }
243                DrawCmd::Clip { path } => {
244                    self.flush_batch(&mut batch_start, batch_kind, batch_image, clip);
245                    clip_stack.push(clip);
246                    clip = Some(intersect_scissor(clip, scissor_of(&path.bounds())));
247                }
248                DrawCmd::PopClip => {
249                    self.flush_batch(&mut batch_start, batch_kind, batch_image, clip);
250                    clip = clip_stack.pop().flatten();
251                }
252            }
253        }
254        self.flush_batch(&mut batch_start, batch_kind, batch_image, clip);
255    }
256
257    /// Appends a filled path.
258    ///
259    /// # Arguments
260    ///
261    /// - `&Path` - Shared reference to a `Path`.
262    /// - `&Fill` - Shared reference to a `Fill`.
263    /// - `f64` - A 64-bit float (`f64`).
264    fn append_fill(&mut self, path: &Path, paint: &Fill, opacity: f64) {
265        let style: WebGl2Style = match paint {
266            Fill::None => return,
267            Fill::Solid(color) => WebGl2Style {
268                color: scaled_color(*color, opacity),
269                kind: WebGl2VertexKind::Solid.code(),
270                gradient: 0.0,
271            },
272            Fill::Linear(gradient) => {
273                let row: f32 = self.gradient_row(&WebGl2Gradient::Linear((*gradient).clone()));
274                WebGl2Style {
275                    color: [1.0, 1.0, 1.0, opacity.clamp(0.0, 1.0) as f32],
276                    kind: WebGl2VertexKind::Gradient.code(),
277                    gradient: row,
278                }
279            }
280            Fill::Radial(gradient) => {
281                let row: f32 = self.gradient_row(&WebGl2Gradient::Radial((*gradient).clone()));
282                WebGl2Style {
283                    color: [1.0, 1.0, 1.0, opacity.clamp(0.0, 1.0) as f32],
284                    kind: WebGl2VertexKind::Gradient.code(),
285                    gradient: row,
286                }
287            }
288        };
289        let mesh: SolidMesh = self.get_mut_tessellator().fill(path, 0);
290        self.push_mesh(&mesh, &style);
291    }
292
293    /// Appends a stroked path.
294    ///
295    /// # Arguments
296    ///
297    /// - `&Path` - Shared reference to a `Path`.
298    /// - `&Stroke` - Shared reference to a `Stroke`.
299    /// - `&LineStyle` - Shared reference to a `LineStyle`.
300    /// - `f64` - A 64-bit float (`f64`).
301    fn append_stroke(&mut self, path: &Path, paint: &Stroke, style: &LineStyle, opacity: f64) {
302        if style.width <= 0.0 {
303            return;
304        }
305        let alpha: f32 = (opacity * style.opacity).clamp(0.0, 1.0) as f32;
306        let shading: WebGl2Style = match paint {
307            Stroke::None => return,
308            Stroke::Solid(color) => WebGl2Style {
309                color: scaled_color(*color, opacity * style.opacity),
310                kind: WebGl2VertexKind::Solid.code(),
311                gradient: 0.0,
312            },
313            Stroke::Linear(gradient) => {
314                let row: f32 = self.gradient_row(&WebGl2Gradient::Linear((*gradient).clone()));
315                WebGl2Style {
316                    color: [1.0, 1.0, 1.0, alpha],
317                    kind: WebGl2VertexKind::Gradient.code(),
318                    gradient: row,
319                }
320            }
321            Stroke::Radial(gradient) => {
322                let row: f32 = self.gradient_row(&WebGl2Gradient::Radial((*gradient).clone()));
323                WebGl2Style {
324                    color: [1.0, 1.0, 1.0, alpha],
325                    kind: WebGl2VertexKind::Gradient.code(),
326                    gradient: row,
327                }
328            }
329        };
330        let geometry: StrokeStyle = style.geometry();
331        let mesh: SolidMesh = if style.dash.is_solid() {
332            self.get_mut_tessellator().stroke(path, &geometry, 0)
333        } else {
334            // A dashed stroke is broken into its dashes first, because
335            // the tessellator strokes a path as one piece of geometry.
336            let contours: Vec<Contour> = self.get_mut_tessellator().flatten(path).contours.clone();
337            let dashed: Vec<Contour> =
338                dash_contours(&contours, &style.dash.pattern, style.dash.offset);
339            self.get_mut_tessellator()
340                .stroke_contours(&dashed, &geometry, 0)
341        };
342        self.push_mesh(&mesh, &shading);
343    }
344
345    /// Appends an area strip.
346    ///
347    /// The strip is already a set of triangles, so it is copied over as
348    /// it is: running the tessellator over it again would be wasted
349    /// work on the hottest path of the whole crate.
350    ///
351    /// # Arguments
352    ///
353    /// - `&[Point]` - Shared reference to a `[Point]`.
354    /// - `f32` - A 32-bit float (`f32`).
355    /// - `&Fill` - Shared reference to a `Fill`.
356    /// - `f64` - A 64-bit float (`f64`).
357    fn append_strip(&mut self, points: &[Point], baseline: f32, paint: &Fill, opacity: f64) {
358        if points.len() < 2 {
359            return;
360        }
361        let style: WebGl2Style = match paint {
362            Fill::None => return,
363            Fill::Solid(color) => WebGl2Style {
364                color: scaled_color(*color, opacity),
365                kind: WebGl2VertexKind::Solid.code(),
366                gradient: 0.0,
367            },
368            Fill::Linear(gradient) => {
369                let row: f32 = self.gradient_row(&WebGl2Gradient::Linear((*gradient).clone()));
370                WebGl2Style {
371                    color: [1.0, 1.0, 1.0, opacity.clamp(0.0, 1.0) as f32],
372                    kind: WebGl2VertexKind::Gradient.code(),
373                    gradient: row,
374                }
375            }
376            Fill::Radial(gradient) => {
377                let row: f32 = self.gradient_row(&WebGl2Gradient::Radial((*gradient).clone()));
378                WebGl2Style {
379                    color: [1.0, 1.0, 1.0, opacity.clamp(0.0, 1.0) as f32],
380                    kind: WebGl2VertexKind::Gradient.code(),
381                    gradient: row,
382                }
383            }
384        };
385        let mesh: SolidMesh = self.get_mut_tessellator().fill_strip(points, baseline, 0);
386        self.push_mesh(&mesh, &style);
387    }
388
389    /// Appends a label.
390    ///
391    /// The run is shaped with the same shaper the other backends use, so
392    /// a label lands on the same pixel whichever backend is in use. The
393    /// background of the label is a plain rectangle, drawn before the
394    /// glyphs, exactly as the Canvas2D backend draws it.
395    ///
396    /// # Arguments
397    ///
398    /// - `&str` - Shared reference to a `str`.
399    /// - `f64` - A 64-bit float (`f64`).
400    /// - `&TextStyle` - Shared reference to a `TextStyle`.
401    /// - `&mut TextShaper` - Mutable reference to a `TextShaper` (mutated in place).
402    fn append_text(
403        &mut self,
404        text: &str,
405        x: f64,
406        y: f64,
407        style: &TextStyle,
408        shaper: &mut TextShaper,
409    ) {
410        let run: TextRun = shaper.shape(text, style, x, y);
411        if let Some(background) = style.background {
412            let shading: WebGl2Style = WebGl2Style {
413                color: scaled_color(background, 1.0),
414                kind: WebGl2VertexKind::Solid.code(),
415                gradient: 0.0,
416            };
417            let rect: WebGl2TextureRect = WebGl2TextureRect {
418                u: 0.0,
419                v: 0.0,
420                u_size: 0.0,
421                v_size: 0.0,
422            };
423            for line in &run.lines {
424                let left: f64 = line.left - style.padding.3;
425                let top: f64 = line.baseline - run.ascent - style.padding.0;
426                let width: f64 = line.width + style.padding.1 + style.padding.3;
427                let height: f64 = run.ascent + run.descent + style.padding.0 + style.padding.2;
428                self.push_quad(
429                    left as f32,
430                    top as f32,
431                    width as f32,
432                    height as f32,
433                    rect,
434                    &shading,
435                );
436            }
437        }
438        let shading: WebGl2Style = WebGl2Style {
439            color: unpack_color(style.color.to_u32()),
440            kind: WebGl2VertexKind::Text.code(),
441            gradient: 0.0,
442        };
443        for quad in &run.quads {
444            let rect: WebGl2TextureRect = WebGl2TextureRect {
445                u: quad.u,
446                v: quad.v,
447                u_size: quad.u_size,
448                v_size: quad.v_size,
449            };
450            self.push_quad(quad.x, quad.y, quad.width, quad.height, rect, &shading);
451        }
452    }
453
454    /// Appends an image quad.
455    ///
456    /// # Arguments
457    ///
458    /// - `f64` - A 64-bit float (`f64`).
459    fn append_image(&mut self, x: f64, y: f64, width: f64, height: f64) {
460        let shading: WebGl2Style = WebGl2Style {
461            color: [1.0, 1.0, 1.0, 1.0],
462            kind: WebGl2VertexKind::Image.code(),
463            gradient: 0.0,
464        };
465        let rect: WebGl2TextureRect = WebGl2TextureRect {
466            u: 0.0,
467            v: 0.0,
468            u_size: 1.0,
469            v_size: 1.0,
470        };
471        self.push_quad(
472            x as f32,
473            y as f32,
474            width as f32,
475            height as f32,
476            rect,
477            &shading,
478        );
479    }
480
481    /// Appends one vertex.
482    ///
483    /// # Arguments
484    ///
485    /// - `f32` - A 32-bit float (`f32`).
486    /// - `&WebGl2Style` - Shared reference to a `WebGl2Style`.
487    fn push_vertex(&mut self, x: f32, y: f32, u: f32, v: f32, style: &WebGl2Style) {
488        self.get_mut_vertices().push(x);
489        self.get_mut_vertices().push(y);
490        self.get_mut_vertices().push(u);
491        self.get_mut_vertices().push(v);
492        self.get_mut_vertices().extend_from_slice(&style.color);
493        self.get_mut_vertices().push(style.kind);
494        self.get_mut_vertices().push(style.gradient);
495    }
496
497    /// Appends a tessellated mesh.
498    ///
499    /// # Arguments
500    ///
501    /// - `&SolidMesh` - Shared reference to a `SolidMesh`.
502    /// - `&WebGl2Style` - Shared reference to a `WebGl2Style`.
503    fn push_mesh(&mut self, mesh: &SolidMesh, style: &WebGl2Style) {
504        let base: u32 = (self.get_vertices().len() / GL_VERTEX_FLOATS) as u32;
505        for vertex in &mesh.vertices {
506            self.push_vertex(vertex.x, vertex.y, 0.0, 0.0, style);
507        }
508        for index in &mesh.indices {
509            self.get_mut_indices().push(base + index);
510        }
511    }
512
513    /// Appends one quad.
514    ///
515    /// # Arguments
516    ///
517    /// - `f32` - A 32-bit float (`f32`).
518    /// - `WebGl2TextureRect` - A `WebGl2TextureRect` parameter.
519    /// - `&WebGl2Style` - Shared reference to a `WebGl2Style`.
520    fn push_quad(
521        &mut self,
522        x: f32,
523        y: f32,
524        width: f32,
525        height: f32,
526        rect: WebGl2TextureRect,
527        style: &WebGl2Style,
528    ) {
529        let base: u32 = (self.get_vertices().len() / GL_VERTEX_FLOATS) as u32;
530        self.push_vertex(x, y, rect.u, rect.v, style);
531        self.push_vertex(x + width, y, rect.u + rect.u_size, rect.v, style);
532        self.push_vertex(
533            x + width,
534            y + height,
535            rect.u + rect.u_size,
536            rect.v + rect.v_size,
537            style,
538        );
539        self.push_vertex(x, y + height, rect.u, rect.v + rect.v_size, style);
540        self.get_mut_indices().push(base);
541        self.get_mut_indices().push(base + 1);
542        self.get_mut_indices().push(base + 2);
543        self.get_mut_indices().push(base);
544        self.get_mut_indices().push(base + 2);
545        self.get_mut_indices().push(base + 3);
546    }
547
548    /// Closes the batch that is being built.
549    ///
550    /// # Arguments
551    ///
552    /// - `&mut usize` - Mutable reference to a `usize` (mutated in place).
553    /// - `WebGl2BatchKind` - A `WebGl2BatchKind` parameter.
554    /// - `usize` - Mutable reference to a `usize` (mutated in place).
555    /// - `Option<WebGl2Scissor>` - A `Option<WebGl2Scissor>` parameter.
556    fn flush_batch(
557        &mut self,
558        start: &mut usize,
559        kind: WebGl2BatchKind,
560        image: usize,
561        clip: Option<WebGl2Scissor>,
562    ) {
563        let count: usize = self.get_indices().len() - *start;
564        if count == 0 {
565            return;
566        }
567        self.get_mut_batches().push(WebGl2Batch {
568            kind,
569            first_index: *start as u32,
570            index_count: count as u32,
571            image,
572            clip,
573        });
574        *start = self.get_indices().len();
575    }
576
577    /// Returns the row of the gradient table a gradient sits in.
578    ///
579    /// Gradients are shared: two shapes that use the same gradient point
580    /// at the same row of the ramp texture, so the number of rows stays
581    /// as small as the number of distinct gradients in the chart.
582    ///
583    /// # Arguments
584    ///
585    /// - `&WebGl2Gradient` - Shared reference to a `WebGl2Gradient`.
586    ///
587    /// # Returns
588    ///
589    /// - `f32` - A 32-bit float.
590    fn gradient_row(&mut self, gradient: &WebGl2Gradient) -> f32 {
591        for (index, existing) in self.get_gradients().iter().enumerate() {
592            let same: bool = match (existing, gradient) {
593                (WebGl2Gradient::Linear(left), WebGl2Gradient::Linear(right)) => left == right,
594                (WebGl2Gradient::Radial(left), WebGl2Gradient::Radial(right)) => left == right,
595                _ => false,
596            };
597            if same {
598                return index as f32;
599            }
600        }
601        self.get_mut_gradients().push(gradient.clone());
602        (self.get_gradients().len() - 1) as f32
603    }
604
605    /// Returns the index of the image table an image sits in.
606    ///
607    /// # Arguments
608    ///
609    /// - `&HtmlImageElement` - Shared reference to a `HtmlImageElement`.
610    ///
611    /// # Returns
612    ///
613    /// - `usize` - A non-negative integer.
614    fn image_index(&mut self, image: &HtmlImageElement) -> usize {
615        let incoming: &JsValue = image.as_ref();
616        for (index, existing) in self.get_images().iter().enumerate() {
617            let current: &JsValue = existing.element.as_ref();
618            if current == incoming {
619                return index;
620            }
621        }
622        self.get_mut_images().push(WebGl2Image {
623            element: image.clone(),
624            texture: None,
625        });
626        self.get_images().len() - 1
627    }
628
629    /// Uploads the geometry of the frame.
630    fn upload_geometry(&mut self) {
631        let (Some(vertex_array), Some(vertex_buffer), Some(index_buffer)) = (
632            self.try_get_vertex_array().as_ref(),
633            self.try_get_vertex_buffer().as_ref(),
634            self.try_get_index_buffer().as_ref(),
635        ) else {
636            return;
637        };
638        self.get_context().bind_vertex_array(Some(vertex_array));
639        let vertices: Float32Array = js_sys::Float32Array::from(self.get_vertices().as_slice());
640        self.get_context()
641            .bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(vertex_buffer));
642        self.get_context().buffer_data_with_array_buffer_view(
643            WebGl2RenderingContext::ARRAY_BUFFER,
644            vertices.as_ref(),
645            WebGl2RenderingContext::STATIC_DRAW,
646        );
647        let indices: Uint32Array = js_sys::Uint32Array::from(self.get_indices().as_slice());
648        self.get_context().bind_buffer(
649            WebGl2RenderingContext::ELEMENT_ARRAY_BUFFER,
650            Some(index_buffer),
651        );
652        self.get_context().buffer_data_with_array_buffer_view(
653            WebGl2RenderingContext::ELEMENT_ARRAY_BUFFER,
654            indices.as_ref(),
655            WebGl2RenderingContext::STATIC_DRAW,
656        );
657    }
658
659    /// Uploads the glyph atlas.
660    ///
661    /// # Arguments
662    ///
663    /// - `&GlyphAtlas` - Shared reference to a `GlyphAtlas`.
664    fn upload_atlas(&mut self, atlas: &GlyphAtlas) {
665        let existing: Option<WebGlTexture> = self.try_get_atlas_texture().clone();
666        let texture: WebGlTexture = match existing {
667            Some(texture) => texture,
668            None => {
669                let Some(texture) = self.get_context().create_texture() else {
670                    return;
671                };
672                self.get_context()
673                    .bind_texture(WebGl2RenderingContext::TEXTURE_2D, Some(&texture));
674                self.get_context().tex_parameteri(
675                    WebGl2RenderingContext::TEXTURE_2D,
676                    WebGl2RenderingContext::TEXTURE_MIN_FILTER,
677                    WebGl2RenderingContext::LINEAR as i32,
678                );
679                self.get_context().tex_parameteri(
680                    WebGl2RenderingContext::TEXTURE_2D,
681                    WebGl2RenderingContext::TEXTURE_MAG_FILTER,
682                    WebGl2RenderingContext::LINEAR as i32,
683                );
684                self.get_context().tex_parameteri(
685                    WebGl2RenderingContext::TEXTURE_2D,
686                    WebGl2RenderingContext::TEXTURE_WRAP_S,
687                    WebGl2RenderingContext::CLAMP_TO_EDGE as i32,
688                );
689                self.get_context().tex_parameteri(
690                    WebGl2RenderingContext::TEXTURE_2D,
691                    WebGl2RenderingContext::TEXTURE_WRAP_T,
692                    WebGl2RenderingContext::CLAMP_TO_EDGE as i32,
693                );
694                self.set_atlas_texture(Some(texture.clone()));
695                texture
696            }
697        };
698        self.get_context()
699            .active_texture(WebGl2RenderingContext::TEXTURE0 + GL_TEXTURE_UNIT_PRIMARY);
700        self.get_context()
701            .bind_texture(WebGl2RenderingContext::TEXTURE_2D, Some(&texture));
702        let _: Result<(), JsValue> = self
703            .get_context()
704            .tex_image_2d_with_u32_and_u32_and_html_canvas_element(
705                WebGl2RenderingContext::TEXTURE_2D,
706                0,
707                WebGl2RenderingContext::RGBA as i32,
708                WebGl2RenderingContext::RGBA,
709                WebGl2RenderingContext::UNSIGNED_BYTE,
710                atlas.canvas(),
711            );
712        // The binding on the primary unit changed, so the backend cannot
713        // assume the atlas is still bound there.
714        self.set_primary_texture(None);
715    }
716
717    /// Uploads the gradient ramps and their parameters.
718    fn upload_gradients(&mut self) {
719        let rows: usize = self.get_gradients().len().max(1);
720        let mut ramp: Vec<u8> = vec![0_u8; GL_RAMP_WIDTH * rows * 4];
721        for (index, gradient) in self.get_gradients().iter().enumerate() {
722            let bytes: Vec<u8> = ramp_bytes(gradient);
723            let start: usize = index * GL_RAMP_WIDTH * 4;
724            ramp[start..start + bytes.len()].copy_from_slice(&bytes);
725        }
726        let mut parameters: Vec<f32> = vec![0.0_f32; GL_PARAMETER_WIDTH * rows * 4];
727        for (index, gradient) in self.get_gradients().iter().enumerate() {
728            let values: [f32; 8] = parameter_values(gradient);
729            let start: usize = index * GL_PARAMETER_WIDTH * 4;
730            parameters[start..start + values.len()].copy_from_slice(&values);
731        }
732        // The ramp holds the colours, so it is filtered smoothly.
733        let existing: Option<WebGlTexture> = self.try_get_ramp_texture().clone();
734        let ramp_texture: WebGlTexture = match existing {
735            Some(texture) => texture,
736            None => {
737                let Some(texture) = self.get_context().create_texture() else {
738                    return;
739                };
740                self.set_ramp_texture(Some(texture.clone()));
741                texture
742            }
743        };
744        self.get_context()
745            .active_texture(WebGl2RenderingContext::TEXTURE0 + GL_TEXTURE_UNIT_RAMP);
746        self.get_context()
747            .bind_texture(WebGl2RenderingContext::TEXTURE_2D, Some(&ramp_texture));
748        self.get_context().tex_parameteri(
749            WebGl2RenderingContext::TEXTURE_2D,
750            WebGl2RenderingContext::TEXTURE_MIN_FILTER,
751            WebGl2RenderingContext::LINEAR as i32,
752        );
753        self.get_context().tex_parameteri(
754            WebGl2RenderingContext::TEXTURE_2D,
755            WebGl2RenderingContext::TEXTURE_MAG_FILTER,
756            WebGl2RenderingContext::LINEAR as i32,
757        );
758        self.get_context().tex_parameteri(
759            WebGl2RenderingContext::TEXTURE_2D,
760            WebGl2RenderingContext::TEXTURE_WRAP_S,
761            WebGl2RenderingContext::CLAMP_TO_EDGE as i32,
762        );
763        self.get_context().tex_parameteri(
764            WebGl2RenderingContext::TEXTURE_2D,
765            WebGl2RenderingContext::TEXTURE_WRAP_T,
766            WebGl2RenderingContext::CLAMP_TO_EDGE as i32,
767        );
768        let _: Result<(), JsValue> = self
769            .get_context()
770            .tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
771                WebGl2RenderingContext::TEXTURE_2D,
772                0,
773                WebGl2RenderingContext::RGBA as i32,
774                GL_RAMP_WIDTH as i32,
775                rows as i32,
776                0,
777                WebGl2RenderingContext::RGBA,
778                WebGl2RenderingContext::UNSIGNED_BYTE,
779                Some(&ramp),
780            );
781        // The parameters hold coordinates, so they are read exactly and
782        // never filtered.
783        let existing: Option<WebGlTexture> = self.try_get_parameter_texture().clone();
784        let parameter_texture: WebGlTexture = match existing {
785            Some(texture) => texture,
786            None => {
787                let Some(texture) = self.get_context().create_texture() else {
788                    return;
789                };
790                self.set_parameter_texture(Some(texture.clone()));
791                texture
792            }
793        };
794        self.get_context()
795            .active_texture(WebGl2RenderingContext::TEXTURE0 + GL_TEXTURE_UNIT_PARAMETERS);
796        self.get_context()
797            .bind_texture(WebGl2RenderingContext::TEXTURE_2D, Some(&parameter_texture));
798        self.get_context().tex_parameteri(
799            WebGl2RenderingContext::TEXTURE_2D,
800            WebGl2RenderingContext::TEXTURE_MIN_FILTER,
801            WebGl2RenderingContext::NEAREST as i32,
802        );
803        self.get_context().tex_parameteri(
804            WebGl2RenderingContext::TEXTURE_2D,
805            WebGl2RenderingContext::TEXTURE_MAG_FILTER,
806            WebGl2RenderingContext::NEAREST as i32,
807        );
808        self.get_context().tex_parameteri(
809            WebGl2RenderingContext::TEXTURE_2D,
810            WebGl2RenderingContext::TEXTURE_WRAP_S,
811            WebGl2RenderingContext::CLAMP_TO_EDGE as i32,
812        );
813        self.get_context().tex_parameteri(
814            WebGl2RenderingContext::TEXTURE_2D,
815            WebGl2RenderingContext::TEXTURE_WRAP_T,
816            WebGl2RenderingContext::CLAMP_TO_EDGE as i32,
817        );
818        let view: Float32Array = js_sys::Float32Array::from(parameters.as_slice());
819        let _: Result<(), JsValue> = self
820            .get_context()
821            .tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_array_buffer_view_and_src_offset(
822                WebGl2RenderingContext::TEXTURE_2D,
823                0,
824                WebGl2RenderingContext::RGBA32F as i32,
825                GL_PARAMETER_WIDTH as i32,
826                rows as i32,
827                0,
828                WebGl2RenderingContext::RGBA,
829                WebGl2RenderingContext::FLOAT,
830                view.as_ref(),
831                0,
832            );
833    }
834
835    /// Uploads the images the frame refers to.
836    fn upload_images(&mut self) {
837        for index in 0..self.get_images().len() {
838            let element: HtmlImageElement = self.get_images()[index].element.clone();
839            let existing: Option<WebGlTexture> = self.get_images()[index].texture.clone();
840            let texture: WebGlTexture = match existing {
841                Some(texture) => texture,
842                None => {
843                    let Some(texture) = self.get_context().create_texture() else {
844                        continue;
845                    };
846                    self.get_context()
847                        .bind_texture(WebGl2RenderingContext::TEXTURE_2D, Some(&texture));
848                    self.get_context().tex_parameteri(
849                        WebGl2RenderingContext::TEXTURE_2D,
850                        WebGl2RenderingContext::TEXTURE_MIN_FILTER,
851                        WebGl2RenderingContext::LINEAR as i32,
852                    );
853                    self.get_context().tex_parameteri(
854                        WebGl2RenderingContext::TEXTURE_2D,
855                        WebGl2RenderingContext::TEXTURE_MAG_FILTER,
856                        WebGl2RenderingContext::LINEAR as i32,
857                    );
858                    self.get_context().tex_parameteri(
859                        WebGl2RenderingContext::TEXTURE_2D,
860                        WebGl2RenderingContext::TEXTURE_WRAP_S,
861                        WebGl2RenderingContext::CLAMP_TO_EDGE as i32,
862                    );
863                    self.get_context().tex_parameteri(
864                        WebGl2RenderingContext::TEXTURE_2D,
865                        WebGl2RenderingContext::TEXTURE_WRAP_T,
866                        WebGl2RenderingContext::CLAMP_TO_EDGE as i32,
867                    );
868                    self.get_mut_images()[index].texture = Some(texture.clone());
869                    texture
870                }
871            };
872            self.get_context()
873                .active_texture(WebGl2RenderingContext::TEXTURE0 + GL_TEXTURE_UNIT_PRIMARY);
874            self.get_context()
875                .bind_texture(WebGl2RenderingContext::TEXTURE_2D, Some(&texture));
876            let _: Result<(), JsValue> = self
877                .get_context()
878                .tex_image_2d_with_u32_and_u32_and_html_image_element(
879                    WebGl2RenderingContext::TEXTURE_2D,
880                    0,
881                    WebGl2RenderingContext::RGBA as i32,
882                    WebGl2RenderingContext::RGBA,
883                    WebGl2RenderingContext::UNSIGNED_BYTE,
884                    &element,
885                );
886        }
887        // The binding on the primary unit changed, so the backend cannot
888        // assume the atlas is still bound there.
889        self.set_primary_texture(None);
890    }
891
892    /// Draws the frame.
893    ///
894    /// The frame is one draw call for every batch, and an ordinary chart
895    /// holds a single batch: the whole picture is one set of triangles
896    /// in one buffer, drawn in the order the display list asks for.
897    fn draw(&mut self) {
898        let Some(program) = self.try_get_program().as_ref() else {
899            return;
900        };
901        let drawing_width: u32 = self.get_canvas().width();
902        let drawing_height: u32 = self.get_canvas().height();
903        self.get_context()
904            .viewport(0, 0, drawing_width as i32, drawing_height as i32);
905        self.get_context().clear_color(0.0, 0.0, 0.0, 0.0);
906        self.get_context()
907            .clear(WebGl2RenderingContext::COLOR_BUFFER_BIT);
908        self.get_context().use_program(Some(&program.program));
909        program.set_resolution_uniform(
910            self.get_context(),
911            self.get_width() as f32,
912            self.get_height() as f32,
913        );
914        program.set_gradient_count_uniform(self.get_context(), self.get_gradients().len() as f32);
915        self.bind_ramp();
916        self.bind_parameters();
917        // The batches are taken out of the backend for the duration of
918        // the draw, so that the texture bindings can be updated while
919        // they are walked.
920        let batches: Vec<WebGl2Batch> = take(self.get_mut_batches());
921        for batch in &batches {
922            self.apply_scissor(batch.clip, drawing_height);
923            match batch.kind {
924                WebGl2BatchKind::Atlas => self.bind_atlas(),
925                WebGl2BatchKind::Image => self.bind_image(batch.image),
926            }
927            self.get_context().draw_elements_with_i32(
928                WebGl2RenderingContext::TRIANGLES,
929                batch.index_count as i32,
930                WebGl2RenderingContext::UNSIGNED_INT,
931                (batch.first_index as i32) * 4,
932            );
933        }
934        self.set_batches(batches);
935    }
936
937    /// Applies the scissor rectangle of a batch.
938    ///
939    /// WebGL2 has no path clipping, so a clip path is approximated by
940    /// the rectangle that bounds it. The rectangle is converted into the
941    /// framebuffer's own coordinate system, whose origin is at the
942    /// bottom left.
943    ///
944    /// # Arguments
945    ///
946    /// - `Option<WebGl2Scissor>` - A `Option<WebGl2Scissor>` parameter.
947    /// - `u32` - A 32-bit unsigned integer (`u32`).
948    fn apply_scissor(&mut self, clip: Option<WebGl2Scissor>, drawing_height: u32) {
949        if !self.get_scissor_dirty() && clip == *self.try_get_scissor() {
950            return;
951        }
952        self.set_scissor_dirty(false);
953        self.set_scissor(clip);
954        let Some(rect) = clip else {
955            self.get_context()
956                .disable(WebGl2RenderingContext::SCISSOR_TEST);
957            return;
958        };
959        let x: i32 = (rect.x * self.get_dpr()).round().max(0.0) as i32;
960        let y: i32 = (rect.y * self.get_dpr()).round().max(0.0) as i32;
961        let width: i32 = (rect.width * self.get_dpr()).round().max(0.0) as i32;
962        let height: i32 = (rect.height * self.get_dpr()).round().max(0.0) as i32;
963        let bottom: i32 = drawing_height as i32 - (y + height);
964        self.get_context()
965            .enable(WebGl2RenderingContext::SCISSOR_TEST);
966        self.get_context().scissor(x, bottom.max(0), width, height);
967    }
968
969    /// Binds the glyph atlas to the primary texture unit.
970    fn bind_atlas(&mut self) {
971        if *self.try_get_primary_texture() == Some((WebGl2BatchKind::Atlas, 0)) {
972            return;
973        }
974        let Some(texture) = self.try_get_atlas_texture().clone() else {
975            return;
976        };
977        self.get_context()
978            .active_texture(WebGl2RenderingContext::TEXTURE0 + GL_TEXTURE_UNIT_PRIMARY);
979        self.get_context()
980            .bind_texture(WebGl2RenderingContext::TEXTURE_2D, Some(&texture));
981        self.set_primary_texture(Some((WebGl2BatchKind::Atlas, 0)));
982    }
983
984    /// Binds an image to the primary texture unit.
985    ///
986    /// # Arguments
987    ///
988    /// - `usize` - A non-negative integer (`usize`).
989    fn bind_image(&mut self, image: usize) {
990        if *self.try_get_primary_texture() == Some((WebGl2BatchKind::Image, image)) {
991            return;
992        }
993        let Some(texture) = self
994            .get_images()
995            .get(image)
996            .and_then(|entry: &WebGl2Image| entry.texture.clone())
997        else {
998            return;
999        };
1000        self.get_context()
1001            .active_texture(WebGl2RenderingContext::TEXTURE0 + GL_TEXTURE_UNIT_PRIMARY);
1002        self.get_context()
1003            .bind_texture(WebGl2RenderingContext::TEXTURE_2D, Some(&texture));
1004        self.set_primary_texture(Some((WebGl2BatchKind::Image, image)));
1005    }
1006
1007    /// Binds the gradient ramps to their texture unit.
1008    fn bind_ramp(&mut self) {
1009        let Some(texture) = self.try_get_ramp_texture().clone() else {
1010            return;
1011        };
1012        self.get_context()
1013            .active_texture(WebGl2RenderingContext::TEXTURE0 + GL_TEXTURE_UNIT_RAMP);
1014        self.get_context()
1015            .bind_texture(WebGl2RenderingContext::TEXTURE_2D, Some(&texture));
1016    }
1017
1018    /// Binds the gradient parameters to their texture unit.
1019    fn bind_parameters(&mut self) {
1020        let Some(texture) = self.try_get_parameter_texture().clone() else {
1021            return;
1022        };
1023        self.get_context()
1024            .active_texture(WebGl2RenderingContext::TEXTURE0 + GL_TEXTURE_UNIT_PARAMETERS);
1025        self.get_context()
1026            .bind_texture(WebGl2RenderingContext::TEXTURE_2D, Some(&texture));
1027    }
1028}
1029
1030/// Implements [`ChartBackend`] for [`WebGl2ChartBackend`].
1031impl ChartBackend for WebGl2ChartBackend {
1032    /// Implements the `kind` operation on [`WebGl2ChartBackend`].
1033    ///
1034    /// # Returns
1035    ///
1036    /// - `BackendKind` - A `BackendKind` value.
1037    fn kind(&self) -> BackendKind {
1038        BackendKind::WebGl2
1039    }
1040
1041    /// Implements the `resize` operation on [`WebGl2ChartBackend`].
1042    ///
1043    /// # Arguments
1044    ///
1045    /// - `f64` - A 64-bit float (`f64`).
1046    fn resize(&mut self, width: f64, height: f64, dpr: f64) {
1047        self.set_dpr(dpr);
1048        self.set_width(width);
1049        self.set_height(height);
1050        let physical_width: u32 = (width * dpr).round().max(1.0) as u32;
1051        let physical_height: u32 = (height * dpr).round().max(1.0) as u32;
1052        if self.get_canvas().width() != physical_width {
1053            self.get_canvas().set_width(physical_width);
1054        }
1055        if self.get_canvas().height() != physical_height {
1056            self.get_canvas().set_height(physical_height);
1057        }
1058        // The geometry is stored in CSS pixels, so a resize only moves
1059        // the viewport: nothing has to be re-uploaded.
1060        self.get_context()
1061            .viewport(0, 0, physical_width as i32, physical_height as i32);
1062        // The scissor rectangle is expressed in framebuffer pixels, and
1063        // the framebuffer just moved, so it has to be re-applied.
1064        self.set_scissor_dirty(true);
1065    }
1066
1067    /// Implements the `render` operation on [`WebGl2ChartBackend`].
1068    ///
1069    /// # Arguments
1070    ///
1071    /// - `&DisplayList` - Shared reference to a `DisplayList`.
1072    /// - `&mut TextShaper` - Mutable reference to a `TextShaper` (mutated in place).
1073    fn render(&mut self, list: &DisplayList, shaper: &mut TextShaper) {
1074        if self.get_context().is_context_lost() {
1075            return;
1076        }
1077        if !self.ensure_resources() {
1078            return;
1079        }
1080        // The display list is retained between frames, so the geometry
1081        // is only rebuilt when the list actually changed. An unchanged
1082        // chart costs one fingerprint, one draw call, and nothing else.
1083        let fingerprint: u64 = fingerprint(list);
1084        if !self.get_built() || self.get_fingerprint() != fingerprint {
1085            self.rebuild(list, shaper);
1086            self.set_fingerprint(fingerprint);
1087            self.set_built(true);
1088            self.upload_geometry();
1089            self.upload_gradients();
1090            self.upload_images();
1091        }
1092        // The atlas grows whenever a new glyph is rasterised into it,
1093        // which happens while the text is shaped above.
1094        if self.try_get_atlas_texture().is_none() || shaper.atlas().is_dirty() {
1095            self.upload_atlas(shaper.atlas());
1096            shaper.atlas_mut().mark_clean();
1097        }
1098        self.draw();
1099    }
1100
1101    /// Implements the `dispose` operation on [`WebGl2ChartBackend`].
1102    fn dispose(&mut self) {
1103        if let Some(program) = self.get_mut_program().take() {
1104            self.get_context().delete_program(Some(&program.program));
1105        }
1106        if let Some(vertex_array) = self.get_mut_vertex_array().take() {
1107            self.get_context().delete_vertex_array(Some(&vertex_array));
1108        }
1109        if let Some(buffer) = self.get_mut_vertex_buffer().take() {
1110            self.get_context().delete_buffer(Some(&buffer));
1111        }
1112        if let Some(buffer) = self.get_mut_index_buffer().take() {
1113            self.get_context().delete_buffer(Some(&buffer));
1114        }
1115        if let Some(texture) = self.get_mut_atlas_texture().take() {
1116            self.get_context().delete_texture(Some(&texture));
1117        }
1118        if let Some(texture) = self.get_mut_ramp_texture().take() {
1119            self.get_context().delete_texture(Some(&texture));
1120        }
1121        if let Some(texture) = self.get_mut_parameter_texture().take() {
1122            self.get_context().delete_texture(Some(&texture));
1123        }
1124        for index in 0..self.get_images().len() {
1125            let texture: Option<WebGlTexture> =
1126                self.get_mut_images()[index].get_mut_texture().take();
1127            if let Some(texture) = texture {
1128                self.get_context().delete_texture(Some(&texture));
1129            }
1130        }
1131        self.get_mut_images().clear();
1132        self.get_mut_vertices().clear();
1133        self.get_mut_indices().clear();
1134        self.get_mut_batches().clear();
1135        self.get_mut_gradients().clear();
1136        self.set_built(false);
1137        self.set_fingerprint(0);
1138        self.set_primary_texture(None);
1139        self.set_scissor(None);
1140        self.set_scissor_dirty(true);
1141        // The canvas is left empty rather than holding the last frame.
1142        self.get_context().viewport(
1143            0,
1144            0,
1145            self.get_canvas().width() as i32,
1146            self.get_canvas().height() as i32,
1147        );
1148        self.get_context().clear_color(0.0, 0.0, 0.0, 0.0);
1149        self.get_context()
1150            .clear(WebGl2RenderingContext::COLOR_BUFFER_BIT);
1151    }
1152}
1153
1154/// Implements [`Drop`] for [`WebGl2ChartBackend`].
1155impl Drop for WebGl2ChartBackend {
1156    /// Releases the GPU resources when the backend is dropped.
1157    ///
1158    /// A chart that is dropped without being disposed would hold on to
1159    /// its buffers until the browser collects them, and a chart over a
1160    /// million points holds on to a lot of memory.
1161    fn drop(&mut self) {
1162        <Self as ChartBackend>::dispose(self);
1163    }
1164}
1165
1166/// Inherent implementation of [`WebGl2Program`].
1167impl WebGl2Program {
1168    /// Compiles and links the chart's shader program.
1169    ///
1170    /// Returns `None` when either stage fails to compile or the program
1171    /// fails to link, which lets the factory fall back to the Canvas2D
1172    /// backend instead of drawing nothing.
1173    ///
1174    /// # Arguments
1175    ///
1176    /// - `&WebGl2RenderingContext` - Shared reference to a `WebGl2RenderingContext`.
1177    ///
1178    /// # Returns
1179    ///
1180    /// - `Option<Self>` - `Some(...)` on success, `None` otherwise.
1181    fn create(context: &WebGl2RenderingContext) -> Option<Self> {
1182        let vertex: WebGlShader = compile_shader(
1183            context,
1184            WebGl2RenderingContext::VERTEX_SHADER,
1185            GL_VERTEX_SHADER,
1186        )?;
1187        let fragment: WebGlShader = compile_shader(
1188            context,
1189            WebGl2RenderingContext::FRAGMENT_SHADER,
1190            GL_FRAGMENT_SHADER,
1191        )?;
1192        let program: WebGlProgram = context.create_program()?;
1193        context.attach_shader(&program, &vertex);
1194        context.attach_shader(&program, &fragment);
1195        context.link_program(&program);
1196        context.delete_shader(Some(&vertex));
1197        context.delete_shader(Some(&fragment));
1198        let linked: bool = context
1199            .get_program_parameter(&program, WebGl2RenderingContext::LINK_STATUS)
1200            .as_bool()
1201            .unwrap_or(false);
1202        if !linked {
1203            let log: String = context
1204                .get_program_info_log(&program)
1205                .unwrap_or_else(|| "the program did not link".to_string());
1206            web_sys::console::error_1(&format!("euv-chart: {log}").into());
1207            context.delete_program(Some(&program));
1208            return None;
1209        }
1210        Some(Self {
1211            resolution: context.get_uniform_location(&program, "u_resolution"),
1212            atlas: context.get_uniform_location(&program, "u_atlas"),
1213            ramp: context.get_uniform_location(&program, "u_ramp"),
1214            parameters: context.get_uniform_location(&program, "u_parameters"),
1215            gradient_count: context.get_uniform_location(&program, "u_gradient_count"),
1216            program,
1217        })
1218    }
1219
1220    /// Points the samplers at the texture units the backend uses.
1221    ///
1222    /// # Arguments
1223    ///
1224    /// - `&WebGl2RenderingContext` - Shared reference to a `WebGl2RenderingContext`.
1225    fn set_samplers(&self, context: &WebGl2RenderingContext) {
1226        if let Some(location) = self.try_get_atlas() {
1227            context.uniform1i(Some(location), GL_TEXTURE_UNIT_PRIMARY as i32);
1228        }
1229        if let Some(location) = self.try_get_ramp() {
1230            context.uniform1i(Some(location), GL_TEXTURE_UNIT_RAMP as i32);
1231        }
1232        if let Some(location) = self.try_get_parameters() {
1233            context.uniform1i(Some(location), GL_TEXTURE_UNIT_PARAMETERS as i32);
1234        }
1235    }
1236
1237    /// Sets the size of the canvas, in CSS pixels.
1238    ///
1239    /// # Arguments
1240    ///
1241    /// - `&WebGl2RenderingContext` - Shared reference to a `WebGl2RenderingContext`.
1242    /// - `f32` - A 32-bit float (`f32`).
1243    fn set_resolution_uniform(&self, context: &WebGl2RenderingContext, width: f32, height: f32) {
1244        if let Some(location) = self.try_get_resolution() {
1245            context.uniform2f(Some(location), width, height);
1246        }
1247    }
1248
1249    /// Sets the number of gradient rows the ramp texture holds.
1250    ///
1251    /// # Arguments
1252    ///
1253    /// - `&WebGl2RenderingContext` - Shared reference to a `WebGl2RenderingContext`.
1254    /// - `f32` - A 32-bit float (`f32`).
1255    fn set_gradient_count_uniform(&self, context: &WebGl2RenderingContext, count: f32) {
1256        if let Some(location) = self.try_get_gradient_count() {
1257            context.uniform1f(Some(location), count);
1258        }
1259    }
1260}
1261
1262/// Inherent implementation of [`WebGl2VertexKind`].
1263impl WebGl2VertexKind {
1264    /// Returns the code the shader tells the kinds apart by.
1265    ///
1266    /// The codes are part of the shader contract: the fragment stage
1267    /// compares them against the constants it was written with, so they
1268    /// must not be renumbered without changing the shader.
1269    ///
1270    /// # Returns
1271    ///
1272    /// - `f32` - A 32-bit float.
1273    pub fn code(self) -> f32 {
1274        match self {
1275            WebGl2VertexKind::Solid => 0.0,
1276            WebGl2VertexKind::Text => 1.0,
1277            WebGl2VertexKind::Gradient => 2.0,
1278            WebGl2VertexKind::Image => 3.0,
1279        }
1280    }
1281}
1282
1283/// Inherent implementation of [`DisplayHasher`].
1284impl DisplayHasher {
1285    /// The offset basis of the FNV-1a hash.
1286    const OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
1287
1288    /// The prime the FNV-1a hash multiplies by.
1289    const PRIME: u64 = 0x0000_0100_0000_01b3;
1290
1291    /// Returns a hasher with the empty fingerprint.
1292    pub(crate) fn new() -> Self {
1293        Self { hash: Self::OFFSET }
1294    }
1295
1296    /// Mixes one word into the hash.
1297    ///
1298    /// # Arguments
1299    ///
1300    /// - `u64` - A `u64` parameter.
1301    pub(crate) fn write_u64(&mut self, value: u64) {
1302        self.set_hash(self.get_hash() ^ value);
1303        self.set_hash(self.get_hash().wrapping_mul(Self::PRIME));
1304    }
1305
1306    /// Mixes one 32-bit word into the hash.
1307    ///
1308    /// # Arguments
1309    ///
1310    /// - `u32` - A 32-bit unsigned integer (`u32`).
1311    pub(crate) fn write_u32(&mut self, value: u32) {
1312        self.write_u64(value as u64);
1313    }
1314
1315    /// Mixes one floating-point value into the hash.
1316    ///
1317    /// The bits of the value are mixed rather than the value itself, so
1318    /// that two shapes that differ in the last decimal place are told
1319    /// apart.
1320    ///
1321    /// # Arguments
1322    ///
1323    /// - `f32` - A 32-bit float (`f32`).
1324    pub(crate) fn write_f32(&mut self, value: f32) {
1325        self.write_u32(value.to_bits());
1326    }
1327
1328    /// Mixes one 64-bit floating-point value into the hash.
1329    ///
1330    /// # Arguments
1331    ///
1332    /// - `f64` - A 64-bit float (`f64`).
1333    pub(crate) fn write_f64(&mut self, value: f64) {
1334        self.write_u64(value.to_bits());
1335    }
1336
1337    /// Mixes a string into the hash.
1338    ///
1339    /// # Arguments
1340    ///
1341    /// - `&str` - Shared reference to a `str`.
1342    pub(crate) fn write_str(&mut self, value: &str) {
1343        self.write_u64(value.len() as u64);
1344        for chunk in value.as_bytes().chunks(8) {
1345            let mut word: [u8; 8] = [0_u8; 8];
1346            word[..chunk.len()].copy_from_slice(chunk);
1347            self.write_u64(u64::from_le_bytes(word));
1348        }
1349    }
1350
1351    /// Returns the fingerprint of everything written so far.
1352    ///
1353    /// # Returns
1354    ///
1355    /// - `u64` - A `u64` value.
1356    pub(crate) fn finish(&self) -> u64 {
1357        self.get_hash()
1358    }
1359}