Skip to content

Commit baacf4f

Browse files
committed
feat(WebGPU): rename sampleCount to multiSample and add MSAA regression test
1 parent cb82ace commit baacf4f

File tree

5 files changed

+121
-14
lines changed

5 files changed

+121
-14
lines changed

Sources/Rendering/WebGPU/OpaquePass/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ function vtkWebGPUOpaquePass(publicAPI, model) {
2222
model._currentParent = viewNode;
2323

2424
const device = viewNode.getDevice();
25-
const sampleCount = viewNode.getSampleCount ? viewNode.getSampleCount() : 1;
25+
const sampleCount = viewNode.getMultiSample ? viewNode.getMultiSample() : 1;
2626

2727
// If sampleCount changed since last render, tear down and recreate
2828
if (model.renderEncoder && model._currentSampleCount !== sampleCount) {

Sources/Rendering/WebGPU/OrderIndependentTranslucentPass/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ function vtkWebGPUOrderIndependentTranslucentPass(publicAPI, model) {
5454
model._currentParent = viewNode;
5555

5656
const device = viewNode.getDevice();
57-
const sampleCount = viewNode.getSampleCount ? viewNode.getSampleCount() : 1;
57+
const sampleCount = viewNode.getMultiSample ? viewNode.getMultiSample() : 1;
5858

5959
// If sampleCount changed, tear down
6060
if (

Sources/Rendering/WebGPU/RenderWindow/api.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,20 @@ artifacts on geometry edges. When active, both opaque and translucent
1010
rendering passes use multisampled textures and resolve them before
1111
compositing.
1212

13-
### sampleCount (set/get)
13+
### multiSample (set/get)
1414

1515
Controls the number of MSAA samples per pixel.
1616

1717
- **Default:** `1` (no anti-aliasing)
1818
- **Valid values:** `1` or `4`
1919
- Setting any other value is ignored and logs an error.
20-
- Changing `sampleCount` at runtime automatically recreates the
20+
- Changing `multiSample` at runtime automatically recreates the
2121
internal render encoders and textures on the next frame.
2222

2323
**Usage:**
2424

2525
```js
2626
const renderWindow = vtkWebGPURenderWindow.newInstance();
27-
renderWindow.setSampleCount(4); // enable 4× MSAA
28-
renderWindow.setSampleCount(1); // disable MSAA
27+
renderWindow.setMultiSample(4); // enable 4× MSAA
28+
renderWindow.setMultiSample(1); // disable MSAA
2929
```

Sources/Rendering/WebGPU/RenderWindow/index.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -558,16 +558,16 @@ function vtkWebGPURenderWindow(publicAPI, model) {
558558
return modified;
559559
};
560560

561-
// Validate sampleCount — WebGPU only supports 1 or 4
562-
const superSetSampleCount = publicAPI.setSampleCount;
563-
publicAPI.setSampleCount = (count) => {
561+
// Validate multiSample — WebGPU only supports 1 or 4
562+
const superSetMultiSample = publicAPI.setMultiSample;
563+
publicAPI.setMultiSample = (count) => {
564564
if (count !== 1 && count !== 4) {
565565
vtkErrorMacro(
566-
`Invalid sampleCount ${count}. WebGPU only supports sampleCount of 1 or 4. Ignoring.`
566+
`Invalid multiSample ${count}. WebGPU only supports multiSample of 1 or 4. Ignoring.`
567567
);
568568
return false;
569569
}
570-
return superSetSampleCount(count);
570+
return superSetMultiSample(count);
571571
};
572572

573573
publicAPI.delete = macro.chain(publicAPI.delete, publicAPI.setViewStream);
@@ -594,7 +594,7 @@ const DEFAULT_VALUES = {
594594
nextPropID: 1,
595595
xrSupported: false,
596596
presentationFormat: null,
597-
sampleCount: 1,
597+
multiSample: 1,
598598
};
599599

600600
// ----------------------------------------------------------------------------
@@ -638,7 +638,7 @@ export function extend(publicAPI, model, initialValues = {}) {
638638
'presentationFormat',
639639
'useBackgroundImage',
640640
'xrSupported',
641-
'sampleCount',
641+
'multiSample',
642642
]);
643643

644644
macro.setGet(publicAPI, model, [
@@ -650,7 +650,7 @@ export function extend(publicAPI, model, initialValues = {}) {
650650
'notifyStartCaptureImage',
651651
'cursor',
652652
'useOffScreen',
653-
'sampleCount',
653+
'multiSample',
654654
]);
655655

656656
macro.setGetArray(publicAPI, model, ['size'], 2);
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import test from 'tape';
2+
import testUtils from 'vtk.js/Sources/Testing/testUtils';
3+
4+
import vtkActor from 'vtk.js/Sources/Rendering/Core/Actor';
5+
import vtkMapper from 'vtk.js/Sources/Rendering/Core/Mapper';
6+
import 'vtk.js/Sources/Rendering/Misc/RenderingAPIs';
7+
import vtkRenderer from 'vtk.js/Sources/Rendering/Core/Renderer';
8+
import vtkRenderWindow from 'vtk.js/Sources/Rendering/Core/RenderWindow';
9+
import vtkConeSource from 'vtk.js/Sources/Filters/Sources/ConeSource';
10+
import vtkSphereSource from 'vtk.js/Sources/Filters/Sources/SphereSource';
11+
12+
// ---------------------------------------------------------------------------
13+
// Helpers
14+
// ---------------------------------------------------------------------------
15+
16+
// Detect whether the current runtime actually supports WebGPU.
17+
function isWebGPUAvailable() {
18+
return typeof navigator !== 'undefined' && !!navigator.gpu;
19+
}
20+
21+
// ---------------------------------------------------------------------------
22+
// Test: MSAA opaque + translucent rendering (WebGPU)
23+
// ---------------------------------------------------------------------------
24+
25+
test.onlyIfWebGPU('Test WebGPU MSAA rendering', (t) => {
26+
const gc = testUtils.createGarbageCollector(t);
27+
28+
const container = document.querySelector('body');
29+
const renderWindowContainer = gc.registerDOMElement(
30+
document.createElement('div')
31+
);
32+
container.appendChild(renderWindowContainer);
33+
34+
// ------ Scene setup ------
35+
const renderWindow = gc.registerResource(vtkRenderWindow.newInstance());
36+
const renderer = gc.registerResource(vtkRenderer.newInstance());
37+
renderWindow.addRenderer(renderer);
38+
renderer.setBackground(0.32, 0.34, 0.43);
39+
40+
// Opaque cone
41+
const coneSource = gc.registerResource(
42+
vtkConeSource.newInstance({ height: 1.0, resolution: 60 })
43+
);
44+
const coneMapper = gc.registerResource(vtkMapper.newInstance());
45+
coneMapper.setInputConnection(coneSource.getOutputPort());
46+
const coneActor = gc.registerResource(vtkActor.newInstance());
47+
coneActor.setMapper(coneMapper);
48+
renderer.addActor(coneActor);
49+
50+
// Translucent sphere (exercises OrderIndependentTranslucentPass MSAA path)
51+
const sphereSource = gc.registerResource(
52+
vtkSphereSource.newInstance({ radius: 0.35, center: [0.3, 0.3, 0.0] })
53+
);
54+
const sphereMapper = gc.registerResource(vtkMapper.newInstance());
55+
sphereMapper.setInputConnection(sphereSource.getOutputPort());
56+
const sphereActor = gc.registerResource(vtkActor.newInstance());
57+
sphereActor.setMapper(sphereMapper);
58+
sphereActor.getProperty().setOpacity(0.5);
59+
sphereActor.getProperty().setColor(0.2, 0.6, 0.9);
60+
renderer.addActor(sphereActor);
61+
62+
// ------ Render window view ------
63+
const apiView = gc.registerResource(
64+
renderWindow.newAPISpecificView('WebGPU')
65+
);
66+
apiView.setContainer(renderWindowContainer);
67+
renderWindow.addView(apiView);
68+
apiView.setSize(400, 400);
69+
70+
// ------ MSAA configuration ------
71+
const webgpuAvailable = isWebGPUAvailable();
72+
const desiredSampleCount = webgpuAvailable ? 4 : 1;
73+
74+
if (apiView.setMultiSample) {
75+
// Validate that invalid sample counts are rejected
76+
t.notOk(
77+
apiView.setMultiSample(2),
78+
'setMultiSample(2) should return false (invalid)'
79+
);
80+
81+
// Set the desired sample count
82+
apiView.setMultiSample(desiredSampleCount);
83+
}
84+
85+
t.equal(
86+
apiView.getMultiSample ? apiView.getMultiSample() : 1,
87+
desiredSampleCount,
88+
`multiSample should be ${desiredSampleCount}`
89+
);
90+
91+
renderer.resetCamera();
92+
93+
// ------ Capture and verify ------
94+
const promise = apiView
95+
.captureNextImage()
96+
.then((image) => {
97+
// The rendering completed without errors — this is the primary
98+
// regression check. MSAA misconfiguration (sample count mismatches,
99+
// missing resolve targets, etc.) would cause a GPU validation error
100+
// before we reach this point.
101+
t.ok(image, 'MSAA render produced an image without GPU errors');
102+
})
103+
.finally(gc.releaseResources);
104+
105+
renderWindow.render();
106+
return promise;
107+
});

0 commit comments

Comments
 (0)