@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
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
|
/**
|
|
@@ -510,7 +429,7 @@ export class Toodle {
|
|
|
510
429
|
*/
|
|
511
430
|
Quad(assetId: TextureId, options: QuadOptions = {}) {
|
|
512
431
|
const assetManager = options.assetManager ?? this.assets;
|
|
513
|
-
options.
|
|
432
|
+
options.size ??= assetManager.getSize(assetId);
|
|
514
433
|
options.shader ??= this.#defaultQuadShader();
|
|
515
434
|
options.atlasCoords ??= assetManager.extra.getAtlasCoords(assetId)[0];
|
|
516
435
|
options.textureId ??= assetId;
|
|
@@ -578,7 +497,7 @@ export class Toodle {
|
|
|
578
497
|
height: originalSize.height,
|
|
579
498
|
};
|
|
580
499
|
|
|
581
|
-
options.
|
|
500
|
+
options.size ??= {
|
|
582
501
|
width: originalSize.width,
|
|
583
502
|
height: originalSize.height,
|
|
584
503
|
};
|
|
@@ -612,7 +531,7 @@ export class Toodle {
|
|
|
612
531
|
|
|
613
532
|
shapes = {
|
|
614
533
|
Rect: (options: QuadOptions = {}) => {
|
|
615
|
-
options.
|
|
534
|
+
options.size ??= { width: 1, height: 1 };
|
|
616
535
|
options.shader ??= this.#defaultQuadShader();
|
|
617
536
|
options.atlasCoords ??= {
|
|
618
537
|
atlasIndex: 1000,
|
|
@@ -640,18 +559,25 @@ export class Toodle {
|
|
|
640
559
|
return quad;
|
|
641
560
|
},
|
|
642
561
|
|
|
643
|
-
Circle: (options:
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
562
|
+
Circle: (options: CircleOptions = { radius: 50 }) => {
|
|
563
|
+
const radius = options.radius ?? 50;
|
|
564
|
+
const diameter = radius * 2;
|
|
565
|
+
|
|
566
|
+
const quadOptions: QuadOptions = {
|
|
567
|
+
...options,
|
|
568
|
+
size: { width: diameter, height: diameter },
|
|
569
|
+
shader: options.shader ?? this.#defaultQuadShader(),
|
|
570
|
+
atlasCoords: options.atlasCoords ?? {
|
|
571
|
+
atlasIndex: 1001,
|
|
572
|
+
uvOffset: { x: 0, y: 0 },
|
|
573
|
+
uvScale: { width: 0, height: 0 },
|
|
574
|
+
cropOffset: { x: 0, y: 0 },
|
|
575
|
+
originalSize: { width: 1, height: 1 },
|
|
576
|
+
},
|
|
577
|
+
assetManager: this.assets,
|
|
652
578
|
};
|
|
653
|
-
|
|
654
|
-
const quad = new QuadNode(
|
|
579
|
+
|
|
580
|
+
const quad = new QuadNode(quadOptions, this.#matrixPool);
|
|
655
581
|
|
|
656
582
|
if (options?.position) {
|
|
657
583
|
quad.position = options.position;
|
|
@@ -693,7 +619,7 @@ export class Toodle {
|
|
|
693
619
|
originalSize: { width: 1, height: 1 },
|
|
694
620
|
},
|
|
695
621
|
shader: options.shader ?? this.#defaultQuadShader(),
|
|
696
|
-
|
|
622
|
+
size: { width: 1, height: 1 },
|
|
697
623
|
layer: options.layer,
|
|
698
624
|
key: options.key,
|
|
699
625
|
rotationRadians: angle,
|
|
@@ -711,17 +637,18 @@ export class Toodle {
|
|
|
711
637
|
},
|
|
712
638
|
};
|
|
713
639
|
|
|
714
|
-
#quadShader:
|
|
640
|
+
#quadShader: IBackendShader | null = null;
|
|
715
641
|
|
|
716
|
-
#defaultQuadShader() {
|
|
642
|
+
#defaultQuadShader(): IBackendShader {
|
|
717
643
|
if (this.#quadShader) {
|
|
718
644
|
return this.#quadShader;
|
|
719
645
|
}
|
|
720
646
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
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*/ `
|
|
725
652
|
@fragment
|
|
726
653
|
fn frag(vertex: VertexOutput) -> @location(0) vec4f {
|
|
727
654
|
let color = default_fragment_shader(vertex, ${
|
|
@@ -731,8 +658,14 @@ export class Toodle {
|
|
|
731
658
|
});
|
|
732
659
|
return color;
|
|
733
660
|
}
|
|
734
|
-
|
|
735
|
-
|
|
661
|
+
`
|
|
662
|
+
: undefined;
|
|
663
|
+
|
|
664
|
+
const shader = this.#backend.createQuadShader({
|
|
665
|
+
label: "default quad shader",
|
|
666
|
+
instanceCount: this.limits.instanceCount,
|
|
667
|
+
userCode,
|
|
668
|
+
});
|
|
736
669
|
|
|
737
670
|
this.#quadShader = shader;
|
|
738
671
|
return shader;
|
|
@@ -754,11 +687,29 @@ export class Toodle {
|
|
|
754
687
|
static async attach(canvas: HTMLCanvasElement, options?: ToodleOptions) {
|
|
755
688
|
canvas.width = canvas.clientWidth * devicePixelRatio;
|
|
756
689
|
canvas.height = canvas.clientHeight * devicePixelRatio;
|
|
757
|
-
|
|
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
|
+
|
|
758
711
|
return new Toodle(
|
|
759
|
-
|
|
760
|
-
context,
|
|
761
|
-
presentationFormat,
|
|
712
|
+
backend,
|
|
762
713
|
canvas,
|
|
763
714
|
{
|
|
764
715
|
width: canvas.clientWidth,
|
|
@@ -775,21 +726,22 @@ export class Toodle {
|
|
|
775
726
|
*/
|
|
776
727
|
destroy() {
|
|
777
728
|
this.#resizeObserver.disconnect();
|
|
778
|
-
this.#
|
|
729
|
+
this.#backend.destroy();
|
|
779
730
|
this.assets.destroy();
|
|
780
731
|
}
|
|
781
732
|
|
|
782
733
|
/**
|
|
783
|
-
*
|
|
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
|
+
* }
|
|
784
741
|
*/
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
*/
|
|
789
|
-
device: (): GPUDevice => {
|
|
790
|
-
return this.#device;
|
|
791
|
-
},
|
|
792
|
-
};
|
|
742
|
+
get backend(): WebGPUBackend | WebGLBackend {
|
|
743
|
+
return this.#backend as WebGPUBackend | WebGLBackend;
|
|
744
|
+
}
|
|
793
745
|
}
|
|
794
746
|
|
|
795
747
|
export type StartFrameOptions = {
|
|
@@ -819,6 +771,27 @@ export type ToodleOptions = {
|
|
|
819
771
|
*/
|
|
820
772
|
filter?: "nearest" | "linear";
|
|
821
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";
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
export type CircleOptions = Omit<QuadOptions, "size"> & {
|
|
789
|
+
/**
|
|
790
|
+
* The radius of the circle in pixels.
|
|
791
|
+
* The diameter will be radius * 2.
|
|
792
|
+
* @default 50
|
|
793
|
+
*/
|
|
794
|
+
radius?: number;
|
|
822
795
|
};
|
|
823
796
|
|
|
824
797
|
export type LineOptions = {
|
|
@@ -841,7 +814,7 @@ export type LineOptions = {
|
|
|
841
814
|
/**
|
|
842
815
|
* The shader to use for the line.
|
|
843
816
|
*/
|
|
844
|
-
shader?:
|
|
817
|
+
shader?: IBackendShader;
|
|
845
818
|
/**
|
|
846
819
|
* The layer to draw the line on.
|
|
847
820
|
*/
|
|
@@ -851,3 +824,14 @@ export type LineOptions = {
|
|
|
851
824
|
*/
|
|
852
825
|
key?: string;
|
|
853
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
|
+
}
|