euv_engine/renderer/webgl/struct.rs
1use super::*;
2
3/// A GPU buffer holding vertex, index, or uniform data.
4///
5/// Unlike the WebGPU backend, a WebGL buffer carries its own allocation
6/// size, so this wrapper remembers how many bytes were last written and
7/// can answer [`GlBuffer::fits`](super::GlBuffer::fits) without asking the
8/// driver. That is what lets
9/// [`GlBuffer::upload`](super::GlBuffer::upload) re-issue `bufferData` on
10/// a resize instead of creating a new `WebGlBuffer` object and leaving
11/// the old one to the garbage collector.
12#[derive(Clone, Data, Debug)]
13pub struct GlBuffer {
14 /// The GL buffer object.
15 #[get(pub(crate))]
16 #[set(pub(crate))]
17 #[get_mut(pub(crate))]
18 pub(crate) buffer: WebGlBuffer,
19 /// The byte capacity the driver last allocated for this buffer.
20 #[get(type(copy))]
21 #[set(pub(crate))]
22 #[get_mut(pub(crate))]
23 pub capacity: u32,
24 /// The GL usage hint passed at allocation: `STATIC_DRAW` for data
25 /// uploaded once, `DYNAMIC_DRAW` for data rewritten every frame.
26 #[get(type(copy))]
27 #[set(pub(crate))]
28 #[get_mut(pub(crate))]
29 pub usage: u32,
30}
31
32/// A GPU texture holding sampled image data or render-target contents.
33///
34/// Carries the dimensions and mip count the driver was told about so a
35/// resize re-allocates instead of stretching stale texels, and records
36/// whether a mip chain exists so a caller cannot accidentally select a
37/// minification filter that samples across levels the texture does not
38/// have, which samples as solid black and is the single most common "my
39/// texture is invisible" WebGL bug.
40#[derive(Clone, Data, Debug)]
41pub struct GlTexture {
42 /// The GL texture object.
43 #[set(pub(crate))]
44 #[get_mut(pub(crate))]
45 pub texture: WebGlTexture,
46 /// The width in texels the texture was allocated with.
47 #[get(type(copy))]
48 #[set(pub(crate))]
49 #[get_mut(pub(crate))]
50 pub width: u32,
51 /// The height in texels the texture was allocated with.
52 #[get(type(copy))]
53 #[set(pub(crate))]
54 #[get_mut(pub(crate))]
55 pub height: u32,
56 /// The number of mip levels allocated.
57 #[get(type(copy))]
58 #[set(pub(crate))]
59 #[get_mut(pub(crate))]
60 pub levels: u32,
61 /// Whether `generateMipmap` was called, so a minification filter that
62 /// samples across levels is known to be legal.
63 #[get(type(copy))]
64 #[set(pub(crate))]
65 #[get_mut(pub(crate))]
66 pub mipmapped: bool,
67}
68
69/// A vertex array object: the recorded bundle of buffer bindings and
70/// vertex attribute formats.
71///
72/// Binding a VAO replaces up to a dozen `bindBuffer` /
73/// `enableVertexAttribArray` / `vertexAttribPointer` calls with one,
74/// which is what makes per-mesh draw state cheap enough to change on
75/// every draw call.
76#[derive(Clone, Data, Debug)]
77pub struct GlVertexArray {
78 /// The GL vertex array object.
79 #[get(pub(crate))]
80 #[set(pub(crate))]
81 #[get_mut(pub(crate))]
82 pub(crate) vao: WebGlVertexArrayObject,
83}
84
85/// A framebuffer: a color attachment texture plus an optional depth (and
86/// stencil) renderbuffer.
87///
88/// The color attachment is always a [`GlTexture`] rather than a
89/// renderbuffer, so the result can be sampled by a later pass or read
90/// back without a resolve blit.
91#[derive(Clone, Data, Debug)]
92pub struct GlFramebuffer {
93 /// The GL framebuffer object.
94 #[get(pub(crate))]
95 #[set(pub(crate))]
96 #[get_mut(pub(crate))]
97 pub(crate) framebuffer: WebGlFramebuffer,
98 /// The depth renderbuffer, optionally packed with an 8-bit stencil,
99 /// or `None` for a color-only target such as a post-process pass
100 /// that reads depth from a texture instead of writing its own.
101 #[get(type(clone))]
102 #[set(pub(crate))]
103 #[get_mut(pub(crate))]
104 pub depth: Option<WebGlRenderbuffer>,
105 /// The color attachment, retained so a later pass can sample the
106 /// rendered result without a separate resolve.
107 #[get(type(clone))]
108 #[set(pub(crate))]
109 #[get_mut(pub(crate))]
110 pub color: GlTexture,
111 /// The width every attachment was allocated with, re-checked on
112 /// rebind so a window resize re-allocates rather than leaving a
113 /// frame whose attachments disagree on size.
114 #[get(type(copy))]
115 #[set(pub(crate))]
116 #[get_mut(pub(crate))]
117 pub width: u32,
118 /// The height every attachment was allocated with.
119 #[get(type(copy))]
120 #[set(pub(crate))]
121 #[get_mut(pub(crate))]
122 pub height: u32,
123}
124
125/// A linked GLSL program together with its cached uniform locations.
126///
127/// Uniform locations are stable for the lifetime of a linked program, but
128/// resolving one costs a round trip into the GL frontend that walks the
129/// program's uniform table. Caching them in a name-keyed map means a
130/// per-frame `set_uniform_1f` is a hash lookup plus the upload with no
131/// re-resolution, and a name the GLSL compiler optimized out caches the
132/// `None` so the miss is paid exactly once rather than every frame.
133#[derive(Clone, Data, Debug)]
134pub struct GlProgram {
135 /// The GL program object.
136 #[get(pub(crate))]
137 #[set(pub(crate))]
138 #[get_mut(pub(crate))]
139 pub(crate) program: WebGlProgram,
140 /// Uniform locations resolved so far, keyed by uniform name. A
141 /// `None` entry records a uniform the compiler removed, so the
142 /// negative result is not recomputed every frame either.
143 #[get(pub(crate), type(clone))]
144 #[set(pub(crate))]
145 #[get_mut(pub(crate))]
146 pub(crate) uniforms: HashMap<String, Option<WebGlUniformLocation>>,
147 /// The uniform block indices resolved so far, keyed by block name.
148 #[get(pub(crate), type(clone))]
149 #[set(pub(crate))]
150 #[get_mut(pub(crate))]
151 pub(crate) blocks: HashMap<String, u32>,
152}
153
154/// A uniform buffer bound to a numbered binding point.
155///
156/// WebGL 2 has no bind groups: the equivalent of a `var<uniform>` block
157/// is a `WebGlBuffer` bound to `UNIFORM_BUFFER` at a binding index the
158/// shader agrees on. This holds both halves of that agreement, the buffer
159/// and the binding point, so swapping programs cannot leave the two out
160/// of sync.
161#[derive(Clone, Data, Debug)]
162pub struct GlUniformBlock {
163 /// The GL buffer holding the block's records.
164 #[get(pub(crate))]
165 #[set(pub(crate))]
166 #[get_mut(pub(crate))]
167 pub(crate) buffer: WebGlBuffer,
168 /// The binding point index the shader's `layout(binding = N)`
169 /// qualifier agrees with.
170 #[get(type(copy))]
171 #[set(pub(crate))]
172 #[get_mut(pub(crate))]
173 pub binding: u32,
174 /// The byte capacity allocated for the block.
175 #[get(type(copy))]
176 #[set(pub(crate))]
177 #[get_mut(pub(crate))]
178 pub capacity: u32,
179}
180
181/// The blend and color-write state of one draw.
182///
183/// Mirrors the alpha/color pair of [`BlendState`] exactly, so the WebGL
184/// path can consume the same blend the WebGPU path builds, but carries
185/// the extra `enabled` flag WebGL needs: WebGPU expresses "no blend" as
186/// `blend: None` on the target, whereas WebGL expresses it as leaving
187/// `BLEND` disabled, which is a different call with a different cost.
188#[derive(Clone, Copy, Data, Debug, Default, PartialEq)]
189pub struct GlBlendState {
190 /// Whether `BLEND` is enabled for this draw.
191 #[get(type(copy))]
192 #[set(pub(crate))]
193 #[get_mut(pub(crate))]
194 pub enabled: bool,
195 /// The blend applied to the RGB channels.
196 #[set(pub(crate))]
197 #[get_mut(pub(crate))]
198 pub color: BlendComponent,
199 /// The blend applied to the alpha channel, which is frequently not
200 /// the same blend as the color channels.
201 #[set(pub(crate))]
202 #[get_mut(pub(crate))]
203 pub alpha: BlendComponent,
204}
205
206/// The depth-test state of one draw.
207///
208/// WebGL splits "test" from "write" into two independent calls
209/// (`DEPTH_TEST` plus `depthFunc`, and `depthMask`), whereas WebGPU's
210/// [`DepthStencilState`] derives both from `depth_write_enabled` plus the
211/// presence of an attachment. Keeping the two axes separate here is what
212/// lets a transparent overlay bind a depth buffer it must read but must
213/// not write.
214#[derive(Clone, Copy, Data, Debug, New, PartialEq)]
215pub struct GlDepthState {
216 /// Whether `DEPTH_TEST` is enabled.
217 #[get(type(copy))]
218 #[set(pub(crate))]
219 #[get_mut(pub(crate))]
220 pub enabled: bool,
221 /// The comparison between the fragment's depth and the stored depth.
222 #[set(pub(crate))]
223 #[get_mut(pub(crate))]
224 pub compare: CompareFunction,
225 /// Whether a fragment that passes the test updates the depth buffer.
226 #[get(pub(crate), type(copy))]
227 #[set(pub(crate))]
228 #[get_mut(pub(crate))]
229 pub(crate) write_enabled: bool,
230}
231
232/// The culling and winding state of one draw.
233#[derive(Clone, Copy, Data, Debug, Default, PartialEq)]
234pub struct GlCullState {
235 /// Which faces are discarded before rasterization.
236 #[set(pub(crate))]
237 #[get_mut(pub(crate))]
238 pub mode: CullMode,
239 /// Which winding order counts as front facing. Flipping it swaps what
240 /// [`CullMode::Front`] and [`CullMode::Back`] discard.
241 #[set(pub(crate))]
242 #[get_mut(pub(crate))]
243 pub front_face: FrontFace,
244}
245
246/// Which channels the fragment stage may write, as a raw four-bit mask.
247///
248/// Red is `0x1`, green `0x2`, blue `0x4`, alpha `0x8`, and all four is
249/// `0xf`. WebGL's `colorMask` is the only place a per-channel write mask
250/// can be expressed, and keeping it a bitmask rather than four booleans
251/// matches the [`ColorTargetState`] write-mask convention the WebGPU path
252/// already uses.
253#[derive(Clone, Copy, Data, Debug, New, PartialEq)]
254pub struct GlColorMask {
255 /// The four channel bits.
256 #[get(type(copy))]
257 #[set(pub(crate))]
258 #[get_mut(pub(crate))]
259 pub bits: u32,
260}
261
262/// A scissor rectangle.
263///
264/// A zero-sized box is legal and clips everything away, which is
265/// meaningfully different from scissoring being off; the off case is
266/// therefore expressed by the `Option` on [`GlRenderState::scissor`]
267/// rather than by a sentinel rectangle.
268#[derive(Clone, Copy, Data, Debug, New, PartialEq)]
269pub struct GlScissor {
270 /// The left edge in framebuffer pixels.
271 #[get(type(copy))]
272 #[get_mut(pub(crate))]
273 pub x: i32,
274 /// The bottom edge in framebuffer pixels.
275 #[get(type(copy))]
276 pub y: i32,
277 /// The width in framebuffer pixels.
278 #[get(type(copy))]
279 #[set(pub(crate))]
280 #[get_mut(pub(crate))]
281 pub width: i32,
282 /// The height in framebuffer pixels.
283 #[get(type(copy))]
284 #[set(pub(crate))]
285 #[get_mut(pub(crate))]
286 pub height: i32,
287}
288
289/// The viewport rectangle, in framebuffer pixels.
290///
291/// Stored as signed values because GL accepts a negative origin for
292/// flipped-coordinate passes even though the default is the origin.
293#[derive(Clone, Copy, Data, Debug, New, PartialEq)]
294pub struct GlViewport {
295 /// The left edge in framebuffer pixels.
296 #[get(type(copy))]
297 pub x: i32,
298 /// The bottom edge in framebuffer pixels.
299 #[get(type(copy))]
300 #[get_mut(pub(crate))]
301 pub y: i32,
302 /// The width in framebuffer pixels.
303 #[get(type(copy))]
304 #[set(pub(crate))]
305 #[get_mut(pub(crate))]
306 pub width: i32,
307 /// The height in framebuffer pixels.
308 #[get(type(copy))]
309 #[set(pub(crate))]
310 #[get_mut(pub(crate))]
311 pub height: i32,
312}
313
314/// The whole fixed-function state of one draw, as a single `Copy` value.
315///
316/// The point of bundling these six fields into one struct is that it can
317/// be diffed field by field against the state currently bound, so
318/// [`WebGl2Backend::apply_state`](super::WebGl2Backend::apply_state)
319/// issues a GL call only for what actually changed. A renderer that
320/// calls `enable`, `depthFunc`, `depthMask`, `blendFunc`,
321/// `blendEquation`, `cullFace`, `frontFace`, `colorMask`, `scissor`, and
322/// `viewport` unconditionally pays for a dozen redundant calls per draw,
323/// each one a validation check inside the driver.
324#[derive(Clone, Copy, Data, Debug, PartialEq)]
325pub struct GlRenderState {
326 /// The depth-test state.
327 #[set(pub(crate))]
328 #[get_mut(pub(crate))]
329 pub depth: GlDepthState,
330 /// The blend state.
331 #[set(pub(crate))]
332 #[get_mut(pub(crate))]
333 pub blend: GlBlendState,
334 /// The culling and winding state.
335 #[set(pub(crate))]
336 #[get_mut(pub(crate))]
337 pub cull: GlCullState,
338 /// Which channels the fragment stage may write.
339 #[set(pub(crate))]
340 #[get_mut(pub(crate))]
341 pub color_mask: GlColorMask,
342 /// The scissor rectangle, or `None` when scissoring is off.
343 #[get(type(copy))]
344 #[set(pub(crate))]
345 #[get_mut(pub(crate))]
346 pub scissor: Option<GlScissor>,
347 /// The viewport rectangle.
348 #[set(pub(crate))]
349 #[get_mut(pub(crate))]
350 pub viewport: GlViewport,
351}
352
353/// The complete WebGL 2 backend: a context plus the three shadows that
354/// make a frame cheap.
355///
356/// # Performance
357///
358/// Three things are shadowed rather than re-queried. The fixed-function
359/// state is shadowed by a [`GlRenderState`] copy, so applying a state
360/// costs only the calls for fields that actually changed. The current
361/// texture unit is shadowed by a `u32`, so binding a texture to the unit
362/// that is already active costs no `activeTexture` at all. The current
363/// program is shadowed by a `JsValue` compared with `Object.is`, so
364/// rebinding the same program costs no `useProgram`. None of the three
365/// can be answered by asking the driver without a synchronous
366/// round trip, which is exactly why they are tracked here instead.
367#[derive(Clone, Data, Debug)]
368pub struct WebGl2Backend {
369 /// The canvas element this backend draws into.
370 ///
371 /// Stored at construction instead of re-derived from
372 /// [`WebGl2RenderingContext::canvas`], so
373 /// [`WebGl2Backend::get_canvas`] is a plain field read and cannot
374 /// fail (the old `context.canvas().expect(..)` could panic if the
375 /// context was ever detached from its element).
376 #[set(pub(crate))]
377 #[get_mut(pub(crate))]
378 pub canvas: HtmlCanvasElement,
379 /// The WebGL 2 rendering context every call goes through.
380 #[set(pub(crate))]
381 #[get_mut(pub(crate))]
382 pub context: WebGl2RenderingContext,
383 /// The last state applied, diffed against to skip redundant calls.
384 #[get(pub(crate))]
385 #[set(pub(crate))]
386 #[get_mut(pub(crate))]
387 pub(crate) shadow: GlRenderState,
388 /// The texture unit that is currently active, or
389 /// [`GL_TEXTURE_UNIT_NONE`] when the active unit is not yet known.
390 #[get(pub(crate), type(copy))]
391 #[set(pub(crate))]
392 #[get_mut(pub(crate))]
393 pub(crate) active_unit: u32,
394 /// The program currently bound, compared with `Object.is`.
395 #[get(pub(crate))]
396 #[set(pub(crate))]
397 #[get_mut(pub(crate))]
398 pub(crate) bound_program: JsValue,
399 /// The color [`WebGl2Backend::begin_frame`] clears to.
400 #[get(pub(crate), type(copy))]
401 #[set(pub(crate))]
402 #[get_mut(pub(crate))]
403 pub(crate) clear_color_field: Color,
404 /// The reused destination for
405 /// [`WebGl2Backend::read_pixels`](super::WebGl2Backend::read_pixels),
406 /// grown on demand so a steady-state readback allocates nothing.
407 #[get_mut(pub(crate))]
408 #[get(pub(crate))]
409 #[set(pub(crate))]
410 pub(crate) readback: Vec<u8>,
411}
412
413/// Constructor inputs for [`WebGl2Backend`].
414///
415/// `WebGl2Backend::init` is the only constructor, and it needs a live
416/// document: it queries the canvas by selector and acquires a `webgl2`
417/// context. Every field on the backend is `pub(crate)`, so without this
418/// struct there is no way to build one from outside the crate and the
419/// state-diffing, draw-call and resize surface is untestable. Naming the
420/// inputs lets a caller that already holds a context construct a backend
421/// without going through DOM lookup.
422#[derive(Clone, Data, New)]
423pub struct WebGl2BackendInit {
424 /// The canvas the backend draws into.
425 pub canvas: HtmlCanvasElement,
426 /// The `webgl2` context every call goes through.
427 pub context: WebGl2RenderingContext,
428 /// The physical pixel width of the drawing buffer.
429 #[get(type(copy))]
430 pub width: u32,
431 /// The physical pixel height of the drawing buffer.
432 #[get(type(copy))]
433 pub height: u32,
434 /// The colour `begin_frame` clears to.
435 pub clear_color: Color,
436}
437
438/// Constructor inputs for [`GlRenderState`].
439///
440/// `GlRenderState::context_defaults` is the only way to build one today, and
441/// it hard-codes the state a *freshly created* GL context happens to be in.
442/// Every other state — a depth-tested draw, a scissored viewport, a masked
443/// render target — has to be assembled field by field to reach
444/// `WebGl2Backend::apply_state`, which means it cannot be built from outside
445/// the crate. Naming the inputs closes that gap.
446#[derive(Clone, Copy, Data, New)]
447pub struct GlRenderStateInit {
448 /// The depth-test state.
449 pub depth: GlDepthState,
450 /// The blend state.
451 pub blend: GlBlendState,
452 /// The culling and winding state.
453 pub cull: GlCullState,
454 /// Which channels the fragment stage may write.
455 pub color_mask: GlColorMask,
456 /// The scissor rectangle, or `None` when scissoring is off.
457 #[get(type(copy))]
458 pub scissor: Option<GlScissor>,
459 /// The viewport rectangle.
460 pub viewport: GlViewport,
461}