@aethervtt/shard-gpu-webgl2 0.0.0-stage → 0.1.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.
@@ -0,0 +1,949 @@
1
+ import { GL } from "./gl.js";
2
+ const COMPONENTS = {
3
+ [GL.R8]: [1, "unorm"],
4
+ [GL.RG8]: [2, "unorm"],
5
+ [GL.RGBA8]: [4, "unorm"],
6
+ [GL.SRGB8_ALPHA8]: [4, "unorm"],
7
+ [GL.RGB10_A2]: [4, "unorm"],
8
+ [GL.R16F]: [1, "float"],
9
+ [GL.RG16F]: [2, "float"],
10
+ [GL.RGBA16F]: [4, "float"],
11
+ [GL.R32F]: [1, "float"],
12
+ [GL.RG32F]: [2, "float"],
13
+ [GL.RGBA32F]: [4, "float"],
14
+ [GL.R11F_G11F_B10F]: [3, "float"],
15
+ [GL.R8UI]: [1, "uint"],
16
+ [GL.RGBA8UI]: [4, "uint"],
17
+ [GL.R16UI]: [1, "uint"],
18
+ [GL.R32UI]: [1, "uint"],
19
+ [GL.RG32UI]: [2, "uint"],
20
+ [GL.RGBA32UI]: [4, "uint"],
21
+ [GL.DEPTH_COMPONENT16]: [1, "depth"],
22
+ [GL.DEPTH_COMPONENT24]: [1, "depth"],
23
+ [GL.DEPTH_COMPONENT32F]: [1, "depth"],
24
+ [GL.DEPTH24_STENCIL8]: [1, "depth"],
25
+ [GL.DEPTH32F_STENCIL8]: [1, "depth"],
26
+ [GL.STENCIL_INDEX8]: [1, "stencil"]
27
+ };
28
+ function formatComponents(format) {
29
+ switch (format) {
30
+ case GL.RED:
31
+ case GL.RED_INTEGER:
32
+ case GL.DEPTH_COMPONENT:
33
+ return 1;
34
+ case GL.RG:
35
+ case GL.RG_INTEGER:
36
+ return 2;
37
+ case GL.RGB:
38
+ return 3;
39
+ default:
40
+ return 4;
41
+ }
42
+ }
43
+ function typeSize(type) {
44
+ switch (type) {
45
+ case GL.UNSIGNED_BYTE:
46
+ case GL.BYTE:
47
+ return 1;
48
+ case GL.UNSIGNED_SHORT:
49
+ case GL.SHORT:
50
+ case GL.HALF_FLOAT:
51
+ return 2;
52
+ default:
53
+ return 4;
54
+ }
55
+ }
56
+ const packed = (type) => type === GL.UNSIGNED_INT_10F_11F_11F_REV || type === GL.UNSIGNED_INT_2_10_10_10_REV || type === GL.UNSIGNED_INT_24_8;
57
+ function halfToFloat(h) {
58
+ const s = h & 32768 ? -1 : 1;
59
+ const e = h >> 10 & 31;
60
+ const m = h & 1023;
61
+ if (e === 0) return s * m * 2 ** -24;
62
+ if (e === 31) return m ? Number.NaN : s * Number.POSITIVE_INFINITY;
63
+ return s * (1 + m / 1024) * 2 ** (e - 15);
64
+ }
65
+ function ufloat(bits, mantissa) {
66
+ const e = bits >>> mantissa;
67
+ const m = bits & (1 << mantissa) - 1;
68
+ if (e === 0) return m / (1 << mantissa) * 2 ** -14;
69
+ if (e === 31) return m ? Number.NaN : Number.POSITIVE_INFINITY;
70
+ return (1 + m / (1 << mantissa)) * 2 ** (e - 15);
71
+ }
72
+ class FakeGl {
73
+ canvas;
74
+ /** Method names, in call order. */
75
+ calls = [];
76
+ draws = [];
77
+ /** Whether draws are recorded (off for allocation checks: a record is an allocation). */
78
+ recordDraws = true;
79
+ /** GL errors to hand out from getError. */
80
+ errors = [];
81
+ /** The default framebuffer's texels (the canvas), 4 components each. */
82
+ backbuffer;
83
+ lost = false;
84
+ /** Calls made on extensions, by name. */
85
+ extensionCalls = [];
86
+ options;
87
+ next = 1;
88
+ extensions = /* @__PURE__ */ new Map();
89
+ bound = /* @__PURE__ */ new Map();
90
+ ranges = [];
91
+ defaultVao;
92
+ vao;
93
+ program = null;
94
+ active = 0;
95
+ units = [];
96
+ samplers = [];
97
+ drawFb = null;
98
+ readFb = null;
99
+ renderbuffer = null;
100
+ enabled = /* @__PURE__ */ new Set();
101
+ pixel = /* @__PURE__ */ new Map();
102
+ viewport_ = [0, 0, 300, 150];
103
+ scissor_ = [0, 0, 300, 150];
104
+ colorMask_ = [true, true, true, true];
105
+ depthMask_ = true;
106
+ depthFunc_ = GL.LESS;
107
+ frontFace_ = GL.CCW;
108
+ cullFace_ = GL.BACK;
109
+ blend_ = [GL.FUNC_ADD, GL.FUNC_ADD, GL.ONE, GL.ZERO, GL.ONE, GL.ZERO];
110
+ clearColor_ = [0, 0, 0, 0];
111
+ pendingLinks = 0;
112
+ constructor(options = {}) {
113
+ this.options = options;
114
+ const listeners = /* @__PURE__ */ new Map();
115
+ this.canvas = {
116
+ width: 300,
117
+ height: 150,
118
+ listeners,
119
+ addEventListener: (type, l) => listeners.set(type, [...listeners.get(type) ?? [], l]),
120
+ removeEventListener: (type, l) => listeners.set(
121
+ type,
122
+ (listeners.get(type) ?? []).filter((x) => x !== l)
123
+ ),
124
+ dispatch: (type) => {
125
+ const event = { type, defaultPrevented: false, preventDefault() {
126
+ } };
127
+ for (const l of listeners.get(type) ?? []) l(event);
128
+ }
129
+ };
130
+ this.backbuffer = new Float64Array(300 * 150 * 4);
131
+ this.defaultVao = { kind: "vao", id: 0, element: null, attribs: [] };
132
+ this.vao = this.defaultVao;
133
+ const fn = (name) => () => this.extensionCalls.push(name);
134
+ const all = {
135
+ EXT_color_buffer_float: {},
136
+ EXT_float_blend: {},
137
+ OES_texture_float_linear: {},
138
+ EXT_texture_filter_anisotropic: {},
139
+ KHR_parallel_shader_compile: {},
140
+ EXT_clip_control: { clipControlEXT: fn("clipControlEXT") },
141
+ OES_draw_buffers_indexed: {
142
+ enableiOES: fn("enableiOES"),
143
+ disableiOES: fn("disableiOES"),
144
+ blendEquationSeparateiOES: fn("blendEquationSeparateiOES"),
145
+ blendFuncSeparateiOES: fn("blendFuncSeparateiOES"),
146
+ colorMaskiOES: fn("colorMaskiOES")
147
+ },
148
+ WEBGL_lose_context: {
149
+ loseContext: () => {
150
+ this.extensionCalls.push("loseContext");
151
+ this.lost = true;
152
+ },
153
+ restoreContext: () => {
154
+ this.lost = false;
155
+ }
156
+ }
157
+ };
158
+ for (const [name, ext] of Object.entries(all)) {
159
+ if (!options.without?.includes(name)) this.extensions.set(name, ext);
160
+ }
161
+ }
162
+ /** The fake as the context type the shim takes, logging every call. */
163
+ get context() {
164
+ return new Proxy(this, {
165
+ get: (target, key) => {
166
+ const value = Reflect.get(target, key);
167
+ if (typeof value !== "function") return value;
168
+ return (...args) => {
169
+ target.calls.push(String(key));
170
+ return value.apply(target, args);
171
+ };
172
+ }
173
+ });
174
+ }
175
+ /** Calls since `mark` (an index into `calls`), by name. */
176
+ callsSince(mark) {
177
+ return this.calls.slice(mark);
178
+ }
179
+ /** Texel `(x, y)` of `texture`'s level and layer, 4 components. */
180
+ texel(texture, x, y, level = 0, layer = 0) {
181
+ const [image, w, h] = this.image(texture, level);
182
+ const i = ((layer * h + y) * w + x) * 4;
183
+ return [image[i], image[i + 1], image[i + 2], image[i + 3]];
184
+ }
185
+ // --- context state -----------------------------------------------------------------
186
+ isContextLost() {
187
+ return this.lost;
188
+ }
189
+ getExtension(name) {
190
+ return this.extensions.get(name) ?? null;
191
+ }
192
+ getParameter(p) {
193
+ const o = this.options.parameters?.[p];
194
+ if (o !== void 0) return o;
195
+ switch (p) {
196
+ case GL.MAX_TEXTURE_SIZE:
197
+ case GL.MAX_CUBE_MAP_TEXTURE_SIZE:
198
+ return 8192;
199
+ case GL.MAX_3D_TEXTURE_SIZE:
200
+ return 2048;
201
+ case GL.MAX_ARRAY_TEXTURE_LAYERS:
202
+ return 2048;
203
+ case GL.MAX_TEXTURE_IMAGE_UNITS:
204
+ case GL.MAX_VERTEX_TEXTURE_IMAGE_UNITS:
205
+ return 16;
206
+ case GL.MAX_COMBINED_TEXTURE_IMAGE_UNITS:
207
+ return 32;
208
+ case GL.MAX_DRAW_BUFFERS:
209
+ case GL.MAX_COLOR_ATTACHMENTS:
210
+ return 8;
211
+ case GL.MAX_VERTEX_ATTRIBS:
212
+ return 16;
213
+ case GL.MAX_VARYING_COMPONENTS:
214
+ return 120;
215
+ case GL.MAX_UNIFORM_BLOCK_SIZE:
216
+ return 65536;
217
+ case GL.MAX_VERTEX_UNIFORM_BLOCKS:
218
+ case GL.MAX_FRAGMENT_UNIFORM_BLOCKS:
219
+ return 14;
220
+ case GL.MAX_UNIFORM_BUFFER_BINDINGS:
221
+ return 72;
222
+ case GL.UNIFORM_BUFFER_OFFSET_ALIGNMENT:
223
+ return 256;
224
+ case GL.MAX_SAMPLES:
225
+ return 8;
226
+ case GL.MAX_TEXTURE_MAX_ANISOTROPY_EXT:
227
+ return 16;
228
+ case GL.VENDOR:
229
+ case GL.UNMASKED_VENDOR_WEBGL:
230
+ return "Shard";
231
+ case GL.RENDERER:
232
+ case GL.UNMASKED_RENDERER_WEBGL:
233
+ return "Fake WebGL2";
234
+ default:
235
+ return 0;
236
+ }
237
+ }
238
+ getInternalformatParameter(_target, internal, _p) {
239
+ return new Int32Array(this.options.samples?.[internal] ?? [8, 4, 2]);
240
+ }
241
+ getError() {
242
+ return this.errors.shift() ?? GL.NO_ERROR;
243
+ }
244
+ enable(cap) {
245
+ this.enabled.add(cap);
246
+ }
247
+ disable(cap) {
248
+ this.enabled.delete(cap);
249
+ }
250
+ pixelStorei(p, v) {
251
+ this.pixel.set(p, v);
252
+ }
253
+ viewport(x, y, w, h) {
254
+ const v = this.viewport_;
255
+ v[0] = x;
256
+ v[1] = y;
257
+ v[2] = w;
258
+ v[3] = h;
259
+ }
260
+ scissor(x, y, w, h) {
261
+ const v = this.scissor_;
262
+ v[0] = x;
263
+ v[1] = y;
264
+ v[2] = w;
265
+ v[3] = h;
266
+ }
267
+ depthRange() {
268
+ }
269
+ colorMask(r, g, b, a) {
270
+ const m = this.colorMask_;
271
+ m[0] = r;
272
+ m[1] = g;
273
+ m[2] = b;
274
+ m[3] = a;
275
+ }
276
+ depthMask(on) {
277
+ this.depthMask_ = on;
278
+ }
279
+ depthFunc(f) {
280
+ this.depthFunc_ = f;
281
+ }
282
+ frontFace(f) {
283
+ this.frontFace_ = f;
284
+ }
285
+ cullFace(f) {
286
+ this.cullFace_ = f;
287
+ }
288
+ blendEquationSeparate(c, a) {
289
+ this.blend_[0] = c;
290
+ this.blend_[1] = a;
291
+ }
292
+ blendFuncSeparate(cs, cd, as, ad) {
293
+ this.blend_[2] = cs;
294
+ this.blend_[3] = cd;
295
+ this.blend_[4] = as;
296
+ this.blend_[5] = ad;
297
+ }
298
+ blendColor() {
299
+ }
300
+ polygonOffset() {
301
+ }
302
+ stencilOpSeparate() {
303
+ }
304
+ stencilFuncSeparate() {
305
+ }
306
+ stencilMask() {
307
+ }
308
+ clearColor(r, g, b, a) {
309
+ this.clearColor_ = [r, g, b, a];
310
+ }
311
+ flush() {
312
+ }
313
+ hint() {
314
+ }
315
+ // --- buffers --------------------------------------------------------------------
316
+ createBuffer() {
317
+ return { kind: "buffer", id: this.next++, data: new Uint8Array(0), deleted: false };
318
+ }
319
+ deleteBuffer(b) {
320
+ if (b) b.deleted = true;
321
+ }
322
+ bindBuffer(target, b) {
323
+ if (target === GL.ELEMENT_ARRAY_BUFFER) this.vao.element = b;
324
+ else this.bound.set(target, b);
325
+ }
326
+ buffer(target) {
327
+ const b = target === GL.ELEMENT_ARRAY_BUFFER ? this.vao.element : this.bound.get(target);
328
+ if (!b) throw new Error(`Fake GL: no buffer bound to 0x${target.toString(16)}`);
329
+ return b;
330
+ }
331
+ bufferData(target, sizeOrData) {
332
+ const b = this.buffer(target);
333
+ b.data = typeof sizeOrData === "number" ? new Uint8Array(sizeOrData) : new Uint8Array(
334
+ sizeOrData.buffer.slice(
335
+ sizeOrData.byteOffset,
336
+ sizeOrData.byteOffset + sizeOrData.byteLength
337
+ )
338
+ );
339
+ }
340
+ bufferSubData(target, offset, src, srcOffset = 0, length = 0) {
341
+ const b = this.buffer(target);
342
+ const view = ArrayBuffer.isView(src) ? src : new Uint8Array(src);
343
+ const unit = view.BYTES_PER_ELEMENT ?? 1;
344
+ const count = length || view.byteLength / unit - srcOffset;
345
+ const bytes = new Uint8Array(view.buffer, view.byteOffset + srcOffset * unit, count * unit);
346
+ if (offset + bytes.length > b.data.length)
347
+ throw new Error("Fake GL: bufferSubData out of range");
348
+ b.data.set(bytes, offset);
349
+ }
350
+ copyBufferSubData(readTarget, writeTarget, readOffset, writeOffset, size) {
351
+ const from = this.buffer(readTarget);
352
+ const to = this.buffer(writeTarget);
353
+ to.data.set(from.data.slice(readOffset, readOffset + size), writeOffset);
354
+ }
355
+ getBufferSubData(target, offset, dst, dstOffset = 0, length = 0) {
356
+ const b = this.buffer(target);
357
+ const unit = dst.BYTES_PER_ELEMENT ?? 1;
358
+ const count = (length || dst.byteLength / unit - dstOffset) * unit;
359
+ new Uint8Array(dst.buffer, dst.byteOffset + dstOffset * unit, count).set(
360
+ b.data.subarray(offset, offset + count)
361
+ );
362
+ }
363
+ bindBufferRange(_target, point, b, offset, size) {
364
+ let r = this.ranges[point];
365
+ if (!r) {
366
+ r = { buffer: null, offset: 0, size: 0 };
367
+ this.ranges[point] = r;
368
+ }
369
+ r.buffer = b;
370
+ r.offset = offset;
371
+ r.size = size;
372
+ }
373
+ // --- textures -------------------------------------------------------------------
374
+ createTexture() {
375
+ return {
376
+ kind: "texture",
377
+ id: this.next++,
378
+ target: 0,
379
+ internal: 0,
380
+ width: 0,
381
+ height: 0,
382
+ depth: 0,
383
+ levels: 0,
384
+ images: [],
385
+ params: /* @__PURE__ */ new Map(),
386
+ deleted: false
387
+ };
388
+ }
389
+ deleteTexture(t) {
390
+ if (t) t.deleted = true;
391
+ }
392
+ activeTexture(unit) {
393
+ this.active = unit - GL.TEXTURE0;
394
+ }
395
+ bindTexture(target, t) {
396
+ let unit = this.units[this.active];
397
+ if (!unit) {
398
+ unit = /* @__PURE__ */ new Map();
399
+ this.units[this.active] = unit;
400
+ }
401
+ unit.set(target, t);
402
+ if (t && !t.target) t.target = target;
403
+ }
404
+ boundTexture(target) {
405
+ const base = target >= GL.TEXTURE_CUBE_MAP_POSITIVE_X && target < GL.TEXTURE_CUBE_MAP_POSITIVE_X + 6;
406
+ const t = this.units[this.active]?.get(base ? GL.TEXTURE_CUBE_MAP : target);
407
+ if (!t) throw new Error(`Fake GL: no texture bound to 0x${target.toString(16)}`);
408
+ return t;
409
+ }
410
+ texStorage2D(target, levels, internal, w, h) {
411
+ this.storage(
412
+ this.boundTexture(target),
413
+ levels,
414
+ internal,
415
+ w,
416
+ h,
417
+ target === GL.TEXTURE_CUBE_MAP ? 6 : 1
418
+ );
419
+ }
420
+ texStorage3D(target, levels, internal, w, h, d) {
421
+ this.storage(this.boundTexture(target), levels, internal, w, h, d);
422
+ }
423
+ storage(t, levels, internal, w, h, d) {
424
+ t.internal = internal;
425
+ t.width = w;
426
+ t.height = h;
427
+ t.depth = d;
428
+ t.levels = levels;
429
+ for (let l = 0; l < levels; l++) {
430
+ const layers = t.target === GL.TEXTURE_3D ? Math.max(1, d >> l) : d;
431
+ t.images.push(new Float64Array(Math.max(1, w >> l) * Math.max(1, h >> l) * layers * 4));
432
+ }
433
+ }
434
+ texParameteri(target, p, v) {
435
+ this.boundTexture(target).params.set(p, v);
436
+ }
437
+ texSubImage2D(target, level, x, y, w, h, format, type, src, srcOffset = 0) {
438
+ const t = this.boundTexture(target);
439
+ const face = target === GL.TEXTURE_2D ? 0 : target - GL.TEXTURE_CUBE_MAP_POSITIVE_X;
440
+ this.upload(t, level, x, y, face, w, h, 1, format, type, src, srcOffset);
441
+ }
442
+ texSubImage3D(target, level, x, y, z, w, h, d, format, type, src, srcOffset = 0) {
443
+ this.upload(this.boundTexture(target), level, x, y, z, w, h, d, format, type, src, srcOffset);
444
+ }
445
+ compressedTexSubImage2D() {
446
+ }
447
+ compressedTexSubImage3D() {
448
+ }
449
+ upload(t, level, x, y, z, w, h, d, format, type, src, srcOffset) {
450
+ let bytes;
451
+ let start;
452
+ if (typeof src === "number") {
453
+ bytes = this.buffer(GL.PIXEL_UNPACK_BUFFER).data;
454
+ start = src;
455
+ } else if (src && ArrayBuffer.isView(src)) {
456
+ bytes = new Uint8Array(src.buffer, src.byteOffset, src.byteLength);
457
+ start = srcOffset * (src.BYTES_PER_ELEMENT ?? 1);
458
+ } else {
459
+ return;
460
+ }
461
+ const comps = formatComponents(format);
462
+ const size = typeSize(type);
463
+ const texelBytes = packed(type) ? 4 : comps * size;
464
+ const rowLength = this.pixel.get(GL.UNPACK_ROW_LENGTH) || w;
465
+ const imageHeight = this.pixel.get(GL.UNPACK_IMAGE_HEIGHT) || h;
466
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
467
+ const [image, iw, ih] = this.image(t, level);
468
+ const integer = COMPONENTS[t.internal]?.[1] === "uint";
469
+ for (let k = 0; k < d; k++) {
470
+ for (let r = 0; r < h; r++) {
471
+ for (let c = 0; c < w; c++) {
472
+ const at = start + ((k * imageHeight + r) * rowLength + c) * texelBytes;
473
+ const o = (((z + k) * ih + y + r) * iw + x + c) * 4;
474
+ const values = decode(view, at, comps, type, integer);
475
+ for (let i = 0; i < 4; i++) image[o + i] = values[i];
476
+ }
477
+ }
478
+ }
479
+ }
480
+ image(t, level) {
481
+ if (t.kind === "renderbuffer") return [t.image, t.width, t.height];
482
+ return [t.images[level], Math.max(1, t.width >> level), Math.max(1, t.height >> level)];
483
+ }
484
+ // --- samplers -----------------------------------------------------------------------
485
+ createSampler() {
486
+ return { kind: "sampler", id: this.next++, params: /* @__PURE__ */ new Map() };
487
+ }
488
+ samplerParameteri(s, p, v) {
489
+ s.params.set(p, v);
490
+ }
491
+ samplerParameterf(s, p, v) {
492
+ s.params.set(p, v);
493
+ }
494
+ bindSampler(unit, s) {
495
+ this.samplers[unit] = s;
496
+ }
497
+ // --- framebuffers -------------------------------------------------------------------
498
+ createRenderbuffer() {
499
+ return {
500
+ kind: "renderbuffer",
501
+ id: this.next++,
502
+ internal: 0,
503
+ width: 0,
504
+ height: 0,
505
+ samples: 0,
506
+ image: new Float64Array(0),
507
+ deleted: false
508
+ };
509
+ }
510
+ deleteRenderbuffer(r) {
511
+ if (r) r.deleted = true;
512
+ }
513
+ bindRenderbuffer(_target, r) {
514
+ this.renderbuffer = r;
515
+ }
516
+ renderbufferStorage(_target, internal, w, h) {
517
+ this.renderbufferStorageMultisample(_target, 0, internal, w, h);
518
+ }
519
+ renderbufferStorageMultisample(_target, samples, internal, w, h) {
520
+ const r = this.renderbuffer;
521
+ r.samples = samples;
522
+ r.internal = internal;
523
+ r.width = w;
524
+ r.height = h;
525
+ r.image = new Float64Array(w * h * 4);
526
+ }
527
+ createFramebuffer() {
528
+ return {
529
+ kind: "framebuffer",
530
+ id: this.next++,
531
+ attachments: /* @__PURE__ */ new Map(),
532
+ drawBuffers: [GL.COLOR_ATTACHMENT0],
533
+ readBuffer: GL.COLOR_ATTACHMENT0,
534
+ deleted: false
535
+ };
536
+ }
537
+ deleteFramebuffer(f) {
538
+ if (f) f.deleted = true;
539
+ }
540
+ bindFramebuffer(target, f) {
541
+ if (target === GL.FRAMEBUFFER || target === GL.DRAW_FRAMEBUFFER) this.drawFb = f;
542
+ if (target === GL.FRAMEBUFFER || target === GL.READ_FRAMEBUFFER) this.readFb = f;
543
+ }
544
+ framebufferTexture2D(_target, point, texTarget, t, level) {
545
+ const face = texTarget === GL.TEXTURE_2D ? 0 : texTarget - GL.TEXTURE_CUBE_MAP_POSITIVE_X;
546
+ if (t) this.drawFb.attachments.set(point, { object: t, level, layer: face });
547
+ }
548
+ framebufferTextureLayer(_target, point, t, level, layer) {
549
+ if (t) this.drawFb.attachments.set(point, { object: t, level, layer });
550
+ }
551
+ framebufferRenderbuffer(_target, point, _rt, r) {
552
+ if (r) this.drawFb.attachments.set(point, { object: r, level: 0, layer: 0 });
553
+ }
554
+ drawBuffers(buffers) {
555
+ this.drawFb.drawBuffers = [...buffers];
556
+ }
557
+ readBuffer(b) {
558
+ this.readFb.readBuffer = b;
559
+ }
560
+ checkFramebufferStatus() {
561
+ return GL.FRAMEBUFFER_COMPLETE;
562
+ }
563
+ invalidateFramebuffer() {
564
+ }
565
+ /** The attachment a color draw buffer writes, or the depth/stencil one. */
566
+ attachment(fb, point) {
567
+ if (point === GL.DEPTH_ATTACHMENT || point === GL.STENCIL_ATTACHMENT) {
568
+ return fb.attachments.get(point) ?? fb.attachments.get(GL.DEPTH_STENCIL_ATTACHMENT);
569
+ }
570
+ return fb.attachments.get(point);
571
+ }
572
+ fill(a, values, mask) {
573
+ if (!a) return;
574
+ const o = a.object;
575
+ const image = o.kind === "renderbuffer" ? o.image : o.images[a.level];
576
+ const w = o.kind === "renderbuffer" ? o.width : Math.max(1, o.width >> a.level);
577
+ const h = o.kind === "renderbuffer" ? o.height : Math.max(1, o.height >> a.level);
578
+ const scissor = this.enabled.has(GL.SCISSOR_TEST);
579
+ const [sx, sy, sw, sh] = this.scissor_;
580
+ for (let y = 0; y < h; y++) {
581
+ for (let x = 0; x < w; x++) {
582
+ if (scissor && (x < sx || y < sy || x >= sx + sw || y >= sy + sh)) continue;
583
+ const at = ((a.layer * h + y) * w + x) * 4;
584
+ for (let i = 0; i < 4; i++) if (mask[i] && i < values.length) image[at + i] = values[i];
585
+ }
586
+ }
587
+ }
588
+ clearBufferfv(buffer, drawBuffer, values) {
589
+ const fb = this.drawFb;
590
+ if (!fb) return;
591
+ if (buffer === GL.DEPTH)
592
+ this.fill(this.attachment(fb, GL.DEPTH_ATTACHMENT), [values[0]], [this.depthMask_]);
593
+ else
594
+ this.fill(this.attachment(fb, fb.drawBuffers[drawBuffer] ?? GL.NONE), values, this.colorMask_);
595
+ }
596
+ clearBufferuiv(_buffer, drawBuffer, values) {
597
+ const fb = this.drawFb;
598
+ if (fb)
599
+ this.fill(this.attachment(fb, fb.drawBuffers[drawBuffer] ?? GL.NONE), values, this.colorMask_);
600
+ }
601
+ clearBufferiv(buffer, drawBuffer, values) {
602
+ const fb = this.drawFb;
603
+ if (!fb || buffer === GL.STENCIL) return;
604
+ this.fill(this.attachment(fb, fb.drawBuffers[drawBuffer] ?? GL.NONE), values, this.colorMask_);
605
+ }
606
+ clearBufferfi(_buffer, _drawBuffer, depth) {
607
+ const fb = this.drawFb;
608
+ if (fb) this.fill(this.attachment(fb, GL.DEPTH_ATTACHMENT), [depth], [this.depthMask_]);
609
+ }
610
+ clear(mask) {
611
+ if (mask & GL.COLOR_BUFFER_BIT && !this.drawFb) {
612
+ const c = this.clearColor_;
613
+ for (let i = 0; i < this.backbuffer.length; i += 4) {
614
+ for (let k = 0; k < 4; k++) if (this.colorMask_[k]) this.backbuffer[i + k] = c[k];
615
+ }
616
+ }
617
+ }
618
+ blitFramebuffer(sx0, sy0, sx1, sy1, dx0, dy0, dx1, dy1, mask) {
619
+ const read = this.readFb;
620
+ const draw = this.drawFb;
621
+ if (!read) throw new Error("Fake GL: blit without a read framebuffer");
622
+ const color = (mask & GL.COLOR_BUFFER_BIT) !== 0;
623
+ const from = this.attachment(read, color ? read.readBuffer : GL.DEPTH_ATTACHMENT);
624
+ if (!from) throw new Error("Fake GL: blit from nothing");
625
+ const [src, sw, sh] = this.image(from.object, from.level);
626
+ const targets = [];
627
+ if (!draw) {
628
+ targets.push({
629
+ image: this.backbuffer,
630
+ w: this.canvas.width,
631
+ h: this.canvas.height,
632
+ layer: 0
633
+ });
634
+ } else if (color) {
635
+ for (const b of draw.drawBuffers) {
636
+ const a = this.attachment(draw, b);
637
+ if (a) {
638
+ const [image, w, h] = this.image(a.object, a.level);
639
+ targets.push({ image, w, h, layer: a.layer });
640
+ }
641
+ }
642
+ } else {
643
+ const a = this.attachment(draw, GL.DEPTH_ATTACHMENT);
644
+ if (a) {
645
+ const [image, w, h] = this.image(a.object, a.level);
646
+ targets.push({ image, w, h, layer: a.layer });
647
+ }
648
+ }
649
+ const width = Math.abs(dx1 - dx0);
650
+ const height = Math.abs(dy1 - dy0);
651
+ for (const t of targets) {
652
+ for (let y = 0; y < height; y++) {
653
+ for (let x = 0; x < width; x++) {
654
+ const tx = dx1 > dx0 ? dx0 + x : dx0 - 1 - x;
655
+ const ty = dy1 > dy0 ? dy0 + y : dy0 - 1 - y;
656
+ const fx = sx0 + Math.floor((x + 0.5) * (sx1 - sx0) / width);
657
+ const fy = sy0 + Math.floor((y + 0.5) * (sy1 - sy0) / height);
658
+ if (tx < 0 || ty < 0 || tx >= t.w || ty >= t.h || fx >= sw || fy >= sh) continue;
659
+ const i = ((from.layer * sh + fy) * sw + fx) * 4;
660
+ const o = ((t.layer * t.h + ty) * t.w + tx) * 4;
661
+ for (let k = 0; k < 4; k++) t.image[o + k] = src[i + k];
662
+ }
663
+ }
664
+ }
665
+ }
666
+ readPixels(x, y, w, h, format, type, dst, dstOffset = 0) {
667
+ const fb = this.readFb;
668
+ if (!fb) throw new Error("Fake GL: readPixels from the canvas");
669
+ const a = this.attachment(fb, fb.readBuffer);
670
+ if (!a) throw new Error("Fake GL: readPixels from no attachment");
671
+ const [image, iw, ih] = this.image(a.object, a.level);
672
+ let bytes;
673
+ let start;
674
+ if (typeof dst === "number") {
675
+ bytes = this.buffer(GL.PIXEL_PACK_BUFFER).data;
676
+ start = dst;
677
+ } else {
678
+ bytes = new Uint8Array(dst.buffer, dst.byteOffset, dst.byteLength);
679
+ start = dstOffset * (dst.BYTES_PER_ELEMENT ?? 1);
680
+ }
681
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
682
+ const comps = formatComponents(format);
683
+ const size = typeSize(type);
684
+ const rowLength = this.pixel.get(GL.PACK_ROW_LENGTH) || w;
685
+ for (let r = 0; r < h; r++) {
686
+ for (let c = 0; c < w; c++) {
687
+ const i = ((a.layer * ih + y + r) * iw + x + c) * 4;
688
+ const at = start + (r * rowLength + c) * comps * size;
689
+ for (let k = 0; k < comps; k++) {
690
+ const v = image[i + k];
691
+ if (type === GL.UNSIGNED_BYTE)
692
+ view.setUint8(at + k, Math.round(Math.min(1, Math.max(0, v)) * 255));
693
+ else if (type === GL.FLOAT) view.setFloat32(at + k * 4, v, true);
694
+ else if (type === GL.INT) view.setInt32(at + k * 4, v, true);
695
+ else view.setUint32(at + k * 4, v >>> 0, true);
696
+ }
697
+ }
698
+ }
699
+ }
700
+ // --- programs -----------------------------------------------------------------------
701
+ createShader(type) {
702
+ return { kind: "shader", type, source: "" };
703
+ }
704
+ shaderSource(s, source) {
705
+ s.source = source;
706
+ }
707
+ compileShader() {
708
+ }
709
+ deleteShader() {
710
+ }
711
+ getShaderInfoLog(s) {
712
+ return s.source.includes("FAIL_LINK") ? "fake: FAIL_LINK" : "";
713
+ }
714
+ createProgram() {
715
+ return {
716
+ kind: "program",
717
+ id: this.next++,
718
+ sources: [],
719
+ linked: false,
720
+ blockIndices: /* @__PURE__ */ new Map(),
721
+ blockPoints: /* @__PURE__ */ new Map(),
722
+ uniforms: /* @__PURE__ */ new Map(),
723
+ deleted: false
724
+ };
725
+ }
726
+ attachShader(p, s) {
727
+ p.sources.push(s.source);
728
+ }
729
+ linkProgram(p) {
730
+ p.linked = !p.sources.some((s) => s.includes("FAIL_LINK"));
731
+ this.pendingLinks = this.options.linkPolls ?? 0;
732
+ }
733
+ getProgramParameter(p, which) {
734
+ if (which === GL.COMPLETION_STATUS_KHR) {
735
+ if (this.pendingLinks > 0) {
736
+ this.pendingLinks--;
737
+ return false;
738
+ }
739
+ return true;
740
+ }
741
+ return p.linked;
742
+ }
743
+ getProgramInfoLog(p) {
744
+ return p.linked ? "" : "fake: the program did not link";
745
+ }
746
+ deleteProgram(p) {
747
+ if (p) p.deleted = true;
748
+ }
749
+ useProgram(p) {
750
+ this.program = p;
751
+ }
752
+ getUniformBlockIndex(p, name) {
753
+ if (!p.sources.some((s) => new RegExp(`uniform\\s+${name}\\b`).test(s))) return 4294967295;
754
+ let index = p.blockIndices.get(name);
755
+ if (index === void 0) {
756
+ index = p.blockIndices.size;
757
+ p.blockIndices.set(name, index);
758
+ }
759
+ return index;
760
+ }
761
+ getActiveUniformBlockParameter(p, index) {
762
+ for (const [name, i] of p.blockIndices) {
763
+ if (i !== index) continue;
764
+ for (const [prefix, size] of Object.entries(this.options.blockSizes ?? {})) {
765
+ if (name.startsWith(prefix)) return size;
766
+ }
767
+ }
768
+ return 0;
769
+ }
770
+ uniformBlockBinding(p, index, point) {
771
+ p.blockPoints.set(index, point);
772
+ }
773
+ getUniformLocation(p, name) {
774
+ return p.sources.some((s) => new RegExp(`\\b${name}\\b`).test(s)) ? { program: p, name } : null;
775
+ }
776
+ setUniform(l, value) {
777
+ if (!l) return;
778
+ if (l.program !== this.program) throw new Error("Fake GL: uniform set on a program not in use");
779
+ l.program.uniforms.set(l.name, value);
780
+ }
781
+ uniform1i(l, v) {
782
+ this.setUniform(l, v);
783
+ }
784
+ uniform1ui(l, v) {
785
+ this.setUniform(l, v);
786
+ }
787
+ uniform3i(l, x, y, z) {
788
+ this.setUniform(l, [x, y, z]);
789
+ }
790
+ // --- vertex arrays and draws ---------------------------------------------------------
791
+ createVertexArray() {
792
+ return { kind: "vao", id: this.next++, element: null, attribs: [] };
793
+ }
794
+ bindVertexArray(v) {
795
+ this.vao = v ?? this.defaultVao;
796
+ }
797
+ attrib(loc) {
798
+ const known = this.vao.attribs[loc];
799
+ if (known) return known;
800
+ const made = {
801
+ enabled: false,
802
+ buffer: null,
803
+ size: 4,
804
+ type: GL.FLOAT,
805
+ normalized: false,
806
+ integer: false,
807
+ stride: 0,
808
+ offset: 0,
809
+ divisor: 0
810
+ };
811
+ this.vao.attribs[loc] = made;
812
+ return made;
813
+ }
814
+ enableVertexAttribArray(loc) {
815
+ this.attrib(loc).enabled = true;
816
+ }
817
+ disableVertexAttribArray(loc) {
818
+ this.attrib(loc).enabled = false;
819
+ }
820
+ vertexAttribPointer(loc, size, type, normalized, stride, offset) {
821
+ this.point(loc, size, type, normalized, false, stride, offset);
822
+ }
823
+ vertexAttribIPointer(loc, size, type, stride, offset) {
824
+ this.point(loc, size, type, false, true, stride, offset);
825
+ }
826
+ point(loc, size, type, normalized, integer, stride, offset) {
827
+ const a = this.attrib(loc);
828
+ a.buffer = this.bound.get(GL.ARRAY_BUFFER) ?? null;
829
+ a.size = size;
830
+ a.type = type;
831
+ a.normalized = normalized;
832
+ a.integer = integer;
833
+ a.stride = stride;
834
+ a.offset = offset;
835
+ }
836
+ vertexAttribDivisor(loc, divisor) {
837
+ this.attrib(loc).divisor = divisor;
838
+ }
839
+ drawArrays(mode, first, count) {
840
+ this.record("drawArrays", mode, first, count, 0, 0, 1);
841
+ }
842
+ drawArraysInstanced(mode, first, count, instances) {
843
+ this.record("drawArrays", mode, first, count, 0, 0, instances);
844
+ }
845
+ drawElementsInstanced(mode, count, type, offset, instances) {
846
+ this.record("drawElements", mode, 0, count, type, offset, instances);
847
+ }
848
+ record(call, mode, first, count, type, offset, instances) {
849
+ const program = this.program;
850
+ if (!program?.linked) throw new Error("Fake GL: draw without a linked program");
851
+ if (!this.recordDraws) return;
852
+ const blocks = /* @__PURE__ */ new Map();
853
+ for (const point of program.blockPoints.values()) {
854
+ const r = this.ranges[point];
855
+ blocks.set(point, { buffer: r?.buffer ?? null, offset: r?.offset ?? 0, size: r?.size ?? 0 });
856
+ }
857
+ const units = /* @__PURE__ */ new Map();
858
+ for (const [name, v] of program.uniforms) {
859
+ if (typeof v !== "number" || name === "naga_vs_first_instance") continue;
860
+ const unit = this.units[v];
861
+ let texture = null;
862
+ for (const t of unit?.values() ?? []) if (t) texture = t;
863
+ units.set(v, {
864
+ texture,
865
+ sampler: this.samplers[v] ?? null,
866
+ base: texture?.params.get(GL.TEXTURE_BASE_LEVEL) ?? 0,
867
+ max: texture?.params.get(GL.TEXTURE_MAX_LEVEL) ?? 1e3
868
+ });
869
+ }
870
+ this.draws.push({
871
+ call,
872
+ mode,
873
+ first,
874
+ count,
875
+ type,
876
+ offset,
877
+ instances,
878
+ program,
879
+ framebuffer: this.drawFb,
880
+ viewport: [...this.viewport_],
881
+ blocks,
882
+ units,
883
+ attribs: this.vao.attribs.flatMap((a, location) => a?.enabled ? [{ ...a, location }] : []),
884
+ element: this.vao.element,
885
+ uniforms: new Map(program.uniforms),
886
+ enabled: new Set(this.enabled),
887
+ colorMask: [...this.colorMask_],
888
+ depthMask: this.depthMask_,
889
+ depthFunc: this.depthFunc_,
890
+ frontFace: this.frontFace_,
891
+ cullFace: this.cullFace_,
892
+ blend: [...this.blend_]
893
+ });
894
+ }
895
+ // --- sync -----------------------------------------------------------------------------
896
+ fenceSync() {
897
+ return { kind: "sync", id: this.next++ };
898
+ }
899
+ getSyncParameter() {
900
+ return GL.SIGNALED;
901
+ }
902
+ deleteSync() {
903
+ }
904
+ }
905
+ function decode(view, at, comps, type, integer) {
906
+ const out = [0, 0, 0, 1];
907
+ if (type === GL.UNSIGNED_INT_10F_11F_11F_REV) {
908
+ const v = view.getUint32(at, true);
909
+ return [ufloat(v & 2047, 6), ufloat(v >>> 11 & 2047, 6), ufloat(v >>> 22 & 1023, 5), 1];
910
+ }
911
+ if (type === GL.UNSIGNED_INT_2_10_10_10_REV) {
912
+ const v = view.getUint32(at, true);
913
+ return [
914
+ (v & 1023) / 1023,
915
+ (v >>> 10 & 1023) / 1023,
916
+ (v >>> 20 & 1023) / 1023,
917
+ (v >>> 30) / 3
918
+ ];
919
+ }
920
+ for (let i = 0; i < comps; i++) {
921
+ switch (type) {
922
+ case GL.UNSIGNED_BYTE: {
923
+ const b = view.getUint8(at + i);
924
+ out[i] = integer ? b : b / 255;
925
+ break;
926
+ }
927
+ case GL.HALF_FLOAT:
928
+ out[i] = halfToFloat(view.getUint16(at + i * 2, true));
929
+ break;
930
+ case GL.FLOAT:
931
+ out[i] = view.getFloat32(at + i * 4, true);
932
+ break;
933
+ case GL.UNSIGNED_SHORT: {
934
+ const s = view.getUint16(at + i * 2, true);
935
+ out[i] = integer ? s : s / 65535;
936
+ break;
937
+ }
938
+ case GL.INT:
939
+ out[i] = view.getInt32(at + i * 4, true);
940
+ break;
941
+ default:
942
+ out[i] = view.getUint32(at + i * 4, true);
943
+ }
944
+ }
945
+ return out;
946
+ }
947
+ export {
948
+ FakeGl
949
+ };