@bloopjs/toodle 0.0.103 → 0.1.1

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 (128) hide show
  1. package/dist/Toodle.d.ts +50 -20
  2. package/dist/Toodle.d.ts.map +1 -1
  3. package/dist/backends/IBackendShader.d.ts +48 -0
  4. package/dist/backends/IBackendShader.d.ts.map +1 -0
  5. package/dist/backends/IRenderBackend.d.ts +92 -0
  6. package/dist/backends/IRenderBackend.d.ts.map +1 -0
  7. package/dist/backends/ITextureAtlas.d.ts +34 -0
  8. package/dist/backends/ITextureAtlas.d.ts.map +1 -0
  9. package/dist/backends/detection.d.ts +16 -0
  10. package/dist/backends/detection.d.ts.map +1 -0
  11. package/dist/backends/mod.d.ts +9 -0
  12. package/dist/backends/mod.d.ts.map +1 -0
  13. package/dist/backends/webgl2/WebGLBackend.d.ts +51 -0
  14. package/dist/backends/webgl2/WebGLBackend.d.ts.map +1 -0
  15. package/dist/backends/webgl2/WebGLQuadShader.d.ts +17 -0
  16. package/dist/backends/webgl2/WebGLQuadShader.d.ts.map +1 -0
  17. package/dist/backends/webgl2/glsl/quad.glsl.d.ts +12 -0
  18. package/dist/backends/webgl2/glsl/quad.glsl.d.ts.map +1 -0
  19. package/dist/backends/webgl2/mod.d.ts +3 -0
  20. package/dist/backends/webgl2/mod.d.ts.map +1 -0
  21. package/dist/backends/webgpu/ShaderDescriptor.d.ts.map +1 -0
  22. package/dist/{textures → backends/webgpu}/TextureComputeShader.d.ts +1 -1
  23. package/dist/backends/webgpu/TextureComputeShader.d.ts.map +1 -0
  24. package/dist/backends/webgpu/WebGPUBackend.d.ts +67 -0
  25. package/dist/backends/webgpu/WebGPUBackend.d.ts.map +1 -0
  26. package/dist/backends/webgpu/WebGPUQuadShader.d.ts +18 -0
  27. package/dist/backends/webgpu/WebGPUQuadShader.d.ts.map +1 -0
  28. package/dist/backends/webgpu/mod.d.ts +3 -0
  29. package/dist/backends/webgpu/mod.d.ts.map +1 -0
  30. package/dist/backends/webgpu/parser.d.ts.map +1 -0
  31. package/dist/{shaders → backends/webgpu}/postprocess/blur.d.ts +1 -1
  32. package/dist/backends/webgpu/postprocess/blur.d.ts.map +1 -0
  33. package/dist/{shaders → backends/webgpu}/postprocess/mod.d.ts +1 -1
  34. package/dist/backends/webgpu/postprocess/mod.d.ts.map +1 -0
  35. package/dist/backends/webgpu/samplers.d.ts.map +1 -0
  36. package/dist/backends/webgpu/wgsl/example.wgsl.d.ts.map +1 -0
  37. package/dist/backends/webgpu/wgsl/hello.wgsl.d.ts.map +1 -0
  38. package/dist/backends/webgpu/wgsl/helloInstanced.wgsl.d.ts.map +1 -0
  39. package/dist/backends/webgpu/wgsl/pixel-scraping.wgsl.d.ts.map +1 -0
  40. package/dist/backends/webgpu/wgsl/quad.wgsl.d.ts.map +1 -0
  41. package/dist/coreTypes/EngineUniform.d.ts.map +1 -0
  42. package/dist/mod.d.ts +3 -2
  43. package/dist/mod.d.ts.map +1 -1
  44. package/dist/mod.js +6741 -6151
  45. package/dist/mod.js.map +28 -23
  46. package/dist/scene/Batcher.d.ts +2 -2
  47. package/dist/scene/Batcher.d.ts.map +1 -1
  48. package/dist/scene/QuadNode.d.ts +3 -2
  49. package/dist/scene/QuadNode.d.ts.map +1 -1
  50. package/dist/scene/RenderComponent.d.ts +2 -2
  51. package/dist/scene/RenderComponent.d.ts.map +1 -1
  52. package/dist/scene/SceneNode.d.ts +2 -2
  53. package/dist/scene/SceneNode.d.ts.map +1 -1
  54. package/dist/text/TextShader.d.ts +8 -6
  55. package/dist/text/TextShader.d.ts.map +1 -1
  56. package/dist/textures/AssetManager.d.ts +21 -5
  57. package/dist/textures/AssetManager.d.ts.map +1 -1
  58. package/dist/textures/types.d.ts +4 -2
  59. package/dist/textures/types.d.ts.map +1 -1
  60. package/dist/textures/util.d.ts.map +1 -1
  61. package/dist/utils/boilerplate.d.ts +1 -1
  62. package/dist/utils/boilerplate.d.ts.map +1 -1
  63. package/package.json +1 -1
  64. package/src/Toodle.ts +155 -171
  65. package/src/backends/IBackendShader.ts +52 -0
  66. package/src/backends/IRenderBackend.ts +118 -0
  67. package/src/backends/ITextureAtlas.ts +35 -0
  68. package/src/backends/detection.ts +46 -0
  69. package/src/backends/mod.ts +29 -0
  70. package/src/backends/webgl2/WebGLBackend.ts +256 -0
  71. package/src/backends/webgl2/WebGLQuadShader.ts +278 -0
  72. package/src/backends/webgl2/glsl/quad.glsl.ts +114 -0
  73. package/src/backends/webgl2/mod.ts +2 -0
  74. package/src/{textures → backends/webgpu}/TextureComputeShader.ts +2 -48
  75. package/src/backends/webgpu/WebGPUBackend.ts +350 -0
  76. package/src/{shaders/QuadShader.ts → backends/webgpu/WebGPUQuadShader.ts} +226 -170
  77. package/src/backends/webgpu/mod.ts +2 -0
  78. package/src/{shaders → backends/webgpu}/parser.ts +2 -2
  79. package/src/{shaders → backends/webgpu}/postprocess/blur.ts +2 -2
  80. package/src/{shaders → backends/webgpu}/postprocess/mod.ts +1 -1
  81. package/src/mod.ts +3 -2
  82. package/src/scene/Batcher.ts +3 -3
  83. package/src/scene/QuadNode.ts +7 -6
  84. package/src/scene/RenderComponent.ts +2 -2
  85. package/src/scene/SceneNode.ts +11 -12
  86. package/src/text/TextNode.ts +2 -2
  87. package/src/text/TextShader.ts +17 -11
  88. package/src/textures/AssetManager.ts +119 -94
  89. package/src/textures/types.ts +4 -2
  90. package/src/textures/util.ts +0 -65
  91. package/src/utils/boilerplate.ts +1 -1
  92. package/dist/shaders/EngineUniform.d.ts.map +0 -1
  93. package/dist/shaders/IShader.d.ts +0 -15
  94. package/dist/shaders/IShader.d.ts.map +0 -1
  95. package/dist/shaders/QuadShader.d.ts +0 -18
  96. package/dist/shaders/QuadShader.d.ts.map +0 -1
  97. package/dist/shaders/ShaderDescriptor.d.ts.map +0 -1
  98. package/dist/shaders/mod.d.ts +0 -6
  99. package/dist/shaders/mod.d.ts.map +0 -1
  100. package/dist/shaders/parser.d.ts.map +0 -1
  101. package/dist/shaders/postprocess/blur.d.ts.map +0 -1
  102. package/dist/shaders/postprocess/mod.d.ts.map +0 -1
  103. package/dist/shaders/samplers.d.ts.map +0 -1
  104. package/dist/shaders/wgsl/example.wgsl.d.ts.map +0 -1
  105. package/dist/shaders/wgsl/hello.wgsl.d.ts.map +0 -1
  106. package/dist/shaders/wgsl/helloInstanced.wgsl.d.ts.map +0 -1
  107. package/dist/shaders/wgsl/quad.wgsl.d.ts.map +0 -1
  108. package/dist/textures/TextureComputeShader.d.ts.map +0 -1
  109. package/dist/textures/pixel-scraping.wgsl.d.ts.map +0 -1
  110. package/src/shaders/IShader.ts +0 -20
  111. package/src/shaders/mod.ts +0 -5
  112. /package/dist/{shaders → backends/webgpu}/ShaderDescriptor.d.ts +0 -0
  113. /package/dist/{shaders → backends/webgpu}/parser.d.ts +0 -0
  114. /package/dist/{shaders → backends/webgpu}/samplers.d.ts +0 -0
  115. /package/dist/{shaders → backends/webgpu}/wgsl/example.wgsl.d.ts +0 -0
  116. /package/dist/{shaders → backends/webgpu}/wgsl/hello.wgsl.d.ts +0 -0
  117. /package/dist/{shaders → backends/webgpu}/wgsl/helloInstanced.wgsl.d.ts +0 -0
  118. /package/dist/{textures → backends/webgpu/wgsl}/pixel-scraping.wgsl.d.ts +0 -0
  119. /package/dist/{shaders → backends/webgpu}/wgsl/quad.wgsl.d.ts +0 -0
  120. /package/dist/{shaders → coreTypes}/EngineUniform.d.ts +0 -0
  121. /package/src/{shaders → backends/webgpu}/ShaderDescriptor.ts +0 -0
  122. /package/src/{shaders → backends/webgpu}/samplers.ts +0 -0
  123. /package/src/{shaders → backends/webgpu}/wgsl/example.wgsl.ts +0 -0
  124. /package/src/{shaders → backends/webgpu}/wgsl/hello.wgsl.ts +0 -0
  125. /package/src/{shaders → backends/webgpu}/wgsl/helloInstanced.wgsl.ts +0 -0
  126. /package/src/{textures → backends/webgpu/wgsl}/pixel-scraping.wgsl.ts +0 -0
  127. /package/src/{shaders → backends/webgpu}/wgsl/quad.wgsl.ts +0 -0
  128. /package/src/{shaders → coreTypes}/EngineUniform.ts +0 -0
@@ -0,0 +1,278 @@
1
+ import type { EngineUniform } from "../../coreTypes/EngineUniform";
2
+ import type { SceneNode } from "../../scene/SceneNode";
3
+ import { assert } from "../../utils/assert";
4
+ import type { IBackendShader } from "../IBackendShader";
5
+ import type { ITextureAtlas } from "../ITextureAtlas";
6
+ import { fragmentShader, vertexShader } from "./glsl/quad.glsl";
7
+ import type { WebGLBackend } from "./WebGLBackend";
8
+
9
+ // Instance data size in floats (must match WGSL shader)
10
+ // model (12) + tint (4) + uvOffsetAndScale (4) + cropOffsetAndScale (4) + atlasIndex (1) + padding (3) = 28
11
+ const INSTANCE_FLOATS = 28;
12
+ const INSTANCE_BYTES = INSTANCE_FLOATS * Float32Array.BYTES_PER_ELEMENT;
13
+
14
+ /**
15
+ * WebGL 2 implementation of quad shader for instanced rendering.
16
+ */
17
+ export class WebGLQuadShader implements IBackendShader {
18
+ readonly label: string;
19
+
20
+ #backend: WebGLBackend;
21
+ #atlas: ITextureAtlas;
22
+ #program: WebGLProgram;
23
+ #vao: WebGLVertexArrayObject;
24
+ #instanceBuffer: WebGLBuffer;
25
+ #cpuBuffer: Float32Array;
26
+ #instanceCount: number;
27
+ #instanceIndex = 0;
28
+
29
+ // Uniform locations
30
+ #uViewProjection: WebGLUniformLocation | null = null;
31
+ #uResolution: WebGLUniformLocation | null = null;
32
+ #uTextureArray: WebGLUniformLocation | null = null;
33
+
34
+ constructor(
35
+ label: string,
36
+ backend: WebGLBackend,
37
+ instanceCount: number,
38
+ userFragmentShader?: string,
39
+ atlasId?: string,
40
+ ) {
41
+ const atlas = backend.getTextureAtlas(atlasId ?? "default");
42
+ if (!atlas) {
43
+ throw new Error(`Atlas "${atlasId ?? "default"}" not found`);
44
+ }
45
+
46
+ this.#atlas = atlas;
47
+ this.label = label;
48
+ this.#backend = backend;
49
+ this.#instanceCount = instanceCount;
50
+
51
+ const gl = backend.gl;
52
+
53
+ // Compile shaders
54
+ const vs = this.#compileShader(gl, gl.VERTEX_SHADER, vertexShader);
55
+ const fs = this.#compileShader(
56
+ gl,
57
+ gl.FRAGMENT_SHADER,
58
+ userFragmentShader ?? fragmentShader,
59
+ );
60
+
61
+ // Create program
62
+ const program = gl.createProgram();
63
+ assert(program, "Failed to create WebGL program");
64
+ gl.attachShader(program, vs);
65
+ gl.attachShader(program, fs);
66
+ gl.linkProgram(program);
67
+
68
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
69
+ const info = gl.getProgramInfoLog(program);
70
+ throw new Error(`Failed to link shader program: ${info}`);
71
+ }
72
+
73
+ this.#program = program;
74
+
75
+ // Get uniform locations
76
+ this.#uViewProjection = gl.getUniformLocation(program, "u_viewProjection");
77
+ this.#uResolution = gl.getUniformLocation(program, "u_resolution");
78
+ this.#uTextureArray = gl.getUniformLocation(program, "u_textureArray");
79
+
80
+ // Create VAO
81
+ const vao = gl.createVertexArray();
82
+ assert(vao, "Failed to create WebGL VAO");
83
+ this.#vao = vao;
84
+
85
+ // Create instance buffer
86
+ const instanceBuffer = gl.createBuffer();
87
+ assert(instanceBuffer, "Failed to create WebGL instance buffer");
88
+ this.#instanceBuffer = instanceBuffer;
89
+
90
+ // Allocate CPU buffer
91
+ this.#cpuBuffer = new Float32Array(instanceCount * INSTANCE_FLOATS);
92
+
93
+ // Set up VAO
94
+ gl.bindVertexArray(vao);
95
+ gl.bindBuffer(gl.ARRAY_BUFFER, instanceBuffer);
96
+ gl.bufferData(
97
+ gl.ARRAY_BUFFER,
98
+ instanceCount * INSTANCE_BYTES,
99
+ gl.DYNAMIC_DRAW,
100
+ );
101
+
102
+ // Set up instance attributes
103
+ // Each vec4 attribute takes up 16 bytes
104
+ // model0 at location 0
105
+ gl.enableVertexAttribArray(0);
106
+ gl.vertexAttribPointer(0, 4, gl.FLOAT, false, INSTANCE_BYTES, 0);
107
+ gl.vertexAttribDivisor(0, 1);
108
+
109
+ // model1 at location 1
110
+ gl.enableVertexAttribArray(1);
111
+ gl.vertexAttribPointer(1, 4, gl.FLOAT, false, INSTANCE_BYTES, 16);
112
+ gl.vertexAttribDivisor(1, 1);
113
+
114
+ // model2 at location 2
115
+ gl.enableVertexAttribArray(2);
116
+ gl.vertexAttribPointer(2, 4, gl.FLOAT, false, INSTANCE_BYTES, 32);
117
+ gl.vertexAttribDivisor(2, 1);
118
+
119
+ // tint at location 3
120
+ gl.enableVertexAttribArray(3);
121
+ gl.vertexAttribPointer(3, 4, gl.FLOAT, false, INSTANCE_BYTES, 48);
122
+ gl.vertexAttribDivisor(3, 1);
123
+
124
+ // uvOffsetAndScale at location 4
125
+ gl.enableVertexAttribArray(4);
126
+ gl.vertexAttribPointer(4, 4, gl.FLOAT, false, INSTANCE_BYTES, 64);
127
+ gl.vertexAttribDivisor(4, 1);
128
+
129
+ // cropOffsetAndScale at location 5
130
+ gl.enableVertexAttribArray(5);
131
+ gl.vertexAttribPointer(5, 4, gl.FLOAT, false, INSTANCE_BYTES, 80);
132
+ gl.vertexAttribDivisor(5, 1);
133
+
134
+ // atlasIndex at location 6 (integer attribute - use vertexAttribIPointer)
135
+ gl.enableVertexAttribArray(6);
136
+ gl.vertexAttribIPointer(6, 1, gl.UNSIGNED_INT, INSTANCE_BYTES, 96);
137
+ gl.vertexAttribDivisor(6, 1);
138
+
139
+ gl.bindVertexArray(null);
140
+ gl.bindBuffer(gl.ARRAY_BUFFER, null);
141
+
142
+ // Cleanup shaders (they're linked to the program now)
143
+ gl.deleteShader(vs);
144
+ gl.deleteShader(fs);
145
+ }
146
+
147
+ #cachedUniform: EngineUniform | null = null;
148
+
149
+ startFrame(uniform: EngineUniform): void {
150
+ this.#instanceIndex = 0;
151
+ // Cache uniform for use in processBatch - WebGL state is global,
152
+ // so we need to set program/uniforms/textures right before drawing
153
+ this.#cachedUniform = uniform;
154
+ }
155
+
156
+ #bindState(): void {
157
+ const gl = this.#backend.gl;
158
+ const uniform = this.#cachedUniform;
159
+ if (!uniform)
160
+ throw new Error("Tried to bind state but engine uniform is not set");
161
+
162
+ gl.useProgram(this.#program);
163
+
164
+ // Set uniforms
165
+ if (this.#uViewProjection) {
166
+ // wgpu-matrix mat3 is stored as 12 floats (3 columns × 4 floats with padding)
167
+ // WebGL uniformMatrix3fv expects 9 floats, so extract the relevant values
168
+ const m = uniform.viewProjectionMatrix;
169
+ const mat3x3 = new Float32Array([
170
+ m[0],
171
+ m[1],
172
+ m[2], // column 0
173
+ m[4],
174
+ m[5],
175
+ m[6], // column 1
176
+ m[8],
177
+ m[9],
178
+ m[10], // column 2
179
+ ]);
180
+ gl.uniformMatrix3fv(this.#uViewProjection, false, mat3x3);
181
+ }
182
+
183
+ if (this.#uResolution) {
184
+ gl.uniform2f(
185
+ this.#uResolution,
186
+ uniform.resolution.width,
187
+ uniform.resolution.height,
188
+ );
189
+ }
190
+
191
+ // Bind texture array to texture unit 0
192
+ gl.activeTexture(gl.TEXTURE0);
193
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.#atlas.handle as WebGLTexture);
194
+ if (this.#uTextureArray) {
195
+ gl.uniform1i(this.#uTextureArray, 0);
196
+ }
197
+ }
198
+
199
+ processBatch(nodes: SceneNode[]): number {
200
+ const gl = this.#backend.gl;
201
+
202
+ // Bind program, uniforms, and textures right before drawing
203
+ // (WebGL state is global, so this must happen per-batch)
204
+ this.#bindState();
205
+
206
+ if (nodes.length > this.#instanceCount) {
207
+ throw new Error(
208
+ `ToodleInstanceCap: ${nodes.length} instances enqueued, max is ${this.#instanceCount} for ${this.label} shader`,
209
+ );
210
+ }
211
+
212
+ let instanceCount = 0;
213
+
214
+ // WebGL2 doesn't support firstInstance in drawArraysInstanced,
215
+ // so we always write starting at offset 0 for each batch
216
+ for (let i = 0; i < nodes.length; i++) {
217
+ const instance = nodes[i];
218
+ assert(instance.renderComponent, "instance has no render component");
219
+ const floatOffset = instanceCount * INSTANCE_FLOATS;
220
+
221
+ instanceCount += instance.renderComponent.writeInstance(
222
+ instance,
223
+ this.#cpuBuffer,
224
+ floatOffset,
225
+ );
226
+ }
227
+
228
+ // Upload instance data to GPU starting at offset 0
229
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.#instanceBuffer);
230
+
231
+ gl.bufferSubData(
232
+ gl.ARRAY_BUFFER,
233
+ 0,
234
+ this.#cpuBuffer,
235
+ 0,
236
+ instanceCount * INSTANCE_FLOATS,
237
+ );
238
+
239
+ // Draw instances from offset 0
240
+ gl.bindVertexArray(this.#vao);
241
+ gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, instanceCount);
242
+ gl.bindVertexArray(null);
243
+
244
+ return 1;
245
+ }
246
+
247
+ endFrame(): void {
248
+ // Nothing to do
249
+ }
250
+
251
+ #compileShader(
252
+ gl: WebGL2RenderingContext,
253
+ type: number,
254
+ source: string,
255
+ ): WebGLShader {
256
+ const shader = gl.createShader(type);
257
+ assert(shader, "Failed to create WebGL shader");
258
+
259
+ gl.shaderSource(shader, source);
260
+ gl.compileShader(shader);
261
+
262
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
263
+ const info = gl.getShaderInfoLog(shader);
264
+ const typeStr = type === gl.VERTEX_SHADER ? "vertex" : "fragment";
265
+ gl.deleteShader(shader);
266
+ throw new Error(`Failed to compile ${typeStr} shader: ${info}`);
267
+ }
268
+
269
+ return shader;
270
+ }
271
+
272
+ destroy(): void {
273
+ const gl = this.#backend.gl;
274
+ gl.deleteProgram(this.#program);
275
+ gl.deleteVertexArray(this.#vao);
276
+ gl.deleteBuffer(this.#instanceBuffer);
277
+ }
278
+ }
@@ -0,0 +1,114 @@
1
+ export const vertexShader = /*glsl*/ `#version 300 es
2
+ precision highp float;
3
+
4
+ // Engine uniforms
5
+ uniform mat3 u_viewProjection;
6
+ uniform vec2 u_resolution;
7
+
8
+ // Instance data attributes
9
+ // location 0-2 are the model matrix for this instanced quad (mat3 as 3 vec3s)
10
+ layout(location = 0) in vec4 a_model0;
11
+ layout(location = 1) in vec4 a_model1;
12
+ layout(location = 2) in vec4 a_model2;
13
+ // location 3 is the tint color
14
+ layout(location = 3) in vec4 a_tint;
15
+ // location 4 is the uv offset and scale
16
+ layout(location = 4) in vec4 a_uvOffsetAndScale;
17
+ // location 5 is the crop offset and scale
18
+ layout(location = 5) in vec4 a_cropOffsetAndScale;
19
+ // location 6 is the atlas index (integer attribute)
20
+ layout(location = 6) in uint a_atlasIndex;
21
+
22
+ // Outputs to fragment shader
23
+ out vec4 v_uv; // xy = atlas uv, zw = original uv
24
+ out vec4 v_tint;
25
+ flat out int v_atlasIndex;
26
+
27
+ // Lookup tables for unit quad positions and UVs
28
+ const vec2 posLookup[4] = vec2[4](
29
+ vec2(-0.5, 0.5),
30
+ vec2(-0.5, -0.5),
31
+ vec2(0.5, 0.5),
32
+ vec2(0.5, -0.5)
33
+ );
34
+
35
+ const vec2 uvLookup[4] = vec2[4](
36
+ vec2(0.0, 0.0),
37
+ vec2(0.0, 1.0),
38
+ vec2(1.0, 0.0),
39
+ vec2(1.0, 1.0)
40
+ );
41
+
42
+ void main() {
43
+ // Reconstruct model matrix from instance data
44
+ mat3 modelMatrix = mat3(a_model0.xyz, a_model1.xyz, a_model2.xyz);
45
+
46
+ // Transform vertex position
47
+ vec2 localPosition = posLookup[gl_VertexID];
48
+ vec2 cropOffset = a_cropOffsetAndScale.xy;
49
+ vec2 cropScale = a_cropOffsetAndScale.zw;
50
+ vec2 croppedPosition = localPosition * cropScale + cropOffset;
51
+ vec3 worldPosition = modelMatrix * vec3(croppedPosition, 1.0);
52
+ vec3 clipPosition = u_viewProjection * worldPosition;
53
+ gl_Position = vec4(clipPosition.xy, 0.0, 1.0);
54
+
55
+ // Set UV coordinates
56
+ vec2 originalUv = uvLookup[gl_VertexID];
57
+ vec2 atlasUv = originalUv * a_uvOffsetAndScale.zw * cropScale + a_uvOffsetAndScale.xy;
58
+ v_uv = vec4(atlasUv, originalUv);
59
+
60
+ // Pass through tint and atlas index
61
+ v_tint = a_tint;
62
+ v_atlasIndex = int(a_atlasIndex);
63
+ }
64
+
65
+ `;
66
+
67
+ /**
68
+ * Default fragment shader for WebGL2 quad rendering.
69
+ * Custom fragment shaders must follow the same contract:
70
+ * - Required uniforms: u_resolution, u_textureArray
71
+ * - Required inputs: v_uv (vec4), v_tint (vec4), v_atlasIndex (flat int)
72
+ * - Required output: fragColor (vec4)
73
+ */
74
+ export const fragmentShader = /*glsl*/ `#version 300 es
75
+ precision highp float;
76
+ precision highp sampler2DArray;
77
+
78
+ // Engine uniforms
79
+ uniform vec2 u_resolution;
80
+
81
+ // Texture array sampler
82
+ uniform sampler2DArray u_textureArray;
83
+
84
+ // Inputs from vertex shader
85
+ in vec4 v_uv; // xy = atlas uv, zw = original uv
86
+ in vec4 v_tint;
87
+ flat in int v_atlasIndex;
88
+
89
+ // Output color
90
+ out vec4 fragColor;
91
+
92
+ void main() {
93
+ vec2 atlasUv = v_uv.xy;
94
+ vec2 originalUv = v_uv.zw;
95
+
96
+ if (v_atlasIndex == 1000) {
97
+ // Rectangle - return solid color
98
+ fragColor = vec4(1.0, 1.0, 1.0, 1.0) * v_tint;
99
+ } else if (v_atlasIndex == 1001) {
100
+ // Circle
101
+ float edgeWidth = 4.0 / max(u_resolution.x, u_resolution.y);
102
+ float centerDistance = 2.0 * distance(vec2(0.5, 0.5), originalUv);
103
+ float alpha = 1.0 - smoothstep(1.0 - edgeWidth, 1.0 + edgeWidth, centerDistance);
104
+ fragColor = vec4(v_tint.rgb, alpha * v_tint.a);
105
+ } else {
106
+ // Texture - sample from texture array
107
+ vec4 color = texture(u_textureArray, vec3(atlasUv, float(v_atlasIndex)));
108
+ fragColor = color * v_tint;
109
+ }
110
+ }
111
+ `;
112
+
113
+ /** Alias for users who want to extend the default shader */
114
+ export const defaultFragmentShader = fragmentShader;
@@ -0,0 +1,2 @@
1
+ export { WebGLBackend, type WebGLBackendOptions } from "./WebGLBackend";
2
+ export { WebGLQuadShader } from "./WebGLQuadShader";
@@ -1,12 +1,11 @@
1
- import computeShader from "./pixel-scraping.wgsl";
2
- import type { TextureWithMetadata } from "./types";
1
+ import type { TextureWithMetadata } from "../../textures/types";
2
+ import computeShader from "./wgsl/pixel-scraping.wgsl";
3
3
 
4
4
  // Constants
5
5
  const BOUNDING_BOX_SIZE = 4 * Uint32Array.BYTES_PER_ELEMENT;
6
6
  const WORKGROUP_SIZE = 8;
7
7
  const MAX_BOUND = 0xffffffff;
8
8
  const MIN_BOUND = 0x00000000;
9
- const BYTES_PER_PIXEL = 4;
10
9
 
11
10
  /**
12
11
  * The data returned by the compute shader that represents the opaque pixels in a texture.
@@ -260,51 +259,6 @@ export class TextureComputeShader {
260
259
  };
261
260
  }
262
261
 
263
- /**
264
- * Converts a GPUTexture to an ImageBitmap for display or further use.
265
- */
266
- async #textureToBitmap(
267
- texture: GPUTexture,
268
- width: number,
269
- height: number,
270
- ): Promise<ImageBitmap> {
271
- const paddedBytesPerRow = Math.ceil((width * BYTES_PER_PIXEL) / 256) * 256;
272
- const bufferSize = paddedBytesPerRow * height;
273
-
274
- const readBuffer = this.#device.createBuffer({
275
- size: bufferSize,
276
- usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
277
- });
278
-
279
- const encoder = this.#device.createCommandEncoder();
280
- encoder.copyTextureToBuffer(
281
- { texture },
282
- { buffer: readBuffer, bytesPerRow: paddedBytesPerRow },
283
- { width, height, depthOrArrayLayers: 1 },
284
- );
285
- this.#device.queue.submit([encoder.finish()]);
286
-
287
- await readBuffer.mapAsync(GPUMapMode.READ);
288
- const raw = readBuffer.getMappedRange();
289
- const rawArray = new Uint8Array(raw);
290
-
291
- const pixelData = new Uint8ClampedArray(width * height * 4);
292
- for (let y = 0; y < height; y++) {
293
- const src = y * paddedBytesPerRow;
294
- const dst = y * width * 4;
295
- pixelData.set(rawArray.subarray(src, src + width * 4), dst);
296
- }
297
- readBuffer.unmap();
298
-
299
- const canvas = document.createElement("canvas");
300
- canvas.width = width;
301
- canvas.height = height;
302
- const ctx = canvas.getContext("2d")!;
303
- ctx.putImageData(new ImageData(pixelData, width, height), 0, 0);
304
-
305
- return await createImageBitmap(canvas);
306
- }
307
-
308
262
  // Bind group helpers
309
263
 
310
264
  #boundsBindGroup(inputTexture: GPUTexture): GPUBindGroup {