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