@gjsify/webgl 0.1.1 → 0.1.3

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 (59) hide show
  1. package/lib/esm/canvas-webgl-widget.js +12 -0
  2. package/lib/esm/conformance/attribs.spec.js +296 -0
  3. package/lib/esm/conformance/buffers.spec.js +203 -0
  4. package/lib/esm/conformance/context.spec.js +302 -0
  5. package/lib/esm/conformance/programs.spec.js +468 -0
  6. package/lib/esm/conformance/rendering-basic.spec.js +136 -0
  7. package/lib/esm/conformance/rendering.spec.js +424 -0
  8. package/lib/esm/conformance/setup.js +36 -0
  9. package/lib/esm/conformance/state.spec.js +348 -0
  10. package/lib/esm/conformance/textures.spec.js +354 -0
  11. package/lib/esm/conformance/uniforms.spec.js +305 -0
  12. package/lib/esm/conformance-test.js +23 -0
  13. package/lib/esm/extensions/ext-color-buffer-float.js +13 -0
  14. package/lib/esm/extensions/ext-color-buffer-half-float.js +13 -0
  15. package/lib/esm/extensions/oes-texture-half-float.js +19 -0
  16. package/lib/esm/index.js +5 -0
  17. package/lib/esm/test-utils.js +65 -0
  18. package/lib/esm/test.js +2 -2
  19. package/lib/esm/webgl-buffer.js +1 -1
  20. package/lib/esm/webgl-context-base.js +3371 -0
  21. package/lib/esm/webgl-framebuffer.js +1 -1
  22. package/lib/esm/webgl-program.js +1 -1
  23. package/lib/esm/webgl-renderbuffer.js +1 -1
  24. package/lib/esm/webgl-rendering-context.js +95 -3253
  25. package/lib/esm/webgl-shader.js +2 -1
  26. package/lib/esm/webgl-texture.js +1 -1
  27. package/lib/esm/{index.spec.js → webgl1.spec.js} +2 -2
  28. package/lib/esm/webgl2-rendering-context.js +617 -27
  29. package/lib/esm/webgl2.spec.js +573 -1
  30. package/lib/types/conformance/setup.d.ts +14 -0
  31. package/lib/types/extensions/ext-blend-minmax.d.ts +2 -2
  32. package/lib/types/extensions/ext-color-buffer-float.d.ts +4 -0
  33. package/lib/types/extensions/ext-color-buffer-half-float.d.ts +4 -0
  34. package/lib/types/extensions/ext-texture-filter-anisotropic.d.ts +2 -2
  35. package/lib/types/extensions/oes-element-index-unit.d.ts +2 -2
  36. package/lib/types/extensions/oes-standard-derivatives.d.ts +2 -2
  37. package/lib/types/extensions/oes-texture-float-linear.d.ts +2 -2
  38. package/lib/types/extensions/oes-texture-float.d.ts +2 -2
  39. package/lib/types/extensions/oes-texture-half-float.d.ts +5 -0
  40. package/lib/types/extensions/stackgl-destroy-context.d.ts +3 -3
  41. package/lib/types/extensions/stackgl-resize-drawing-buffer.d.ts +3 -3
  42. package/lib/types/index.d.ts +1 -0
  43. package/lib/types/test-utils.d.ts +24 -0
  44. package/lib/types/types/extension.d.ts +2 -2
  45. package/lib/types/utils.d.ts +9 -10
  46. package/lib/types/webgl-buffer.d.ts +3 -3
  47. package/lib/types/webgl-context-base.d.ts +267 -0
  48. package/lib/types/webgl-framebuffer.d.ts +3 -3
  49. package/lib/types/webgl-program.d.ts +3 -3
  50. package/lib/types/webgl-renderbuffer.d.ts +3 -3
  51. package/lib/types/webgl-rendering-context.d.ts +5 -250
  52. package/lib/types/webgl-shader.d.ts +4 -3
  53. package/lib/types/webgl-texture-unit.d.ts +3 -3
  54. package/lib/types/webgl-texture.d.ts +3 -3
  55. package/lib/types/webgl-vertex-attribute.d.ts +5 -5
  56. package/lib/types/webgl2-rendering-context.d.ts +95 -6
  57. package/package.json +13 -11
  58. package/prebuilds/linux-x86_64/Gwebgl-0.1.typelib +0 -0
  59. package/prebuilds/linux-x86_64/libgwebgl.so +0 -0
@@ -1,5 +1,6 @@
1
1
  import Gwebgl from "@girs/gwebgl-0.1";
2
- import { WebGLRenderingContext } from "./webgl-rendering-context.js";
2
+ import GdkPixbuf from "gi://GdkPixbuf?version=2.0";
3
+ import { WebGLContextBase } from "./webgl-context-base.js";
3
4
  import { WebGLQuery } from "./webgl-query.js";
4
5
  import { WebGLSampler } from "./webgl-sampler.js";
5
6
  import { WebGLSync } from "./webgl-sync.js";
@@ -8,9 +9,10 @@ import { WebGLVertexArrayObject } from "./webgl-vertex-array-object.js";
8
9
  import { WebGLActiveInfo } from "./webgl-active-info.js";
9
10
  import { WebGLTexture } from "./webgl-texture.js";
10
11
  import { WebGLRenderbuffer } from "./webgl-renderbuffer.js";
11
- import { Uint8ArrayToVariant, arrayToUint8Array, vertexCount } from "./utils.js";
12
+ import { WebGLFramebuffer } from "./webgl-framebuffer.js";
13
+ import { Uint8ArrayToVariant, arrayToUint8Array, vertexCount, convertPixels, extractImageData, checkObject } from "./utils.js";
12
14
  import { warnNotImplemented } from "@gjsify/utils";
13
- class WebGL2RenderingContext extends WebGLRenderingContext {
15
+ class WebGL2RenderingContext extends WebGLContextBase {
14
16
  constructor(canvas, options = {}) {
15
17
  super(canvas, options);
16
18
  this._queries = {};
@@ -18,20 +20,118 @@ class WebGL2RenderingContext extends WebGLRenderingContext {
18
20
  this._transformFeedbacks = {};
19
21
  this._vertexArrayObjects = {};
20
22
  this._syncs = {};
23
+ this._activeReadFramebuffer = null;
24
+ this._activeDrawFramebuffer = null;
21
25
  this._native2 = new Gwebgl.WebGL2RenderingContext({});
26
+ this._init();
27
+ }
28
+ get _gl() {
29
+ return this._native2;
22
30
  }
23
31
  _getGlslVersion(es) {
24
32
  return es ? "300 es" : "130";
25
33
  }
26
34
  // ─── WebGL2 overrides for WebGL1 validation that's too strict ─────────
35
+ /**
36
+ * WebGL2 delegates framebuffer completeness to the native GL driver.
37
+ * Called by _framebufferOk() before draw calls and by _updateFramebufferAttachments.
38
+ * The base class version uses JS-side format whitelists that reject valid WebGL2 formats.
39
+ */
27
40
  /** WebGL2 allows COLOR_ATTACHMENT1–15 as framebuffer attachment points. */
28
41
  _validFramebufferAttachment(attachment) {
29
42
  if (super._validFramebufferAttachment(attachment)) return true;
30
43
  return attachment >= 36065 && attachment <= 36079;
31
44
  }
45
+ static {
46
+ // ─── MRT: native COLOR_ATTACHMENT0–15 support ────────────────────────
47
+ this._WGL2_ALL_COLOR_ATTACHMENTS = [
48
+ 36064,
49
+ 36065,
50
+ 36066,
51
+ 36067,
52
+ 36068,
53
+ 36069,
54
+ 36070,
55
+ 36071,
56
+ 36072,
57
+ 36073,
58
+ 36074,
59
+ 36075,
60
+ 36076,
61
+ 36077,
62
+ 36078,
63
+ 36079
64
+ ];
65
+ }
66
+ _getColorAttachments() {
67
+ return WebGL2RenderingContext._WGL2_ALL_COLOR_ATTACHMENTS;
68
+ }
69
+ /**
70
+ * WebGL2 extends the base-class framebuffer completeness pre-check to
71
+ * accept WebGL2-specific formats that the WebGL1 whitelist rejects.
72
+ *
73
+ * NOTE: This is called by _updateFramebufferAttachments BEFORE the native
74
+ * GL attachments are set, so we must NOT query glCheckFramebufferStatus
75
+ * here (it would see an empty FBO and always return INCOMPLETE).
76
+ * Instead we extend the JS-side format whitelist to cover WebGL2 formats.
77
+ */
78
+ _preCheckFramebufferStatus(framebuffer) {
79
+ const attachments = framebuffer._attachments;
80
+ let bestWidth = 0;
81
+ let bestHeight = 0;
82
+ const allEnums = [
83
+ this.COLOR_ATTACHMENT0,
84
+ this.DEPTH_ATTACHMENT,
85
+ this.STENCIL_ATTACHMENT,
86
+ this.DEPTH_STENCIL_ATTACHMENT,
87
+ ...WebGL2RenderingContext._WGL2_ALL_COLOR_ATTACHMENTS
88
+ ];
89
+ for (const enumVal of allEnums) {
90
+ const attach = attachments[enumVal];
91
+ if (!attach) continue;
92
+ if (attach instanceof WebGLTexture) {
93
+ const level = framebuffer._attachmentLevel[enumVal] ?? 0;
94
+ const w = attach._levelWidth[level] ?? 0;
95
+ const h = attach._levelHeight[level] ?? 0;
96
+ if (w > 0 && h > 0) {
97
+ bestWidth = w;
98
+ bestHeight = h;
99
+ break;
100
+ }
101
+ } else if (attach instanceof WebGLRenderbuffer) {
102
+ if (attach._width > 0 && attach._height > 0) {
103
+ bestWidth = attach._width;
104
+ bestHeight = attach._height;
105
+ break;
106
+ }
107
+ }
108
+ }
109
+ if (bestWidth > 0 && bestHeight > 0) {
110
+ framebuffer._width = bestWidth;
111
+ framebuffer._height = bestHeight;
112
+ return this.FRAMEBUFFER_COMPLETE;
113
+ }
114
+ return this.FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
115
+ }
32
116
  /**
33
- * Apply COLOR_ATTACHMENT1–15 to the native GL FBO when they have attachments.
34
- * The base class only knows about CA0, DEPTH, STENCIL, DEPTH_STENCIL.
117
+ * WebGL2 completely replaces the base class framebuffer attachment flow.
118
+ *
119
+ * The base class flow is: (1) pre-check formats in JS → (2) set native
120
+ * attachments only if pre-check passes. This is wrong for WebGL2 because
121
+ * the JS pre-check uses WebGL1 format whitelists that reject valid WebGL2
122
+ * formats.
123
+ *
124
+ * WebGL2 flow: (1) always set native attachments first → (2) query native
125
+ * glCheckFramebufferStatus to determine completeness. This delegates all
126
+ * format validation to the native GL driver, matching browser behavior.
127
+ *
128
+ * Also handles COLOR_ATTACHMENT1–15 (WebGL2 MRT) that the base class
129
+ * doesn't know about.
130
+ */
131
+ /**
132
+ * Apply COLOR_ATTACHMENT1–15 to the native GL FBO (WebGL2 MRT).
133
+ * The base class handles CA0, DEPTH, STENCIL, DEPTH_STENCIL and calls
134
+ * _preCheckFramebufferStatus (which we override to query native GL).
35
135
  */
36
136
  _updateFramebufferAttachments(framebuffer) {
37
137
  super._updateFramebufferAttachments(framebuffer);
@@ -43,11 +143,11 @@ class WebGL2RenderingContext extends WebGLRenderingContext {
43
143
  if (attachment instanceof WebGLTexture) {
44
144
  const face = framebuffer._attachmentFace[attachmentEnum] || this.TEXTURE_2D;
45
145
  const level = framebuffer._attachmentLevel[attachmentEnum] ?? 0;
46
- this._native.framebufferTexture2D(this.FRAMEBUFFER, attachmentEnum, face, attachment._ | 0, level | 0);
146
+ this._gl.framebufferTexture2D(this.FRAMEBUFFER, attachmentEnum, face, attachment._ | 0, level | 0);
47
147
  } else if (attachment instanceof WebGLRenderbuffer) {
48
- this._native.framebufferRenderbuffer(this.FRAMEBUFFER, attachmentEnum, this.RENDERBUFFER, attachment._ | 0);
148
+ this._gl.framebufferRenderbuffer(this.FRAMEBUFFER, attachmentEnum, this.RENDERBUFFER, attachment._ | 0);
49
149
  } else {
50
- this._native.framebufferTexture2D(this.FRAMEBUFFER, attachmentEnum, this.TEXTURE_2D, 0, 0);
150
+ this._gl.framebufferTexture2D(this.FRAMEBUFFER, attachmentEnum, this.TEXTURE_2D, 0, 0);
51
151
  }
52
152
  }
53
153
  }
@@ -56,11 +156,63 @@ class WebGL2RenderingContext extends WebGLRenderingContext {
56
156
  const isWebGL2Target = target === 35345 || target === 35982 || target === 36662 || target === 36663;
57
157
  if (isWebGL2Target) {
58
158
  const id = buffer ? buffer._ | 0 : 0;
59
- this._native.bindBuffer(target, id);
159
+ this._gl.bindBuffer(target, id);
60
160
  return;
61
161
  }
62
162
  super.bindBuffer(target, buffer);
63
163
  }
164
+ /**
165
+ * WebGL2 adds READ_FRAMEBUFFER (0x8CA8) and DRAW_FRAMEBUFFER (0x8CA9) targets.
166
+ * The base class only accepts FRAMEBUFFER; this override handles the two new targets
167
+ * and keeps _activeReadFramebuffer / _activeDrawFramebuffer in sync.
168
+ */
169
+ bindFramebuffer(target, framebuffer) {
170
+ if (target === 36008 || target === 36009) {
171
+ if (!checkObject(framebuffer)) {
172
+ throw new TypeError("bindFramebuffer(GLenum, WebGLFramebuffer)");
173
+ }
174
+ if (framebuffer && framebuffer._pendingDelete) return;
175
+ if (framebuffer && !this._checkWrapper(framebuffer, WebGLFramebuffer)) return;
176
+ const id = framebuffer ? framebuffer._ | 0 : this._gtkFboId;
177
+ this._gl.bindFramebuffer(target, id);
178
+ if (target === 36008) {
179
+ const prev = this._activeReadFramebuffer;
180
+ if (prev !== framebuffer) {
181
+ if (prev) {
182
+ prev._refCount -= 1;
183
+ prev._checkDelete();
184
+ }
185
+ if (framebuffer) framebuffer._refCount += 1;
186
+ }
187
+ this._activeReadFramebuffer = framebuffer;
188
+ } else {
189
+ const prev = this._activeDrawFramebuffer;
190
+ if (prev !== framebuffer) {
191
+ if (prev) {
192
+ prev._refCount -= 1;
193
+ prev._checkDelete();
194
+ }
195
+ if (framebuffer) framebuffer._refCount += 1;
196
+ }
197
+ this._activeDrawFramebuffer = framebuffer;
198
+ this._activeFramebuffer = framebuffer;
199
+ }
200
+ return;
201
+ }
202
+ super.bindFramebuffer(this.FRAMEBUFFER, framebuffer);
203
+ this._activeReadFramebuffer = framebuffer;
204
+ this._activeDrawFramebuffer = framebuffer;
205
+ }
206
+ /** WebGL2 also unbinds from read/draw framebuffer slots when deleting. */
207
+ deleteFramebuffer(framebuffer) {
208
+ if (this._activeReadFramebuffer === framebuffer) {
209
+ this.bindFramebuffer(36008, null);
210
+ }
211
+ if (this._activeDrawFramebuffer === framebuffer) {
212
+ this.bindFramebuffer(36009, null);
213
+ }
214
+ super.deleteFramebuffer(framebuffer);
215
+ }
64
216
  /** WebGL2 adds READ/COPY buffer usages and additional buffer targets. */
65
217
  bufferData(target, dataOrSize, usage) {
66
218
  const isWebGL2Target = target === 35345 || target === 35982 || target === 36662 || target === 36663;
@@ -68,37 +220,60 @@ class WebGL2RenderingContext extends WebGLRenderingContext {
68
220
  const remappedUsage = isReadOrCopy ? this.STATIC_DRAW : usage;
69
221
  if (isWebGL2Target) {
70
222
  if (typeof dataOrSize === "number") {
71
- if (dataOrSize >= 0) this._native.bufferDataSizeOnly(target, dataOrSize, remappedUsage);
223
+ if (dataOrSize >= 0) this._gl.bufferDataSizeOnly(target, dataOrSize, remappedUsage);
72
224
  } else if (dataOrSize !== null && typeof dataOrSize === "object") {
73
225
  const u8Data = arrayToUint8Array(dataOrSize);
74
- this._native.bufferData(target, Uint8ArrayToVariant(u8Data), remappedUsage);
226
+ this._gl.bufferData(target, Uint8ArrayToVariant(u8Data), remappedUsage);
75
227
  }
76
228
  return;
77
229
  }
78
230
  super.bufferData(target, dataOrSize, remappedUsage);
79
231
  }
232
+ /** WebGL2 adds UNIFORM_BUFFER, TRANSFORM_FEEDBACK_BUFFER, COPY_READ/WRITE targets. */
233
+ bufferSubData(target, offset, data) {
234
+ const isWebGL2Target = target === 35345 || target === 35982 || target === 36662 || target === 36663;
235
+ if (isWebGL2Target) {
236
+ if (offset < 0) {
237
+ this.setError(this.INVALID_VALUE);
238
+ return;
239
+ }
240
+ if (!data) {
241
+ this.setError(this.INVALID_VALUE);
242
+ return;
243
+ }
244
+ const u8Data = arrayToUint8Array(data);
245
+ this._gl.bufferSubData(target, offset, Uint8ArrayToVariant(u8Data));
246
+ return;
247
+ }
248
+ super.bufferSubData(target, offset, data);
249
+ }
80
250
  /** WebGL2 adds TEXTURE_3D and TEXTURE_2D_ARRAY target support. */
81
251
  bindTexture(target, texture) {
82
252
  if (target === 32879 || target === 35866) {
83
253
  const id = texture ? texture._ | 0 : 0;
84
- this._native.bindTexture(target, id);
254
+ this._gl.bindTexture(target, id);
85
255
  if (texture) texture._binding = target;
86
256
  return;
87
257
  }
88
258
  super.bindTexture(target, texture);
89
259
  }
90
- /** WebGL2 adds TEXTURE_3D/TEXTURE_2D_ARRAY targets and TEXTURE_WRAP_R pname. */
260
+ /** WebGL2 adds TEXTURE_3D/TEXTURE_2D_ARRAY targets and many new pnames. */
91
261
  texParameteri(target, pname, param) {
92
262
  if (target === 32879 || target === 35866) {
93
- this._native.texParameteri(target, pname, param);
263
+ this._gl.texParameteri(target, pname, param);
94
264
  return;
95
265
  }
96
- if (pname === 32882) {
97
- this._native.texParameteri(target, pname, param);
266
+ const isWebGL2Pname = pname === 32882 || pname === 34892 || pname === 34893 || pname === 33084 || pname === 33085 || pname === 33083 || pname === 33082;
267
+ if (isWebGL2Pname) {
268
+ this._gl.texParameteri(target, pname, param);
98
269
  return;
99
270
  }
100
271
  super.texParameteri(target, pname, param);
101
272
  }
273
+ /**
274
+ * In WebGL2/GLES3 the attribute-0 requirement from WebGL1 does not apply.
275
+ * Override drawArrays to skip the attrib0 hack and call glDrawArrays directly.
276
+ */
102
277
  /**
103
278
  * In WebGL2/GLES3 the attribute-0 requirement from WebGL1 does not apply.
104
279
  * Override drawArrays to skip the attrib0 hack and call glDrawArrays directly.
@@ -116,8 +291,82 @@ class WebGL2RenderingContext extends WebGLRenderingContext {
116
291
  }
117
292
  if (!this._framebufferOk()) return;
118
293
  if (count === 0) return;
119
- if (this._checkVertexAttribState(count + first - 1 >>> 0)) {
120
- this._native.drawArrays(mode, first, rc);
294
+ if (!this._checkVertexAttribState(count + first - 1 >>> 0)) return;
295
+ this._native2.drawArrays(mode, first, rc);
296
+ }
297
+ /**
298
+ * In WebGL2, UNSIGNED_INT element indices are a core feature — no extension needed.
299
+ * Override drawElements to skip the oes_element_index_uint extension check.
300
+ */
301
+ drawElements(mode = 0, count = 0, type = 0, offset = 0) {
302
+ if (count < 0 || offset < 0) {
303
+ this.setError(this.INVALID_VALUE);
304
+ return;
305
+ }
306
+ if (!this._checkStencilState()) return;
307
+ const elementBuffer = this._vertexObjectState._elementArrayBufferBinding;
308
+ if (!elementBuffer) {
309
+ this.setError(this.INVALID_OPERATION);
310
+ return;
311
+ }
312
+ let elementData = null;
313
+ let adjustedOffset = offset;
314
+ if (type === this.UNSIGNED_SHORT) {
315
+ if (adjustedOffset % 2) {
316
+ this.setError(this.INVALID_OPERATION);
317
+ return;
318
+ }
319
+ adjustedOffset >>= 1;
320
+ elementData = new Uint16Array(elementBuffer._elements.buffer);
321
+ } else if (type === this.UNSIGNED_INT) {
322
+ if (adjustedOffset % 4) {
323
+ this.setError(this.INVALID_OPERATION);
324
+ return;
325
+ }
326
+ adjustedOffset >>= 2;
327
+ elementData = new Uint32Array(elementBuffer._elements.buffer);
328
+ } else if (type === this.UNSIGNED_BYTE) {
329
+ elementData = elementBuffer._elements;
330
+ } else {
331
+ this.setError(this.INVALID_ENUM);
332
+ return;
333
+ }
334
+ let reducedCount = count;
335
+ switch (mode) {
336
+ case this.TRIANGLES:
337
+ if (count % 3) reducedCount -= count % 3;
338
+ break;
339
+ case this.LINES:
340
+ if (count % 2) reducedCount -= count % 2;
341
+ break;
342
+ case this.POINTS:
343
+ break;
344
+ case this.LINE_LOOP:
345
+ case this.LINE_STRIP:
346
+ if (count < 2) {
347
+ this.setError(this.INVALID_OPERATION);
348
+ return;
349
+ }
350
+ break;
351
+ case this.TRIANGLE_FAN:
352
+ case this.TRIANGLE_STRIP:
353
+ if (count < 3) {
354
+ this.setError(this.INVALID_OPERATION);
355
+ return;
356
+ }
357
+ break;
358
+ default:
359
+ this.setError(this.INVALID_ENUM);
360
+ return;
361
+ }
362
+ if (!this._framebufferOk()) return;
363
+ if (count === 0) return;
364
+ let maxIndex = 0;
365
+ for (let i = adjustedOffset; i < adjustedOffset + reducedCount; ++i) {
366
+ if (i < elementData.length && elementData[i] > maxIndex) maxIndex = elementData[i];
367
+ }
368
+ if (this._checkVertexAttribState(maxIndex)) {
369
+ this._native2.drawElements(mode, reducedCount, type, offset);
121
370
  }
122
371
  }
123
372
  // ─── Vertex Array Objects ─────────────────────────────────────────────
@@ -322,19 +571,238 @@ class WebGL2RenderingContext extends WebGLRenderingContext {
322
571
  }
323
572
  texStorage2D(target, levels, internalformat, width, height) {
324
573
  this._native2.texStorage2D(target, levels, internalformat, width, height);
574
+ const texture = this._getTexImage(target);
575
+ if (texture) {
576
+ for (let lvl = 0; lvl < levels; lvl++) {
577
+ texture._levelWidth[lvl] = Math.max(1, width >> lvl);
578
+ texture._levelHeight[lvl] = Math.max(1, height >> lvl);
579
+ }
580
+ texture._format = this.RGBA;
581
+ texture._type = this.UNSIGNED_BYTE;
582
+ }
325
583
  }
326
584
  texStorage3D(target, levels, internalformat, width, height, depth) {
327
585
  this._native2.texStorage3D(target, levels, internalformat, width, height, depth);
328
586
  }
587
+ texImage2D(target = 0, level = 0, internalFormat = 0, formatOrWidth = 0, typeOrHeight = 0, sourceOrBorder = 0, _format = 0, type = 0, pixels) {
588
+ let width = 0;
589
+ let height = 0;
590
+ let format = 0;
591
+ let border = 0;
592
+ if (arguments.length === 6) {
593
+ type = typeOrHeight;
594
+ format = formatOrWidth;
595
+ if (sourceOrBorder instanceof GdkPixbuf.Pixbuf) {
596
+ const pixbuf = sourceOrBorder;
597
+ width = pixbuf.get_width();
598
+ height = pixbuf.get_height();
599
+ pixels = pixbuf.get_pixels();
600
+ } else {
601
+ const imageData = extractImageData(sourceOrBorder);
602
+ if (imageData == null) {
603
+ throw new TypeError("texImage2D(GLenum, GLint, GLenum, GLint, GLenum, GLenum, ImageData | HTMLImageElement | HTMLCanvasElement | HTMLVideoElement)");
604
+ }
605
+ width = imageData.width;
606
+ height = imageData.height;
607
+ pixels = imageData.data;
608
+ }
609
+ } else if (arguments.length >= 9) {
610
+ width = formatOrWidth;
611
+ height = typeOrHeight;
612
+ border = sourceOrBorder;
613
+ format = _format;
614
+ }
615
+ const texture = this._getTexImage(target);
616
+ if (!texture) {
617
+ this.setError(this.INVALID_OPERATION);
618
+ return;
619
+ }
620
+ let data = convertPixels(pixels);
621
+ if (this._unpackFlipY && data && width > 0 && height > 0) {
622
+ const pixelSize = this._computePixelSize(type, format);
623
+ if (pixelSize > 0) {
624
+ const rowStride = this._computeRowStride(width, pixelSize);
625
+ const flipped = new Uint8Array(data.length);
626
+ for (let row = 0; row < height; row++) {
627
+ const srcOffset = row * rowStride;
628
+ const dstOffset = (height - 1 - row) * rowStride;
629
+ flipped.set(data.subarray(srcOffset, srcOffset + rowStride), dstOffset);
630
+ }
631
+ data = flipped;
632
+ }
633
+ }
634
+ this._saveError();
635
+ this._gl.texImage2D(target, level, internalFormat, width, height, border, format, type, Uint8ArrayToVariant(data));
636
+ const error = this.getError();
637
+ this._restoreError(error);
638
+ if (error !== this.NO_ERROR) return;
639
+ texture._levelWidth[level] = width;
640
+ texture._levelHeight[level] = height;
641
+ texture._format = format;
642
+ texture._type = type;
643
+ const activeFramebuffer = this._activeFramebuffer;
644
+ if (activeFramebuffer) {
645
+ let needsUpdate = false;
646
+ const attachments = this._getAttachments();
647
+ for (let i = 0; i < attachments.length; ++i) {
648
+ if (activeFramebuffer._attachments[attachments[i]] === texture) {
649
+ needsUpdate = true;
650
+ break;
651
+ }
652
+ }
653
+ if (needsUpdate && this._activeFramebuffer) {
654
+ this._updateFramebufferAttachments(this._activeFramebuffer);
655
+ }
656
+ }
657
+ }
658
+ texSubImage2D(target = 0, level = 0, xoffset = 0, yoffset = 0, formatOrWidth = 0, typeOrHeight = 0, sourceOrFormat = 0, type = 0, pixels) {
659
+ let width = 0;
660
+ let height = 0;
661
+ let format = 0;
662
+ if (arguments.length === 7) {
663
+ type = typeOrHeight;
664
+ format = formatOrWidth;
665
+ if (sourceOrFormat instanceof GdkPixbuf.Pixbuf) {
666
+ const pixbuf = sourceOrFormat;
667
+ width = pixbuf.get_width();
668
+ height = pixbuf.get_height();
669
+ pixels = pixbuf.get_pixels();
670
+ } else {
671
+ const imageData = extractImageData(sourceOrFormat);
672
+ if (imageData == null) {
673
+ throw new TypeError("texSubImage2D(GLenum, GLint, GLint, GLint, GLenum, GLenum, ImageData | HTMLImageElement | HTMLCanvasElement | HTMLVideoElement)");
674
+ }
675
+ width = imageData.width;
676
+ height = imageData.height;
677
+ pixels = imageData.data;
678
+ }
679
+ } else {
680
+ width = formatOrWidth;
681
+ height = typeOrHeight;
682
+ format = sourceOrFormat;
683
+ }
684
+ const texture = this._getTexImage(target);
685
+ if (!texture) {
686
+ this.setError(this.INVALID_OPERATION);
687
+ return;
688
+ }
689
+ let data = convertPixels(pixels);
690
+ if (!data) {
691
+ this.setError(this.INVALID_OPERATION);
692
+ return;
693
+ }
694
+ if (this._unpackFlipY && data && width > 0 && height > 0) {
695
+ const pixelSize = this._computePixelSize(type, format);
696
+ if (pixelSize > 0) {
697
+ const rowStride = this._computeRowStride(width, pixelSize);
698
+ const flipped = new Uint8Array(data.length);
699
+ for (let row = 0; row < height; row++) {
700
+ const srcOffset = row * rowStride;
701
+ const dstOffset = (height - 1 - row) * rowStride;
702
+ flipped.set(data.subarray(srcOffset, srcOffset + rowStride), dstOffset);
703
+ }
704
+ data = flipped;
705
+ }
706
+ }
707
+ this._gl.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, Uint8ArrayToVariant(data));
708
+ }
329
709
  framebufferTextureLayer(target, attachment, texture, level, layer) {
330
710
  this._native2.framebufferTextureLayer(target, attachment, texture ? texture._ : 0, level, layer);
331
711
  }
332
712
  // ─── Instancing & Advanced Draw ───────────────────────────────────────
333
713
  drawArraysInstanced(mode, first, count, instanceCount) {
334
- this._native2.drawArraysInstanced(mode, first, count, instanceCount);
714
+ if (first < 0 || count < 0 || instanceCount < 0) {
715
+ this.setError(this.INVALID_VALUE);
716
+ return;
717
+ }
718
+ if (!this._checkStencilState()) return;
719
+ const rc = vertexCount(this, mode, count);
720
+ if (rc < 0) {
721
+ this.setError(this.INVALID_ENUM);
722
+ return;
723
+ }
724
+ if (!this._framebufferOk()) return;
725
+ if (count === 0 || instanceCount === 0) return;
726
+ if (!this._checkVertexAttribState(count + first - 1 >>> 0)) return;
727
+ this._native2.drawArraysInstanced(mode, first, rc, instanceCount);
335
728
  }
336
729
  drawElementsInstanced(mode, count, type, offset, instanceCount) {
337
- this._native2.drawElementsInstanced(mode, count, type, offset, instanceCount);
730
+ if (count < 0 || offset < 0 || instanceCount < 0) {
731
+ this.setError(this.INVALID_VALUE);
732
+ return;
733
+ }
734
+ if (!this._checkStencilState()) return;
735
+ const elementBuffer = this._vertexObjectState._elementArrayBufferBinding;
736
+ if (!elementBuffer) {
737
+ this.setError(this.INVALID_OPERATION);
738
+ return;
739
+ }
740
+ let elementData = null;
741
+ let adjustedOffset = offset;
742
+ if (type === this.UNSIGNED_SHORT) {
743
+ if (adjustedOffset % 2) {
744
+ this.setError(this.INVALID_OPERATION);
745
+ return;
746
+ }
747
+ adjustedOffset >>= 1;
748
+ elementData = new Uint16Array(elementBuffer._elements.buffer);
749
+ } else if (type === this.UNSIGNED_INT) {
750
+ if (adjustedOffset % 4) {
751
+ this.setError(this.INVALID_OPERATION);
752
+ return;
753
+ }
754
+ adjustedOffset >>= 2;
755
+ elementData = new Uint32Array(elementBuffer._elements.buffer);
756
+ } else if (type === this.UNSIGNED_BYTE) {
757
+ elementData = elementBuffer._elements;
758
+ } else {
759
+ this.setError(this.INVALID_ENUM);
760
+ return;
761
+ }
762
+ let reducedCount = count;
763
+ switch (mode) {
764
+ case this.TRIANGLES:
765
+ if (count % 3) reducedCount -= count % 3;
766
+ break;
767
+ case this.LINES:
768
+ if (count % 2) reducedCount -= count % 2;
769
+ break;
770
+ case this.POINTS:
771
+ break;
772
+ case this.LINE_LOOP:
773
+ case this.LINE_STRIP:
774
+ if (count < 2) {
775
+ this.setError(this.INVALID_OPERATION);
776
+ return;
777
+ }
778
+ break;
779
+ case this.TRIANGLE_FAN:
780
+ case this.TRIANGLE_STRIP:
781
+ if (count < 3) {
782
+ this.setError(this.INVALID_OPERATION);
783
+ return;
784
+ }
785
+ break;
786
+ default:
787
+ this.setError(this.INVALID_ENUM);
788
+ return;
789
+ }
790
+ if (!this._framebufferOk()) return;
791
+ if (reducedCount === 0 || instanceCount === 0) {
792
+ this._checkVertexAttribState(0);
793
+ return;
794
+ }
795
+ if (reducedCount + adjustedOffset >>> 0 > elementData.length) {
796
+ this.setError(this.INVALID_OPERATION);
797
+ return;
798
+ }
799
+ let maxIndex = 0;
800
+ for (let i = adjustedOffset; i < adjustedOffset + reducedCount; ++i) {
801
+ if (elementData[i] > maxIndex) maxIndex = elementData[i];
802
+ }
803
+ if (this._checkVertexAttribState(maxIndex)) {
804
+ this._native2.drawElementsInstanced(mode, reducedCount, type, offset, instanceCount);
805
+ }
338
806
  }
339
807
  vertexAttribDivisor(index, divisor) {
340
808
  this._native2.vertexAttribDivisor(index, divisor);
@@ -343,10 +811,19 @@ class WebGL2RenderingContext extends WebGLRenderingContext {
343
811
  this._native2.vertexAttribIPointer(index, size, type, stride, offset);
344
812
  }
345
813
  drawBuffers(buffers) {
346
- this._native2.drawBuffers(Array.from(buffers));
814
+ const mapped = buffers.map((b) => b === 1029 ? this.COLOR_ATTACHMENT0 : b);
815
+ this._native2.drawBuffers(Array.from(mapped));
347
816
  }
348
817
  drawRangeElements(mode, start, end, count, type, offset) {
349
- this._native2.drawRangeElements(mode, start, end, count, type, offset);
818
+ if (count < 0 || offset < 0) {
819
+ this.setError(this.INVALID_VALUE);
820
+ return;
821
+ }
822
+ if (end < start) {
823
+ this.setError(this.INVALID_VALUE);
824
+ return;
825
+ }
826
+ this.drawElements(mode, count, type, offset);
350
827
  }
351
828
  blitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter) {
352
829
  this._native2.blitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
@@ -360,8 +837,24 @@ class WebGL2RenderingContext extends WebGLRenderingContext {
360
837
  readBuffer(src) {
361
838
  this._native2.readBuffer(src);
362
839
  }
363
- renderbufferStorageMultisample(target, samples, internalformat, width, height) {
364
- this._native2.renderbufferStorageMultisample(target, samples, internalformat, width, height);
840
+ renderbufferStorageMultisample(target, samples, internalFormat, width, height) {
841
+ if (target !== this.RENDERBUFFER) {
842
+ this.setError(this.INVALID_ENUM);
843
+ return;
844
+ }
845
+ const renderbuffer = this._activeRenderbuffer;
846
+ if (!renderbuffer) {
847
+ this.setError(this.INVALID_OPERATION);
848
+ return;
849
+ }
850
+ this._saveError();
851
+ this._native2.renderbufferStorageMultisample(target, samples, internalFormat, width, height);
852
+ const error = this.getError();
853
+ this._restoreError(error);
854
+ if (error !== this.NO_ERROR) return;
855
+ renderbuffer._width = width;
856
+ renderbuffer._height = height;
857
+ renderbuffer._format = internalFormat;
365
858
  }
366
859
  // ─── Unsigned Integer Uniforms ────────────────────────────────────────
367
860
  uniform1ui(location, v0) {
@@ -439,7 +932,7 @@ class WebGL2RenderingContext extends WebGLRenderingContext {
439
932
  const isUintType = type === UINT || type === UVEC2 || type === UVEC3 || type === UVEC4;
440
933
  if (!isUintType) return super.getUniform(program, location);
441
934
  if (!program || !location) return null;
442
- const data = this._native.getUniformi(program._ | 0, location._ | 0);
935
+ const data = this._gl.getUniformi(program._ | 0, location._ | 0);
443
936
  if (!data) return null;
444
937
  if (type === UINT) return data[0] >>> 0;
445
938
  if (type === UVEC2) return new Uint32Array([data[0] >>> 0, data[1] >>> 0]);
@@ -482,6 +975,8 @@ class WebGL2RenderingContext extends WebGLRenderingContext {
482
975
  warnNotImplemented("WebGL2RenderingContext.getParameter(GL_EXTENSIONS)");
483
976
  return "";
484
977
  }
978
+ if (pname === 36006) return this._activeDrawFramebuffer;
979
+ if (pname === 36010) return this._activeReadFramebuffer;
485
980
  switch (pname) {
486
981
  case 36183:
487
982
  // MAX_SAMPLES
@@ -545,8 +1040,6 @@ class WebGL2RenderingContext extends WebGLRenderingContext {
545
1040
  // PIXEL_UNPACK_BUFFER_BINDING
546
1041
  case 3074:
547
1042
  // READ_BUFFER
548
- case 36010:
549
- // READ_FRAMEBUFFER_BINDING
550
1043
  case 32874:
551
1044
  // TEXTURE_BINDING_3D
552
1045
  case 35869:
@@ -585,6 +1078,103 @@ class WebGL2RenderingContext extends WebGLRenderingContext {
585
1078
  const s = this._native2.getStringi(name, index);
586
1079
  return s.length > 0 ? s : null;
587
1080
  }
1081
+ // ─── WebGL2 overrides for format validation ────────────────────────────
1082
+ /**
1083
+ * WebGL2 supports ~30+ renderbuffer formats (R8, RG8, RGBA8, RGBA16F,
1084
+ * DEPTH_COMPONENT24, DEPTH32F_STENCIL8, etc.). The WebGL1 base class
1085
+ * only allows 7 formats. Delegate format validation to native GL.
1086
+ */
1087
+ renderbufferStorage(target, internalFormat, width, height) {
1088
+ if (target !== this.RENDERBUFFER) {
1089
+ this.setError(this.INVALID_ENUM);
1090
+ return;
1091
+ }
1092
+ const renderbuffer = this._activeRenderbuffer;
1093
+ if (!renderbuffer) {
1094
+ this.setError(this.INVALID_OPERATION);
1095
+ return;
1096
+ }
1097
+ if (width < 0 || height < 0) {
1098
+ this.setError(this.INVALID_VALUE);
1099
+ return;
1100
+ }
1101
+ while (this._gl.getError() !== this.NO_ERROR) {
1102
+ }
1103
+ this._gl.renderbufferStorage(target, internalFormat, width, height);
1104
+ if (this._gl.getError() !== this.NO_ERROR) return;
1105
+ renderbuffer._width = width;
1106
+ renderbuffer._height = height;
1107
+ renderbuffer._format = internalFormat;
1108
+ const activeFramebuffer = this._activeFramebuffer;
1109
+ if (activeFramebuffer) {
1110
+ const attachments = this._getAttachments();
1111
+ let needsUpdate = false;
1112
+ for (let i = 0; i < attachments.length; ++i) {
1113
+ if (activeFramebuffer._attachments[attachments[i]] === renderbuffer) {
1114
+ needsUpdate = true;
1115
+ break;
1116
+ }
1117
+ }
1118
+ if (needsUpdate) this._updateFramebufferAttachments(activeFramebuffer);
1119
+ }
1120
+ }
1121
+ /**
1122
+ * WebGL2 makes several WebGL1 extensions part of the core spec.
1123
+ * EXT_color_buffer_float and EXT_color_buffer_half_float are always
1124
+ * available in WebGL2 contexts. Append them if the base class didn't.
1125
+ */
1126
+ getSupportedExtensions() {
1127
+ const exts = super.getSupportedExtensions();
1128
+ const ensure = ["EXT_color_buffer_float", "EXT_color_buffer_half_float", "OES_texture_half_float"];
1129
+ for (const ext of ensure) {
1130
+ if (exts.indexOf(ext) === -1) exts.push(ext);
1131
+ }
1132
+ return exts;
1133
+ }
1134
+ /**
1135
+ * WebGL2 allows reading pixels in many more format/type combinations
1136
+ * than WebGL1's strict RGBA/UNSIGNED_BYTE. Delegate validation to native.
1137
+ */
1138
+ readPixels(x, y, width, height, format, type, pixels) {
1139
+ if (!pixels) return;
1140
+ if (width < 0 || height < 0) {
1141
+ this.setError(this.INVALID_VALUE);
1142
+ return;
1143
+ }
1144
+ if (!this._framebufferOk()) return;
1145
+ const componentCount = format === 6408 || format === 32856 ? 4 : format === 6407 ? 3 : format === 33319 ? 2 : 1;
1146
+ const bytesPerComponent = type === 5126 ? 4 : type === 5131 || type === 36193 ? 2 : type === 5125 || type === 5124 ? 4 : type === 5123 || type === 5122 ? 2 : 1;
1147
+ const byteCount = width * height * componentCount * bytesPerComponent;
1148
+ const pixelData = new Uint8Array(byteCount);
1149
+ this._saveError();
1150
+ const result = this._gl.readPixels(x, y, width, height, format, type, Uint8ArrayToVariant(pixelData));
1151
+ const error = this.getError();
1152
+ this._restoreError(error);
1153
+ if (error !== this.NO_ERROR) return;
1154
+ const src = result && result.length > 0 ? result : pixelData;
1155
+ if (pixels instanceof Uint8Array) {
1156
+ pixels.set(src);
1157
+ } else if (pixels instanceof Float32Array) {
1158
+ const floatView = new Float32Array(src.buffer, 0, pixels.length);
1159
+ pixels.set(floatView);
1160
+ }
1161
+ }
1162
+ // framebufferTexture2D: inherits from base class. WebGL2 allows level>0 for
1163
+ // mipmap attachments, but Three.js only uses level=0. The base class level===0
1164
+ // check is acceptable for now. If needed, override to skip level validation.
1165
+ /**
1166
+ * WebGL2 never blocks draw calls on JS-side framebuffer format checks.
1167
+ * The native GL (Mesa/libepoxy) handles completeness and generates
1168
+ * INVALID_FRAMEBUFFER_OPERATION for truly incomplete FBOs at draw time.
1169
+ * This matches headless-gl's approach: _framebufferOk() always returns true.
1170
+ *
1171
+ * The base class rejects valid WebGL2 formats (RGBA16F/HALF_FLOAT, depth
1172
+ * textures, WebGL2 renderbuffer formats) causing silent rendering failures
1173
+ * for postprocessing effects and environment maps.
1174
+ */
1175
+ _framebufferOk() {
1176
+ return true;
1177
+ }
588
1178
  }
589
1179
  export {
590
1180
  WebGL2RenderingContext