Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 84 additions & 23 deletions Sources/Rendering/WebGPU/OpaquePass/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,50 +22,94 @@ function vtkWebGPUOpaquePass(publicAPI, model) {
model._currentParent = viewNode;

const device = viewNode.getDevice();
const sampleCount = viewNode.getMultiSample ? viewNode.getMultiSample() : 1;

// If sampleCount changed since last render, tear down and recreate
if (model.renderEncoder && model._currentSampleCount !== sampleCount) {
model.renderEncoder = null;
model.colorTexture = null;
model.depthTexture = null;
model.resolveColorTexture = null;
model._resolveColorTextureView = null;
}

if (!model.renderEncoder) {
publicAPI.createRenderEncoder();
publicAPI.createRenderEncoder(sampleCount);
model._currentSampleCount = sampleCount;

const width = viewNode.getCanvas().width;
const height = viewNode.getCanvas().height;

// Color texture — multisampled when sampleCount > 1
model.colorTexture = vtkWebGPUTexture.newInstance({
label: 'opaquePassColor',
});
/* eslint-disable no-undef */
/* eslint-disable no-bitwise */
model.colorTexture.create(device, {
width: viewNode.getCanvas().width,
height: viewNode.getCanvas().height,
width,
height,
format: 'rgba16float',
/* eslint-disable no-undef */
/* eslint-disable no-bitwise */
sampleCount,
usage:
GPUTextureUsage.RENDER_ATTACHMENT |
GPUTextureUsage.TEXTURE_BINDING |
GPUTextureUsage.COPY_SRC,
(sampleCount === 1
? GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC
: 0),
});
const ctView = model.colorTexture.createView('opaquePassColorTexture');
model.renderEncoder.setColorTextureView(0, ctView);

// When MSAA is active, create a resolve target (1-sample) for
// downstream passes that need to sample the color result
if (sampleCount > 1) {
model.resolveColorTexture = vtkWebGPUTexture.newInstance({
label: 'opaquePassResolveColor',
});
model.resolveColorTexture.create(device, {
width,
height,
format: 'rgba16float',
usage:
GPUTextureUsage.RENDER_ATTACHMENT |
GPUTextureUsage.TEXTURE_BINDING |
GPUTextureUsage.COPY_SRC,
});
model._resolveColorTextureView = model.resolveColorTexture.createView(
'opaquePassColorTexture'
);
const resolveView = model._resolveColorTextureView;
model.renderEncoder.setResolveTextureView(0, resolveView);
}

// Depth texture — also multisampled
model.depthFormat = 'depth32float';
model.depthTexture = vtkWebGPUTexture.newInstance({
label: 'opaquePassDepth',
});
model.depthTexture.create(device, {
width: viewNode.getCanvas().width,
height: viewNode.getCanvas().height,
width,
height,
format: model.depthFormat,
sampleCount,
usage:
GPUTextureUsage.RENDER_ATTACHMENT |
GPUTextureUsage.TEXTURE_BINDING |
GPUTextureUsage.COPY_SRC,
(sampleCount === 1
? GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC
: 0),
});
/* eslint-enable no-undef */
/* eslint-enable no-bitwise */
const dView = model.depthTexture.createView('opaquePassDepthTexture');
model.renderEncoder.setDepthTextureView(dView);
} else {
model.colorTexture.resize(
viewNode.getCanvas().width,
viewNode.getCanvas().height
);
model.depthTexture.resize(
viewNode.getCanvas().width,
viewNode.getCanvas().height
);
const width = viewNode.getCanvas().width;
const height = viewNode.getCanvas().height;
model.colorTexture.resize(width, height);
model.depthTexture.resize(width, height);
if (model.resolveColorTexture) {
model.resolveColorTexture.resize(width, height);
}
}

model.renderEncoder.attachTextureViews();
Expand All @@ -74,18 +118,30 @@ function vtkWebGPUOpaquePass(publicAPI, model) {
renNode.traverse(publicAPI);
};

publicAPI.getColorTextureView = () =>
model.renderEncoder.getColorTextureViews()[0];
// When MSAA is active, downstream passes must sample from the resolved
// (1-sample) texture, not the multisampled one
publicAPI.getColorTextureView = () => {
if (model._resolveColorTextureView) {
return model._resolveColorTextureView;
}
return model.renderEncoder.getColorTextureViews()[0];
};

publicAPI.getDepthTextureView = () =>
model.renderEncoder.getDepthTextureView();

publicAPI.createRenderEncoder = () => {
publicAPI.createRenderEncoder = (sampleCount = 1) => {
model.renderEncoder = vtkWebGPURenderEncoder.newInstance({
label: 'OpaquePass',
});
// default settings are fine for this
model.renderEncoder.setPipelineHash('op');
// Set multisample state in pipeline settings when MSAA is active
if (sampleCount > 1) {
const settings = model.renderEncoder.getPipelineSettings();
settings.multisample = { count: sampleCount };
model.renderEncoder.setPipelineSettings(settings);
}
};
}

Expand All @@ -97,6 +153,7 @@ const DEFAULT_VALUES = {
renderEncoder: null,
colorTexture: null,
depthTexture: null,
resolveColorTexture: null,
};

// ----------------------------------------------------------------------------
Expand All @@ -107,7 +164,11 @@ export function extend(publicAPI, model, initialValues = {}) {
// Build VTK API
vtkRenderPass.extend(publicAPI, model, initialValues);

macro.get(publicAPI, model, ['colorTexture', 'depthTexture']);
macro.get(publicAPI, model, [
'colorTexture',
'depthTexture',
'resolveColorTexture',
]);

// Object methods
vtkWebGPUOpaquePass(publicAPI, model);
Expand Down
105 changes: 94 additions & 11 deletions Sources/Rendering/WebGPU/OrderIndependentTranslucentPass/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,15 @@ fn main(
}
`;

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

function vtkWebGPUOrderIndependentTranslucentPass(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkWebGPUOrderIndependentTranslucentPass');

// this pass implements a forward rendering pipeline
// if both volumes and opaque geometry are present
// it will mix the two together by capturing a zbuffer
// first
// This pass implements a forward rendering pipeline for translucent geometry.
// It uses order-independent transparency (OIT) with weighted blended
// compositing, reading the opaque depth buffer for depth testing.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sankhesh is the OIT pass really alpha blending in VTK.js ? According to Lucas Gandel, it is not just an alpha blending pass in VTK (hence there would be more to do here).

publicAPI.traverse = (renNode, viewNode) => {
if (model.deleted) {
return;
Expand All @@ -53,46 +54,112 @@ function vtkWebGPUOrderIndependentTranslucentPass(publicAPI, model) {
model._currentParent = viewNode;

const device = viewNode.getDevice();
const sampleCount = viewNode.getMultiSample ? viewNode.getMultiSample() : 1;

// If sampleCount changed, tear down
if (
model.translucentRenderEncoder &&
model._currentSampleCount !== sampleCount
) {
model.translucentRenderEncoder = null;
model.translucentColorTexture = null;
model.translucentAccumulateTexture = null;
model.translucentResolveColorTexture = null;
model.translucentResolveAccumulateTexture = null;
model._resolveColorView = null;
model._resolveAccumulateView = null;
}

if (!model.translucentRenderEncoder) {
publicAPI.createRenderEncoder();
publicAPI.createRenderEncoder(sampleCount);
publicAPI.createFinalEncoder();
model._currentSampleCount = sampleCount;
model.translucentColorTexture = vtkWebGPUTexture.newInstance({
label: 'translucentPassColor',
});
model.translucentColorTexture.create(device, {
width: viewNode.getCanvas().width,
height: viewNode.getCanvas().height,
format: 'rgba16float',
sampleCount,
/* eslint-disable no-undef */
/* eslint-disable no-bitwise */
usage:
GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
GPUTextureUsage.RENDER_ATTACHMENT |
(sampleCount === 1 ? GPUTextureUsage.TEXTURE_BINDING : 0),
});
const v1 = model.translucentColorTexture.createView('oitpColorTexture');
model.translucentRenderEncoder.setColorTextureView(0, v1);

// Resolve color texture for MSAA
if (sampleCount > 1) {
model.translucentResolveColorTexture = vtkWebGPUTexture.newInstance({
label: 'translucentPassResolveColor',
});
model.translucentResolveColorTexture.create(device, {
width: viewNode.getCanvas().width,
height: viewNode.getCanvas().height,
format: 'rgba16float',
usage:
GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
});
model._resolveColorView =
model.translucentResolveColorTexture.createView('oitpColorTexture');
model.translucentRenderEncoder.setResolveTextureView(
0,
model._resolveColorView
);
}

model.translucentAccumulateTexture = vtkWebGPUTexture.newInstance({
label: 'translucentPassAccumulate',
});
model.translucentAccumulateTexture.create(device, {
width: viewNode.getCanvas().width,
height: viewNode.getCanvas().height,
format: 'r16float',
sampleCount,
/* eslint-disable no-undef */
/* eslint-disable no-bitwise */
usage:
GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
GPUTextureUsage.RENDER_ATTACHMENT |
(sampleCount === 1 ? GPUTextureUsage.TEXTURE_BINDING : 0),
});
const v2 =
model.translucentAccumulateTexture.createView('oitpAccumTexture');
model.translucentRenderEncoder.setColorTextureView(1, v2);

// Resolve accumulate texture for MSAA
if (sampleCount > 1) {
model.translucentResolveAccumulateTexture =
vtkWebGPUTexture.newInstance({
label: 'translucentPassResolveAccumulate',
});
model.translucentResolveAccumulateTexture.create(device, {
width: viewNode.getCanvas().width,
height: viewNode.getCanvas().height,
format: 'r16float',
usage:
GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
});
model._resolveAccumulateView =
model.translucentResolveAccumulateTexture.createView(
'oitpAccumTexture'
);
model.translucentRenderEncoder.setResolveTextureView(
1,
model._resolveAccumulateView
);
}
model.fullScreenQuad = vtkWebGPUFullScreenQuad.newInstance();
model.fullScreenQuad.setDevice(viewNode.getDevice());
model.fullScreenQuad.setPipelineHash('oitpfsq');
model.fullScreenQuad.setTextureViews(
model.translucentRenderEncoder.getColorTextureViews()
);
// Use resolved textures for the full screen quad if MSAA is on
const views =
sampleCount > 1
? [model._resolveColorView, model._resolveAccumulateView]
: model.translucentRenderEncoder.getColorTextureViews();
model.fullScreenQuad.setTextureViews(views);
model.fullScreenQuad.setFragmentShaderTemplate(oitpFragTemplate);
} else {
model.translucentColorTexture.resizeToMatch(
Expand All @@ -101,6 +168,14 @@ function vtkWebGPUOrderIndependentTranslucentPass(publicAPI, model) {
model.translucentAccumulateTexture.resizeToMatch(
model.colorTextureView.getTexture()
);
if (model.translucentResolveColorTexture) {
model.translucentResolveColorTexture.resizeToMatch(
model.colorTextureView.getTexture()
);
model.translucentResolveAccumulateTexture.resizeToMatch(
model.colorTextureView.getTexture()
);
}
}

model.translucentRenderEncoder.setDepthTextureView(model.depthTextureView);
Expand Down Expand Up @@ -129,10 +204,16 @@ function vtkWebGPUOrderIndependentTranslucentPass(publicAPI, model) {
model.translucentAccumulateTexture,
];

publicAPI.createRenderEncoder = () => {
publicAPI.createRenderEncoder = (sampleCount = 1) => {
model.translucentRenderEncoder = vtkWebGPURenderEncoder.newInstance({
label: 'translucentRender',
});
// Set multisample state if needed
if (sampleCount > 1) {
const settings = model.translucentRenderEncoder.getPipelineSettings();
settings.multisample = { count: sampleCount };
model.translucentRenderEncoder.setPipelineSettings(settings);
}
const rDesc = model.translucentRenderEncoder.getDescription();
rDesc.colorAttachments = [
{
Expand Down Expand Up @@ -261,6 +342,8 @@ function vtkWebGPUOrderIndependentTranslucentPass(publicAPI, model) {
const DEFAULT_VALUES = {
colorTextureView: null,
depthTextureView: null,
translucentResolveColorTexture: null,
translucentResolveAccumulateTexture: null,
};

// ----------------------------------------------------------------------------
Expand Down
16 changes: 16 additions & 0 deletions Sources/Rendering/WebGPU/RenderEncoder/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ function vtkWebGPURenderEncoder(publicAPI, model) {
model.colorTextureViews[idx] = view;
};

publicAPI.setResolveTextureView = (idx, view) => {
if (model.resolveTextureViews[idx] === view) {
return;
}
model.resolveTextureViews[idx] = view;
};

publicAPI.activateBindGroup = (bg) => {
const device = model.boundPipeline.getDevice();
const midx = model.boundPipeline.getBindGroupLayoutCount(bg.getLabel());
Expand Down Expand Up @@ -126,6 +133,14 @@ function vtkWebGPURenderEncoder(publicAPI, model) {
model.description.colorAttachments[i].view =
model.colorTextureViews[i].getHandle();
}
// MSAA: set resolveTarget if a resolve texture view is provided
if (model.resolveTextureViews[i]) {
model.description.colorAttachments[i].resolveTarget =
model.resolveTextureViews[i].getHandle();
// When using MSAA, the multisampled texture is transient;
// store only into the resolve target
model.description.colorAttachments[i].storeOp = 'discard';
}
}
if (model.depthTextureView) {
model.description.depthStencilAttachment.view =
Expand Down Expand Up @@ -225,6 +240,7 @@ export function extend(publicAPI, model, initialValues = {}) {
};

model.colorTextureViews = [];
model.resolveTextureViews = [];

macro.get(publicAPI, model, ['boundPipeline', 'colorTextureViews']);

Expand Down
4 changes: 0 additions & 4 deletions Sources/Rendering/WebGPU/RenderWindow/api.md

This file was deleted.

Loading