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    ///
144    /// The cache is read through a borrow and written through the
145    /// mutable accessor, so looking a name up costs a hash lookup
146    /// rather than a copy of the whole table. Only the single location
147    /// handle a caller actually receives is cloned.
148    #[get(pub(crate))]
149    #[set(pub(crate))]
150    #[get_mut(pub(crate))]
151    pub(crate) uniforms: HashMap<String, Option<WebGlUniformLocation>>,
152    /// The uniform block indices resolved so far, keyed by block name.
153    ///
154    /// Like the uniform cache above, this is borrowed on lookup and
155    /// filled in place on a miss, so a cached block costs no copy of
156    /// the table.
157    #[get(pub(crate))]
158    #[set(pub(crate))]
159    #[get_mut(pub(crate))]
160    pub(crate) blocks: HashMap<String, u32>,
161}
162
163/// A uniform buffer bound to a numbered binding point.
164///
165/// WebGL 2 has no bind groups: the equivalent of a `var<uniform>` block
166/// is a `WebGlBuffer` bound to `UNIFORM_BUFFER` at a binding index the
167/// shader agrees on. This holds both halves of that agreement, the buffer
168/// and the binding point, so swapping programs cannot leave the two out
169/// of sync.
170#[derive(Clone, Data, Debug)]
171pub struct GlUniformBlock {
172    /// The GL buffer holding the block's records.
173    #[get(pub(crate))]
174    #[set(pub(crate))]
175    #[get_mut(pub(crate))]
176    pub(crate) buffer: WebGlBuffer,
177    /// The binding point index the shader's `layout(binding = N)`
178    /// qualifier agrees with.
179    #[get(type(copy))]
180    #[set(pub(crate))]
181    #[get_mut(pub(crate))]
182    pub binding: u32,
183    /// The byte capacity allocated for the block.
184    #[get(type(copy))]
185    #[set(pub(crate))]
186    #[get_mut(pub(crate))]
187    pub capacity: u32,
188}
189
190/// The blend and color-write state of one draw.
191///
192/// Mirrors the alpha/color pair of [`BlendState`] exactly, so the WebGL
193/// path can consume the same blend the WebGPU path builds, but carries
194/// the extra `enabled` flag WebGL needs: WebGPU expresses "no blend" as
195/// `blend: None` on the target, whereas WebGL expresses it as leaving
196/// `BLEND` disabled, which is a different call with a different cost.
197#[derive(Clone, Copy, Data, Debug, Default, PartialEq)]
198pub struct GlBlendState {
199    /// Whether `BLEND` is enabled for this draw.
200    #[get(type(copy))]
201    #[set(pub(crate))]
202    #[get_mut(pub(crate))]
203    pub enabled: bool,
204    /// The blend applied to the RGB channels.
205    #[set(pub(crate))]
206    #[get_mut(pub(crate))]
207    pub color: BlendComponent,
208    /// The blend applied to the alpha channel, which is frequently not
209    /// the same blend as the color channels.
210    #[set(pub(crate))]
211    #[get_mut(pub(crate))]
212    pub alpha: BlendComponent,
213}
214
215/// The depth-test state of one draw.
216///
217/// WebGL splits "test" from "write" into two independent calls
218/// (`DEPTH_TEST` plus `depthFunc`, and `depthMask`), whereas WebGPU's
219/// [`DepthStencilState`] derives both from `depth_write_enabled` plus the
220/// presence of an attachment. Keeping the two axes separate here is what
221/// lets a transparent overlay bind a depth buffer it must read but must
222/// not write.
223#[derive(Clone, Copy, Data, Debug, New, PartialEq)]
224pub struct GlDepthState {
225    /// Whether `DEPTH_TEST` is enabled.
226    #[get(type(copy))]
227    #[set(pub(crate))]
228    #[get_mut(pub(crate))]
229    pub enabled: bool,
230    /// The comparison between the fragment's depth and the stored depth.
231    #[set(pub(crate))]
232    #[get_mut(pub(crate))]
233    pub compare: CompareFunction,
234    /// Whether a fragment that passes the test updates the depth buffer.
235    #[get(pub(crate), type(copy))]
236    #[set(pub(crate))]
237    #[get_mut(pub(crate))]
238    pub(crate) write_enabled: bool,
239}
240
241/// The culling and winding state of one draw.
242#[derive(Clone, Copy, Data, Debug, Default, PartialEq)]
243pub struct GlCullState {
244    /// Which faces are discarded before rasterization.
245    #[set(pub(crate))]
246    #[get_mut(pub(crate))]
247    pub mode: CullMode,
248    /// Which winding order counts as front facing. Flipping it swaps what
249    /// [`CullMode::Front`] and [`CullMode::Back`] discard.
250    #[set(pub(crate))]
251    #[get_mut(pub(crate))]
252    pub front_face: FrontFace,
253}
254
255/// Which channels the fragment stage may write, as a raw four-bit mask.
256///
257/// Red is `0x1`, green `0x2`, blue `0x4`, alpha `0x8`, and all four is
258/// `0xf`. WebGL's `colorMask` is the only place a per-channel write mask
259/// can be expressed, and keeping it a bitmask rather than four booleans
260/// matches the [`ColorTargetState`] write-mask convention the WebGPU path
261/// already uses.
262#[derive(Clone, Copy, Data, Debug, New, PartialEq)]
263pub struct GlColorMask {
264    /// The four channel bits.
265    #[get(type(copy))]
266    #[set(pub(crate))]
267    #[get_mut(pub(crate))]
268    pub bits: u32,
269}
270
271/// A scissor rectangle.
272///
273/// A zero-sized box is legal and clips everything away, which is
274/// meaningfully different from scissoring being off; the off case is
275/// therefore expressed by the `Option` on [`GlRenderState::scissor`]
276/// rather than by a sentinel rectangle.
277#[derive(Clone, Copy, Data, Debug, New, PartialEq)]
278pub struct GlScissor {
279    /// The left edge in framebuffer pixels.
280    #[get(type(copy))]
281    #[get_mut(pub(crate))]
282    pub x: i32,
283    /// The bottom edge in framebuffer pixels.
284    #[get(type(copy))]
285    pub y: i32,
286    /// The width in framebuffer pixels.
287    #[get(type(copy))]
288    #[set(pub(crate))]
289    #[get_mut(pub(crate))]
290    pub width: i32,
291    /// The height in framebuffer pixels.
292    #[get(type(copy))]
293    #[set(pub(crate))]
294    #[get_mut(pub(crate))]
295    pub height: i32,
296}
297
298/// The viewport rectangle, in framebuffer pixels.
299///
300/// Stored as signed values because GL accepts a negative origin for
301/// flipped-coordinate passes even though the default is the origin.
302#[derive(Clone, Copy, Data, Debug, New, PartialEq)]
303pub struct GlViewport {
304    /// The left edge in framebuffer pixels.
305    #[get(type(copy))]
306    pub x: i32,
307    /// The bottom edge in framebuffer pixels.
308    #[get(type(copy))]
309    #[get_mut(pub(crate))]
310    pub y: i32,
311    /// The width in framebuffer pixels.
312    #[get(type(copy))]
313    #[set(pub(crate))]
314    #[get_mut(pub(crate))]
315    pub width: i32,
316    /// The height in framebuffer pixels.
317    #[get(type(copy))]
318    #[set(pub(crate))]
319    #[get_mut(pub(crate))]
320    pub height: i32,
321}
322
323/// The whole fixed-function state of one draw, as a single `Copy` value.
324///
325/// The point of bundling these six fields into one struct is that it can
326/// be diffed field by field against the state currently bound, so
327/// [`WebGl2Backend::apply_state`](super::WebGl2Backend::apply_state)
328/// issues a GL call only for what actually changed. A renderer that
329/// calls `enable`, `depthFunc`, `depthMask`, `blendFunc`,
330/// `blendEquation`, `cullFace`, `frontFace`, `colorMask`, `scissor`, and
331/// `viewport` unconditionally pays for a dozen redundant calls per draw,
332/// each one a validation check inside the driver.
333#[derive(Clone, Copy, Data, Debug, PartialEq)]
334pub struct GlRenderState {
335    /// The depth-test state.
336    #[set(pub(crate))]
337    #[get_mut(pub(crate))]
338    pub depth: GlDepthState,
339    /// The blend state.
340    #[set(pub(crate))]
341    #[get_mut(pub(crate))]
342    pub blend: GlBlendState,
343    /// The culling and winding state.
344    #[set(pub(crate))]
345    #[get_mut(pub(crate))]
346    pub cull: GlCullState,
347    /// Which channels the fragment stage may write.
348    #[set(pub(crate))]
349    #[get_mut(pub(crate))]
350    pub color_mask: GlColorMask,
351    /// The scissor rectangle, or `None` when scissoring is off.
352    #[get(type(copy))]
353    #[set(pub(crate))]
354    #[get_mut(pub(crate))]
355    pub scissor: Option<GlScissor>,
356    /// The viewport rectangle.
357    #[set(pub(crate))]
358    #[get_mut(pub(crate))]
359    pub viewport: GlViewport,
360}
361
362/// The complete WebGL 2 backend: a context plus the three shadows that
363/// make a frame cheap.
364///
365/// # Performance
366///
367/// Three things are shadowed rather than re-queried. The fixed-function
368/// state is shadowed by a [`GlRenderState`] copy, so applying a state
369/// costs only the calls for fields that actually changed. The current
370/// texture unit is shadowed by a `u32`, so binding a texture to the unit
371/// that is already active costs no `activeTexture` at all. The current
372/// program is shadowed by a `JsValue` compared with `Object.is`, so
373/// rebinding the same program costs no `useProgram`. None of the three
374/// can be answered by asking the driver without a synchronous
375/// round trip, which is exactly why they are tracked here instead.
376#[derive(Clone, Data, Debug)]
377pub struct WebGl2Backend {
378    /// The canvas element this backend draws into.
379    ///
380    /// Stored at construction instead of re-derived from
381    /// [`WebGl2RenderingContext::canvas`], so
382    /// [`WebGl2Backend::get_canvas`] is a plain field read and cannot
383    /// fail (the old `context.canvas().expect(..)` could panic if the
384    /// context was ever detached from its element).
385    #[set(pub(crate))]
386    #[get_mut(pub(crate))]
387    pub canvas: HtmlCanvasElement,
388    /// The WebGL 2 rendering context every call goes through.
389    #[set(pub(crate))]
390    #[get_mut(pub(crate))]
391    pub context: WebGl2RenderingContext,
392    /// The last state applied, diffed against to skip redundant calls.
393    #[get(pub(crate))]
394    #[set(pub(crate))]
395    #[get_mut(pub(crate))]
396    pub(crate) shadow: GlRenderState,
397    /// The texture unit that is currently active, or
398    /// [`GL_TEXTURE_UNIT_NONE`] when the active unit is not yet known.
399    #[get(pub(crate), type(copy))]
400    #[set(pub(crate))]
401    #[get_mut(pub(crate))]
402    pub(crate) active_unit: u32,
403    /// The program currently bound, compared with `Object.is`.
404    #[get(pub(crate))]
405    #[set(pub(crate))]
406    #[get_mut(pub(crate))]
407    pub(crate) bound_program: JsValue,
408    /// The color [`WebGl2Backend::begin_frame`] clears to.
409    #[get(pub(crate), type(copy))]
410    #[set(pub(crate))]
411    #[get_mut(pub(crate))]
412    pub(crate) clear_color_field: Color,
413    /// The reused destination for
414    /// [`WebGl2Backend::read_pixels`](super::WebGl2Backend::read_pixels),
415    /// grown on demand so a steady-state readback allocates nothing.
416    #[get_mut(pub(crate))]
417    #[get(pub(crate))]
418    #[set(pub(crate))]
419    pub(crate) readback: Vec<u8>,
420}
421
422/// Constructor inputs for [`WebGl2Backend`].
423///
424/// `WebGl2Backend::init` is the only constructor, and it needs a live
425/// document: it queries the canvas by selector and acquires a `webgl2`
426/// context. Every field on the backend is `pub(crate)`, so without this
427/// struct there is no way to build one from outside the crate and the
428/// state-diffing, draw-call and resize surface is untestable. Naming the
429/// inputs lets a caller that already holds a context construct a backend
430/// without going through DOM lookup.
431#[derive(Clone, Data, New)]
432pub struct WebGl2BackendInit {
433    /// The canvas the backend draws into.
434    pub canvas: HtmlCanvasElement,
435    /// The `webgl2` context every call goes through.
436    pub context: WebGl2RenderingContext,
437    /// The physical pixel width of the drawing buffer.
438    #[get(type(copy))]
439    pub width: u32,
440    /// The physical pixel height of the drawing buffer.
441    #[get(type(copy))]
442    pub height: u32,
443    /// The colour `begin_frame` clears to.
444    pub clear_color: Color,
445}
446
447/// Constructor inputs for [`GlRenderState`].
448///
449/// `GlRenderState::context_defaults` is the only way to build one today, and
450/// it hard-codes the state a *freshly created* GL context happens to be in.
451/// Every other state — a depth-tested draw, a scissored viewport, a masked
452/// render target — has to be assembled field by field to reach
453/// `WebGl2Backend::apply_state`, which means it cannot be built from outside
454/// the crate. Naming the inputs closes that gap.
455#[derive(Clone, Copy, Data, New)]
456pub struct GlRenderStateInit {
457    /// The depth-test state.
458    pub depth: GlDepthState,
459    /// The blend state.
460    pub blend: GlBlendState,
461    /// The culling and winding state.
462    pub cull: GlCullState,
463    /// Which channels the fragment stage may write.
464    pub color_mask: GlColorMask,
465    /// The scissor rectangle, or `None` when scissoring is off.
466    #[get(type(copy))]
467    pub scissor: Option<GlScissor>,
468    /// The viewport rectangle.
469    pub viewport: GlViewport,
470}