Skip to content

Commit 109672c

Browse files
committed
feat(WebGPU): update to use half float buffers
With this MR the WebGPU backend will use half float buffers for the opaque pass, and blit that into the target preferredFormat for the canvas.
1 parent 0e7a80c commit 109672c

File tree

7 files changed

+138
-39
lines changed

7 files changed

+138
-39
lines changed

Sources/Rendering/WebGPU/ForwardPass/index.js

Lines changed: 97 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,36 @@
11
import macro from 'vtk.js/Sources/macros';
2+
import vtkWebGPUFullScreenQuad from 'vtk.js/Sources/Rendering/WebGPU/FullScreenQuad';
23
import vtkWebGPUOpaquePass from 'vtk.js/Sources/Rendering/WebGPU/OpaquePass';
34
import vtkWebGPUOrderIndepenentTranslucentPass from 'vtk.js/Sources/Rendering/WebGPU/OrderIndependentTranslucentPass';
5+
import vtkWebGPURenderEncoder from 'vtk.js/Sources/Rendering/WebGPU/RenderEncoder';
46
import vtkWebGPUVolumePass from 'vtk.js/Sources/Rendering/WebGPU/VolumePass';
57
import vtkRenderPass from 'vtk.js/Sources/Rendering/SceneGraph/RenderPass';
8+
import vtkWebGPUSampler from 'vtk.js/Sources/Rendering/WebGPU/Sampler';
9+
import vtkWebGPUTextureView from 'vtk.js/Sources/Rendering/WebGPU/TextureView';
10+
11+
const finalBlitFragTemplate = `
12+
//VTK::Mapper::Dec
13+
14+
//VTK::TCoord::Dec
15+
16+
//VTK::RenderEncoder::Dec
17+
18+
//VTK::IOStructs::Dec
19+
20+
@fragment
21+
fn main(
22+
//VTK::IOStructs::Input
23+
)
24+
//VTK::IOStructs::Output
25+
{
26+
var output: fragmentOutput;
27+
28+
var computedColor: vec4<f32> = clamp(textureSampleLevel(opaquePassColorTexture, finalPassSampler, input.tcoordVS, 0),vec4<f32>(0.0),vec4<f32>(1.0));
29+
30+
//VTK::RenderEncoder::Impl
31+
return output;
32+
}
33+
`;
634

735
// ----------------------------------------------------------------------------
836

@@ -83,26 +111,80 @@ function vtkForwardPass(publicAPI, model) {
83111
model.volumePass.setVolumes(model.volumes);
84112
model.volumePass.traverse(renNode, viewNode);
85113
}
114+
115+
// blit the result into the swap chain
116+
publicAPI.finalPass(viewNode, renNode);
86117
}
87118
}
88119
}
120+
};
89121

90-
// blit the result into the swap chain
91-
const sctex = viewNode.getCurrentTexture();
92-
const optex = model.opaquePass.getColorTexture();
93-
const cmdEnc = viewNode.getCommandEncoder();
94-
cmdEnc.copyTextureToTexture(
95-
{
96-
texture: optex.getHandle(),
97-
},
98-
{
99-
texture: sctex,
122+
publicAPI.finalPass = (viewNode, renNode) => {
123+
if (!model._finalBlitEncoder) {
124+
publicAPI.createFinalBlitEncoder(viewNode);
125+
}
126+
model._finalBlitOutputTextureView.createFromTextureHandle(
127+
viewNode.getCurrentTexture(),
128+
{ depth: 1, format: viewNode.getPresentationFormat() }
129+
);
130+
131+
model._finalBlitEncoder.attachTextureViews();
132+
model._finalBlitEncoder.begin(viewNode.getCommandEncoder());
133+
renNode.scissorAndViewport(model._finalBlitEncoder);
134+
model._fullScreenQuad.prepareAndDraw(model._finalBlitEncoder);
135+
model._finalBlitEncoder.end();
136+
};
137+
138+
publicAPI.createFinalBlitEncoder = (viewNode) => {
139+
model._finalBlitEncoder = vtkWebGPURenderEncoder.newInstance({
140+
label: 'forwardPassBlit',
141+
});
142+
model._finalBlitEncoder.setDescription({
143+
colorAttachments: [
144+
{
145+
view: null,
146+
loadOp: 'load',
147+
storeOp: 'store',
148+
},
149+
],
150+
});
151+
model._finalBlitEncoder.setPipelineHash('fpf');
152+
model._finalBlitEncoder.setPipelineSettings({
153+
primitive: { cullMode: 'none' },
154+
fragment: {
155+
targets: [
156+
{
157+
format: viewNode.getPresentationFormat(),
158+
blend: {
159+
color: {
160+
srcFactor: 'src-alpha',
161+
dstFactor: 'one-minus-src-alpha',
162+
},
163+
alpha: { srcfactor: 'one', dstFactor: 'one-minus-src-alpha' },
164+
},
165+
},
166+
],
100167
},
101-
{
102-
width: optex.getWidth(),
103-
height: optex.getHeight(),
104-
depthOrArrayLayers: 1,
105-
}
168+
});
169+
model._fsqSampler = vtkWebGPUSampler.newInstance({
170+
label: 'finalPassSampler',
171+
});
172+
model._fsqSampler.create(viewNode.getDevice(), {
173+
minFilter: 'linear',
174+
magFilter: 'linear',
175+
});
176+
model._fullScreenQuad = vtkWebGPUFullScreenQuad.newInstance();
177+
model._fullScreenQuad.setDevice(viewNode.getDevice());
178+
model._fullScreenQuad.setPipelineHash('fpfsq');
179+
model._fullScreenQuad.setTextureViews([
180+
model.opaquePass.getColorTextureView(),
181+
]);
182+
model._fullScreenQuad.setAdditionalBindables([model._fsqSampler]);
183+
model._fullScreenQuad.setFragmentShaderTemplate(finalBlitFragTemplate);
184+
model._finalBlitOutputTextureView = vtkWebGPUTextureView.newInstance();
185+
model._finalBlitEncoder.setColorTextureView(
186+
0,
187+
model._finalBlitOutputTextureView
106188
);
107189
};
108190

Sources/Rendering/WebGPU/OpaquePass/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ function vtkWebGPUOpaquePass(publicAPI, model) {
3131
model.colorTexture.create(device, {
3232
width: viewNode.getCanvas().width,
3333
height: viewNode.getCanvas().height,
34-
format: 'bgra8unorm',
34+
format: 'rgba16float',
3535
/* eslint-disable no-undef */
3636
/* eslint-disable no-bitwise */
3737
usage:

Sources/Rendering/WebGPU/OrderIndependentTranslucentPass/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ function vtkWebGPUOrderIndependentTranslucentPass(publicAPI, model) {
239239
fragment: {
240240
targets: [
241241
{
242-
format: 'bgra8unorm',
242+
format: 'rgba16float',
243243
blend: {
244244
color: {
245245
srcFactor: 'src-alpha',

Sources/Rendering/WebGPU/RenderEncoder/index.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ function vtkWebGPURenderEncoder(publicAPI, model) {
5959
console.trace();
6060
} else {
6161
for (let i = 0; i < model.colorTextureViews.length; i++) {
62-
const fmt = model.colorTextureViews[i].getTexture().getFormat();
63-
if (fmt !== pd.fragment.targets[i].format) {
62+
const fmt = model.colorTextureViews[i].getTexture()?.getFormat();
63+
if (fmt && fmt !== pd.fragment.targets[i].format) {
6464
console.log(
6565
`mismatched attachments for attachment ${i} on pipeline ${pd.fragment.targets[i].format} while encoder has ${fmt}`
6666
);
@@ -74,8 +74,8 @@ function vtkWebGPURenderEncoder(publicAPI, model) {
7474
console.log('mismatched depth attachments');
7575
console.trace();
7676
} else if (model.depthTextureView) {
77-
const dfmt = model.depthTextureView.getTexture().getFormat();
78-
if (dfmt !== pd.depthStencil.format) {
77+
const dfmt = model.depthTextureView.getTexture()?.getFormat();
78+
if (dfmt && dfmt !== pd.depthStencil.format) {
7979
console.log(
8080
`mismatched depth attachments on pipeline ${pd.depthStencil.format} while encoder has ${dfmt}`
8181
);
@@ -211,7 +211,7 @@ export function extend(publicAPI, model, initialValues = {}) {
211211
fragment: {
212212
targets: [
213213
{
214-
format: 'bgra8unorm',
214+
format: 'rgba16float',
215215
blend: {
216216
color: {
217217
srcFactor: 'src-alpha',

Sources/Rendering/WebGPU/RenderWindow/index.js

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import vtkWebGPUHardwareSelector from 'vtk.js/Sources/Rendering/WebGPU/HardwareS
77
import vtkWebGPUViewNodeFactory from 'vtk.js/Sources/Rendering/WebGPU/ViewNodeFactory';
88
import vtkRenderPass from 'vtk.js/Sources/Rendering/SceneGraph/RenderPass';
99
import vtkRenderWindowViewNode from 'vtk.js/Sources/Rendering/SceneGraph/RenderWindowViewNode';
10-
// import { VtkDataTypes } from 'vtk.js/Sources/Common/Core/DataArray/Constants';
10+
import HalfFloat from 'vtk.js/Sources/Common/Core/HalfFloat';
1111

1212
const { vtkErrorMacro } = macro;
1313
// const IS_CHROME = navigator.userAgent.indexOf('Chrome') !== -1;
@@ -68,15 +68,15 @@ function vtkWebGPURenderWindow(publicAPI, model) {
6868
publicAPI.recreateSwapChain = () => {
6969
if (model.context) {
7070
model.context.unconfigure();
71-
const presentationFormat = navigator.gpu.getPreferredCanvasFormat(
71+
model.presentationFormat = navigator.gpu.getPreferredCanvasFormat(
7272
model.adapter
7373
);
7474

7575
/* eslint-disable no-undef */
7676
/* eslint-disable no-bitwise */
7777
model.context.configure({
7878
device: model.device.getHandle(),
79-
format: presentationFormat,
79+
format: model.presentationFormat,
8080
alphaMode: 'premultiplied',
8181
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_DST,
8282
width: model.size[0],
@@ -478,9 +478,9 @@ function vtkWebGPURenderWindow(publicAPI, model) {
478478
height: texture.getHeight(),
479479
};
480480

481-
// must be a multiple of 256 bytes, so 64 texels with rgba8
482-
result.colorBufferWidth = 64 * Math.floor((result.width + 63) / 64);
483-
result.colorBufferSizeInBytes = result.colorBufferWidth * result.height * 4;
481+
// must be a multiple of 256 bytes, so 32 texels with rgba16
482+
result.colorBufferWidth = 32 * Math.floor((result.width + 31) / 32);
483+
result.colorBufferSizeInBytes = result.colorBufferWidth * result.height * 8;
484484
const colorBuffer = vtkWebGPUBuffer.newInstance();
485485
colorBuffer.setDevice(device);
486486
/* eslint-disable no-bitwise */
@@ -499,7 +499,7 @@ function vtkWebGPURenderWindow(publicAPI, model) {
499499
},
500500
{
501501
buffer: colorBuffer.getHandle(),
502-
bytesPerRow: 4 * result.colorBufferWidth,
502+
bytesPerRow: 8 * result.colorBufferWidth,
503503
rowsPerImage: result.height,
504504
},
505505
{
@@ -515,20 +515,22 @@ function vtkWebGPURenderWindow(publicAPI, model) {
515515
await cLoad;
516516
/* eslint-enable no-undef */
517517

518-
result.colorValues = new Uint8ClampedArray(
519-
colorBuffer.getMappedRange().slice()
520-
);
518+
result.colorValues = new Uint16Array(colorBuffer.getMappedRange().slice());
521519
colorBuffer.unmap();
522520
// repack the array
523521
const tmparray = new Uint8ClampedArray(result.height * result.width * 4);
524522
for (let y = 0; y < result.height; y++) {
525523
for (let x = 0; x < result.width; x++) {
526524
const doffset = (y * result.width + x) * 4;
527525
const soffset = (y * result.colorBufferWidth + x) * 4;
528-
tmparray[doffset] = result.colorValues[soffset + 2];
529-
tmparray[doffset + 1] = result.colorValues[soffset + 1];
530-
tmparray[doffset + 2] = result.colorValues[soffset];
531-
tmparray[doffset + 3] = result.colorValues[soffset + 3];
526+
tmparray[doffset] =
527+
255.0 * HalfFloat.fromHalf(result.colorValues[soffset]);
528+
tmparray[doffset + 1] =
529+
255.0 * HalfFloat.fromHalf(result.colorValues[soffset + 1]);
530+
tmparray[doffset + 2] =
531+
255.0 * HalfFloat.fromHalf(result.colorValues[soffset + 2]);
532+
tmparray[doffset + 3] =
533+
255.0 * HalfFloat.fromHalf(result.colorValues[soffset + 3]);
532534
}
533535
}
534536
result.colorValues = tmparray;
@@ -564,6 +566,7 @@ const DEFAULT_VALUES = {
564566
useBackgroundImage: false,
565567
nextPropID: 1,
566568
xrSupported: false,
569+
presentationFormat: null,
567570
};
568571

569572
// ----------------------------------------------------------------------------
@@ -607,6 +610,7 @@ export function extend(publicAPI, model, initialValues = {}) {
607610
macro.get(publicAPI, model, [
608611
'commandEncoder',
609612
'device',
613+
'presentationFormat',
610614
'useBackgroundImage',
611615
'xrSupported',
612616
]);

Sources/Rendering/WebGPU/TextureView/index.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,19 @@ function vtkWebGPUTextureView(publicAPI, model) {
2626
model.bindGroupLayoutEntry.texture.sampleType = tDetails.sampleType;
2727
};
2828

29+
publicAPI.createFromTextureHandle = (textureHandle, options) => {
30+
model.texture = null;
31+
model.options = options;
32+
model.options.dimension = model.options.dimension || '2d';
33+
model.options.label = model.label;
34+
model.textureHandle = textureHandle;
35+
model.handle = model.textureHandle.createView(model.options);
36+
model.bindGroupLayoutEntry.texture.viewDimension = model.options.dimension;
37+
const tDetails = vtkWebGPUTypes.getDetailsFromTextureFormat(options.format);
38+
model.bindGroupLayoutEntry.texture.sampleType = tDetails.sampleType;
39+
model.bindGroupTime.modified();
40+
};
41+
2942
publicAPI.getBindGroupEntry = () => {
3043
const foo = {
3144
resource: publicAPI.getHandle(),
@@ -57,7 +70,7 @@ function vtkWebGPUTextureView(publicAPI, model) {
5770

5871
publicAPI.getBindGroupTime = () => {
5972
// check if the handle changed
60-
if (model.texture.getHandle() !== model.textureHandle) {
73+
if (model.texture && model.texture.getHandle() !== model.textureHandle) {
6174
model.textureHandle = model.texture.getHandle();
6275
model.handle = model.textureHandle.createView(model.options);
6376
model.bindGroupTime.modified();
@@ -67,7 +80,7 @@ function vtkWebGPUTextureView(publicAPI, model) {
6780

6881
// if the texture has changed then get a new view
6982
publicAPI.getHandle = () => {
70-
if (model.texture.getHandle() !== model.textureHandle) {
83+
if (model.texture && model.texture.getHandle() !== model.textureHandle) {
7184
model.textureHandle = model.texture.getHandle();
7285
model.handle = model.textureHandle.createView(model.options);
7386
model.bindGroupTime.modified();

Sources/Rendering/WebGPU/VolumePass/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -629,7 +629,7 @@ function vtkWebGPUVolumePass(publicAPI, model) {
629629
fragment: {
630630
targets: [
631631
{
632-
format: 'bgra8unorm',
632+
format: 'rgba16float',
633633
blend: {
634634
color: {
635635
srcFactor: 'one',

0 commit comments

Comments
 (0)