wgsl-renderer 0.1.9 → 0.2.0

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
@@ -10,6 +10,7 @@ var RenderPass = class {
10
10
  view;
11
11
  format;
12
12
  renderToCanvas;
13
+ sampleCount = 1;
13
14
  passResources = [];
14
15
  bindGroups = {};
15
16
  activeBindGroupSet = "default";
@@ -30,6 +31,7 @@ var RenderPass = class {
30
31
  this.view = descriptor.view;
31
32
  this.format = descriptor.format;
32
33
  this.renderToCanvas = descriptor.renderToCanvas;
34
+ this.sampleCount = descriptor.sampleCount || 1;
33
35
  const actualFormat = descriptor.format || format;
34
36
  const module$1 = this.device.createShaderModule({
35
37
  code: descriptor.shaderCode,
@@ -76,7 +78,8 @@ var RenderPass = class {
76
78
  blend: this.getBlendState()
77
79
  }]
78
80
  },
79
- primitive: { topology: "triangle-list" }
81
+ primitive: { topology: "triangle-list" },
82
+ multisample: { count: this.sampleCount }
80
83
  });
81
84
  this.bindGroup = null;
82
85
  }
@@ -395,6 +398,8 @@ var WGSLRenderer = class {
395
398
  textureManager;
396
399
  animationFrameId = null;
397
400
  isResizing = false;
401
+ supportedSampleCounts = [];
402
+ testedSampleCounts = /* @__PURE__ */ new Set();
398
403
  constructor(canvas, options) {
399
404
  this.canvas = canvas;
400
405
  this.options = options;
@@ -404,6 +409,8 @@ var WGSLRenderer = class {
404
409
  async init() {
405
410
  this.device = await (await navigator.gpu.requestAdapter()).requestDevice();
406
411
  this.format = navigator.gpu.getPreferredCanvasFormat();
412
+ this.supportedSampleCounts = [1];
413
+ this.testedSampleCounts = new Set([1]);
407
414
  const config = Object.assign({
408
415
  device: this.device,
409
416
  format: this.format,
@@ -437,6 +444,13 @@ var WGSLRenderer = class {
437
444
  getDevice() {
438
445
  return this.device;
439
446
  }
447
+ getSupportedSampleCounts() {
448
+ return [...this.supportedSampleCounts];
449
+ }
450
+ isSampleCountSupported(sampleCount) {
451
+ if (this.testedSampleCounts.has(sampleCount)) return this.supportedSampleCounts.includes(sampleCount);
452
+ return this.supportedSampleCounts.includes(sampleCount);
453
+ }
440
454
  /**
441
455
  * Get texture reference by pass name
442
456
  * Returns a PassTextureRef that will resolve to the actual texture at render time
@@ -445,7 +459,9 @@ var WGSLRenderer = class {
445
459
  * @param options Optional texture creation options for when the texture needs to be created
446
460
  */
447
461
  getPassTexture(passName, options) {
448
- 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(", ")}]`);
462
+ const pass = this.passes.find((pass$1) => pass$1.name === passName);
463
+ if (!pass) throw new Error(`Cannot find pass named '${passName}'. Available passes: [${this.passes.map((p) => p.name).join(", ")}]`);
464
+ if ((options?.format ?? "rgba8unorm") !== pass.format) throw new Error(`Format must be set to ${pass.format}, pass name: '${passName}'`);
449
465
  return PassTextureRef.create(passName, options);
450
466
  }
451
467
  /**
@@ -461,11 +477,13 @@ var WGSLRenderer = class {
461
477
  const format = ref.options?.format || this.format;
462
478
  const usage = ref.options?.usage || GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT;
463
479
  const size = this.textureManager.getPixelSize();
480
+ const requestedSampleCount = ref.options?.sampleCount || 1;
481
+ const actualSampleCount = this.isSampleCountSupported(requestedSampleCount) ? requestedSampleCount : 1;
464
482
  texture = this.device.createTexture({
465
483
  size: [size.width, size.height],
466
484
  format,
467
485
  usage,
468
- sampleCount: ref.options?.sampleCount || 1
486
+ sampleCount: actualSampleCount
469
487
  });
470
488
  this.textureManager.setTexture(textureName, texture);
471
489
  }
@@ -577,7 +595,8 @@ var WGSLRenderer = class {
577
595
  bindGroupSets: bindGroupSetsCopy,
578
596
  view: descriptor.view,
579
597
  format: descriptor.format,
580
- renderToCanvas: descriptor.renderToCanvas
598
+ renderToCanvas: descriptor.renderToCanvas,
599
+ sampleCount: descriptor.sampleCount
581
600
  };
582
601
  const pipelineFormat = descriptor.format || this.format;
583
602
  return new RenderPass(internalDescriptor, this.device, pipelineFormat);
@@ -586,6 +605,10 @@ var WGSLRenderer = class {
586
605
  * Add a render pass to the multi-pass pipeline
587
606
  */
588
607
  addPass(descriptor) {
608
+ if (descriptor.sampleCount && !this.isSampleCountSupported(descriptor.sampleCount)) {
609
+ console.warn(`Sample count ${descriptor.sampleCount} is not supported. Using sample count 1 instead.`);
610
+ descriptor.sampleCount = 1;
611
+ }
589
612
  const pass = this.createPass(descriptor);
590
613
  pass.passResources = descriptor.resources ?? [];
591
614
  this.passes.push(pass);
@@ -702,16 +725,64 @@ var WGSLRenderer = class {
702
725
  let loadOp = "load";
703
726
  if (i === 0) loadOp = "clear";
704
727
  let renderTarget;
705
- if (pass.renderToCanvas || i === enabledPasses.length - 1) renderTarget = this.ctx.getCurrentTexture().createView();
728
+ let resolveTarget;
729
+ const canvasTexture = this.ctx.getCurrentTexture();
730
+ const isLastPass = i === enabledPasses.length - 1;
731
+ if (pass.renderToCanvas || isLastPass && !pass.view) if (pass.sampleCount && pass.sampleCount > 1) {
732
+ const actualSampleCount = this.isSampleCountSupported(pass.sampleCount) ? pass.sampleCount : 1;
733
+ if (actualSampleCount === 1) renderTarget = canvasTexture.createView();
734
+ else {
735
+ renderTarget = this.device.createTexture({
736
+ size: [canvasTexture.width, canvasTexture.height],
737
+ format: canvasTexture.format,
738
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
739
+ sampleCount: actualSampleCount
740
+ }).createView();
741
+ resolveTarget = canvasTexture.createView();
742
+ }
743
+ } else renderTarget = canvasTexture.createView();
706
744
  else if (pass.view) renderTarget = pass.view;
707
745
  else {
708
746
  const textureName = `pass_${i}_output`;
709
747
  let texture = this.textureManager.getTexture(textureName);
710
- if (!texture) texture = this.textureManager.createTexture(textureName, pass.format || this.format);
711
- renderTarget = texture.createView();
748
+ if (texture) {
749
+ const msaaTexture = this.textureManager.getTexture(`${textureName}_msaa`);
750
+ const resolveTexture = this.textureManager.getTexture(`${textureName}_resolve`);
751
+ if (msaaTexture && resolveTexture && pass.sampleCount && pass.sampleCount > 1) {
752
+ renderTarget = msaaTexture.createView();
753
+ resolveTarget = resolveTexture.createView();
754
+ } else renderTarget = texture.createView();
755
+ } else if (pass.sampleCount && pass.sampleCount > 1) {
756
+ const actualSampleCount = this.isSampleCountSupported(pass.sampleCount) ? pass.sampleCount : 1;
757
+ if (actualSampleCount > 1) {
758
+ texture = this.device.createTexture({
759
+ size: [this.textureManager.getPixelSize().width, this.textureManager.getPixelSize().height],
760
+ format: pass.format || this.format,
761
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
762
+ sampleCount: actualSampleCount
763
+ });
764
+ const resolveTexture = this.device.createTexture({
765
+ size: [this.textureManager.getPixelSize().width, this.textureManager.getPixelSize().height],
766
+ format: pass.format || this.format,
767
+ usage: GPUTextureUsage.TEXTURE_BINDING,
768
+ sampleCount: 1
769
+ });
770
+ this.textureManager.setTexture(`${textureName}_msaa`, texture);
771
+ this.textureManager.setTexture(`${textureName}_resolve`, resolveTexture);
772
+ renderTarget = texture.createView();
773
+ resolveTarget = resolveTexture.createView();
774
+ } else {
775
+ texture = this.textureManager.createTexture(textureName, pass.format || this.format);
776
+ renderTarget = texture.createView();
777
+ }
778
+ } else {
779
+ texture = this.textureManager.createTexture(textureName, pass.format || this.format);
780
+ renderTarget = texture.createView();
781
+ }
712
782
  }
713
783
  const renderPass = commandEncoder.beginRenderPass({ colorAttachments: [{
714
784
  view: renderTarget,
785
+ resolveTarget,
715
786
  loadOp,
716
787
  storeOp: "store",
717
788
  clearValue: pass.clearColor
@@ -751,4 +822,6 @@ async function createWGSLRenderer(cvs, options) {
751
822
  }
752
823
 
753
824
  //#endregion
825
+ exports.PassTextureRef = PassTextureRef;
826
+ exports.WGSLRenderer = WGSLRenderer;
754
827
  exports.createWGSLRenderer = createWGSLRenderer;
@@ -53,6 +53,7 @@ interface RenderPassOptions {
53
53
  view?: GPUTextureView;
54
54
  format?: GPUTextureFormat;
55
55
  renderToCanvas?: boolean;
56
+ sampleCount?: number;
56
57
  }
57
58
  interface InternalRenderPassDescriptor {
58
59
  name: string;
@@ -75,6 +76,7 @@ interface InternalRenderPassDescriptor {
75
76
  view?: GPUTextureView;
76
77
  format?: GPUTextureFormat;
77
78
  renderToCanvas?: boolean;
79
+ sampleCount?: number;
78
80
  }
79
81
  declare class RenderPass {
80
82
  name: string;
@@ -91,6 +93,7 @@ declare class RenderPass {
91
93
  view?: GPUTextureView;
92
94
  format?: GPUTextureFormat;
93
95
  renderToCanvas?: boolean;
96
+ sampleCount: number;
94
97
  passResources: BindingResource[];
95
98
  bindGroups: {
96
99
  [setName: string]: GPUBindGroup;
@@ -148,11 +151,15 @@ declare class WGSLRenderer {
148
151
  private textureManager;
149
152
  private animationFrameId;
150
153
  private isResizing;
154
+ private supportedSampleCounts;
155
+ private testedSampleCounts;
151
156
  constructor(canvas: HTMLCanvasElement, options?: WGSLRendererOptions | undefined);
152
157
  init(): Promise<void>;
153
158
  resize(width: number, height: number): Promise<void>;
154
159
  getContext(): GPUCanvasContext;
155
160
  getDevice(): GPUDevice;
161
+ getSupportedSampleCounts(): number[];
162
+ isSampleCountSupported(sampleCount: number): boolean;
156
163
  /**
157
164
  * Get texture reference by pass name
158
165
  * Returns a PassTextureRef that will resolve to the actual texture at render time
@@ -256,4 +263,4 @@ declare class WGSLRenderer {
256
263
  }
257
264
  declare function createWGSLRenderer(cvs: HTMLCanvasElement, options?: WGSLRendererOptions): Promise<WGSLRenderer>;
258
265
  //#endregion
259
- export { type BindingResource, type PassTextureRef, type RenderPassOptions, type WGSLRenderer, createWGSLRenderer };
266
+ export { BindingResource, PassTextureRef, RenderPassOptions, WGSLRenderer, createWGSLRenderer };
package/dist/esm/index.js CHANGED
@@ -9,6 +9,7 @@ var RenderPass = class {
9
9
  view;
10
10
  format;
11
11
  renderToCanvas;
12
+ sampleCount = 1;
12
13
  passResources = [];
13
14
  bindGroups = {};
14
15
  activeBindGroupSet = "default";
@@ -29,6 +30,7 @@ var RenderPass = class {
29
30
  this.view = descriptor.view;
30
31
  this.format = descriptor.format;
31
32
  this.renderToCanvas = descriptor.renderToCanvas;
33
+ this.sampleCount = descriptor.sampleCount || 1;
32
34
  const actualFormat = descriptor.format || format;
33
35
  const module = this.device.createShaderModule({
34
36
  code: descriptor.shaderCode,
@@ -75,7 +77,8 @@ var RenderPass = class {
75
77
  blend: this.getBlendState()
76
78
  }]
77
79
  },
78
- primitive: { topology: "triangle-list" }
80
+ primitive: { topology: "triangle-list" },
81
+ multisample: { count: this.sampleCount }
79
82
  });
80
83
  this.bindGroup = null;
81
84
  }
@@ -394,6 +397,8 @@ var WGSLRenderer = class {
394
397
  textureManager;
395
398
  animationFrameId = null;
396
399
  isResizing = false;
400
+ supportedSampleCounts = [];
401
+ testedSampleCounts = /* @__PURE__ */ new Set();
397
402
  constructor(canvas, options) {
398
403
  this.canvas = canvas;
399
404
  this.options = options;
@@ -403,6 +408,8 @@ var WGSLRenderer = class {
403
408
  async init() {
404
409
  this.device = await (await navigator.gpu.requestAdapter()).requestDevice();
405
410
  this.format = navigator.gpu.getPreferredCanvasFormat();
411
+ this.supportedSampleCounts = [1];
412
+ this.testedSampleCounts = new Set([1]);
406
413
  const config = Object.assign({
407
414
  device: this.device,
408
415
  format: this.format,
@@ -436,6 +443,13 @@ var WGSLRenderer = class {
436
443
  getDevice() {
437
444
  return this.device;
438
445
  }
446
+ getSupportedSampleCounts() {
447
+ return [...this.supportedSampleCounts];
448
+ }
449
+ isSampleCountSupported(sampleCount) {
450
+ if (this.testedSampleCounts.has(sampleCount)) return this.supportedSampleCounts.includes(sampleCount);
451
+ return this.supportedSampleCounts.includes(sampleCount);
452
+ }
439
453
  /**
440
454
  * Get texture reference by pass name
441
455
  * Returns a PassTextureRef that will resolve to the actual texture at render time
@@ -444,7 +458,9 @@ var WGSLRenderer = class {
444
458
  * @param options Optional texture creation options for when the texture needs to be created
445
459
  */
446
460
  getPassTexture(passName, options) {
447
- 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(", ")}]`);
461
+ const pass = this.passes.find((pass$1) => pass$1.name === passName);
462
+ if (!pass) throw new Error(`Cannot find pass named '${passName}'. Available passes: [${this.passes.map((p) => p.name).join(", ")}]`);
463
+ if ((options?.format ?? "rgba8unorm") !== pass.format) throw new Error(`Format must be set to ${pass.format}, pass name: '${passName}'`);
448
464
  return PassTextureRef.create(passName, options);
449
465
  }
450
466
  /**
@@ -460,11 +476,13 @@ var WGSLRenderer = class {
460
476
  const format = ref.options?.format || this.format;
461
477
  const usage = ref.options?.usage || GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT;
462
478
  const size = this.textureManager.getPixelSize();
479
+ const requestedSampleCount = ref.options?.sampleCount || 1;
480
+ const actualSampleCount = this.isSampleCountSupported(requestedSampleCount) ? requestedSampleCount : 1;
463
481
  texture = this.device.createTexture({
464
482
  size: [size.width, size.height],
465
483
  format,
466
484
  usage,
467
- sampleCount: ref.options?.sampleCount || 1
485
+ sampleCount: actualSampleCount
468
486
  });
469
487
  this.textureManager.setTexture(textureName, texture);
470
488
  }
@@ -576,7 +594,8 @@ var WGSLRenderer = class {
576
594
  bindGroupSets: bindGroupSetsCopy,
577
595
  view: descriptor.view,
578
596
  format: descriptor.format,
579
- renderToCanvas: descriptor.renderToCanvas
597
+ renderToCanvas: descriptor.renderToCanvas,
598
+ sampleCount: descriptor.sampleCount
580
599
  };
581
600
  const pipelineFormat = descriptor.format || this.format;
582
601
  return new RenderPass(internalDescriptor, this.device, pipelineFormat);
@@ -585,6 +604,10 @@ var WGSLRenderer = class {
585
604
  * Add a render pass to the multi-pass pipeline
586
605
  */
587
606
  addPass(descriptor) {
607
+ if (descriptor.sampleCount && !this.isSampleCountSupported(descriptor.sampleCount)) {
608
+ console.warn(`Sample count ${descriptor.sampleCount} is not supported. Using sample count 1 instead.`);
609
+ descriptor.sampleCount = 1;
610
+ }
588
611
  const pass = this.createPass(descriptor);
589
612
  pass.passResources = descriptor.resources ?? [];
590
613
  this.passes.push(pass);
@@ -701,16 +724,64 @@ var WGSLRenderer = class {
701
724
  let loadOp = "load";
702
725
  if (i === 0) loadOp = "clear";
703
726
  let renderTarget;
704
- if (pass.renderToCanvas || i === enabledPasses.length - 1) renderTarget = this.ctx.getCurrentTexture().createView();
727
+ let resolveTarget;
728
+ const canvasTexture = this.ctx.getCurrentTexture();
729
+ const isLastPass = i === enabledPasses.length - 1;
730
+ if (pass.renderToCanvas || isLastPass && !pass.view) if (pass.sampleCount && pass.sampleCount > 1) {
731
+ const actualSampleCount = this.isSampleCountSupported(pass.sampleCount) ? pass.sampleCount : 1;
732
+ if (actualSampleCount === 1) renderTarget = canvasTexture.createView();
733
+ else {
734
+ renderTarget = this.device.createTexture({
735
+ size: [canvasTexture.width, canvasTexture.height],
736
+ format: canvasTexture.format,
737
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
738
+ sampleCount: actualSampleCount
739
+ }).createView();
740
+ resolveTarget = canvasTexture.createView();
741
+ }
742
+ } else renderTarget = canvasTexture.createView();
705
743
  else if (pass.view) renderTarget = pass.view;
706
744
  else {
707
745
  const textureName = `pass_${i}_output`;
708
746
  let texture = this.textureManager.getTexture(textureName);
709
- if (!texture) texture = this.textureManager.createTexture(textureName, pass.format || this.format);
710
- renderTarget = texture.createView();
747
+ if (texture) {
748
+ const msaaTexture = this.textureManager.getTexture(`${textureName}_msaa`);
749
+ const resolveTexture = this.textureManager.getTexture(`${textureName}_resolve`);
750
+ if (msaaTexture && resolveTexture && pass.sampleCount && pass.sampleCount > 1) {
751
+ renderTarget = msaaTexture.createView();
752
+ resolveTarget = resolveTexture.createView();
753
+ } else renderTarget = texture.createView();
754
+ } else if (pass.sampleCount && pass.sampleCount > 1) {
755
+ const actualSampleCount = this.isSampleCountSupported(pass.sampleCount) ? pass.sampleCount : 1;
756
+ if (actualSampleCount > 1) {
757
+ texture = this.device.createTexture({
758
+ size: [this.textureManager.getPixelSize().width, this.textureManager.getPixelSize().height],
759
+ format: pass.format || this.format,
760
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
761
+ sampleCount: actualSampleCount
762
+ });
763
+ const resolveTexture = this.device.createTexture({
764
+ size: [this.textureManager.getPixelSize().width, this.textureManager.getPixelSize().height],
765
+ format: pass.format || this.format,
766
+ usage: GPUTextureUsage.TEXTURE_BINDING,
767
+ sampleCount: 1
768
+ });
769
+ this.textureManager.setTexture(`${textureName}_msaa`, texture);
770
+ this.textureManager.setTexture(`${textureName}_resolve`, resolveTexture);
771
+ renderTarget = texture.createView();
772
+ resolveTarget = resolveTexture.createView();
773
+ } else {
774
+ texture = this.textureManager.createTexture(textureName, pass.format || this.format);
775
+ renderTarget = texture.createView();
776
+ }
777
+ } else {
778
+ texture = this.textureManager.createTexture(textureName, pass.format || this.format);
779
+ renderTarget = texture.createView();
780
+ }
711
781
  }
712
782
  const renderPass = commandEncoder.beginRenderPass({ colorAttachments: [{
713
783
  view: renderTarget,
784
+ resolveTarget,
714
785
  loadOp,
715
786
  storeOp: "store",
716
787
  clearValue: pass.clearColor
@@ -750,4 +821,4 @@ async function createWGSLRenderer(cvs, options) {
750
821
  }
751
822
 
752
823
  //#endregion
753
- export { createWGSLRenderer };
824
+ export { PassTextureRef, WGSLRenderer, createWGSLRenderer };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wgsl-renderer",
3
- "version": "0.1.9",
3
+ "version": "0.2.0",
4
4
  "description": "A multi-pass renderer based on WebGPU and WGSL.",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",