Skip to content

Commit b771c7d

Browse files
authored
Merge pull request #2541 from Kitware/webgpu_use_half_float_buffers
feat(WebGPU): update to use half float buffers
2 parents 0e7a80c + 109672c commit b771c7d

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)