Skip to content

Commit

Permalink
feat(WebGPU): update to use half float buffers
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Ken Martin committed Jul 29, 2022
1 parent 0e7a80c commit 109672c
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 39 deletions.
112 changes: 97 additions & 15 deletions Sources/Rendering/WebGPU/ForwardPass/index.js
@@ -1,8 +1,36 @@
import macro from 'vtk.js/Sources/macros';
import vtkWebGPUFullScreenQuad from 'vtk.js/Sources/Rendering/WebGPU/FullScreenQuad';
import vtkWebGPUOpaquePass from 'vtk.js/Sources/Rendering/WebGPU/OpaquePass';
import vtkWebGPUOrderIndepenentTranslucentPass from 'vtk.js/Sources/Rendering/WebGPU/OrderIndependentTranslucentPass';
import vtkWebGPURenderEncoder from 'vtk.js/Sources/Rendering/WebGPU/RenderEncoder';
import vtkWebGPUVolumePass from 'vtk.js/Sources/Rendering/WebGPU/VolumePass';
import vtkRenderPass from 'vtk.js/Sources/Rendering/SceneGraph/RenderPass';
import vtkWebGPUSampler from 'vtk.js/Sources/Rendering/WebGPU/Sampler';
import vtkWebGPUTextureView from 'vtk.js/Sources/Rendering/WebGPU/TextureView';

const finalBlitFragTemplate = `
//VTK::Mapper::Dec
//VTK::TCoord::Dec
//VTK::RenderEncoder::Dec
//VTK::IOStructs::Dec
@fragment
fn main(
//VTK::IOStructs::Input
)
//VTK::IOStructs::Output
{
var output: fragmentOutput;
var computedColor: vec4<f32> = clamp(textureSampleLevel(opaquePassColorTexture, finalPassSampler, input.tcoordVS, 0),vec4<f32>(0.0),vec4<f32>(1.0));
//VTK::RenderEncoder::Impl
return output;
}
`;

// ----------------------------------------------------------------------------

Expand Down Expand Up @@ -83,26 +111,80 @@ function vtkForwardPass(publicAPI, model) {
model.volumePass.setVolumes(model.volumes);
model.volumePass.traverse(renNode, viewNode);
}

// blit the result into the swap chain
publicAPI.finalPass(viewNode, renNode);
}
}
}
};

// blit the result into the swap chain
const sctex = viewNode.getCurrentTexture();
const optex = model.opaquePass.getColorTexture();
const cmdEnc = viewNode.getCommandEncoder();
cmdEnc.copyTextureToTexture(
{
texture: optex.getHandle(),
},
{
texture: sctex,
publicAPI.finalPass = (viewNode, renNode) => {
if (!model._finalBlitEncoder) {
publicAPI.createFinalBlitEncoder(viewNode);
}
model._finalBlitOutputTextureView.createFromTextureHandle(
viewNode.getCurrentTexture(),
{ depth: 1, format: viewNode.getPresentationFormat() }
);

model._finalBlitEncoder.attachTextureViews();
model._finalBlitEncoder.begin(viewNode.getCommandEncoder());
renNode.scissorAndViewport(model._finalBlitEncoder);
model._fullScreenQuad.prepareAndDraw(model._finalBlitEncoder);
model._finalBlitEncoder.end();
};

publicAPI.createFinalBlitEncoder = (viewNode) => {
model._finalBlitEncoder = vtkWebGPURenderEncoder.newInstance({
label: 'forwardPassBlit',
});
model._finalBlitEncoder.setDescription({
colorAttachments: [
{
view: null,
loadOp: 'load',
storeOp: 'store',
},
],
});
model._finalBlitEncoder.setPipelineHash('fpf');
model._finalBlitEncoder.setPipelineSettings({
primitive: { cullMode: 'none' },
fragment: {
targets: [
{
format: viewNode.getPresentationFormat(),
blend: {
color: {
srcFactor: 'src-alpha',
dstFactor: 'one-minus-src-alpha',
},
alpha: { srcfactor: 'one', dstFactor: 'one-minus-src-alpha' },
},
},
],
},
{
width: optex.getWidth(),
height: optex.getHeight(),
depthOrArrayLayers: 1,
}
});
model._fsqSampler = vtkWebGPUSampler.newInstance({
label: 'finalPassSampler',
});
model._fsqSampler.create(viewNode.getDevice(), {
minFilter: 'linear',
magFilter: 'linear',
});
model._fullScreenQuad = vtkWebGPUFullScreenQuad.newInstance();
model._fullScreenQuad.setDevice(viewNode.getDevice());
model._fullScreenQuad.setPipelineHash('fpfsq');
model._fullScreenQuad.setTextureViews([
model.opaquePass.getColorTextureView(),
]);
model._fullScreenQuad.setAdditionalBindables([model._fsqSampler]);
model._fullScreenQuad.setFragmentShaderTemplate(finalBlitFragTemplate);
model._finalBlitOutputTextureView = vtkWebGPUTextureView.newInstance();
model._finalBlitEncoder.setColorTextureView(
0,
model._finalBlitOutputTextureView
);
};

Expand Down
2 changes: 1 addition & 1 deletion Sources/Rendering/WebGPU/OpaquePass/index.js
Expand Up @@ -31,7 +31,7 @@ function vtkWebGPUOpaquePass(publicAPI, model) {
model.colorTexture.create(device, {
width: viewNode.getCanvas().width,
height: viewNode.getCanvas().height,
format: 'bgra8unorm',
format: 'rgba16float',
/* eslint-disable no-undef */
/* eslint-disable no-bitwise */
usage:
Expand Down
Expand Up @@ -239,7 +239,7 @@ function vtkWebGPUOrderIndependentTranslucentPass(publicAPI, model) {
fragment: {
targets: [
{
format: 'bgra8unorm',
format: 'rgba16float',
blend: {
color: {
srcFactor: 'src-alpha',
Expand Down
10 changes: 5 additions & 5 deletions Sources/Rendering/WebGPU/RenderEncoder/index.js
Expand Up @@ -59,8 +59,8 @@ function vtkWebGPURenderEncoder(publicAPI, model) {
console.trace();
} else {
for (let i = 0; i < model.colorTextureViews.length; i++) {
const fmt = model.colorTextureViews[i].getTexture().getFormat();
if (fmt !== pd.fragment.targets[i].format) {
const fmt = model.colorTextureViews[i].getTexture()?.getFormat();
if (fmt && fmt !== pd.fragment.targets[i].format) {
console.log(
`mismatched attachments for attachment ${i} on pipeline ${pd.fragment.targets[i].format} while encoder has ${fmt}`
);
Expand All @@ -74,8 +74,8 @@ function vtkWebGPURenderEncoder(publicAPI, model) {
console.log('mismatched depth attachments');
console.trace();
} else if (model.depthTextureView) {
const dfmt = model.depthTextureView.getTexture().getFormat();
if (dfmt !== pd.depthStencil.format) {
const dfmt = model.depthTextureView.getTexture()?.getFormat();
if (dfmt && dfmt !== pd.depthStencil.format) {
console.log(
`mismatched depth attachments on pipeline ${pd.depthStencil.format} while encoder has ${dfmt}`
);
Expand Down Expand Up @@ -211,7 +211,7 @@ export function extend(publicAPI, model, initialValues = {}) {
fragment: {
targets: [
{
format: 'bgra8unorm',
format: 'rgba16float',
blend: {
color: {
srcFactor: 'src-alpha',
Expand Down
32 changes: 18 additions & 14 deletions Sources/Rendering/WebGPU/RenderWindow/index.js
Expand Up @@ -7,7 +7,7 @@ import vtkWebGPUHardwareSelector from 'vtk.js/Sources/Rendering/WebGPU/HardwareS
import vtkWebGPUViewNodeFactory from 'vtk.js/Sources/Rendering/WebGPU/ViewNodeFactory';
import vtkRenderPass from 'vtk.js/Sources/Rendering/SceneGraph/RenderPass';
import vtkRenderWindowViewNode from 'vtk.js/Sources/Rendering/SceneGraph/RenderWindowViewNode';
// import { VtkDataTypes } from 'vtk.js/Sources/Common/Core/DataArray/Constants';
import HalfFloat from 'vtk.js/Sources/Common/Core/HalfFloat';

const { vtkErrorMacro } = macro;
// const IS_CHROME = navigator.userAgent.indexOf('Chrome') !== -1;
Expand Down Expand Up @@ -68,15 +68,15 @@ function vtkWebGPURenderWindow(publicAPI, model) {
publicAPI.recreateSwapChain = () => {
if (model.context) {
model.context.unconfigure();
const presentationFormat = navigator.gpu.getPreferredCanvasFormat(
model.presentationFormat = navigator.gpu.getPreferredCanvasFormat(
model.adapter
);

/* eslint-disable no-undef */
/* eslint-disable no-bitwise */
model.context.configure({
device: model.device.getHandle(),
format: presentationFormat,
format: model.presentationFormat,
alphaMode: 'premultiplied',
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_DST,
width: model.size[0],
Expand Down Expand Up @@ -478,9 +478,9 @@ function vtkWebGPURenderWindow(publicAPI, model) {
height: texture.getHeight(),
};

// must be a multiple of 256 bytes, so 64 texels with rgba8
result.colorBufferWidth = 64 * Math.floor((result.width + 63) / 64);
result.colorBufferSizeInBytes = result.colorBufferWidth * result.height * 4;
// must be a multiple of 256 bytes, so 32 texels with rgba16
result.colorBufferWidth = 32 * Math.floor((result.width + 31) / 32);
result.colorBufferSizeInBytes = result.colorBufferWidth * result.height * 8;
const colorBuffer = vtkWebGPUBuffer.newInstance();
colorBuffer.setDevice(device);
/* eslint-disable no-bitwise */
Expand All @@ -499,7 +499,7 @@ function vtkWebGPURenderWindow(publicAPI, model) {
},
{
buffer: colorBuffer.getHandle(),
bytesPerRow: 4 * result.colorBufferWidth,
bytesPerRow: 8 * result.colorBufferWidth,
rowsPerImage: result.height,
},
{
Expand All @@ -515,20 +515,22 @@ function vtkWebGPURenderWindow(publicAPI, model) {
await cLoad;
/* eslint-enable no-undef */

result.colorValues = new Uint8ClampedArray(
colorBuffer.getMappedRange().slice()
);
result.colorValues = new Uint16Array(colorBuffer.getMappedRange().slice());
colorBuffer.unmap();
// repack the array
const tmparray = new Uint8ClampedArray(result.height * result.width * 4);
for (let y = 0; y < result.height; y++) {
for (let x = 0; x < result.width; x++) {
const doffset = (y * result.width + x) * 4;
const soffset = (y * result.colorBufferWidth + x) * 4;
tmparray[doffset] = result.colorValues[soffset + 2];
tmparray[doffset + 1] = result.colorValues[soffset + 1];
tmparray[doffset + 2] = result.colorValues[soffset];
tmparray[doffset + 3] = result.colorValues[soffset + 3];
tmparray[doffset] =
255.0 * HalfFloat.fromHalf(result.colorValues[soffset]);
tmparray[doffset + 1] =
255.0 * HalfFloat.fromHalf(result.colorValues[soffset + 1]);
tmparray[doffset + 2] =
255.0 * HalfFloat.fromHalf(result.colorValues[soffset + 2]);
tmparray[doffset + 3] =
255.0 * HalfFloat.fromHalf(result.colorValues[soffset + 3]);
}
}
result.colorValues = tmparray;
Expand Down Expand Up @@ -564,6 +566,7 @@ const DEFAULT_VALUES = {
useBackgroundImage: false,
nextPropID: 1,
xrSupported: false,
presentationFormat: null,
};

// ----------------------------------------------------------------------------
Expand Down Expand Up @@ -607,6 +610,7 @@ export function extend(publicAPI, model, initialValues = {}) {
macro.get(publicAPI, model, [
'commandEncoder',
'device',
'presentationFormat',
'useBackgroundImage',
'xrSupported',
]);
Expand Down
17 changes: 15 additions & 2 deletions Sources/Rendering/WebGPU/TextureView/index.js
Expand Up @@ -26,6 +26,19 @@ function vtkWebGPUTextureView(publicAPI, model) {
model.bindGroupLayoutEntry.texture.sampleType = tDetails.sampleType;
};

publicAPI.createFromTextureHandle = (textureHandle, options) => {
model.texture = null;
model.options = options;
model.options.dimension = model.options.dimension || '2d';
model.options.label = model.label;
model.textureHandle = textureHandle;
model.handle = model.textureHandle.createView(model.options);
model.bindGroupLayoutEntry.texture.viewDimension = model.options.dimension;
const tDetails = vtkWebGPUTypes.getDetailsFromTextureFormat(options.format);
model.bindGroupLayoutEntry.texture.sampleType = tDetails.sampleType;
model.bindGroupTime.modified();
};

publicAPI.getBindGroupEntry = () => {
const foo = {
resource: publicAPI.getHandle(),
Expand Down Expand Up @@ -57,7 +70,7 @@ function vtkWebGPUTextureView(publicAPI, model) {

publicAPI.getBindGroupTime = () => {
// check if the handle changed
if (model.texture.getHandle() !== model.textureHandle) {
if (model.texture && model.texture.getHandle() !== model.textureHandle) {
model.textureHandle = model.texture.getHandle();
model.handle = model.textureHandle.createView(model.options);
model.bindGroupTime.modified();
Expand All @@ -67,7 +80,7 @@ function vtkWebGPUTextureView(publicAPI, model) {

// if the texture has changed then get a new view
publicAPI.getHandle = () => {
if (model.texture.getHandle() !== model.textureHandle) {
if (model.texture && model.texture.getHandle() !== model.textureHandle) {
model.textureHandle = model.texture.getHandle();
model.handle = model.textureHandle.createView(model.options);
model.bindGroupTime.modified();
Expand Down
2 changes: 1 addition & 1 deletion Sources/Rendering/WebGPU/VolumePass/index.js
Expand Up @@ -629,7 +629,7 @@ function vtkWebGPUVolumePass(publicAPI, model) {
fragment: {
targets: [
{
format: 'bgra8unorm',
format: 'rgba16float',
blend: {
color: {
srcFactor: 'one',
Expand Down

0 comments on commit 109672c

Please sign in to comment.