@bloopjs/toodle 0.0.104 → 0.1.2

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 (122) hide show
  1. package/dist/Toodle.d.ts +41 -19
  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 +7247 -6663
  45. package/dist/mod.js.map +27 -22
  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/text/TextShader.d.ts +8 -6
  53. package/dist/text/TextShader.d.ts.map +1 -1
  54. package/dist/textures/AssetManager.d.ts +21 -5
  55. package/dist/textures/AssetManager.d.ts.map +1 -1
  56. package/dist/textures/util.d.ts +0 -4
  57. package/dist/textures/util.d.ts.map +1 -1
  58. package/dist/utils/boilerplate.d.ts +1 -1
  59. package/dist/utils/boilerplate.d.ts.map +1 -1
  60. package/package.json +1 -2
  61. package/src/Toodle.ts +124 -156
  62. package/src/backends/IBackendShader.ts +52 -0
  63. package/src/backends/IRenderBackend.ts +118 -0
  64. package/src/backends/ITextureAtlas.ts +35 -0
  65. package/src/backends/detection.ts +46 -0
  66. package/src/backends/mod.ts +29 -0
  67. package/src/backends/webgl2/WebGLBackend.ts +256 -0
  68. package/src/backends/webgl2/WebGLQuadShader.ts +278 -0
  69. package/src/backends/webgl2/glsl/quad.glsl.ts +114 -0
  70. package/src/backends/webgl2/mod.ts +2 -0
  71. package/src/{textures → backends/webgpu}/TextureComputeShader.ts +2 -48
  72. package/src/backends/webgpu/WebGPUBackend.ts +350 -0
  73. package/src/{shaders/QuadShader.ts → backends/webgpu/WebGPUQuadShader.ts} +226 -170
  74. package/src/backends/webgpu/mod.ts +2 -0
  75. package/src/{shaders → backends/webgpu}/parser.ts +2 -2
  76. package/src/{shaders → backends/webgpu}/postprocess/blur.ts +2 -2
  77. package/src/{shaders → backends/webgpu}/postprocess/mod.ts +1 -1
  78. package/src/mod.ts +3 -2
  79. package/src/scene/Batcher.ts +3 -3
  80. package/src/scene/QuadNode.ts +6 -2
  81. package/src/scene/RenderComponent.ts +2 -2
  82. package/src/text/TextShader.ts +17 -11
  83. package/src/textures/AssetManager.ts +117 -93
  84. package/src/textures/util.ts +0 -92
  85. package/src/utils/boilerplate.ts +1 -1
  86. package/dist/shaders/EngineUniform.d.ts.map +0 -1
  87. package/dist/shaders/IShader.d.ts +0 -15
  88. package/dist/shaders/IShader.d.ts.map +0 -1
  89. package/dist/shaders/QuadShader.d.ts +0 -18
  90. package/dist/shaders/QuadShader.d.ts.map +0 -1
  91. package/dist/shaders/ShaderDescriptor.d.ts.map +0 -1
  92. package/dist/shaders/mod.d.ts +0 -6
  93. package/dist/shaders/mod.d.ts.map +0 -1
  94. package/dist/shaders/parser.d.ts.map +0 -1
  95. package/dist/shaders/postprocess/blur.d.ts.map +0 -1
  96. package/dist/shaders/postprocess/mod.d.ts.map +0 -1
  97. package/dist/shaders/samplers.d.ts.map +0 -1
  98. package/dist/shaders/wgsl/example.wgsl.d.ts.map +0 -1
  99. package/dist/shaders/wgsl/hello.wgsl.d.ts.map +0 -1
  100. package/dist/shaders/wgsl/helloInstanced.wgsl.d.ts.map +0 -1
  101. package/dist/shaders/wgsl/quad.wgsl.d.ts.map +0 -1
  102. package/dist/textures/TextureComputeShader.d.ts.map +0 -1
  103. package/dist/textures/pixel-scraping.wgsl.d.ts.map +0 -1
  104. package/src/shaders/IShader.ts +0 -20
  105. package/src/shaders/mod.ts +0 -5
  106. /package/dist/{shaders → backends/webgpu}/ShaderDescriptor.d.ts +0 -0
  107. /package/dist/{shaders → backends/webgpu}/parser.d.ts +0 -0
  108. /package/dist/{shaders → backends/webgpu}/samplers.d.ts +0 -0
  109. /package/dist/{shaders → backends/webgpu}/wgsl/example.wgsl.d.ts +0 -0
  110. /package/dist/{shaders → backends/webgpu}/wgsl/hello.wgsl.d.ts +0 -0
  111. /package/dist/{shaders → backends/webgpu}/wgsl/helloInstanced.wgsl.d.ts +0 -0
  112. /package/dist/{textures → backends/webgpu/wgsl}/pixel-scraping.wgsl.d.ts +0 -0
  113. /package/dist/{shaders → backends/webgpu}/wgsl/quad.wgsl.d.ts +0 -0
  114. /package/dist/{shaders → coreTypes}/EngineUniform.d.ts +0 -0
  115. /package/src/{shaders → backends/webgpu}/ShaderDescriptor.ts +0 -0
  116. /package/src/{shaders → backends/webgpu}/samplers.ts +0 -0
  117. /package/src/{shaders → backends/webgpu}/wgsl/example.wgsl.ts +0 -0
  118. /package/src/{shaders → backends/webgpu}/wgsl/hello.wgsl.ts +0 -0
  119. /package/src/{shaders → backends/webgpu}/wgsl/helloInstanced.wgsl.ts +0 -0
  120. /package/src/{textures → backends/webgpu/wgsl}/pixel-scraping.wgsl.ts +0 -0
  121. /package/src/{shaders → backends/webgpu}/wgsl/quad.wgsl.ts +0 -0
  122. /package/src/{shaders → coreTypes}/EngineUniform.ts +0 -0
@@ -4,134 +4,86 @@ import {
4
4
  type StructuredView,
5
5
  } from "webgpu-utils";
6
6
  import { WgslReflect } from "wgsl_reflect";
7
- import type { SceneNode } from "../scene/SceneNode";
8
- import type { AssetManager } from "../textures/AssetManager";
9
- import { assert } from "../utils/assert";
7
+ import type { EngineUniform } from "../../coreTypes/EngineUniform";
8
+ import type { SceneNode } from "../../scene/SceneNode";
9
+ import { assert } from "../../utils/assert";
10
10
  import {
11
- attachVertexInstanceBuffer,
12
11
  createGpuPipeline,
13
12
  getGpuPipelineDescriptor,
14
13
  setVertexInstanceBufferLayout,
15
- } from "../utils/boilerplate";
16
- import type { EngineUniform } from "./EngineUniform";
17
- import type { IShader } from "./IShader";
14
+ } from "../../utils/boilerplate";
15
+ import type { IBackendShader } from "../IBackendShader";
16
+ import type { BlendMode } from "../IRenderBackend";
17
+ import type { ITextureAtlas } from "../ITextureAtlas";
18
18
  import {
19
19
  codeWithLineNumbers,
20
20
  combineShaderCode,
21
21
  struct2BufferLayout,
22
22
  } from "./parser";
23
23
  import { pixelArtSampler, smoothSampler } from "./samplers";
24
+ import type { WebGPUBackend } from "./WebGPUBackend";
24
25
  import quadWgsl from "./wgsl/quad.wgsl";
25
26
 
26
- export type QuadShaderOpts = {
27
- assetManager?: AssetManager;
28
- blendMode?: GPUBlendState;
27
+ type InstanceData = {
28
+ cpuBuffer: Float32Array<ArrayBuffer>;
29
+ gpuBuffer: GPUBuffer;
30
+ bufferLayout: GPUVertexBufferLayout;
29
31
  };
30
32
 
31
- export class QuadShader implements IShader {
32
- label: string;
33
- code: string;
33
+ /**
34
+ * WebGPU implementation of quad shader for instanced rendering.
35
+ */
36
+ export class WebGPUQuadShader implements IBackendShader {
37
+ readonly label: string;
38
+ readonly code: string;
34
39
 
40
+ #backend: WebGPUBackend;
41
+ #atlas: ITextureAtlas;
35
42
  #uniformValues: StructuredView;
36
43
  #instanceData: InstanceData;
37
44
  #instanceIndex = 0;
38
- #instanceCount;
45
+ #instanceCount: number;
39
46
 
40
- #device: GPUDevice;
41
47
  #pipeline: GPURenderPipeline;
42
48
  #bindGroups: GPUBindGroup[] = [];
43
49
  #uniformBuffer: GPUBuffer;
44
50
 
45
- startFrame(device: GPUDevice, uniform: EngineUniform) {
46
- this.#instanceIndex = 0;
47
-
48
- this.#uniformValues.set(uniform);
49
- this.#uniformValues.set({
50
- viewProjection: uniform.viewProjectionMatrix,
51
- resolution: [uniform.resolution.width, uniform.resolution.height],
52
- });
53
-
54
- // Write view projection matrix to uniform buffer
55
- device.queue.writeBuffer(
56
- this.#uniformBuffer,
57
- 0,
58
- this.#uniformValues.arrayBuffer,
59
- );
60
- }
61
-
62
- processBatch(renderPass: GPURenderPassEncoder, nodes: SceneNode[]) {
63
- renderPass.setPipeline(this.#pipeline);
64
- const batchStartInstanceIndex = this.#instanceIndex;
65
-
66
- // Count for the number of instances in the buffer...
67
- let instanceCount = 0;
68
-
69
- if (nodes.length > this.#instanceCount) {
70
- throw new Error(
71
- `ToodleInstanceCap: ${nodes.length} instances enqueued, max is ${this.#instanceCount} for ${this.label} shader`,
72
- );
73
- }
74
-
75
- for (let i = 0; i < nodes.length; i++) {
76
- if (!this.#instanceData) {
77
- continue;
78
- }
79
- const instance = nodes[i];
80
- assert(instance.renderComponent, "instance has no render component");
81
- const floatOffset =
82
- ((batchStartInstanceIndex + instanceCount) *
83
- this.#instanceData.bufferLayout.arrayStride) /
84
- Float32Array.BYTES_PER_ELEMENT;
85
-
86
- instanceCount += instance.renderComponent.writeInstance(
87
- instance,
88
- this.#instanceData.cpuBuffer,
89
- floatOffset,
90
- );
91
- }
92
-
93
- if (this.#instanceData) {
94
- const byteOffset =
95
- batchStartInstanceIndex * this.#instanceData.bufferLayout.arrayStride;
96
- const byteLength =
97
- instanceCount * this.#instanceData.bufferLayout.arrayStride;
98
-
99
- this.#device.queue.writeBuffer(
100
- this.#instanceData.gpuBuffer,
101
- byteOffset,
102
- this.#instanceData.cpuBuffer,
103
- byteOffset / Float32Array.BYTES_PER_ELEMENT,
104
- byteLength / Float32Array.BYTES_PER_ELEMENT,
105
- );
106
-
107
- attachVertexInstanceBuffer(renderPass, this.#instanceData.gpuBuffer);
108
-
109
- for (let i = 0; i < this.#bindGroups.length; i++) {
110
- renderPass.setBindGroup(i, this.#bindGroups[i]);
111
- }
112
- }
113
-
114
- this.#instanceIndex += instanceCount;
115
-
116
- renderPass.draw(4, instanceCount, 0, batchStartInstanceIndex);
117
- return 1;
118
- }
119
-
120
- endFrame() {}
121
-
122
51
  constructor(
123
52
  label: string,
124
- assetManager: AssetManager,
125
- device: GPUDevice,
126
- presentationFormat: GPUTextureFormat,
127
- userCode: string,
53
+ backend: WebGPUBackend,
128
54
  instanceCount: number,
129
- blendMode?: GPUBlendState,
55
+ userCode?: string,
56
+ blendMode?: BlendMode,
57
+ atlasId?: string,
130
58
  ) {
59
+ const atlas = backend.getTextureAtlas(atlasId ?? "default");
60
+ if (!atlas) {
61
+ throw new Error(`Atlas "${atlasId ?? "default"}" not found`);
62
+ }
63
+ this.#atlas = atlas;
131
64
  this.label = label;
65
+ this.#backend = backend;
66
+ this.#instanceCount = instanceCount;
67
+
68
+ const device = backend.device;
69
+ const presentationFormat = backend.presentationFormat;
132
70
 
133
71
  // Combine user code with base quad shader code
134
- const shaderDescriptor = combineShaderCode(label, quadWgsl, userCode);
72
+ const effectiveUserCode =
73
+ userCode ??
74
+ /*wgsl*/ `
75
+ @fragment
76
+ fn frag(vertex: VertexOutput) -> @location(0) vec4f {
77
+ let color = default_fragment_shader(vertex, linearSampler);
78
+ return color;
79
+ }
80
+ `;
81
+
82
+ const shaderDescriptor = combineShaderCode(
83
+ label,
84
+ quadWgsl,
85
+ effectiveUserCode,
86
+ );
135
87
 
136
88
  // Create shader module from combined code
137
89
  let module: GPUShaderModule;
@@ -149,18 +101,20 @@ export class QuadShader implements IShader {
149
101
  this.code = shaderDescriptor.code;
150
102
 
151
103
  // Create blend state
152
- const blend = blendMode ?? {
153
- color: {
154
- srcFactor: "src-alpha",
155
- dstFactor: "one-minus-src-alpha",
156
- operation: "add",
157
- },
158
- alpha: {
159
- srcFactor: "one",
160
- dstFactor: "one-minus-src-alpha",
161
- operation: "add",
162
- },
163
- };
104
+ const blend = blendMode
105
+ ? convertBlendMode(blendMode)
106
+ : {
107
+ color: {
108
+ srcFactor: "src-alpha" as GPUBlendFactor,
109
+ dstFactor: "one-minus-src-alpha" as GPUBlendFactor,
110
+ operation: "add" as GPUBlendOperation,
111
+ },
112
+ alpha: {
113
+ srcFactor: "one" as GPUBlendFactor,
114
+ dstFactor: "one-minus-src-alpha" as GPUBlendFactor,
115
+ operation: "add" as GPUBlendOperation,
116
+ },
117
+ };
164
118
 
165
119
  // Create instance data from shader code
166
120
  const ast = new WgslReflect(shaderDescriptor.code);
@@ -208,12 +162,6 @@ export class QuadShader implements IShader {
208
162
  pipelineDescriptor,
209
163
  );
210
164
 
211
- // Reference texture atlas
212
- const textureAtlas = assetManager.textureAtlas;
213
-
214
- // Store device
215
- this.#device = device;
216
-
217
165
  // Create uniform buffer for engine uniforms (mat3x3 viewProjection)
218
166
  const defs = makeShaderDataDefinitions(this.code);
219
167
  this.#uniformValues = makeStructuredView(defs.uniforms.engineUniform);
@@ -224,65 +172,173 @@ export class QuadShader implements IShader {
224
172
  });
225
173
 
226
174
  // Create bind groups
227
- this.#bindGroups = setQuadBindGroups(
228
- label,
229
- device,
230
- this.#pipeline,
231
- textureAtlas,
175
+ this.#bindGroups = this.#createBindGroups(device);
176
+ }
177
+
178
+ startFrame(uniform: EngineUniform): void {
179
+ this.#instanceIndex = 0;
180
+
181
+ this.#uniformValues.set(uniform);
182
+ this.#uniformValues.set({
183
+ viewProjection: uniform.viewProjectionMatrix,
184
+ resolution: [uniform.resolution.width, uniform.resolution.height],
185
+ });
186
+
187
+ // Write view projection matrix to uniform buffer
188
+ const device = this.#backend.device;
189
+ device.queue.writeBuffer(
232
190
  this.#uniformBuffer,
191
+ 0,
192
+ this.#uniformValues.arrayBuffer,
233
193
  );
194
+ }
234
195
 
235
- this.#instanceCount = instanceCount;
196
+ processBatch(nodes: SceneNode[]): number {
197
+ const device = this.#backend.device;
198
+ const renderPass = this.#backend.renderPass;
199
+
200
+ renderPass.setPipeline(this.#pipeline);
201
+ const batchStartInstanceIndex = this.#instanceIndex;
202
+
203
+ let instanceCount = 0;
204
+
205
+ if (nodes.length > this.#instanceCount) {
206
+ throw new Error(
207
+ `ToodleInstanceCap: ${nodes.length} instances enqueued, max is ${this.#instanceCount} for ${this.label} shader`,
208
+ );
209
+ }
210
+
211
+ for (let i = 0; i < nodes.length; i++) {
212
+ if (!this.#instanceData) {
213
+ continue;
214
+ }
215
+ const instance = nodes[i];
216
+ assert(instance.renderComponent, "instance has no render component");
217
+ const floatOffset =
218
+ ((batchStartInstanceIndex + instanceCount) *
219
+ this.#instanceData.bufferLayout.arrayStride) /
220
+ Float32Array.BYTES_PER_ELEMENT;
221
+
222
+ instanceCount += instance.renderComponent.writeInstance(
223
+ instance,
224
+ this.#instanceData.cpuBuffer,
225
+ floatOffset,
226
+ );
227
+ }
228
+
229
+ if (this.#instanceData) {
230
+ const byteOffset =
231
+ batchStartInstanceIndex * this.#instanceData.bufferLayout.arrayStride;
232
+ const byteLength =
233
+ instanceCount * this.#instanceData.bufferLayout.arrayStride;
234
+
235
+ device.queue.writeBuffer(
236
+ this.#instanceData.gpuBuffer,
237
+ byteOffset,
238
+ this.#instanceData.cpuBuffer,
239
+ byteOffset / Float32Array.BYTES_PER_ELEMENT,
240
+ byteLength / Float32Array.BYTES_PER_ELEMENT,
241
+ );
242
+
243
+ renderPass.setVertexBuffer(0, this.#instanceData.gpuBuffer);
244
+
245
+ for (let i = 0; i < this.#bindGroups.length; i++) {
246
+ renderPass.setBindGroup(i, this.#bindGroups[i]);
247
+ }
248
+ }
249
+
250
+ this.#instanceIndex += instanceCount;
251
+
252
+ renderPass.draw(4, instanceCount, 0, batchStartInstanceIndex);
253
+ return 1;
254
+ }
255
+
256
+ endFrame(): void {
257
+ // Nothing to do
236
258
  }
237
- }
238
259
 
239
- function setQuadBindGroups(
240
- label: string,
241
- device: GPUDevice,
242
- pipeline: GPURenderPipeline,
243
- textureAtlas: GPUTexture,
244
- buffer: GPUBuffer,
245
- ) {
246
- const bindGroup = device.createBindGroup({
247
- label: `${label} engine bind group`,
248
- layout: pipeline.getBindGroupLayout(0),
249
- entries: [
250
- {
251
- binding: 0,
252
- resource: {
253
- buffer,
260
+ #createBindGroups(device: GPUDevice): GPUBindGroup[] {
261
+ const textureAtlas = this.#atlas.handle as GPUTexture;
262
+
263
+ const bindGroup = device.createBindGroup({
264
+ label: `${this.label} engine bind group`,
265
+ layout: this.#pipeline.getBindGroupLayout(0),
266
+ entries: [
267
+ {
268
+ binding: 0,
269
+ resource: {
270
+ buffer: this.#uniformBuffer,
271
+ },
254
272
  },
255
- },
256
- {
257
- binding: 1,
258
- resource: device.createSampler(smoothSampler),
259
- },
260
- {
261
- binding: 2,
262
- resource: device.createSampler(pixelArtSampler),
263
- },
264
- ],
265
- });
266
-
267
- const atlasBindGroup = device.createBindGroup({
268
- label: `${label} atlas bind group`,
269
- layout: pipeline.getBindGroupLayout(1),
270
- entries: [
271
- {
272
- binding: 0,
273
- resource: textureAtlas.createView({
274
- dimension: "2d-array",
275
- arrayLayerCount: textureAtlas.depthOrArrayLayers,
276
- }),
277
- },
278
- ],
279
- });
280
-
281
- return [bindGroup, atlasBindGroup];
273
+ {
274
+ binding: 1,
275
+ resource: device.createSampler(smoothSampler),
276
+ },
277
+ {
278
+ binding: 2,
279
+ resource: device.createSampler(pixelArtSampler),
280
+ },
281
+ ],
282
+ });
283
+
284
+ const atlasBindGroup = device.createBindGroup({
285
+ label: `${this.label} atlas bind group`,
286
+ layout: this.#pipeline.getBindGroupLayout(1),
287
+ entries: [
288
+ {
289
+ binding: 0,
290
+ resource: textureAtlas.createView({
291
+ dimension: "2d-array",
292
+ arrayLayerCount: textureAtlas.depthOrArrayLayers,
293
+ }),
294
+ },
295
+ ],
296
+ });
297
+
298
+ return [bindGroup, atlasBindGroup];
299
+ }
282
300
  }
283
301
 
284
- type InstanceData = {
285
- cpuBuffer: Float32Array<ArrayBuffer>;
286
- gpuBuffer: GPUBuffer;
287
- bufferLayout: GPUVertexBufferLayout;
288
- };
302
+ function convertBlendMode(mode: BlendMode): GPUBlendState {
303
+ const convertFactor = (f: string): GPUBlendFactor => {
304
+ const map: Record<string, GPUBlendFactor> = {
305
+ one: "one",
306
+ zero: "zero",
307
+ "src-alpha": "src-alpha",
308
+ "one-minus-src-alpha": "one-minus-src-alpha",
309
+ "dst-alpha": "dst-alpha",
310
+ "one-minus-dst-alpha": "one-minus-dst-alpha",
311
+ };
312
+ const result = map[f];
313
+ if (!result) {
314
+ throw new Error(`Unknown blend factor: ${f}`);
315
+ }
316
+ return result;
317
+ };
318
+
319
+ const convertOp = (o: string): GPUBlendOperation => {
320
+ const map: Record<string, GPUBlendOperation> = {
321
+ add: "add",
322
+ subtract: "subtract",
323
+ "reverse-subtract": "reverse-subtract",
324
+ };
325
+ const result = map[o];
326
+ if (!result) {
327
+ throw new Error(`Unknown blend operation: ${o}`);
328
+ }
329
+ return result;
330
+ };
331
+
332
+ return {
333
+ color: {
334
+ srcFactor: convertFactor(mode.color.srcFactor),
335
+ dstFactor: convertFactor(mode.color.dstFactor),
336
+ operation: convertOp(mode.color.operation),
337
+ },
338
+ alpha: {
339
+ srcFactor: convertFactor(mode.alpha.srcFactor),
340
+ dstFactor: convertFactor(mode.alpha.dstFactor),
341
+ operation: convertOp(mode.alpha.operation),
342
+ },
343
+ };
344
+ }
@@ -0,0 +1,2 @@
1
+ export { WebGPUBackend, type WebGPUBackendOptions } from "./WebGPUBackend";
2
+ export { WebGPUQuadShader } from "./WebGPUQuadShader";
@@ -120,7 +120,7 @@ function findStartingLocation(struct: StructInfo) {
120
120
  (attr) => attr.name === "location",
121
121
  );
122
122
  if (locationAttr) {
123
- const location = Number.parseInt(locationAttr.value as string);
123
+ const location = Number.parseInt(locationAttr.value as string, 10);
124
124
  startingLocation = Math.max(startingLocation, location);
125
125
  }
126
126
  }
@@ -164,7 +164,7 @@ export function struct2BufferLayout(
164
164
  throw new Error(`Location attribute is an array for member: ${m.name}`);
165
165
  }
166
166
 
167
- const shaderLocation = Number.parseInt(location);
167
+ const shaderLocation = Number.parseInt(location, 10);
168
168
  if (Number.isNaN(shaderLocation)) {
169
169
  throw new Error(
170
170
  `Invalid location attribute: ${location} for member: ${m.name}`,
@@ -1,4 +1,4 @@
1
- import type { Color } from "../../coreTypes/Color";
1
+ import type { Color } from "../../../coreTypes/Color";
2
2
  import { PostProcessDefaults } from "./mod";
3
3
 
4
4
  // example of a blur post-process effect using a two-pass Gaussian blur
@@ -16,7 +16,7 @@ export function blur(
16
16
  const lines = weights.map((w) => w.toFixed(7)).join(", ");
17
17
  const wgslFragment = `let weights = array<f32, ${weights.length}>(${lines});`;
18
18
 
19
- const brightPipeline = device.createRenderPipeline({
19
+ const _brightPipeline = device.createRenderPipeline({
20
20
  ...PostProcessDefaults.pipelineDescriptor(device),
21
21
  label: "toodle post process - brightness pass",
22
22
  fragment: {
@@ -26,7 +26,7 @@ export const PostProcessDefaults = {
26
26
  });
27
27
  },
28
28
 
29
- vertexBufferLayout(device: GPUDevice): GPUVertexBufferLayout {
29
+ vertexBufferLayout(_device: GPUDevice): GPUVertexBufferLayout {
30
30
  return {
31
31
  arrayStride: 4 * 4,
32
32
  attributes: [{ shaderLocation: 0, offset: 0, format: "float32x2" }],
package/src/mod.ts CHANGED
@@ -4,8 +4,9 @@ export type * from "./coreTypes/mod";
4
4
 
5
5
  export type * from "./scene/QuadNode";
6
6
  export type * from "./scene/SceneNode";
7
- export type * from "./shaders/QuadShader";
8
7
  export type * from "./text/TextNode";
8
+ export type * from "./backends/IBackendShader";
9
+ export type * from "./backends/IRenderBackend";
9
10
 
10
11
  export * from "./limits";
11
12
  export * from "./Toodle";
@@ -14,7 +15,7 @@ export * as Colors from "./colors/mod";
14
15
  export * as GfxMath from "./math/mod";
15
16
  export * as Scene from "./scene/mod";
16
17
  export * as Screen from "./screen/mod";
17
- export * as Shaders from "./shaders/mod";
18
+ export * as Backends from "./backends/mod";
18
19
  export * as Text from "./text/mod";
19
20
  export * as Textures from "./textures/mod";
20
21
  export * as Utils from "./utils/mod";
@@ -1,4 +1,4 @@
1
- import type { IShader } from "../shaders/IShader";
1
+ import type { IBackendShader } from "../backends/IBackendShader";
2
2
  import type { SceneNode } from "./SceneNode";
3
3
 
4
4
  type Layer = {
@@ -7,7 +7,7 @@ type Layer = {
7
7
  };
8
8
 
9
9
  export type Pipeline<TNode extends SceneNode = SceneNode> = {
10
- shader: IShader;
10
+ shader: IBackendShader;
11
11
  nodes: TNode[];
12
12
  };
13
13
 
@@ -49,7 +49,7 @@ export class Batcher {
49
49
  return layer;
50
50
  }
51
51
 
52
- #findOrCreatePipeline(layer: Layer, shader: IShader) {
52
+ #findOrCreatePipeline(layer: Layer, shader: IBackendShader) {
53
53
  let pipeline = layer.pipelines.find((p) => p.shader === shader);
54
54
  if (!pipeline) {
55
55
  pipeline = { shader, nodes: [] };
@@ -1,8 +1,8 @@
1
1
  import { type Mat3, mat3 } from "wgpu-matrix";
2
+ import type { IBackendShader } from "../backends/IBackendShader";
2
3
  import type { Color } from "../coreTypes/Color";
3
4
  import type { Size } from "../coreTypes/Size";
4
5
  import type { Vec2 } from "../coreTypes/Vec2";
5
- import type { IShader } from "../shaders/IShader";
6
6
  import type { Toodle } from "../Toodle";
7
7
  import type { AssetManager, TextureId } from "../textures/AssetManager";
8
8
  import type { AtlasCoords, TexelRegion } from "../textures/types";
@@ -144,6 +144,10 @@ export class QuadNode extends SceneNode {
144
144
  return size;
145
145
  }
146
146
 
147
+ set size(val: Size) {
148
+ super.size = val;
149
+ }
150
+
147
151
  /**
148
152
  * This is the final model matrix used to render the quad, which
149
153
  * may differ from the matrix passed down to the node's children.
@@ -302,7 +306,7 @@ export type QuadOptions = NodeOptions & {
302
306
 
303
307
  assetManager?: AssetManager;
304
308
 
305
- shader?: IShader;
309
+ shader?: IBackendShader;
306
310
  writeInstance?: (array: Float32Array, offset: number) => void;
307
311
  color?: Color;
308
312
  /**
@@ -1,8 +1,8 @@
1
- import type { IShader } from "../shaders/IShader";
1
+ import type { IBackendShader } from "../backends/IBackendShader";
2
2
  import type { SceneNode } from "./SceneNode";
3
3
 
4
4
  export type RenderComponent = {
5
- shader: IShader;
5
+ shader: IBackendShader;
6
6
  data?: Float32Array;
7
7
 
8
8
  /**
@@ -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,