@bloopjs/toodle 0.0.104 → 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 +41 -19
- 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 +7247 -6663
- package/dist/mod.js.map +27 -22
- 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/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/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 +124 -156
- 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 +6 -2
- package/src/scene/RenderComponent.ts +2 -2
- package/src/text/TextShader.ts +17 -11
- package/src/textures/AssetManager.ts +117 -93
- 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
package/src/Toodle.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { type Mat3, mat3 } from "wgpu-matrix";
|
|
2
|
+
import type { IBackendShader } from "./backends/IBackendShader";
|
|
3
|
+
import type { BlendMode } from "./backends/IRenderBackend";
|
|
4
|
+
import type { BackendType, IRenderBackend } from "./backends/mod";
|
|
5
|
+
import { detectBackend } from "./backends/mod";
|
|
6
|
+
import { WebGLBackend } from "./backends/webgl2/mod";
|
|
7
|
+
import { WebGPUBackend } from "./backends/webgpu/mod";
|
|
8
|
+
import type { PostProcess } from "./backends/webgpu/postprocess/mod";
|
|
2
9
|
import type { Color } from "./coreTypes/Color";
|
|
10
|
+
import type { EngineUniform } from "./coreTypes/EngineUniform";
|
|
3
11
|
import type { Point } from "./coreTypes/Point";
|
|
4
12
|
import type { Size } from "./coreTypes/Size";
|
|
5
13
|
import type { Limits, LimitsOptions } from "./limits";
|
|
6
|
-
import { DEFAULT_LIMITS } from "./limits";
|
|
7
14
|
import {
|
|
8
15
|
convertScreenToWorld,
|
|
9
16
|
convertWorldToScreen,
|
|
@@ -15,14 +22,9 @@ import { JumboQuadNode, type JumboQuadOptions } from "./scene/JumboQuadNode";
|
|
|
15
22
|
import { QuadNode, type QuadOptions } from "./scene/QuadNode";
|
|
16
23
|
import { type NodeOptions, SceneNode } from "./scene/SceneNode";
|
|
17
24
|
import type { Resolution } from "./screen/resolution";
|
|
18
|
-
import type { EngineUniform } from "./shaders/EngineUniform";
|
|
19
|
-
import type { IShader } from "./shaders/IShader";
|
|
20
|
-
import type { PostProcess } from "./shaders/postprocess/mod";
|
|
21
|
-
import { QuadShader, type QuadShaderOpts } from "./shaders/QuadShader";
|
|
22
25
|
import { TextNode, type TextOptions } from "./text/TextNode";
|
|
23
26
|
import { AssetManager, type TextureId } from "./textures/AssetManager";
|
|
24
|
-
import {
|
|
25
|
-
import { assert, Pool } from "./utils/mod";
|
|
27
|
+
import { Pool } from "./utils/mod";
|
|
26
28
|
|
|
27
29
|
export class Toodle {
|
|
28
30
|
/**
|
|
@@ -45,8 +47,8 @@ export class Toodle {
|
|
|
45
47
|
instancesEnqueued: 0,
|
|
46
48
|
};
|
|
47
49
|
|
|
48
|
-
/**
|
|
49
|
-
|
|
50
|
+
/** The render backend (WebGPU or WebGL2) */
|
|
51
|
+
#backend: IRenderBackend;
|
|
50
52
|
|
|
51
53
|
/**
|
|
52
54
|
* Camera. This applies a 2d perspective projection matrix to any nodes drawn with toodle.draw
|
|
@@ -64,14 +66,6 @@ export class Toodle {
|
|
|
64
66
|
#engineUniform: EngineUniform;
|
|
65
67
|
#projectionMatrix: Mat3 = mat3.identity();
|
|
66
68
|
#batcher = new Batcher();
|
|
67
|
-
#limits: Limits;
|
|
68
|
-
#device: GPUDevice;
|
|
69
|
-
#context: GPUCanvasContext;
|
|
70
|
-
#presentationFormat: GPUTextureFormat;
|
|
71
|
-
#postprocess: PostProcess | null = null;
|
|
72
|
-
#renderPass?: GPURenderPassEncoder;
|
|
73
|
-
#encoder?: GPUCommandEncoder;
|
|
74
|
-
#pingpong!: [GPUTexture, GPUTexture];
|
|
75
69
|
#defaultFilter: GPUFilterMode;
|
|
76
70
|
#matrixPool: Pool<Mat3>;
|
|
77
71
|
#atlasSize: Size;
|
|
@@ -81,31 +75,22 @@ export class Toodle {
|
|
|
81
75
|
* see {@link Toodle.attach} for creating a Toodle instance that draws to a canvas.
|
|
82
76
|
*/
|
|
83
77
|
constructor(
|
|
84
|
-
|
|
85
|
-
context: GPUCanvasContext,
|
|
86
|
-
presentationFormat: GPUTextureFormat,
|
|
78
|
+
backend: IRenderBackend,
|
|
87
79
|
canvas: HTMLCanvasElement,
|
|
88
80
|
resolution: Resolution,
|
|
89
81
|
options: ToodleOptions,
|
|
90
82
|
) {
|
|
91
|
-
this.#
|
|
92
|
-
...DEFAULT_LIMITS,
|
|
93
|
-
...options.limits,
|
|
94
|
-
};
|
|
83
|
+
this.#backend = backend;
|
|
95
84
|
this.#matrixPool = new Pool<Mat3>(
|
|
96
85
|
() => mat3.identity(),
|
|
97
|
-
|
|
86
|
+
backend.limits.instanceCount,
|
|
98
87
|
);
|
|
99
|
-
this.#device = device;
|
|
100
|
-
this.#context = context;
|
|
101
|
-
this.#presentationFormat = presentationFormat;
|
|
102
88
|
this.#defaultFilter = options.filter ?? "linear";
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
this.debug = { device, presentationFormat };
|
|
89
|
+
|
|
90
|
+
// Create AssetManager with the backend
|
|
91
|
+
this.assets = new AssetManager(backend);
|
|
92
|
+
|
|
93
|
+
this.#atlasSize = backend.atlasSize;
|
|
109
94
|
this.#engineUniform = {
|
|
110
95
|
resolution,
|
|
111
96
|
camera: this.camera,
|
|
@@ -113,29 +98,26 @@ export class Toodle {
|
|
|
113
98
|
};
|
|
114
99
|
this.#resolution = resolution;
|
|
115
100
|
|
|
116
|
-
this.#createPingPongTextures(resolution);
|
|
117
101
|
this.resize(this.#resolution);
|
|
118
|
-
|
|
119
102
|
this.#resizeObserver = this.#createResizeObserver(canvas);
|
|
120
103
|
}
|
|
121
104
|
|
|
122
105
|
/**
|
|
123
106
|
* Screen shader is an optional slot for post-processing effects.
|
|
124
107
|
* Note that this will do the main render pass to an offscreen texture, which may impact performance.
|
|
108
|
+
* Currently only supported in WebGPU mode.
|
|
125
109
|
*/
|
|
126
|
-
get postprocess() {
|
|
127
|
-
|
|
110
|
+
get postprocess(): PostProcess | null {
|
|
111
|
+
if (this.#backend.type !== "webgpu") return null;
|
|
112
|
+
return (this.#backend as WebGPUBackend).getPostprocess();
|
|
128
113
|
}
|
|
129
114
|
|
|
130
115
|
set postprocess(value: PostProcess | null) {
|
|
131
|
-
this.#
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
this.#
|
|
136
|
-
this.#pingpong[0].height / window.devicePixelRatio;
|
|
137
|
-
if (value != null && hasChanged) {
|
|
138
|
-
this.#createPingPongTextures(this.#resolution);
|
|
116
|
+
if (value !== null && this.#backend.type !== "webgpu") {
|
|
117
|
+
throw new Error("Post-processing is only supported in WebGPU mode");
|
|
118
|
+
}
|
|
119
|
+
if (this.#backend.type === "webgpu") {
|
|
120
|
+
(this.#backend as WebGPUBackend).setPostprocess(value);
|
|
139
121
|
}
|
|
140
122
|
}
|
|
141
123
|
|
|
@@ -159,43 +141,8 @@ export class Toodle {
|
|
|
159
141
|
*/
|
|
160
142
|
resize(resolution: Resolution) {
|
|
161
143
|
createProjectionMatrix(resolution, this.#projectionMatrix);
|
|
162
|
-
|
|
163
|
-
const hasChanged =
|
|
164
|
-
resolution.width !== this.#resolution.width ||
|
|
165
|
-
resolution.height !== this.#resolution.height;
|
|
166
|
-
if (this.postprocess && hasChanged) {
|
|
167
|
-
this.#pingpong[0].destroy();
|
|
168
|
-
this.#pingpong[1].destroy();
|
|
169
|
-
}
|
|
170
144
|
this.#resolution = resolution;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
#createPingPongTextures(resolution: Resolution) {
|
|
174
|
-
if (this.#pingpong?.length) {
|
|
175
|
-
this.#pingpong[0].destroy();
|
|
176
|
-
this.#pingpong[1].destroy();
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
this.#pingpong = [
|
|
180
|
-
this.#device.createTexture({
|
|
181
|
-
size: {
|
|
182
|
-
width: resolution.width * window.devicePixelRatio,
|
|
183
|
-
height: resolution.height * window.devicePixelRatio,
|
|
184
|
-
},
|
|
185
|
-
format: this.#presentationFormat,
|
|
186
|
-
usage:
|
|
187
|
-
GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT,
|
|
188
|
-
}),
|
|
189
|
-
this.#device.createTexture({
|
|
190
|
-
size: {
|
|
191
|
-
width: resolution.width * window.devicePixelRatio,
|
|
192
|
-
height: resolution.height * window.devicePixelRatio,
|
|
193
|
-
},
|
|
194
|
-
format: this.#presentationFormat,
|
|
195
|
-
usage:
|
|
196
|
-
GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT,
|
|
197
|
-
}),
|
|
198
|
-
];
|
|
145
|
+
this.#backend.resize(resolution.width, resolution.height);
|
|
199
146
|
}
|
|
200
147
|
|
|
201
148
|
#createResizeObserver(canvas: HTMLCanvasElement) {
|
|
@@ -265,7 +212,7 @@ export class Toodle {
|
|
|
265
212
|
* const instanceLimit: number = toodle.limits.instanceCount;
|
|
266
213
|
*/
|
|
267
214
|
get limits(): Limits {
|
|
268
|
-
return this.#limits;
|
|
215
|
+
return this.#backend.limits;
|
|
269
216
|
}
|
|
270
217
|
|
|
271
218
|
get batcher(): Batcher {
|
|
@@ -277,9 +224,9 @@ export class Toodle {
|
|
|
277
224
|
*/
|
|
278
225
|
get maxPixels() {
|
|
279
226
|
return (
|
|
280
|
-
this
|
|
281
|
-
this
|
|
282
|
-
this
|
|
227
|
+
this.limits.textureSize *
|
|
228
|
+
this.limits.textureSize *
|
|
229
|
+
this.limits.textureArrayLayers
|
|
283
230
|
);
|
|
284
231
|
}
|
|
285
232
|
|
|
@@ -289,9 +236,9 @@ export class Toodle {
|
|
|
289
236
|
*/
|
|
290
237
|
get maxGpuMemory() {
|
|
291
238
|
return (
|
|
292
|
-
this
|
|
293
|
-
this
|
|
294
|
-
this
|
|
239
|
+
this.limits.instanceCount *
|
|
240
|
+
this.limits.instanceBufferSize *
|
|
241
|
+
this.limits.shaderCount
|
|
295
242
|
);
|
|
296
243
|
}
|
|
297
244
|
|
|
@@ -306,21 +253,7 @@ export class Toodle {
|
|
|
306
253
|
* toodle.endFrame();
|
|
307
254
|
*/
|
|
308
255
|
startFrame(options?: StartFrameOptions) {
|
|
309
|
-
this.#
|
|
310
|
-
const target = this.postprocess
|
|
311
|
-
? this.#pingpong[0]
|
|
312
|
-
: this.#context.getCurrentTexture();
|
|
313
|
-
this.#renderPass = this.#encoder.beginRenderPass({
|
|
314
|
-
label: `toodle frame ${this.diagnostics.frames}`,
|
|
315
|
-
colorAttachments: [
|
|
316
|
-
{
|
|
317
|
-
view: target.createView(),
|
|
318
|
-
clearValue: this.clearColor,
|
|
319
|
-
loadOp: options?.loadOp ?? "clear",
|
|
320
|
-
storeOp: "store",
|
|
321
|
-
},
|
|
322
|
-
],
|
|
323
|
-
});
|
|
256
|
+
this.#backend.startFrame(this.clearColor, options?.loadOp ?? "clear");
|
|
324
257
|
|
|
325
258
|
this.diagnostics.drawCalls =
|
|
326
259
|
this.diagnostics.pipelineSwitches =
|
|
@@ -360,24 +293,22 @@ export class Toodle {
|
|
|
360
293
|
*/
|
|
361
294
|
endFrame() {
|
|
362
295
|
try {
|
|
363
|
-
assert(
|
|
364
|
-
this.#renderPass,
|
|
365
|
-
"no render pass found. have you called startFrame?",
|
|
366
|
-
);
|
|
367
|
-
assert(this.#encoder, "no encoder found. have you called startFrame?");
|
|
368
|
-
|
|
369
296
|
mat3.mul(
|
|
370
297
|
this.#projectionMatrix,
|
|
371
298
|
this.camera.matrix,
|
|
372
299
|
this.#engineUniform.viewProjectionMatrix,
|
|
373
300
|
);
|
|
374
301
|
this.#engineUniform.resolution = this.#resolution;
|
|
302
|
+
|
|
303
|
+
// Update engine uniforms on the backend
|
|
304
|
+
this.#backend.updateEngineUniform(this.#engineUniform);
|
|
305
|
+
|
|
375
306
|
for (const pipeline of this.#batcher.pipelines) {
|
|
376
|
-
pipeline.shader.startFrame(this.#
|
|
307
|
+
pipeline.shader.startFrame(this.#engineUniform);
|
|
377
308
|
}
|
|
378
309
|
|
|
379
310
|
this.diagnostics.instancesEnqueued = this.#batcher.nodes.length;
|
|
380
|
-
if (this.#batcher.nodes.length > this
|
|
311
|
+
if (this.#batcher.nodes.length > this.limits.instanceCount) {
|
|
381
312
|
const err = new Error(
|
|
382
313
|
`ToodleInstanceCap: ${this.batcher.nodes.length} instances enqueued, max is ${this.limits.instanceCount}`,
|
|
383
314
|
);
|
|
@@ -389,7 +320,6 @@ export class Toodle {
|
|
|
389
320
|
for (const pipeline of layer.pipelines) {
|
|
390
321
|
this.diagnostics.pipelineSwitches++;
|
|
391
322
|
this.diagnostics.drawCalls += pipeline.shader.processBatch(
|
|
392
|
-
this.#renderPass,
|
|
393
323
|
pipeline.nodes,
|
|
394
324
|
);
|
|
395
325
|
}
|
|
@@ -398,17 +328,8 @@ export class Toodle {
|
|
|
398
328
|
for (const pipeline of this.#batcher.pipelines) {
|
|
399
329
|
pipeline.shader.endFrame();
|
|
400
330
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
if (this.postprocess) {
|
|
404
|
-
this.postprocess.process(
|
|
405
|
-
this.#device.queue,
|
|
406
|
-
this.#encoder,
|
|
407
|
-
this.#pingpong,
|
|
408
|
-
this.#context.getCurrentTexture(),
|
|
409
|
-
);
|
|
410
|
-
}
|
|
411
|
-
this.#device.queue.submit([this.#encoder.finish()]);
|
|
331
|
+
|
|
332
|
+
this.#backend.endFrame();
|
|
412
333
|
} finally {
|
|
413
334
|
this.#batcher.flush();
|
|
414
335
|
this.#matrixPool.free();
|
|
@@ -466,7 +387,7 @@ export class Toodle {
|
|
|
466
387
|
*
|
|
467
388
|
* @param label Debug name of the shader
|
|
468
389
|
* @param instanceCount - The maximum number of instances that will be processed by the shader. Note that a worst-case buffer of this many instances will be immediately allocated.
|
|
469
|
-
* @param userCode - The WGSL code to be used for the shader.
|
|
390
|
+
* @param userCode - The WGSL code to be used for the shader (WebGPU only).
|
|
470
391
|
* @param blendMode - The blend mode to be used for the shader.
|
|
471
392
|
*
|
|
472
393
|
* @example
|
|
@@ -478,16 +399,14 @@ export class Toodle {
|
|
|
478
399
|
instanceCount: number,
|
|
479
400
|
userCode: string,
|
|
480
401
|
shaderOpts?: QuadShaderOpts,
|
|
481
|
-
) {
|
|
482
|
-
return
|
|
402
|
+
): IBackendShader {
|
|
403
|
+
return this.#backend.createQuadShader({
|
|
483
404
|
label,
|
|
484
|
-
shaderOpts?.assetManager ?? this.assets,
|
|
485
|
-
this.#device,
|
|
486
|
-
this.#presentationFormat,
|
|
487
|
-
userCode,
|
|
488
405
|
instanceCount,
|
|
489
|
-
|
|
490
|
-
|
|
406
|
+
userCode,
|
|
407
|
+
blendMode: shaderOpts?.blendMode,
|
|
408
|
+
atlasId: shaderOpts?.atlasId,
|
|
409
|
+
});
|
|
491
410
|
}
|
|
492
411
|
|
|
493
412
|
/**
|
|
@@ -718,17 +637,18 @@ export class Toodle {
|
|
|
718
637
|
},
|
|
719
638
|
};
|
|
720
639
|
|
|
721
|
-
#quadShader:
|
|
640
|
+
#quadShader: IBackendShader | null = null;
|
|
722
641
|
|
|
723
|
-
#defaultQuadShader() {
|
|
642
|
+
#defaultQuadShader(): IBackendShader {
|
|
724
643
|
if (this.#quadShader) {
|
|
725
644
|
return this.#quadShader;
|
|
726
645
|
}
|
|
727
646
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
647
|
+
// For WebGPU, we can provide custom WGSL shader code
|
|
648
|
+
// For WebGL, the backend will use its default shader
|
|
649
|
+
const userCode =
|
|
650
|
+
this.#backend.type === "webgpu"
|
|
651
|
+
? /*wgsl*/ `
|
|
732
652
|
@fragment
|
|
733
653
|
fn frag(vertex: VertexOutput) -> @location(0) vec4f {
|
|
734
654
|
let color = default_fragment_shader(vertex, ${
|
|
@@ -738,8 +658,14 @@ export class Toodle {
|
|
|
738
658
|
});
|
|
739
659
|
return color;
|
|
740
660
|
}
|
|
741
|
-
|
|
742
|
-
|
|
661
|
+
`
|
|
662
|
+
: undefined;
|
|
663
|
+
|
|
664
|
+
const shader = this.#backend.createQuadShader({
|
|
665
|
+
label: "default quad shader",
|
|
666
|
+
instanceCount: this.limits.instanceCount,
|
|
667
|
+
userCode,
|
|
668
|
+
});
|
|
743
669
|
|
|
744
670
|
this.#quadShader = shader;
|
|
745
671
|
return shader;
|
|
@@ -761,11 +687,29 @@ export class Toodle {
|
|
|
761
687
|
static async attach(canvas: HTMLCanvasElement, options?: ToodleOptions) {
|
|
762
688
|
canvas.width = canvas.clientWidth * devicePixelRatio;
|
|
763
689
|
canvas.height = canvas.clientHeight * devicePixelRatio;
|
|
764
|
-
|
|
690
|
+
|
|
691
|
+
const backendOption = options?.backend ?? "auto";
|
|
692
|
+
let backendType: BackendType;
|
|
693
|
+
|
|
694
|
+
if (backendOption === "auto") {
|
|
695
|
+
backendType = await detectBackend();
|
|
696
|
+
} else {
|
|
697
|
+
backendType = backendOption;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
let backend: IRenderBackend;
|
|
701
|
+
if (backendType === "webgpu") {
|
|
702
|
+
backend = await WebGPUBackend.create(canvas, {
|
|
703
|
+
limits: options?.limits,
|
|
704
|
+
});
|
|
705
|
+
} else {
|
|
706
|
+
backend = await WebGLBackend.create(canvas, {
|
|
707
|
+
limits: options?.limits,
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
|
|
765
711
|
return new Toodle(
|
|
766
|
-
|
|
767
|
-
context,
|
|
768
|
-
presentationFormat,
|
|
712
|
+
backend,
|
|
769
713
|
canvas,
|
|
770
714
|
{
|
|
771
715
|
width: canvas.clientWidth,
|
|
@@ -782,21 +726,22 @@ export class Toodle {
|
|
|
782
726
|
*/
|
|
783
727
|
destroy() {
|
|
784
728
|
this.#resizeObserver.disconnect();
|
|
785
|
-
this.#
|
|
729
|
+
this.#backend.destroy();
|
|
786
730
|
this.assets.destroy();
|
|
787
731
|
}
|
|
788
732
|
|
|
789
733
|
/**
|
|
790
|
-
*
|
|
734
|
+
* Get the render backend instance.
|
|
735
|
+
* Cast to WebGPUBackend or WebGLBackend to access backend-specific properties.
|
|
736
|
+
*
|
|
737
|
+
* @example
|
|
738
|
+
* if (toodle.backend instanceof WebGPUBackend) {
|
|
739
|
+
* const device = toodle.backend.device;
|
|
740
|
+
* }
|
|
791
741
|
*/
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
*/
|
|
796
|
-
device: (): GPUDevice => {
|
|
797
|
-
return this.#device;
|
|
798
|
-
},
|
|
799
|
-
};
|
|
742
|
+
get backend(): WebGPUBackend | WebGLBackend {
|
|
743
|
+
return this.#backend as WebGPUBackend | WebGLBackend;
|
|
744
|
+
}
|
|
800
745
|
}
|
|
801
746
|
|
|
802
747
|
export type StartFrameOptions = {
|
|
@@ -826,6 +771,18 @@ export type ToodleOptions = {
|
|
|
826
771
|
*/
|
|
827
772
|
filter?: "nearest" | "linear";
|
|
828
773
|
limits?: LimitsOptions;
|
|
774
|
+
/**
|
|
775
|
+
* The rendering backend to use.
|
|
776
|
+
*
|
|
777
|
+
* **auto**: Automatically detect the best available backend (WebGPU > WebGL).
|
|
778
|
+
*
|
|
779
|
+
* **webgpu**: Use WebGPU backend. Throws if WebGPU is not available.
|
|
780
|
+
*
|
|
781
|
+
* **webgl2**: Use WebGL 2 backend (fallback for older browsers).
|
|
782
|
+
*
|
|
783
|
+
* @default "auto"
|
|
784
|
+
*/
|
|
785
|
+
backend?: BackendType | "auto";
|
|
829
786
|
};
|
|
830
787
|
|
|
831
788
|
export type CircleOptions = Omit<QuadOptions, "size"> & {
|
|
@@ -857,7 +814,7 @@ export type LineOptions = {
|
|
|
857
814
|
/**
|
|
858
815
|
* The shader to use for the line.
|
|
859
816
|
*/
|
|
860
|
-
shader?:
|
|
817
|
+
shader?: IBackendShader;
|
|
861
818
|
/**
|
|
862
819
|
* The layer to draw the line on.
|
|
863
820
|
*/
|
|
@@ -867,3 +824,14 @@ export type LineOptions = {
|
|
|
867
824
|
*/
|
|
868
825
|
key?: string;
|
|
869
826
|
};
|
|
827
|
+
|
|
828
|
+
export type QuadShaderOpts = {
|
|
829
|
+
/**
|
|
830
|
+
* Blend mode for alpha compositing.
|
|
831
|
+
*/
|
|
832
|
+
blendMode?: BlendMode;
|
|
833
|
+
/**
|
|
834
|
+
* Which texture atlas to bind (default: "default").
|
|
835
|
+
*/
|
|
836
|
+
atlasId?: string;
|
|
837
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { EngineUniform } from "../coreTypes/EngineUniform";
|
|
2
|
+
import type { SceneNode } from "../scene/SceneNode";
|
|
3
|
+
import type { BlendMode } from "./IRenderBackend";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Options for creating a quad shader.
|
|
7
|
+
*/
|
|
8
|
+
export type QuadShaderCreationOpts = {
|
|
9
|
+
/** Debug label for the shader */
|
|
10
|
+
label: string;
|
|
11
|
+
/** Maximum number of instances this shader can process per frame */
|
|
12
|
+
instanceCount: number;
|
|
13
|
+
/** User-defined shader code (WGSL for WebGPU, GLSL for WebGL) */
|
|
14
|
+
userCode?: string;
|
|
15
|
+
/** Blend mode for alpha compositing */
|
|
16
|
+
blendMode?: BlendMode;
|
|
17
|
+
/** Which texture atlas to bind (default: "default") */
|
|
18
|
+
atlasId?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Backend-agnostic shader interface.
|
|
23
|
+
*
|
|
24
|
+
* This interface abstracts the differences between WebGPU and WebGL shaders.
|
|
25
|
+
* Each backend provides its own implementation.
|
|
26
|
+
*/
|
|
27
|
+
export interface IBackendShader {
|
|
28
|
+
/** Debug label for the shader */
|
|
29
|
+
readonly label: string;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Prepare for a new frame.
|
|
33
|
+
* Called once per frame before any processBatch calls.
|
|
34
|
+
*
|
|
35
|
+
* @param uniform - Engine uniforms (view-projection, resolution)
|
|
36
|
+
*/
|
|
37
|
+
startFrame(uniform: EngineUniform): void;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Process a batch of nodes and issue draw calls.
|
|
41
|
+
*
|
|
42
|
+
* @param nodes - The nodes to render
|
|
43
|
+
* @returns Number of draw calls issued
|
|
44
|
+
*/
|
|
45
|
+
processBatch(nodes: SceneNode[]): number;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Cleanup after frame.
|
|
49
|
+
* Called once per frame after all processBatch calls.
|
|
50
|
+
*/
|
|
51
|
+
endFrame(): void;
|
|
52
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { Color } from "../coreTypes/Color";
|
|
2
|
+
import type { EngineUniform } from "../coreTypes/EngineUniform";
|
|
3
|
+
import type { Size } from "../coreTypes/Size";
|
|
4
|
+
import type { Limits } from "../limits";
|
|
5
|
+
import type { CpuTextureAtlas } from "../textures/types";
|
|
6
|
+
import type { IBackendShader, QuadShaderCreationOpts } from "./IBackendShader";
|
|
7
|
+
import type { ITextureAtlas, TextureAtlasOptions } from "./ITextureAtlas";
|
|
8
|
+
|
|
9
|
+
export type BackendType = "webgpu" | "webgl2";
|
|
10
|
+
|
|
11
|
+
export type BlendFactor =
|
|
12
|
+
| "one"
|
|
13
|
+
| "zero"
|
|
14
|
+
| "src-alpha"
|
|
15
|
+
| "one-minus-src-alpha"
|
|
16
|
+
| "dst-alpha"
|
|
17
|
+
| "one-minus-dst-alpha";
|
|
18
|
+
export type BlendOperation = "add" | "subtract" | "reverse-subtract";
|
|
19
|
+
|
|
20
|
+
export type BlendMode = {
|
|
21
|
+
color: {
|
|
22
|
+
srcFactor: BlendFactor;
|
|
23
|
+
dstFactor: BlendFactor;
|
|
24
|
+
operation: BlendOperation;
|
|
25
|
+
};
|
|
26
|
+
alpha: {
|
|
27
|
+
srcFactor: BlendFactor;
|
|
28
|
+
dstFactor: BlendFactor;
|
|
29
|
+
operation: BlendOperation;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The render backend interface abstracts WebGPU and WebGL differences.
|
|
35
|
+
*
|
|
36
|
+
* Implementations handle GPU-specific operations like texture management,
|
|
37
|
+
* shader creation, and frame lifecycle.
|
|
38
|
+
*/
|
|
39
|
+
export interface IRenderBackend {
|
|
40
|
+
/** The type of backend ("webgpu" or "webgl2") */
|
|
41
|
+
readonly type: BackendType;
|
|
42
|
+
|
|
43
|
+
/** Engine limits (texture size, instance count, etc.) */
|
|
44
|
+
readonly limits: Limits;
|
|
45
|
+
|
|
46
|
+
/** Size of the default texture atlas */
|
|
47
|
+
readonly atlasSize: Size;
|
|
48
|
+
|
|
49
|
+
/** Default atlas ID (always "default") */
|
|
50
|
+
readonly defaultAtlasId: string;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Begin a new frame.
|
|
54
|
+
* WebGPU: Creates command encoder and render pass
|
|
55
|
+
* WebGL: Clears the canvas if loadOp is "clear"
|
|
56
|
+
*/
|
|
57
|
+
startFrame(clearColor: Color, loadOp: "clear" | "load"): void;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* End the current frame and submit to GPU.
|
|
61
|
+
* WebGPU: Ends render pass and submits command buffer
|
|
62
|
+
* WebGL: Flushes pending operations
|
|
63
|
+
*/
|
|
64
|
+
endFrame(): void;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Update engine uniforms (view-projection matrix, resolution).
|
|
68
|
+
* Called once per frame before shader processing.
|
|
69
|
+
*/
|
|
70
|
+
updateEngineUniform(uniform: EngineUniform): void;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Upload a CPU texture atlas to a GPU texture array layer.
|
|
74
|
+
* @param atlas - The CPU-side atlas data to upload
|
|
75
|
+
* @param layerIndex - Which layer in the texture array to upload to
|
|
76
|
+
* @param atlasId - Which atlas to upload to (default: "default")
|
|
77
|
+
*/
|
|
78
|
+
uploadAtlas(
|
|
79
|
+
atlas: CpuTextureAtlas,
|
|
80
|
+
layerIndex: number,
|
|
81
|
+
atlasId?: string,
|
|
82
|
+
): Promise<void>;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Create a new texture atlas.
|
|
86
|
+
* @param id - Unique identifier for this atlas
|
|
87
|
+
* @param options - Atlas configuration (format, layers, size)
|
|
88
|
+
*/
|
|
89
|
+
createTextureAtlas(id: string, options?: TextureAtlasOptions): ITextureAtlas;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get a texture atlas by ID.
|
|
93
|
+
* @param id - Atlas identifier or defaults to "default"
|
|
94
|
+
* @returns The atlas, or null if not found
|
|
95
|
+
*/
|
|
96
|
+
getTextureAtlas(id?: string): ITextureAtlas | null;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Destroy a texture atlas and free GPU resources.
|
|
100
|
+
* @param id - Atlas identifier
|
|
101
|
+
*/
|
|
102
|
+
destroyTextureAtlas(id: string): void;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Create a quad shader for instanced rendering.
|
|
106
|
+
*/
|
|
107
|
+
createQuadShader(opts: QuadShaderCreationOpts): IBackendShader;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Handle canvas resize.
|
|
111
|
+
*/
|
|
112
|
+
resize(width: number, height: number): void;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Clean up GPU resources.
|
|
116
|
+
*/
|
|
117
|
+
destroy(): void;
|
|
118
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supported texture atlas formats.
|
|
3
|
+
*/
|
|
4
|
+
export type TextureAtlasFormat = "rgba8unorm" | "rg8unorm";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Options for creating a texture atlas.
|
|
8
|
+
*/
|
|
9
|
+
export type TextureAtlasOptions = {
|
|
10
|
+
/** Texture format (default: "rgba8unorm") */
|
|
11
|
+
format?: TextureAtlasFormat;
|
|
12
|
+
/** Number of array layers (default: limits.textureArrayLayers) */
|
|
13
|
+
layers?: number;
|
|
14
|
+
/** Atlas size in pixels (default: limits.textureSize) */
|
|
15
|
+
size?: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Backend-agnostic texture atlas interface.
|
|
20
|
+
*
|
|
21
|
+
* Texture atlases are GPU texture arrays that store multiple textures.
|
|
22
|
+
* Each backend manages its atlases and provides this common interface.
|
|
23
|
+
*/
|
|
24
|
+
export interface ITextureAtlas {
|
|
25
|
+
/** Unique identifier for this atlas */
|
|
26
|
+
readonly id: string;
|
|
27
|
+
/** Texture format */
|
|
28
|
+
readonly format: TextureAtlasFormat;
|
|
29
|
+
/** Number of array layers */
|
|
30
|
+
readonly layers: number;
|
|
31
|
+
/** Size in pixels (width = height) */
|
|
32
|
+
readonly size: number;
|
|
33
|
+
/** Underlying GPU handle (GPUTexture for WebGPU, WebGLTexture for WebGL) */
|
|
34
|
+
readonly handle: unknown;
|
|
35
|
+
}
|