euv_chart/backend/webgpu/struct.rs
1use super::*;
2
3/// The WebGPU rendering backend for the chart.
4///
5/// This is the backend the chart prefers: vector geometry is tessellated
6/// into triangles once, uploaded to the GPU once, and replayed with a
7/// handful of draw calls. The display list is retained between frames, so a
8/// chart that has not changed does not re-tessellate and does not re-upload
9/// anything — the backend keeps the GPU buffers alive and reuses them as
10/// long as the list is unchanged.
11///
12/// The backend is created asynchronously, because acquiring a GPU adapter
13/// and a GPU device returns JavaScript promises. When the host cannot
14/// provide WebGPU, [`WebGpuChartBackend::create`] returns `None` and the
15/// factory falls back to WebGL2 and then to Canvas2D.
16#[derive(Data)]
17pub struct WebGpuChartBackend {
18 /// The canvas the chart draws into.
19 #[get(pub(crate))]
20 #[get_mut(pub(crate))]
21 #[set(pub(crate))]
22 pub(crate) canvas: HtmlCanvasElement,
23 /// The GPU device the resources are created on.
24 #[get(pub(crate))]
25 #[get_mut(pub(crate))]
26 #[set(pub(crate))]
27 pub(crate) device: JsValue,
28 /// The GPU queue the command buffers are submitted to.
29 #[get(pub(crate))]
30 #[get_mut(pub(crate))]
31 #[set(pub(crate))]
32 pub(crate) queue: JsValue,
33 /// The canvas's WebGPU context.
34 #[get(pub(crate))]
35 #[get_mut(pub(crate))]
36 #[set(pub(crate))]
37 pub(crate) context: JsValue,
38 /// The swap-chain texture format, as reported by
39 /// `getPreferredCanvasFormat`.
40 #[get(pub(crate))]
41 #[get_mut(pub(crate))]
42 #[set(pub(crate))]
43 pub(crate) format: String,
44 /// The JavaScript methods the backend calls every frame.
45 #[get(pub(crate))]
46 #[get_mut(pub(crate))]
47 #[set(pub(crate))]
48 pub(crate) api: WebGpuApi,
49 /// The methods of the command encoder, fetched on the first frame.
50 #[get(pub(crate))]
51 #[get_mut(pub(crate))]
52 #[set(pub(crate))]
53 pub(crate) encoder_api: Option<WebGpuEncoderApi>,
54 /// The methods of the render pass, fetched on the first frame.
55 #[get(pub(crate))]
56 #[get_mut(pub(crate))]
57 #[set(pub(crate))]
58 pub(crate) pass_api: Option<WebGpuPassApi>,
59 /// The pipeline that draws flat-coloured triangles.
60 #[get(pub(crate))]
61 #[get_mut(pub(crate))]
62 #[set(pub(crate))]
63 pub(crate) solid_pipeline: JsValue,
64 /// The pipeline that draws textured triangles.
65 #[get(pub(crate))]
66 #[get_mut(pub(crate))]
67 #[set(pub(crate))]
68 pub(crate) textured_pipeline: JsValue,
69 /// The bind group layout of the texture and the sampler.
70 #[get(pub(crate))]
71 #[get_mut(pub(crate))]
72 #[set(pub(crate))]
73 pub(crate) texture_layout: JsValue,
74 /// The sampler bound to the texture bind group.
75 #[get(pub(crate))]
76 #[get_mut(pub(crate))]
77 #[set(pub(crate))]
78 pub(crate) sampler: JsValue,
79 /// The buffer holding the CSS-pixel to clip-space transform.
80 #[get(pub(crate))]
81 #[get_mut(pub(crate))]
82 #[set(pub(crate))]
83 pub(crate) uniform_buffer: JsValue,
84 /// The bind group that exposes the transform to the pipelines.
85 #[get(pub(crate))]
86 #[get_mut(pub(crate))]
87 #[set(pub(crate))]
88 pub(crate) uniform_bind_group: JsValue,
89 /// The render pass descriptor, reused across frames.
90 #[get(pub(crate))]
91 #[get_mut(pub(crate))]
92 #[set(pub(crate))]
93 pub(crate) pass_descriptor: JsValue,
94 /// The colour attachment inside the render pass descriptor.
95 #[get(pub(crate))]
96 #[get_mut(pub(crate))]
97 #[set(pub(crate))]
98 pub(crate) pass_attachment: JsValue,
99 /// The glyph atlas texture and its bind group.
100 #[get(pub(crate))]
101 #[get_mut(pub(crate))]
102 #[set(pub(crate))]
103 pub(crate) atlas: WebGpuAtlas,
104 /// The buffer holding the flat-coloured vertices.
105 #[get(pub(crate))]
106 #[get_mut(pub(crate))]
107 #[set(pub(crate))]
108 pub(crate) solid_buffer: WebGpuBuffer,
109 /// The buffer holding the indices of the flat-coloured vertices.
110 #[get(pub(crate))]
111 #[get_mut(pub(crate))]
112 #[set(pub(crate))]
113 pub(crate) solid_index_buffer: WebGpuBuffer,
114 /// The buffer holding the textured vertices.
115 #[get(pub(crate))]
116 #[get_mut(pub(crate))]
117 #[set(pub(crate))]
118 pub(crate) textured_buffer: WebGpuBuffer,
119 /// The buffer holding the indices of the textured vertices.
120 #[get(pub(crate))]
121 #[get_mut(pub(crate))]
122 #[set(pub(crate))]
123 pub(crate) textured_index_buffer: WebGpuBuffer,
124 /// The batches to draw, in paint order.
125 #[get(pub(crate))]
126 #[get_mut(pub(crate))]
127 #[set(pub(crate))]
128 pub(crate) batches: Vec<WebGpuBatch>,
129 /// The images the display list refers to.
130 #[get(pub(crate))]
131 #[get_mut(pub(crate))]
132 #[set(pub(crate))]
133 pub(crate) images: Vec<WebGpuImage>,
134 /// The identifier handed to the next image that shows up.
135 #[get(pub(crate), type(copy))]
136 #[get_mut(pub(crate))]
137 #[set(pub(crate))]
138 pub(crate) next_image_id: u32,
139 /// The number of glyphs the atlas held at the last render.
140 ///
141 /// The atlas starts over when it runs out of room, which moves the
142 /// glyphs around. A shrinking count is the signal that the text
143 /// geometry has to be built again.
144 #[get(pub(crate), type(copy))]
145 #[get_mut(pub(crate))]
146 #[set(pub(crate))]
147 pub(crate) atlas_glyphs: usize,
148 /// The fingerprint of the display list the buffers currently hold.
149 #[get(pub(crate))]
150 #[get_mut(pub(crate))]
151 #[set(pub(crate))]
152 pub(crate) fingerprint: Option<u64>,
153 /// The tessellator, reused across frames so its scratch buffers are
154 /// allocated once.
155 #[get(pub(crate))]
156 #[get_mut(pub(crate))]
157 #[set(pub(crate))]
158 pub(crate) tessellator: Tessellator,
159 /// The logical width of the canvas, in CSS pixels.
160 #[get(pub(crate), type(copy))]
161 #[get_mut(pub(crate))]
162 #[set(pub(crate))]
163 pub(crate) width: f64,
164 /// The logical height of the canvas, in CSS pixels.
165 #[get(pub(crate), type(copy))]
166 #[get_mut(pub(crate))]
167 #[set(pub(crate))]
168 pub(crate) height: f64,
169 /// The device pixel ratio the canvas renders at.
170 #[get(pub(crate), type(copy))]
171 #[get_mut(pub(crate))]
172 #[set(pub(crate))]
173 pub(crate) dpr: f64,
174 /// The scratch storage for the flat-coloured vertices.
175 #[get(pub(crate))]
176 #[get_mut(pub(crate))]
177 #[set(pub(crate))]
178 pub(crate) solid_vertices: Vec<u8>,
179 /// The scratch storage for the indices of the flat-coloured vertices.
180 #[get(pub(crate))]
181 #[get_mut(pub(crate))]
182 #[set(pub(crate))]
183 pub(crate) solid_indices: Vec<u32>,
184 /// The scratch storage for the textured vertices.
185 #[get(pub(crate))]
186 #[get_mut(pub(crate))]
187 #[set(pub(crate))]
188 pub(crate) textured_vertices: Vec<u8>,
189 /// The scratch storage for the indices of the textured vertices.
190 #[get(pub(crate))]
191 #[get_mut(pub(crate))]
192 #[set(pub(crate))]
193 pub(crate) textured_indices: Vec<u32>,
194 /// The clip rectangles currently in effect.
195 #[get(pub(crate))]
196 #[get_mut(pub(crate))]
197 #[set(pub(crate))]
198 pub(crate) clips: Vec<WebGpuScissor>,
199}
200
201/// A GPU buffer whose capacity the backend manages.
202///
203/// The buffer is only re-created when the data outgrows it, and the new
204/// capacity is rounded up to a power of two so that a chart whose data
205/// grows a little at a time does not re-create the buffer on every change.
206#[derive(Data)]
207pub(crate) struct WebGpuBuffer {
208 /// The GPU buffer, or `None` before the first allocation.
209 #[get(pub(crate))]
210 #[get_mut(pub(crate))]
211 #[set(pub(crate))]
212 pub(crate) buffer: Option<JsValue>,
213 /// The capacity of the buffer, in bytes.
214 #[get(pub(crate), type(copy))]
215 #[get_mut(pub(crate))]
216 #[set(pub(crate))]
217 pub(crate) capacity: u32,
218}
219
220/// The GPU-side state of the glyph atlas.
221///
222/// The atlas is a canvas the text shaper rasterises glyphs into. The
223/// backend uploads it as a texture and binds it to the textured pipeline,
224/// which is how the GPU backends draw text at all — WebGPU has no text
225/// API.
226#[derive(Clone, Debug)]
227pub(crate) struct WebGpuAtlas {
228 /// The atlas texture, or `None` before the first upload.
229 pub(crate) texture: Option<JsValue>,
230 /// The bind group that binds the atlas to group one.
231 pub(crate) bind_group: Option<JsValue>,
232 /// The width of the atlas texture, in pixels.
233 pub(crate) width: u32,
234 /// The height of the atlas texture, in pixels.
235 pub(crate) height: u32,
236}
237
238/// The GPU-side state of one image.
239///
240/// Images are uploaded with `copyExternalImageToTexture` and drawn with the
241/// textured pipeline, using a bind group of their own.
242pub(crate) struct WebGpuImage {
243 /// The image element the texture was made from.
244 pub(crate) element: JsValue,
245 /// A stable identifier, used to notice that the display list changed.
246 pub(crate) id: u32,
247 /// The texture holding the image, or `None` before the first upload.
248 pub(crate) texture: Option<JsValue>,
249 /// The bind group that binds the texture to group one.
250 pub(crate) bind_group: Option<JsValue>,
251 /// The width of the texture, in pixels.
252 pub(crate) width: u32,
253 /// The height of the texture, in pixels.
254 pub(crate) height: u32,
255 /// The source the texture was made from, used to notice a reload.
256 pub(crate) source: String,
257}
258
259/// One contiguous run of triangles that costs a single draw call.
260///
261/// The batches are kept in paint order, and consecutive batches usually
262/// share a pipeline, so the render pass only has to rebind when the kind
263/// changes.
264pub(crate) struct WebGpuBatch {
265 /// The pipeline the batch is drawn with.
266 pub(crate) kind: WebGpuBatchKind,
267 /// The index of the batch's first triangle.
268 pub(crate) first_index: u32,
269 /// The number of indices in the batch.
270 pub(crate) index_count: u32,
271 /// The bind group bound to group one, for textured batches.
272 pub(crate) bind_group: Option<JsValue>,
273 /// The scissor rectangle the batch is clipped to, if any.
274 pub(crate) scissor: Option<WebGpuScissor>,
275}
276
277/// A scissor rectangle, in physical pixels.
278#[derive(Clone, Copy, Debug, Eq, PartialEq)]
279pub(crate) struct WebGpuScissor {
280 /// The left edge of the rectangle.
281 pub(crate) x: u32,
282 /// The top edge of the rectangle.
283 pub(crate) y: u32,
284 /// The width of the rectangle.
285 pub(crate) width: u32,
286 /// The height of the rectangle.
287 pub(crate) height: u32,
288}
289
290/// The JavaScript methods the backend calls on the device, the queue, and
291/// the canvas context.
292///
293/// The methods are fetched once, when the backend is created: JavaScript
294/// class methods live on the prototype, so every call returns the same
295/// `Function` object, and caching them keeps a steady-state frame free of
296/// property lookups.
297#[derive(Clone)]
298pub(crate) struct WebGpuApi {
299 /// `GPUDevice.createCommandEncoder`.
300 pub(crate) create_command_encoder: Function,
301 /// `GPUQueue.submit`.
302 pub(crate) submit: Function,
303 /// `GPUQueue.writeBuffer`.
304 pub(crate) write_buffer: Function,
305 /// `GPUQueue.copyExternalImageToTexture`.
306 pub(crate) copy_external_image: Function,
307 /// `GPUCanvasContext.getCurrentTexture`.
308 pub(crate) get_current_texture: Function,
309}
310
311/// The methods of the command encoder.
312///
313/// The encoder is created per frame, but its methods live on the
314/// prototype, so they are fetched from the first encoder and then reused.
315#[derive(Clone)]
316pub(crate) struct WebGpuEncoderApi {
317 /// `GPUCommandEncoder.beginRenderPass`.
318 pub(crate) begin_render_pass: Function,
319 /// `GPUCommandEncoder.finish`.
320 pub(crate) finish_encoder: Function,
321}
322
323/// The methods of the render pass.
324///
325/// The pass is created per frame, but its methods live on the prototype,
326/// so they are fetched from the first pass and then reused.
327#[derive(Clone)]
328pub(crate) struct WebGpuPassApi {
329 /// `GPURenderPassEncoder.setPipeline`.
330 pub(crate) set_pipeline: Function,
331 /// `GPURenderPassEncoder.setVertexBuffer`.
332 pub(crate) set_vertex_buffer: Function,
333 /// `GPURenderPassEncoder.setIndexBuffer`.
334 pub(crate) set_index_buffer: Function,
335 /// `GPURenderPassEncoder.setBindGroup`.
336 pub(crate) set_bind_group: Function,
337 /// `GPURenderPassEncoder.setScissorRect`.
338 pub(crate) set_scissor_rect: Function,
339 /// `GPURenderPassEncoder.drawIndexed`.
340 pub(crate) draw_indexed: Function,
341 /// `GPURenderPassEncoder.end`.
342 pub(crate) end_pass: Function,
343}
344
345/// A small FNV-1a hasher, used to fingerprint the display list.
346///
347/// The fingerprint tells the backend whether the geometry it holds on the
348/// GPU is still the geometry the display list asks for. Hashing the actual
349/// coordinates — rather than just counting the shapes — means a chart that
350/// moves its data but keeps the same number of points is still noticed.
351#[derive(Data)]
352pub(crate) struct GpuHasher {
353 /// The running hash state.
354 #[get(pub(crate), type(copy))]
355 #[get_mut(pub(crate))]
356 #[set(pub(crate))]
357 pub(crate) state: u64,
358}