Skip to main content

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}