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}