webgl.mjs raw
1 // Moxie jsruntime - WebGL2 Bridge
2 // Handle-based shim for WebGL2 contexts and objects.
3 // Functions read/write WASM memory directly via globalThis.__moxie_wasm_memory.
4
5 const _contexts = new Map();
6 const _buffers = new Map();
7 const _textures = new Map();
8 const _framebuffers = new Map();
9 const _renderbuffers = new Map();
10 const _shaders = new Map();
11 const _programs = new Map();
12 const _uniformLocs = new Map();
13 const _vaos = new Map();
14
15 let _nextCtx = 1;
16 let _nextBuf = 1;
17 let _nextTex = 1;
18 let _nextFbo = 1;
19 let _nextRbo = 1;
20 let _nextShader = 1;
21 let _nextProg = 1;
22 let _nextUloc = 1;
23 let _nextVao = 1;
24
25 function _mem() {
26 return globalThis.__moxie_wasm_memory;
27 }
28
29 function _readBytes(ptr, len) {
30 if (!ptr || len <= 0) return null;
31 return new Uint8Array(_mem().buffer, ptr, len);
32 }
33
34 function _readStr(ptr, len) {
35 if (!ptr || len <= 0) return '';
36 return new TextDecoder().decode(new Uint8Array(_mem().buffer, ptr, len));
37 }
38
39 function _writeBytes(ptr, data) {
40 new Uint8Array(_mem().buffer, ptr, data.length).set(data);
41 }
42
43 // --- Context ---
44
45 // RegisterContext registers an existing WebGL2 context into the handle system.
46 // Called by app.mjs after creating the context from a canvas element.
47 export function RegisterContext(gl) {
48 if (!gl) return 0;
49 const id = _nextCtx++;
50 _contexts.set(id, gl);
51 return id;
52 }
53
54 export function CreateContext(canvasId) {
55 const canvas = document.getElementById(canvasId) || document.querySelector('canvas');
56 if (!canvas) return 0;
57 const gl = canvas.getContext('webgl2', {
58 alpha: true,
59 depth: true,
60 stencil: true,
61 antialias: false,
62 premultipliedAlpha: true,
63 preserveDrawingBuffer: false,
64 });
65 if (!gl) return 0;
66 const id = _nextCtx++;
67 _contexts.set(id, gl);
68 return id;
69 }
70
71 export function CreateContextFromHandle(elHandle) {
72 // For use with DOM handle system - caller passes DOM element handle.
73 // The canvas element must be retrieved from the DOM module's handle map.
74 // Fallback: if globalThis.__moxie_dom_elements exists, use it.
75 const elements = globalThis.__moxie_dom_elements;
76 if (!elements) return 0;
77 const canvas = elements.get(elHandle);
78 if (!canvas || canvas.tagName !== 'CANVAS') return 0;
79 const gl = canvas.getContext('webgl2', {
80 alpha: true,
81 depth: true,
82 stencil: true,
83 antialias: false,
84 premultipliedAlpha: true,
85 preserveDrawingBuffer: false,
86 });
87 if (!gl) return 0;
88 const id = _nextCtx++;
89 _contexts.set(id, gl);
90 return id;
91 }
92
93 export function DeleteContext(ctx) {
94 const gl = _contexts.get(ctx);
95 if (!gl) return;
96 gl.getExtension('WEBGL_lose_context')?.loseContext();
97 _contexts.delete(ctx);
98 }
99
100 export function IsWebGL2(ctx) {
101 const gl = _contexts.get(ctx);
102 return gl instanceof WebGL2RenderingContext ? 1 : 0;
103 }
104
105 // --- State ---
106
107 export function Enable(ctx, cap) {
108 _contexts.get(ctx)?.enable(cap);
109 }
110
111 export function Disable(ctx, cap) {
112 _contexts.get(ctx)?.disable(cap);
113 }
114
115 export function Viewport(ctx, x, y, w, h) {
116 _contexts.get(ctx)?.viewport(x, y, w, h);
117 }
118
119 export function Scissor(ctx, x, y, w, h) {
120 _contexts.get(ctx)?.scissor(x, y, w, h);
121 }
122
123 export function BlendFunc(ctx, src, dst) {
124 _contexts.get(ctx)?.blendFunc(src, dst);
125 }
126
127 export function BlendFuncSeparate(ctx, srcRGB, dstRGB, srcA, dstA) {
128 _contexts.get(ctx)?.blendFuncSeparate(srcRGB, dstRGB, srcA, dstA);
129 }
130
131 export function DepthFunc(ctx, func) {
132 _contexts.get(ctx)?.depthFunc(func);
133 }
134
135 export function DepthMask(ctx, flag) {
136 _contexts.get(ctx)?.depthMask(!!flag);
137 }
138
139 export function ColorMask(ctx, r, g, b, a) {
140 _contexts.get(ctx)?.colorMask(!!r, !!g, !!b, !!a);
141 }
142
143 export function StencilFunc(ctx, func, ref, mask) {
144 _contexts.get(ctx)?.stencilFunc(func, ref, mask);
145 }
146
147 export function StencilOp(ctx, sfail, dpfail, dppass) {
148 _contexts.get(ctx)?.stencilOp(sfail, dpfail, dppass);
149 }
150
151 export function ClearColor(ctx, r, g, b, a) {
152 _contexts.get(ctx)?.clearColor(r, g, b, a);
153 }
154
155 export function ClearDepth(ctx, d) {
156 _contexts.get(ctx)?.clearDepth(d);
157 }
158
159 export function ClearStencil(ctx, s) {
160 _contexts.get(ctx)?.clearStencil(s);
161 }
162
163 export function Clear(ctx, mask) {
164 _contexts.get(ctx)?.clear(mask);
165 }
166
167 export function Flush(ctx) {
168 _contexts.get(ctx)?.flush();
169 }
170
171 export function GetError(ctx) {
172 const gl = _contexts.get(ctx);
173 return gl ? gl.getError() : 0;
174 }
175
176 export function PixelStorei(ctx, pname, param) {
177 _contexts.get(ctx)?.pixelStorei(pname, param);
178 }
179
180 export function ReadPixels(ctx, x, y, w, h, format, type, bufPtr, bufLen) {
181 const gl = _contexts.get(ctx);
182 if (!gl) return;
183 const dst = new Uint8Array(_mem().buffer, bufPtr, bufLen);
184 gl.readPixels(x, y, w, h, format, type, dst);
185 }
186
187 // --- Buffers ---
188
189 export function CreateBuffer(ctx) {
190 const gl = _contexts.get(ctx);
191 if (!gl) return 0;
192 const buf = gl.createBuffer();
193 if (!buf) return 0;
194 const id = _nextBuf++;
195 _buffers.set(id, buf);
196 return id;
197 }
198
199 export function DeleteBuffer(ctx, buf) {
200 const gl = _contexts.get(ctx);
201 const obj = _buffers.get(buf);
202 if (gl && obj) gl.deleteBuffer(obj);
203 _buffers.delete(buf);
204 }
205
206 export function BindBuffer(ctx, target, buf) {
207 const gl = _contexts.get(ctx);
208 if (!gl) return;
209 gl.bindBuffer(target, buf ? _buffers.get(buf) || null : null);
210 }
211
212 export function BufferData(ctx, target, dataPtr, dataLen, usage) {
213 const gl = _contexts.get(ctx);
214 if (!gl) return;
215 const data = _readBytes(dataPtr, dataLen);
216 if (data) {
217 gl.bufferData(target, data, usage);
218 } else {
219 gl.bufferData(target, dataLen, usage);
220 }
221 }
222
223 export function BufferSubData(ctx, target, offset, dataPtr, dataLen) {
224 const gl = _contexts.get(ctx);
225 if (!gl) return;
226 const data = _readBytes(dataPtr, dataLen);
227 if (data) gl.bufferSubData(target, offset, data);
228 }
229
230 // --- Textures ---
231
232 export function CreateTexture(ctx) {
233 const gl = _contexts.get(ctx);
234 if (!gl) return 0;
235 const tex = gl.createTexture();
236 if (!tex) return 0;
237 const id = _nextTex++;
238 _textures.set(id, tex);
239 return id;
240 }
241
242 export function DeleteTexture(ctx, tex) {
243 const gl = _contexts.get(ctx);
244 const obj = _textures.get(tex);
245 if (gl && obj) gl.deleteTexture(obj);
246 _textures.delete(tex);
247 }
248
249 export function BindTexture(ctx, target, tex) {
250 const gl = _contexts.get(ctx);
251 if (!gl) return;
252 gl.bindTexture(target, tex ? _textures.get(tex) || null : null);
253 }
254
255 export function ActiveTexture(ctx, unit) {
256 _contexts.get(ctx)?.activeTexture(unit);
257 }
258
259 export function TexParameteri(ctx, target, pname, param) {
260 _contexts.get(ctx)?.texParameteri(target, pname, param);
261 }
262
263 export function TexImage2D(ctx, target, level, internalformat, width, height, border, format, type, dataPtr, dataLen) {
264 const gl = _contexts.get(ctx);
265 if (!gl) return;
266 const data = _readBytes(dataPtr, dataLen);
267 gl.texImage2D(target, level, internalformat, width, height, border, format, type, data);
268 }
269
270 export function TexSubImage2D(ctx, target, level, xoff, yoff, width, height, format, type, dataPtr, dataLen) {
271 const gl = _contexts.get(ctx);
272 if (!gl) return;
273 const data = _readBytes(dataPtr, dataLen);
274 if (data) gl.texSubImage2D(target, level, xoff, yoff, width, height, format, type, data);
275 }
276
277 export function GenerateMipmap(ctx, target) {
278 _contexts.get(ctx)?.generateMipmap(target);
279 }
280
281 // --- Framebuffers ---
282
283 export function CreateFramebuffer(ctx) {
284 const gl = _contexts.get(ctx);
285 if (!gl) return 0;
286 const fbo = gl.createFramebuffer();
287 if (!fbo) return 0;
288 const id = _nextFbo++;
289 _framebuffers.set(id, fbo);
290 return id;
291 }
292
293 export function DeleteFramebuffer(ctx, fbo) {
294 const gl = _contexts.get(ctx);
295 const obj = _framebuffers.get(fbo);
296 if (gl && obj) gl.deleteFramebuffer(obj);
297 _framebuffers.delete(fbo);
298 }
299
300 export function BindFramebuffer(ctx, target, fbo) {
301 const gl = _contexts.get(ctx);
302 if (!gl) return;
303 gl.bindFramebuffer(target, fbo ? _framebuffers.get(fbo) || null : null);
304 }
305
306 export function FramebufferTexture2D(ctx, target, attachment, textarget, tex, level) {
307 const gl = _contexts.get(ctx);
308 if (!gl) return;
309 const obj = tex ? _textures.get(tex) || null : null;
310 gl.framebufferTexture2D(target, attachment, textarget, obj, level);
311 }
312
313 export function CheckFramebufferStatus(ctx, target) {
314 const gl = _contexts.get(ctx);
315 return gl ? gl.checkFramebufferStatus(target) : 0;
316 }
317
318 // --- Renderbuffers ---
319
320 export function CreateRenderbuffer(ctx) {
321 const gl = _contexts.get(ctx);
322 if (!gl) return 0;
323 const rbo = gl.createRenderbuffer();
324 if (!rbo) return 0;
325 const id = _nextRbo++;
326 _renderbuffers.set(id, rbo);
327 return id;
328 }
329
330 export function DeleteRenderbuffer(ctx, rbo) {
331 const gl = _contexts.get(ctx);
332 const obj = _renderbuffers.get(rbo);
333 if (gl && obj) gl.deleteRenderbuffer(obj);
334 _renderbuffers.delete(rbo);
335 }
336
337 export function BindRenderbuffer(ctx, target, rbo) {
338 const gl = _contexts.get(ctx);
339 if (!gl) return;
340 gl.bindRenderbuffer(target, rbo ? _renderbuffers.get(rbo) || null : null);
341 }
342
343 export function RenderbufferStorage(ctx, target, internalformat, width, height) {
344 _contexts.get(ctx)?.renderbufferStorage(target, internalformat, width, height);
345 }
346
347 export function FramebufferRenderbuffer(ctx, target, attachment, renderbuffertarget, rbo) {
348 const gl = _contexts.get(ctx);
349 if (!gl) return;
350 const obj = rbo ? _renderbuffers.get(rbo) || null : null;
351 gl.framebufferRenderbuffer(target, attachment, renderbuffertarget, obj);
352 }
353
354 // --- Shaders ---
355
356 export function CreateShader(ctx, type) {
357 const gl = _contexts.get(ctx);
358 if (!gl) return 0;
359 const shader = gl.createShader(type);
360 if (!shader) return 0;
361 const id = _nextShader++;
362 _shaders.set(id, shader);
363 return id;
364 }
365
366 export function DeleteShader(ctx, shader) {
367 const gl = _contexts.get(ctx);
368 const obj = _shaders.get(shader);
369 if (gl && obj) gl.deleteShader(obj);
370 _shaders.delete(shader);
371 }
372
373 export function ShaderSource(ctx, shader, srcPtr, srcLen) {
374 const gl = _contexts.get(ctx);
375 const obj = _shaders.get(shader);
376 if (!gl || !obj) return;
377 gl.shaderSource(obj, _readStr(srcPtr, srcLen));
378 }
379
380 export function CompileShader(ctx, shader) {
381 const gl = _contexts.get(ctx);
382 const obj = _shaders.get(shader);
383 if (gl && obj) gl.compileShader(obj);
384 }
385
386 export function GetShaderParameter(ctx, shader, pname) {
387 const gl = _contexts.get(ctx);
388 const obj = _shaders.get(shader);
389 if (!gl || !obj) return 0;
390 const v = gl.getShaderParameter(obj, pname);
391 return typeof v === 'boolean' ? (v ? 1 : 0) : (v | 0);
392 }
393
394 export function GetShaderInfoLog(ctx, shader, bufPtr, bufLen) {
395 const gl = _contexts.get(ctx);
396 const obj = _shaders.get(shader);
397 if (!gl || !obj) return 0;
398 const log = gl.getShaderInfoLog(obj) || '';
399 const encoded = new TextEncoder().encode(log);
400 const n = Math.min(encoded.length, bufLen);
401 if (n > 0) _writeBytes(bufPtr, encoded.subarray(0, n));
402 return encoded.length;
403 }
404
405 // --- Programs ---
406
407 export function CreateProgram(ctx) {
408 const gl = _contexts.get(ctx);
409 if (!gl) return 0;
410 const prog = gl.createProgram();
411 if (!prog) return 0;
412 const id = _nextProg++;
413 _programs.set(id, prog);
414 return id;
415 }
416
417 export function DeleteProgram(ctx, prog) {
418 const gl = _contexts.get(ctx);
419 const obj = _programs.get(prog);
420 if (gl && obj) gl.deleteProgram(obj);
421 _programs.delete(prog);
422 }
423
424 export function AttachShader(ctx, prog, shader) {
425 const gl = _contexts.get(ctx);
426 const p = _programs.get(prog);
427 const s = _shaders.get(shader);
428 if (gl && p && s) gl.attachShader(p, s);
429 }
430
431 export function LinkProgram(ctx, prog) {
432 const gl = _contexts.get(ctx);
433 const obj = _programs.get(prog);
434 if (gl && obj) gl.linkProgram(obj);
435 }
436
437 export function UseProgram(ctx, prog) {
438 const gl = _contexts.get(ctx);
439 if (!gl) return;
440 gl.useProgram(prog ? _programs.get(prog) || null : null);
441 }
442
443 export function GetProgramParameter(ctx, prog, pname) {
444 const gl = _contexts.get(ctx);
445 const obj = _programs.get(prog);
446 if (!gl || !obj) return 0;
447 const v = gl.getProgramParameter(obj, pname);
448 return typeof v === 'boolean' ? (v ? 1 : 0) : (v | 0);
449 }
450
451 export function GetProgramInfoLog(ctx, prog, bufPtr, bufLen) {
452 const gl = _contexts.get(ctx);
453 const obj = _programs.get(prog);
454 if (!gl || !obj) return 0;
455 const log = gl.getProgramInfoLog(obj) || '';
456 const encoded = new TextEncoder().encode(log);
457 const n = Math.min(encoded.length, bufLen);
458 if (n > 0) _writeBytes(bufPtr, encoded.subarray(0, n));
459 return encoded.length;
460 }
461
462 export function BindAttribLocation(ctx, prog, index, namePtr, nameLen) {
463 const gl = _contexts.get(ctx);
464 const obj = _programs.get(prog);
465 if (!gl || !obj) return;
466 gl.bindAttribLocation(obj, index, _readStr(namePtr, nameLen));
467 }
468
469 export function GetUniformLocation(ctx, prog, namePtr, nameLen) {
470 const gl = _contexts.get(ctx);
471 const obj = _programs.get(prog);
472 if (!gl || !obj) return 0;
473 const loc = gl.getUniformLocation(obj, _readStr(namePtr, nameLen));
474 if (!loc) return 0;
475 const id = _nextUloc++;
476 _uniformLocs.set(id, loc);
477 return id;
478 }
479
480 // --- Uniforms ---
481
482 export function Uniform1f(ctx, loc, v0) {
483 const gl = _contexts.get(ctx);
484 const u = _uniformLocs.get(loc);
485 if (gl && u) gl.uniform1f(u, v0);
486 }
487
488 export function Uniform2f(ctx, loc, v0, v1) {
489 const gl = _contexts.get(ctx);
490 const u = _uniformLocs.get(loc);
491 if (gl && u) gl.uniform2f(u, v0, v1);
492 }
493
494 export function Uniform4f(ctx, loc, v0, v1, v2, v3) {
495 const gl = _contexts.get(ctx);
496 const u = _uniformLocs.get(loc);
497 if (gl && u) gl.uniform4f(u, v0, v1, v2, v3);
498 }
499
500 export function Uniform1i(ctx, loc, v0) {
501 const gl = _contexts.get(ctx);
502 const u = _uniformLocs.get(loc);
503 if (gl && u) gl.uniform1i(u, v0);
504 }
505
506 // --- Vertex Attributes ---
507
508 export function EnableVertexAttribArray(ctx, index) {
509 _contexts.get(ctx)?.enableVertexAttribArray(index);
510 }
511
512 export function DisableVertexAttribArray(ctx, index) {
513 _contexts.get(ctx)?.disableVertexAttribArray(index);
514 }
515
516 export function VertexAttribPointer(ctx, index, size, type, normalized, stride, offset) {
517 _contexts.get(ctx)?.vertexAttribPointer(index, size, type, !!normalized, stride, offset);
518 }
519
520 // --- VAO (WebGL2) ---
521
522 export function CreateVertexArray(ctx) {
523 const gl = _contexts.get(ctx);
524 if (!gl) return 0;
525 const vao = gl.createVertexArray();
526 if (!vao) return 0;
527 const id = _nextVao++;
528 _vaos.set(id, vao);
529 return id;
530 }
531
532 export function DeleteVertexArray(ctx, vao) {
533 const gl = _contexts.get(ctx);
534 const obj = _vaos.get(vao);
535 if (gl && obj) gl.deleteVertexArray(obj);
536 _vaos.delete(vao);
537 }
538
539 export function BindVertexArray(ctx, vao) {
540 const gl = _contexts.get(ctx);
541 if (!gl) return;
542 gl.bindVertexArray(vao ? _vaos.get(vao) || null : null);
543 }
544
545 // --- Blending and misc state ---
546
547 export function BlendEquation(ctx, mode) {
548 _contexts.get(ctx)?.blendEquation(mode);
549 }
550
551 // --- Mipmap textures ---
552
553 export function TexStorage2D(ctx, target, levels, internalformat, width, height) {
554 _contexts.get(ctx)?.texStorage2D(target, levels, internalformat, width, height);
555 }
556
557 // --- Framebuffer ops ---
558
559 export function CopyTexSubImage2D(ctx, target, level, xoff, yoff, x, y, w, h) {
560 _contexts.get(ctx)?.copyTexSubImage2D(target, level, xoff, yoff, x, y, w, h);
561 }
562
563 export function InvalidateFramebuffer(ctx, target, attachment) {
564 const gl = _contexts.get(ctx);
565 if (!gl || !gl.invalidateFramebuffer) return;
566 gl.invalidateFramebuffer(target, [attachment]);
567 }
568
569 // --- Queries ---
570
571 export function GetInteger(ctx, pname) {
572 const gl = _contexts.get(ctx);
573 return gl ? (gl.getParameter(pname) | 0) : 0;
574 }
575
576 export function IsEnabled(ctx, cap) {
577 const gl = _contexts.get(ctx);
578 return gl && gl.isEnabled(cap) ? 1 : 0;
579 }
580
581 // --- Finish ---
582
583 export function Finish(ctx) {
584 _contexts.get(ctx)?.finish();
585 }
586
587 // --- Extra uniforms ---
588
589 export function Uniform3f(ctx, loc, v0, v1, v2) {
590 const gl = _contexts.get(ctx);
591 const u = _uniformLocs.get(loc);
592 if (gl && u) gl.uniform3f(u, v0, v1, v2);
593 }
594
595 // --- Drawing ---
596
597 export function DrawArrays(ctx, mode, first, count) {
598 _contexts.get(ctx)?.drawArrays(mode, first, count);
599 }
600
601 export function DrawElements(ctx, mode, count, type, offset) {
602 _contexts.get(ctx)?.drawElements(mode, count, type, offset);
603 }
604