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
2 changes: 2 additions & 0 deletions Sources/Rendering/WebGPU/Device/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ function vtkWebGPUDevice(publicAPI, model) {
model.handle = handle;
};

publicAPI.hasFeature = (name) => !!model.handle?.features?.has(name);

publicAPI.createCommandEncoder = () => model.handle.createCommandEncoder();

publicAPI.submitCommandEncoder = (commandEncoder) => {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Rendering/WebGPU/HardwareSelectionPass/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ function vtkWebGPUHardwareSelectionPass(publicAPI, model) {
primitive: { cullMode: 'none' },
depthStencil: {
depthWriteEnabled: true,
depthCompare: 'greater',
depthCompare: 'greater-equal',
format: 'depth32float',
},
fragment: {
Expand Down
7 changes: 6 additions & 1 deletion Sources/Rendering/WebGPU/HardwareSelector/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,11 @@ function vtkWebGPUHardwareSelector(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkWebGPUHardwareSelector');

publicAPI.attach = (webGPURenderWindow, renderer) => {
model._WebGPURenderWindow = webGPURenderWindow;
model._renderer = renderer;
};

publicAPI.getPropIDForSelection = (runtimePropID, prop = null) => {
if (model._selectionPropMap.has(runtimePropID)) {
return model._selectionPropMap.get(runtimePropID);
Expand All @@ -296,7 +301,7 @@ function vtkWebGPUHardwareSelector(publicAPI, model) {
// of the entire depth bufer. We could realloc hardware selection textures
// based on the passed in size etc but it gets messy so for now we always
// render the full size window and copy it to the buffers.
publicAPI.getSourceDataAsync = async (renderer) => {
publicAPI.getSourceDataAsync = async (renderer = model._renderer) => {
if (!renderer || !model._WebGPURenderWindow) {
vtkErrorMacro('Renderer and view must be set before calling Select.');
return false;
Expand Down
32 changes: 26 additions & 6 deletions Sources/Rendering/WebGPU/Helpers/ImageSampling.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ function textureSamplerMatches(textureView, options) {
return (
current.minFilter === options.minFilter &&
current.magFilter === options.magFilter &&
current.mipmapFilter === (options.mipmapFilter ?? 'nearest') &&
current.addressModeU === (options.addressModeU ?? 'clamp-to-edge') &&
current.addressModeV === (options.addressModeV ?? 'clamp-to-edge') &&
current.addressModeW === (options.addressModeW ?? 'clamp-to-edge')
Expand Down Expand Up @@ -57,13 +58,32 @@ function getUseLabelOutline(
);
}

function computeFnToString(property, fn, numberOfComponents) {
const pwfun = fn.apply(property);
if (pwfun) {
const iComps = property.getIndependentComponents();
return `${property.getMTime()}-${iComps}-${numberOfComponents}`;
/**
* Compute a string that uniquely identifies the texture sampling function for a given property.
* This is used to determine if a cached texture can be reused or if a new texture needs to be generated.
* @param {*} property
* @param {*} fn
* @param {*} numberOfComponents
* @param {*} options
* @returns
*/
function computeFnToString(property, fn, numberOfComponents, options = {}) {
// Shared textures must have the same layout and source functions. The cache
// key contains both items to prevent an incorrect texture match.
const parts = [
options.label ?? 'tfun',
options.rowLength ?? 0,
numberOfComponents,
];
for (let c = 0; c < numberOfComponents; c++) {
const tf = fn.call(property, c);
if (tf) {
parts.push(`${tf.getMTime()}:${tf.getRange().join(',')}`);
} else {
parts.push('none');
}
}
return '0';
return parts.join('-');
}

export {
Expand Down
29 changes: 29 additions & 0 deletions Sources/Rendering/WebGPU/Helpers/test/testImageSampling.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { expect, it } from 'vitest';

import { computeFnToString } from 'vtk.js/Sources/Rendering/WebGPU/Helpers/ImageSampling';

function createTransferFunction(mtime) {
return {
getMTime: () => mtime,
getRange: () => [0, 1],
};
}

it('includes transfer functions from every component in the cache key', () => {
const propertyA = {
getTransferFunction(component) {
return component === 1 ? createTransferFunction(1) : null;
},
};
const propertyB = {
getTransferFunction(component) {
return component === 1 ? createTransferFunction(2) : null;
},
};

const keyA = computeFnToString(propertyA, propertyA.getTransferFunction, 2);
const keyB = computeFnToString(propertyB, propertyB.getTransferFunction, 2);

expect(keyA).not.toBe(keyB);
expect(keyA).toContain('none-1:0,1');
});
33 changes: 29 additions & 4 deletions Sources/Rendering/WebGPU/ImageCPRMapper/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -468,9 +468,23 @@ function vtkWebGPUImageCPRMapper(publicAPI, model) {
};

publicAPI.updateVolumeTexture = () => {
// Updated extents are valid for one upload. Reuse the current texture and
// then clear the extents to prevent repeated writes.
const property = model.WebGPUImageSlice.getRenderable().getProperty();
const updatedExtents = property?.getUpdatedExtents?.() ?? [];
const existingTexture = model.textureViews[0]?.getTexture();
const preferSizeOverAccuracy =
!!model.renderable.getPreferSizeOverAccuracy?.();
const newTex = model.device
.getTextureManager()
.getTextureForImageData(model.currentImageDataInput);
.getTextureForImageData(model.currentImageDataInput, {
updatedExtents,
existingTexture,
preferSizeOverAccuracy,
});
if (updatedExtents.length) {
property.setUpdatedExtents([]);
}
if (
!model.textureViews[0] ||
model.textureViews[0].getTexture() !== newTex
Expand All @@ -493,7 +507,8 @@ function vtkWebGPUImageCPRMapper(publicAPI, model) {
const colorTextureString = computeFnToString(
property,
property.getRGBTransferFunction,
numRows
numRows,
{ label: 'cprColorLUT', rowLength: model.rowLength }
);

if (model.colorTextureString === colorTextureString) {
Expand Down Expand Up @@ -529,6 +544,7 @@ function vtkWebGPUImageCPRMapper(publicAPI, model) {
}

const newTex = model.device.getTextureManager().getTexture({
hash: colorTextureString,
nativeArray: colorArray,
width: model.rowLength,
height: numRows,
Expand All @@ -553,7 +569,8 @@ function vtkWebGPUImageCPRMapper(publicAPI, model) {
const pwfTextureString = computeFnToString(
property,
property.getPiecewiseFunction,
numRows
numRows,
{ label: 'cprOpacityLUT', rowLength: model.rowLength }
);

if (model.pwfTextureString === pwfTextureString) {
Expand Down Expand Up @@ -589,6 +606,7 @@ function vtkWebGPUImageCPRMapper(publicAPI, model) {
}

const newTex = model.device.getTextureManager().getTexture({
hash: pwfTextureString,
nativeArray: opacityArray,
width: model.rowLength,
height: numRows,
Expand All @@ -604,6 +622,13 @@ function vtkWebGPUImageCPRMapper(publicAPI, model) {
const actor = model.WebGPUImageSlice.getRenderable();
const property = actor.getProperty();
const image = model.currentImageDataInput;
const runtimePropID = model.WebGPUImageSlice.getPropID();
const selector = model.WebGPURenderer.getSelector();
let propID = runtimePropID;
if (selector?.getPropIDForSelection) {
propID = selector.getPropIDForSelection(runtimePropID, actor) + 1;
}
model.UBO.setValue('PropID', propID);
if (
publicAPI.getMTime() <= utime &&
model.renderable.getMTime() <= utime &&
Expand All @@ -612,6 +637,7 @@ function vtkWebGPUImageCPRMapper(publicAPI, model) {
image.getMTime() <= utime &&
model.WebGPURenderer.getStabilizedTime() <= utime
) {
model.UBO.sendIfNeeded(model.device);
return;
}

Expand Down Expand Up @@ -660,7 +686,6 @@ function vtkWebGPUImageCPRMapper(publicAPI, model) {
);
model.UBO.setValue('Width', model.renderable.getWidth());
model.UBO.setValue('Opacity', property.getOpacity());
model.UBO.setValue('PropID', model.WebGPUImageSlice.getPropID());
const numClipPlanes = Math.min(
model.renderable.getNumberOfClippingPlanes(),
MAX_CLIPPING_PLANES
Expand Down
Loading
Loading