@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.
- package/dist/Toodle.d.ts +50 -20
- package/dist/Toodle.d.ts.map +1 -1
- package/dist/backends/IBackendShader.d.ts +48 -0
- package/dist/backends/IBackendShader.d.ts.map +1 -0
- package/dist/backends/IRenderBackend.d.ts +92 -0
- package/dist/backends/IRenderBackend.d.ts.map +1 -0
- package/dist/backends/ITextureAtlas.d.ts +34 -0
- package/dist/backends/ITextureAtlas.d.ts.map +1 -0
- package/dist/backends/detection.d.ts +16 -0
- package/dist/backends/detection.d.ts.map +1 -0
- package/dist/backends/mod.d.ts +9 -0
- package/dist/backends/mod.d.ts.map +1 -0
- package/dist/backends/webgl2/WebGLBackend.d.ts +51 -0
- package/dist/backends/webgl2/WebGLBackend.d.ts.map +1 -0
- package/dist/backends/webgl2/WebGLQuadShader.d.ts +17 -0
- package/dist/backends/webgl2/WebGLQuadShader.d.ts.map +1 -0
- package/dist/backends/webgl2/glsl/quad.glsl.d.ts +12 -0
- package/dist/backends/webgl2/glsl/quad.glsl.d.ts.map +1 -0
- package/dist/backends/webgl2/mod.d.ts +3 -0
- package/dist/backends/webgl2/mod.d.ts.map +1 -0
- package/dist/backends/webgpu/ShaderDescriptor.d.ts.map +1 -0
- package/dist/{textures → backends/webgpu}/TextureComputeShader.d.ts +1 -1
- package/dist/backends/webgpu/TextureComputeShader.d.ts.map +1 -0
- package/dist/backends/webgpu/WebGPUBackend.d.ts +67 -0
- package/dist/backends/webgpu/WebGPUBackend.d.ts.map +1 -0
- package/dist/backends/webgpu/WebGPUQuadShader.d.ts +18 -0
- package/dist/backends/webgpu/WebGPUQuadShader.d.ts.map +1 -0
- package/dist/backends/webgpu/mod.d.ts +3 -0
- package/dist/backends/webgpu/mod.d.ts.map +1 -0
- package/dist/backends/webgpu/parser.d.ts.map +1 -0
- package/dist/{shaders → backends/webgpu}/postprocess/blur.d.ts +1 -1
- package/dist/backends/webgpu/postprocess/blur.d.ts.map +1 -0
- package/dist/{shaders → backends/webgpu}/postprocess/mod.d.ts +1 -1
- package/dist/backends/webgpu/postprocess/mod.d.ts.map +1 -0
- package/dist/backends/webgpu/samplers.d.ts.map +1 -0
- package/dist/backends/webgpu/wgsl/example.wgsl.d.ts.map +1 -0
- package/dist/backends/webgpu/wgsl/hello.wgsl.d.ts.map +1 -0
- package/dist/backends/webgpu/wgsl/helloInstanced.wgsl.d.ts.map +1 -0
- package/dist/backends/webgpu/wgsl/pixel-scraping.wgsl.d.ts.map +1 -0
- package/dist/backends/webgpu/wgsl/quad.wgsl.d.ts.map +1 -0
- package/dist/coreTypes/EngineUniform.d.ts.map +1 -0
- package/dist/mod.d.ts +3 -2
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +6741 -6151
- package/dist/mod.js.map +28 -23
- package/dist/scene/Batcher.d.ts +2 -2
- package/dist/scene/Batcher.d.ts.map +1 -1
- package/dist/scene/QuadNode.d.ts +3 -2
- package/dist/scene/QuadNode.d.ts.map +1 -1
- package/dist/scene/RenderComponent.d.ts +2 -2
- package/dist/scene/RenderComponent.d.ts.map +1 -1
- package/dist/scene/SceneNode.d.ts +2 -2
- package/dist/scene/SceneNode.d.ts.map +1 -1
- package/dist/text/TextShader.d.ts +8 -6
- package/dist/text/TextShader.d.ts.map +1 -1
- package/dist/textures/AssetManager.d.ts +21 -5
- package/dist/textures/AssetManager.d.ts.map +1 -1
- package/dist/textures/types.d.ts +4 -2
- package/dist/textures/types.d.ts.map +1 -1
- package/dist/textures/util.d.ts.map +1 -1
- package/dist/utils/boilerplate.d.ts +1 -1
- package/dist/utils/boilerplate.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Toodle.ts +155 -171
- package/src/backends/IBackendShader.ts +52 -0
- package/src/backends/IRenderBackend.ts +118 -0
- package/src/backends/ITextureAtlas.ts +35 -0
- package/src/backends/detection.ts +46 -0
- package/src/backends/mod.ts +29 -0
- package/src/backends/webgl2/WebGLBackend.ts +256 -0
- package/src/backends/webgl2/WebGLQuadShader.ts +278 -0
- package/src/backends/webgl2/glsl/quad.glsl.ts +114 -0
- package/src/backends/webgl2/mod.ts +2 -0
- package/src/{textures → backends/webgpu}/TextureComputeShader.ts +2 -48
- package/src/backends/webgpu/WebGPUBackend.ts +350 -0
- package/src/{shaders/QuadShader.ts → backends/webgpu/WebGPUQuadShader.ts} +226 -170
- package/src/backends/webgpu/mod.ts +2 -0
- package/src/{shaders → backends/webgpu}/parser.ts +2 -2
- package/src/{shaders → backends/webgpu}/postprocess/blur.ts +2 -2
- package/src/{shaders → backends/webgpu}/postprocess/mod.ts +1 -1
- package/src/mod.ts +3 -2
- package/src/scene/Batcher.ts +3 -3
- package/src/scene/QuadNode.ts +7 -6
- package/src/scene/RenderComponent.ts +2 -2
- package/src/scene/SceneNode.ts +11 -12
- package/src/text/TextNode.ts +2 -2
- package/src/text/TextShader.ts +17 -11
- package/src/textures/AssetManager.ts +119 -94
- package/src/textures/types.ts +4 -2
- package/src/textures/util.ts +0 -65
- package/src/utils/boilerplate.ts +1 -1
- package/dist/shaders/EngineUniform.d.ts.map +0 -1
- package/dist/shaders/IShader.d.ts +0 -15
- package/dist/shaders/IShader.d.ts.map +0 -1
- package/dist/shaders/QuadShader.d.ts +0 -18
- package/dist/shaders/QuadShader.d.ts.map +0 -1
- package/dist/shaders/ShaderDescriptor.d.ts.map +0 -1
- package/dist/shaders/mod.d.ts +0 -6
- package/dist/shaders/mod.d.ts.map +0 -1
- package/dist/shaders/parser.d.ts.map +0 -1
- package/dist/shaders/postprocess/blur.d.ts.map +0 -1
- package/dist/shaders/postprocess/mod.d.ts.map +0 -1
- package/dist/shaders/samplers.d.ts.map +0 -1
- package/dist/shaders/wgsl/example.wgsl.d.ts.map +0 -1
- package/dist/shaders/wgsl/hello.wgsl.d.ts.map +0 -1
- package/dist/shaders/wgsl/helloInstanced.wgsl.d.ts.map +0 -1
- package/dist/shaders/wgsl/quad.wgsl.d.ts.map +0 -1
- package/dist/textures/TextureComputeShader.d.ts.map +0 -1
- package/dist/textures/pixel-scraping.wgsl.d.ts.map +0 -1
- package/src/shaders/IShader.ts +0 -20
- package/src/shaders/mod.ts +0 -5
- /package/dist/{shaders → backends/webgpu}/ShaderDescriptor.d.ts +0 -0
- /package/dist/{shaders → backends/webgpu}/parser.d.ts +0 -0
- /package/dist/{shaders → backends/webgpu}/samplers.d.ts +0 -0
- /package/dist/{shaders → backends/webgpu}/wgsl/example.wgsl.d.ts +0 -0
- /package/dist/{shaders → backends/webgpu}/wgsl/hello.wgsl.d.ts +0 -0
- /package/dist/{shaders → backends/webgpu}/wgsl/helloInstanced.wgsl.d.ts +0 -0
- /package/dist/{textures → backends/webgpu/wgsl}/pixel-scraping.wgsl.d.ts +0 -0
- /package/dist/{shaders → backends/webgpu}/wgsl/quad.wgsl.d.ts +0 -0
- /package/dist/{shaders → coreTypes}/EngineUniform.d.ts +0 -0
- /package/src/{shaders → backends/webgpu}/ShaderDescriptor.ts +0 -0
- /package/src/{shaders → backends/webgpu}/samplers.ts +0 -0
- /package/src/{shaders → backends/webgpu}/wgsl/example.wgsl.ts +0 -0
- /package/src/{shaders → backends/webgpu}/wgsl/hello.wgsl.ts +0 -0
- /package/src/{shaders → backends/webgpu}/wgsl/helloInstanced.wgsl.ts +0 -0
- /package/src/{textures → backends/webgpu/wgsl}/pixel-scraping.wgsl.ts +0 -0
- /package/src/{shaders → backends/webgpu}/wgsl/quad.wgsl.ts +0 -0
- /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;
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
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 {
|