@gjsify/webgl 0.35.0 → 0.37.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.
package/README.md CHANGED
@@ -66,19 +66,38 @@ The `'webgl'` / `'webgl2'` contexts themselves need no register: this package's
66
66
  ## Running GJS apps that use this package
67
67
 
68
68
  This package ships prebuilt native libraries for the platforms listed under
69
- [Platform coverage](#platform-coverage) — today that is Linux only:
69
+ [Platform coverage](#platform-coverage) — Linux, macOS and Windows. Since
70
+ [ADR 0017](https://github.com/gjsify/gjsify/blob/main/docs/adr/0017-native-package-distribution.md)
71
+ each target lives in its own `optionalDependencies` package, so your package
72
+ manager installs the one that fits and silently skips the rest:
70
73
 
71
74
  ```
72
- prebuilds/
73
- linux-x64/ libgwebgl.so + Gwebgl-0.1.typelib
74
- linux-arm64/ libgwebgl.so + Gwebgl-0.1.typelib
75
- linux-ppc64/ libgwebgl.so + Gwebgl-0.1.typelib
76
- linux-s390x/ libgwebgl.so + Gwebgl-0.1.typelib
77
- linux-riscv64/ libgwebgl.so + Gwebgl-0.1.typelib
75
+ @gjsify/webgl-linux-x64/prebuilds/linux-x64/ libgwebgl.so + Gwebgl-0.1.typelib
76
+ @gjsify/webgl-linux-arm64/… libgwebgl.so + Gwebgl-0.1.typelib
77
+ @gjsify/webgl-linux-{ppc64,s390x,riscv64}/… libgwebgl.so + Gwebgl-0.1.typelib
78
+ @gjsify/webgl-darwin-{arm64,x64}/… libgwebgl.dylib + Gwebgl-0.1.typelib
79
+ @gjsify/webgl-win32-x64/prebuilds/win32-x64/ gwebgl.dll + Gwebgl-0.1.typelib
78
80
  ```
79
81
 
80
- There is **no macOS or Windows prebuild**; on those platforms `getContext('webgl')`
81
- has no native backend.
82
+ **Windows needs two more things than the other platforms**, and neither is
83
+ optional:
84
+
85
+ - **`@gjsify/gtk-runtime-win32-x64`.** `gwebgl.dll` imports `epoxy-0.dll`, and
86
+ Windows has no system libepoxy — so unlike the `.so`/`.dylib` artifacts this
87
+ one is not loadable from the host alone. The epoxy it needs is already in that
88
+ bundle (GTK4 links it), which every Windows consumer has anyway because it is
89
+ how [`@gjsify/node-gi`](../../node-gi/node-gi) resolves `gi://` at all. It is
90
+ deliberately not duplicated here: two libepoxy images in one address space is
91
+ a worse failure than the one it would solve.
92
+ - **A real OpenGL ICD.** GTK4 rejects the GDI generic OpenGL 1.1 that Windows
93
+ falls back to, and the GTK bundle ships no GL implementation of its own
94
+ (`epoxy-0.dll` is the *dispatch* layer — it resolves nothing by itself). On a
95
+ machine with vendor graphics drivers this is already true; on a GPU-less host
96
+ (VM, RDP session, CI) install [Mesa for Windows](https://github.com/pal1000/mesa-dist-win)
97
+ and register it system-wide (`systemwidedeploy.cmd 1`). Without one,
98
+ `Gdk.Display.create_gl_context()` fails with *"No GL implementation is
99
+ available"* and the window paints that string instead of your scene — a
100
+ `Gtk.GLArea` failure, not a `gi://Gwebgl` one.
82
101
 
83
102
  Use the gjsify CLI to run your app — it automatically sets `LD_LIBRARY_PATH` and
84
103
  `GI_TYPELIB_PATH` so GJS can find the native library:
@@ -133,7 +152,7 @@ consumer installs only the artifact that fits its platform.
133
152
  | `linux-arm64` | ✅ | native runner |
134
153
  | `linux-ppc64`, `linux-s390x`, `linux-riscv64` | ✅ | QEMU emulation |
135
154
  | `darwin-arm64` / `darwin-x64` | ✅ `libgwebgl.dylib` + `Gwebgl-0.1.typelib` | native runner — **WebGL2 via a shader-dialect rewrite, see below** |
136
- | Windows | ❌ | — no Vala/GI bridge in this repo targets Windows |
155
+ | `win32-x64` | ✅ `gwebgl.dll` + `Gwebgl-0.1.typelib` | **two jobs** — valac does not run on Windows, so a Linux runner emits the C + GIR and a `windows-latest` runner compiles them with MSVC against gvsbuild's GTK4. Needs a GL ICD and the GTK bundle at run time, [see above](#running-gjs-apps-that-use-this-package) |
137
156
 
138
157
  **macOS renders, WebGL1 and WebGL2.** Measured on macOS 15.7.8 / x86_64 with gtk 4.22 and
139
158
  libepoxy 1.5.10: `Gtk.GLArea` realizes a `GdkMacosGLContext`, and this bridge draws real
@@ -163,8 +182,14 @@ platform, not from the bridge:
163
182
  eleven separately probed edge constructs all compile on GLSL 4.10 with just that
164
183
  substitution. Measured end to end on macOS 15.7.9 / GL 4.1 core: compiles, links, draws,
165
184
  and `readPixels` returns the shader's colour. `#version 100` is left byte-for-byte alone,
166
- and a context that already speaks GLSL ES 3.00 — Mesa's `4.6 (Compatibility Profile)` on
167
- win32, GLES anywhere — is not rewritten at all.
185
+ and a context that already speaks GLSL ES 3.00 — GLES anywhere, or any desktop context
186
+ with `ARB_ES3_compatibility` — is not rewritten at all.
187
+
188
+ **The same rewrite applies on Windows, which is not what this file used to predict.** Mesa
189
+ on win32 advertises `4.6 (Compatibility Profile)`, so the guess was that win32 would take
190
+ the untouched path; measured on the win11-gjsify VM it does not, because GDK asks for a
191
+ CORE profile. `is_legacy` is false there, and the dialect decision goes exactly as it does
192
+ on macOS.
168
193
 
169
194
  Of the 219 GL entry points the library references, exactly two are absent from Apple's
170
195
  `OpenGL.framework` (`glInvalidateFramebuffer`, `glInvalidateSubFramebuffer`, both GL 4.3);
@@ -1 +1 @@
1
- import"../_virtual/_rolldown/runtime.js";import{Uint8ArrayToVariant as e,arrayToUint8Array as t,typeSize as n,vertexCount as r}from"../utils.js";const i={drawArrays(e=0,t=0,n=0){if(t<0||n<0){this.setError(this.INVALID_VALUE);return}if(!this._checkStencilState())return;let i=r(this,e,n);if(i<0){this.setError(this.INVALID_ENUM);return}if(!this._framebufferOk()||n===0)return;let a=t;n>0&&(a=n+t-1>>>0),this._checkVertexAttribState(a)&&this._gl.drawArrays(e,t,i)},drawElements(e=0,t=0,n=0,r=0){if(t<0||r<0){this.setError(this.INVALID_VALUE);return}if(!this._checkStencilState())return;let i=this._vertexObjectState._elementArrayBufferBinding;if(!i){this.setError(this.INVALID_OPERATION);return}let a=null,o=r;if(n===this.UNSIGNED_SHORT){if(o%2){this.setError(this.INVALID_OPERATION);return}o>>=1,a=new Uint16Array(i._elements.buffer)}else if(this._extensions.oes_element_index_uint&&n===this.UNSIGNED_INT){if(o%4){this.setError(this.INVALID_OPERATION);return}o>>=2,a=new Uint32Array(i._elements.buffer)}else if(n===this.UNSIGNED_BYTE)a=i._elements;else{this.setError(this.INVALID_ENUM);return}let s=t;switch(e){case this.TRIANGLES:t%3&&(s-=t%3);break;case this.LINES:t%2&&(s-=t%2);break;case this.POINTS:break;case this.LINE_LOOP:case this.LINE_STRIP:if(t<2){this.setError(this.INVALID_OPERATION);return}break;case this.TRIANGLE_FAN:case this.TRIANGLE_STRIP:if(t<3){this.setError(this.INVALID_OPERATION);return}break;default:this.setError(this.INVALID_ENUM);return}if(!this._framebufferOk())return;if(t===0){this._checkVertexAttribState(0);return}if(t+o>>>0>a.length){this.setError(this.INVALID_OPERATION);return}let c=-1;for(let e=o;e<o+t;++e)c=Math.max(c,a[e]);if(c<0){this._checkVertexAttribState(0);return}this._checkVertexAttribState(c)&&s>0&&this._gl.drawElements(e,s,n,r)},viewport(e,t,n,r){this._viewport[0]=e|0,this._viewport[1]=t|0,this._viewport[2]=n|0,this._viewport[3]=r|0,this._gl.viewport(e,t,n,r)},scissor(e,t,n,r){this._scissorBox[0]=e|0,this._scissorBox[1]=t|0,this._scissorBox[2]=n|0,this._scissorBox[3]=r|0,this._gl.scissor(e|0,t|0,n|0,r|0)},readPixels(n=0,r=0,i=0,a=0,o=0,s=0,c){if(!c)return;if(!(this._extensions.oes_texture_float&&s===this.FLOAT&&o===this.RGBA)){if(o===this.RGB||o===this.ALPHA||s!==this.UNSIGNED_BYTE){this.setError(this.INVALID_OPERATION);return}else if(o!==this.RGBA){this.setError(this.INVALID_ENUM);return}else if(i<0||a<0||!(c instanceof Uint8Array)){this.setError(this.INVALID_VALUE);return}}if(!this._framebufferOk()){console.error(`framebuffer is not okay!`);return}let l=i*4;l%this._packAlignment!==0&&(l+=this._packAlignment-l%this._packAlignment);let u=l*(a-1)+i*4;if(u<=0)return;if((c.length||c.byteLength||0)<u){this.setError(this.INVALID_VALUE);return}let d=this.drawingBufferWidth,f=this.drawingBufferHeight;this._activeFramebuffer&&(d=this._activeFramebuffer._width,f=this._activeFramebuffer._height);let p=t(c);if(n>=d||n+i<=0||r>=f||r+a<=0)for(let e=0;e<p.length;++e)p[e]=0;else if(n<0||n+i>d||r<0||r+a>f){for(let e=0;e<p.length;++e)p[e]=0;let t=n,c=i;n<0&&(c+=n,t=0),t+i>d&&(c=d-t);let u=r,m=a;r<0&&(m+=r,u=0),u+a>f&&(m=f-u);let h=c*4;if(h%this._packAlignment!==0&&(h+=this._packAlignment-h%this._packAlignment),c>0&&m>0){let i=new Uint8Array(h*m),a=this._gl.readPixels(t,u,c,m,o,s,e(i)),d=a&&a.length>0?a:i,f=4*(t-n)+(u-r)*l;for(let e=0;e<m;++e)for(let t=0;t<c;++t)for(let n=0;n<4;++n)p[f+e*l+4*t+n]=d[e*h+4*t+n]}}else{let t=this._gl.readPixels(n,r,i,a,o,s,e(p));t&&t.length>0&&p.set(t)}},enableVertexAttribArray(e){if(e<0||e>=this._vertexObjectState._attribs.length){this.setError(this.INVALID_VALUE);return}this._gl.enableVertexAttribArray(e),this._vertexObjectState._attribs[e]._isPointer=!0},disableVertexAttribArray(e=0){if(e<0||e>=this._vertexObjectState._attribs.length){this.setError(this.INVALID_VALUE);return}this._gl.disableVertexAttribArray(e),this._vertexObjectState._attribs[e]._isPointer=!1},vertexAttrib1f(e,t){if(e|=0,!this._checkVertexIndex(e))return;let n=this._vertexGlobalState._attribs[e]._data;n[3]=1,n[1]=n[2]=0,n[0]=t,this._gl.vertexAttrib1f(e|0,+t)},vertexAttrib1fv(e,t){if(e|=0,!this._checkVertexIndex(e))return;if(typeof t!=`object`||!t||t.length<1){this.setError(this.INVALID_OPERATION);return}let n=this._vertexGlobalState._attribs[e]._data;n[3]=1,n[2]=0,n[1]=0,n[0]=t[0],this._gl.vertexAttrib1f(e|0,+t[0])},vertexAttrib2f(e,t,n){if(e|=0,!this._checkVertexIndex(e))return;let r=this._vertexGlobalState._attribs[e]._data;r[3]=1,r[2]=0,r[1]=n,r[0]=t,this._gl.vertexAttrib2f(e|0,+t,+n)},vertexAttrib2fv(e,t){if(e|=0,!this._checkVertexIndex(e))return;if(typeof t!=`object`||!t||t.length<2){this.setError(this.INVALID_OPERATION);return}let n=this._vertexGlobalState._attribs[e]._data;n[3]=1,n[2]=0,n[1]=t[1],n[0]=t[0],this._gl.vertexAttrib2f(e|0,+t[0],+t[1])},vertexAttrib3f(e,t,n,r){if(e|=0,!this._checkVertexIndex(e))return;let i=this._vertexGlobalState._attribs[e]._data;i[3]=1,i[2]=r,i[1]=n,i[0]=t,this._gl.vertexAttrib3f(e|0,+t,+n,+r)},vertexAttrib3fv(e,t){if(e|=0,!this._checkVertexIndex(e))return;if(typeof t!=`object`||!t||t.length<3){this.setError(this.INVALID_OPERATION);return}let n=this._vertexGlobalState._attribs[e]._data;n[3]=1,n[2]=t[2],n[1]=t[1],n[0]=t[0],this._gl.vertexAttrib3f(e|0,+t[0],+t[1],+t[2])},vertexAttrib4f(e=0,t,n,r,i){if(!this._checkVertexIndex(e))return;let a=this._vertexGlobalState._attribs[e]._data;a[3]=i,a[2]=r,a[1]=n,a[0]=t,this._gl.vertexAttrib4f(e|0,+t,+n,+r,+i)},vertexAttrib4fv(e,t){if(e|=0,!this._checkVertexIndex(e))return;if(typeof t!=`object`||!t||t.length<4){this.setError(this.INVALID_OPERATION);return}let n=this._vertexGlobalState._attribs[e]._data;n[3]=t[3],n[2]=t[2],n[1]=t[1],n[0]=t[0],this._gl.vertexAttrib4f(e|0,+t[0],+t[1],+t[2],+t[3])},vertexAttribPointer(e=0,t=0,r=0,i=!1,a=0,o=0){if(a<0||o<0){this.setError(this.INVALID_VALUE);return}if(a<0||o<0||e<0||e>=this._vertexObjectState._attribs.length||!(t===1||t===2||t===3||t===4)){this.setError(this.INVALID_VALUE);return}if(this._vertexGlobalState._arrayBufferBinding===null){this.setError(this.INVALID_OPERATION);return}let s=n(this,r);if(s===0||r===this.INT||r===this.UNSIGNED_INT){this.setError(this.INVALID_ENUM);return}if(a>255||a<0){this.setError(this.INVALID_VALUE);return}if(a%s!==0||o%s!==0){this.setError(this.INVALID_OPERATION);return}this._gl.vertexAttribPointer(e,t,r,i,a,o),this._vertexObjectState.setVertexAttribPointer(this._vertexGlobalState._arrayBufferBinding,e,t*s,o,a||t*s,r,i,a,t)},getVertexAttrib(e=0,t=0){if(e<0||e>=this._vertexObjectState._attribs.length)return this.setError(this.INVALID_VALUE),null;let n=this._vertexObjectState._attribs[e],r=this._vertexGlobalState._attribs[e]._data,i=this._extensions.angle_instanced_arrays;if(i&&t===i.VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE)return n._divisor;switch(t){case this.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING:return n._pointerBuffer;case this.VERTEX_ATTRIB_ARRAY_ENABLED:return n._isPointer;case this.VERTEX_ATTRIB_ARRAY_SIZE:return n._inputSize;case this.VERTEX_ATTRIB_ARRAY_STRIDE:return n._inputStride;case this.VERTEX_ATTRIB_ARRAY_TYPE:return n._pointerType;case this.VERTEX_ATTRIB_ARRAY_NORMALIZED:return n._pointerNormal;case this.CURRENT_VERTEX_ATTRIB:return new Float32Array(r);default:return this.setError(this.INVALID_ENUM),null}},getVertexAttribOffset(e=0,t=0){return e<0||e>=this._vertexObjectState._attribs.length?(this.setError(this.INVALID_VALUE),-1):t===this.VERTEX_ATTRIB_ARRAY_POINTER?this._vertexObjectState._attribs[e]._pointerOffset:(this.setError(this.INVALID_ENUM),-1)},_checkVertexAttribState(e){let t=this._activeProgram;if(!t)return this.setError(this.INVALID_OPERATION),!1;let n=this._vertexObjectState._attribs;for(let r=0;r<n.length;++r){let i=n[r];if(i._isPointer){let n=i._pointerBuffer;if(!n)return this.setError(this.INVALID_OPERATION),!1;if(t._attributes.indexOf(r)>=0){let t=0;if(t=i._divisor?i._pointerSize+i._pointerOffset:i._pointerStride*e+i._pointerSize+i._pointerOffset,t>n._size)return this.setError(this.INVALID_OPERATION),!1}}}return!0},_checkVertexIndex(e){return e<0||e>=this._vertexObjectState._attribs.length?(this.setError(this.INVALID_VALUE),!1):!0}};function installDrawingMethods(e){Object.assign(e,i)}export{installDrawingMethods};
1
+ import"../_virtual/_rolldown/runtime.js";import{Uint8ArrayToVariant as e,arrayToUint8Array as t,typeSize as n,vertexCount as r}from"../utils.js";const i={drawArrays(e=0,t=0,n=0){if(t<0||n<0){this.setError(this.INVALID_VALUE);return}if(!this._checkStencilState())return;let i=r(this,e,n);if(i<0){this.setError(this.INVALID_ENUM);return}if(!this._framebufferOk()||n===0)return;let a=t;n>0&&(a=n+t-1>>>0),this._checkVertexAttribState(a)&&this._gl.drawArrays(e,t,i)},drawElements(e=0,t=0,n=0,r=0){if(t<0||r<0){this.setError(this.INVALID_VALUE);return}if(!this._checkStencilState())return;let i=this._vertexObjectState._elementArrayBufferBinding;if(!i){this.setError(this.INVALID_OPERATION);return}let a=null,o=r;if(n===this.UNSIGNED_SHORT){if(o%2){this.setError(this.INVALID_OPERATION);return}o>>=1,a=new Uint16Array(i._elements.buffer)}else if(this._extensions.oes_element_index_uint&&n===this.UNSIGNED_INT){if(o%4){this.setError(this.INVALID_OPERATION);return}o>>=2,a=new Uint32Array(i._elements.buffer)}else if(n===this.UNSIGNED_BYTE)a=i._elements;else{this.setError(this.INVALID_ENUM);return}let s=t;switch(e){case this.TRIANGLES:t%3&&(s-=t%3);break;case this.LINES:t%2&&(s-=t%2);break;case this.POINTS:break;case this.LINE_LOOP:case this.LINE_STRIP:if(t<2){this.setError(this.INVALID_OPERATION);return}break;case this.TRIANGLE_FAN:case this.TRIANGLE_STRIP:if(t<3){this.setError(this.INVALID_OPERATION);return}break;default:this.setError(this.INVALID_ENUM);return}if(!this._framebufferOk())return;if(t===0){this._checkVertexAttribState(0);return}if(t+o>>>0>a.length){this.setError(this.INVALID_OPERATION);return}let c=-1;for(let e=o;e<o+t;++e)c=Math.max(c,a[e]);if(c<0){this._checkVertexAttribState(0);return}this._checkVertexAttribState(c)&&s>0&&this._gl.drawElements(e,s,n,r)},viewport(e,t,n,r){this._viewport[0]=e|0,this._viewport[1]=t|0,this._viewport[2]=n|0,this._viewport[3]=r|0,this._gl.viewport(e,t,n,r)},scissor(e,t,n,r){this._scissorBox[0]=e|0,this._scissorBox[1]=t|0,this._scissorBox[2]=n|0,this._scissorBox[3]=r|0,this._gl.scissor(e|0,t|0,n|0,r|0)},readPixels(n=0,r=0,i=0,a=0,o=0,s=0,c){if(!c)return;if(!(this._extensions.oes_texture_float&&s===this.FLOAT&&o===this.RGBA)){if(o===this.RGB||o===this.ALPHA||s!==this.UNSIGNED_BYTE){this.setError(this.INVALID_OPERATION);return}else if(o!==this.RGBA){this.setError(this.INVALID_ENUM);return}else if(i<0||a<0||!(c instanceof Uint8Array)){this.setError(this.INVALID_VALUE);return}}if(!this._framebufferOk()){console.error(`framebuffer is not okay!`);return}let l=i*4;l%this._packAlignment!==0&&(l+=this._packAlignment-l%this._packAlignment);let u=l*(a-1)+i*4;if(u<=0)return;if((c.length||c.byteLength||0)<u){this.setError(this.INVALID_VALUE);return}let d=this.drawingBufferWidth,f=this.drawingBufferHeight;this._activeFramebuffer&&(d=this._activeFramebuffer._width,f=this._activeFramebuffer._height);let p=t(c);if(n>=d||n+i<=0||r>=f||r+a<=0)for(let e=0;e<p.length;++e)p[e]=0;else if(n<0||n+i>d||r<0||r+a>f){for(let e=0;e<p.length;++e)p[e]=0;let t=n,c=i;n<0&&(c+=n,t=0),t+i>d&&(c=d-t);let u=r,m=a;r<0&&(m+=r,u=0),u+a>f&&(m=f-u);let h=c*4;if(h%this._packAlignment!==0&&(h+=this._packAlignment-h%this._packAlignment),c>0&&m>0){let i=new Uint8Array(h*m),a=this._gl.readPixels(t,u,c,m,o,s,e(i)),d=a&&a.length>0?a:i,f=4*(t-n)+(u-r)*l;for(let e=0;e<m;++e)for(let t=0;t<c;++t)for(let n=0;n<4;++n)p[f+e*l+4*t+n]=d[e*h+4*t+n]}}else{let t=this._gl.readPixels(n,r,i,a,o,s,e(p));t&&t.length>0&&p.set(t)}},enableVertexAttribArray(e){if(e<0||e>=this._vertexObjectState._attribs.length){this.setError(this.INVALID_VALUE);return}this._gl.enableVertexAttribArray(e),this._vertexObjectState._attribs[e]._isPointer=!0},disableVertexAttribArray(e=0){if(e<0||e>=this._vertexObjectState._attribs.length){this.setError(this.INVALID_VALUE);return}this._gl.disableVertexAttribArray(e),this._vertexObjectState._attribs[e]._isPointer=!1},vertexAttrib1f(e,t){if(e|=0,!this._checkVertexIndex(e))return;let n=this._vertexGlobalState._attribs[e]._data;n[3]=1,n[1]=n[2]=0,n[0]=t,this._gl.vertexAttrib1f(e|0,+t)},vertexAttrib1fv(e,t){if(e|=0,!this._checkVertexIndex(e))return;if(typeof t!=`object`||!t||t.length<1){this.setError(this.INVALID_OPERATION);return}let n=this._vertexGlobalState._attribs[e]._data;n[3]=1,n[2]=0,n[1]=0,n[0]=t[0],this._gl.vertexAttrib1f(e|0,+t[0])},vertexAttrib2f(e,t,n){if(e|=0,!this._checkVertexIndex(e))return;let r=this._vertexGlobalState._attribs[e]._data;r[3]=1,r[2]=0,r[1]=n,r[0]=t,this._gl.vertexAttrib2f(e|0,+t,+n)},vertexAttrib2fv(e,t){if(e|=0,!this._checkVertexIndex(e))return;if(typeof t!=`object`||!t||t.length<2){this.setError(this.INVALID_OPERATION);return}let n=this._vertexGlobalState._attribs[e]._data;n[3]=1,n[2]=0,n[1]=t[1],n[0]=t[0],this._gl.vertexAttrib2f(e|0,+t[0],+t[1])},vertexAttrib3f(e,t,n,r){if(e|=0,!this._checkVertexIndex(e))return;let i=this._vertexGlobalState._attribs[e]._data;i[3]=1,i[2]=r,i[1]=n,i[0]=t,this._gl.vertexAttrib3f(e|0,+t,+n,+r)},vertexAttrib3fv(e,t){if(e|=0,!this._checkVertexIndex(e))return;if(typeof t!=`object`||!t||t.length<3){this.setError(this.INVALID_OPERATION);return}let n=this._vertexGlobalState._attribs[e]._data;n[3]=1,n[2]=t[2],n[1]=t[1],n[0]=t[0],this._gl.vertexAttrib3f(e|0,+t[0],+t[1],+t[2])},vertexAttrib4f(e=0,t,n,r,i){if(!this._checkVertexIndex(e))return;let a=this._vertexGlobalState._attribs[e]._data;a[3]=i,a[2]=r,a[1]=n,a[0]=t,this._gl.vertexAttrib4f(e|0,+t,+n,+r,+i)},vertexAttrib4fv(e,t){if(e|=0,!this._checkVertexIndex(e))return;if(typeof t!=`object`||!t||t.length<4){this.setError(this.INVALID_OPERATION);return}let n=this._vertexGlobalState._attribs[e]._data;n[3]=t[3],n[2]=t[2],n[1]=t[1],n[0]=t[0],this._gl.vertexAttrib4f(e|0,+t[0],+t[1],+t[2],+t[3])},vertexAttribPointer(e=0,t=0,r=0,i=!1,a=0,o=0){if(a<0||o<0){this.setError(this.INVALID_VALUE);return}if(a<0||o<0||e<0||e>=this._vertexObjectState._attribs.length||!(t===1||t===2||t===3||t===4)){this.setError(this.INVALID_VALUE);return}if(this._vertexGlobalState._arrayBufferBinding===null){this.setError(this.INVALID_OPERATION);return}let s=n(this,r);if(s===0||r===this.INT||r===this.UNSIGNED_INT){this.setError(this.INVALID_ENUM);return}if(a>255||a<0){this.setError(this.INVALID_VALUE);return}if(a%s!==0||o%s!==0){this.setError(this.INVALID_OPERATION);return}this._gl.vertexAttribPointer(e,t,r,i,a,o),this._vertexObjectState.setVertexAttribPointer(this._vertexGlobalState._arrayBufferBinding,e,t*s,o,a||t*s,r,i,a,t)},getVertexAttrib(e=0,t=0){if(e<0||e>=this._vertexObjectState._attribs.length)return this.setError(this.INVALID_VALUE),null;let n=this._vertexObjectState._attribs[e],r=this._vertexGlobalState._attribs[e]._data,i=this._extensions.angle_instanced_arrays;if(i&&t===i.VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE||t===this.VERTEX_ATTRIB_ARRAY_DIVISOR)return n._divisor;switch(t){case this.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING:return n._pointerBuffer;case this.VERTEX_ATTRIB_ARRAY_ENABLED:return n._isPointer;case this.VERTEX_ATTRIB_ARRAY_SIZE:return n._inputSize;case this.VERTEX_ATTRIB_ARRAY_STRIDE:return n._inputStride;case this.VERTEX_ATTRIB_ARRAY_TYPE:return n._pointerType;case this.VERTEX_ATTRIB_ARRAY_NORMALIZED:return n._pointerNormal;case this.CURRENT_VERTEX_ATTRIB:return new Float32Array(r);default:return this.setError(this.INVALID_ENUM),null}},getVertexAttribOffset(e=0,t=0){return e<0||e>=this._vertexObjectState._attribs.length?(this.setError(this.INVALID_VALUE),-1):t===this.VERTEX_ATTRIB_ARRAY_POINTER?this._vertexObjectState._attribs[e]._pointerOffset:(this.setError(this.INVALID_ENUM),-1)},_checkVertexAttribState(e){let t=this._activeProgram;if(!t)return this.setError(this.INVALID_OPERATION),!1;let n=this._vertexObjectState._attribs;for(let r=0;r<n.length;++r){let i=n[r];if(i._isPointer){let n=i._pointerBuffer;if(!n)return this.setError(this.INVALID_OPERATION),!1;if(t._attributes.indexOf(r)>=0){let t=0;if(t=i._divisor?i._pointerSize+i._pointerOffset:i._pointerStride*e+i._pointerSize+i._pointerOffset,t>n._size)return this.setError(this.INVALID_OPERATION),!1}}}return!0},_checkVertexIndex(e){return e<0||e>=this._vertexObjectState._attribs.length?(this.setError(this.INVALID_VALUE),!1):!0}};function installDrawingMethods(e){Object.assign(e,i)}export{installDrawingMethods};
@@ -1 +1 @@
1
- import"../_virtual/_rolldown/runtime.js";import{vertexCount as e}from"../utils.js";function debugGlEnabled(){return globalThis.__GJSIFY_DEBUG_GL===!0}const t={drawArraysInstanced(t,n,r,i){if(n<0||r<0||i<0){this.setError(this.INVALID_VALUE);return}if(!this._checkStencilState())return;let a=e(this,t,r);if(a<0){this.setError(this.INVALID_ENUM);return}if(this._framebufferOk()&&!(r===0||i===0)&&this._checkVertexAttribState(r+n-1>>>0)){if(debugGlEnabled()){let e=this,t=e.__drawInstCount=(e.__drawInstCount|0)+1;(t<=5||t%100==0)&&console.log(`[WebGL] drawArraysInstanced #${t} count=${a} instances=${i} fbo=${this._activeFramebuffer?._??`_gtkFbo`}`)}this._native2.drawArraysInstanced(t,n,a,i)}},drawElementsInstanced(e,t,n,r,i){if(t<0||r<0||i<0){this.setError(this.INVALID_VALUE);return}if(!this._checkStencilState())return;let a=this._vertexObjectState._elementArrayBufferBinding;if(!a){this.setError(this.INVALID_OPERATION);return}let o=null,s=r;if(n===this.UNSIGNED_SHORT){if(s%2){this.setError(this.INVALID_OPERATION);return}s>>=1,o=new Uint16Array(a._elements.buffer)}else if(n===this.UNSIGNED_INT){if(s%4){this.setError(this.INVALID_OPERATION);return}s>>=2,o=new Uint32Array(a._elements.buffer)}else if(n===this.UNSIGNED_BYTE)o=a._elements;else{this.setError(this.INVALID_ENUM);return}let c=t;switch(e){case this.TRIANGLES:t%3&&(c-=t%3);break;case this.LINES:t%2&&(c-=t%2);break;case this.POINTS:break;case this.LINE_LOOP:case this.LINE_STRIP:if(t<2){this.setError(this.INVALID_OPERATION);return}break;case this.TRIANGLE_FAN:case this.TRIANGLE_STRIP:if(t<3){this.setError(this.INVALID_OPERATION);return}break;default:this.setError(this.INVALID_ENUM);return}if(!this._framebufferOk())return;if(c===0||i===0){this._checkVertexAttribState(0);return}if(c+s>>>0>o.length){this.setError(this.INVALID_OPERATION);return}let l=0;for(let e=s;e<s+c;++e)o[e]>l&&(l=o[e]);this._checkVertexAttribState(l)&&this._native2.drawElementsInstanced(e,c,n,r,i)},vertexAttribDivisor(e,t){this._native2.vertexAttribDivisor(e,t)},vertexAttribIPointer(e,t,n,r,i){this._native2.vertexAttribIPointer(e,t,n,r,i)},drawBuffers(e){let t=!1;for(let n=0;n<e.length;n++)if(e[n]===1029){t=!0;break}if(!t){this._native2.drawBuffers(e);return}this._native2.drawBuffers(e.map(e=>e===1029?this.COLOR_ATTACHMENT0:e))},drawRangeElements(e,t,n,r,i,a){if(r<0||a<0){this.setError(this.INVALID_VALUE);return}if(n<t){this.setError(this.INVALID_VALUE);return}this.drawElements(e,r,i,a)},blitFramebuffer(e,t,n,r,i,a,o,s,c,l){if(debugGlEnabled()){let e=this._gl.getError();e!==0&&console.log(`[WebGL] blitFramebuffer PRE-ERROR 0x${e.toString(16)}`)}if(this._native2.blitFramebuffer(e,t,n,r,i,a,o,s,c,l),debugGlEnabled()){let i=this._gl.getError(),a=this,o=a.__blitCount=(a.__blitCount|0)+1;o<=5&&console.log(`[WebGL] blitFramebuffer #${o} src=(${e},${t},${n},${r}) readFbo=${this._activeReadFramebuffer?._??`_gtkFbo`} err=${i===0?`OK`:`0x`+i.toString(16)}`)}},framebufferTextureLayer(e,t,n,r,i){this._native2.framebufferTextureLayer(e,t,n?n._:0,r,i)},invalidateFramebuffer(e,t){this._native2.invalidateFramebuffer(e,t)},invalidateSubFramebuffer(e,t,n,r,i,a){this._native2.invalidateSubFramebuffer(e,t,n,r,i,a)},readBuffer(e){this._native2.readBuffer(e)},renderbufferStorageMultisample(e,t,n,r,i){if(e!==this.RENDERBUFFER){this.setError(this.INVALID_ENUM);return}let a=this._activeRenderbuffer;if(!a){this.setError(this.INVALID_OPERATION);return}this._saveError(),this._native2.renderbufferStorageMultisample(e,t,n,r,i);let o=this.getError();this._restoreError(o),o===this.NO_ERROR&&(a._width=r,a._height=i,a._format=n)}};function installInstancedAndFramebufferOpsMethods(e){Object.assign(e,t)}export{installInstancedAndFramebufferOpsMethods};
1
+ import"../_virtual/_rolldown/runtime.js";import{vertexCount as e}from"../utils.js";function debugGlEnabled(){return globalThis.__GJSIFY_DEBUG_GL===!0}const t={drawArraysInstanced(t,n,r,i){if(n<0||r<0||i<0){this.setError(this.INVALID_VALUE);return}if(!this._checkStencilState())return;let a=e(this,t,r);if(a<0){this.setError(this.INVALID_ENUM);return}if(this._framebufferOk()&&!(r===0||i===0)&&this._checkVertexAttribState(r+n-1>>>0)){if(debugGlEnabled()){let e=this,t=e.__drawInstCount=(e.__drawInstCount|0)+1;(t<=5||t%100==0)&&console.log(`[WebGL] drawArraysInstanced #${t} count=${a} instances=${i} fbo=${this._activeFramebuffer?._??`_gtkFbo`}`)}this._native2.drawArraysInstanced(t,n,a,i)}},drawElementsInstanced(e,t,n,r,i){if(t<0||r<0||i<0){this.setError(this.INVALID_VALUE);return}if(!this._checkStencilState())return;let a=this._vertexObjectState._elementArrayBufferBinding;if(!a){this.setError(this.INVALID_OPERATION);return}let o=null,s=r;if(n===this.UNSIGNED_SHORT){if(s%2){this.setError(this.INVALID_OPERATION);return}s>>=1,o=new Uint16Array(a._elements.buffer)}else if(n===this.UNSIGNED_INT){if(s%4){this.setError(this.INVALID_OPERATION);return}s>>=2,o=new Uint32Array(a._elements.buffer)}else if(n===this.UNSIGNED_BYTE)o=a._elements;else{this.setError(this.INVALID_ENUM);return}let c=t;switch(e){case this.TRIANGLES:t%3&&(c-=t%3);break;case this.LINES:t%2&&(c-=t%2);break;case this.POINTS:break;case this.LINE_LOOP:case this.LINE_STRIP:if(t<2){this.setError(this.INVALID_OPERATION);return}break;case this.TRIANGLE_FAN:case this.TRIANGLE_STRIP:if(t<3){this.setError(this.INVALID_OPERATION);return}break;default:this.setError(this.INVALID_ENUM);return}if(!this._framebufferOk())return;if(c===0||i===0){this._checkVertexAttribState(0);return}if(c+s>>>0>o.length){this.setError(this.INVALID_OPERATION);return}let l=0;for(let e=s;e<s+c;++e)o[e]>l&&(l=o[e]);this._checkVertexAttribState(l)&&this._native2.drawElementsInstanced(e,c,n,r,i)},vertexAttribDivisor(e,t){this._checkVertexIndex(e)&&(this._native2.vertexAttribDivisor(e,t),this._vertexObjectState._attribs[e]._divisor=t)},vertexAttribIPointer(e,t,n,r,i){this._native2.vertexAttribIPointer(e,t,n,r,i)},drawBuffers(e){let t=!1;for(let n=0;n<e.length;n++)if(e[n]===1029){t=!0;break}if(!t){this._native2.drawBuffers(e);return}this._native2.drawBuffers(e.map(e=>e===1029?this.COLOR_ATTACHMENT0:e))},drawRangeElements(e,t,n,r,i,a){if(r<0||a<0){this.setError(this.INVALID_VALUE);return}if(n<t){this.setError(this.INVALID_VALUE);return}this.drawElements(e,r,i,a)},blitFramebuffer(e,t,n,r,i,a,o,s,c,l){if(debugGlEnabled()){let e=this._gl.getError();e!==0&&console.log(`[WebGL] blitFramebuffer PRE-ERROR 0x${e.toString(16)}`)}if(this._native2.blitFramebuffer(e,t,n,r,i,a,o,s,c,l),debugGlEnabled()){let i=this._gl.getError(),a=this,o=a.__blitCount=(a.__blitCount|0)+1;o<=5&&console.log(`[WebGL] blitFramebuffer #${o} src=(${e},${t},${n},${r}) readFbo=${this._activeReadFramebuffer?._??`_gtkFbo`} err=${i===0?`OK`:`0x`+i.toString(16)}`)}},framebufferTextureLayer(e,t,n,r,i){this._native2.framebufferTextureLayer(e,t,n?n._:0,r,i)},invalidateFramebuffer(e,t){this._native2.invalidateFramebuffer(e,t)},invalidateSubFramebuffer(e,t,n,r,i,a){this._native2.invalidateSubFramebuffer(e,t,n,r,i,a)},readBuffer(e){this._native2.readBuffer(e)},renderbufferStorageMultisample(e,t,n,r,i){if(e!==this.RENDERBUFFER){this.setError(this.INVALID_ENUM);return}let a=this._activeRenderbuffer;if(!a){this.setError(this.INVALID_OPERATION);return}this._saveError(),this._native2.renderbufferStorageMultisample(e,t,n,r,i);let o=this.getError();this._restoreError(o),o===this.NO_ERROR&&(a._width=r,a._height=i,a._format=n)}};function installInstancedAndFramebufferOpsMethods(e){Object.assign(e,t)}export{installInstancedAndFramebufferOpsMethods};
@@ -1,4 +1,4 @@
1
- import{__name as e}from"./_virtual/_rolldown/runtime.js";import{TEXTURE_FS_300 as t,TEXTURE_VS_300 as n,destroyTestFBO as r,drawTriangle as i,makeProgram as a,makeTestFBO as o,makeTestFBOFloat as s,makeTestFBOWithDepthTexture as c,readPixel as l}from"./test-utils.js";import{beforeEach as u,describe as d,expect as f,it as p,on as m}from"@gjsify/unit";import{WebGL2RenderingContext as h,WebGLBridge as g}from"@gjsify/webgl";import _ from"@girs/glib-2.0";import v from"@girs/gtk-4.0";var y=e(async()=>{await m(`Display`,async()=>{v.init();let e,m,y=new _.MainLoop(null,!1),b=new v.Window({});b.set_default_size(200,200),e=new g,e.onReady((e,t)=>{m=e.getContext(`webgl2`),y.quit()}),b.set_child(e),b.present();let x=_.timeout_add(_.PRIORITY_DEFAULT,1e4,()=>(y.quit(),_.SOURCE_REMOVE));if(y.run(),_.source_remove(x),!m){console.warn(`WebGL2 context not available — skipping WebGL2 tests`),b.destroy();return}e.make_current(),await d(`WebGL2 context`,async()=>{u(async()=>{e.make_current()}),await p(`getContext("webgl2") returns a WebGL2RenderingContext`,async()=>{f(m).toBeInstanceOf(h)}),await p(`VERSION is "WebGL 2.0"`,async()=>{f(m.getParameter(m.VERSION)).toBe(`WebGL 2.0`)}),await p(`SHADING_LANGUAGE_VERSION is "WebGL GLSL ES 3.00"`,async()=>{f(m.getParameter(m.SHADING_LANGUAGE_VERSION)).toBe(`WebGL GLSL ES 3.00`)}),await p(`drawingBufferWidth and drawingBufferHeight are positive`,async()=>{f(m.drawingBufferWidth>0).toBeTruthy(),f(m.drawingBufferHeight>0).toBeTruthy()}),await p(`getError() returns NO_ERROR initially`,async()=>{f(m.getError()).toBe(m.NO_ERROR)})}),await d(`GLSL ES 3.00 compilation`,async()=>{u(async()=>{e.make_current()});let t=[`#version 300 es`,`in vec2 position;`,`void main() { gl_Position = vec4(position, 0.0, 1.0); }`].join(`
1
+ import{__name as e}from"./_virtual/_rolldown/runtime.js";import{TEXTURE_FS_300 as t,TEXTURE_VS_300 as n,destroyTestFBO as r,drawTriangle as i,makeProgram as a,makeTestFBO as o,makeTestFBOFloat as s,makeTestFBOWithDepthTexture as c,readPixel as l}from"./test-utils.js";import{beforeEach as u,describe as d,expect as f,it as p,on as m}from"@gjsify/unit";import{WebGL2RenderingContext as h,WebGLBridge as g}from"@gjsify/webgl";import _ from"@girs/glib-2.0";import v from"@girs/gtk-4.0";var y=e(async()=>{await m(`Display`,async()=>{v.init();let e,m,y=new _.MainLoop(null,!1),b=new v.Window({});b.set_default_size(200,200),e=new g,e.onReady((e,t)=>{m=e.getContext(`webgl2`),y.quit()}),b.set_child(e),b.present();let x=_.timeout_add(_.PRIORITY_DEFAULT,1e4,()=>(y.quit(),_.SOURCE_REMOVE));if(y.run(),_.source_remove(x),!m){console.warn(`WebGL2 context not available — skipping WebGL2 tests`),b.destroy();return}e.make_current(),await d(`WebGL2 context`,async()=>{u(async()=>{e.make_current()}),await p(`getContext("webgl2") returns a WebGL2RenderingContext`,async()=>{f(m).toBeInstanceOf(h)}),await p(`VERSION is "WebGL 2.0"`,async()=>{f(m.getParameter(m.VERSION)).toBe(`WebGL 2.0`)}),await p(`SHADING_LANGUAGE_VERSION is "WebGL GLSL ES 3.00"`,async()=>{f(m.getParameter(m.SHADING_LANGUAGE_VERSION)).toBe(`WebGL GLSL ES 3.00`)}),await p(`drawingBufferWidth and drawingBufferHeight are positive`,async()=>{f(m.drawingBufferWidth>0).toBeTruthy(),f(m.drawingBufferHeight>0).toBeTruthy()}),await p(`getError() returns NO_ERROR initially`,async()=>{f(m.getError()).toBe(m.NO_ERROR)}),await p(`exposes every WebGL2 sized internal format constant`,async()=>{let e=`R8.R8_SNORM.R16F.R32F.R8UI.R8I.R16UI.R16I.R32UI.R32I.RG8.RG8_SNORM.RG16F.RG32F.RG8UI.RG8I.RG16UI.RG16I.RG32UI.RG32I.RGB8.SRGB8.RGB565.RGB8_SNORM.R11F_G11F_B10F.RGB9_E5.RGB16F.RGB32F.RGB8UI.RGB8I.RGB16UI.RGB16I.RGB32UI.RGB32I.RGBA8.SRGB8_ALPHA8.RGBA8_SNORM.RGB5_A1.RGBA4.RGB10_A2.RGBA16F.RGBA32F.RGBA8UI.RGBA8I.RGB10_A2UI.RGBA16UI.RGBA16I.RGBA32I.RGBA32UI.DEPTH_COMPONENT16.DEPTH_COMPONENT24.DEPTH_COMPONENT32F.DEPTH24_STENCIL8.DEPTH32F_STENCIL8`.split(`.`),t=m;f(e.filter(e=>typeof t[e]!=`number`).join(`, `)).toBe(``)}),await p(`allocates an RG16F texture with texStorage2D without error`,async()=>{let e=m.createTexture();m.bindTexture(m.TEXTURE_2D,e),m.texStorage2D(m.TEXTURE_2D,1,m.RG16F,16,16),f(m.getError()).toBe(m.NO_ERROR),m.texSubImage2D(m.TEXTURE_2D,0,0,0,16,16,m.RG,m.HALF_FLOAT,new Uint16Array(256*2)),f(m.getError()).toBe(m.NO_ERROR),m.bindTexture(m.TEXTURE_2D,null),m.deleteTexture(e)})}),await d(`GLSL ES 3.00 compilation`,async()=>{u(async()=>{e.make_current()});let t=[`#version 300 es`,`in vec2 position;`,`void main() { gl_Position = vec4(position, 0.0, 1.0); }`].join(`
2
2
  `),n=[`#version 300 es`,`precision mediump float;`,`out vec4 fragColor;`,`void main() { fragColor = vec4(0.0, 1.0, 0.0, 1.0); }`].join(`
3
3
  `);await p(`#version 300 es vertex shader compiles`,async()=>{let e=m.createShader(m.VERTEX_SHADER);m.shaderSource(e,t),m.compileShader(e),m.getShaderParameter(e,m.COMPILE_STATUS)||console.error(`VS300 log:`,m.getShaderInfoLog(e)),f(m.getShaderParameter(e,m.COMPILE_STATUS)).toBeTruthy(),m.deleteShader(e)}),await p(`#version 300 es fragment shader compiles`,async()=>{let e=m.createShader(m.FRAGMENT_SHADER);m.shaderSource(e,n),m.compileShader(e),m.getShaderParameter(e,m.COMPILE_STATUS)||console.error(`FS300 log:`,m.getShaderInfoLog(e)),f(m.getShaderParameter(e,m.COMPILE_STATUS)).toBeTruthy(),m.deleteShader(e)}),await p(`leaves the source alone on a context that speaks GLSL ES 3.00`,async()=>{let e=m.createShader(m.VERTEX_SHADER);m.shaderSource(e,t);let n=m.getShaderSource(e)??``,r=n.split(`
4
4
  `)[0].trim(),i=n.split(`
@@ -26,7 +26,9 @@ void main(){ gl_FragColor = textureCube(uCube, vec3(0.0)); }`]])await p(`treats
26
26
  `),t=[`#version 300 es`,`precision mediump float;`,`in vec4 vColor; out vec4 c;`,`void main() { c = vColor; }`].join(`
27
27
  `),n=a(m,e,t);f(m.getProgramParameter(n,m.LINK_STATUS)).toBeTruthy();let s=m.getUniformBlockIndex(n,`Params`);f(s).not.toBe(m.INVALID_INDEX),m.uniformBlockBinding(n,s,0),f(m.getError()).toBe(m.NO_ERROR);let c=m.createBuffer();m.bindBuffer(m.UNIFORM_BUFFER,c),m.bufferData(m.UNIFORM_BUFFER,new Float32Array([0,1,0,1]),m.STATIC_DRAW),m.bindBufferBase(m.UNIFORM_BUFFER,0,c);let u=o(m,4,4);m.useProgram(n),m.clearColor(0,0,0,1),m.clear(m.COLOR_BUFFER_BIT),i(m),f(m.getError()).toBe(m.NO_ERROR);let d=l(m,0,0);r(m,u),f(d[1]).toBe(255),m.deleteBuffer(c),m.deleteProgram(n)})}),await d(`instanced drawing`,async()=>{u(async()=>{e.make_current()}),await p(`drawArraysInstanced renders without error`,async()=>{let e=[`#version 300 es`,`in vec2 position;`,`void main() { gl_Position = vec4(position,0.0,1.0); }`].join(`
28
28
  `),t=[`#version 300 es`,`precision mediump float;`,`out vec4 c;`,`void main() { c = vec4(0.0,1.0,0.0,1.0); }`].join(`
29
- `),n=a(m,e,t);m.useProgram(n);let i=o(m,4,4),s=m.createBuffer();m.bindBuffer(m.ARRAY_BUFFER,s),m.bufferData(m.ARRAY_BUFFER,new Float32Array([-2,-2,-2,4,4,-2]),m.STREAM_DRAW),m.enableVertexAttribArray(0),m.vertexAttribPointer(0,2,m.FLOAT,!1,0,0),m.vertexAttribDivisor(0,0),m.clearColor(0,0,0,1),m.clear(m.COLOR_BUFFER_BIT),m.drawArraysInstanced(m.TRIANGLES,0,3,1),f(m.getError()).toBe(m.NO_ERROR);let c=l(m,0,0);r(m,i),f(c[1]).toBe(255),m.disableVertexAttribArray(0),m.deleteBuffer(s),m.deleteProgram(n)})}),await d(`drawBuffers (MRT)`,async()=>{u(async()=>{e.make_current()}),await p(`drawBuffers with 2 COLOR_ATTACHMENTs renders to both`,async()=>{let e=[`#version 300 es`,`in vec2 position;`,`void main() { gl_Position = vec4(position,0.0,1.0); }`].join(`
29
+ `),n=a(m,e,t);m.useProgram(n);let i=o(m,4,4),s=m.createBuffer();m.bindBuffer(m.ARRAY_BUFFER,s),m.bufferData(m.ARRAY_BUFFER,new Float32Array([-2,-2,-2,4,4,-2]),m.STREAM_DRAW),m.enableVertexAttribArray(0),m.vertexAttribPointer(0,2,m.FLOAT,!1,0,0),m.vertexAttribDivisor(0,0),m.clearColor(0,0,0,1),m.clear(m.COLOR_BUFFER_BIT),m.drawArraysInstanced(m.TRIANGLES,0,3,1),f(m.getError()).toBe(m.NO_ERROR);let c=l(m,0,0);r(m,i),f(c[1]).toBe(255),m.disableVertexAttribArray(0),m.deleteBuffer(s),m.deleteProgram(n)}),await p(`records the divisor in the tracked attrib state`,async()=>{let e=m.createBuffer();m.bindBuffer(m.ARRAY_BUFFER,e),m.bufferData(m.ARRAY_BUFFER,new Float32Array(8),m.STATIC_DRAW),m.enableVertexAttribArray(1),m.vertexAttribPointer(1,4,m.FLOAT,!1,16,0),m.vertexAttribDivisor(1,1),f(m.getVertexAttrib(1,m.VERTEX_ATTRIB_ARRAY_DIVISOR)).toBe(1),m.vertexAttribDivisor(1,0),f(m.getVertexAttrib(1,m.VERTEX_ATTRIB_ARRAY_DIVISOR)).toBe(0),m.disableVertexAttribArray(1),m.deleteBuffer(e)}),await p(`draws instanced with an instance-sized mat4 attribute buffer`,async()=>{let e=[`#version 300 es`,`in vec2 position;`,`in vec4 offsetRow;`,`void main() { gl_Position = vec4(position + offsetRow.xy * 0.001, 0.0, 1.0); }`].join(`
30
+ `),t=[`#version 300 es`,`precision mediump float;`,`out vec4 c;`,`void main() { c = vec4(0.0,1.0,0.0,1.0); }`].join(`
31
+ `),n=a(m,e,t);m.useProgram(n);let i=m.getAttribLocation(n,`position`),s=m.getAttribLocation(n,`offsetRow`),c=o(m,4,4),u=m.createBuffer();m.bindBuffer(m.ARRAY_BUFFER,u),m.bufferData(m.ARRAY_BUFFER,new Float32Array([-2,-2,-2,4,4,-2]),m.STATIC_DRAW),m.enableVertexAttribArray(i),m.vertexAttribPointer(i,2,m.FLOAT,!1,0,0),m.vertexAttribDivisor(i,0);let d=m.createBuffer();m.bindBuffer(m.ARRAY_BUFFER,d),m.bufferData(m.ARRAY_BUFFER,new Float32Array(96),m.STATIC_DRAW),m.enableVertexAttribArray(s),m.vertexAttribPointer(s,4,m.FLOAT,!1,64,0),m.vertexAttribDivisor(s,1);let p=m.createBuffer();m.bindBuffer(m.ELEMENT_ARRAY_BUFFER,p),m.bufferData(m.ELEMENT_ARRAY_BUFFER,new Uint16Array([0,1,2]),m.STATIC_DRAW),m.clearColor(0,0,0,1),m.clear(m.COLOR_BUFFER_BIT),m.drawElementsInstanced(m.TRIANGLES,3,m.UNSIGNED_SHORT,0,6),f(m.getError()).toBe(m.NO_ERROR);let h=l(m,0,0);r(m,c),f(h[1]).toBe(255),m.disableVertexAttribArray(i),m.disableVertexAttribArray(s),m.vertexAttribDivisor(s,0),m.deleteBuffer(u),m.deleteBuffer(d),m.deleteBuffer(p),m.deleteProgram(n)})}),await d(`drawBuffers (MRT)`,async()=>{u(async()=>{e.make_current()}),await p(`drawBuffers with 2 COLOR_ATTACHMENTs renders to both`,async()=>{let e=[`#version 300 es`,`in vec2 position;`,`void main() { gl_Position = vec4(position,0.0,1.0); }`].join(`
30
32
  `),t=[`#version 300 es`,`precision mediump float;`,`layout(location=0) out vec4 c0;`,`layout(location=1) out vec4 c1;`,`void main() { c0 = vec4(1.0,0.0,0.0,1.0); c1 = vec4(0.0,0.0,1.0,1.0); }`].join(`
31
33
  `),n=a(m,e,t);f(m.getProgramParameter(n,m.LINK_STATUS)).toBeTruthy();let r=m.createFramebuffer();m.bindFramebuffer(m.FRAMEBUFFER,r);let o=m.createTexture();m.bindTexture(m.TEXTURE_2D,o),m.texImage2D(m.TEXTURE_2D,0,m.RGBA,4,4,0,m.RGBA,m.UNSIGNED_BYTE,null),m.framebufferTexture2D(m.FRAMEBUFFER,m.COLOR_ATTACHMENT0,m.TEXTURE_2D,o,0);let s=m.createTexture();m.bindTexture(m.TEXTURE_2D,s),m.texImage2D(m.TEXTURE_2D,0,m.RGBA,4,4,0,m.RGBA,m.UNSIGNED_BYTE,null),m.framebufferTexture2D(m.FRAMEBUFFER,m.COLOR_ATTACHMENT1,m.TEXTURE_2D,s,0);let c=m.checkFramebufferStatus(m.FRAMEBUFFER);if(c!==m.FRAMEBUFFER_COMPLETE){console.warn(`MRT framebuffer not complete:`,c),m.bindFramebuffer(m.FRAMEBUFFER,null),m.deleteFramebuffer(r),m.deleteTexture(o),m.deleteTexture(s),m.deleteProgram(n);return}m.drawBuffers([m.COLOR_ATTACHMENT0,m.COLOR_ATTACHMENT1]),m.viewport(0,0,4,4),m.clearColor(0,0,0,1),m.clear(m.COLOR_BUFFER_BIT),m.useProgram(n),i(m),f(m.getError()).toBe(m.NO_ERROR),m.readBuffer(m.COLOR_ATTACHMENT0);let u=l(m,0,0);f(u[0]).toBe(255),f(u[1]).toBe(0),m.readBuffer(m.COLOR_ATTACHMENT1);let d=l(m,0,0);f(d[0]).toBe(0),f(d[2]).toBe(255),m.bindFramebuffer(m.FRAMEBUFFER,null),m.deleteFramebuffer(r),m.deleteTexture(o),m.deleteTexture(s),m.deleteProgram(n)})}),await d(`3D texture`,async()=>{u(async()=>{e.make_current()}),await p(`createTexture + TEXTURE_3D binding works`,async()=>{let e=m.createTexture();f(e).not.toBeNull(),m.bindTexture(m.TEXTURE_3D,e),f(m.getError()).toBe(m.NO_ERROR),m.deleteTexture(e)}),await p(`texImage3D uploads a 2×2×2 RGBA texture without error`,async()=>{let e=m.createTexture();m.bindTexture(m.TEXTURE_3D,e),m.texParameteri(m.TEXTURE_3D,m.TEXTURE_MIN_FILTER,m.NEAREST),m.texParameteri(m.TEXTURE_3D,m.TEXTURE_MAG_FILTER,m.NEAREST),m.texParameteri(m.TEXTURE_3D,m.TEXTURE_WRAP_S,m.CLAMP_TO_EDGE),m.texParameteri(m.TEXTURE_3D,m.TEXTURE_WRAP_T,m.CLAMP_TO_EDGE),m.texParameteri(m.TEXTURE_3D,m.TEXTURE_WRAP_R,m.CLAMP_TO_EDGE);let t=new Uint8Array(32).fill(128);m.texImage3D(m.TEXTURE_3D,0,m.RGBA,2,2,2,0,m.RGBA,m.UNSIGNED_BYTE,t),f(m.getError()).toBe(m.NO_ERROR),m.deleteTexture(e)}),await p(`texStorage3D allocates storage without error`,async()=>{let e=m.createTexture();m.bindTexture(m.TEXTURE_3D,e),m.texStorage3D(m.TEXTURE_3D,1,m.RGBA8,4,4,4),f(m.getError()).toBe(m.NO_ERROR),m.deleteTexture(e)})}),await d(`transform feedback`,async()=>{u(async()=>{e.make_current()}),await p(`captures vertex shader output via transform feedback`,async()=>{let e=[`#version 300 es`,`in float inVal;`,`out float outVal;`,`void main() { outVal = inVal * 2.0; gl_Position = vec4(0.0); }`].join(`
32
34
  `),t=[`#version 300 es`,`precision mediump float;`,`out vec4 c;`,`void main() { c = vec4(0.0); }`].join(`
@@ -10,21 +10,16 @@ export declare class HTMLCanvasElement extends BaseHTMLCanvasElement {
10
10
  /**
11
11
  * DRAWING-BUFFER width, in DEVICE pixels (overrides the DOM attr-backed getter).
12
12
  *
13
- * GtkGLArea's framebuffer is `allocation × scale-factor`, and the GL viewport
14
- * is raw device pixels — GTK does NOT apply the surface scale to GL the way
15
- * it silently pre-scales a Cairo context for `Gtk.DrawingArea`. So on a
16
- * scale-factor-3 display (a phone) a viewport computed from the ALLOCATION
17
- * covers the bottom-left ninth of the framebuffer and the rest stays at the
18
- * clear colour. Measured on a OnePlus 6T / postmarketOS, GNOME Mobile,
19
- * 1080×2340 at scale 3: the three.js teapot and the Excalibur game each
20
- * rendered into a 120×218 corner of a 360×655-logical widget.
13
+ * GtkGLArea's framebuffer is `allocation × scale-factor` and the GL viewport is raw
14
+ * device pixels: GTK does NOT apply the surface scale to GL the way it silently
15
+ * pre-scales a Cairo context for `Gtk.DrawingArea`. A viewport computed from the
16
+ * ALLOCATION therefore covers the bottom-left ninth at scale 3 and the rest stays at
17
+ * the clear colour — measured on a OnePlus 6T (postmarketOS, 1080×2340 at scale 3),
18
+ * where the three.js teapot rendered into a 120×218 corner of a 360×655 widget.
21
19
  *
22
- * Reporting device pixels here is also what the browser contract means by
23
- * `canvas.width` — the drawing buffer, which is exactly what
24
- * `WebGLRenderingContextBase.drawingBufferWidth` re-exports. CSS layout size
25
- * stays logical (`clientWidth`/`offsetWidth` below), so a consumer that
26
- * multiplies a layout size by `devicePixelRatio` (`installGlobals()` exposes
27
- * the live scale factor) lands on the same number.
20
+ * Device pixels are also what the browser contract means by `canvas.width` (the
21
+ * drawing buffer). CSS layout size stays logical in `clientWidth`/`offsetWidth`, so a
22
+ * consumer multiplying a layout size by `devicePixelRatio` lands on this number.
28
23
  */
29
24
  get width(): number;
30
25
  set width(_width: number);
@@ -32,9 +27,9 @@ export declare class HTMLCanvasElement extends BaseHTMLCanvasElement {
32
27
  get height(): number;
33
28
  set height(_height: number);
34
29
  /**
35
- * The widget's surface scale factor (1 on a standard display, 2–3 on HiDPI).
36
- * Read live rather than cached: moving a window between monitors changes it,
37
- * and GTK re-allocates the widget rather than recreating the canvas.
30
+ * The widget's surface scale factor. Read live rather than cached: moving a window
31
+ * between monitors changes it, and GTK re-allocates the widget rather than recreating
32
+ * the canvas.
38
33
  */
39
34
  get scaleFactor(): number;
40
35
  /** CSS layout width — the GTK allocation, in LOGICAL pixels. */
@@ -50,25 +45,22 @@ export declare class HTMLCanvasElement extends BaseHTMLCanvasElement {
50
45
  /**
51
46
  * A copy of this canvas is a PLAIN, widget-less one.
52
47
  *
53
- * There is exactly one `Gtk.GLArea` and it stays with the original, so a
54
- * clone cannot be GLArea-backed: the default `new this.constructor()` built
55
- * one with `gtkGlArea === undefined`, and the first `width` read threw. That
56
- * is not a hypothetical — it is how Excalibur switches renderers
57
- * (`canvas.cloneNode(false)` → `replaceChild` → `getContext('2d')`), so the
58
- * whole fallback died at the clone rather than degrading (gjsify#1107).
48
+ * There is one `Gtk.GLArea` and it stays with the original, so a clone cannot be
49
+ * GLArea-backed: the default `new this.constructor()` produced one with
50
+ * `gtkGlArea === undefined` and the first `width` read threw. That is how Excalibur
51
+ * switches renderers (`cloneNode(false)` → `replaceChild` → `getContext('2d')`), so
52
+ * the whole fallback died at the clone instead of degrading (gjsify#1107).
59
53
  *
60
- * A detached canvas is also what a browser hands back: same size, blank
61
- * bitmap, no context, not presented by anything. Dropping to the DOM base
62
- * class gives precisely that, including a writable `width`/`height` — the
63
- * overrides above deliberately ignore writes because GTK owns the widget's
54
+ * A detached canvas is also what a browser hands back: same size, blank bitmap, no
55
+ * context. The DOM base class gives exactly that, including a WRITABLE
56
+ * `width`/`height` — the overrides above ignore writes because GTK owns the widget's
64
57
  * size, and a clone has no widget to own it.
65
58
  */
66
59
  protected _createCloneTarget(): BaseHTMLCanvasElement;
67
60
  /**
68
- * Returns a WebGL rendering context backed by the underlying Gtk.GLArea.
69
- * 'webgl' and 'experimental-webgl' return a WebGLRenderingContext (WebGL 1.0).
70
- * 'webgl2' returns a WebGL2RenderingContext (WebGL 2.0).
71
- * Other context types emit a warning and return null.
61
+ * A WebGL context backed by the underlying Gtk.GLArea: 'webgl' and
62
+ * 'experimental-webgl' give WebGL 1.0, 'webgl2' gives WebGL 2.0, anything else falls
63
+ * through to the base class's context-factory registry.
72
64
  */
73
65
  getContext(contextId: string, options?: unknown): unknown;
74
66
  }
@@ -1,16 +1,11 @@
1
1
  /**
2
- * True when `renderer` names a GL implementation that rasterises on the CPU.
2
+ * True when the unmasked `RENDERER` string names a GL implementation that rasterises on
3
+ * the CPU.
3
4
  *
4
- * This is a DIAGNOSTIC, not a capability check, and deliberately so: a software
5
- * renderer is not by itself a reason to change behaviour. Measured on one
6
- * GPU-less macOS host, the same `Apple Software Renderer` draws a demand-driven
7
- * three.js scene and a flat-shaded full-screen quad without trouble (1.2 ms per
8
- * frame) while a continuously animating, fill-heavy 2D game needs 1.1 s for a
9
- * single textured full-screen draw. What separates them is the workload, not the
10
- * driver — so key any fallback on a MEASURED frame budget and use this only to
11
- * name the cause.
12
- *
13
- * @param renderer The unmasked `RENDERER` string, e.g. from
14
- * `gl.getParameter(gl.getExtension('WEBGL_debug_renderer_info').UNMASKED_RENDERER_WEBGL)`.
5
+ * A DIAGNOSTIC, not a capability check: measured on one GPU-less macOS host, the same
6
+ * `Apple Software Renderer` drew a demand-driven three.js scene at 1.2 ms per frame while
7
+ * a fill-heavy animating 2D game needed 1.1 s for a single textured full-screen draw.
8
+ * The workload separates them, not the driver, so key any fallback on a MEASURED frame
9
+ * budget and use this only to name the cause.
15
10
  */
16
11
  export declare function isSoftwareRenderer(renderer: string | null | undefined): boolean;
@@ -6,22 +6,15 @@ import { HTMLCanvasElement as OurHTMLCanvasElement } from './html-canvas-element
6
6
  import { WebGLRenderingContext as OurWebGLRenderingContext } from './webgl-rendering-context.js';
7
7
  type WebGLReadyCallback = (canvas: globalThis.HTMLCanvasElement, gl: globalThis.WebGLRenderingContext) => void;
8
8
  /**
9
- * A `Gtk.GLArea` subclass that handles WebGL bootstrapping:
10
- * - Sets up OpenGL ES 3.2 context, depth buffer, stencil buffer
11
- * - Creates an `HTMLCanvasElement` wrapping the GLArea on first render
12
- * - Fires `onReady()` callbacks with (canvas, gl) once the context is available
13
- * - Provides `requestAnimationFrame()` backed by GTK frame clock (vsync) + render signal
14
- * - `installGlobals()` sets `globalThis.{requestAnimationFrame, cancelAnimationFrame,
15
- * performance, WebGLRenderingContext, WebGL2RenderingContext}`
9
+ * A `Gtk.GLArea` subclass that negotiates the GL context, wraps it in an
10
+ * `HTMLCanvasElement` on first render, and drives `requestAnimationFrame` off the GTK
11
+ * frame clock.
16
12
  *
17
- * Usage:
13
+ * `installGlobals()` must run BEFORE the app code that expects the browser globals:
18
14
  * ```ts
19
15
  * const widget = new WebGLBridge();
20
- * widget.installGlobals(); // sets globalThis.requestAnimationFrame
21
- * widget.onReady((canvas, gl) => {
22
- * gl.clearColor(0, 0, 0, 1);
23
- * // requestAnimationFrame is now available globally
24
- * });
16
+ * widget.installGlobals();
17
+ * widget.onReady((canvas, gl) => { … });
25
18
  * window.set_child(widget);
26
19
  * ```
27
20
  */
@@ -46,95 +39,68 @@ export declare const WebGLBridge: {
46
39
  /** The HTMLCanvasElement wrapping this GLArea. Available after the first render. */
47
40
  get canvas(): globalThis.HTMLCanvasElement | null;
48
41
  /**
49
- * The GL implementation GDK negotiated, unmasked — `{vendor, renderer}`
50
- * straight from `glGetString`, e.g. `Apple Software Renderer`. `null`
51
- * until the context exists (i.e. before the first `onReady`).
42
+ * The GL implementation GDK negotiated, unmasked, straight from `glGetString`;
43
+ * `null` until the context exists.
52
44
  *
53
- * Read it to REPORT what a host provides. It is not a capability check:
54
- * a CPU rasteriser draws everything a GPU does, only slower, and how much
55
- * slower depends entirely on the workload. Anything that switches
56
- * renderers should key on a measured frame budget, not on this string.
45
+ * For REPORTING, not as a capability check: a CPU rasteriser draws everything a
46
+ * GPU does, only slower, and by how much depends on the workload. Anything that
47
+ * switches renderers should key on a measured frame budget, not on this string.
57
48
  */
58
49
  get rendererInfo(): {
59
50
  vendor: string;
60
51
  renderer: string;
61
52
  } | null;
62
- /**
63
- * Registers a callback to be called once the WebGL context is ready.
64
- * If the context is already available, the callback fires synchronously.
65
- */
53
+ /** Runs `cb` once the WebGL context is ready — synchronously if it already is. */
66
54
  onReady(cb: WebGLReadyCallback): void;
67
55
  /**
68
56
  * @deprecated Use `onReady()` instead.
69
57
  */
70
58
  onWebGLReady(cb: WebGLReadyCallback): void;
71
59
  /**
72
- * Records the negotiated GL implementation and, when it rasterises on the
73
- * CPU, says so ONCE on stderr.
60
+ * Records the negotiated GL implementation and, when it rasterises on the CPU,
61
+ * says so ONCE on stderr.
74
62
  *
75
- * A GPU-less host is invisible from inside the app: context creation
76
- * succeeds, every draw call succeeds, nothing rejects, and the only tell
77
- * is a frame budget blown by three orders of magnitude — which presents as
78
- * a window that sits there. Naming the renderer at the one place that
79
- * knows it turns that into a line anyone can act on, for every consumer,
80
- * without any of them asking.
63
+ * A GPU-less host is otherwise invisible from inside the app: the context is
64
+ * created, every draw call succeeds, and the only tell is a frame budget blown by
65
+ * three orders of magnitude, which presents as a window that just sits there.
81
66
  *
82
- * Diagnostic only: it changes no behaviour, because a software renderer is
83
- * fine for plenty of workloads (measured on one GPU-less macOS host: a
84
- * demand-driven three.js scene and a flat-shaded full-screen quad both run
85
- * comfortably on the same driver that needs 1.1 s for one textured
86
- * full-screen draw in a 2D game).
67
+ * Diagnostic only, and deliberately changes no behaviour: a software renderer is
68
+ * fine for plenty of workloads. Measured on one GPU-less macOS host, a
69
+ * demand-driven three.js scene ran comfortably on the same driver that needed
70
+ * 1.1 s for a single textured full-screen draw in a 2D game.
87
71
  */
88
72
  _reportRenderer(gl: OurWebGLRenderingContext): void;
89
73
  /**
90
- * Register a callback invoked whenever the GTK widget is resized.
91
- * The callback fires alongside the native 'resize' GObject signal and
92
- * after the DOM 'resize' event has been dispatched on the canvas.
93
- * Canvas buffer dimensions are NOT automatically updated — consumers
94
- * should set `canvas.width`/`canvas.height` themselves if desired.
74
+ * Runs `cb` on every GTK resize, after the DOM 'resize' event has been dispatched
75
+ * on the canvas. Canvas buffer dimensions are NOT updated for you: set
76
+ * `canvas.width`/`canvas.height` yourself if the drawing buffer must follow.
95
77
  */
96
78
  onResize(cb: (width: number, height: number) => void): void;
97
79
  /**
98
- * Schedules a single animation frame callback, matching the browser `requestAnimationFrame` API.
99
- * Backed by a persistent GTK frame clock tick callback (vsync-synced) + a persistent GLArea
100
- * render signal handler. Both are installed once at construction / first render respectively,
101
- * eliminating per-frame GLib.Source allocation and GObject signal connect/disconnect overhead.
80
+ * Browser `requestAnimationFrame`, driven by the vsync-synced GTK frame clock.
102
81
  *
103
- * Returns a monotonically-increasing handle; pass it to `cancelAnimationFrame(id)` to cancel
104
- * the specific pending callback. A second `requestAnimationFrame` call before the first has
105
- * fired replaces the pending callback (single-slot model — matching the existing rAF
106
- * convention of "queue 1 callback per frame, the latest wins"); the older handle becomes
107
- * stale and a `cancelAnimationFrame` with that handle is a no-op.
82
+ * SINGLE SLOT: a second call before the first has fired REPLACES the pending
83
+ * callback (latest wins), which makes the older handle stale and a
84
+ * `cancelAnimationFrame` with it a no-op.
108
85
  */
109
86
  requestAnimationFrame(cb: FrameRequestCallback): number;
110
87
  /**
111
- * Cancels a previously-scheduled animation frame callback, matching the browser
112
- * `cancelAnimationFrame` API. Only clears the pending callback when `id` matches
113
- * the handle returned by the most recent `requestAnimationFrame` call — a stale
114
- * handle (from a callback that already fired, or was replaced by a later
115
- * `requestAnimationFrame`) is a no-op.
88
+ * Browser `cancelAnimationFrame`. Clears the pending callback only when `id` is
89
+ * the handle from the most recent `requestAnimationFrame`; a stale handle is a
90
+ * no-op.
116
91
  *
117
- * Per-ID matching is load-bearing: without it any consumer that calls
118
- * `cancelAnimationFrame` (e.g. Excalibur's `Engine.stop()`, called from a
119
- * transient parent `unmap` in a responsive layout) would clear the loop's
120
- * latest pending callback and freeze rendering until the next manual
121
- * `requestAnimationFrame`. With it, the cancel only fires for the handle
122
- * the caller actually owns.
92
+ * That matching is load-bearing: without it any stray cancel (Excalibur's
93
+ * `Engine.stop()`, fired from a transient parent `unmap` in a responsive layout)
94
+ * kills the loop's current callback and freezes rendering.
123
95
  */
124
96
  cancelAnimationFrame(id: number): void;
125
97
  /**
126
- * Sets the browser globals this bridge owns — `requestAnimationFrame`,
127
- * `cancelAnimationFrame`, `performance` and the two WebGL context
128
- * constructors — so that browser-targeted code (e.g. Three.js) works
129
- * unchanged on GJS.
98
+ * Sets the browser globals this bridge owns, so browser-targeted code (Three.js)
99
+ * runs unchanged on GJS.
130
100
  *
131
- * `WebGLRenderingContext` / `WebGL2RenderingContext` are installed
132
- * UNCONDITIONALLY here: per ADR 0012 rule 5 a bridge's `installGlobals()`
133
- * and its `/register` install the same set, `installGlobals()` being the
134
- * explicit imperative path (`@gjsify/webgl/register` keeps the
135
- * if-undefined guard instead). They used to reach `globalThis` for free
136
- * as a side effect of importing the `@gjsify/webgl` barrel; moving that
137
- * write into `register.ts` must not shrink what this method does.
101
+ * The two context constructors are installed UNCONDITIONALLY: per ADR 0012 rule 5
102
+ * `installGlobals()` is the explicit imperative path and installs the same set as
103
+ * `@gjsify/webgl/register`, which keeps the if-undefined guard instead.
138
104
  */
139
105
  installGlobals(): void;
140
106
  get allowed_apis(): Gdk.GLAPI;
@@ -577,95 +543,68 @@ export declare const WebGLBridge: {
577
543
  /** The HTMLCanvasElement wrapping this GLArea. Available after the first render. */
578
544
  get canvas(): globalThis.HTMLCanvasElement | null;
579
545
  /**
580
- * The GL implementation GDK negotiated, unmasked — `{vendor, renderer}`
581
- * straight from `glGetString`, e.g. `Apple Software Renderer`. `null`
582
- * until the context exists (i.e. before the first `onReady`).
546
+ * The GL implementation GDK negotiated, unmasked, straight from `glGetString`;
547
+ * `null` until the context exists.
583
548
  *
584
- * Read it to REPORT what a host provides. It is not a capability check:
585
- * a CPU rasteriser draws everything a GPU does, only slower, and how much
586
- * slower depends entirely on the workload. Anything that switches
587
- * renderers should key on a measured frame budget, not on this string.
549
+ * For REPORTING, not as a capability check: a CPU rasteriser draws everything a
550
+ * GPU does, only slower, and by how much depends on the workload. Anything that
551
+ * switches renderers should key on a measured frame budget, not on this string.
588
552
  */
589
553
  get rendererInfo(): {
590
554
  vendor: string;
591
555
  renderer: string;
592
556
  } | null;
593
- /**
594
- * Registers a callback to be called once the WebGL context is ready.
595
- * If the context is already available, the callback fires synchronously.
596
- */
557
+ /** Runs `cb` once the WebGL context is ready — synchronously if it already is. */
597
558
  onReady(cb: WebGLReadyCallback): void;
598
559
  /**
599
560
  * @deprecated Use `onReady()` instead.
600
561
  */
601
562
  onWebGLReady(cb: WebGLReadyCallback): void;
602
563
  /**
603
- * Records the negotiated GL implementation and, when it rasterises on the
604
- * CPU, says so ONCE on stderr.
564
+ * Records the negotiated GL implementation and, when it rasterises on the CPU,
565
+ * says so ONCE on stderr.
605
566
  *
606
- * A GPU-less host is invisible from inside the app: context creation
607
- * succeeds, every draw call succeeds, nothing rejects, and the only tell
608
- * is a frame budget blown by three orders of magnitude — which presents as
609
- * a window that sits there. Naming the renderer at the one place that
610
- * knows it turns that into a line anyone can act on, for every consumer,
611
- * without any of them asking.
567
+ * A GPU-less host is otherwise invisible from inside the app: the context is
568
+ * created, every draw call succeeds, and the only tell is a frame budget blown by
569
+ * three orders of magnitude, which presents as a window that just sits there.
612
570
  *
613
- * Diagnostic only: it changes no behaviour, because a software renderer is
614
- * fine for plenty of workloads (measured on one GPU-less macOS host: a
615
- * demand-driven three.js scene and a flat-shaded full-screen quad both run
616
- * comfortably on the same driver that needs 1.1 s for one textured
617
- * full-screen draw in a 2D game).
571
+ * Diagnostic only, and deliberately changes no behaviour: a software renderer is
572
+ * fine for plenty of workloads. Measured on one GPU-less macOS host, a
573
+ * demand-driven three.js scene ran comfortably on the same driver that needed
574
+ * 1.1 s for a single textured full-screen draw in a 2D game.
618
575
  */
619
576
  _reportRenderer(gl: OurWebGLRenderingContext): void;
620
577
  /**
621
- * Register a callback invoked whenever the GTK widget is resized.
622
- * The callback fires alongside the native 'resize' GObject signal and
623
- * after the DOM 'resize' event has been dispatched on the canvas.
624
- * Canvas buffer dimensions are NOT automatically updated — consumers
625
- * should set `canvas.width`/`canvas.height` themselves if desired.
578
+ * Runs `cb` on every GTK resize, after the DOM 'resize' event has been dispatched
579
+ * on the canvas. Canvas buffer dimensions are NOT updated for you: set
580
+ * `canvas.width`/`canvas.height` yourself if the drawing buffer must follow.
626
581
  */
627
582
  onResize(cb: (width: number, height: number) => void): void;
628
583
  /**
629
- * Schedules a single animation frame callback, matching the browser `requestAnimationFrame` API.
630
- * Backed by a persistent GTK frame clock tick callback (vsync-synced) + a persistent GLArea
631
- * render signal handler. Both are installed once at construction / first render respectively,
632
- * eliminating per-frame GLib.Source allocation and GObject signal connect/disconnect overhead.
584
+ * Browser `requestAnimationFrame`, driven by the vsync-synced GTK frame clock.
633
585
  *
634
- * Returns a monotonically-increasing handle; pass it to `cancelAnimationFrame(id)` to cancel
635
- * the specific pending callback. A second `requestAnimationFrame` call before the first has
636
- * fired replaces the pending callback (single-slot model — matching the existing rAF
637
- * convention of "queue 1 callback per frame, the latest wins"); the older handle becomes
638
- * stale and a `cancelAnimationFrame` with that handle is a no-op.
586
+ * SINGLE SLOT: a second call before the first has fired REPLACES the pending
587
+ * callback (latest wins), which makes the older handle stale and a
588
+ * `cancelAnimationFrame` with it a no-op.
639
589
  */
640
590
  requestAnimationFrame(cb: FrameRequestCallback): number;
641
591
  /**
642
- * Cancels a previously-scheduled animation frame callback, matching the browser
643
- * `cancelAnimationFrame` API. Only clears the pending callback when `id` matches
644
- * the handle returned by the most recent `requestAnimationFrame` call — a stale
645
- * handle (from a callback that already fired, or was replaced by a later
646
- * `requestAnimationFrame`) is a no-op.
592
+ * Browser `cancelAnimationFrame`. Clears the pending callback only when `id` is
593
+ * the handle from the most recent `requestAnimationFrame`; a stale handle is a
594
+ * no-op.
647
595
  *
648
- * Per-ID matching is load-bearing: without it any consumer that calls
649
- * `cancelAnimationFrame` (e.g. Excalibur's `Engine.stop()`, called from a
650
- * transient parent `unmap` in a responsive layout) would clear the loop's
651
- * latest pending callback and freeze rendering until the next manual
652
- * `requestAnimationFrame`. With it, the cancel only fires for the handle
653
- * the caller actually owns.
596
+ * That matching is load-bearing: without it any stray cancel (Excalibur's
597
+ * `Engine.stop()`, fired from a transient parent `unmap` in a responsive layout)
598
+ * kills the loop's current callback and freezes rendering.
654
599
  */
655
600
  cancelAnimationFrame(id: number): void;
656
601
  /**
657
- * Sets the browser globals this bridge owns — `requestAnimationFrame`,
658
- * `cancelAnimationFrame`, `performance` and the two WebGL context
659
- * constructors — so that browser-targeted code (e.g. Three.js) works
660
- * unchanged on GJS.
602
+ * Sets the browser globals this bridge owns, so browser-targeted code (Three.js)
603
+ * runs unchanged on GJS.
661
604
  *
662
- * `WebGLRenderingContext` / `WebGL2RenderingContext` are installed
663
- * UNCONDITIONALLY here: per ADR 0012 rule 5 a bridge's `installGlobals()`
664
- * and its `/register` install the same set, `installGlobals()` being the
665
- * explicit imperative path (`@gjsify/webgl/register` keeps the
666
- * if-undefined guard instead). They used to reach `globalThis` for free
667
- * as a side effect of importing the `@gjsify/webgl` barrel; moving that
668
- * write into `register.ts` must not shrink what this method does.
605
+ * The two context constructors are installed UNCONDITIONALLY: per ADR 0012 rule 5
606
+ * `installGlobals()` is the explicit imperative path and installs the same set as
607
+ * `@gjsify/webgl/register`, which keeps the if-undefined guard instead.
669
608
  */
670
609
  installGlobals(): void;
671
610
  get allowed_apis(): Gdk.GLAPI;
@@ -1108,95 +1047,68 @@ export declare const WebGLBridge: {
1108
1047
  /** The HTMLCanvasElement wrapping this GLArea. Available after the first render. */
1109
1048
  get canvas(): globalThis.HTMLCanvasElement | null;
1110
1049
  /**
1111
- * The GL implementation GDK negotiated, unmasked — `{vendor, renderer}`
1112
- * straight from `glGetString`, e.g. `Apple Software Renderer`. `null`
1113
- * until the context exists (i.e. before the first `onReady`).
1050
+ * The GL implementation GDK negotiated, unmasked, straight from `glGetString`;
1051
+ * `null` until the context exists.
1114
1052
  *
1115
- * Read it to REPORT what a host provides. It is not a capability check:
1116
- * a CPU rasteriser draws everything a GPU does, only slower, and how much
1117
- * slower depends entirely on the workload. Anything that switches
1118
- * renderers should key on a measured frame budget, not on this string.
1053
+ * For REPORTING, not as a capability check: a CPU rasteriser draws everything a
1054
+ * GPU does, only slower, and by how much depends on the workload. Anything that
1055
+ * switches renderers should key on a measured frame budget, not on this string.
1119
1056
  */
1120
1057
  get rendererInfo(): {
1121
1058
  vendor: string;
1122
1059
  renderer: string;
1123
1060
  } | null;
1124
- /**
1125
- * Registers a callback to be called once the WebGL context is ready.
1126
- * If the context is already available, the callback fires synchronously.
1127
- */
1061
+ /** Runs `cb` once the WebGL context is ready — synchronously if it already is. */
1128
1062
  onReady(cb: WebGLReadyCallback): void;
1129
1063
  /**
1130
1064
  * @deprecated Use `onReady()` instead.
1131
1065
  */
1132
1066
  onWebGLReady(cb: WebGLReadyCallback): void;
1133
1067
  /**
1134
- * Records the negotiated GL implementation and, when it rasterises on the
1135
- * CPU, says so ONCE on stderr.
1068
+ * Records the negotiated GL implementation and, when it rasterises on the CPU,
1069
+ * says so ONCE on stderr.
1136
1070
  *
1137
- * A GPU-less host is invisible from inside the app: context creation
1138
- * succeeds, every draw call succeeds, nothing rejects, and the only tell
1139
- * is a frame budget blown by three orders of magnitude — which presents as
1140
- * a window that sits there. Naming the renderer at the one place that
1141
- * knows it turns that into a line anyone can act on, for every consumer,
1142
- * without any of them asking.
1071
+ * A GPU-less host is otherwise invisible from inside the app: the context is
1072
+ * created, every draw call succeeds, and the only tell is a frame budget blown by
1073
+ * three orders of magnitude, which presents as a window that just sits there.
1143
1074
  *
1144
- * Diagnostic only: it changes no behaviour, because a software renderer is
1145
- * fine for plenty of workloads (measured on one GPU-less macOS host: a
1146
- * demand-driven three.js scene and a flat-shaded full-screen quad both run
1147
- * comfortably on the same driver that needs 1.1 s for one textured
1148
- * full-screen draw in a 2D game).
1075
+ * Diagnostic only, and deliberately changes no behaviour: a software renderer is
1076
+ * fine for plenty of workloads. Measured on one GPU-less macOS host, a
1077
+ * demand-driven three.js scene ran comfortably on the same driver that needed
1078
+ * 1.1 s for a single textured full-screen draw in a 2D game.
1149
1079
  */
1150
1080
  _reportRenderer(gl: OurWebGLRenderingContext): void;
1151
1081
  /**
1152
- * Register a callback invoked whenever the GTK widget is resized.
1153
- * The callback fires alongside the native 'resize' GObject signal and
1154
- * after the DOM 'resize' event has been dispatched on the canvas.
1155
- * Canvas buffer dimensions are NOT automatically updated — consumers
1156
- * should set `canvas.width`/`canvas.height` themselves if desired.
1082
+ * Runs `cb` on every GTK resize, after the DOM 'resize' event has been dispatched
1083
+ * on the canvas. Canvas buffer dimensions are NOT updated for you: set
1084
+ * `canvas.width`/`canvas.height` yourself if the drawing buffer must follow.
1157
1085
  */
1158
1086
  onResize(cb: (width: number, height: number) => void): void;
1159
1087
  /**
1160
- * Schedules a single animation frame callback, matching the browser `requestAnimationFrame` API.
1161
- * Backed by a persistent GTK frame clock tick callback (vsync-synced) + a persistent GLArea
1162
- * render signal handler. Both are installed once at construction / first render respectively,
1163
- * eliminating per-frame GLib.Source allocation and GObject signal connect/disconnect overhead.
1088
+ * Browser `requestAnimationFrame`, driven by the vsync-synced GTK frame clock.
1164
1089
  *
1165
- * Returns a monotonically-increasing handle; pass it to `cancelAnimationFrame(id)` to cancel
1166
- * the specific pending callback. A second `requestAnimationFrame` call before the first has
1167
- * fired replaces the pending callback (single-slot model — matching the existing rAF
1168
- * convention of "queue 1 callback per frame, the latest wins"); the older handle becomes
1169
- * stale and a `cancelAnimationFrame` with that handle is a no-op.
1090
+ * SINGLE SLOT: a second call before the first has fired REPLACES the pending
1091
+ * callback (latest wins), which makes the older handle stale and a
1092
+ * `cancelAnimationFrame` with it a no-op.
1170
1093
  */
1171
1094
  requestAnimationFrame(cb: FrameRequestCallback): number;
1172
1095
  /**
1173
- * Cancels a previously-scheduled animation frame callback, matching the browser
1174
- * `cancelAnimationFrame` API. Only clears the pending callback when `id` matches
1175
- * the handle returned by the most recent `requestAnimationFrame` call — a stale
1176
- * handle (from a callback that already fired, or was replaced by a later
1177
- * `requestAnimationFrame`) is a no-op.
1096
+ * Browser `cancelAnimationFrame`. Clears the pending callback only when `id` is
1097
+ * the handle from the most recent `requestAnimationFrame`; a stale handle is a
1098
+ * no-op.
1178
1099
  *
1179
- * Per-ID matching is load-bearing: without it any consumer that calls
1180
- * `cancelAnimationFrame` (e.g. Excalibur's `Engine.stop()`, called from a
1181
- * transient parent `unmap` in a responsive layout) would clear the loop's
1182
- * latest pending callback and freeze rendering until the next manual
1183
- * `requestAnimationFrame`. With it, the cancel only fires for the handle
1184
- * the caller actually owns.
1100
+ * That matching is load-bearing: without it any stray cancel (Excalibur's
1101
+ * `Engine.stop()`, fired from a transient parent `unmap` in a responsive layout)
1102
+ * kills the loop's current callback and freezes rendering.
1185
1103
  */
1186
1104
  cancelAnimationFrame(id: number): void;
1187
1105
  /**
1188
- * Sets the browser globals this bridge owns — `requestAnimationFrame`,
1189
- * `cancelAnimationFrame`, `performance` and the two WebGL context
1190
- * constructors — so that browser-targeted code (e.g. Three.js) works
1191
- * unchanged on GJS.
1106
+ * Sets the browser globals this bridge owns, so browser-targeted code (Three.js)
1107
+ * runs unchanged on GJS.
1192
1108
  *
1193
- * `WebGLRenderingContext` / `WebGL2RenderingContext` are installed
1194
- * UNCONDITIONALLY here: per ADR 0012 rule 5 a bridge's `installGlobals()`
1195
- * and its `/register` install the same set, `installGlobals()` being the
1196
- * explicit imperative path (`@gjsify/webgl/register` keeps the
1197
- * if-undefined guard instead). They used to reach `globalThis` for free
1198
- * as a side effect of importing the `@gjsify/webgl` barrel; moving that
1199
- * write into `register.ts` must not shrink what this method does.
1109
+ * The two context constructors are installed UNCONDITIONALLY: per ADR 0012 rule 5
1110
+ * `installGlobals()` is the explicit imperative path and installs the same set as
1111
+ * `@gjsify/webgl/register`, which keeps the if-undefined guard instead.
1200
1112
  */
1201
1113
  installGlobals(): void;
1202
1114
  get allowed_apis(): Gdk.GLAPI;
@@ -1639,95 +1551,68 @@ export declare const WebGLBridge: {
1639
1551
  /** The HTMLCanvasElement wrapping this GLArea. Available after the first render. */
1640
1552
  get canvas(): globalThis.HTMLCanvasElement | null;
1641
1553
  /**
1642
- * The GL implementation GDK negotiated, unmasked — `{vendor, renderer}`
1643
- * straight from `glGetString`, e.g. `Apple Software Renderer`. `null`
1644
- * until the context exists (i.e. before the first `onReady`).
1554
+ * The GL implementation GDK negotiated, unmasked, straight from `glGetString`;
1555
+ * `null` until the context exists.
1645
1556
  *
1646
- * Read it to REPORT what a host provides. It is not a capability check:
1647
- * a CPU rasteriser draws everything a GPU does, only slower, and how much
1648
- * slower depends entirely on the workload. Anything that switches
1649
- * renderers should key on a measured frame budget, not on this string.
1557
+ * For REPORTING, not as a capability check: a CPU rasteriser draws everything a
1558
+ * GPU does, only slower, and by how much depends on the workload. Anything that
1559
+ * switches renderers should key on a measured frame budget, not on this string.
1650
1560
  */
1651
1561
  get rendererInfo(): {
1652
1562
  vendor: string;
1653
1563
  renderer: string;
1654
1564
  } | null;
1655
- /**
1656
- * Registers a callback to be called once the WebGL context is ready.
1657
- * If the context is already available, the callback fires synchronously.
1658
- */
1565
+ /** Runs `cb` once the WebGL context is ready — synchronously if it already is. */
1659
1566
  onReady(cb: WebGLReadyCallback): void;
1660
1567
  /**
1661
1568
  * @deprecated Use `onReady()` instead.
1662
1569
  */
1663
1570
  onWebGLReady(cb: WebGLReadyCallback): void;
1664
1571
  /**
1665
- * Records the negotiated GL implementation and, when it rasterises on the
1666
- * CPU, says so ONCE on stderr.
1572
+ * Records the negotiated GL implementation and, when it rasterises on the CPU,
1573
+ * says so ONCE on stderr.
1667
1574
  *
1668
- * A GPU-less host is invisible from inside the app: context creation
1669
- * succeeds, every draw call succeeds, nothing rejects, and the only tell
1670
- * is a frame budget blown by three orders of magnitude — which presents as
1671
- * a window that sits there. Naming the renderer at the one place that
1672
- * knows it turns that into a line anyone can act on, for every consumer,
1673
- * without any of them asking.
1575
+ * A GPU-less host is otherwise invisible from inside the app: the context is
1576
+ * created, every draw call succeeds, and the only tell is a frame budget blown by
1577
+ * three orders of magnitude, which presents as a window that just sits there.
1674
1578
  *
1675
- * Diagnostic only: it changes no behaviour, because a software renderer is
1676
- * fine for plenty of workloads (measured on one GPU-less macOS host: a
1677
- * demand-driven three.js scene and a flat-shaded full-screen quad both run
1678
- * comfortably on the same driver that needs 1.1 s for one textured
1679
- * full-screen draw in a 2D game).
1579
+ * Diagnostic only, and deliberately changes no behaviour: a software renderer is
1580
+ * fine for plenty of workloads. Measured on one GPU-less macOS host, a
1581
+ * demand-driven three.js scene ran comfortably on the same driver that needed
1582
+ * 1.1 s for a single textured full-screen draw in a 2D game.
1680
1583
  */
1681
1584
  _reportRenderer(gl: OurWebGLRenderingContext): void;
1682
1585
  /**
1683
- * Register a callback invoked whenever the GTK widget is resized.
1684
- * The callback fires alongside the native 'resize' GObject signal and
1685
- * after the DOM 'resize' event has been dispatched on the canvas.
1686
- * Canvas buffer dimensions are NOT automatically updated — consumers
1687
- * should set `canvas.width`/`canvas.height` themselves if desired.
1586
+ * Runs `cb` on every GTK resize, after the DOM 'resize' event has been dispatched
1587
+ * on the canvas. Canvas buffer dimensions are NOT updated for you: set
1588
+ * `canvas.width`/`canvas.height` yourself if the drawing buffer must follow.
1688
1589
  */
1689
1590
  onResize(cb: (width: number, height: number) => void): void;
1690
1591
  /**
1691
- * Schedules a single animation frame callback, matching the browser `requestAnimationFrame` API.
1692
- * Backed by a persistent GTK frame clock tick callback (vsync-synced) + a persistent GLArea
1693
- * render signal handler. Both are installed once at construction / first render respectively,
1694
- * eliminating per-frame GLib.Source allocation and GObject signal connect/disconnect overhead.
1592
+ * Browser `requestAnimationFrame`, driven by the vsync-synced GTK frame clock.
1695
1593
  *
1696
- * Returns a monotonically-increasing handle; pass it to `cancelAnimationFrame(id)` to cancel
1697
- * the specific pending callback. A second `requestAnimationFrame` call before the first has
1698
- * fired replaces the pending callback (single-slot model — matching the existing rAF
1699
- * convention of "queue 1 callback per frame, the latest wins"); the older handle becomes
1700
- * stale and a `cancelAnimationFrame` with that handle is a no-op.
1594
+ * SINGLE SLOT: a second call before the first has fired REPLACES the pending
1595
+ * callback (latest wins), which makes the older handle stale and a
1596
+ * `cancelAnimationFrame` with it a no-op.
1701
1597
  */
1702
1598
  requestAnimationFrame(cb: FrameRequestCallback): number;
1703
1599
  /**
1704
- * Cancels a previously-scheduled animation frame callback, matching the browser
1705
- * `cancelAnimationFrame` API. Only clears the pending callback when `id` matches
1706
- * the handle returned by the most recent `requestAnimationFrame` call — a stale
1707
- * handle (from a callback that already fired, or was replaced by a later
1708
- * `requestAnimationFrame`) is a no-op.
1600
+ * Browser `cancelAnimationFrame`. Clears the pending callback only when `id` is
1601
+ * the handle from the most recent `requestAnimationFrame`; a stale handle is a
1602
+ * no-op.
1709
1603
  *
1710
- * Per-ID matching is load-bearing: without it any consumer that calls
1711
- * `cancelAnimationFrame` (e.g. Excalibur's `Engine.stop()`, called from a
1712
- * transient parent `unmap` in a responsive layout) would clear the loop's
1713
- * latest pending callback and freeze rendering until the next manual
1714
- * `requestAnimationFrame`. With it, the cancel only fires for the handle
1715
- * the caller actually owns.
1604
+ * That matching is load-bearing: without it any stray cancel (Excalibur's
1605
+ * `Engine.stop()`, fired from a transient parent `unmap` in a responsive layout)
1606
+ * kills the loop's current callback and freezes rendering.
1716
1607
  */
1717
1608
  cancelAnimationFrame(id: number): void;
1718
1609
  /**
1719
- * Sets the browser globals this bridge owns — `requestAnimationFrame`,
1720
- * `cancelAnimationFrame`, `performance` and the two WebGL context
1721
- * constructors — so that browser-targeted code (e.g. Three.js) works
1722
- * unchanged on GJS.
1610
+ * Sets the browser globals this bridge owns, so browser-targeted code (Three.js)
1611
+ * runs unchanged on GJS.
1723
1612
  *
1724
- * `WebGLRenderingContext` / `WebGL2RenderingContext` are installed
1725
- * UNCONDITIONALLY here: per ADR 0012 rule 5 a bridge's `installGlobals()`
1726
- * and its `/register` install the same set, `installGlobals()` being the
1727
- * explicit imperative path (`@gjsify/webgl/register` keeps the
1728
- * if-undefined guard instead). They used to reach `globalThis` for free
1729
- * as a side effect of importing the `@gjsify/webgl` barrel; moving that
1730
- * write into `register.ts` must not shrink what this method does.
1613
+ * The two context constructors are installed UNCONDITIONALLY: per ADR 0012 rule 5
1614
+ * `installGlobals()` is the explicit imperative path and installs the same set as
1615
+ * `@gjsify/webgl/register`, which keeps the if-undefined guard instead.
1731
1616
  */
1732
1617
  installGlobals(): void;
1733
1618
  get allowed_apis(): Gdk.GLAPI;
@@ -2170,95 +2055,68 @@ export declare const WebGLBridge: {
2170
2055
  /** The HTMLCanvasElement wrapping this GLArea. Available after the first render. */
2171
2056
  get canvas(): globalThis.HTMLCanvasElement | null;
2172
2057
  /**
2173
- * The GL implementation GDK negotiated, unmasked — `{vendor, renderer}`
2174
- * straight from `glGetString`, e.g. `Apple Software Renderer`. `null`
2175
- * until the context exists (i.e. before the first `onReady`).
2058
+ * The GL implementation GDK negotiated, unmasked, straight from `glGetString`;
2059
+ * `null` until the context exists.
2176
2060
  *
2177
- * Read it to REPORT what a host provides. It is not a capability check:
2178
- * a CPU rasteriser draws everything a GPU does, only slower, and how much
2179
- * slower depends entirely on the workload. Anything that switches
2180
- * renderers should key on a measured frame budget, not on this string.
2061
+ * For REPORTING, not as a capability check: a CPU rasteriser draws everything a
2062
+ * GPU does, only slower, and by how much depends on the workload. Anything that
2063
+ * switches renderers should key on a measured frame budget, not on this string.
2181
2064
  */
2182
2065
  get rendererInfo(): {
2183
2066
  vendor: string;
2184
2067
  renderer: string;
2185
2068
  } | null;
2186
- /**
2187
- * Registers a callback to be called once the WebGL context is ready.
2188
- * If the context is already available, the callback fires synchronously.
2189
- */
2069
+ /** Runs `cb` once the WebGL context is ready — synchronously if it already is. */
2190
2070
  onReady(cb: WebGLReadyCallback): void;
2191
2071
  /**
2192
2072
  * @deprecated Use `onReady()` instead.
2193
2073
  */
2194
2074
  onWebGLReady(cb: WebGLReadyCallback): void;
2195
2075
  /**
2196
- * Records the negotiated GL implementation and, when it rasterises on the
2197
- * CPU, says so ONCE on stderr.
2076
+ * Records the negotiated GL implementation and, when it rasterises on the CPU,
2077
+ * says so ONCE on stderr.
2198
2078
  *
2199
- * A GPU-less host is invisible from inside the app: context creation
2200
- * succeeds, every draw call succeeds, nothing rejects, and the only tell
2201
- * is a frame budget blown by three orders of magnitude — which presents as
2202
- * a window that sits there. Naming the renderer at the one place that
2203
- * knows it turns that into a line anyone can act on, for every consumer,
2204
- * without any of them asking.
2079
+ * A GPU-less host is otherwise invisible from inside the app: the context is
2080
+ * created, every draw call succeeds, and the only tell is a frame budget blown by
2081
+ * three orders of magnitude, which presents as a window that just sits there.
2205
2082
  *
2206
- * Diagnostic only: it changes no behaviour, because a software renderer is
2207
- * fine for plenty of workloads (measured on one GPU-less macOS host: a
2208
- * demand-driven three.js scene and a flat-shaded full-screen quad both run
2209
- * comfortably on the same driver that needs 1.1 s for one textured
2210
- * full-screen draw in a 2D game).
2083
+ * Diagnostic only, and deliberately changes no behaviour: a software renderer is
2084
+ * fine for plenty of workloads. Measured on one GPU-less macOS host, a
2085
+ * demand-driven three.js scene ran comfortably on the same driver that needed
2086
+ * 1.1 s for a single textured full-screen draw in a 2D game.
2211
2087
  */
2212
2088
  _reportRenderer(gl: OurWebGLRenderingContext): void;
2213
2089
  /**
2214
- * Register a callback invoked whenever the GTK widget is resized.
2215
- * The callback fires alongside the native 'resize' GObject signal and
2216
- * after the DOM 'resize' event has been dispatched on the canvas.
2217
- * Canvas buffer dimensions are NOT automatically updated — consumers
2218
- * should set `canvas.width`/`canvas.height` themselves if desired.
2090
+ * Runs `cb` on every GTK resize, after the DOM 'resize' event has been dispatched
2091
+ * on the canvas. Canvas buffer dimensions are NOT updated for you: set
2092
+ * `canvas.width`/`canvas.height` yourself if the drawing buffer must follow.
2219
2093
  */
2220
2094
  onResize(cb: (width: number, height: number) => void): void;
2221
2095
  /**
2222
- * Schedules a single animation frame callback, matching the browser `requestAnimationFrame` API.
2223
- * Backed by a persistent GTK frame clock tick callback (vsync-synced) + a persistent GLArea
2224
- * render signal handler. Both are installed once at construction / first render respectively,
2225
- * eliminating per-frame GLib.Source allocation and GObject signal connect/disconnect overhead.
2096
+ * Browser `requestAnimationFrame`, driven by the vsync-synced GTK frame clock.
2226
2097
  *
2227
- * Returns a monotonically-increasing handle; pass it to `cancelAnimationFrame(id)` to cancel
2228
- * the specific pending callback. A second `requestAnimationFrame` call before the first has
2229
- * fired replaces the pending callback (single-slot model — matching the existing rAF
2230
- * convention of "queue 1 callback per frame, the latest wins"); the older handle becomes
2231
- * stale and a `cancelAnimationFrame` with that handle is a no-op.
2098
+ * SINGLE SLOT: a second call before the first has fired REPLACES the pending
2099
+ * callback (latest wins), which makes the older handle stale and a
2100
+ * `cancelAnimationFrame` with it a no-op.
2232
2101
  */
2233
2102
  requestAnimationFrame(cb: FrameRequestCallback): number;
2234
2103
  /**
2235
- * Cancels a previously-scheduled animation frame callback, matching the browser
2236
- * `cancelAnimationFrame` API. Only clears the pending callback when `id` matches
2237
- * the handle returned by the most recent `requestAnimationFrame` call — a stale
2238
- * handle (from a callback that already fired, or was replaced by a later
2239
- * `requestAnimationFrame`) is a no-op.
2104
+ * Browser `cancelAnimationFrame`. Clears the pending callback only when `id` is
2105
+ * the handle from the most recent `requestAnimationFrame`; a stale handle is a
2106
+ * no-op.
2240
2107
  *
2241
- * Per-ID matching is load-bearing: without it any consumer that calls
2242
- * `cancelAnimationFrame` (e.g. Excalibur's `Engine.stop()`, called from a
2243
- * transient parent `unmap` in a responsive layout) would clear the loop's
2244
- * latest pending callback and freeze rendering until the next manual
2245
- * `requestAnimationFrame`. With it, the cancel only fires for the handle
2246
- * the caller actually owns.
2108
+ * That matching is load-bearing: without it any stray cancel (Excalibur's
2109
+ * `Engine.stop()`, fired from a transient parent `unmap` in a responsive layout)
2110
+ * kills the loop's current callback and freezes rendering.
2247
2111
  */
2248
2112
  cancelAnimationFrame(id: number): void;
2249
2113
  /**
2250
- * Sets the browser globals this bridge owns — `requestAnimationFrame`,
2251
- * `cancelAnimationFrame`, `performance` and the two WebGL context
2252
- * constructors — so that browser-targeted code (e.g. Three.js) works
2253
- * unchanged on GJS.
2114
+ * Sets the browser globals this bridge owns, so browser-targeted code (Three.js)
2115
+ * runs unchanged on GJS.
2254
2116
  *
2255
- * `WebGLRenderingContext` / `WebGL2RenderingContext` are installed
2256
- * UNCONDITIONALLY here: per ADR 0012 rule 5 a bridge's `installGlobals()`
2257
- * and its `/register` install the same set, `installGlobals()` being the
2258
- * explicit imperative path (`@gjsify/webgl/register` keeps the
2259
- * if-undefined guard instead). They used to reach `globalThis` for free
2260
- * as a side effect of importing the `@gjsify/webgl` barrel; moving that
2261
- * write into `register.ts` must not shrink what this method does.
2117
+ * The two context constructors are installed UNCONDITIONALLY: per ADR 0012 rule 5
2118
+ * `installGlobals()` is the explicit imperative path and installs the same set as
2119
+ * `@gjsify/webgl/register`, which keeps the if-undefined guard instead.
2262
2120
  */
2263
2121
  installGlobals(): void;
2264
2122
  get allowed_apis(): Gdk.GLAPI;
@@ -2701,95 +2559,68 @@ export declare const WebGLBridge: {
2701
2559
  /** The HTMLCanvasElement wrapping this GLArea. Available after the first render. */
2702
2560
  get canvas(): globalThis.HTMLCanvasElement | null;
2703
2561
  /**
2704
- * The GL implementation GDK negotiated, unmasked — `{vendor, renderer}`
2705
- * straight from `glGetString`, e.g. `Apple Software Renderer`. `null`
2706
- * until the context exists (i.e. before the first `onReady`).
2562
+ * The GL implementation GDK negotiated, unmasked, straight from `glGetString`;
2563
+ * `null` until the context exists.
2707
2564
  *
2708
- * Read it to REPORT what a host provides. It is not a capability check:
2709
- * a CPU rasteriser draws everything a GPU does, only slower, and how much
2710
- * slower depends entirely on the workload. Anything that switches
2711
- * renderers should key on a measured frame budget, not on this string.
2565
+ * For REPORTING, not as a capability check: a CPU rasteriser draws everything a
2566
+ * GPU does, only slower, and by how much depends on the workload. Anything that
2567
+ * switches renderers should key on a measured frame budget, not on this string.
2712
2568
  */
2713
2569
  get rendererInfo(): {
2714
2570
  vendor: string;
2715
2571
  renderer: string;
2716
2572
  } | null;
2717
- /**
2718
- * Registers a callback to be called once the WebGL context is ready.
2719
- * If the context is already available, the callback fires synchronously.
2720
- */
2573
+ /** Runs `cb` once the WebGL context is ready — synchronously if it already is. */
2721
2574
  onReady(cb: WebGLReadyCallback): void;
2722
2575
  /**
2723
2576
  * @deprecated Use `onReady()` instead.
2724
2577
  */
2725
2578
  onWebGLReady(cb: WebGLReadyCallback): void;
2726
2579
  /**
2727
- * Records the negotiated GL implementation and, when it rasterises on the
2728
- * CPU, says so ONCE on stderr.
2580
+ * Records the negotiated GL implementation and, when it rasterises on the CPU,
2581
+ * says so ONCE on stderr.
2729
2582
  *
2730
- * A GPU-less host is invisible from inside the app: context creation
2731
- * succeeds, every draw call succeeds, nothing rejects, and the only tell
2732
- * is a frame budget blown by three orders of magnitude — which presents as
2733
- * a window that sits there. Naming the renderer at the one place that
2734
- * knows it turns that into a line anyone can act on, for every consumer,
2735
- * without any of them asking.
2583
+ * A GPU-less host is otherwise invisible from inside the app: the context is
2584
+ * created, every draw call succeeds, and the only tell is a frame budget blown by
2585
+ * three orders of magnitude, which presents as a window that just sits there.
2736
2586
  *
2737
- * Diagnostic only: it changes no behaviour, because a software renderer is
2738
- * fine for plenty of workloads (measured on one GPU-less macOS host: a
2739
- * demand-driven three.js scene and a flat-shaded full-screen quad both run
2740
- * comfortably on the same driver that needs 1.1 s for one textured
2741
- * full-screen draw in a 2D game).
2587
+ * Diagnostic only, and deliberately changes no behaviour: a software renderer is
2588
+ * fine for plenty of workloads. Measured on one GPU-less macOS host, a
2589
+ * demand-driven three.js scene ran comfortably on the same driver that needed
2590
+ * 1.1 s for a single textured full-screen draw in a 2D game.
2742
2591
  */
2743
2592
  _reportRenderer(gl: OurWebGLRenderingContext): void;
2744
2593
  /**
2745
- * Register a callback invoked whenever the GTK widget is resized.
2746
- * The callback fires alongside the native 'resize' GObject signal and
2747
- * after the DOM 'resize' event has been dispatched on the canvas.
2748
- * Canvas buffer dimensions are NOT automatically updated — consumers
2749
- * should set `canvas.width`/`canvas.height` themselves if desired.
2594
+ * Runs `cb` on every GTK resize, after the DOM 'resize' event has been dispatched
2595
+ * on the canvas. Canvas buffer dimensions are NOT updated for you: set
2596
+ * `canvas.width`/`canvas.height` yourself if the drawing buffer must follow.
2750
2597
  */
2751
2598
  onResize(cb: (width: number, height: number) => void): void;
2752
2599
  /**
2753
- * Schedules a single animation frame callback, matching the browser `requestAnimationFrame` API.
2754
- * Backed by a persistent GTK frame clock tick callback (vsync-synced) + a persistent GLArea
2755
- * render signal handler. Both are installed once at construction / first render respectively,
2756
- * eliminating per-frame GLib.Source allocation and GObject signal connect/disconnect overhead.
2600
+ * Browser `requestAnimationFrame`, driven by the vsync-synced GTK frame clock.
2757
2601
  *
2758
- * Returns a monotonically-increasing handle; pass it to `cancelAnimationFrame(id)` to cancel
2759
- * the specific pending callback. A second `requestAnimationFrame` call before the first has
2760
- * fired replaces the pending callback (single-slot model — matching the existing rAF
2761
- * convention of "queue 1 callback per frame, the latest wins"); the older handle becomes
2762
- * stale and a `cancelAnimationFrame` with that handle is a no-op.
2602
+ * SINGLE SLOT: a second call before the first has fired REPLACES the pending
2603
+ * callback (latest wins), which makes the older handle stale and a
2604
+ * `cancelAnimationFrame` with it a no-op.
2763
2605
  */
2764
2606
  requestAnimationFrame(cb: FrameRequestCallback): number;
2765
2607
  /**
2766
- * Cancels a previously-scheduled animation frame callback, matching the browser
2767
- * `cancelAnimationFrame` API. Only clears the pending callback when `id` matches
2768
- * the handle returned by the most recent `requestAnimationFrame` call — a stale
2769
- * handle (from a callback that already fired, or was replaced by a later
2770
- * `requestAnimationFrame`) is a no-op.
2608
+ * Browser `cancelAnimationFrame`. Clears the pending callback only when `id` is
2609
+ * the handle from the most recent `requestAnimationFrame`; a stale handle is a
2610
+ * no-op.
2771
2611
  *
2772
- * Per-ID matching is load-bearing: without it any consumer that calls
2773
- * `cancelAnimationFrame` (e.g. Excalibur's `Engine.stop()`, called from a
2774
- * transient parent `unmap` in a responsive layout) would clear the loop's
2775
- * latest pending callback and freeze rendering until the next manual
2776
- * `requestAnimationFrame`. With it, the cancel only fires for the handle
2777
- * the caller actually owns.
2612
+ * That matching is load-bearing: without it any stray cancel (Excalibur's
2613
+ * `Engine.stop()`, fired from a transient parent `unmap` in a responsive layout)
2614
+ * kills the loop's current callback and freezes rendering.
2778
2615
  */
2779
2616
  cancelAnimationFrame(id: number): void;
2780
2617
  /**
2781
- * Sets the browser globals this bridge owns — `requestAnimationFrame`,
2782
- * `cancelAnimationFrame`, `performance` and the two WebGL context
2783
- * constructors — so that browser-targeted code (e.g. Three.js) works
2784
- * unchanged on GJS.
2618
+ * Sets the browser globals this bridge owns, so browser-targeted code (Three.js)
2619
+ * runs unchanged on GJS.
2785
2620
  *
2786
- * `WebGLRenderingContext` / `WebGL2RenderingContext` are installed
2787
- * UNCONDITIONALLY here: per ADR 0012 rule 5 a bridge's `installGlobals()`
2788
- * and its `/register` install the same set, `installGlobals()` being the
2789
- * explicit imperative path (`@gjsify/webgl/register` keeps the
2790
- * if-undefined guard instead). They used to reach `globalThis` for free
2791
- * as a side effect of importing the `@gjsify/webgl` barrel; moving that
2792
- * write into `register.ts` must not shrink what this method does.
2621
+ * The two context constructors are installed UNCONDITIONALLY: per ADR 0012 rule 5
2622
+ * `installGlobals()` is the explicit imperative path and installs the same set as
2623
+ * `@gjsify/webgl/register`, which keeps the if-undefined guard instead.
2793
2624
  */
2794
2625
  installGlobals(): void;
2795
2626
  get allowed_apis(): Gdk.GLAPI;
@@ -43,7 +43,6 @@ export declare abstract class WebGLContextBase {
43
43
  get drawingBufferWidth(): number;
44
44
  DEFAULT_ATTACHMENTS: number[];
45
45
  DEFAULT_COLOR_ATTACHMENTS: number[];
46
- /** context counter */
47
46
  _: number;
48
47
  abstract get _gl(): Gwebgl.WebGLRenderingContextBase;
49
48
  _contextAttributes: WebGLContextAttributes;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/webgl",
3
- "version": "0.35.0",
3
+ "version": "0.37.0",
4
4
  "description": "WebGL module for Gjs",
5
5
  "module": "lib/esm/index.js",
6
6
  "types": "lib/types/index.d.ts",
@@ -29,7 +29,8 @@
29
29
  "linux-s390x",
30
30
  "linux-riscv64",
31
31
  "darwin-arm64",
32
- "darwin-x64"
32
+ "darwin-x64",
33
+ "win32-x64"
33
34
  ],
34
35
  "runtimes": {
35
36
  "gjs": "polyfill",
@@ -60,8 +61,8 @@
60
61
  "WebGL"
61
62
  ],
62
63
  "devDependencies": {
63
- "@gjsify/cli": "^0.35.0",
64
- "@gjsify/unit": "^0.35.0",
64
+ "@gjsify/cli": "^0.37.0",
65
+ "@gjsify/unit": "^0.37.0",
65
66
  "@types/bit-twiddle": "^1.0.3",
66
67
  "@types/node": "^25.9.2",
67
68
  "typescript": "^6.0.3"
@@ -71,10 +72,10 @@
71
72
  "@girs/gjs": "^4.1.0",
72
73
  "@girs/gtk-4.0": "^4.1.0",
73
74
  "@girs/gwebgl-0.1": "0.1.0-4.0.0-rc.5",
74
- "@gjsify/dom-elements": "^0.35.0",
75
- "@gjsify/dom-events": "^0.35.0",
76
- "@gjsify/event-bridge": "^0.35.0",
77
- "@gjsify/utils": "^0.35.0",
75
+ "@gjsify/dom-elements": "^0.37.0",
76
+ "@gjsify/dom-events": "^0.37.0",
77
+ "@gjsify/event-bridge": "^0.37.0",
78
+ "@gjsify/utils": "^0.37.0",
78
79
  "bit-twiddle": "^1.0.2",
79
80
  "glsl-tokenizer": "^2.1.5"
80
81
  },
@@ -89,12 +90,13 @@
89
90
  },
90
91
  "homepage": "https://github.com/gjsify/gjsify/tree/main/packages/framework/webgl#readme",
91
92
  "optionalDependencies": {
92
- "@gjsify/webgl-darwin-arm64": "0.35.0",
93
- "@gjsify/webgl-darwin-x64": "0.35.0",
94
- "@gjsify/webgl-linux-arm64": "0.35.0",
95
- "@gjsify/webgl-linux-ppc64": "0.35.0",
96
- "@gjsify/webgl-linux-riscv64": "0.35.0",
97
- "@gjsify/webgl-linux-s390x": "0.35.0",
98
- "@gjsify/webgl-linux-x64": "0.35.0"
93
+ "@gjsify/webgl-darwin-arm64": "0.37.0",
94
+ "@gjsify/webgl-darwin-x64": "0.37.0",
95
+ "@gjsify/webgl-linux-arm64": "0.37.0",
96
+ "@gjsify/webgl-linux-ppc64": "0.37.0",
97
+ "@gjsify/webgl-linux-riscv64": "0.37.0",
98
+ "@gjsify/webgl-linux-s390x": "0.37.0",
99
+ "@gjsify/webgl-linux-x64": "0.37.0",
100
+ "@gjsify/webgl-win32-x64": "0.37.0"
99
101
  }
100
102
  }