@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,350 @@
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, LimitsOptions } from "../../limits";
5
+ import { DEFAULT_LIMITS } from "../../limits";
6
+ import type { CpuTextureAtlas } from "../../textures/types";
7
+ import { assert } from "../../utils/assert";
8
+ import type { IBackendShader, QuadShaderCreationOpts } from "../IBackendShader";
9
+ import type { IRenderBackend } from "../IRenderBackend";
10
+ import type {
11
+ ITextureAtlas,
12
+ TextureAtlasFormat,
13
+ TextureAtlasOptions,
14
+ } from "../ITextureAtlas";
15
+ import type { PostProcess } from "./postprocess/mod";
16
+ import { WebGPUQuadShader } from "./WebGPUQuadShader";
17
+
18
+ export type WebGPUBackendOptions = {
19
+ limits?: LimitsOptions;
20
+ format?: "rgba8unorm" | "rg8unorm";
21
+ };
22
+
23
+ /**
24
+ * WebGPU implementation of the render backend.
25
+ */
26
+ export class WebGPUBackend implements IRenderBackend {
27
+ readonly type = "webgpu" as const;
28
+ readonly limits: Limits;
29
+ readonly atlasSize: Size;
30
+ readonly defaultAtlasId = "default";
31
+
32
+ #atlases = new Map<string, ITextureAtlas>();
33
+
34
+ #device: GPUDevice;
35
+ #context: GPUCanvasContext;
36
+ #presentationFormat: GPUTextureFormat;
37
+ #encoder: GPUCommandEncoder | null = null;
38
+ #renderPass: GPURenderPassEncoder | null = null;
39
+ #postprocess: PostProcess | null = null;
40
+ #pingpong: [GPUTexture, GPUTexture] | null = null;
41
+ #canvas: HTMLCanvasElement;
42
+
43
+ private constructor(
44
+ device: GPUDevice,
45
+ context: GPUCanvasContext,
46
+ presentationFormat: GPUTextureFormat,
47
+ limits: Limits,
48
+ canvas: HTMLCanvasElement,
49
+ ) {
50
+ this.#device = device;
51
+ this.#context = context;
52
+ this.#presentationFormat = presentationFormat;
53
+ this.limits = limits;
54
+ this.atlasSize = {
55
+ width: limits.textureSize,
56
+ height: limits.textureSize,
57
+ };
58
+ this.#canvas = canvas;
59
+ }
60
+
61
+ /**
62
+ * Create a WebGPU backend attached to a canvas.
63
+ */
64
+ static async create(
65
+ canvas: HTMLCanvasElement,
66
+ options: WebGPUBackendOptions = {},
67
+ ): Promise<WebGPUBackend> {
68
+ const adapter = await navigator.gpu.requestAdapter();
69
+ if (!adapter) {
70
+ throw new Error("WebGPU not supported: no adapter found");
71
+ }
72
+
73
+ const device = await adapter.requestDevice();
74
+ device.lost.then((info) => {
75
+ console.error("GPU Device lost", info);
76
+ });
77
+
78
+ const context = canvas.getContext("webgpu");
79
+ assert(context, "Could not get WebGPU context from canvas");
80
+
81
+ const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
82
+
83
+ context.configure({
84
+ device,
85
+ format: presentationFormat,
86
+ });
87
+
88
+ const limits: Limits = {
89
+ ...DEFAULT_LIMITS,
90
+ ...options.limits,
91
+ };
92
+
93
+ const backend = new WebGPUBackend(
94
+ device,
95
+ context,
96
+ presentationFormat,
97
+ limits,
98
+ canvas,
99
+ );
100
+
101
+ // Create the default texture atlas
102
+ backend.createTextureAtlas("default", {
103
+ format: options.format ?? "rgba8unorm",
104
+ layers: limits.textureArrayLayers,
105
+ size: limits.textureSize,
106
+ });
107
+
108
+ return backend;
109
+ }
110
+
111
+ startFrame(clearColor: Color, loadOp: "clear" | "load"): void {
112
+ this.#encoder = this.#device.createCommandEncoder();
113
+
114
+ // If postprocessing, render to ping-pong texture; otherwise render to canvas
115
+ const target = this.#postprocess
116
+ ? this.#pingpong![0]
117
+ : this.#context.getCurrentTexture();
118
+
119
+ this.#renderPass = this.#encoder.beginRenderPass({
120
+ label: "toodle frame",
121
+ colorAttachments: [
122
+ {
123
+ view: target.createView(),
124
+ clearValue: clearColor,
125
+ loadOp,
126
+ storeOp: "store",
127
+ },
128
+ ],
129
+ });
130
+ }
131
+
132
+ endFrame(): void {
133
+ assert(this.#renderPass, "No render pass - did you call startFrame?");
134
+ assert(this.#encoder, "No encoder - did you call startFrame?");
135
+
136
+ this.#renderPass.end();
137
+
138
+ // Run postprocessing if set
139
+ if (this.#postprocess && this.#pingpong) {
140
+ this.#postprocess.process(
141
+ this.#device.queue,
142
+ this.#encoder,
143
+ this.#pingpong,
144
+ this.#context.getCurrentTexture(),
145
+ );
146
+ }
147
+
148
+ this.#device.queue.submit([this.#encoder.finish()]);
149
+
150
+ this.#renderPass = null;
151
+ this.#encoder = null;
152
+ }
153
+
154
+ updateEngineUniform(_uniform: EngineUniform): void {
155
+ // Uniforms are updated per-shader in WebGPU, not at the backend level
156
+ // This is handled in WebGPUQuadShader.startFrame
157
+ }
158
+
159
+ async uploadAtlas(
160
+ atlas: CpuTextureAtlas,
161
+ layerIndex: number,
162
+ atlasId?: string,
163
+ ): Promise<void> {
164
+ const targetAtlas = this.getTextureAtlas(atlasId ?? "default");
165
+ assert(targetAtlas, `Atlas "${atlasId ?? "default"}" not found`);
166
+ const texture = targetAtlas.handle as GPUTexture;
167
+
168
+ if (atlas.rg8Bytes) {
169
+ const w = texture.width;
170
+ const h = texture.height;
171
+
172
+ // WebGPU requires 256-byte bytesPerRow
173
+ const rowBytes = w * 2;
174
+ assert(rowBytes % 256 === 0, "rowBytes must be a multiple of 256");
175
+
176
+ this.#device.queue.writeTexture(
177
+ {
178
+ texture,
179
+ origin: { x: 0, y: 0, z: layerIndex },
180
+ },
181
+ atlas.rg8Bytes,
182
+ { bytesPerRow: rowBytes, rowsPerImage: h },
183
+ { width: w, height: h, depthOrArrayLayers: 1 },
184
+ );
185
+ } else {
186
+ this.#device.queue.copyExternalImageToTexture(
187
+ {
188
+ source: atlas.texture,
189
+ },
190
+ {
191
+ texture,
192
+ origin: [0, 0, layerIndex],
193
+ },
194
+ [atlas.texture.width, atlas.texture.height, 1],
195
+ );
196
+ }
197
+ }
198
+
199
+ createQuadShader(opts: QuadShaderCreationOpts): IBackendShader {
200
+ return new WebGPUQuadShader(
201
+ opts.label,
202
+ this,
203
+ opts.instanceCount,
204
+ opts.userCode,
205
+ opts.blendMode,
206
+ opts.atlasId,
207
+ );
208
+ }
209
+
210
+ createTextureAtlas(id: string, options?: TextureAtlasOptions): ITextureAtlas {
211
+ if (this.#atlases.has(id)) {
212
+ throw new Error(`Atlas "${id}" already exists`);
213
+ }
214
+
215
+ const format: TextureAtlasFormat = options?.format ?? "rgba8unorm";
216
+ const layers = options?.layers ?? this.limits.textureArrayLayers;
217
+ const size = options?.size ?? this.limits.textureSize;
218
+
219
+ const texture = this.#device.createTexture({
220
+ label: `Toodle Atlas "${id}"`,
221
+ size: [size, size, layers],
222
+ format,
223
+ usage:
224
+ GPUTextureUsage.TEXTURE_BINDING |
225
+ GPUTextureUsage.COPY_DST |
226
+ GPUTextureUsage.RENDER_ATTACHMENT,
227
+ });
228
+
229
+ const atlas: ITextureAtlas = { id, format, layers, size, handle: texture };
230
+ this.#atlases.set(id, atlas);
231
+ return atlas;
232
+ }
233
+
234
+ getTextureAtlas(id?: string): ITextureAtlas | null {
235
+ return this.#atlases.get(id ?? "default") ?? null;
236
+ }
237
+
238
+ destroyTextureAtlas(id: string): void {
239
+ const atlas = this.#atlases.get(id);
240
+ if (atlas) {
241
+ (atlas.handle as GPUTexture).destroy();
242
+ this.#atlases.delete(id);
243
+ }
244
+ }
245
+
246
+ resize(_width: number, _height: number): void {
247
+ // Canvas resize is handled automatically by WebGPU context
248
+ // The presentation size updates on next getCurrentTexture()
249
+
250
+ // Recreate ping-pong textures if postprocessing is active
251
+ if (this.#postprocess && this.#pingpong) {
252
+ this.#destroyPingPongTextures();
253
+ this.#createPingPongTextures();
254
+ }
255
+ }
256
+
257
+ destroy(): void {
258
+ this.#destroyPingPongTextures();
259
+ // Destroy all atlases
260
+ for (const atlas of this.#atlases.values()) {
261
+ (atlas.handle as GPUTexture).destroy();
262
+ }
263
+ this.#atlases.clear();
264
+ this.#device.destroy();
265
+ }
266
+
267
+ /**
268
+ * Set a post-processor for screen effects.
269
+ * Setting a post-processor will cause the main render to go to an offscreen texture.
270
+ * Note: Ping-pong textures are not destroyed when setting to null to avoid
271
+ * race conditions with in-flight command buffers. They are cleaned up on destroy().
272
+ */
273
+ setPostprocess(processor: PostProcess | null): void {
274
+ this.#postprocess = processor;
275
+ if (processor && !this.#pingpong) {
276
+ this.#createPingPongTextures();
277
+ }
278
+ // Don't destroy pingpong textures when setting to null - they may still be
279
+ // referenced by in-flight command buffers. They'll be cleaned up on destroy().
280
+ }
281
+
282
+ /**
283
+ * Get the current post-processor.
284
+ */
285
+ getPostprocess(): PostProcess | null {
286
+ return this.#postprocess;
287
+ }
288
+
289
+ #createPingPongTextures(): void {
290
+ const width = this.#canvas.width;
291
+ const height = this.#canvas.height;
292
+
293
+ const createTexture = (label: string) =>
294
+ this.#device.createTexture({
295
+ label,
296
+ size: [width, height],
297
+ format: this.#presentationFormat,
298
+ usage:
299
+ GPUTextureUsage.RENDER_ATTACHMENT |
300
+ GPUTextureUsage.TEXTURE_BINDING |
301
+ GPUTextureUsage.COPY_SRC,
302
+ });
303
+
304
+ this.#pingpong = [
305
+ createTexture("toodle pingpong 0"),
306
+ createTexture("toodle pingpong 1"),
307
+ ];
308
+ }
309
+
310
+ #destroyPingPongTextures(): void {
311
+ if (this.#pingpong) {
312
+ this.#pingpong[0].destroy();
313
+ this.#pingpong[1].destroy();
314
+ this.#pingpong = null;
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Get the GPU device for advanced operations.
320
+ */
321
+ get device(): GPUDevice {
322
+ return this.#device;
323
+ }
324
+
325
+ /**
326
+ * Get the canvas context.
327
+ */
328
+ get context(): GPUCanvasContext {
329
+ return this.#context;
330
+ }
331
+
332
+ /**
333
+ * Get the presentation format.
334
+ */
335
+ get presentationFormat(): GPUTextureFormat {
336
+ return this.#presentationFormat;
337
+ }
338
+
339
+ /**
340
+ * Get the current render pass encoder.
341
+ * Only available between startFrame() and endFrame().
342
+ */
343
+ get renderPass(): GPURenderPassEncoder {
344
+ assert(
345
+ this.#renderPass,
346
+ "No render pass available - did you call startFrame?",
347
+ );
348
+ return this.#renderPass;
349
+ }
350
+ }