wgsl-renderer 0.0.2 → 0.0.3

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/cjs/index.js CHANGED
@@ -7,13 +7,12 @@ var RenderPass = class {
7
7
  vertexBuffer;
8
8
  clearColor;
9
9
  blendMode;
10
- hasOutputTexture = false;
10
+ view;
11
+ format;
11
12
  passResources = [];
12
13
  device;
13
- originalBindGroupEntries;
14
14
  constructor(descriptor, device, format, layout) {
15
15
  this.device = device;
16
- this.originalBindGroupEntries = [...descriptor.bindGroupEntries || []];
17
16
  this.name = descriptor.name;
18
17
  this.clearColor = descriptor.clearColor || {
19
18
  r: 0,
@@ -21,7 +20,10 @@ var RenderPass = class {
21
20
  b: 0,
22
21
  a: 1
23
22
  };
24
- this.blendMode = descriptor.blendMode || "alpha";
23
+ this.blendMode = descriptor.blendMode || "none";
24
+ this.view = descriptor.view;
25
+ this.format = descriptor.format;
26
+ const actualFormat = descriptor.format || format;
25
27
  const module$1 = this.device.createShaderModule({ code: descriptor.shaderCode });
26
28
  this.vertexBuffer = this.device.createBuffer({
27
29
  size: 36,
@@ -40,11 +42,13 @@ var RenderPass = class {
40
42
  0
41
43
  ]);
42
44
  this.vertexBuffer.unmap();
45
+ const vertexEntryPoint = descriptor.entryPoints?.vertex || "vs_main";
46
+ const fragmentEntryPoint = descriptor.entryPoints?.fragment || "fs_main";
43
47
  this.pipeline = this.device.createRenderPipeline({
44
48
  layout,
45
49
  vertex: {
46
50
  module: module$1,
47
- entryPoint: "vs_main",
51
+ entryPoint: vertexEntryPoint,
48
52
  buffers: [{
49
53
  arrayStride: 12,
50
54
  attributes: [{
@@ -56,26 +60,21 @@ var RenderPass = class {
56
60
  },
57
61
  fragment: {
58
62
  module: module$1,
59
- entryPoint: "fs_main",
63
+ entryPoint: fragmentEntryPoint,
60
64
  targets: [{
61
- format,
65
+ format: actualFormat,
62
66
  blend: this.getBlendState()
63
67
  }]
64
68
  },
65
69
  primitive: { topology: "triangle-list" }
66
70
  });
67
- const bindGroupLayout = this.pipeline.getBindGroupLayout(0);
68
- this.bindGroup = this.device.createBindGroup({
69
- layout: bindGroupLayout,
70
- entries: this.originalBindGroupEntries
71
- });
71
+ this.bindGroup = null;
72
72
  }
73
73
  /**
74
74
  * Update bind group with new entries (e.g., after texture resize)
75
75
  */
76
76
  updateBindGroup(newEntries) {
77
77
  const bindGroupLayout = this.pipeline.getBindGroupLayout(0);
78
- this.originalBindGroupEntries = [...newEntries];
79
78
  this.bindGroup = this.device.createBindGroup({
80
79
  layout: bindGroupLayout,
81
80
  entries: newEntries
@@ -132,21 +131,17 @@ var TextureManager = class {
132
131
  device;
133
132
  width;
134
133
  height;
135
- oldTextures = [];
136
134
  constructor(device, width, height) {
137
135
  this.device = device;
138
136
  this.width = width;
139
137
  this.height = height;
140
138
  }
141
139
  createTexture(name, format) {
142
- if (this.textures.has(name)) {
143
- const oldTexture = this.textures.get(name);
144
- this.oldTextures.push(oldTexture);
145
- }
140
+ if (this.textures.has(name)) this.textures.get(name).destroy();
146
141
  const texture = this.device.createTexture({
147
142
  size: [this.width, this.height],
148
143
  format: format || "bgra8unorm",
149
- usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
144
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
150
145
  });
151
146
  this.textures.set(name, texture);
152
147
  return texture;
@@ -159,37 +154,13 @@ var TextureManager = class {
159
154
  this.width = width;
160
155
  this.height = height;
161
156
  this.textures.forEach((texture) => {
162
- this.oldTextures.push(texture);
163
- });
164
- this.textures.clear();
165
- }
166
- /**
167
- * Recreate a specific texture with current dimensions
168
- */
169
- recreateTexture(name, format) {
170
- const texture = this.device.createTexture({
171
- size: [this.width, this.height],
172
- format: format || "bgra8unorm",
173
- usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
174
- });
175
- this.textures.set(name, texture);
176
- return texture;
177
- }
178
- /**
179
- * Clean up old textures that are no longer needed
180
- * Call this after ensuring GPU work is complete
181
- */
182
- cleanupOldTextures() {
183
- this.oldTextures.forEach((texture) => {
184
157
  texture.destroy();
185
158
  });
186
- this.oldTextures.length = 0;
159
+ this.textures.clear();
187
160
  }
188
161
  destroy() {
189
162
  this.textures.forEach((texture) => texture.destroy());
190
163
  this.textures.clear();
191
- this.oldTextures.forEach((texture) => texture.destroy());
192
- this.oldTextures.length = 0;
193
164
  }
194
165
  getPixelSize() {
195
166
  return {
@@ -199,6 +170,30 @@ var TextureManager = class {
199
170
  }
200
171
  };
201
172
 
173
+ //#endregion
174
+ //#region src/PassTextureRef.ts
175
+ const PASS_TEXTURE_REF_SYMBOL = Symbol("PassTextureRef");
176
+ var PassTextureRef = class PassTextureRef {
177
+ [PASS_TEXTURE_REF_SYMBOL] = true;
178
+ passName;
179
+ constructor(passName) {
180
+ this.passName = passName;
181
+ }
182
+ static is(obj) {
183
+ return obj && typeof obj === "object" && PASS_TEXTURE_REF_SYMBOL in obj;
184
+ }
185
+ static fromGPUBindingResource(resource) {
186
+ if (this.is(resource)) return resource;
187
+ return null;
188
+ }
189
+ static create(passName) {
190
+ return new PassTextureRef(passName);
191
+ }
192
+ };
193
+ function isPassTextureRef(obj) {
194
+ return PassTextureRef.is(obj);
195
+ }
196
+
202
197
  //#endregion
203
198
  //#region src/index.ts
204
199
  var WGSLRenderer = class {
@@ -230,19 +225,17 @@ var WGSLRenderer = class {
230
225
  }
231
226
  async resize(width, height) {
232
227
  if (this.isResizing) return;
233
- const currentSize = this.textureManager.getPixelSize();
234
- if (currentSize.width === width && currentSize.height === height) return;
228
+ if (this.canvas.width === width && this.canvas.height === height) return;
235
229
  this.isResizing = true;
236
230
  this.canvas.width = width;
237
231
  this.canvas.height = height;
238
232
  const future = this.device.queue.onSubmittedWorkDone();
239
233
  future.catch(() => {
234
+ console.warn("GPU work submission failed during resize.");
240
235
  this.isResizing = false;
241
236
  });
242
237
  await future;
243
- this.textureManager.cleanupOldTextures();
244
238
  this.textureManager.resize(width, height);
245
- this.updateAllBindGroups();
246
239
  this.isResizing = false;
247
240
  }
248
241
  getContext() {
@@ -252,84 +245,79 @@ var WGSLRenderer = class {
252
245
  return this.device;
253
246
  }
254
247
  /**
255
- * Update bind groups for all passes after texture resize
248
+ * Get texture reference by pass name
249
+ * Returns a PassTextureRef that will resolve to the actual texture at render time
256
250
  */
257
- updateAllBindGroups() {
258
- this.recreatePassTextures();
259
- this.passes.forEach((pass, index) => {
260
- const finalBindGroupEntries = [];
261
- if (index >= 1) {
262
- const previousOutputTextureName = `pass_${index - 1}_output`;
263
- let previousOutput = this.textureManager.getTexture(previousOutputTextureName);
264
- if (!previousOutput) previousOutput = this.textureManager.recreateTexture(previousOutputTextureName, this.format);
265
- finalBindGroupEntries.push({
266
- binding: 0,
267
- resource: previousOutput.createView()
268
- });
269
- }
270
- if (pass.passResources) pass.passResources.forEach((resource, resourceIndex) => {
271
- finalBindGroupEntries.push({
272
- binding: resourceIndex + 1,
273
- resource
274
- });
275
- });
276
- pass.updateBindGroup(finalBindGroupEntries);
277
- });
251
+ getPassTexture(passName) {
252
+ if (!this.passes.find((pass) => pass.name === passName)) throw new Error(`Cannot find pass named '${passName}'. Available passes: [${this.passes.map((p) => p.name).join(", ")}]`);
253
+ return PassTextureRef.create(passName);
278
254
  }
279
255
  /**
280
- * Recreate all pass output textures after resize
256
+ * Resolve a PassTextureRef to actual GPUTextureView with validation
281
257
  */
282
- recreatePassTextures() {
283
- this.passes.forEach((pass, index) => {
284
- if (index < this.passes.length - 1 || pass.hasOutputTexture) {
285
- const textureName = `pass_${index}_output`;
286
- this.textureManager.recreateTexture(textureName, this.format);
287
- }
288
- });
258
+ resolveTextureRef(ref) {
259
+ const targetPassIndex = this.passes.findIndex((pass) => pass.name === ref.passName);
260
+ if (targetPassIndex === -1) throw new Error(`Cannot find pass named '${ref.passName}'. Available passes: [${this.passes.map((p) => p.name).join(", ")}]`);
261
+ const textureName = `pass_${targetPassIndex}_output`;
262
+ let texture = this.textureManager.getTexture(textureName);
263
+ if (!texture) texture = this.textureManager.createTexture(textureName, this.format);
264
+ return texture.createView();
265
+ }
266
+ /**
267
+ * Get pass by name
268
+ */
269
+ getPassByName(passName) {
270
+ return this.passes.find((pass) => pass.name === passName);
289
271
  }
290
272
  /**
291
273
  * Add a render pass to the multi-pass pipeline
292
274
  */
293
275
  addPass(descriptor) {
294
276
  const finalBindGroupEntries = [];
295
- const firstPass = this.passes.length === 0;
296
- if (!firstPass) {
297
- const previousOutput = this.getPassOutput(this.passes.length - 1);
298
- if (previousOutput) finalBindGroupEntries.push({
299
- binding: 0,
300
- resource: previousOutput.createView()
301
- });
302
- }
303
- descriptor.resources.forEach((resource, index) => {
277
+ descriptor.resources?.forEach((resource, index) => {
304
278
  finalBindGroupEntries.push({
305
- binding: index + (firstPass ? 0 : 1),
279
+ binding: index,
306
280
  resource
307
281
  });
308
282
  });
309
- const pass = new RenderPass({
283
+ const internalDescriptor = {
310
284
  name: descriptor.name,
311
285
  shaderCode: descriptor.shaderCode,
286
+ entryPoints: descriptor.entryPoints,
312
287
  clearColor: descriptor.clearColor,
313
288
  blendMode: descriptor.blendMode,
314
- bindGroupEntries: finalBindGroupEntries
315
- }, this.device, this.format, "auto");
316
- pass.passResources = [...descriptor.resources];
289
+ bindGroupEntries: finalBindGroupEntries,
290
+ view: descriptor.view,
291
+ format: descriptor.format
292
+ };
293
+ const pipelineFormat = descriptor.format || this.format;
294
+ const pass = new RenderPass(internalDescriptor, this.device, pipelineFormat, "auto");
295
+ pass.passResources = descriptor.resources ?? [];
317
296
  this.passes.push(pass);
318
- const currentPassIndex = this.passes.length - 1;
319
- const textureName = `pass_${currentPassIndex}_output`;
320
- this.textureManager.createTexture(textureName, this.format);
321
- this.passes[currentPassIndex].hasOutputTexture = true;
322
297
  }
323
298
  /**
324
- * Get the output texture of a specific pass
299
+ * Resolve resource to actual GPU binding resource
300
+ * Handles PassTextureRef by getting the current texture view with validation
325
301
  */
326
- getPassOutput(passIndex) {
327
- if (passIndex < 0 || passIndex >= this.passes.length) return;
328
- if (passIndex === this.passes.length - 1) {
329
- if (!this.passes[passIndex]?.hasOutputTexture) return;
330
- }
331
- const textureName = `pass_${passIndex}_output`;
332
- return this.textureManager.getTexture(textureName);
302
+ resolveResource(resource) {
303
+ if (isPassTextureRef(resource)) return this.resolveTextureRef(resource);
304
+ return resource;
305
+ }
306
+ /**
307
+ * Update bind groups to resolve current texture references
308
+ * Call this before rendering to ensure all PassTextureRef are resolved
309
+ */
310
+ updateBindGroups() {
311
+ this.passes.forEach((pass) => {
312
+ const finalBindGroupEntries = [];
313
+ pass.passResources.forEach((resource, index) => {
314
+ finalBindGroupEntries.push({
315
+ binding: index,
316
+ resource: this.resolveResource(resource)
317
+ });
318
+ });
319
+ pass.updateBindGroup(finalBindGroupEntries);
320
+ });
333
321
  }
334
322
  /**
335
323
  * Create a uniforms
@@ -386,22 +374,25 @@ var WGSLRenderer = class {
386
374
  }
387
375
  renderFrame() {
388
376
  if (this.passes.length === 0) return;
377
+ this.updateBindGroups();
389
378
  const commandEncoder = this.device.createCommandEncoder();
390
379
  for (let i = 0; i < this.passes.length; i++) {
391
380
  const pass = this.passes[i];
381
+ let loadOp = "load";
382
+ const isLast = i === this.passes.length - 1;
383
+ if (isLast) loadOp = "clear";
392
384
  let renderTarget;
393
- let loadOp = "clear";
394
- if (i === this.passes.length - 1) renderTarget = this.ctx.getCurrentTexture().createView();
385
+ if (pass.view) renderTarget = pass.view;
386
+ else if (isLast) renderTarget = this.ctx.getCurrentTexture().createView();
395
387
  else {
396
388
  const textureName = `pass_${i}_output`;
397
- const texture = this.textureManager.getTexture(textureName);
398
- if (!texture) continue;
389
+ let texture = this.textureManager.getTexture(textureName);
390
+ if (!texture) texture = this.textureManager.createTexture(textureName, "rgba16float");
399
391
  renderTarget = texture.createView();
400
- loadOp = "load";
401
392
  }
402
393
  const renderPass = commandEncoder.beginRenderPass({ colorAttachments: [{
403
394
  view: renderTarget,
404
- loadOp: i === 0 ? "clear" : loadOp,
395
+ loadOp,
405
396
  storeOp: "store",
406
397
  clearValue: pass.clearColor
407
398
  }] });
@@ -414,9 +405,11 @@ var WGSLRenderer = class {
414
405
  this.device.queue.submit([commandEncoder.finish()]);
415
406
  }
416
407
  loopRender(cb) {
417
- cb?.();
418
- this.renderFrame();
419
- this.animationFrameId = requestAnimationFrame(() => this.loopRender(cb));
408
+ this.animationFrameId = requestAnimationFrame((t) => {
409
+ cb?.(t);
410
+ this.renderFrame();
411
+ this.loopRender(cb);
412
+ });
420
413
  }
421
414
  stopLoop() {
422
415
  if (this.animationFrameId !== null) {