@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
@@ -23,8 +23,8 @@ export class TextNode extends SceneNode {
23
23
 
24
24
  const em2px = shader.font.lineHeight / (opts.fontSize ?? DEFAULT_FONT_SIZE);
25
25
 
26
- if (!opts.shrinkToFit && !opts.idealSize) {
27
- opts.idealSize = { width: width / em2px, height: height / em2px };
26
+ if (!opts.shrinkToFit && !opts.size) {
27
+ opts.size = { width: width / em2px, height: height / em2px };
28
28
  }
29
29
 
30
30
  super({
@@ -1,7 +1,8 @@
1
1
  import { WgslReflect } from "wgsl_reflect";
2
+ import type { IBackendShader } from "../backends/IBackendShader";
3
+ import type { WebGPUBackend } from "../backends/webgpu/WebGPUBackend";
4
+ import type { EngineUniform } from "../coreTypes/EngineUniform";
2
5
  import type { SceneNode } from "../scene/SceneNode";
3
- import type { EngineUniform } from "../shaders/EngineUniform";
4
- import type { IShader } from "../shaders/IShader";
5
6
  import type { FontPipeline } from "./FontPipeline";
6
7
  import type { MsdfFont } from "./MsdfFont";
7
8
  import { findLargestFontSize, measureText, shapeText } from "./shaping";
@@ -15,8 +16,10 @@ if (!struct) {
15
16
  }
16
17
  const textDescriptorInstanceSize = struct.size;
17
18
 
18
- export class TextShader implements IShader {
19
- #device: GPUDevice;
19
+ export class TextShader implements IBackendShader {
20
+ readonly label = "text";
21
+
22
+ #backend: WebGPUBackend;
20
23
  #pipeline: GPURenderPipeline;
21
24
  #bindGroups: GPUBindGroup[] = [];
22
25
  #font: MsdfFont;
@@ -30,13 +33,14 @@ export class TextShader implements IShader {
30
33
  #textBlockOffset = 0;
31
34
 
32
35
  constructor(
33
- device: GPUDevice,
36
+ backend: WebGPUBackend,
34
37
  pipeline: FontPipeline,
35
38
  font: MsdfFont,
36
- colorFormat: GPUTextureFormat,
39
+ _colorFormat: GPUTextureFormat,
37
40
  instanceCount: number,
38
41
  ) {
39
- this.#device = device;
42
+ this.#backend = backend;
43
+ const device = backend.device;
40
44
  this.#font = font;
41
45
  this.#pipeline = pipeline.pipeline;
42
46
  this.#maxCharCount = pipeline.maxCharCount;
@@ -102,7 +106,8 @@ export class TextShader implements IShader {
102
106
  this.#bindGroups.push(engineUniformsBindGroup);
103
107
  }
104
108
 
105
- startFrame(device: GPUDevice, uniform: EngineUniform): void {
109
+ startFrame(uniform: EngineUniform): void {
110
+ const device = this.#backend.device;
106
111
  device.queue.writeBuffer(
107
112
  this.#engineUniformsBuffer,
108
113
  0,
@@ -112,9 +117,10 @@ export class TextShader implements IShader {
112
117
  this.#textBlockOffset = 0;
113
118
  }
114
119
 
115
- processBatch(renderPass: GPURenderPassEncoder, nodes: SceneNode[]): number {
120
+ processBatch(nodes: SceneNode[]): number {
116
121
  if (nodes.length === 0) return 0;
117
122
 
123
+ const renderPass = this.#backend.renderPass;
118
124
  renderPass.setPipeline(this.#pipeline);
119
125
  for (let i = 0; i < this.#bindGroups.length; i++) {
120
126
  renderPass.setBindGroup(i, this.#bindGroups[i]);
@@ -178,7 +184,7 @@ export class TextShader implements IShader {
178
184
  );
179
185
 
180
186
  // Write instance data
181
- this.#device.queue.writeBuffer(
187
+ this.#backend.device.queue.writeBuffer(
182
188
  this.#descriptorBuffer,
183
189
  textDescriptorOffset * Float32Array.BYTES_PER_ELEMENT,
184
190
  this.#cpuDescriptorBuffer,
@@ -186,7 +192,7 @@ export class TextShader implements IShader {
186
192
  textDescriptorInstanceSize / Float32Array.BYTES_PER_ELEMENT,
187
193
  );
188
194
 
189
- this.#device.queue.writeBuffer(
195
+ this.#backend.device.queue.writeBuffer(
190
196
  this.#textBlockBuffer,
191
197
  this.#textBlockOffset * Float32Array.BYTES_PER_ELEMENT,
192
198
  this.#cpuTextBlockBuffer,
@@ -1,15 +1,15 @@
1
+ import type { IRenderBackend } from "../backends/IRenderBackend";
2
+ import { TextureComputeShader } from "../backends/webgpu/TextureComputeShader";
3
+ import type { WebGPUBackend } from "../backends/webgpu/WebGPUBackend";
1
4
  import type { Size } from "../coreTypes/Size";
2
5
  import type { Vec2 } from "../coreTypes/Vec2";
3
- import type { Limits } from "../limits";
4
6
  import { JumboQuadNode } from "../scene/JumboQuadNode";
5
7
  import { QuadNode } from "../scene/QuadNode";
6
8
  import type { SceneNode } from "../scene/SceneNode";
7
9
  import { FontPipeline } from "../text/FontPipeline";
8
10
  import { MsdfFont } from "../text/MsdfFont";
9
11
  import { TextShader } from "../text/TextShader";
10
- import { assert } from "../utils/mod";
11
12
  import { Bundles } from "./Bundles";
12
- import { TextureComputeShader } from "./TextureComputeShader";
13
13
  import type {
14
14
  AtlasBundleOpts,
15
15
  AtlasCoords,
@@ -26,51 +26,63 @@ export type FontId = string;
26
26
  export type AssetManagerOptions = {
27
27
  /** Existing Bundles instance to use for CPU-side storage. If not provided, a new one is created. */
28
28
  bundles?: Bundles;
29
- /** Texture format (default: "rgba8unorm") */
30
- format?: "rgba8unorm" | "rg8unorm";
29
+ /** Which texture atlas to use (default: "default") */
30
+ atlasId?: string;
31
31
  };
32
32
 
33
33
  export class AssetManager {
34
- readonly textureAtlas: GPUTexture;
35
34
  readonly bundles: Bundles;
36
- #device: GPUDevice;
37
- #presentationFormat: GPUTextureFormat;
35
+ #backend: IRenderBackend;
36
+ #atlasId: string;
38
37
  #fonts: Map<string, TextShader> = new Map();
39
- #cropComputeShader: TextureComputeShader;
40
- #limits: Limits;
38
+ #cropComputeShader: TextureComputeShader | null = null;
41
39
  #availableIndices: Set<number> = new Set();
42
40
 
43
- constructor(
44
- device: GPUDevice,
45
- presentationFormat: GPUTextureFormat,
46
- limits: Limits,
47
- options: AssetManagerOptions = {},
48
- ) {
49
- this.#device = device;
50
- this.#presentationFormat = presentationFormat;
51
- this.#limits = limits;
52
- this.bundles =
53
- options.bundles ?? new Bundles({ atlasSize: limits.textureSize });
54
- const format = options.format ?? "rgba8unorm";
55
- this.textureAtlas = device.createTexture({
56
- label: "Asset Manager Atlas Texture",
57
- size: [
58
- this.#limits.textureSize,
59
- this.#limits.textureSize,
60
- this.#limits.textureArrayLayers,
61
- ],
62
- format,
63
- usage:
64
- GPUTextureUsage.TEXTURE_BINDING |
65
- GPUTextureUsage.COPY_DST |
66
- GPUTextureUsage.RENDER_ATTACHMENT,
67
- });
68
- this.#cropComputeShader = TextureComputeShader.create(device);
41
+ constructor(backend: IRenderBackend, options: AssetManagerOptions = {}) {
42
+ this.#backend = backend;
43
+ this.#atlasId = options.atlasId ?? "default";
44
+
45
+ const atlas = backend.getTextureAtlas(this.#atlasId);
46
+ if (!atlas) {
47
+ throw new Error(`Atlas "${this.#atlasId}" not found`);
48
+ }
49
+
50
+ this.bundles = options.bundles ?? new Bundles({ atlasSize: atlas.size });
51
+
52
+ // Initialize compute shader only for WebGPU backend
53
+ if (backend.type === "webgpu") {
54
+ const device = (backend as WebGPUBackend).device;
55
+ this.#cropComputeShader = TextureComputeShader.create(device);
56
+ }
57
+
69
58
  this.#availableIndices = new Set(
70
- Array.from({ length: limits.textureArrayLayers }, (_, i) => i),
59
+ Array.from({ length: atlas.layers }, (_, i) => i),
71
60
  );
72
61
  }
73
62
 
63
+ /**
64
+ * Get the atlas ID this asset manager uses.
65
+ */
66
+ get atlasId(): string {
67
+ return this.#atlasId;
68
+ }
69
+
70
+ /**
71
+ * Get the GPU texture atlas. For WebGPU, returns GPUTexture.
72
+ * @deprecated Access via backend.getTextureAtlas(atlasId).handle instead
73
+ */
74
+ get textureAtlas(): GPUTexture {
75
+ return this.#backend.getTextureAtlas(this.#atlasId)!.handle as GPUTexture;
76
+ }
77
+
78
+ /**
79
+ * Get the atlas dimensions
80
+ */
81
+ get atlasSize(): Size {
82
+ const atlas = this.#backend.getTextureAtlas(this.#atlasId);
83
+ return { width: atlas!.size, height: atlas!.size };
84
+ }
85
+
74
86
  /**
75
87
  * True dimensions of a loaded texture, prior to any transparent pixel cropping.
76
88
  *
@@ -80,9 +92,10 @@ export class AssetManager {
80
92
  getSize(id: TextureId): Size {
81
93
  const coords = this.extra.getAtlasCoords(id);
82
94
  const originalScale = coords[0].uvScale;
95
+ // Use atlasSize instead of textureAtlas.width/height for WebGL2 compatibility
83
96
  return {
84
- width: originalScale.width * this.textureAtlas.width,
85
- height: originalScale.height * this.textureAtlas.height,
97
+ width: originalScale.width * this.atlasSize.width,
98
+ height: originalScale.height * this.atlasSize.height,
86
99
  };
87
100
  }
88
101
 
@@ -95,9 +108,10 @@ export class AssetManager {
95
108
  getCroppedSize(id: TextureId): Size {
96
109
  const scaledUvs = this.extra.getAtlasCoords(id)[0].uvScaleCropped;
97
110
  if (scaledUvs) {
111
+ // Use atlasSize instead of textureAtlas.width/height for WebGL2 compatibility
98
112
  return {
99
- width: scaledUvs.width * this.textureAtlas.width,
100
- height: scaledUvs.height * this.textureAtlas.height,
113
+ width: scaledUvs.width * this.atlasSize.width,
114
+ height: scaledUvs.height * this.atlasSize.height,
101
115
  };
102
116
  }
103
117
  return this.getSize(id);
@@ -165,12 +179,20 @@ export class AssetManager {
165
179
  *
166
180
  * Note: this will consume one texture atlas per texture.
167
181
  * For more efficient loading of multiple textures, consider {@link loadBundle}
182
+ *
183
+ * @throws Error if using WebGL backend (use registerBundle instead)
168
184
  */
169
185
  async loadTexture(
170
186
  id: TextureId,
171
187
  url: URL | ImageBitmap,
172
188
  options?: Partial<TextureBundleOpts>,
173
189
  ) {
190
+ if (this.#backend.type !== "webgpu") {
191
+ throw new Error(
192
+ "loadTexture is only supported with WebGPU backend. Use registerBundle with prebaked atlases instead.",
193
+ );
194
+ }
195
+
174
196
  const bitmap =
175
197
  url instanceof ImageBitmap ? url : await getBitmapFromUrl(url);
176
198
 
@@ -180,24 +202,25 @@ export class AssetManager {
180
202
  );
181
203
  const atlasIndex = this.extra.nextAvailableAtlasIndex();
182
204
 
183
- if (options?.cropTransparentPixels) {
205
+ if (options?.cropTransparentPixels && this.#cropComputeShader) {
184
206
  textureWrapper =
185
207
  await this.#cropComputeShader.processTexture(textureWrapper);
186
208
  }
187
209
 
188
210
  this.#copyTextureToAtlas(textureWrapper.texture, atlasIndex);
189
211
 
212
+ const { width: atlasWidth, height: atlasHeight } = this.atlasSize;
190
213
  const coords: AtlasCoords = {
191
214
  uvOffset: { x: 0, y: 0 },
192
215
  cropOffset: textureWrapper.cropOffset,
193
216
  uvScale: {
194
- width: textureWrapper.texture.width / this.textureAtlas.width,
195
- height: textureWrapper.texture.height / this.textureAtlas.height,
217
+ width: textureWrapper.texture.width / atlasWidth,
218
+ height: textureWrapper.texture.height / atlasHeight,
196
219
  },
197
220
  originalSize: textureWrapper.originalSize,
198
221
  uvScaleCropped: {
199
- width: textureWrapper.texture.width / this.textureAtlas.width,
200
- height: textureWrapper.texture.height / this.textureAtlas.height,
222
+ width: textureWrapper.texture.width / atlasWidth,
223
+ height: textureWrapper.texture.height / atlasHeight,
201
224
  },
202
225
  atlasIndex,
203
226
  };
@@ -228,7 +251,8 @@ export class AssetManager {
228
251
  await this.#registerBundleFromAtlases(bundleId, opts);
229
252
  }
230
253
 
231
- if (opts.autoLoad) {
254
+ const autoLoad = opts.autoLoad ?? true;
255
+ if (autoLoad) {
232
256
  await this.loadBundle(bundleId);
233
257
  }
234
258
  return bundleId;
@@ -291,23 +315,36 @@ export class AssetManager {
291
315
  * @param id - The id of the font to load
292
316
  * @param url - The url of the font to load
293
317
  * @param fallbackCharacter - The character to use as a fallback if the font does not contain a character to be rendererd
318
+ *
319
+ * @throws Error if using WebGL backend (fonts not supported in WebGL mode)
294
320
  */
295
321
  async loadFont(id: string, url: URL, fallbackCharacter = "_") {
322
+ if (this.#backend.type !== "webgpu") {
323
+ throw new Error(
324
+ "loadFont is only supported with WebGPU backend. Text rendering is not available in WebGL mode.",
325
+ );
326
+ }
327
+
328
+ const webgpuBackend = this.#backend as WebGPUBackend;
329
+ const device = webgpuBackend.device;
330
+ const presentationFormat = webgpuBackend.presentationFormat;
331
+ const limits = this.#backend.limits;
332
+
296
333
  const font = await MsdfFont.create(id, url);
297
334
  font.fallbackCharacter = fallbackCharacter;
298
335
  const fontPipeline = await FontPipeline.create(
299
- this.#device,
336
+ device,
300
337
  font,
301
- this.#presentationFormat,
302
- this.#limits.maxTextLength,
338
+ presentationFormat,
339
+ limits.maxTextLength,
303
340
  );
304
341
 
305
342
  const textShader = new TextShader(
306
- this.#device,
343
+ this.#backend as WebGPUBackend,
307
344
  fontPipeline,
308
345
  font,
309
- this.#presentationFormat,
310
- this.#limits.instanceCount,
346
+ presentationFormat,
347
+ limits.instanceCount,
311
348
  );
312
349
  this.#fonts.set(id, textShader);
313
350
  return id;
@@ -352,7 +389,8 @@ export class AssetManager {
352
389
  * @private
353
390
  */
354
391
  #createTextureFromImageBitmap(bitmap: ImageBitmap, name: string): GPUTexture {
355
- const texture = this.#device.createTexture({
392
+ const device = (this.#backend as WebGPUBackend).device;
393
+ const texture = device.createTexture({
356
394
  label: `${name} Intermediary Texture`,
357
395
  size: [bitmap.width, bitmap.height],
358
396
  format: "rgba8unorm",
@@ -363,7 +401,7 @@ export class AssetManager {
363
401
  GPUTextureUsage.RENDER_ATTACHMENT,
364
402
  });
365
403
 
366
- this.#device.queue.copyExternalImageToTexture(
404
+ device.queue.copyExternalImageToTexture(
367
405
  {
368
406
  source: bitmap,
369
407
  },
@@ -379,20 +417,27 @@ export class AssetManager {
379
417
  bundleId: BundleId,
380
418
  opts: TextureBundleOpts,
381
419
  ) {
420
+ if (this.#backend.type !== "webgpu") {
421
+ throw new Error(
422
+ "Dynamic texture bundle registration is only supported with WebGPU backend. Use prebaked atlases instead.",
423
+ );
424
+ }
425
+
426
+ const device = (this.#backend as WebGPUBackend).device;
382
427
  const images = new Map<string, TextureWithMetadata>();
383
428
 
384
- let networkLoadTime = 0;
429
+ let _networkLoadTime = 0;
385
430
  await Promise.all(
386
431
  Object.entries(opts.textures).map(async ([id, url]) => {
387
432
  const now = performance.now();
388
433
  const bitmap = await getBitmapFromUrl(url);
389
- networkLoadTime += performance.now() - now;
434
+ _networkLoadTime += performance.now() - now;
390
435
  let textureWrapper: TextureWithMetadata = this.#wrapBitmapToTexture(
391
436
  bitmap,
392
437
  id,
393
438
  );
394
439
 
395
- if (opts.cropTransparentPixels) {
440
+ if (opts.cropTransparentPixels && this.#cropComputeShader) {
396
441
  textureWrapper =
397
442
  await this.#cropComputeShader.processTexture(textureWrapper);
398
443
  }
@@ -402,8 +447,8 @@ export class AssetManager {
402
447
 
403
448
  const atlases = await packBitmapsToAtlas(
404
449
  images,
405
- this.#limits.textureSize,
406
- this.#device,
450
+ this.#backend.limits.textureSize,
451
+ device,
407
452
  );
408
453
 
409
454
  this.bundles.registerDynamicBundle(bundleId, atlases);
@@ -466,6 +511,8 @@ export class AssetManager {
466
511
  * @returns Usage stats for texture atlases
467
512
  */
468
513
  getAtlasUsage: () => {
514
+ const atlas = this.#backend.getTextureAtlas(this.#atlasId);
515
+ const totalLayers = atlas?.layers ?? 0;
469
516
  return {
470
517
  /**
471
518
  * The number of texture atlases that are currently unused
@@ -475,11 +522,11 @@ export class AssetManager {
475
522
  /**
476
523
  * The number of texture atlases that are currently in use.
477
524
  */
478
- used: this.#limits.textureArrayLayers - this.#availableIndices.size,
525
+ used: totalLayers - this.#availableIndices.size,
479
526
  /**
480
527
  * The total number of texture atlases that can be loaded.
481
528
  */
482
- total: this.#limits.textureArrayLayers,
529
+ total: totalLayers,
483
530
  };
484
531
  },
485
532
 
@@ -488,7 +535,9 @@ export class AssetManager {
488
535
  *
489
536
  */
490
537
  nextAvailableAtlasIndex: () => {
491
- for (let i = 0; i < this.#limits.textureArrayLayers; i++) {
538
+ const atlas = this.#backend.getTextureAtlas(this.#atlasId);
539
+ const totalLayers = atlas?.layers ?? 0;
540
+ for (let i = 0; i < totalLayers; i++) {
492
541
  if (this.#availableIndices.has(i)) {
493
542
  this.#availableIndices.delete(i);
494
543
  return i;
@@ -506,34 +555,8 @@ export class AssetManager {
506
555
  loadAtlas: async (atlas: CpuTextureAtlas) => {
507
556
  const atlasIndex = this.extra.nextAvailableAtlasIndex();
508
557
 
509
- if (atlas.rg8Bytes) {
510
- const { width: w, height: h } = {
511
- width: this.textureAtlas.width,
512
- height: this.textureAtlas.height,
513
- };
514
-
515
- // WebGPU requires 256-byte bytesPerRow
516
- const rowBytes = w * 2;
517
- assert(rowBytes % 256 === 0, "rowBytes must be a multiple of 256");
518
-
519
- this.#device.queue.writeTexture(
520
- { texture: this.textureAtlas, origin: { x: 0, y: 0, z: atlasIndex } },
521
- atlas.rg8Bytes,
522
- { bytesPerRow: rowBytes, rowsPerImage: h },
523
- { width: w, height: h, depthOrArrayLayers: 1 },
524
- );
525
- } else {
526
- this.#device.queue.copyExternalImageToTexture(
527
- {
528
- source: atlas.texture,
529
- },
530
- {
531
- texture: this.textureAtlas,
532
- origin: [0, 0, atlasIndex],
533
- },
534
- [atlas.texture.width, atlas.texture.height, 1],
535
- );
536
- }
558
+ // Delegate to backend for actual GPU upload
559
+ await this.#backend.uploadAtlas(atlas, atlasIndex, this.#atlasId);
537
560
 
538
561
  for (const [id, region] of atlas.textureRegions) {
539
562
  this.bundles.addTextureEntry(id, { ...region, atlasIndex });
@@ -568,7 +591,8 @@ export class AssetManager {
568
591
  }
569
592
 
570
593
  #copyTextureToAtlas(texture: GPUTexture, atlasIndex: number) {
571
- const copyEncoder: GPUCommandEncoder = this.#device.createCommandEncoder();
594
+ const device = (this.#backend as WebGPUBackend).device;
595
+ const copyEncoder: GPUCommandEncoder = device.createCommandEncoder();
572
596
  copyEncoder.copyTextureToTexture(
573
597
  {
574
598
  texture: texture,
@@ -583,12 +607,13 @@ export class AssetManager {
583
607
  [texture.width, texture.height, 1],
584
608
  );
585
609
 
586
- this.#device.queue.submit([copyEncoder.finish()]);
610
+ device.queue.submit([copyEncoder.finish()]);
587
611
  }
612
+
588
613
  /**
589
614
  * Destroy the texture atlas. This should free up ~4gb of gpu memory (and make all draw calls fail)
590
615
  */
591
616
  destroy() {
592
- this.textureAtlas.destroy();
617
+ this.#backend.destroy();
593
618
  }
594
619
  }
@@ -107,7 +107,8 @@ export type TextureBundleOpts = {
107
107
  */
108
108
  cropTransparentPixels?: boolean;
109
109
  /**
110
- * Whether the bundle should be loaded automatically on registration
110
+ * Whether the bundle should be loaded automatically on registration.
111
+ * @default true
111
112
  */
112
113
  autoLoad?: boolean;
113
114
  };
@@ -135,7 +136,8 @@ export type AtlasBundleOpts = {
135
136
  atlases: AtlasDef[];
136
137
 
137
138
  /**
138
- * Whether the bundle should be loaded automatically on registration
139
+ * Whether the bundle should be loaded automatically on registration.
140
+ * @default true
139
141
  */
140
142
  autoLoad?: boolean;
141
143
 
@@ -16,71 +16,6 @@ export async function getBitmapFromUrl(url: URL): Promise<ImageBitmap> {
16
16
  }
17
17
  }
18
18
 
19
- /**
20
- * Converts an image Blob to ImageData.
21
- *
22
- * @param blob - The Blob containing the image.
23
- * @returns A Promise resolving to the image as ImageData.
24
- */
25
- async function blobToImageData(blob: Blob) {
26
- const imageBitmap: ImageBitmap = await createImageBitmap(blob);
27
-
28
- const canvas = document.createElement("canvas");
29
- const ctx = canvas.getContext("2d");
30
-
31
- if (!ctx) {
32
- throw new Error("Failed to get 2D context from canvas");
33
- }
34
-
35
- canvas.width = imageBitmap.width;
36
- canvas.height = imageBitmap.height;
37
-
38
- ctx.drawImage(imageBitmap, 0, 0);
39
-
40
- const imageData: ImageData = ctx.getImageData(
41
- 0,
42
- 0,
43
- canvas.width,
44
- canvas.height,
45
- );
46
-
47
- imageBitmap.close();
48
-
49
- return imageData;
50
- }
51
-
52
- /**
53
- * Creates a checkerboard pattern ImageData in black and purple.
54
- *
55
- * @param width - Width of the image.
56
- * @param height - Height of the image.
57
- * @param tileSize - Size of each checker tile (default is 8).
58
- * @returns The generated ImageData object.
59
- */
60
- function createCheckerboardImageData(
61
- width: number,
62
- height: number,
63
- tileSize = 8,
64
- ): ImageData {
65
- const data = new Uint8ClampedArray(width * height * 4);
66
-
67
- const purple = [128, 0, 128, 255]; // #800080
68
- const black = [0, 0, 0, 255]; // #000000
69
-
70
- for (let y = 0; y < height; y++) {
71
- for (let x = 0; x < width; x++) {
72
- const usePurple =
73
- (Math.floor(x / tileSize) + Math.floor(y / tileSize)) % 2 === 0;
74
- const color = usePurple ? purple : black;
75
- const idx = (y * width + x) * 4;
76
-
77
- data.set(color, idx);
78
- }
79
- }
80
-
81
- return new ImageData(data, width, height);
82
- }
83
-
84
19
  export async function loadZip(
85
20
  zipUrl: URL,
86
21
  ): Promise<{ path: string; bitmap: ImageBitmap }[]> {
@@ -2,7 +2,7 @@ import {
2
2
  makeBindGroupLayoutDescriptors,
3
3
  makeShaderDataDefinitions,
4
4
  } from "webgpu-utils";
5
- import type { ShaderDescriptor } from "../shaders/ShaderDescriptor";
5
+ import type { ShaderDescriptor } from "../backends/webgpu/ShaderDescriptor";
6
6
  import { assert } from "./assert";
7
7
 
8
8
  // convenience functions to wrap verbose webgpu apis - not part of public api
@@ -1 +0,0 @@
1
- {"version":3,"file":"EngineUniform.d.ts","sourceRoot":"","sources":["../../src/shaders/EngineUniform.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,MAAM,MAAM,aAAa,GAAG;IAC1B,UAAU,EAAE,UAAU,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,oBAAoB,EAAE,IAAI,CAAC;CAG5B,CAAC"}
@@ -1,15 +0,0 @@
1
- import type { SceneNode } from "../scene/SceneNode";
2
- import type { EngineUniform } from "./EngineUniform";
3
- export interface IShader {
4
- startFrame: (device: GPUDevice, uniform: EngineUniform) => void;
5
- /**
6
- * Process a batch of nodes.
7
- *
8
- * @param renderPass - The render pass to use.
9
- * @param nodes - The nodes to process.
10
- * @returns The number of draw calls made.
11
- */
12
- processBatch: (renderPass: GPURenderPassEncoder, nodes: SceneNode[]) => number;
13
- endFrame: () => void;
14
- }
15
- //# sourceMappingURL=IShader.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"IShader.d.ts","sourceRoot":"","sources":["../../src/shaders/IShader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD,MAAM,WAAW,OAAO;IACtB,UAAU,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IAEhE;;;;;;OAMG;IACH,YAAY,EAAE,CACZ,UAAU,EAAE,oBAAoB,EAChC,KAAK,EAAE,SAAS,EAAE,KACf,MAAM,CAAC;IAEZ,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB"}
@@ -1,18 +0,0 @@
1
- import type { SceneNode } from "../scene/SceneNode";
2
- import type { AssetManager } from "../textures/AssetManager";
3
- import type { EngineUniform } from "./EngineUniform";
4
- import type { IShader } from "./IShader";
5
- export type QuadShaderOpts = {
6
- assetManager?: AssetManager;
7
- blendMode?: GPUBlendState;
8
- };
9
- export declare class QuadShader implements IShader {
10
- #private;
11
- label: string;
12
- code: string;
13
- startFrame(device: GPUDevice, uniform: EngineUniform): void;
14
- processBatch(renderPass: GPURenderPassEncoder, nodes: SceneNode[]): number;
15
- endFrame(): void;
16
- constructor(label: string, assetManager: AssetManager, device: GPUDevice, presentationFormat: GPUTextureFormat, userCode: string, instanceCount: number, blendMode?: GPUBlendState);
17
- }
18
- //# sourceMappingURL=QuadShader.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"QuadShader.d.ts","sourceRoot":"","sources":["../../src/shaders/QuadShader.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAQ7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASzC,MAAM,MAAM,cAAc,GAAG;IAC3B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,SAAS,CAAC,EAAE,aAAa,CAAC;CAC3B,CAAC;AAEF,qBAAa,UAAW,YAAW,OAAO;;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IAYb,UAAU,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,aAAa;IAiBpD,YAAY,CAAC,UAAU,EAAE,oBAAoB,EAAE,KAAK,EAAE,SAAS,EAAE;IA0DjE,QAAQ;gBAGN,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,YAAY,EAC1B,MAAM,EAAE,SAAS,EACjB,kBAAkB,EAAE,gBAAgB,EACpC,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,SAAS,CAAC,EAAE,aAAa;CA4G5B"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"ShaderDescriptor.d.ts","sourceRoot":"","sources":["../../src/shaders/ShaderDescriptor.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,CAAC;IACzB,kBAAkB,EAAE,MAAM,CAAC;CAC5B,CAAC"}
@@ -1,6 +0,0 @@
1
- export * from "./EngineUniform";
2
- export * from "./IShader";
3
- export * from "./postprocess/mod";
4
- export * from "./ShaderDescriptor";
5
- export * from "./samplers";
6
- //# sourceMappingURL=mod.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/shaders/mod.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,YAAY,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/shaders/parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAe,MAAM,cAAc,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,UAK/C;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,GACV,gBAAgB,CA4ClB;AAuED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,UAAU,EAClB,QAAQ,GAAE,iBAA8B,GACvC,qBAAqB,CAkDvB;AAED,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,QAaxD;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CAqB9D"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"blur.d.ts","sourceRoot":"","sources":["../../../src/shaders/postprocess/blur.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAInD,wBAAgB,IAAI,CAClB,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,SAAS,EACjB,UAAU,EAAE,KAAK,EACjB,kBAAkB,EAAE,gBAAgB,EACpC,QAAQ,EAAE,CAAC,UAAU,EAAE,UAAU,CAAC,QA2InC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../../src/shaders/postprocess/mod.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG;IACxB;;;;;;OAMG;IACH,OAAO,CACL,KAAK,EAAE,QAAQ,EACf,OAAO,EAAE,iBAAiB,EAC1B,QAAQ,EAAE,CAAC,UAAU,EAAE,UAAU,CAAC,EAClC,MAAM,EAAE,UAAU,GACjB,IAAI,CAAC;CACT,CAAC;AAEF,eAAO,MAAM,mBAAmB;+BACd,SAAS,KAAG,UAAU;0CAWX,SAAS,KAAG,qBAAqB;oCAOvC,SAAS,KAAG,eAAe;0CAuBrB,SAAS,KAAG,2BAA2B;CAY1D,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"samplers.d.ts","sourceRoot":"","sources":["../../src/shaders/samplers.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe,EAAE,oBAK7B,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,oBAK3B,CAAC"}