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 pub(crate) buffer: WebGlBuffer,
16 /// The byte capacity the driver last allocated for this buffer.
17 #[get(type(copy))]
18 pub(crate) capacity: u32,
19 /// The GL usage hint passed at allocation: `STATIC_DRAW` for data
20 /// uploaded once, `DYNAMIC_DRAW` for data rewritten every frame.
21 #[get(type(copy))]
22 pub(crate) usage: u32,
23}
24
25/// A GPU texture holding sampled image data or render-target contents.
26///
27/// Carries the dimensions and mip count the driver was told about so a
28/// resize re-allocates instead of stretching stale texels, and records
29/// whether a mip chain exists so a caller cannot accidentally select a
30/// minification filter that samples across levels the texture does not
31/// have, which samples as solid black and is the single most common "my
32/// texture is invisible" WebGL bug.
33#[derive(Clone, Data, Debug)]
34pub struct GlTexture {
35 /// The GL texture object.
36 pub(crate) texture: WebGlTexture,
37 /// The width in texels the texture was allocated with.
38 #[get(type(copy))]
39 pub(crate) width: u32,
40 /// The height in texels the texture was allocated with.
41 #[get(type(copy))]
42 pub(crate) height: u32,
43 /// The number of mip levels allocated.
44 #[get(type(copy))]
45 pub(crate) levels: u32,
46 /// Whether `generateMipmap` was called, so a minification filter that
47 /// samples across levels is known to be legal.
48 #[get(type(copy))]
49 pub(crate) mipmapped: bool,
50}
51
52/// A vertex array object: the recorded bundle of buffer bindings and
53/// vertex attribute formats.
54///
55/// Binding a VAO replaces up to a dozen `bindBuffer` /
56/// `enableVertexAttribArray` / `vertexAttribPointer` calls with one,
57/// which is what makes per-mesh draw state cheap enough to change on
58/// every draw call.
59#[derive(Clone, Data, Debug)]
60pub struct GlVertexArray {
61 /// The GL vertex array object.
62 pub(crate) vao: WebGlVertexArrayObject,
63}
64
65/// A framebuffer: a color attachment texture plus an optional depth (and
66/// stencil) renderbuffer.
67///
68/// The color attachment is always a [`GlTexture`] rather than a
69/// renderbuffer, so the result can be sampled by a later pass or read
70/// back without a resolve blit.
71#[derive(Clone, Data, Debug)]
72pub struct GlFramebuffer {
73 /// The GL framebuffer object.
74 pub(crate) framebuffer: WebGlFramebuffer,
75 /// The depth renderbuffer, optionally packed with an 8-bit stencil,
76 /// or `None` for a color-only target such as a post-process pass
77 /// that reads depth from a texture instead of writing its own.
78 #[get(type(clone))]
79 pub(crate) depth: Option<WebGlRenderbuffer>,
80 /// The color attachment, retained so a later pass can sample the
81 /// rendered result without a separate resolve.
82 #[get(type(clone))]
83 pub(crate) color: GlTexture,
84 /// The width every attachment was allocated with, re-checked on
85 /// rebind so a window resize re-allocates rather than leaving a
86 /// frame whose attachments disagree on size.
87 #[get(type(copy))]
88 pub(crate) width: u32,
89 /// The height every attachment was allocated with.
90 #[get(type(copy))]
91 pub(crate) height: u32,
92}
93
94/// A linked GLSL program together with its cached uniform locations.
95///
96/// Uniform locations are stable for the lifetime of a linked program, but
97/// resolving one costs a round trip into the GL frontend that walks the
98/// program's uniform table. Caching them in a name-keyed map means a
99/// per-frame `set_uniform_1f` is a hash lookup plus the upload with no
100/// re-resolution, and a name the GLSL compiler optimized out caches the
101/// `None` so the miss is paid exactly once rather than every frame.
102#[derive(Clone, Data, Debug)]
103pub struct GlProgram {
104 /// The GL program object.
105 pub(crate) program: WebGlProgram,
106 /// Uniform locations resolved so far, keyed by uniform name. A
107 /// `None` entry records a uniform the compiler removed, so the
108 /// negative result is not recomputed every frame either.
109 #[get(type(clone))]
110 pub(crate) uniforms: HashMap<String, Option<WebGlUniformLocation>>,
111 /// The reusable 16-float scratch slice every `mat4` upload goes
112 /// through. A fixed-size array rather than a `Vec` so uploading a
113 /// [`Matrix4x4`] cannot allocate at all, not even on the first call.
114 pub(crate) matrix_scratch: [f32; GL_MAT4_FLOATS],
115 /// The uniform block indices resolved so far, keyed by block name.
116 #[get(type(clone))]
117 pub(crate) blocks: HashMap<String, u32>,
118}
119
120/// A uniform buffer bound to a numbered binding point.
121///
122/// WebGL 2 has no bind groups: the equivalent of a `var<uniform>` block
123/// is a `WebGlBuffer` bound to `UNIFORM_BUFFER` at a binding index the
124/// shader agrees on. This holds both halves of that agreement, the buffer
125/// and the binding point, so swapping programs cannot leave the two out
126/// of sync.
127#[derive(Clone, Data, Debug)]
128pub struct GlUniformBlock {
129 /// The GL buffer holding the block's records.
130 pub(crate) buffer: WebGlBuffer,
131 /// The binding point index the shader's `layout(binding = N)`
132 /// qualifier agrees with.
133 #[get(type(copy))]
134 pub(crate) binding: u32,
135 /// The byte capacity allocated for the block.
136 #[get(type(copy))]
137 pub(crate) capacity: u32,
138}
139
140/// The blend and color-write state of one draw.
141///
142/// Mirrors the alpha/color pair of [`BlendState`] exactly, so the WebGL
143/// path can consume the same blend the WebGPU path builds, but carries
144/// the extra `enabled` flag WebGL needs: WebGPU expresses "no blend" as
145/// `blend: None` on the target, whereas WebGL expresses it as leaving
146/// `BLEND` disabled, which is a different call with a different cost.
147#[derive(Clone, Copy, Data, Debug, Default, PartialEq)]
148pub struct GlBlendState {
149 /// Whether `BLEND` is enabled for this draw.
150 pub(crate) enabled: bool,
151 /// The blend applied to the RGB channels.
152 pub(crate) color: BlendComponent,
153 /// The blend applied to the alpha channel, which is frequently not
154 /// the same blend as the color channels.
155 pub(crate) alpha: BlendComponent,
156}
157
158/// The depth-test state of one draw.
159///
160/// WebGL splits "test" from "write" into two independent calls
161/// (`DEPTH_TEST` plus `depthFunc`, and `depthMask`), whereas WebGPU's
162/// [`DepthStencilState`] derives both from `depth_write_enabled` plus the
163/// presence of an attachment. Keeping the two axes separate here is what
164/// lets a transparent overlay bind a depth buffer it must read but must
165/// not write.
166#[derive(Clone, Copy, Data, Debug, PartialEq)]
167pub struct GlDepthState {
168 /// Whether `DEPTH_TEST` is enabled.
169 pub(crate) enabled: bool,
170 /// The comparison between the fragment's depth and the stored depth.
171 pub(crate) compare: CompareFunction,
172 /// Whether a fragment that passes the test updates the depth buffer.
173 pub(crate) write_enabled: bool,
174}
175
176/// The culling and winding state of one draw.
177#[derive(Clone, Copy, Data, Debug, Default, PartialEq)]
178pub struct GlCullState {
179 /// Which faces are discarded before rasterization.
180 pub(crate) mode: CullMode,
181 /// Which winding order counts as front facing. Flipping it swaps what
182 /// [`CullMode::Front`] and [`CullMode::Back`] discard.
183 pub(crate) front_face: FrontFace,
184}
185
186/// Which channels the fragment stage may write, as a raw four-bit mask.
187///
188/// Red is `0x1`, green `0x2`, blue `0x4`, alpha `0x8`, and all four is
189/// `0xf`. WebGL's `colorMask` is the only place a per-channel write mask
190/// can be expressed, and keeping it a bitmask rather than four booleans
191/// matches the [`ColorTargetState`] write-mask convention the WebGPU path
192/// already uses.
193#[derive(Clone, Copy, Data, Debug, PartialEq)]
194pub struct GlColorMask {
195 /// The four channel bits.
196 pub(crate) bits: u32,
197}
198
199/// A scissor rectangle.
200///
201/// A zero-sized box is legal and clips everything away, which is
202/// meaningfully different from scissoring being off; the off case is
203/// therefore expressed by the `Option` on [`GlRenderState::scissor`]
204/// rather than by a sentinel rectangle.
205#[derive(Clone, Copy, Data, Debug, PartialEq)]
206pub struct GlScissor {
207 /// The left edge in framebuffer pixels.
208 pub(crate) x: i32,
209 /// The bottom edge in framebuffer pixels.
210 pub(crate) y: i32,
211 /// The width in framebuffer pixels.
212 pub(crate) width: i32,
213 /// The height in framebuffer pixels.
214 pub(crate) height: i32,
215}
216
217/// The viewport rectangle, in framebuffer pixels.
218///
219/// Stored as signed values because GL accepts a negative origin for
220/// flipped-coordinate passes even though the default is the origin.
221#[derive(Clone, Copy, Data, Debug, PartialEq)]
222pub struct GlViewport {
223 /// The left edge in framebuffer pixels.
224 pub(crate) x: i32,
225 /// The bottom edge in framebuffer pixels.
226 pub(crate) y: i32,
227 /// The width in framebuffer pixels.
228 pub(crate) width: i32,
229 /// The height in framebuffer pixels.
230 pub(crate) height: i32,
231}
232
233/// The whole fixed-function state of one draw, as a single `Copy` value.
234///
235/// The point of bundling these six fields into one struct is that it can
236/// be diffed field by field against the state currently bound, so
237/// [`WebGl2Backend::apply_state`](super::WebGl2Backend::apply_state)
238/// issues a GL call only for what actually changed. A renderer that
239/// calls `enable`, `depthFunc`, `depthMask`, `blendFunc`,
240/// `blendEquation`, `cullFace`, `frontFace`, `colorMask`, `scissor`, and
241/// `viewport` unconditionally pays for a dozen redundant calls per draw,
242/// each one a validation check inside the driver.
243#[derive(Clone, Copy, Data, Debug, PartialEq)]
244pub struct GlRenderState {
245 /// The depth-test state.
246 pub(crate) depth: GlDepthState,
247 /// The blend state.
248 pub(crate) blend: GlBlendState,
249 /// The culling and winding state.
250 pub(crate) cull: GlCullState,
251 /// Which channels the fragment stage may write.
252 pub(crate) color_mask: GlColorMask,
253 /// The scissor rectangle, or `None` when scissoring is off.
254 pub(crate) scissor: Option<GlScissor>,
255 /// The viewport rectangle.
256 pub(crate) viewport: GlViewport,
257}
258
259/// The complete WebGL 2 backend: a context plus the three shadows that
260/// make a frame cheap.
261///
262/// # Performance
263///
264/// Three things are shadowed rather than re-queried. The fixed-function
265/// state is shadowed by a [`GlRenderState`] copy, so applying a state
266/// costs only the calls for fields that actually changed. The current
267/// texture unit is shadowed by a `u32`, so binding a texture to the unit
268/// that is already active costs no `activeTexture` at all. The current
269/// program is shadowed by a `JsValue` compared with `Object.is`, so
270/// rebinding the same program costs no `useProgram`. None of the three
271/// can be answered by asking the driver without a synchronous
272/// round trip, which is exactly why they are tracked here instead.
273#[derive(Clone, Data, Debug)]
274pub struct WebGl2Backend {
275 /// The canvas element this backend draws into.
276 ///
277 /// Stored at construction instead of re-derived from
278 /// [`WebGl2RenderingContext::canvas`], so
279 /// [`WebGl2Backend::get_canvas`] is a plain field read and cannot
280 /// fail (the old `context.canvas().expect(..)` could panic if the
281 /// context was ever detached from its element).
282 pub(crate) canvas: HtmlCanvasElement,
283 /// The WebGL 2 rendering context every call goes through.
284 pub(crate) context: WebGl2RenderingContext,
285 /// The last state applied, diffed against to skip redundant calls.
286 pub(crate) shadow: GlRenderState,
287 /// The texture unit that is currently active, or
288 /// [`GL_TEXTURE_UNIT_NONE`] when the active unit is not yet known.
289 #[get(type(copy))]
290 pub(crate) active_unit: u32,
291 /// The program currently bound, compared with `Object.is`.
292 pub(crate) bound_program: JsValue,
293 /// The color [`WebGl2Backend::begin_frame`] clears to.
294 #[get(type(copy))]
295 pub(crate) clear_color_field: Color,
296 /// The reused destination for
297 /// [`WebGl2Backend::read_pixels`](super::WebGl2Backend::read_pixels),
298 /// grown on demand so a steady-state readback allocates nothing.
299 #[get_mut(pub(crate))]
300 pub(crate) readback: Vec<u8>,
301}