@babylonjs/lite-gl 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +10 -34
  2. package/apply-states.d.ts +1 -0
  3. package/apply-states.js +30 -0
  4. package/apply-states.js.map +1 -0
  5. package/blend.d.ts +123 -0
  6. package/blend.js +194 -0
  7. package/blend.js.map +1 -0
  8. package/context.d.ts +130 -0
  9. package/context.js +354 -0
  10. package/context.js.map +1 -0
  11. package/depth-stencil.d.ts +155 -231
  12. package/depth-stencil.js +399 -262
  13. package/depth-stencil.js.map +1 -1
  14. package/dynamic-texture.d.ts +59 -149
  15. package/dynamic-texture.js +123 -69
  16. package/dynamic-texture.js.map +1 -1
  17. package/effect-renderer.d.ts +65 -0
  18. package/effect-renderer.js +132 -0
  19. package/effect-renderer.js.map +1 -0
  20. package/effect.d.ts +142 -0
  21. package/effect.js +465 -0
  22. package/effect.js.map +1 -0
  23. package/html-texture.d.ts +44 -143
  24. package/html-texture.js +87 -81
  25. package/html-texture.js.map +1 -1
  26. package/index.d.ts +23 -1482
  27. package/index.js +42 -263
  28. package/index.js.map +1 -1
  29. package/mesh.d.ts +210 -307
  30. package/mesh.js +436 -311
  31. package/mesh.js.map +1 -1
  32. package/package.json +5 -29
  33. package/render-loop.d.ts +8 -0
  34. package/render-loop.js +62 -0
  35. package/render-loop.js.map +1 -0
  36. package/render-target.d.ts +233 -290
  37. package/render-target.js +534 -335
  38. package/render-target.js.map +1 -1
  39. package/scissor.d.ts +30 -72
  40. package/scissor.js +47 -33
  41. package/scissor.js.map +1 -1
  42. package/shader.d.ts +22 -0
  43. package/shader.js +65 -0
  44. package/shader.js.map +1 -0
  45. package/sprites.d.ts +182 -263
  46. package/sprites.js +426 -10
  47. package/sprites.js.map +1 -1
  48. package/state.d.ts +126 -0
  49. package/state.js +153 -0
  50. package/state.js.map +1 -0
  51. package/texture.d.ts +142 -0
  52. package/texture.js +433 -0
  53. package/texture.js.map +1 -0
  54. package/effect-BxxwfB_O.js +0 -737
  55. package/effect-BxxwfB_O.js.map +0 -1
  56. package/sprites--1oyVtJ3.js +0 -437
  57. package/sprites--1oyVtJ3.js.map +0 -1
  58. package/state--j_ncWIi.js +0 -155
  59. package/state--j_ncWIi.js.map +0 -1
  60. package/texture-DaMd1gGm.js +0 -329
  61. package/texture-DaMd1gGm.js.map +0 -1
package/render-target.js CHANGED
@@ -1,355 +1,554 @@
1
- import { p as pickSizedInternalFormat, i as bindTextureForUpload } from "./texture-DaMd1gGm.js";
2
- const UNSIGNED_BYTE = 5121;
3
- const HALF_FLOAT = 5131;
4
- const FLOAT = 5126;
5
- const RGBA = 6408;
6
- const RGBA8 = 32856;
7
- const LINEAR = 9729;
8
- const CLAMP_TO_EDGE = 33071;
9
- function createRenderTarget(engine, options) {
10
- return buildRT(engine, options, RGBA8, RGBA, UNSIGNED_BYTE);
1
+ /**
2
+ * Render-to-texture (offscreen framebuffer) support.
3
+ *
4
+ * Part of the public API via the `@babylonjs/lite-gl` barrel. The package is
5
+ * `sideEffects: false`, so consumers that only render fullscreen effects to the
6
+ * canvas tree-shake the FBO code out.
7
+ *
8
+ * This is the lite-gl equivalent of Babylon's `RenderTargetWrapper` +
9
+ * `ThinEngine.createRenderTargetTexture` / `bindFramebuffer` /
10
+ * `restoreDefaultFramebuffer` / `_readTexturePixelsSync`. A render target owns a
11
+ * color {@link GLTexture} (or attaches a caller-supplied one) plus an optional
12
+ * depth and/or stencil renderbuffer, all wrapped in a single
13
+ * `WebGLFramebuffer`.
14
+ *
15
+ * The default {@link createRenderTarget} makes an **RGBA8** color target and
16
+ * ships none of the HDR sized-format knowledge; {@link createFloatRenderTarget}
17
+ * is the separate opt-in for float / half-float color attachments (it alone
18
+ * carries the `RGBA16F` / `RGBA32F` table, so RGBA8 consumers tree-shake it
19
+ * away).
20
+ *
21
+ * The full color/depth/stencil GPU set is rebuilt automatically on
22
+ * `webglcontextrestored` — the render target registers itself with the engine's
23
+ * `_renderTargets` registry, and the context-restore protocol calls its
24
+ * `_restore` closure AFTER the standalone texture replay. A caller-supplied
25
+ * (BYO) color texture lives in the engine's `_textures` registry and is restored
26
+ * there first; the RT then re-attaches its freshly-swapped handle.
27
+ */
28
+ import { bindTextureForUpload, pickSizedInternalFormat } from "./texture.js";
29
+ /** GL `gl.UNSIGNED_BYTE` — the default (RGBA8) color attachment type. */
30
+ const UNSIGNED_BYTE = 0x1401;
31
+ /** GL `gl.HALF_FLOAT`. */
32
+ const HALF_FLOAT = 0x140b;
33
+ /** GL `gl.FLOAT`. */
34
+ const FLOAT = 0x1406;
35
+ /** GL `gl.RGBA`. */
36
+ const RGBA = 0x1908;
37
+ /** GL `gl.RGBA8` — the default sized internalFormat. */
38
+ const RGBA8 = 0x8058;
39
+ /** GL `gl.LINEAR`. */
40
+ const LINEAR = 0x2601;
41
+ /** GL `gl.CLAMP_TO_EDGE`. */
42
+ const CLAMP_TO_EDGE = 0x812f;
43
+ /**
44
+ * Create an offscreen **RGBA8** render target.
45
+ *
46
+ * The color attachment is an owned {@link GLTexture} (rebuilt by this target's
47
+ * own restore hook), unless {@link GLRenderTargetOptions.colorTexture} supplies a
48
+ * caller-managed (BYO) one. Mirrors Babylon's `createRenderTargetTexture`.
49
+ *
50
+ * @param engine - The engine to allocate GL resources on.
51
+ * @param options - See {@link GLRenderTargetOptions} (`width`/`height` required).
52
+ * @returns The new {@link GLRenderTarget}.
53
+ * @throws If `width`/`height` are not positive integers, a GL handle could not
54
+ * be allocated, or the resulting framebuffer is not complete. On failure every
55
+ * partial GPU object (including an owned color texture) is released first.
56
+ */
57
+ export function createRenderTarget(engine, options) {
58
+ return buildRT(engine, options, RGBA8, RGBA, UNSIGNED_BYTE);
11
59
  }
12
- function createFloatRenderTarget(engine, options) {
13
- const type = resolveColorType(engine, options.type ?? HALF_FLOAT);
14
- const internalFormat = pickSizedInternalFormat(engine.gl, RGBA, type);
15
- return buildRT(engine, options, internalFormat, RGBA, type);
60
+ /**
61
+ * Create an offscreen **float / half-float** render target — the HDR opt-in
62
+ * counterpart of {@link createRenderTarget}. This is the only render-target
63
+ * factory that references the `RGBA16F` / `RGBA32F` sized-format table, so RGBA8
64
+ * consumers ship none of it.
65
+ *
66
+ * Defaults to `gl.HALF_FLOAT`; pass `options.type = gl.FLOAT` for full 32-bit.
67
+ * The requested type is downgraded to the best renderable type the engine
68
+ * supports (mirroring Babylon's `getTextureType`).
69
+ *
70
+ * @param engine - The engine to allocate GL resources on.
71
+ * @param options - See {@link GLFloatRenderTargetOptions}.
72
+ * @returns The new {@link GLRenderTarget}.
73
+ * @throws As {@link createRenderTarget}.
74
+ */
75
+ export function createFloatRenderTarget(engine, options) {
76
+ const type = resolveColorType(engine, options.type ?? HALF_FLOAT);
77
+ const internalFormat = pickSizedInternalFormat(engine.gl, RGBA, type);
78
+ return buildRT(engine, options, internalFormat, RGBA, type);
16
79
  }
17
- function bindRenderTarget(engine, rt) {
18
- if (engine._isLost || engine._disposed) {
19
- return;
20
- }
21
- if (rt !== null && rt._disposed) {
22
- return;
23
- }
24
- const gl = engine.gl;
25
- const s = engine._state;
26
- const fb = rt === null ? null : rt._framebuffer;
27
- if (s.boundFramebuffer !== fb) {
28
- gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
29
- s.boundFramebuffer = fb;
30
- }
31
- engine._currentRenderTarget = rt;
32
- if (rt === null) {
33
- setViewportCached(engine, 0, 0, engine.canvas.width, engine.canvas.height);
34
- } else {
35
- setViewportCached(engine, 0, 0, rt.width, rt.height);
36
- }
80
+ /**
81
+ * Bind the render target's framebuffer as the draw target and set the viewport
82
+ * to cover it. `rt = null` binds the default (canvas) framebuffer and resets the
83
+ * viewport to the full canvas — the counterpart of Babylon's
84
+ * `restoreDefaultFramebuffer`. Subsequent `drawEffect` / `drawIndexed` /
85
+ * `clearEngine` calls write into the bound target.
86
+ *
87
+ * Cached. No-op on a lost/disposed context or a disposed `rt`. Mipmaps are NOT
88
+ * regenerated here — refresh a target's mip chain explicitly via
89
+ * {@link generateRenderTargetMipMaps} after rendering into it.
90
+ *
91
+ * @param engine - The engine.
92
+ * @param rt - The render target to draw into, or `null` for the canvas.
93
+ */
94
+ export function bindRenderTarget(engine, rt) {
95
+ if (engine._isLost || engine._disposed) {
96
+ return;
97
+ }
98
+ if (rt !== null && rt._disposed) {
99
+ return;
100
+ }
101
+ const gl = engine.gl;
102
+ const s = engine._state;
103
+ const fb = rt === null ? null : rt._framebuffer;
104
+ if (s.boundFramebuffer !== fb) {
105
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
106
+ s.boundFramebuffer = fb;
107
+ }
108
+ engine._currentRenderTarget = rt;
109
+ if (rt === null) {
110
+ setViewportCached(engine, 0, 0, engine.canvas.width, engine.canvas.height);
111
+ }
112
+ else {
113
+ setViewportCached(engine, 0, 0, rt.width, rt.height);
114
+ }
37
115
  }
38
- function generateRenderTargetMipMaps(engine, rt) {
39
- if (engine._isLost || engine._disposed || rt._disposed || rt.texture.handle === null) {
40
- return;
41
- }
42
- bindTextureForUpload(engine, rt.texture.handle);
43
- engine.gl.generateMipmap(engine.gl.TEXTURE_2D);
116
+ /** Regenerate a render target's color-attachment mip chain from its (freshly
117
+ * rendered) level-0 mipmaps for render targets are a pure manual opt-in
118
+ * (call this after rendering into the target). No-op for a disposed target, a
119
+ * handle-less color attachment, or a lost/disposed context. */
120
+ export function generateRenderTargetMipMaps(engine, rt) {
121
+ if (engine._isLost || engine._disposed || rt._disposed || rt.texture.handle === null) {
122
+ return;
123
+ }
124
+ bindTextureForUpload(engine, rt.texture.handle);
125
+ engine.gl.generateMipmap(engine.gl.TEXTURE_2D);
44
126
  }
45
- function resizeRenderTarget(engine, rt, width, height) {
46
- var _a, _b;
47
- if (rt._disposed) {
48
- return;
49
- }
50
- validateSize(width, height);
51
- if (rt.width === width && rt.height === height) {
52
- return;
53
- }
54
- rt.width = width;
55
- rt.height = height;
56
- rt.texture.width = width;
57
- rt.texture.height = height;
58
- if (engine._isLost || engine._disposed) {
59
- return;
60
- }
61
- const wasBound = engine._state.boundFramebuffer === rt._framebuffer && rt._framebuffer !== null;
62
- if (!rt._config.ownsColorTexture) {
63
- (_b = (_a = rt.texture)._updateRaw) == null ? void 0 : _b.call(_a, engine, null, width, height, 4);
64
- }
65
- rt._deleteGpu(engine.gl);
66
- allocateRenderTargetGpu(engine, rt);
67
- if (wasBound) {
68
- bindRenderTarget(engine, rt);
69
- }
127
+ /**
128
+ * Resize the render target's color attachment (and depth/stencil renderbuffer).
129
+ * Reallocates storage at the new size; the contents are discarded. The
130
+ * `GLRenderTarget` / `GLTexture` identity is preserved, so consumers and
131
+ * effect-sampler bindings holding the reference stay valid. If this target was
132
+ * the live draw target, it is rebound (with the new-size viewport) afterwards.
133
+ *
134
+ * No-op when the size is unchanged or `rt` is disposed. While the context is
135
+ * lost the new size is recorded but the GL reallocation is deferred to the
136
+ * restore hook.
137
+ *
138
+ * @param engine - The engine.
139
+ * @param rt - The render target to resize.
140
+ * @param width - New width in texels ( 1).
141
+ * @param height - New height in texels (≥ 1).
142
+ */
143
+ export function resizeRenderTarget(engine, rt, width, height) {
144
+ if (rt._disposed) {
145
+ return;
146
+ }
147
+ validateSize(width, height);
148
+ if (rt.width === width && rt.height === height) {
149
+ return;
150
+ }
151
+ rt.width = width;
152
+ rt.height = height;
153
+ rt.texture.width = width;
154
+ rt.texture.height = height;
155
+ if (engine._isLost || engine._disposed) {
156
+ return;
157
+ }
158
+ // Capture the live binding BEFORE we delete the old FBO so we can restore it.
159
+ const wasBound = engine._state.boundFramebuffer === rt._framebuffer && rt._framebuffer !== null;
160
+ // For a BYO color texture, re-specify the caller's storage at the new size
161
+ // (contents discarded, like the owned path). `_updateRaw` updates the
162
+ // texture's tracked dims AND re-runs its upload closure — `_upload` alone
163
+ // would re-upload at the stale ORIGINAL size, leaving the attachment a
164
+ // different size than the FBO. An external handle without `_updateRaw` must
165
+ // be resized by its owner; we then just rebuild the FBO around it. An owned
166
+ // texture is recreated wholesale by allocateRenderTargetGpu.
167
+ if (!rt._config.ownsColorTexture) {
168
+ rt.texture._updateRaw?.(engine, null, width, height, 4);
169
+ }
170
+ rt._deleteGpu(engine.gl);
171
+ allocateRenderTargetGpu(engine, rt);
172
+ if (wasBound) {
173
+ bindRenderTarget(engine, rt);
174
+ }
70
175
  }
71
- function readRenderTargetPixels(engine, rt, x, y, width, height, into) {
72
- const elements = width * height * 4;
73
- if (engine._isLost || engine._disposed || rt._disposed) {
74
- return into ?? new Uint8Array(0);
75
- }
76
- const gl = engine.gl;
77
- const s = engine._state;
78
- if (s.boundFramebuffer !== rt._framebuffer) {
79
- gl.bindFramebuffer(gl.FRAMEBUFFER, rt._framebuffer);
80
- s.boundFramebuffer = rt._framebuffer;
81
- }
82
- const type = rt._config.type;
83
- let buffer = into;
84
- if (buffer === void 0) {
85
- buffer = type === gl.FLOAT ? new Float32Array(elements) : type === gl.HALF_FLOAT ? new Uint16Array(elements) : new Uint8Array(elements);
86
- }
87
- gl.readPixels(x, y, width, height, gl.RGBA, type, buffer);
88
- return buffer;
176
+ /**
177
+ * Synchronously read back a rectangle of the render target's color attachment
178
+ * via `gl.readPixels` the lite-gl equivalent of Babylon's
179
+ * `_readTexturePixelsSync`. Binds the target's framebuffer (leaving it bound,
180
+ * matching Babylon).
181
+ *
182
+ * The returned array element type follows the color attachment type:
183
+ * `Uint8Array` for `UNSIGNED_BYTE`, `Float32Array` for `FLOAT`, `Uint16Array`
184
+ * for `HALF_FLOAT`. Origin is GL bottom-left.
185
+ *
186
+ * @param engine - The engine.
187
+ * @param rt - The render target to read from.
188
+ * @param x - Lower-left X of the read rectangle, in texels.
189
+ * @param y - Lower-left Y of the read rectangle, in texels.
190
+ * @param width - Read rectangle width in texels.
191
+ * @param height - Read rectangle height in texels.
192
+ * @param into - Optional preallocated buffer (`width*height*4` elements of the
193
+ * matching type). Reused to avoid per-call allocation.
194
+ * @returns The pixel buffer (the provided `into`, or a freshly allocated one).
195
+ * Empty buffer on a lost/disposed context.
196
+ */
197
+ export function readRenderTargetPixels(engine, rt, x, y, width, height, into) {
198
+ const elements = width * height * 4;
199
+ if (engine._isLost || engine._disposed || rt._disposed) {
200
+ return into ?? new Uint8Array(0);
201
+ }
202
+ const gl = engine.gl;
203
+ const s = engine._state;
204
+ if (s.boundFramebuffer !== rt._framebuffer) {
205
+ gl.bindFramebuffer(gl.FRAMEBUFFER, rt._framebuffer);
206
+ s.boundFramebuffer = rt._framebuffer;
207
+ }
208
+ const type = rt._config.type;
209
+ let buffer = into;
210
+ if (buffer === undefined) {
211
+ buffer = type === gl.FLOAT ? new Float32Array(elements) : type === gl.HALF_FLOAT ? new Uint16Array(elements) : new Uint8Array(elements);
212
+ }
213
+ gl.readPixels(x, y, width, height, gl.RGBA, type, buffer);
214
+ return buffer;
89
215
  }
90
- function disposeRenderTarget(engine, rt) {
91
- if (rt === null || rt === void 0 || rt._disposed) {
92
- return;
93
- }
94
- rt._disposed = true;
95
- if (rt._config.ownsColorTexture) {
96
- rt.texture._disposed = true;
97
- }
98
- if (engine._currentRenderTarget === rt) {
99
- engine._currentRenderTarget = null;
100
- }
101
- const i = engine._renderTargets.indexOf(rt);
102
- if (i !== -1) {
103
- engine._renderTargets.splice(i, 1);
104
- }
105
- const handle = rt.texture.handle;
106
- if (!engine._isLost && !engine._disposed) {
107
- rt._deleteGpu(engine.gl);
108
- } else {
109
- rt._framebuffer = null;
110
- rt._depthStencil = null;
111
- }
112
- if (handle !== null) {
113
- const bound = engine._state.boundTextures;
114
- for (let u = 0; u < bound.length; u++) {
115
- if (bound[u] === handle) {
116
- bound[u] = null;
117
- }
118
- }
119
- }
216
+ /**
217
+ * Release the render target's framebuffer, depth/stencil renderbuffer and (iff
218
+ * owned) color texture, and unregister it from the engine. Idempotent, and a
219
+ * no-op for `null`/`undefined` (so an optional target can be released
220
+ * unconditionally). Clears the bound-framebuffer cache if it pointed at this
221
+ * target, and any sampler slot that held the color texture handle.
222
+ *
223
+ * A BYO {@link GLRenderTargetOptions.colorTexture} is NOT disposed here — it is
224
+ * engine-managed and the caller owns its lifetime.
225
+ *
226
+ * @param engine - The engine.
227
+ * @param rt - The render target to dispose, or `null`/`undefined` for a no-op.
228
+ */
229
+ export function disposeRenderTarget(engine, rt) {
230
+ if (rt === null || rt === undefined || rt._disposed) {
231
+ return;
232
+ }
233
+ rt._disposed = true;
234
+ // Only the owned color texture is marked disposed here; a BYO texture stays
235
+ // live for its caller-owner.
236
+ if (rt._config.ownsColorTexture) {
237
+ rt.texture._disposed = true;
238
+ }
239
+ if (engine._currentRenderTarget === rt) {
240
+ engine._currentRenderTarget = null;
241
+ }
242
+ const i = engine._renderTargets.indexOf(rt);
243
+ if (i !== -1) {
244
+ engine._renderTargets.splice(i, 1);
245
+ }
246
+ // Capture the handle before _deleteGpu nulls it (owned case).
247
+ const handle = rt.texture.handle;
248
+ if (!engine._isLost && !engine._disposed) {
249
+ rt._deleteGpu(engine.gl);
250
+ }
251
+ else {
252
+ rt._framebuffer = null;
253
+ rt._depthStencil = null;
254
+ }
255
+ if (handle !== null) {
256
+ const bound = engine._state.boundTextures;
257
+ for (let u = 0; u < bound.length; u++) {
258
+ if (bound[u] === handle) {
259
+ bound[u] = null;
260
+ }
261
+ }
262
+ }
120
263
  }
121
- function createPingPong(engine, options) {
122
- const a = createRenderTarget(engine, options);
123
- let b;
124
- try {
125
- b = createRenderTarget(engine, options);
126
- } catch (e) {
127
- disposeRenderTarget(engine, a);
128
- throw e;
129
- }
130
- const pp = {
131
- _a: a,
132
- _b: b,
133
- _readIsA: true,
134
- _disposed: false,
135
- get read() {
136
- return pp._readIsA ? pp._a : pp._b;
137
- },
138
- get write() {
139
- return pp._readIsA ? pp._b : pp._a;
140
- },
141
- swap() {
142
- pp._readIsA = !pp._readIsA;
143
- }
144
- };
145
- return pp;
264
+ /**
265
+ * Create a {@link GLPingPong}: two same-sized {@link GLRenderTarget}s for
266
+ * self-feedback effects. `read` starts as the first target and `write` the
267
+ * second; {@link GLPingPong.swap} exchanges them allocation-free.
268
+ *
269
+ * @param engine - The engine to create GL resources on.
270
+ * @param options - Applied identically to both targets.
271
+ * @returns The new {@link GLPingPong}.
272
+ * @throws As {@link createRenderTarget}. If the second target fails to build the
273
+ * first is disposed before rethrowing (no leak).
274
+ */
275
+ export function createPingPong(engine, options) {
276
+ const a = createRenderTarget(engine, options);
277
+ let b;
278
+ try {
279
+ b = createRenderTarget(engine, options);
280
+ }
281
+ catch (e) {
282
+ disposeRenderTarget(engine, a);
283
+ throw e;
284
+ }
285
+ const pp = {
286
+ _a: a,
287
+ _b: b,
288
+ _readIsA: true,
289
+ _disposed: false,
290
+ get read() {
291
+ return pp._readIsA ? pp._a : pp._b;
292
+ },
293
+ get write() {
294
+ return pp._readIsA ? pp._b : pp._a;
295
+ },
296
+ swap() {
297
+ pp._readIsA = !pp._readIsA;
298
+ },
299
+ };
300
+ return pp;
146
301
  }
147
- function resizePingPong(engine, pp, width, height) {
148
- if (pp._disposed) {
149
- return;
150
- }
151
- resizeRenderTarget(engine, pp._a, width, height);
152
- resizeRenderTarget(engine, pp._b, width, height);
302
+ /**
303
+ * Resize both targets of a {@link GLPingPong}. No-op when disposed.
304
+ *
305
+ * @param engine - The engine that owns `pp`.
306
+ * @param pp - The ping-pong pair to resize.
307
+ * @param width - New width in texels (positive integer).
308
+ * @param height - New height in texels (positive integer).
309
+ */
310
+ export function resizePingPong(engine, pp, width, height) {
311
+ if (pp._disposed) {
312
+ return;
313
+ }
314
+ resizeRenderTarget(engine, pp._a, width, height);
315
+ resizeRenderTarget(engine, pp._b, width, height);
153
316
  }
154
- function disposePingPong(engine, pp) {
155
- if (pp === null || pp === void 0 || pp._disposed) {
156
- return;
157
- }
158
- pp._disposed = true;
159
- disposeRenderTarget(engine, pp._a);
160
- disposeRenderTarget(engine, pp._b);
317
+ /**
318
+ * Release both targets of a {@link GLPingPong}. Idempotent, and a no-op for
319
+ * `null`/`undefined` (matching {@link disposeRenderTarget}).
320
+ *
321
+ * @param engine - The engine that owns `pp`.
322
+ * @param pp - The ping-pong pair to release, or `null`/`undefined` for a no-op.
323
+ */
324
+ export function disposePingPong(engine, pp) {
325
+ if (pp === null || pp === undefined || pp._disposed) {
326
+ return;
327
+ }
328
+ pp._disposed = true;
329
+ disposeRenderTarget(engine, pp._a);
330
+ disposeRenderTarget(engine, pp._b);
161
331
  }
332
+ /* ──────────────────────────── internal helpers ──────────────────────────── */
333
+ /** Shared private constructor. Validates size, resolves the config (given the
334
+ * exact sized `internalFormat` by the caller — NO format-table lookup), wires
335
+ * the owned-or-BYO color texture, allocates the GPU set atomically, and
336
+ * registers the target. */
162
337
  function buildRT(engine, options, internalFormat, format, type) {
163
- const width = options.width;
164
- const height = options.height;
165
- validateSize(width, height);
166
- const gl = engine.gl;
167
- const byo = options.colorTexture;
168
- const config = {
169
- internalFormat,
170
- format,
171
- type,
172
- hasDepth: options.generateDepthBuffer ?? false,
173
- minFilter: options.minFilter ?? LINEAR,
174
- magFilter: options.magFilter ?? LINEAR,
175
- wrapS: options.wrapS ?? CLAMP_TO_EDGE,
176
- wrapT: options.wrapT ?? CLAMP_TO_EDGE,
177
- ownsColorTexture: byo === void 0
178
- };
179
- const texture = byo ?? {
180
- handle: null,
181
- target: gl.TEXTURE_2D,
182
- width,
183
- height,
184
- isReady: false,
185
- _disposed: false,
186
- _refCount: 1,
187
- // Owned-by-RT: its storage is (re)allocated by allocateRenderTargetGpu,
188
- // never via the engine `_textures` replay (it is NOT registered there).
189
- _upload: () => {
190
- },
191
- _wasReady: false
192
- };
193
- const rt = {
194
- texture,
195
- width,
196
- height,
197
- isReady: false,
198
- _framebuffer: null,
199
- _depthStencil: null,
200
- _rebuildDepthStencil: void 0,
201
- _config: config,
202
- _disposed: false,
203
- _deleteGpu: () => {
204
- },
205
- _restore: () => {
206
- }
207
- };
208
- rt._deleteGpu = (glc) => {
209
- const s = engine._state;
210
- if (rt._framebuffer !== null) {
211
- if (s.boundFramebuffer === rt._framebuffer) {
212
- s.boundFramebuffer = null;
213
- }
214
- glc.deleteFramebuffer(rt._framebuffer);
215
- rt._framebuffer = null;
216
- }
217
- if (rt._depthStencil !== null) {
218
- glc.deleteRenderbuffer(rt._depthStencil);
219
- rt._depthStencil = null;
220
- }
221
- if (config.ownsColorTexture && rt.texture.handle !== null) {
222
- glc.deleteTexture(rt.texture.handle);
223
- rt.texture.handle = null;
224
- }
225
- rt.texture.isReady = false;
226
- rt.isReady = false;
227
- };
228
- rt._restore = (target) => {
229
- try {
230
- allocateRenderTargetGpu(target, rt);
231
- } catch (err) {
232
- console.error("lite-gl: render target restore failed", err);
233
- }
234
- };
235
- allocateRenderTargetGpu(engine, rt);
236
- engine._renderTargets.push(rt);
237
- return rt;
338
+ const width = options.width;
339
+ const height = options.height;
340
+ validateSize(width, height);
341
+ const gl = engine.gl;
342
+ const byo = options.colorTexture;
343
+ const config = {
344
+ internalFormat,
345
+ format,
346
+ type,
347
+ hasDepth: options.generateDepthBuffer ?? false,
348
+ minFilter: options.minFilter ?? LINEAR,
349
+ magFilter: options.magFilter ?? LINEAR,
350
+ wrapS: options.wrapS ?? CLAMP_TO_EDGE,
351
+ wrapT: options.wrapT ?? CLAMP_TO_EDGE,
352
+ ownsColorTexture: byo === undefined,
353
+ };
354
+ const texture = byo ?? {
355
+ handle: null,
356
+ target: gl.TEXTURE_2D,
357
+ width,
358
+ height,
359
+ isReady: false,
360
+ _disposed: false,
361
+ _refCount: 1,
362
+ // Owned-by-RT: its storage is (re)allocated by allocateRenderTargetGpu,
363
+ // never via the engine `_textures` replay (it is NOT registered there).
364
+ _upload: () => { },
365
+ _wasReady: false,
366
+ };
367
+ const rt = {
368
+ texture,
369
+ width,
370
+ height,
371
+ isReady: false,
372
+ _framebuffer: null,
373
+ _depthStencil: null,
374
+ _rebuildDepthStencil: undefined,
375
+ _config: config,
376
+ _disposed: false,
377
+ _deleteGpu: () => { },
378
+ _restore: () => { },
379
+ };
380
+ rt._deleteGpu = (glc) => {
381
+ const s = engine._state;
382
+ if (rt._framebuffer !== null) {
383
+ // Deleting the bound FBO reverts GL to framebuffer 0 (spec) just
384
+ // reset the cache, no explicit rebind needed.
385
+ if (s.boundFramebuffer === rt._framebuffer) {
386
+ s.boundFramebuffer = null;
387
+ }
388
+ glc.deleteFramebuffer(rt._framebuffer);
389
+ rt._framebuffer = null;
390
+ }
391
+ if (rt._depthStencil !== null) {
392
+ glc.deleteRenderbuffer(rt._depthStencil);
393
+ rt._depthStencil = null;
394
+ }
395
+ // Never delete a BYO color texture — the caller-owner manages it.
396
+ if (config.ownsColorTexture && rt.texture.handle !== null) {
397
+ glc.deleteTexture(rt.texture.handle);
398
+ rt.texture.handle = null;
399
+ }
400
+ rt.texture.isReady = false;
401
+ rt.isReady = false;
402
+ };
403
+ rt._restore = (target) => {
404
+ // Resilient: a restore failure must not break the engine's restore loop.
405
+ try {
406
+ allocateRenderTargetGpu(target, rt);
407
+ }
408
+ catch (err) {
409
+ console.error("lite-gl: render target restore failed", err);
410
+ }
411
+ };
412
+ allocateRenderTargetGpu(engine, rt);
413
+ engine._renderTargets.push(rt);
414
+ return rt;
238
415
  }
416
+ /** Downgrade a requested float/half-float color type to the best renderable
417
+ * type the engine supports, mirroring Babylon's `getTextureType`. */
239
418
  function resolveColorType(engine, type) {
240
- const gl = engine.gl;
241
- if (type === FLOAT && !engine.caps.textureFloatRender) {
242
- return engine.caps.textureHalfFloatRender ? gl.HALF_FLOAT : gl.UNSIGNED_BYTE;
243
- }
244
- if (type === HALF_FLOAT && !engine.caps.textureHalfFloatRender) {
245
- return gl.UNSIGNED_BYTE;
246
- }
247
- return type;
419
+ const gl = engine.gl;
420
+ if (type === FLOAT && !engine.caps.textureFloatRender) {
421
+ return engine.caps.textureHalfFloatRender ? gl.HALF_FLOAT : gl.UNSIGNED_BYTE;
422
+ }
423
+ if (type === HALF_FLOAT && !engine.caps.textureHalfFloatRender) {
424
+ return gl.UNSIGNED_BYTE;
425
+ }
426
+ return type;
248
427
  }
428
+ /** Validate a render-target size is a pair of positive integers. */
249
429
  function validateSize(width, height) {
250
- if (!Number.isInteger(width) || !Number.isInteger(height) || width < 1 || height < 1) {
251
- throw new Error(`lite-gl: render target size must be positive integers, got ${width}x${height}`);
252
- }
430
+ if (!Number.isInteger(width) || !Number.isInteger(height) || width < 1 || height < 1) {
431
+ throw new Error(`lite-gl: render target size must be positive integers, got ${width}x${height}`);
432
+ }
253
433
  }
434
+ /**
435
+ * (Re)allocate the color texture (iff owned), framebuffer and depth/stencil
436
+ * renderbuffer into fresh handles, attach them, and validate completeness.
437
+ * Shared by create / resize / context-restore. No-op on a lost/disposed context.
438
+ *
439
+ * ATOMIC: captures the previously-bound framebuffer and restores it in a
440
+ * `finally`; on any failure deletes the partial FBO / renderbuffer (and the
441
+ * owned color texture it created), nulls the fields, and rethrows — so a failed
442
+ * build never leaks a GPU handle nor leaves a half-attached framebuffer bound.
443
+ */
254
444
  function allocateRenderTargetGpu(engine, rt) {
255
- var _a;
256
- if (engine._isLost || engine._disposed) {
257
- return;
258
- }
259
- const gl = engine.gl;
260
- const s = engine._state;
261
- const c = rt._config;
262
- const prevFb = s.boundFramebuffer;
263
- let createdTexture = null;
264
- let fb = null;
265
- let rb = null;
266
- try {
267
- if (c.ownsColorTexture) {
268
- const texHandle = gl.createTexture();
269
- if (texHandle === null) {
270
- throw new Error("lite-gl: gl.createTexture returned null (render target color)");
271
- }
272
- createdTexture = texHandle;
273
- rt.texture.handle = texHandle;
274
- bindTextureForUpload(engine, texHandle);
275
- gl.texImage2D(gl.TEXTURE_2D, 0, c.internalFormat, rt.width, rt.height, 0, c.format, c.type, null);
276
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, c.minFilter);
277
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, c.magFilter);
278
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, c.wrapS);
279
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, c.wrapT);
280
- rt.texture.isReady = true;
281
- rt.texture._wasReady = true;
282
- }
283
- fb = gl.createFramebuffer();
284
- if (fb === null) {
285
- throw new Error("lite-gl: gl.createFramebuffer returned null");
286
- }
287
- rt._framebuffer = fb;
288
- gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
289
- s.boundFramebuffer = fb;
290
- gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, rt.texture.handle, 0);
291
- rt._depthStencil = null;
292
- if (c.hasDepth) {
293
- rb = gl.createRenderbuffer();
294
- if (rb === null) {
295
- throw new Error("lite-gl: gl.createRenderbuffer returned null");
296
- }
297
- gl.bindRenderbuffer(gl.RENDERBUFFER, rb);
298
- gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, rt.width, rt.height);
299
- gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, rb);
300
- gl.bindRenderbuffer(gl.RENDERBUFFER, null);
301
- rt._depthStencil = rb;
302
- }
303
- (_a = rt._rebuildDepthStencil) == null ? void 0 : _a.call(rt, engine);
304
- const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
305
- if (status !== gl.FRAMEBUFFER_COMPLETE) {
306
- throw new Error(`lite-gl: render target framebuffer incomplete (status 0x${status.toString(16)})`);
307
- }
308
- rt.isReady = true;
309
- } catch (e) {
310
- if (rt._depthStencil !== null) {
311
- gl.deleteRenderbuffer(rt._depthStencil);
312
- }
313
- rt._depthStencil = null;
314
- if (fb !== null) {
315
- gl.deleteFramebuffer(fb);
316
- }
317
- rt._framebuffer = null;
318
- if (createdTexture !== null) {
319
- gl.deleteTexture(createdTexture);
320
- rt.texture.handle = null;
321
- rt.texture.isReady = false;
322
- }
323
- rt.isReady = false;
324
- throw e;
325
- } finally {
326
- if (s.boundFramebuffer !== prevFb) {
327
- gl.bindFramebuffer(gl.FRAMEBUFFER, prevFb);
328
- s.boundFramebuffer = prevFb;
329
- }
330
- }
445
+ if (engine._isLost || engine._disposed) {
446
+ return;
447
+ }
448
+ const gl = engine.gl;
449
+ const s = engine._state;
450
+ const c = rt._config;
451
+ const prevFb = s.boundFramebuffer;
452
+ let createdTexture = null;
453
+ let fb = null;
454
+ let rb = null;
455
+ try {
456
+ // ── Color texture (owned only) ───────────────────────────────────────
457
+ if (c.ownsColorTexture) {
458
+ const texHandle = gl.createTexture();
459
+ if (texHandle === null) {
460
+ throw new Error("lite-gl: gl.createTexture returned null (render target color)");
461
+ }
462
+ createdTexture = texHandle;
463
+ rt.texture.handle = texHandle;
464
+ bindTextureForUpload(engine, texHandle);
465
+ gl.texImage2D(gl.TEXTURE_2D, 0, c.internalFormat, rt.width, rt.height, 0, c.format, c.type, null);
466
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, c.minFilter);
467
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, c.magFilter);
468
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, c.wrapS);
469
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, c.wrapT);
470
+ rt.texture.isReady = true;
471
+ rt.texture._wasReady = true;
472
+ }
473
+ // ── Framebuffer + attachments ────────────────────────────────────────
474
+ fb = gl.createFramebuffer();
475
+ if (fb === null) {
476
+ throw new Error("lite-gl: gl.createFramebuffer returned null");
477
+ }
478
+ rt._framebuffer = fb;
479
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
480
+ s.boundFramebuffer = fb;
481
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, rt.texture.handle, 0);
482
+ // ── Depth (core, DEPTH-ONLY) ─────────────────────────────────────────
483
+ // The core only ever builds a DEPTH_COMPONENT16 depth buffer. Stencil /
484
+ // packed depth-stencil is an opt-in installed by
485
+ // `generateRenderTargetStencil` (`@babylonjs/lite-gl`),
486
+ // which sets `_rebuildDepthStencil` to a closure that REPLACES the
487
+ // depth-only buffer below with its own packed/stencil renderbuffer. That
488
+ // hook is re-run here on every rebuild (create / resize / context-restore)
489
+ // so a helper-added stencil attachment survives at the new size.
490
+ rt._depthStencil = null;
491
+ if (c.hasDepth) {
492
+ rb = gl.createRenderbuffer();
493
+ if (rb === null) {
494
+ throw new Error("lite-gl: gl.createRenderbuffer returned null");
495
+ }
496
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rb);
497
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, rt.width, rt.height);
498
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, rb);
499
+ gl.bindRenderbuffer(gl.RENDERBUFFER, null);
500
+ rt._depthStencil = rb;
501
+ }
502
+ // A helper-attached stencil replaces/augments the depth-only buffer.
503
+ rt._rebuildDepthStencil?.(engine);
504
+ const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
505
+ if (status !== gl.FRAMEBUFFER_COMPLETE) {
506
+ throw new Error(`lite-gl: render target framebuffer incomplete (status 0x${status.toString(16)})`);
507
+ }
508
+ rt.isReady = true;
509
+ }
510
+ catch (e) {
511
+ // Free whatever depth/stencil renderbuffer is currently attached. After a
512
+ // `_rebuildDepthStencil` hook ran, `rt._depthStencil` may be a packed buffer
513
+ // the hook swapped in (it deletes the core `rb` itself on success), so free
514
+ // the live `rt._depthStencil` rather than the stale local `rb`.
515
+ if (rt._depthStencil !== null) {
516
+ gl.deleteRenderbuffer(rt._depthStencil);
517
+ }
518
+ rt._depthStencil = null;
519
+ if (fb !== null) {
520
+ gl.deleteFramebuffer(fb);
521
+ }
522
+ rt._framebuffer = null;
523
+ // Delete ONLY a texture we created here (owned). Never a BYO texture.
524
+ if (createdTexture !== null) {
525
+ gl.deleteTexture(createdTexture);
526
+ rt.texture.handle = null;
527
+ rt.texture.isReady = false;
528
+ }
529
+ rt.isReady = false;
530
+ throw e;
531
+ }
532
+ finally {
533
+ // Restore the previously-bound draw target — creation/restore must not
534
+ // silently redirect subsequent draws.
535
+ if (s.boundFramebuffer !== prevFb) {
536
+ gl.bindFramebuffer(gl.FRAMEBUFFER, prevFb);
537
+ s.boundFramebuffer = prevFb;
538
+ }
539
+ }
331
540
  }
541
+ /** Inline cached `gl.viewport` — kept local so the render-target module has
542
+ * no runtime dependency on the effect-renderer module. */
332
543
  function setViewportCached(engine, x, y, w, h) {
333
- const s = engine._state;
334
- if (s.viewportX === x && s.viewportY === y && s.viewportW === w && s.viewportH === h) {
335
- return;
336
- }
337
- s.viewportX = x;
338
- s.viewportY = y;
339
- s.viewportW = w;
340
- s.viewportH = h;
341
- engine.gl.viewport(x, y, w, h);
544
+ const s = engine._state;
545
+ if (s.viewportX === x && s.viewportY === y && s.viewportW === w && s.viewportH === h) {
546
+ return;
547
+ }
548
+ s.viewportX = x;
549
+ s.viewportY = y;
550
+ s.viewportW = w;
551
+ s.viewportH = h;
552
+ engine.gl.viewport(x, y, w, h);
342
553
  }
343
- export {
344
- bindRenderTarget,
345
- createFloatRenderTarget,
346
- createPingPong,
347
- createRenderTarget,
348
- disposePingPong,
349
- disposeRenderTarget,
350
- generateRenderTargetMipMaps,
351
- readRenderTargetPixels,
352
- resizePingPong,
353
- resizeRenderTarget
354
- };
355
- //# sourceMappingURL=render-target.js.map
554
+ //# sourceMappingURL=render-target.js.map