@easywasm/gl 0.0.2 → 0.0.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.
package/web/gl.js ADDED
@@ -0,0 +1,1060 @@
1
+ // OpenGL → WebGL2 host implementation for wasm modules compiled with wasi-sdk.
2
+ // Covers modern GL (ES 3.0), immediate mode emulation, and fixed-function matrices.
3
+ //
4
+ // Usage:
5
+ // const gl = new GL({ memory })
6
+ // // after _start() runs (window + context exist):
7
+ // gl.setGL(glfw.getContextGL())
8
+ // // include in env imports:
9
+ // env: { ...glfw, ...gl, memory }
10
+
11
+ const dec = new TextDecoder()
12
+ const enc = new TextEncoder()
13
+
14
+ // ── Matrix math (column-major, matches OpenGL) ───────────────────────────────
15
+
16
+ function m4id() {
17
+ return new Float32Array([1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1])
18
+ }
19
+
20
+ function m4mul(a, b) {
21
+ const o = new Float32Array(16)
22
+ for (let c = 0; c < 4; c++)
23
+ for (let r = 0; r < 4; r++) {
24
+ let s = 0
25
+ for (let k = 0; k < 4; k++) s += a[r + k*4] * b[k + c*4]
26
+ o[r + c*4] = s
27
+ }
28
+ return o
29
+ }
30
+
31
+ function m4ortho(l, r, b, t, n, f) {
32
+ return new Float32Array([
33
+ 2/(r-l), 0, 0, 0,
34
+ 0, 2/(t-b), 0, 0,
35
+ 0, 0, -2/(f-n), 0,
36
+ -(r+l)/(r-l), -(t+b)/(t-b), -(f+n)/(f-n), 1
37
+ ])
38
+ }
39
+
40
+ function m4frustum(l, r, b, t, n, f) {
41
+ return new Float32Array([
42
+ 2*n/(r-l), 0, 0, 0,
43
+ 0, 2*n/(t-b), 0, 0,
44
+ (r+l)/(r-l), (t+b)/(t-b), -(f+n)/(f-n), -1,
45
+ 0, 0, -2*f*n/(f-n), 0
46
+ ])
47
+ }
48
+
49
+ function m4translate(x, y, z) {
50
+ const m = m4id()
51
+ m[12] = x; m[13] = y; m[14] = z
52
+ return m
53
+ }
54
+
55
+ function m4scale(x, y, z) {
56
+ return new Float32Array([x,0,0,0, 0,y,0,0, 0,0,z,0, 0,0,0,1])
57
+ }
58
+
59
+ function m4rotate(angleDeg, x, y, z) {
60
+ const a = angleDeg * Math.PI / 180
61
+ const c = Math.cos(a), s = Math.sin(a)
62
+ const len = Math.sqrt(x*x + y*y + z*z)
63
+ if (len === 0) return m4id()
64
+ x /= len; y /= len; z /= len
65
+ const t = 1 - c
66
+ return new Float32Array([
67
+ t*x*x+c, t*x*y+s*z, t*x*z-s*y, 0,
68
+ t*x*y-s*z, t*y*y+c, t*y*z+s*x, 0,
69
+ t*x*z+s*y, t*y*z-s*x, t*z*z+c, 0,
70
+ 0, 0, 0, 1
71
+ ])
72
+ }
73
+
74
+ // Immediate mode: stride in floats per vertex: pos(3)+color(4)+uv(2)+normal(3)
75
+ const IM_STRIDE = 12
76
+
77
+ const IM_VERT_SRC = `#version 300 es
78
+ in vec3 a_pos; in vec4 a_col; in vec2 a_uv; in vec3 a_nrm;
79
+ uniform mat4 u_mv; uniform mat4 u_proj;
80
+ out vec4 v_col; out vec2 v_uv;
81
+ void main() {
82
+ gl_Position = u_proj * u_mv * vec4(a_pos, 1.0);
83
+ v_col = a_col; v_uv = a_uv;
84
+ }`
85
+
86
+ const IM_FRAG_SRC = `#version 300 es
87
+ precision mediump float;
88
+ in vec4 v_col; in vec2 v_uv;
89
+ uniform sampler2D u_tex; uniform int u_use_tex;
90
+ out vec4 fragColor;
91
+ void main() {
92
+ fragColor = u_use_tex != 0 ? v_col * texture(u_tex, v_uv) : v_col;
93
+ }`
94
+
95
+ // GL_QUADS → triangles: every 4 verts → 2 tris
96
+ function quadsToTris(verts, stride) {
97
+ const out = []
98
+ for (let i = 0; i < verts.length; i += stride * 4) {
99
+ const v = (j) => verts.slice(i + j*stride, i + (j+1)*stride)
100
+ out.push(...v(0), ...v(1), ...v(2))
101
+ out.push(...v(0), ...v(2), ...v(3))
102
+ }
103
+ return out
104
+ }
105
+
106
+ export class GL {
107
+ constructor({ memory } = {}) {
108
+ this._memory = memory || null
109
+ this._gl = null
110
+ this._instance = null
111
+
112
+ // Object registries: integer ID → WebGL object
113
+ this._buffers = new Map()
114
+ this._textures = new Map()
115
+ this._vaos = new Map()
116
+ this._shaders = new Map()
117
+ this._programs = new Map()
118
+ this._fbos = new Map()
119
+ this._rbos = new Map()
120
+ this._ulocs = new Map() // uniform location ID → WebGLUniformLocation
121
+ this._nextId = 1
122
+
123
+ // String allocation cache (same pattern as Glfw)
124
+ this._strCache = new Map()
125
+
126
+ // Fixed-function matrix stacks
127
+ this._matMode = 0x1700 // GL_MODELVIEW
128
+ this._mvStack = [m4id()]
129
+ this._projStack = [m4id()]
130
+ this._texStack = [m4id()]
131
+
132
+ // Immediate mode state
133
+ this._imMode = -1
134
+ this._imVerts = []
135
+ this._imColor = [1, 1, 1, 1]
136
+ this._imNormal = [0, 0, 1]
137
+ this._imUV = [0, 0]
138
+ this._imVao = null
139
+ this._imVbo = null
140
+ this._imProg = null
141
+ this._imLocs = null // uniform locations for im program
142
+ this._imTex = false
143
+
144
+ // Bind all methods for use as wasm imports
145
+ const proto = Object.getPrototypeOf(this)
146
+ for (const key of Object.getOwnPropertyNames(proto)) {
147
+ if (key === 'constructor') continue
148
+ const desc = Object.getOwnPropertyDescriptor(proto, key)
149
+ if (typeof desc.value === 'function') this[key] = desc.value.bind(this)
150
+ }
151
+ }
152
+
153
+ // Call after instantiation with the wasm exports object (mirrors WasiPreview1.start).
154
+ // Call setGL(glfw.getContextGL()) after exports._start() to wire the WebGL2 context.
155
+ start(exports) {
156
+ this._instance = { exports }
157
+ if (!this._memory && exports.memory) this._memory = exports.memory
158
+ }
159
+
160
+ // Legacy: call with the full WebAssembly.Instance object.
161
+ setInstance(instance) {
162
+ this._instance = instance
163
+ if (!this._memory && instance.exports.memory) {
164
+ this._memory = instance.exports.memory
165
+ }
166
+ }
167
+
168
+ setGL(gl) {
169
+ this._gl = gl
170
+ this._initImmediate()
171
+ }
172
+
173
+ // ── Memory helpers ──────────────────────────────────────────────────────────
174
+
175
+ get _view() { return new DataView(this._memory.buffer) }
176
+
177
+ _readStr(ptr) {
178
+ if (!ptr) return ''
179
+ const buf = new Uint8Array(this._memory.buffer)
180
+ let end = ptr; while (buf[end]) end++
181
+ return dec.decode(buf.subarray(ptr, end))
182
+ }
183
+
184
+ _wi32(ptr, v) { if (ptr) this._view.setInt32(ptr, v | 0, true) }
185
+ _wf32(ptr, v) { if (ptr) this._view.setFloat32(ptr, v, true) }
186
+
187
+ _allocStr(str) {
188
+ if (!str) return 0
189
+ if (this._strCache.has(str)) return this._strCache.get(str)
190
+ const fn = this._instance?.exports?.malloc
191
+ if (!fn) return 0
192
+ const bytes = enc.encode(str + '\0')
193
+ const ptr = fn(bytes.length)
194
+ new Uint8Array(this._memory.buffer, ptr, bytes.length).set(bytes)
195
+ this._strCache.set(str, ptr)
196
+ return ptr
197
+ }
198
+
199
+ _f32s(ptr, n) {
200
+ // Return Float32Array view into wasm memory (ptr must be 4-byte aligned)
201
+ if (!ptr) return null
202
+ return new Float32Array(this._memory.buffer, ptr, n)
203
+ }
204
+
205
+ _i32s(ptr, n) {
206
+ if (!ptr) return null
207
+ return new Int32Array(this._memory.buffer, ptr, n)
208
+ }
209
+
210
+ _bytes(ptr, n) {
211
+ if (!ptr) return null
212
+ return new Uint8Array(this._memory.buffer, ptr, n)
213
+ }
214
+
215
+ // ── Immediate mode setup ────────────────────────────────────────────────────
216
+
217
+ _initImmediate() {
218
+ const g = this._gl
219
+ const vs = g.createShader(g.VERTEX_SHADER)
220
+ g.shaderSource(vs, IM_VERT_SRC)
221
+ g.compileShader(vs)
222
+ const fs = g.createShader(g.FRAGMENT_SHADER)
223
+ g.shaderSource(fs, IM_FRAG_SRC)
224
+ g.compileShader(fs)
225
+ const prog = g.createProgram()
226
+ g.attachShader(prog, vs); g.attachShader(prog, fs)
227
+ g.linkProgram(prog)
228
+ g.deleteShader(vs); g.deleteShader(fs)
229
+
230
+ this._imProg = prog
231
+ this._imLocs = {
232
+ mv: g.getUniformLocation(prog, 'u_mv'),
233
+ proj: g.getUniformLocation(prog, 'u_proj'),
234
+ tex: g.getUniformLocation(prog, 'u_tex'),
235
+ useTex: g.getUniformLocation(prog, 'u_use_tex'),
236
+ }
237
+
238
+ this._imVao = g.createVertexArray()
239
+ g.bindVertexArray(this._imVao)
240
+ this._imVbo = g.createBuffer()
241
+ g.bindBuffer(g.ARRAY_BUFFER, this._imVbo)
242
+
243
+ const stride = IM_STRIDE * 4
244
+ const al = (name, size, off) => {
245
+ const loc = g.getAttribLocation(prog, name)
246
+ g.vertexAttribPointer(loc, size, g.FLOAT, false, stride, off * 4)
247
+ g.enableVertexAttribArray(loc)
248
+ }
249
+ al('a_pos', 3, 0)
250
+ al('a_col', 4, 3)
251
+ al('a_uv', 2, 7)
252
+ al('a_nrm', 3, 9)
253
+
254
+ g.bindVertexArray(null)
255
+ g.bindBuffer(g.ARRAY_BUFFER, null)
256
+ }
257
+
258
+ _flushImmediate() {
259
+ if (!this._imVerts.length) return
260
+ const g = this._gl
261
+
262
+ let mode = this._imMode
263
+ let verts = this._imVerts
264
+
265
+ // Emulate GL_QUADS → GL_TRIANGLES
266
+ if (mode === 7 /*GL_QUADS*/) {
267
+ verts = quadsToTris(verts, IM_STRIDE)
268
+ mode = 4 /*GL_TRIANGLES*/
269
+ }
270
+ // GL_POLYGON treated as triangle fan
271
+ if (mode === 9 /*GL_POLYGON*/) mode = 6 /*GL_TRIANGLE_FAN*/
272
+
273
+ const data = new Float32Array(verts)
274
+
275
+ // Save state
276
+ const prevProg = g.getParameter(g.CURRENT_PROGRAM)
277
+ const prevVao = g.getParameter(g.VERTEX_ARRAY_BINDING)
278
+ const prevVbo = g.getParameter(g.ARRAY_BUFFER_BINDING)
279
+
280
+ g.useProgram(this._imProg)
281
+ g.uniformMatrix4fv(this._imLocs.mv, false, this._mvStack[this._mvStack.length-1])
282
+ g.uniformMatrix4fv(this._imLocs.proj, false, this._projStack[this._projStack.length-1])
283
+ g.uniform1i(this._imLocs.useTex, this._imTex ? 1 : 0)
284
+
285
+ g.bindVertexArray(this._imVao)
286
+ g.bindBuffer(g.ARRAY_BUFFER, this._imVbo)
287
+ g.bufferData(g.ARRAY_BUFFER, data, g.STREAM_DRAW)
288
+ g.drawArrays(mode, 0, verts.length / IM_STRIDE)
289
+
290
+ // Restore state
291
+ g.bindVertexArray(prevVao)
292
+ g.bindBuffer(g.ARRAY_BUFFER, prevVbo)
293
+ g.useProgram(prevProg)
294
+
295
+ this._imVerts = []
296
+ }
297
+
298
+ _curMatrix() {
299
+ if (this._matMode === 0x1701) return this._projStack
300
+ if (this._matMode === 0x1702) return this._texStack
301
+ return this._mvStack
302
+ }
303
+
304
+ // ── Clear ───────────────────────────────────────────────────────────────────
305
+
306
+ glClearColor(r, g, b, a) { this._gl.clearColor(r, g, b, a) }
307
+ glClearDepth(d) { this._gl.clearDepth(d) }
308
+ glClearDepthf(d) { this._gl.clearDepth(d) }
309
+ glClearStencil(s) { this._gl.clearStencil(s) }
310
+ glClear(mask) { this._gl.clear(mask) }
311
+
312
+ // ── State ───────────────────────────────────────────────────────────────────
313
+
314
+ glEnable(cap) { this._gl.enable(cap) }
315
+ glDisable(cap) { this._gl.disable(cap) }
316
+ glIsEnabled(cap) { return this._gl.isEnabled(cap) ? 1 : 0 }
317
+ glBlendFunc(sfactor, dfactor) { this._gl.blendFunc(sfactor, dfactor) }
318
+ glBlendFuncSeparate(srcRGB, dstRGB, srcA, dstA) { this._gl.blendFuncSeparate(srcRGB, dstRGB, srcA, dstA) }
319
+ glBlendEquation(mode) { this._gl.blendEquation(mode) }
320
+ glBlendEquationSeparate(modeRGB, modeA) { this._gl.blendEquationSeparate(modeRGB, modeA) }
321
+ glBlendColor(r, g, b, a) { this._gl.blendColor(r, g, b, a) }
322
+ glDepthFunc(func) { this._gl.depthFunc(func) }
323
+ glDepthMask(flag) { this._gl.depthMask(!!flag) }
324
+ glDepthRange(near, far) { this._gl.depthRange(near, far) }
325
+ glDepthRangef(near, far) { this._gl.depthRange(near, far) }
326
+ glColorMask(r, g, b, a) { this._gl.colorMask(!!r, !!g, !!b, !!a) }
327
+ glCullFace(mode) { this._gl.cullFace(mode) }
328
+ glFrontFace(mode) { this._gl.frontFace(mode) }
329
+ glLineWidth(w) { this._gl.lineWidth(w) }
330
+ glPolygonOffset(factor, units) { this._gl.polygonOffset(factor, units) }
331
+ glScissor(x, y, w, h) { this._gl.scissor(x, y, w, h) }
332
+ glViewport(x, y, w, h) { this._gl.viewport(x, y, w, h) }
333
+ glSampleCoverage(value, invert) { this._gl.sampleCoverage(value, !!invert) }
334
+ glStencilFunc(func, ref, mask) { this._gl.stencilFunc(func, ref, mask) }
335
+ glStencilFuncSeparate(face, f, r, m) { this._gl.stencilFuncSeparate(face, f, r, m) }
336
+ glStencilOp(fail, zfail, zpass) { this._gl.stencilOp(fail, zfail, zpass) }
337
+ glStencilOpSeparate(face, f, zf, zp) { this._gl.stencilOpSeparate(face, f, zf, zp) }
338
+ glStencilMask(mask) { this._gl.stencilMask(mask) }
339
+ glStencilMaskSeparate(face, mask) { this._gl.stencilMaskSeparate(face, mask) }
340
+ glPixelStorei(pname, param) { this._gl.pixelStorei(pname, param) }
341
+ glFinish() { this._gl.finish() }
342
+ glFlush() { this._gl.flush() }
343
+
344
+ // ── Buffers ─────────────────────────────────────────────────────────────────
345
+
346
+ glGenBuffers(n, ptr) {
347
+ for (let i = 0; i < n; i++) {
348
+ const id = this._nextId++
349
+ this._buffers.set(id, this._gl.createBuffer())
350
+ this._wi32(ptr + i * 4, id)
351
+ }
352
+ }
353
+
354
+ glDeleteBuffers(n, ptr) {
355
+ for (let i = 0; i < n; i++) {
356
+ const id = this._view.getInt32(ptr + i * 4, true)
357
+ const buf = this._buffers.get(id)
358
+ if (buf) { this._gl.deleteBuffer(buf); this._buffers.delete(id) }
359
+ }
360
+ }
361
+
362
+ glBindBuffer(target, id) {
363
+ this._gl.bindBuffer(target, this._buffers.get(id) ?? null)
364
+ }
365
+
366
+ glBufferData(target, size, dataPtr, usage) {
367
+ const data = dataPtr ? new Uint8Array(this._memory.buffer, dataPtr, size) : null
368
+ this._gl.bufferData(target, data ?? size, usage)
369
+ }
370
+
371
+ glBufferSubData(target, offset, size, dataPtr) {
372
+ const data = new Uint8Array(this._memory.buffer, dataPtr, size)
373
+ this._gl.bufferSubData(target, offset, data)
374
+ }
375
+
376
+ glIsBuffer(id) { return this._buffers.has(id) ? 1 : 0 }
377
+
378
+ // ── Vertex Arrays ────────────────────────────────────────────────────────────
379
+
380
+ glGenVertexArrays(n, ptr) {
381
+ for (let i = 0; i < n; i++) {
382
+ const id = this._nextId++
383
+ this._vaos.set(id, this._gl.createVertexArray())
384
+ this._wi32(ptr + i * 4, id)
385
+ }
386
+ }
387
+
388
+ glDeleteVertexArrays(n, ptr) {
389
+ for (let i = 0; i < n; i++) {
390
+ const id = this._view.getInt32(ptr + i * 4, true)
391
+ const vao = this._vaos.get(id)
392
+ if (vao) { this._gl.deleteVertexArray(vao); this._vaos.delete(id) }
393
+ }
394
+ }
395
+
396
+ glBindVertexArray(id) {
397
+ this._gl.bindVertexArray(this._vaos.get(id) ?? null)
398
+ }
399
+
400
+ glIsVertexArray(id) { return this._vaos.has(id) ? 1 : 0 }
401
+
402
+ glEnableVertexAttribArray(index) { this._gl.enableVertexAttribArray(index) }
403
+ glDisableVertexAttribArray(index) { this._gl.disableVertexAttribArray(index) }
404
+
405
+ glVertexAttribPointer(index, size, type, normalized, stride, offset) {
406
+ this._gl.vertexAttribPointer(index, size, type, !!normalized, stride, offset)
407
+ }
408
+
409
+ glVertexAttribIPointer(index, size, type, stride, offset) {
410
+ this._gl.vertexAttribIPointer(index, size, type, stride, offset)
411
+ }
412
+
413
+ glVertexAttrib1f(i, x) { this._gl.vertexAttrib1f(i, x) }
414
+ glVertexAttrib2f(i, x, y) { this._gl.vertexAttrib2f(i, x, y) }
415
+ glVertexAttrib3f(i, x, y, z) { this._gl.vertexAttrib3f(i, x, y, z) }
416
+ glVertexAttrib4f(i, x, y, z, w) { this._gl.vertexAttrib4f(i, x, y, z, w) }
417
+
418
+ glVertexAttrib1fv(i, ptr) { this._gl.vertexAttrib1fv(i, this._f32s(ptr, 1)) }
419
+ glVertexAttrib2fv(i, ptr) { this._gl.vertexAttrib2fv(i, this._f32s(ptr, 2)) }
420
+ glVertexAttrib3fv(i, ptr) { this._gl.vertexAttrib3fv(i, this._f32s(ptr, 3)) }
421
+ glVertexAttrib4fv(i, ptr) { this._gl.vertexAttrib4fv(i, this._f32s(ptr, 4)) }
422
+
423
+ glVertexAttribDivisor(index, divisor) { this._gl.vertexAttribDivisor(index, divisor) }
424
+
425
+ // ── Shaders ──────────────────────────────────────────────────────────────────
426
+
427
+ glCreateShader(type) {
428
+ const id = this._nextId++
429
+ this._shaders.set(id, this._gl.createShader(type))
430
+ return id
431
+ }
432
+
433
+ glDeleteShader(id) {
434
+ const s = this._shaders.get(id)
435
+ if (s) { this._gl.deleteShader(s); this._shaders.delete(id) }
436
+ }
437
+
438
+ glShaderSource(id, count, stringsPtrPtr, lengthsPtrPtr) {
439
+ const v = this._view
440
+ let src = ''
441
+ for (let i = 0; i < count; i++) {
442
+ const strPtr = v.getInt32(stringsPtrPtr + i * 4, true)
443
+ const len = lengthsPtrPtr ? v.getInt32(lengthsPtrPtr + i * 4, true) : -1
444
+ if (len >= 0) {
445
+ src += dec.decode(new Uint8Array(this._memory.buffer, strPtr, len))
446
+ } else {
447
+ src += this._readStr(strPtr)
448
+ }
449
+ }
450
+ this._gl.shaderSource(this._shaders.get(id), src)
451
+ }
452
+
453
+ glCompileShader(id) {
454
+ this._gl.compileShader(this._shaders.get(id))
455
+ }
456
+
457
+ glGetShaderiv(id, pname, paramsPtr) {
458
+ const g = this._gl
459
+ const s = this._shaders.get(id)
460
+ let val
461
+ if (pname === 0x8B81 /*COMPILE_STATUS*/) val = g.getShaderParameter(s, pname) ? 1 : 0
462
+ else if (pname === 0x8B84 /*INFO_LOG_LENGTH*/) val = (g.getShaderInfoLog(s) || '').length + 1
463
+ else if (pname === 0x8B4F /*SHADER_TYPE*/) val = g.getShaderParameter(s, pname)
464
+ else if (pname === 0x8B80 /*DELETE_STATUS*/) val = g.getShaderParameter(s, pname) ? 1 : 0
465
+ else val = 0
466
+ this._wi32(paramsPtr, val)
467
+ }
468
+
469
+ glGetShaderInfoLog(id, maxLen, lengthPtr, logPtr) {
470
+ const log = this._gl.getShaderInfoLog(this._shaders.get(id)) || ''
471
+ const bytes = enc.encode(log.substring(0, maxLen - 1) + '\0')
472
+ new Uint8Array(this._memory.buffer, logPtr, bytes.length).set(bytes)
473
+ this._wi32(lengthPtr, bytes.length - 1)
474
+ }
475
+
476
+ glIsShader(id) { return this._shaders.has(id) ? 1 : 0 }
477
+
478
+ // ── Programs ─────────────────────────────────────────────────────────────────
479
+
480
+ glCreateProgram() {
481
+ const id = this._nextId++
482
+ this._programs.set(id, this._gl.createProgram())
483
+ return id
484
+ }
485
+
486
+ glDeleteProgram(id) {
487
+ const p = this._programs.get(id)
488
+ if (p) { this._gl.deleteProgram(p); this._programs.delete(id) }
489
+ }
490
+
491
+ glAttachShader(progId, shaderId) {
492
+ this._gl.attachShader(this._programs.get(progId), this._shaders.get(shaderId))
493
+ }
494
+
495
+ glDetachShader(progId, shaderId) {
496
+ this._gl.detachShader(this._programs.get(progId), this._shaders.get(shaderId))
497
+ }
498
+
499
+ glBindAttribLocation(progId, index, namePtr) {
500
+ this._gl.bindAttribLocation(this._programs.get(progId), index, this._readStr(namePtr))
501
+ }
502
+
503
+ glLinkProgram(id) {
504
+ this._gl.linkProgram(this._programs.get(id))
505
+ }
506
+
507
+ glValidateProgram(id) {
508
+ this._gl.validateProgram(this._programs.get(id))
509
+ }
510
+
511
+ glUseProgram(id) {
512
+ this._gl.useProgram(id ? this._programs.get(id) : null)
513
+ }
514
+
515
+ glGetProgramiv(id, pname, paramsPtr) {
516
+ const g = this._gl
517
+ const p = this._programs.get(id)
518
+ let val
519
+ switch (pname) {
520
+ case 0x8B82: val = g.getProgramParameter(p, pname) ? 1 : 0; break // LINK_STATUS
521
+ case 0x8B80: val = g.getProgramParameter(p, pname) ? 1 : 0; break // DELETE_STATUS
522
+ case 0x8B83: val = g.getProgramParameter(p, pname) ? 1 : 0; break // VALIDATE_STATUS
523
+ case 0x8B85: val = (g.getProgramInfoLog(p) || '').length + 1; break // INFO_LOG_LENGTH
524
+ default: val = g.getProgramParameter(p, pname) ?? 0
525
+ }
526
+ this._wi32(paramsPtr, typeof val === 'boolean' ? (val ? 1 : 0) : val)
527
+ }
528
+
529
+ glGetProgramInfoLog(id, maxLen, lengthPtr, logPtr) {
530
+ const log = this._gl.getProgramInfoLog(this._programs.get(id)) || ''
531
+ const bytes = enc.encode(log.substring(0, maxLen - 1) + '\0')
532
+ new Uint8Array(this._memory.buffer, logPtr, bytes.length).set(bytes)
533
+ this._wi32(lengthPtr, bytes.length - 1)
534
+ }
535
+
536
+ glGetAttribLocation(progId, namePtr) {
537
+ return this._gl.getAttribLocation(this._programs.get(progId), this._readStr(namePtr))
538
+ }
539
+
540
+ glIsProgram(id) { return this._programs.has(id) ? 1 : 0 }
541
+
542
+ // ── Uniforms ─────────────────────────────────────────────────────────────────
543
+
544
+ glGetUniformLocation(progId, namePtr) {
545
+ const loc = this._gl.getUniformLocation(this._programs.get(progId), this._readStr(namePtr))
546
+ if (loc == null) return -1
547
+ const id = this._nextId++
548
+ this._ulocs.set(id, loc)
549
+ return id
550
+ }
551
+
552
+ _ul(id) { return id >= 0 ? this._ulocs.get(id) : null }
553
+
554
+ glUniform1f(l, x) { this._gl.uniform1f(this._ul(l), x) }
555
+ glUniform2f(l, x, y) { this._gl.uniform2f(this._ul(l), x, y) }
556
+ glUniform3f(l, x, y, z) { this._gl.uniform3f(this._ul(l), x, y, z) }
557
+ glUniform4f(l, x, y, z, w) { this._gl.uniform4f(this._ul(l), x, y, z, w) }
558
+ glUniform1i(l, x) { this._gl.uniform1i(this._ul(l), x) }
559
+ glUniform2i(l, x, y) { this._gl.uniform2i(this._ul(l), x, y) }
560
+ glUniform3i(l, x, y, z) { this._gl.uniform3i(this._ul(l), x, y, z) }
561
+ glUniform4i(l, x, y, z, w) { this._gl.uniform4i(this._ul(l), x, y, z, w) }
562
+ glUniform1ui(l, x) { this._gl.uniform1ui(this._ul(l), x) }
563
+ glUniform2ui(l, x, y) { this._gl.uniform2ui(this._ul(l), x, y) }
564
+ glUniform3ui(l, x, y, z) { this._gl.uniform3ui(this._ul(l), x, y, z) }
565
+ glUniform4ui(l, x, y, z, w) { this._gl.uniform4ui(this._ul(l), x, y, z, w) }
566
+
567
+ glUniform1fv(l, n, ptr) { this._gl.uniform1fv(this._ul(l), this._f32s(ptr, n*1)) }
568
+ glUniform2fv(l, n, ptr) { this._gl.uniform2fv(this._ul(l), this._f32s(ptr, n*2)) }
569
+ glUniform3fv(l, n, ptr) { this._gl.uniform3fv(this._ul(l), this._f32s(ptr, n*3)) }
570
+ glUniform4fv(l, n, ptr) { this._gl.uniform4fv(this._ul(l), this._f32s(ptr, n*4)) }
571
+ glUniform1iv(l, n, ptr) { this._gl.uniform1iv(this._ul(l), this._i32s(ptr, n*1)) }
572
+ glUniform2iv(l, n, ptr) { this._gl.uniform2iv(this._ul(l), this._i32s(ptr, n*2)) }
573
+ glUniform3iv(l, n, ptr) { this._gl.uniform3iv(this._ul(l), this._i32s(ptr, n*3)) }
574
+ glUniform4iv(l, n, ptr) { this._gl.uniform4iv(this._ul(l), this._i32s(ptr, n*4)) }
575
+ glUniform1uiv(l, n, ptr) { this._gl.uniform1uiv(this._ul(l), new Uint32Array(this._memory.buffer, ptr, n)) }
576
+ glUniform2uiv(l, n, ptr) { this._gl.uniform2uiv(this._ul(l), new Uint32Array(this._memory.buffer, ptr, n*2)) }
577
+ glUniform3uiv(l, n, ptr) { this._gl.uniform3uiv(this._ul(l), new Uint32Array(this._memory.buffer, ptr, n*3)) }
578
+ glUniform4uiv(l, n, ptr) { this._gl.uniform4uiv(this._ul(l), new Uint32Array(this._memory.buffer, ptr, n*4)) }
579
+
580
+ glUniformMatrix2fv(l, n, t, ptr) { this._gl.uniformMatrix2fv(this._ul(l), !!t, this._f32s(ptr, n*4)) }
581
+ glUniformMatrix3fv(l, n, t, ptr) { this._gl.uniformMatrix3fv(this._ul(l), !!t, this._f32s(ptr, n*9)) }
582
+ glUniformMatrix4fv(l, n, t, ptr) { this._gl.uniformMatrix4fv(this._ul(l), !!t, this._f32s(ptr, n*16)) }
583
+ glUniformMatrix2x3fv(l,n,t,ptr) { this._gl.uniformMatrix2x3fv(this._ul(l), !!t, this._f32s(ptr, n*6)) }
584
+ glUniformMatrix3x2fv(l,n,t,ptr) { this._gl.uniformMatrix3x2fv(this._ul(l), !!t, this._f32s(ptr, n*6)) }
585
+ glUniformMatrix2x4fv(l,n,t,ptr) { this._gl.uniformMatrix2x4fv(this._ul(l), !!t, this._f32s(ptr, n*8)) }
586
+ glUniformMatrix4x2fv(l,n,t,ptr) { this._gl.uniformMatrix4x2fv(this._ul(l), !!t, this._f32s(ptr, n*8)) }
587
+ glUniformMatrix3x4fv(l,n,t,ptr) { this._gl.uniformMatrix3x4fv(this._ul(l), !!t, this._f32s(ptr, n*12)) }
588
+ glUniformMatrix4x3fv(l,n,t,ptr) { this._gl.uniformMatrix4x3fv(this._ul(l), !!t, this._f32s(ptr, n*12)) }
589
+
590
+ // ── Textures ─────────────────────────────────────────────────────────────────
591
+
592
+ glGenTextures(n, ptr) {
593
+ for (let i = 0; i < n; i++) {
594
+ const id = this._nextId++
595
+ this._textures.set(id, this._gl.createTexture())
596
+ this._wi32(ptr + i * 4, id)
597
+ }
598
+ }
599
+
600
+ glDeleteTextures(n, ptr) {
601
+ for (let i = 0; i < n; i++) {
602
+ const id = this._view.getInt32(ptr + i * 4, true)
603
+ const t = this._textures.get(id)
604
+ if (t) { this._gl.deleteTexture(t); this._textures.delete(id) }
605
+ }
606
+ }
607
+
608
+ glBindTexture(target, id) {
609
+ this._gl.bindTexture(target, id ? this._textures.get(id) : null)
610
+ }
611
+
612
+ glActiveTexture(unit) { this._gl.activeTexture(unit) }
613
+
614
+ glTexImage2D(target, level, internalFormat, width, height, border, format, type, dataPtr) {
615
+ const g = this._gl
616
+ if (!dataPtr) {
617
+ g.texImage2D(target, level, internalFormat, width, height, border, format, type, null)
618
+ return
619
+ }
620
+ const bytesPerPixel = this._glBytesPerPixel(format, type)
621
+ const size = width * height * bytesPerPixel
622
+ const src = this._typedView(type, dataPtr, size / this._glTypeSize(type))
623
+ g.texImage2D(target, level, internalFormat, width, height, border, format, type, src)
624
+ }
625
+
626
+ glTexImage3D(target, level, internalFmt, w, h, depth, border, fmt, type, dataPtr) {
627
+ const g = this._gl
628
+ if (!dataPtr) {
629
+ g.texImage3D(target, level, internalFmt, w, h, depth, border, fmt, type, null)
630
+ return
631
+ }
632
+ const bytesPerPixel = this._glBytesPerPixel(fmt, type)
633
+ const size = w * h * depth * bytesPerPixel
634
+ const src = this._typedView(type, dataPtr, size / this._glTypeSize(type))
635
+ g.texImage3D(target, level, internalFmt, w, h, depth, border, fmt, type, src)
636
+ }
637
+
638
+ glTexSubImage2D(target, level, xoff, yoff, w, h, format, type, dataPtr) {
639
+ const size = w * h * this._glBytesPerPixel(format, type)
640
+ const src = this._typedView(type, dataPtr, size / this._glTypeSize(type))
641
+ this._gl.texSubImage2D(target, level, xoff, yoff, w, h, format, type, src)
642
+ }
643
+
644
+ glTexParameteri(target, pname, param) { this._gl.texParameteri(target, pname, param) }
645
+ glTexParameterf(target, pname, param) { this._gl.texParameterf(target, pname, param) }
646
+ glGenerateMipmap(target) { this._gl.generateMipmap(target) }
647
+ glIsTexture(id) { return this._textures.has(id) ? 1 : 0 }
648
+
649
+ _glTypeSize(type) {
650
+ const map = { 0x1400:1, 0x1401:1, 0x1402:2, 0x1403:2, 0x1404:4, 0x1405:4, 0x1406:4 }
651
+ return map[type] ?? 1
652
+ }
653
+
654
+ _glBytesPerPixel(format, type) {
655
+ const channels = { 0x1903:1, 0x1902:1, 0x1904:3, 0x1908:4, 0x8227:2, 0x8228:4, 0x8D94:1, 0x8D95:2, 0x8D96:3, 0x8D97:4 }
656
+ return (channels[format] ?? 4) * this._glTypeSize(type)
657
+ }
658
+
659
+ _typedView(type, ptr, count) {
660
+ switch (type) {
661
+ case 0x1406: return new Float32Array(this._memory.buffer, ptr, count) // FLOAT
662
+ case 0x1405: return new Uint32Array(this._memory.buffer, ptr, count) // UNSIGNED_INT
663
+ case 0x1404: return new Int32Array(this._memory.buffer, ptr, count) // INT
664
+ case 0x1403: return new Uint16Array(this._memory.buffer, ptr, count) // UNSIGNED_SHORT
665
+ case 0x1402: return new Int16Array(this._memory.buffer, ptr, count) // SHORT
666
+ case 0x1401: return new Uint8Array(this._memory.buffer, ptr, count) // UNSIGNED_BYTE
667
+ case 0x1400: return new Int8Array(this._memory.buffer, ptr, count) // BYTE
668
+ default: return new Uint8Array(this._memory.buffer, ptr, count)
669
+ }
670
+ }
671
+
672
+ // ── Framebuffers ─────────────────────────────────────────────────────────────
673
+
674
+ glGenFramebuffers(n, ptr) {
675
+ for (let i = 0; i < n; i++) {
676
+ const id = this._nextId++
677
+ this._fbos.set(id, this._gl.createFramebuffer())
678
+ this._wi32(ptr + i * 4, id)
679
+ }
680
+ }
681
+
682
+ glDeleteFramebuffers(n, ptr) {
683
+ for (let i = 0; i < n; i++) {
684
+ const id = this._view.getInt32(ptr + i * 4, true)
685
+ const f = this._fbos.get(id)
686
+ if (f) { this._gl.deleteFramebuffer(f); this._fbos.delete(id) }
687
+ }
688
+ }
689
+
690
+ glBindFramebuffer(target, id) {
691
+ this._gl.bindFramebuffer(target, id ? this._fbos.get(id) : null)
692
+ }
693
+
694
+ glFramebufferTexture2D(target, attachment, texTarget, texId, level) {
695
+ this._gl.framebufferTexture2D(target, attachment, texTarget, this._textures.get(texId) ?? null, level)
696
+ }
697
+
698
+ glFramebufferRenderbuffer(target, attachment, rbTarget, rbId) {
699
+ this._gl.framebufferRenderbuffer(target, attachment, rbTarget, this._rbos.get(rbId) ?? null)
700
+ }
701
+
702
+ glCheckFramebufferStatus(target) { return this._gl.checkFramebufferStatus(target) }
703
+ glIsFramebuffer(id) { return this._fbos.has(id) ? 1 : 0 }
704
+
705
+ glGenRenderbuffers(n, ptr) {
706
+ for (let i = 0; i < n; i++) {
707
+ const id = this._nextId++
708
+ this._rbos.set(id, this._gl.createRenderbuffer())
709
+ this._wi32(ptr + i * 4, id)
710
+ }
711
+ }
712
+
713
+ glDeleteRenderbuffers(n, ptr) {
714
+ for (let i = 0; i < n; i++) {
715
+ const id = this._view.getInt32(ptr + i * 4, true)
716
+ const r = this._rbos.get(id)
717
+ if (r) { this._gl.deleteRenderbuffer(r); this._rbos.delete(id) }
718
+ }
719
+ }
720
+
721
+ glBindRenderbuffer(target, id) {
722
+ this._gl.bindRenderbuffer(target, id ? this._rbos.get(id) : null)
723
+ }
724
+
725
+ glRenderbufferStorage(target, internalFormat, w, h) {
726
+ this._gl.renderbufferStorage(target, internalFormat, w, h)
727
+ }
728
+
729
+ glRenderbufferStorageMultisample(target, samples, internalFormat, w, h) {
730
+ this._gl.renderbufferStorageMultisample(target, samples, internalFormat, w, h)
731
+ }
732
+
733
+ glIsRenderbuffer(id) { return this._rbos.has(id) ? 1 : 0 }
734
+
735
+ glBlitFramebuffer(sx0, sy0, sx1, sy1, dx0, dy0, dx1, dy1, mask, filter) {
736
+ this._gl.blitFramebuffer(sx0, sy0, sx1, sy1, dx0, dy0, dx1, dy1, mask, filter)
737
+ }
738
+
739
+ // ── Drawing ──────────────────────────────────────────────────────────────────
740
+
741
+ glDrawArrays(mode, first, count) { this._gl.drawArrays(mode, first, count) }
742
+
743
+ glDrawElements(mode, count, type, offset) {
744
+ this._gl.drawElements(mode, count, type, offset)
745
+ }
746
+
747
+ glDrawArraysInstanced(mode, first, count, instanceCount) {
748
+ this._gl.drawArraysInstanced(mode, first, count, instanceCount)
749
+ }
750
+
751
+ glDrawElementsInstanced(mode, count, type, offset, instanceCount) {
752
+ this._gl.drawElementsInstanced(mode, count, type, offset, instanceCount)
753
+ }
754
+
755
+ glDrawRangeElements(mode, start, end, count, type, offset) {
756
+ this._gl.drawRangeElements(mode, start, end, count, type, offset)
757
+ }
758
+
759
+ glMultiDrawArrays(mode, firstsPtr, countsPtr, drawCount) {
760
+ const v = this._view
761
+ for (let i = 0; i < drawCount; i++) {
762
+ const first = v.getInt32(firstsPtr + i * 4, true)
763
+ const count = v.getInt32(countsPtr + i * 4, true)
764
+ this._gl.drawArrays(mode, first, count)
765
+ }
766
+ }
767
+
768
+ // ── Read ─────────────────────────────────────────────────────────────────────
769
+
770
+ glReadPixels(x, y, w, h, format, type, dataPtr) {
771
+ const size = w * h * this._glBytesPerPixel(format, type)
772
+ const dst = this._typedView(type, dataPtr, size / this._glTypeSize(type))
773
+ this._gl.readPixels(x, y, w, h, format, type, dst)
774
+ }
775
+
776
+ // ── Queries / getters ────────────────────────────────────────────────────────
777
+
778
+ glGetError() { return this._gl.getError() }
779
+
780
+ glGetIntegerv(pname, ptr) {
781
+ const val = this._gl.getParameter(pname)
782
+ if (val == null) return
783
+ const v = this._view
784
+ if (typeof val === 'number' || typeof val === 'boolean') {
785
+ v.setInt32(ptr, (typeof val === 'boolean' ? (val ? 1 : 0) : val) | 0, true)
786
+ } else if (val instanceof Int32Array || val instanceof Uint32Array) {
787
+ for (let i = 0; i < val.length; i++) v.setInt32(ptr + i * 4, val[i], true)
788
+ } else if (val instanceof Float32Array) {
789
+ for (let i = 0; i < val.length; i++) v.setInt32(ptr + i * 4, val[i] | 0, true)
790
+ }
791
+ }
792
+
793
+ glGetFloatv(pname, ptr) {
794
+ const val = this._gl.getParameter(pname)
795
+ if (val == null) return
796
+ const v = this._view
797
+ if (typeof val === 'number') {
798
+ v.setFloat32(ptr, val, true)
799
+ } else if (val instanceof Float32Array || val instanceof Int32Array) {
800
+ for (let i = 0; i < val.length; i++) v.setFloat32(ptr + i * 4, val[i], true)
801
+ }
802
+ }
803
+
804
+ glGetBooleanv(pname, ptr) {
805
+ const val = this._gl.getParameter(pname)
806
+ new Uint8Array(this._memory.buffer)[ptr] = val ? 1 : 0
807
+ }
808
+
809
+ glGetString(name) {
810
+ const g = this._gl
811
+ const str = {
812
+ 0x1F00: 'WebGL', // GL_VENDOR
813
+ 0x1F01: g.getParameter(g.RENDERER), // GL_RENDERER
814
+ 0x1F02: '3.0.0 WebGL2', // GL_VERSION
815
+ 0x1F03: '', // GL_EXTENSIONS
816
+ 0x8B8C: '3.00 ES', // GL_SHADING_LANGUAGE_VERSION
817
+ }[name]
818
+ return str != null ? this._allocStr(str) : 0
819
+ }
820
+
821
+ glGetStringi(name, index) {
822
+ if (name === 0x1F03 /*GL_EXTENSIONS*/) {
823
+ const ext = this._gl.getSupportedExtensions() ?? []
824
+ return index < ext.length ? this._allocStr(ext[index]) : 0
825
+ }
826
+ return 0
827
+ }
828
+
829
+ glGetActiveUniform(progId, index, bufSize, lengthPtr, sizePtr, typePtr, namePtr) {
830
+ const info = this._gl.getActiveUniform(this._programs.get(progId), index)
831
+ if (!info) return
832
+ const bytes = enc.encode(info.name.substring(0, bufSize - 1) + '\0')
833
+ new Uint8Array(this._memory.buffer, namePtr, bytes.length).set(bytes)
834
+ this._wi32(lengthPtr, bytes.length - 1)
835
+ this._wi32(sizePtr, info.size)
836
+ this._wi32(typePtr, info.type)
837
+ }
838
+
839
+ glGetActiveAttrib(progId, index, bufSize, lengthPtr, sizePtr, typePtr, namePtr) {
840
+ const info = this._gl.getActiveAttrib(this._programs.get(progId), index)
841
+ if (!info) return
842
+ const bytes = enc.encode(info.name.substring(0, bufSize - 1) + '\0')
843
+ new Uint8Array(this._memory.buffer, namePtr, bytes.length).set(bytes)
844
+ this._wi32(lengthPtr, bytes.length - 1)
845
+ this._wi32(sizePtr, info.size)
846
+ this._wi32(typePtr, info.type)
847
+ }
848
+
849
+ glGetUniformfv(progId, locId, paramsPtr) {
850
+ const vals = this._gl.getUniform(this._programs.get(progId), this._ul(locId))
851
+ const v = this._view
852
+ if (typeof vals === 'number') { v.setFloat32(paramsPtr, vals, true); return }
853
+ if (vals?.length) for (let i = 0; i < vals.length; i++) v.setFloat32(paramsPtr + i*4, vals[i], true)
854
+ }
855
+
856
+ glGetUniformiv(progId, locId, paramsPtr) {
857
+ const vals = this._gl.getUniform(this._programs.get(progId), this._ul(locId))
858
+ const v = this._view
859
+ if (typeof vals === 'number') { v.setInt32(paramsPtr, vals, true); return }
860
+ if (vals?.length) for (let i = 0; i < vals.length; i++) v.setInt32(paramsPtr + i*4, vals[i], true)
861
+ }
862
+
863
+ // ── Fixed-function matrix stack ──────────────────────────────────────────────
864
+
865
+ glMatrixMode(mode) { this._matMode = mode }
866
+
867
+ glLoadIdentity() {
868
+ const s = this._curMatrix()
869
+ s[s.length - 1] = m4id()
870
+ }
871
+
872
+ glLoadMatrixf(ptr) {
873
+ const s = this._curMatrix()
874
+ s[s.length - 1] = new Float32Array(this._memory.buffer.slice(ptr, ptr + 64))
875
+ }
876
+
877
+ glLoadMatrixd(ptr) {
878
+ const s = this._curMatrix()
879
+ const v = this._view
880
+ const m = new Float32Array(16)
881
+ for (let i = 0; i < 16; i++) m[i] = v.getFloat64(ptr + i * 8, true)
882
+ s[s.length - 1] = m
883
+ }
884
+
885
+ glMultMatrixf(ptr) {
886
+ const s = this._curMatrix()
887
+ const b = new Float32Array(this._memory.buffer.slice(ptr, ptr + 64))
888
+ s[s.length - 1] = m4mul(s[s.length - 1], b)
889
+ }
890
+
891
+ glMultMatrixd(ptr) {
892
+ const s = this._curMatrix()
893
+ const v = this._view
894
+ const b = new Float32Array(16)
895
+ for (let i = 0; i < 16; i++) b[i] = v.getFloat64(ptr + i * 8, true)
896
+ s[s.length - 1] = m4mul(s[s.length - 1], b)
897
+ }
898
+
899
+ glPushMatrix() {
900
+ const s = this._curMatrix()
901
+ s.push(new Float32Array(s[s.length - 1]))
902
+ }
903
+
904
+ glPopMatrix() {
905
+ const s = this._curMatrix()
906
+ if (s.length > 1) s.pop()
907
+ }
908
+
909
+ glOrtho(l, r, b, t, n, f) {
910
+ const s = this._curMatrix()
911
+ s[s.length - 1] = m4mul(s[s.length - 1], m4ortho(l, r, b, t, n, f))
912
+ }
913
+
914
+ glOrthof(l, r, b, t, n, f) { this.glOrtho(l, r, b, t, n, f) }
915
+
916
+ glFrustum(l, r, b, t, n, f) {
917
+ const s = this._curMatrix()
918
+ s[s.length - 1] = m4mul(s[s.length - 1], m4frustum(l, r, b, t, n, f))
919
+ }
920
+
921
+ glTranslatef(x, y, z) {
922
+ const s = this._curMatrix()
923
+ s[s.length - 1] = m4mul(s[s.length - 1], m4translate(x, y, z))
924
+ }
925
+ glTranslated(x, y, z) { this.glTranslatef(x, y, z) }
926
+
927
+ glScalef(x, y, z) {
928
+ const s = this._curMatrix()
929
+ s[s.length - 1] = m4mul(s[s.length - 1], m4scale(x, y, z))
930
+ }
931
+ glScaled(x, y, z) { this.glScalef(x, y, z) }
932
+
933
+ glRotatef(angle, x, y, z) {
934
+ const s = this._curMatrix()
935
+ s[s.length - 1] = m4mul(s[s.length - 1], m4rotate(angle, x, y, z))
936
+ }
937
+ glRotated(angle, x, y, z) { this.glRotatef(angle, x, y, z) }
938
+
939
+ // ── Immediate mode ────────────────────────────────────────────────────────────
940
+
941
+ glBegin(mode) {
942
+ this._imMode = mode
943
+ this._imVerts = []
944
+ }
945
+
946
+ glEnd() {
947
+ this._flushImmediate()
948
+ this._imMode = -1
949
+ }
950
+
951
+ _pushVert(x, y, z) {
952
+ this._imVerts.push(
953
+ x, y, z,
954
+ ...this._imColor,
955
+ ...this._imUV,
956
+ ...this._imNormal
957
+ )
958
+ }
959
+
960
+ glVertex2f(x, y) { this._pushVert(x, y, 0) }
961
+ glVertex3f(x, y, z) { this._pushVert(x, y, z) }
962
+ glVertex4f(x, y, z, w) { this._pushVert(x/w, y/w, z/w) }
963
+ glVertex2i(x, y) { this._pushVert(x, y, 0) }
964
+ glVertex3i(x, y, z) { this._pushVert(x, y, z) }
965
+ glVertex2d(x, y) { this._pushVert(x, y, 0) }
966
+ glVertex3d(x, y, z) { this._pushVert(x, y, z) }
967
+
968
+ glVertex2fv(ptr) { const f = this._f32s(ptr, 2); this._pushVert(f[0], f[1], 0) }
969
+ glVertex3fv(ptr) { const f = this._f32s(ptr, 3); this._pushVert(f[0], f[1], f[2]) }
970
+
971
+ glColor3f(r, g, b) { this._imColor = [r, g, b, 1] }
972
+ glColor4f(r, g, b, a) { this._imColor = [r, g, b, a] }
973
+ glColor3d(r, g, b) { this._imColor = [r, g, b, 1] }
974
+ glColor4d(r, g, b, a) { this._imColor = [r, g, b, a] }
975
+ glColor3ub(r, g, b) { this._imColor = [r/255, g/255, b/255, 1] }
976
+ glColor4ub(r, g, b, a) { this._imColor = [r/255, g/255, b/255, a/255] }
977
+ glColor3i(r, g, b) { this._imColor = [r/0x7fffffff, g/0x7fffffff, b/0x7fffffff, 1] }
978
+ glColor4i(r, g, b, a) { this._imColor = [r/0x7fffffff, g/0x7fffffff, b/0x7fffffff, a/0x7fffffff] }
979
+ glColor3fv(ptr) { const f = this._f32s(ptr, 3); this._imColor = [f[0], f[1], f[2], 1] }
980
+ glColor4fv(ptr) { const f = this._f32s(ptr, 4); this._imColor = [f[0], f[1], f[2], f[3]] }
981
+
982
+ glNormal3f(x, y, z) { this._imNormal = [x, y, z] }
983
+ glNormal3d(x, y, z) { this._imNormal = [x, y, z] }
984
+ glNormal3fv(ptr) { const f = this._f32s(ptr, 3); this._imNormal = [f[0], f[1], f[2]] }
985
+
986
+ glTexCoord1f(s) { this._imUV = [s, 0] }
987
+ glTexCoord2f(s, t) { this._imUV = [s, t] }
988
+ glTexCoord3f(s, t, r) { this._imUV = [s, t] }
989
+ glTexCoord2fv(ptr) { const f = this._f32s(ptr, 2); this._imUV = [f[0], f[1]] }
990
+
991
+ glRectf(x1, y1, x2, y2) {
992
+ this.glBegin(7 /*GL_QUADS*/)
993
+ this._pushVert(x1, y1, 0); this._pushVert(x2, y1, 0)
994
+ this._pushVert(x2, y2, 0); this._pushVert(x1, y2, 0)
995
+ this.glEnd()
996
+ }
997
+
998
+ glCopyTexImage2D(target, level, internalFormat, x, y, w, h, border) {
999
+ this._gl.copyTexImage2D(target, level, internalFormat, x, y, w, h, border)
1000
+ }
1001
+ glCopyTexSubImage2D(target, level, xoff, yoff, x, y, w, h) {
1002
+ this._gl.copyTexSubImage2D(target, level, xoff, yoff, x, y, w, h)
1003
+ }
1004
+
1005
+ /*
1006
+
1007
+ // ── Legacy stubs (no-ops or trivial mappings) ─────────────────────────────────
1008
+
1009
+ glEnableClientState() {}
1010
+ glDisableClientState() {}
1011
+ glClientActiveTexture() {}
1012
+ glShadeModel() {}
1013
+ glTexEnvi() {}
1014
+ glTexEnvf() {}
1015
+ glTexEnvfv() {}
1016
+ glFogi() {}
1017
+ glFogf() {}
1018
+ glFogfv() {}
1019
+ glFogiv() {}
1020
+ glAlphaFunc() {} // emulate via discard in shader if needed
1021
+ glLightf() {}
1022
+ glLightfv() {}
1023
+ glLighti() {}
1024
+ glLightiv() {}
1025
+ glMaterialf() {}
1026
+ glMaterialfv() {}
1027
+ glMateriali() {}
1028
+ glMaterialiv() {}
1029
+ glPointSize() {}
1030
+ glColorMaterial() {}
1031
+ glLogicOp() {}
1032
+ glPassThrough() {}
1033
+ glSelectBuffer() {}
1034
+ glFeedbackBuffer() {}
1035
+ glRenderMode() { return 0 }
1036
+ glInitNames() {}
1037
+ glPushName() {}
1038
+ glPopName() {}
1039
+ glLoadName() {}
1040
+ glPushAttrib() {}
1041
+ glPopAttrib() {}
1042
+ glPushClientAttrib() {}
1043
+ glPopClientAttrib() {}
1044
+
1045
+ // Legacy array drawing (client-side arrays — use VBOs instead)
1046
+ glVertexPointer() {}
1047
+ glColorPointer() {}
1048
+ glNormalPointer() {}
1049
+ glTexCoordPointer() {}
1050
+ glIndexPointer() {}
1051
+
1052
+ // ── Misc ─────────────────────────────────────────────────────────────────────
1053
+
1054
+ glLineStipple() {}
1055
+ glPolygonStipple() {}
1056
+ glPolygonMode() {} // no equivalent in WebGL2
1057
+
1058
+
1059
+ */
1060
+ }