wgsl-renderer 0.0.3 → 0.0.5

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/README.zh-CN.md CHANGED
@@ -104,7 +104,7 @@ renderer.loopRender((t) => {
104
104
  uniforms.apply() // 应用到GPU
105
105
  })
106
106
 
107
- // 或者手动改执行官单帧渲染
107
+ // 或者手动执行单帧渲染
108
108
  renderer.renderFrame()
109
109
  ```
110
110
 
package/dist/cjs/index.js CHANGED
@@ -311,7 +311,7 @@ var WGSLRenderer = class {
311
311
  this.passes.forEach((pass) => {
312
312
  const finalBindGroupEntries = [];
313
313
  pass.passResources.forEach((resource, index) => {
314
- finalBindGroupEntries.push({
314
+ if (resource) finalBindGroupEntries.push({
315
315
  binding: index,
316
316
  resource: this.resolveResource(resource)
317
317
  });
@@ -355,7 +355,11 @@ var WGSLRenderer = class {
355
355
  console.error("Failed to load texture:", err);
356
356
  });
357
357
  const res = await resp;
358
- const imgBitmap = await createImageBitmap(await res.blob());
358
+ const future = createImageBitmap(await res.blob());
359
+ future.catch((err) => {
360
+ console.error("Failed to load texture:", err);
361
+ });
362
+ const imgBitmap = await future;
359
363
  const texture = this.device.createTexture({
360
364
  size: [
361
365
  imgBitmap.width,
@@ -1,4 +1,20 @@
1
+ //#region src/PassTextureRef.d.ts
2
+ declare const PASS_TEXTURE_REF_SYMBOL: unique symbol;
3
+ declare class PassTextureRef {
4
+ readonly [PASS_TEXTURE_REF_SYMBOL] = true;
5
+ readonly passName: string;
6
+ constructor(passName: string);
7
+ static is(obj: any): obj is PassTextureRef;
8
+ static fromGPUBindingResource(resource: GPUBindingResource): PassTextureRef | null;
9
+ static create(passName: string): PassTextureRef;
10
+ }
11
+ //#endregion
1
12
  //#region src/RenderPass.d.ts
13
+ type BandingResource = GPUBindingResource | PassTextureRef;
14
+ type BindingEntry = {
15
+ binding: number;
16
+ resource: BandingResource;
17
+ };
2
18
  interface RenderPassOptions {
3
19
  name: string;
4
20
  shaderCode: string;
@@ -13,7 +29,7 @@ interface RenderPassOptions {
13
29
  a: number;
14
30
  };
15
31
  blendMode?: 'additive' | 'alpha' | 'multiply' | 'none';
16
- resources: GPUBindingResource[];
32
+ resources?: BandingResource[];
17
33
  view?: GPUTextureView;
18
34
  format?: GPUTextureFormat;
19
35
  }
@@ -31,7 +47,7 @@ interface InternalRenderPassDescriptor {
31
47
  a: number;
32
48
  };
33
49
  blendMode?: 'additive' | 'alpha' | 'multiply' | 'none';
34
- bindGroupEntries: GPUBindGroupEntry[];
50
+ bindGroupEntries: BindingEntry[];
35
51
  view?: GPUTextureView;
36
52
  format?: GPUTextureFormat;
37
53
  }
@@ -49,27 +65,16 @@ declare class RenderPass {
49
65
  blendMode: 'additive' | 'alpha' | 'multiply' | 'none';
50
66
  view?: GPUTextureView;
51
67
  format?: GPUTextureFormat;
52
- passResources: GPUBindingResource[];
68
+ passResources: BandingResource[];
53
69
  private device;
54
70
  constructor(descriptor: InternalRenderPassDescriptor, device: GPUDevice, format: GPUTextureFormat, layout: GPUPipelineLayout | 'auto');
55
71
  /**
56
72
  * Update bind group with new entries (e.g., after texture resize)
57
73
  */
58
- updateBindGroup(newEntries: GPUBindGroupEntry[]): void;
74
+ updateBindGroup(newEntries: BindingEntry[]): void;
59
75
  private getBlendState;
60
76
  }
61
77
  //#endregion
62
- //#region src/PassTextureRef.d.ts
63
- declare const PASS_TEXTURE_REF_SYMBOL: unique symbol;
64
- declare class PassTextureRef {
65
- readonly [PASS_TEXTURE_REF_SYMBOL] = true;
66
- readonly passName: string;
67
- constructor(passName: string);
68
- static is(obj: any): obj is PassTextureRef;
69
- static fromGPUBindingResource(resource: GPUBindingResource): PassTextureRef | null;
70
- static create(passName: string): PassTextureRef;
71
- }
72
- //#endregion
73
78
  //#region src/index.d.ts
74
79
  interface MultiPassDescriptor {
75
80
  passes: RenderPassOptions[];
@@ -140,7 +145,7 @@ declare class WGSLRenderer {
140
145
  }>;
141
146
  renderFrame(): void;
142
147
  loopRender(cb?: {
143
- (t?: number): void;
148
+ (t: number): void;
144
149
  }): void;
145
150
  stopLoop(): void;
146
151
  }
package/dist/esm/index.js CHANGED
@@ -310,7 +310,7 @@ var WGSLRenderer = class {
310
310
  this.passes.forEach((pass) => {
311
311
  const finalBindGroupEntries = [];
312
312
  pass.passResources.forEach((resource, index) => {
313
- finalBindGroupEntries.push({
313
+ if (resource) finalBindGroupEntries.push({
314
314
  binding: index,
315
315
  resource: this.resolveResource(resource)
316
316
  });
@@ -354,7 +354,11 @@ var WGSLRenderer = class {
354
354
  console.error("Failed to load texture:", err);
355
355
  });
356
356
  const res = await resp;
357
- const imgBitmap = await createImageBitmap(await res.blob());
357
+ const future = createImageBitmap(await res.blob());
358
+ future.catch((err) => {
359
+ console.error("Failed to load texture:", err);
360
+ });
361
+ const imgBitmap = await future;
358
362
  const texture = this.device.createTexture({
359
363
  size: [
360
364
  imgBitmap.width,
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "wgsl-renderer",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "一个基于WebGPU和WGSL的多通道渲染器",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
7
7
  "module": "./dist/esm/index.js",
8
- "types": "./dist/types/index.d.ts",
8
+ "typings": "./dist/esm/index.d.ts",
9
9
  "repository": {
10
10
  "type": "git",
11
11
  "url": "https://github.com/taiyuuki/wgsl-renderer"
@@ -23,7 +23,7 @@
23
23
  "sideEffects": false,
24
24
  "exports": {
25
25
  ".": {
26
- "types": "./dist/index.d.ts",
26
+ "types": "./dist/esm/index.d.ts",
27
27
  "import": "./dist/esm/index.js",
28
28
  "require": "./dist/cjs/index.js"
29
29
  }
@@ -1,427 +0,0 @@
1
- //#region src/RenderPass.ts
2
- var RenderPass = class {
3
- name;
4
- pipeline;
5
- bindGroup;
6
- vertexBuffer;
7
- clearColor;
8
- blendMode;
9
- view;
10
- format;
11
- passResources = [];
12
- device;
13
- constructor(descriptor, device, format, layout) {
14
- this.device = device;
15
- this.name = descriptor.name;
16
- this.clearColor = descriptor.clearColor || {
17
- r: 0,
18
- g: 0,
19
- b: 0,
20
- a: 1
21
- };
22
- this.blendMode = descriptor.blendMode || "none";
23
- this.view = descriptor.view;
24
- this.format = descriptor.format;
25
- const actualFormat = descriptor.format || format;
26
- const module = this.device.createShaderModule({ code: descriptor.shaderCode });
27
- this.vertexBuffer = this.device.createBuffer({
28
- size: 36,
29
- usage: GPUBufferUsage.VERTEX,
30
- mappedAtCreation: true
31
- });
32
- new Float32Array(this.vertexBuffer.getMappedRange()).set([
33
- -1,
34
- -1,
35
- 0,
36
- 3,
37
- -1,
38
- 0,
39
- -1,
40
- 3,
41
- 0
42
- ]);
43
- this.vertexBuffer.unmap();
44
- const vertexEntryPoint = descriptor.entryPoints?.vertex || "vs_main";
45
- const fragmentEntryPoint = descriptor.entryPoints?.fragment || "fs_main";
46
- this.pipeline = this.device.createRenderPipeline({
47
- layout,
48
- vertex: {
49
- module,
50
- entryPoint: vertexEntryPoint,
51
- buffers: [{
52
- arrayStride: 12,
53
- attributes: [{
54
- shaderLocation: 0,
55
- offset: 0,
56
- format: "float32x3"
57
- }]
58
- }]
59
- },
60
- fragment: {
61
- module,
62
- entryPoint: fragmentEntryPoint,
63
- targets: [{
64
- format: actualFormat,
65
- blend: this.getBlendState()
66
- }]
67
- },
68
- primitive: { topology: "triangle-list" }
69
- });
70
- this.bindGroup = null;
71
- }
72
- /**
73
- * Update bind group with new entries (e.g., after texture resize)
74
- */
75
- updateBindGroup(newEntries) {
76
- const bindGroupLayout = this.pipeline.getBindGroupLayout(0);
77
- this.bindGroup = this.device.createBindGroup({
78
- layout: bindGroupLayout,
79
- entries: newEntries
80
- });
81
- }
82
- getBlendState() {
83
- switch (this.blendMode) {
84
- case "none": return;
85
- case "alpha": return {
86
- color: {
87
- srcFactor: "src-alpha",
88
- dstFactor: "one-minus-src-alpha",
89
- operation: "add"
90
- },
91
- alpha: {
92
- srcFactor: "one",
93
- dstFactor: "one-minus-src-alpha",
94
- operation: "add"
95
- }
96
- };
97
- case "additive": return {
98
- color: {
99
- srcFactor: "src-alpha",
100
- dstFactor: "one",
101
- operation: "add"
102
- },
103
- alpha: {
104
- srcFactor: "one",
105
- dstFactor: "one",
106
- operation: "add"
107
- }
108
- };
109
- case "multiply": return {
110
- color: {
111
- srcFactor: "src",
112
- dstFactor: "dst",
113
- operation: "add"
114
- },
115
- alpha: {
116
- srcFactor: "one",
117
- dstFactor: "one-minus-src-alpha",
118
- operation: "add"
119
- }
120
- };
121
- default: return;
122
- }
123
- }
124
- };
125
-
126
- //#endregion
127
- //#region src/TextureManager.ts
128
- var TextureManager = class {
129
- textures = /* @__PURE__ */ new Map();
130
- device;
131
- width;
132
- height;
133
- constructor(device, width, height) {
134
- this.device = device;
135
- this.width = width;
136
- this.height = height;
137
- }
138
- createTexture(name, format) {
139
- if (this.textures.has(name)) this.textures.get(name).destroy();
140
- const texture = this.device.createTexture({
141
- size: [this.width, this.height],
142
- format: format || "bgra8unorm",
143
- usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
144
- });
145
- this.textures.set(name, texture);
146
- return texture;
147
- }
148
- getTexture(name) {
149
- return this.textures.get(name);
150
- }
151
- resize(width, height) {
152
- if (width === this.width && height === this.height) return;
153
- this.width = width;
154
- this.height = height;
155
- this.textures.forEach((texture) => {
156
- texture.destroy();
157
- });
158
- this.textures.clear();
159
- }
160
- destroy() {
161
- this.textures.forEach((texture) => texture.destroy());
162
- this.textures.clear();
163
- }
164
- getPixelSize() {
165
- return {
166
- width: this.width,
167
- height: this.height
168
- };
169
- }
170
- };
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
-
196
- //#endregion
197
- //#region src/index.ts
198
- var WGSLRenderer = class {
199
- ctx;
200
- device;
201
- format;
202
- passes = [];
203
- textureManager;
204
- animationFrameId = null;
205
- isResizing = false;
206
- constructor(canvas, options) {
207
- this.canvas = canvas;
208
- this.options = options;
209
- if (!navigator.gpu) throw new Error("WebGPU is not supported in this browser.");
210
- this.ctx = canvas.getContext("webgpu");
211
- }
212
- async init() {
213
- this.device = await (await navigator.gpu.requestAdapter()).requestDevice();
214
- this.format = navigator.gpu.getPreferredCanvasFormat();
215
- const config = Object.assign({
216
- device: this.device,
217
- format: this.format,
218
- alphaMode: "opaque"
219
- }, this.options?.config);
220
- this.ctx.configure(config);
221
- const canvasWidth = this.canvas.width || this.canvas.clientWidth;
222
- const canvasHeight = this.canvas.height || this.canvas.clientHeight;
223
- this.textureManager = new TextureManager(this.device, canvasWidth, canvasHeight);
224
- }
225
- async resize(width, height) {
226
- if (this.isResizing) return;
227
- if (this.canvas.width === width && this.canvas.height === height) return;
228
- this.isResizing = true;
229
- this.canvas.width = width;
230
- this.canvas.height = height;
231
- const future = this.device.queue.onSubmittedWorkDone();
232
- future.catch(() => {
233
- console.warn("GPU work submission failed during resize.");
234
- this.isResizing = false;
235
- });
236
- await future;
237
- this.textureManager.resize(width, height);
238
- this.isResizing = false;
239
- }
240
- getContext() {
241
- return this.ctx;
242
- }
243
- getDevice() {
244
- return this.device;
245
- }
246
- /**
247
- * Get texture reference by pass name
248
- * Returns a PassTextureRef that will resolve to the actual texture at render time
249
- */
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);
253
- }
254
- /**
255
- * Resolve a PassTextureRef to actual GPUTextureView with validation
256
- */
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);
270
- }
271
- /**
272
- * Add a render pass to the multi-pass pipeline
273
- */
274
- addPass(descriptor) {
275
- const finalBindGroupEntries = [];
276
- descriptor.resources?.forEach((resource, index) => {
277
- finalBindGroupEntries.push({
278
- binding: index,
279
- resource
280
- });
281
- });
282
- const internalDescriptor = {
283
- name: descriptor.name,
284
- shaderCode: descriptor.shaderCode,
285
- entryPoints: descriptor.entryPoints,
286
- clearColor: descriptor.clearColor,
287
- blendMode: descriptor.blendMode,
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 ?? [];
295
- this.passes.push(pass);
296
- }
297
- /**
298
- * Resolve resource to actual GPU binding resource
299
- * Handles PassTextureRef by getting the current texture view with validation
300
- */
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
- });
320
- }
321
- /**
322
- * Create a uniforms
323
- * @param length The length of the uniform buffer in number of floats
324
- * @return The uniform object containing the buffer and data array
325
- */
326
- createUniforms(length) {
327
- const values = new Float32Array(Math.ceil(length));
328
- const buffer = this.device.createBuffer({
329
- size: values.byteLength,
330
- usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
331
- });
332
- return {
333
- values,
334
- apply: () => {
335
- this.device.queue.writeBuffer(buffer, 0, values.buffer, values.byteOffset, values.byteLength);
336
- },
337
- getBuffer: () => buffer
338
- };
339
- }
340
- /**
341
- * Create a sampler
342
- */
343
- createSampler(options) {
344
- return this.device.createSampler(Object.assign({
345
- magFilter: "linear",
346
- minFilter: "linear",
347
- addressModeU: "clamp-to-edge",
348
- addressModeV: "clamp-to-edge"
349
- }, options));
350
- }
351
- async loadImageTexture(url) {
352
- const resp = fetch(url);
353
- resp.catch((err) => {
354
- console.error("Failed to load texture:", err);
355
- });
356
- const res = await resp;
357
- const imgBitmap = await createImageBitmap(await res.blob());
358
- const texture = this.device.createTexture({
359
- size: [
360
- imgBitmap.width,
361
- imgBitmap.height,
362
- 1
363
- ],
364
- format: "rgba8unorm",
365
- usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
366
- });
367
- this.device.queue.copyExternalImageToTexture({ source: imgBitmap }, { texture }, [imgBitmap.width, imgBitmap.height]);
368
- return {
369
- texture,
370
- width: imgBitmap.width,
371
- height: imgBitmap.height
372
- };
373
- }
374
- renderFrame() {
375
- if (this.passes.length === 0) return;
376
- this.updateBindGroups();
377
- const commandEncoder = this.device.createCommandEncoder();
378
- for (let i = 0; i < this.passes.length; i++) {
379
- const pass = this.passes[i];
380
- let loadOp = "load";
381
- const isLast = i === this.passes.length - 1;
382
- if (isLast) loadOp = "clear";
383
- let renderTarget;
384
- if (pass.view) renderTarget = pass.view;
385
- else if (isLast) renderTarget = this.ctx.getCurrentTexture().createView();
386
- else {
387
- const textureName = `pass_${i}_output`;
388
- let texture = this.textureManager.getTexture(textureName);
389
- if (!texture) texture = this.textureManager.createTexture(textureName, "rgba16float");
390
- renderTarget = texture.createView();
391
- }
392
- const renderPass = commandEncoder.beginRenderPass({ colorAttachments: [{
393
- view: renderTarget,
394
- loadOp,
395
- storeOp: "store",
396
- clearValue: pass.clearColor
397
- }] });
398
- renderPass.setPipeline(pass.pipeline);
399
- if (pass.bindGroup) renderPass.setBindGroup(0, pass.bindGroup);
400
- renderPass.setVertexBuffer(0, pass.vertexBuffer);
401
- renderPass.draw(3, 1, 0, 0);
402
- renderPass.end();
403
- }
404
- this.device.queue.submit([commandEncoder.finish()]);
405
- }
406
- loopRender(cb) {
407
- this.animationFrameId = requestAnimationFrame((t) => {
408
- cb?.(t);
409
- this.renderFrame();
410
- this.loopRender(cb);
411
- });
412
- }
413
- stopLoop() {
414
- if (this.animationFrameId !== null) {
415
- cancelAnimationFrame(this.animationFrameId);
416
- this.animationFrameId = null;
417
- }
418
- }
419
- };
420
- async function createWGSLRenderer(cvs, options) {
421
- const renderer = new WGSLRenderer(cvs, options);
422
- await renderer.init();
423
- return renderer;
424
- }
425
-
426
- //#endregion
427
- export { createWGSLRenderer };