@flighthq/render-gl 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/glBackground.d.ts +3 -0
  2. package/dist/glBackground.d.ts.map +1 -0
  3. package/dist/glBackground.js +17 -0
  4. package/dist/glBackground.js.map +1 -0
  5. package/dist/glDraw.d.ts +11 -0
  6. package/dist/glDraw.d.ts.map +1 -0
  7. package/dist/glDraw.js +132 -0
  8. package/dist/glDraw.js.map +1 -0
  9. package/dist/glElement.d.ts +2 -0
  10. package/dist/glElement.d.ts.map +1 -0
  11. package/dist/glElement.js +9 -0
  12. package/dist/glElement.js.map +1 -0
  13. package/dist/glFullscreenPass.d.ts +11 -0
  14. package/dist/glFullscreenPass.d.ts.map +1 -0
  15. package/dist/glFullscreenPass.js +114 -0
  16. package/dist/glFullscreenPass.js.map +1 -0
  17. package/dist/glMaterialRegistry.d.ts +5 -0
  18. package/dist/glMaterialRegistry.d.ts.map +1 -0
  19. package/dist/glMaterialRegistry.js +25 -0
  20. package/dist/glMaterialRegistry.js.map +1 -0
  21. package/dist/glProgram.d.ts +4 -0
  22. package/dist/glProgram.d.ts.map +1 -0
  23. package/dist/glProgram.js +37 -0
  24. package/dist/glProgram.js.map +1 -0
  25. package/dist/glReadback.d.ts +3 -0
  26. package/dist/glReadback.d.ts.map +1 -0
  27. package/dist/glReadback.js +33 -0
  28. package/dist/glReadback.js.map +1 -0
  29. package/dist/glRenderState.d.ts +7 -0
  30. package/dist/glRenderState.d.ts.map +1 -0
  31. package/dist/glRenderState.js +153 -0
  32. package/dist/glRenderState.js.map +1 -0
  33. package/dist/glRenderTarget.d.ts +41 -0
  34. package/dist/glRenderTarget.d.ts.map +1 -0
  35. package/dist/glRenderTarget.js +284 -0
  36. package/dist/glRenderTarget.js.map +1 -0
  37. package/dist/glRenderTargetPool.d.ts +6 -0
  38. package/dist/glRenderTargetPool.d.ts.map +1 -0
  39. package/dist/glRenderTargetPool.js +33 -0
  40. package/dist/glRenderTargetPool.js.map +1 -0
  41. package/dist/glShader.d.ts +39 -0
  42. package/dist/glShader.d.ts.map +1 -0
  43. package/dist/glShader.js +116 -0
  44. package/dist/glShader.js.map +1 -0
  45. package/dist/glShaderBinding.d.ts +21 -0
  46. package/dist/glShaderBinding.d.ts.map +1 -0
  47. package/dist/glShaderBinding.js +60 -0
  48. package/dist/glShaderBinding.js.map +1 -0
  49. package/dist/glShaderRegistry.d.ts +5 -0
  50. package/dist/glShaderRegistry.d.ts.map +1 -0
  51. package/dist/glShaderRegistry.js +6 -0
  52. package/dist/glShaderRegistry.js.map +1 -0
  53. package/dist/glShaderTypes.d.ts +2 -0
  54. package/dist/glShaderTypes.d.ts.map +1 -0
  55. package/dist/glShaderTypes.js +2 -0
  56. package/dist/glShaderTypes.js.map +1 -0
  57. package/dist/glTestHelper.d.ts +14 -0
  58. package/dist/glTestHelper.d.ts.map +1 -0
  59. package/dist/glTestHelper.js +69 -0
  60. package/dist/glTestHelper.js.map +1 -0
  61. package/dist/index.d.ts +14 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +13 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/internal.d.ts +2 -0
  66. package/dist/internal.d.ts.map +1 -0
  67. package/dist/internal.js +2 -0
  68. package/dist/internal.js.map +1 -0
  69. package/package.json +40 -0
  70. package/src/glBackground.test.ts +43 -0
  71. package/src/glDraw.test.ts +360 -0
  72. package/src/glElement.test.ts +38 -0
  73. package/src/glFullscreenPass.test.ts +185 -0
  74. package/src/glMaterialRegistry.test.ts +48 -0
  75. package/src/glProgram.test.ts +59 -0
  76. package/src/glReadback.test.ts +82 -0
  77. package/src/glRenderState.test.ts +186 -0
  78. package/src/glRenderTarget.test.ts +317 -0
  79. package/src/glRenderTargetPool.test.ts +97 -0
  80. package/src/glShader.test.ts +331 -0
  81. package/src/glShaderBinding.test.ts +149 -0
  82. package/src/glShaderRegistry.test.ts +19 -0
@@ -0,0 +1,116 @@
1
+ import { createGlProgram } from './glProgram';
2
+ import { getGlRenderStateRuntime } from './glRenderState';
3
+ const VERTEX_SRC = `#version 300 es
4
+ in vec2 a_position;
5
+ in vec2 a_texCoord;
6
+ uniform mat3 u_matrix;
7
+ out vec2 v_texCoord;
8
+ void main() {
9
+ vec3 pos = u_matrix * vec3(a_position, 1.0);
10
+ gl_Position = vec4(pos.xy, 0.0, 1.0);
11
+ v_texCoord = a_texCoord;
12
+ }`;
13
+ const FRAGMENT_SRC = `#version 300 es
14
+ precision mediump float;
15
+ in vec2 v_texCoord;
16
+ uniform sampler2D u_texture;
17
+ uniform float u_alpha;
18
+ out vec4 fragColor;
19
+ void main() {
20
+ vec4 color = texture(u_texture, v_texCoord) * clamp(u_alpha, 0.0, 1.0);
21
+ if (color.a <= 0.0) discard;
22
+ fragColor = color;
23
+ }`;
24
+ export function compileDefaultGlProgram(gl) {
25
+ return compileGlBitmapProgram(gl);
26
+ }
27
+ /**
28
+ * Compiles a bitmap program, reusing the standard quad vertex shader. Pass a
29
+ * custom fragment source to build a custom shader; it must declare the interface
30
+ * the draw path drives — `in vec2 v_texCoord`, `uniform sampler2D u_texture`,
31
+ * `uniform float u_alpha` — and may add its own uniforms, resolved against the
32
+ * returned `program`.
33
+ */
34
+ export function compileGlBitmapProgram(gl, fragmentSrc = FRAGMENT_SRC) {
35
+ const program = createGlProgram(gl, VERTEX_SRC, fragmentSrc, 'Bitmap');
36
+ return {
37
+ program,
38
+ locPosition: gl.getAttribLocation(program, 'a_position'),
39
+ locTexCoord: gl.getAttribLocation(program, 'a_texCoord'),
40
+ locMatrix: gl.getUniformLocation(program, 'u_matrix'),
41
+ locAlpha: gl.getUniformLocation(program, 'u_alpha'),
42
+ locTexture: gl.getUniformLocation(program, 'u_texture'),
43
+ };
44
+ }
45
+ export function createDefaultGlBitmapShader(shaderLoc, matrixArray) {
46
+ return {
47
+ locations: shaderLoc,
48
+ program: shaderLoc.program,
49
+ bind(gl, state, renderProxy) {
50
+ const runtime = getGlRenderStateRuntime(state);
51
+ setGlAttributes(gl, shaderLoc);
52
+ setGlMatrixFromTransform(gl, shaderLoc, matrixArray, renderProxy.transform2D, runtime.renderTargetViewport ?? state.canvas);
53
+ setGlBaseUniforms(gl, shaderLoc, renderProxy);
54
+ },
55
+ };
56
+ }
57
+ /**
58
+ * Builds a custom bitmap shader from a fragment shader source, ready to pass to
59
+ * `setGlShader(state, node, shader)`. The standard quad vertex shader is
60
+ * reused, so the fragment shader must sample `u_texture` and respect `u_alpha`;
61
+ * any extra uniforms can be set per draw via `onBind` (resolve them against
62
+ * `locations.program`).
63
+ */
64
+ export function createGlBitmapShader(gl, fragmentSrc, onBind) {
65
+ const locations = compileGlBitmapProgram(gl, fragmentSrc);
66
+ return {
67
+ locations,
68
+ program: locations.program,
69
+ bind(gl, state, renderProxy) {
70
+ const runtime = getGlRenderStateRuntime(state);
71
+ setGlAttributes(gl, locations);
72
+ setGlMatrixFromTransform(gl, locations, runtime.matrixArray, renderProxy.transform2D, runtime.renderTargetViewport ?? state.canvas);
73
+ setGlBaseUniforms(gl, locations, renderProxy);
74
+ onBind?.(gl, locations, renderProxy);
75
+ },
76
+ };
77
+ }
78
+ export function setGlAttributes(gl, loc) {
79
+ gl.enableVertexAttribArray(loc.locPosition);
80
+ gl.enableVertexAttribArray(loc.locTexCoord);
81
+ gl.vertexAttribPointer(loc.locPosition, 2, gl.FLOAT, false, 16, 0);
82
+ gl.vertexAttribPointer(loc.locTexCoord, 2, gl.FLOAT, false, 16, 8);
83
+ }
84
+ export function setGlBaseUniforms(gl, loc, renderProxy) {
85
+ gl.uniform1f(loc.locAlpha, renderProxy.alpha);
86
+ gl.uniform1i(loc.locTexture, 0);
87
+ }
88
+ export function setGlMatrixFromTransform(gl, loc, m, t, viewport) {
89
+ const iw = 2 / viewport.width;
90
+ const ih = 2 / viewport.height;
91
+ m[0] = t.a * iw;
92
+ m[1] = -t.b * ih;
93
+ m[2] = 0;
94
+ m[3] = t.c * iw;
95
+ m[4] = -t.d * ih;
96
+ m[5] = 0;
97
+ m[6] = t.tx * iw - 1;
98
+ m[7] = -t.ty * ih + 1;
99
+ m[8] = 1;
100
+ gl.uniformMatrix3fv(loc.locMatrix, false, m);
101
+ }
102
+ export function setGlMatrixFromValues(gl, loc, m, a, b, c, d, tx, ty, viewport) {
103
+ const iw = 2 / viewport.width;
104
+ const ih = 2 / viewport.height;
105
+ m[0] = a * iw;
106
+ m[1] = -b * ih;
107
+ m[2] = 0;
108
+ m[3] = c * iw;
109
+ m[4] = -d * ih;
110
+ m[5] = 0;
111
+ m[6] = tx * iw - 1;
112
+ m[7] = -ty * ih + 1;
113
+ m[8] = 1;
114
+ gl.uniformMatrix3fv(loc.locMatrix, false, m);
115
+ }
116
+ //# sourceMappingURL=glShader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glShader.js","sourceRoot":"","sources":["../src/glShader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { DisplayObject, GlRenderState, Kind, RenderProxy2D } from '@flighthq/types';
2
+ import type { GlBitmapShader } from './glShaderTypes';
3
+ export declare function getGlMaterialShader(state: GlRenderState, kind: Kind): GlBitmapShader | null;
4
+ export declare function getGlShader(renderProxy: RenderProxy2D): GlBitmapShader | undefined;
5
+ export declare function registerGlMaterialShader(state: GlRenderState, kind: Kind, shader: GlBitmapShader): void;
6
+ /**
7
+ * Returns the shader to draw renderProxy with: the per-node binding when one is
8
+ * set, then the material-kind shader, otherwise the state's default bitmap shader.
9
+ * The per-node lookup is reached only through the installed resolver, so it (and
10
+ * the binding map) stay off the hot path and tree-shake until setGlShader is used.
11
+ */
12
+ export declare function resolveGlShader(state: GlRenderState, renderProxy: RenderProxy2D): GlBitmapShader;
13
+ /**
14
+ * Binds a custom Gl shader to a display object for the given render state, or
15
+ * clears it when shader is null. The binding lives on the render state side, not
16
+ * the scene graph, so the same display object can carry different shaders (or
17
+ * none) in different render states — e.g. a custom shader for an offscreen pass
18
+ * that the standard renderer has no memory of.
19
+ */
20
+ export declare function setGlShader(state: GlRenderState, node: DisplayObject, shader: GlBitmapShader | null): void;
21
+ //# sourceMappingURL=glShaderBinding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glShaderBinding.d.ts","sourceRoot":"","sources":["../src/glShaderBinding.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGzF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAStD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,IAAI,GAAG,cAAc,GAAG,IAAI,CAE3F;AAED,wBAAgB,WAAW,CAAC,WAAW,EAAE,aAAa,GAAG,cAAc,GAAG,SAAS,CAElF;AAKD,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,CAGvG;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,aAAa,GAAG,cAAc,CAahG;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,GAAG,IAAI,CAQ1G"}
@@ -0,0 +1,60 @@
1
+ import { getOrCreateRenderProxy2D } from '@flighthq/render';
2
+ import { getGlRenderStateRuntime } from './glRenderState';
3
+ // Per-state shader bindings, keyed by the render node. Because render nodes are
4
+ // per-state (state.renderProxyMap), a module-level map keyed by render node is
5
+ // automatically isolated per state — a binding made for one render state's pass
6
+ // is invisible to any other state that renders the same display object. This
7
+ // mirrors the per-render-node texture maps in webglText / webglRichText.
8
+ const _shaderBindings = new WeakMap();
9
+ export function getGlMaterialShader(state, kind) {
10
+ return getGlRenderStateRuntime(state).materialBitmapShaderMap?.get(kind) ?? null;
11
+ }
12
+ export function getGlShader(renderProxy) {
13
+ return _shaderBindings.get(renderProxy);
14
+ }
15
+ // Registers the bitmap shader to draw nodes whose material has the given kind. Keeps the render path
16
+ // generic: resolveGlShader looks shaders up by material kind and knows nothing about which kinds
17
+ // mean what. Material-specific knowledge (e.g. color transform) lives in the shader + this call.
18
+ export function registerGlMaterialShader(state, kind, shader) {
19
+ const runtime = getGlRenderStateRuntime(state);
20
+ (runtime.materialBitmapShaderMap ??= new Map()).set(kind, shader);
21
+ }
22
+ /**
23
+ * Returns the shader to draw renderProxy with: the per-node binding when one is
24
+ * set, then the material-kind shader, otherwise the state's default bitmap shader.
25
+ * The per-node lookup is reached only through the installed resolver, so it (and
26
+ * the binding map) stay off the hot path and tree-shake until setGlShader is used.
27
+ */
28
+ export function resolveGlShader(state, renderProxy) {
29
+ const runtime = getGlRenderStateRuntime(state);
30
+ const resolver = runtime.webglShaderBindingResolver;
31
+ if (resolver !== undefined) {
32
+ const shader = resolver(renderProxy);
33
+ if (shader !== undefined)
34
+ return shader;
35
+ }
36
+ const material = renderProxy.material;
37
+ if (material !== null) {
38
+ const shader = runtime.materialBitmapShaderMap?.get(material.kind);
39
+ if (shader !== undefined)
40
+ return shader;
41
+ }
42
+ return runtime.defaultBitmapShader;
43
+ }
44
+ /**
45
+ * Binds a custom Gl shader to a display object for the given render state, or
46
+ * clears it when shader is null. The binding lives on the render state side, not
47
+ * the scene graph, so the same display object can carry different shaders (or
48
+ * none) in different render states — e.g. a custom shader for an offscreen pass
49
+ * that the standard renderer has no memory of.
50
+ */
51
+ export function setGlShader(state, node, shader) {
52
+ const renderProxy = getOrCreateRenderProxy2D(state, node);
53
+ if (shader === null) {
54
+ _shaderBindings.delete(renderProxy);
55
+ return;
56
+ }
57
+ _shaderBindings.set(renderProxy, shader);
58
+ getGlRenderStateRuntime(state).webglShaderBindingResolver = getGlShader;
59
+ }
60
+ //# sourceMappingURL=glShaderBinding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glShaderBinding.js","sourceRoot":"","sources":["../src/glShaderBinding.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAG5D,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAG1D,gFAAgF;AAChF,+EAA+E;AAC/E,gFAAgF;AAChF,6EAA6E;AAC7E,yEAAyE;AACzE,MAAM,eAAe,GAAG,IAAI,OAAO,EAAiC,CAAC;AAErE,MAAM,UAAU,mBAAmB,CAAC,KAAoB,EAAE,IAAU;IAClE,OAAO,uBAAuB,CAAC,KAAK,CAAC,CAAC,uBAAuB,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;AACnF,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,WAA0B;IACpD,OAAO,eAAe,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;AAC1C,CAAC;AAED,qGAAqG;AACrG,iGAAiG;AACjG,iGAAiG;AACjG,MAAM,UAAU,wBAAwB,CAAC,KAAoB,EAAE,IAAU,EAAE,MAAsB;IAC/F,MAAM,OAAO,GAAG,uBAAuB,CAAC,KAAK,CAAC,CAAC;IAC/C,CAAC,OAAO,CAAC,uBAAuB,KAAK,IAAI,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AACpE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,KAAoB,EAAE,WAA0B;IAC9E,MAAM,OAAO,GAAG,uBAAuB,CAAC,KAAK,CAAC,CAAC;IAC/C,MAAM,QAAQ,GAAG,OAAO,CAAC,0BAA0B,CAAC;IACpD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;QACrC,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,MAAM,CAAC;IAC1C,CAAC;IACD,MAAM,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC;IACtC,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACtB,MAAM,MAAM,GAAG,OAAO,CAAC,uBAAuB,EAAE,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACnE,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,MAAM,CAAC;IAC1C,CAAC;IACD,OAAO,OAAO,CAAC,mBAAmB,CAAC;AACrC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,KAAoB,EAAE,IAAmB,EAAE,MAA6B;IAClG,MAAM,WAAW,GAAG,wBAAwB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC1D,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,eAAe,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QACpC,OAAO;IACT,CAAC;IACD,eAAe,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IACzC,uBAAuB,CAAC,KAAK,CAAC,CAAC,0BAA0B,GAAG,WAAW,CAAC;AAC1E,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { GlRenderState } from '@flighthq/types';
2
+ import type { GlBitmapShader } from './glShaderTypes';
3
+ export type { GlBitmapShader, GlShaderLocations } from './glShaderTypes';
4
+ export declare function registerGlBitmapShader(state: GlRenderState, shader: GlBitmapShader): void;
5
+ //# sourceMappingURL=glShaderRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glShaderRegistry.d.ts","sourceRoot":"","sources":["../src/glShaderRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzE,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,CAGzF"}
@@ -0,0 +1,6 @@
1
+ import { getGlRenderStateRuntime } from './glRenderState';
2
+ export function registerGlBitmapShader(state, shader) {
3
+ const runtime = getGlRenderStateRuntime(state);
4
+ runtime.defaultBitmapShader = shader;
5
+ }
6
+ //# sourceMappingURL=glShaderRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glShaderRegistry.js","sourceRoot":"","sources":["../src/glShaderRegistry.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAK1D,MAAM,UAAU,sBAAsB,CAAC,KAAoB,EAAE,MAAsB;IACjF,MAAM,OAAO,GAAG,uBAAuB,CAAC,KAAK,CAAC,CAAC;IAC/C,OAAO,CAAC,mBAAmB,GAAG,MAAM,CAAC;AACvC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export type { GlBitmapShader, GlShaderLocations } from '@flighthq/types';
2
+ //# sourceMappingURL=glShaderTypes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glShaderTypes.d.ts","sourceRoot":"","sources":["../src/glShaderTypes.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=glShaderTypes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glShaderTypes.js","sourceRoot":"","sources":["../src/glShaderTypes.ts"],"names":[],"mappings":""}
@@ -0,0 +1,14 @@
1
+ import type { GlRenderState } from '@flighthq/types';
2
+ import type { GlShaderLocations } from './glShaderTypes';
3
+ export declare function createGlState(options?: {
4
+ allowSmoothing?: boolean;
5
+ backgroundColorRgba?: number[];
6
+ }): {
7
+ state: GlRenderState;
8
+ gl: WebGL2RenderingContext;
9
+ canvas: HTMLCanvasElement;
10
+ shaderLoc: GlShaderLocations;
11
+ };
12
+ export declare function makeGL(): WebGL2RenderingContext;
13
+ export declare function makeShaderLoc(): GlShaderLocations;
14
+ //# sourceMappingURL=glTestHelper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glTestHelper.d.ts","sourceRoot":"","sources":["../src/glTestHelper.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAwB,MAAM,iBAAiB,CAAC;AAI3E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,wBAAgB,aAAa,CAAC,OAAO,CAAC,EAAE;IAAE,cAAc,CAAC,EAAE,OAAO,CAAC;IAAC,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG;IACrG,KAAK,EAAE,aAAa,CAAC;IACrB,EAAE,EAAE,sBAAsB,CAAC;IAC3B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,SAAS,EAAE,iBAAiB,CAAC;CAC9B,CA+CA;AAKD,wBAAgB,MAAM,IAAI,sBAAsB,CAG/C;AAED,wBAAgB,aAAa,IAAI,iBAAiB,CAYjD"}
@@ -0,0 +1,69 @@
1
+ import { createRenderState } from '@flighthq/render';
2
+ import { EntityRuntimeKey } from '@flighthq/types';
3
+ import { createGlRenderStateRuntime } from './glRenderState';
4
+ export function createGlState(options) {
5
+ const canvas = document.createElement('canvas');
6
+ canvas.width = 200;
7
+ canvas.height = 100;
8
+ const gl = makeGL();
9
+ const shaderLoc = makeShaderLoc();
10
+ const state = createRenderState({
11
+ allowSmoothing: options?.allowSmoothing ?? true,
12
+ backgroundColorRgba: options?.backgroundColorRgba ?? [0, 0, 0, 0],
13
+ });
14
+ // Entity fields live directly on the state.
15
+ Object.assign(state, {
16
+ canvas,
17
+ gl,
18
+ applyBlendMode: null,
19
+ });
20
+ // Runtime (package-private GPU) fields live on the runtime object stored under EntityRuntimeKey,
21
+ // mirroring what createGlRenderState does in production.
22
+ const runtime = createGlRenderStateRuntime();
23
+ Object.assign(runtime, {
24
+ currentBlendMode: null,
25
+ currentFramebuffer: null,
26
+ currentMaskDepth: 0,
27
+ currentProgram: null,
28
+ currentScissorRect: null,
29
+ currentTexture: null,
30
+ renderTargetViewport: null,
31
+ textureCache: new WeakMap(),
32
+ shaderLoc,
33
+ defaultBitmapShader: { locations: shaderLoc, program: shaderLoc.program, bind: vi.fn() },
34
+ quadVertexBuffer: {},
35
+ quadIndexBuffer: {},
36
+ quadVertexData: new Float32Array(16),
37
+ matrixArray: new Float32Array(9),
38
+ scissorStack: [],
39
+ clipForms: [],
40
+ spriteBatchBlendMode: null,
41
+ spriteBatchCount: 0,
42
+ spriteBatchInstanceBuffer: null,
43
+ spriteBatchInstanceData: new Float32Array(13 * 256),
44
+ spriteBatchTexture: null,
45
+ });
46
+ state[EntityRuntimeKey] = runtime;
47
+ return { state, gl, canvas, shaderLoc };
48
+ }
49
+ // makeGL returns a fresh isolated mock for unit tests that call GL functions
50
+ // directly (e.g. shader math tests) and need a clean call-count slate.
51
+ // Relies on the jsdom webgl2Mock setup file patching HTMLCanvasElement.getContext.
52
+ export function makeGL() {
53
+ const canvas = document.createElement('canvas');
54
+ return canvas.getContext('webgl2');
55
+ }
56
+ export function makeShaderLoc() {
57
+ return {
58
+ program: {},
59
+ locPosition: 0,
60
+ locTexCoord: 1,
61
+ locMatrix: {},
62
+ locAlpha: {},
63
+ locColorMultiplier: {},
64
+ locColorOffset: {},
65
+ locHasColorTransform: {},
66
+ locTexture: {},
67
+ };
68
+ }
69
+ //# sourceMappingURL=glTestHelper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glTestHelper.js","sourceRoot":"","sources":["../src/glTestHelper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEnD,OAAO,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAG7D,MAAM,UAAU,aAAa,CAAC,OAAsE;IAMlG,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IAChD,MAAM,CAAC,KAAK,GAAG,GAAG,CAAC;IACnB,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC;IACpB,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;IACpB,MAAM,SAAS,GAAG,aAAa,EAAE,CAAC;IAClC,MAAM,KAAK,GAAG,iBAAiB,CAAC;QAC9B,cAAc,EAAE,OAAO,EAAE,cAAc,IAAI,IAAI;QAC/C,mBAAmB,EAAE,OAAO,EAAE,mBAAmB,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;KAClE,CAAkB,CAAC;IAEpB,4CAA4C;IAC5C,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE;QACnB,MAAM;QACN,EAAE;QACF,cAAc,EAAE,IAAI;KACrB,CAAC,CAAC;IAEH,iGAAiG;IACjG,yDAAyD;IACzD,MAAM,OAAO,GAAG,0BAA0B,EAAE,CAAC;IAC7C,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE;QACrB,gBAAgB,EAAE,IAAI;QACtB,kBAAkB,EAAE,IAAI;QACxB,gBAAgB,EAAE,CAAC;QACnB,cAAc,EAAE,IAAI;QACpB,kBAAkB,EAAE,IAAI;QACxB,cAAc,EAAE,IAAI;QACpB,oBAAoB,EAAE,IAAI;QAC1B,YAAY,EAAE,IAAI,OAAO,EAAmC;QAC5D,SAAS;QACT,mBAAmB,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;QACxF,gBAAgB,EAAE,EAAiB;QACnC,eAAe,EAAE,EAAiB;QAClC,cAAc,EAAE,IAAI,YAAY,CAAC,EAAE,CAAC;QACpC,WAAW,EAAE,IAAI,YAAY,CAAC,CAAC,CAAC;QAChC,YAAY,EAAE,EAAE;QAChB,SAAS,EAAE,EAAE;QACb,oBAAoB,EAAE,IAAI;QAC1B,gBAAgB,EAAE,CAAC;QACnB,yBAAyB,EAAE,IAAI;QAC/B,uBAAuB,EAAE,IAAI,YAAY,CAAC,EAAE,GAAG,GAAG,CAAC;QACnD,kBAAkB,EAAE,IAAI;KACe,CAAC,CAAC;IAC3C,KAAK,CAAC,gBAAgB,CAAC,GAAG,OAAO,CAAC;IAElC,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AAC1C,CAAC;AAED,6EAA6E;AAC7E,uEAAuE;AACvE,mFAAmF;AACnF,MAAM,UAAU,MAAM;IACpB,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IAChD,OAAO,MAAM,CAAC,UAAU,CAAC,QAAQ,CAA2B,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO;QACL,OAAO,EAAE,EAAkB;QAC3B,WAAW,EAAE,CAAC;QACd,WAAW,EAAE,CAAC;QACd,SAAS,EAAE,EAA0B;QACrC,QAAQ,EAAE,EAA0B;QACpC,kBAAkB,EAAE,EAA0B;QAC9C,cAAc,EAAE,EAA0B;QAC1C,oBAAoB,EAAE,EAA0B;QAChD,UAAU,EAAE,EAA0B;KACvC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,14 @@
1
+ export * from './glBackground';
2
+ export * from './glDraw';
3
+ export * from './glElement';
4
+ export * from './glFullscreenPass';
5
+ export * from './glMaterialRegistry';
6
+ export * from './glProgram';
7
+ export * from './glReadback';
8
+ export * from './glRenderState';
9
+ export * from './glRenderTarget';
10
+ export * from './glRenderTargetPool';
11
+ export * from './glShader';
12
+ export * from './glShaderBinding';
13
+ export type { GlRenderTarget } from '@flighthq/types';
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ export * from './glBackground';
2
+ export * from './glDraw';
3
+ export * from './glElement';
4
+ export * from './glFullscreenPass';
5
+ export * from './glMaterialRegistry';
6
+ export * from './glProgram';
7
+ export * from './glReadback';
8
+ export * from './glRenderState';
9
+ export * from './glRenderTarget';
10
+ export * from './glRenderTargetPool';
11
+ export * from './glShader';
12
+ export * from './glShaderBinding';
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export type { GlBitmapShader, GlColorTransformInstancedShader, GlParticleShader, GlQuadBatchShader, GlScissorRect, GlShaderLocations, GlUniformColorTransformShader, } from '@flighthq/types';
2
+ //# sourceMappingURL=internal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,cAAc,EACd,+BAA+B,EAC/B,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EACb,iBAAiB,EACjB,6BAA6B,GAC9B,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=internal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal.js","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@flighthq/render-gl",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "src/**/*.test.ts",
16
+ "!dist/**/*.test.js",
17
+ "!dist/**/*.test.d.ts",
18
+ "!dist/**/*.test.js.map",
19
+ "!dist/**/*.test.d.ts.map"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsc -b",
23
+ "clean": "tsc -b --clean",
24
+ "test": "vitest run --config vitest.config.ts",
25
+ "test:watch": "vitest --watch --config vitest.config.ts",
26
+ "prepack": "npm run clean && npm run clean:dist && npm run build",
27
+ "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
28
+ },
29
+ "dependencies": {
30
+ "@flighthq/displayobject": "0.1.0",
31
+ "@flighthq/geometry": "0.1.0",
32
+ "@flighthq/render": "0.1.0",
33
+ "@flighthq/types": "0.1.0"
34
+ },
35
+ "devDependencies": {
36
+ "typescript": "^5.3.0"
37
+ },
38
+ "description": "WebGL2 renderer core: render state, targets, shaders, draw, and fullscreen passes",
39
+ "sideEffects": false
40
+ }
@@ -0,0 +1,43 @@
1
+ import { renderGlBackground } from './glBackground';
2
+ import { getGlRenderStateRuntime } from './glRenderState';
3
+ import { createGlState } from './glTestHelper';
4
+
5
+ describe('renderGlBackground', () => {
6
+ it('calls clearColor with zeros when background alpha is 0', () => {
7
+ const { state, gl } = createGlState({ backgroundColorRgba: [0, 0, 0, 0] });
8
+ renderGlBackground(state);
9
+ expect(gl.clearColor).toHaveBeenCalledWith(0, 0, 0, 0);
10
+ });
11
+
12
+ it('calls clearColor with the RGBA values when background is non-transparent', () => {
13
+ const { state, gl } = createGlState({ backgroundColorRgba: [1, 0, 0, 1] });
14
+ renderGlBackground(state);
15
+ expect(gl.clearColor).toHaveBeenCalledWith(1, 0, 0, 1);
16
+ });
17
+
18
+ it('calls clearColor with zeros when RGBA array has fewer than 4 elements', () => {
19
+ const { state, gl } = createGlState({ backgroundColorRgba: [] });
20
+ renderGlBackground(state);
21
+ expect(gl.clearColor).toHaveBeenCalledWith(0, 0, 0, 0);
22
+ });
23
+
24
+ it('always calls gl.clear with COLOR_BUFFER_BIT', () => {
25
+ const { state, gl } = createGlState();
26
+ renderGlBackground(state);
27
+ expect(gl.clear).toHaveBeenCalledWith((gl as unknown as { COLOR_BUFFER_BIT: number }).COLOR_BUFFER_BIT);
28
+ });
29
+
30
+ it('resets currentBlendMode to null', () => {
31
+ const { state } = createGlState();
32
+ const runtime = getGlRenderStateRuntime(state);
33
+ runtime.currentBlendMode = 0;
34
+ renderGlBackground(state);
35
+ expect(runtime.currentBlendMode).toBeNull();
36
+ });
37
+
38
+ it('passes fractional RGBA values through unchanged', () => {
39
+ const { state, gl } = createGlState({ backgroundColorRgba: [0.5, 0.25, 0.75, 0.8] });
40
+ renderGlBackground(state);
41
+ expect(gl.clearColor).toHaveBeenCalledWith(0.5, 0.25, 0.75, 0.8);
42
+ });
43
+ });