Skip to main content

euv_chart/backend/webgl/
struct.rs

1use super::*;
2
3/// The WebGL2 rendering backend for the chart.
4///
5/// This is the GPU fallback for browsers that do not expose WebGPU. It
6/// replays the same display list as the WebGPU backend and the Canvas2D
7/// backend, so the picture it produces is the same; only the plumbing
8/// differs.
9///
10/// # Performance
11///
12/// The display list is retained between frames, and the backend takes
13/// advantage of that in two ways.
14///
15/// The first is caching: the backend fingerprints the display list and
16/// only re-tessellates and re-uploads when the fingerprint changes. An
17/// unchanged chart therefore costs no vertex work at all — the GPU
18/// buffers are simply drawn again.
19///
20/// The second is batching: every shape in the display list ends up in
21/// one vertex buffer, in paint order, and is drawn with a handful of
22/// draw calls. A steady-state frame of an ordinary chart issues two or
23/// three draw calls, and none of them crosses the JavaScript boundary
24/// more than a handful of times, because the vertex data is uploaded in
25/// one go rather than one vertex at a time.
26#[derive(Data)]
27pub struct WebGl2ChartBackend {
28    /// The canvas the chart draws into.
29    #[get(pub(crate))]
30    #[get_mut(pub(crate))]
31    #[set(pub(crate))]
32    pub(crate) canvas: HtmlCanvasElement,
33    /// The WebGL2 rendering context every call goes through.
34    #[get(pub(crate))]
35    #[get_mut(pub(crate))]
36    #[set(pub(crate))]
37    pub(crate) context: WebGl2RenderingContext,
38    /// The linked shader program, or `None` before it is built.
39    #[get(pub(crate))]
40    #[get_mut(pub(crate))]
41    #[set(pub(crate))]
42    pub(crate) program: Option<WebGl2Program>,
43    /// The vertex array object that records the vertex layout.
44    #[get(pub(crate))]
45    #[get_mut(pub(crate))]
46    #[set(pub(crate))]
47    pub(crate) vertex_array: Option<WebGlVertexArrayObject>,
48    /// The buffer holding the vertices of the whole frame.
49    #[get(pub(crate))]
50    #[get_mut(pub(crate))]
51    #[set(pub(crate))]
52    pub(crate) vertex_buffer: Option<WebGlBuffer>,
53    /// The buffer holding the indices of the whole frame.
54    #[get(pub(crate))]
55    #[get_mut(pub(crate))]
56    #[set(pub(crate))]
57    pub(crate) index_buffer: Option<WebGlBuffer>,
58    /// The texture the glyph atlas is uploaded into.
59    #[get(pub(crate))]
60    #[get_mut(pub(crate))]
61    #[set(pub(crate))]
62    pub(crate) atlas_texture: Option<WebGlTexture>,
63    /// The texture holding the colour ramp of every gradient.
64    #[get(pub(crate))]
65    #[get_mut(pub(crate))]
66    #[set(pub(crate))]
67    pub(crate) ramp_texture: Option<WebGlTexture>,
68    /// The texture holding the geometry of every gradient.
69    #[get(pub(crate))]
70    #[get_mut(pub(crate))]
71    #[set(pub(crate))]
72    pub(crate) parameter_texture: Option<WebGlTexture>,
73    /// The images the display list refers to, in the order they appear.
74    #[get(pub(crate))]
75    #[get_mut(pub(crate))]
76    #[set(pub(crate))]
77    pub(crate) images: Vec<WebGl2Image>,
78    /// The tessellator, reused across frames so its scratch buffers are
79    /// allocated once.
80    #[get(pub(crate))]
81    #[get_mut(pub(crate))]
82    #[set(pub(crate))]
83    pub(crate) tessellator: Tessellator,
84    /// The vertices of the whole frame, interleaved.
85    #[get(pub(crate))]
86    #[get_mut(pub(crate))]
87    #[set(pub(crate))]
88    pub(crate) vertices: Vec<f32>,
89    /// The indices of the whole frame.
90    #[get(pub(crate))]
91    #[get_mut(pub(crate))]
92    #[set(pub(crate))]
93    pub(crate) indices: Vec<u32>,
94    /// The batches to draw, in paint order.
95    #[get(pub(crate))]
96    #[get_mut(pub(crate))]
97    #[set(pub(crate))]
98    pub(crate) batches: Vec<WebGl2Batch>,
99    /// The gradients the frame uses, one row each in the ramp texture.
100    #[get(pub(crate))]
101    #[get_mut(pub(crate))]
102    #[set(pub(crate))]
103    pub(crate) gradients: Vec<WebGl2Gradient>,
104    /// The fingerprint of the display list the buffers currently hold.
105    ///
106    /// The fingerprint is a hash over everything in the list that can
107    /// change what is drawn — the commands, the paths, the paints, the
108    /// text, and the positions. A chart whose data changed but whose
109    /// shape count stayed the same therefore still invalidates the
110    /// cache.
111    #[get(pub(crate), type(copy))]
112    #[get_mut(pub(crate))]
113    #[set(pub(crate))]
114    pub(crate) fingerprint: u64,
115    /// Whether the GPU buffers currently hold the display list.
116    #[get(pub(crate), type(copy))]
117    #[get_mut(pub(crate))]
118    #[set(pub(crate))]
119    pub(crate) built: bool,
120    /// The logical width of the canvas, in CSS pixels.
121    #[get(pub(crate), type(copy))]
122    #[get_mut(pub(crate))]
123    #[set(pub(crate))]
124    pub(crate) width: f64,
125    /// The logical height of the canvas, in CSS pixels.
126    #[get(pub(crate), type(copy))]
127    #[get_mut(pub(crate))]
128    #[set(pub(crate))]
129    pub(crate) height: f64,
130    /// The device pixel ratio the canvas renders at.
131    #[get(pub(crate), type(copy))]
132    #[get_mut(pub(crate))]
133    #[set(pub(crate))]
134    pub(crate) dpr: f64,
135    /// The scissor rectangle currently in effect, in CSS pixels.
136    ///
137    /// WebGL2 has no path clipping, so a clip path is approximated by
138    /// the rectangle that bounds it. The rectangle is tracked so that a
139    /// frame only issues a scissor call when the clip actually changes.
140    #[get(pub(crate))]
141    #[get_mut(pub(crate))]
142    #[set(pub(crate))]
143    pub(crate) scissor: Option<WebGl2Scissor>,
144    /// Whether the scissor rectangle has to be re-applied.
145    ///
146    /// A resize moves the framebuffer the rectangle is expressed in, so
147    /// the rectangle is re-applied after one even when it did not
148    /// change.
149    #[get(pub(crate), type(copy))]
150    #[get_mut(pub(crate))]
151    #[set(pub(crate))]
152    pub(crate) scissor_dirty: bool,
153    /// Which texture is bound to the primary texture unit.
154    ///
155    /// The atlas and the images share one unit, so the backend has to
156    /// know what is bound to avoid rebinding it for every batch. The
157    /// second component names the image, for an image binding.
158    #[get(pub(crate))]
159    #[get_mut(pub(crate))]
160    #[set(pub(crate))]
161    pub(crate) primary_texture: Option<(WebGl2BatchKind, usize)>,
162}
163
164/// The linked shader program and the uniform locations it exposes.
165///
166/// The uniform locations are resolved once, when the program is built:
167/// resolving one costs a round trip into the driver that walks the
168/// program's uniform table, and the names never change afterwards.
169#[derive(Clone, Data)]
170pub(crate) struct WebGl2Program {
171    /// The linked program.
172    #[get(pub(crate))]
173    #[get_mut(pub(crate))]
174    #[set(pub(crate))]
175    pub(crate) program: WebGlProgram,
176    /// The location of canvas size, in CSS pixels.
177    #[get(pub(crate))]
178    #[get_mut(pub(crate))]
179    #[set(pub(crate))]
180    pub(crate) resolution: Option<WebGlUniformLocation>,
181    /// The location of the sampler that reads the atlas or the image.
182    #[get(pub(crate))]
183    #[get_mut(pub(crate))]
184    #[set(pub(crate))]
185    pub(crate) atlas: Option<WebGlUniformLocation>,
186    /// The location of the sampler that reads the gradient ramps.
187    #[get(pub(crate))]
188    #[get_mut(pub(crate))]
189    #[set(pub(crate))]
190    pub(crate) ramp: Option<WebGlUniformLocation>,
191    /// The location of the sampler that reads the gradient parameters.
192    #[get(pub(crate))]
193    #[get_mut(pub(crate))]
194    #[set(pub(crate))]
195    pub(crate) parameters: Option<WebGlUniformLocation>,
196    /// The location of the number of gradient rows.
197    #[get(pub(crate))]
198    #[get_mut(pub(crate))]
199    #[set(pub(crate))]
200    pub(crate) gradient_count: Option<WebGlUniformLocation>,
201}
202
203/// The shading a run of vertices shares.
204///
205/// The kind, the colour, and the gradient row are the same for every
206/// vertex of a shape, so they are carried together instead of being
207/// passed around one by one.
208#[derive(Clone, Copy, Data, Debug)]
209pub(crate) struct WebGl2Style {
210    /// The colour of the vertices, as normalised channels.
211    #[get(pub(crate))]
212    #[get_mut(pub(crate))]
213    #[set(pub(crate))]
214    pub(crate) color: [f32; 4],
215    /// The shading kind of the vertices.
216    #[get(pub(crate), type(copy))]
217    #[get_mut(pub(crate))]
218    #[set(pub(crate))]
219    pub(crate) kind: f32,
220    /// The gradient row the vertices belong to.
221    #[get(pub(crate), type(copy))]
222    #[get_mut(pub(crate))]
223    #[set(pub(crate))]
224    pub(crate) gradient: f32,
225}
226
227/// The rectangle of a texture a quad covers.
228///
229/// The rectangle is expressed in texture coordinates: the top-left
230/// corner and the size of the area the quad reads.
231#[derive(Clone, Copy, Data, Debug)]
232pub(crate) struct WebGl2TextureRect {
233    /// The horizontal coordinate of the top-left corner.
234    #[get(pub(crate), type(copy))]
235    #[get_mut(pub(crate))]
236    #[set(pub(crate))]
237    pub(crate) u: f32,
238    /// The vertical coordinate of the top-left corner.
239    #[get(pub(crate), type(copy))]
240    #[get_mut(pub(crate))]
241    #[set(pub(crate))]
242    pub(crate) v: f32,
243    /// The width of the rectangle.
244    #[get(pub(crate), type(copy))]
245    #[get_mut(pub(crate))]
246    #[set(pub(crate))]
247    pub(crate) u_size: f32,
248    /// The height of the rectangle.
249    #[get(pub(crate), type(copy))]
250    #[get_mut(pub(crate))]
251    #[set(pub(crate))]
252    pub(crate) v_size: f32,
253}
254
255/// One contiguous run of triangles that costs a single draw call.
256///
257/// The batches are kept in paint order. A batch ends when the texture it
258/// samples changes, or when the clip rectangle changes, so an ordinary
259/// chart holds one or two of them.
260#[derive(Clone, Copy, Data)]
261pub(crate) struct WebGl2Batch {
262    /// The texture the batch samples.
263    #[get(pub(crate))]
264    #[get_mut(pub(crate))]
265    #[set(pub(crate))]
266    pub(crate) kind: WebGl2BatchKind,
267    /// The index of the batch's first vertex.
268    #[get(pub(crate), type(copy))]
269    #[get_mut(pub(crate))]
270    #[set(pub(crate))]
271    pub(crate) first_index: u32,
272    /// The number of indices in the batch.
273    #[get(pub(crate), type(copy))]
274    #[get_mut(pub(crate))]
275    #[set(pub(crate))]
276    pub(crate) index_count: u32,
277    /// The image the batch samples, for an image batch.
278    #[get(pub(crate), type(copy))]
279    #[get_mut(pub(crate))]
280    #[set(pub(crate))]
281    pub(crate) image: usize,
282    /// The scissor rectangle the batch is clipped to, in CSS pixels.
283    #[get(pub(crate))]
284    #[get_mut(pub(crate))]
285    #[set(pub(crate))]
286    pub(crate) clip: Option<WebGl2Scissor>,
287}
288
289/// A scissor rectangle, in CSS pixels, with the origin at the top left.
290///
291/// The rectangle is stored in the chart's own coordinate system; the
292/// backend converts it into the framebuffer's bottom-left origin when
293/// it hands it to WebGL.
294#[derive(Clone, Copy, Data, Debug, PartialEq)]
295pub(crate) struct WebGl2Scissor {
296    /// The left edge of the rectangle.
297    #[get(pub(crate), type(copy))]
298    #[get_mut(pub(crate))]
299    #[set(pub(crate))]
300    pub(crate) x: f64,
301    /// The top edge of the rectangle.
302    #[get(pub(crate), type(copy))]
303    #[get_mut(pub(crate))]
304    #[set(pub(crate))]
305    pub(crate) y: f64,
306    /// The width of the rectangle.
307    #[get(pub(crate), type(copy))]
308    #[get_mut(pub(crate))]
309    #[set(pub(crate))]
310    pub(crate) width: f64,
311    /// The height of the rectangle.
312    #[get(pub(crate), type(copy))]
313    #[get_mut(pub(crate))]
314    #[set(pub(crate))]
315    pub(crate) height: f64,
316}
317
318/// The GPU-side state of one image.
319///
320/// The image is uploaded into a texture of its own and drawn with the
321/// same program the glyphs use, so an image costs exactly one draw call
322/// on top of the frame.
323#[derive(Data)]
324pub(crate) struct WebGl2Image {
325    /// The image element the texture was made from.
326    #[get(pub(crate))]
327    #[get_mut(pub(crate))]
328    #[set(pub(crate))]
329    pub(crate) element: HtmlImageElement,
330    /// The texture holding the image, or `None` before the first upload.
331    #[get(pub(crate))]
332    #[get_mut(pub(crate))]
333    #[set(pub(crate))]
334    pub(crate) texture: Option<WebGlTexture>,
335}
336
337/// A small hash used to fingerprint the display list.
338///
339/// The hash is an FNV-1a variant over machine words. It is not a
340/// cryptographic hash and does not need to be: it only has to notice
341/// that the display list changed, and it has to do so without allocating
342/// or crossing into JavaScript, because it runs on every frame.
343#[derive(Data)]
344pub(crate) struct DisplayHasher {
345    /// The hash accumulated so far.
346    #[get(pub(crate), type(copy))]
347    #[get_mut(pub(crate))]
348    #[set(pub(crate))]
349    pub(crate) hash: u64,
350}