Skip to content

Commit 9ff24ac

Browse files
committed
feat(WebGPU): add attribute id support to hardware selection
1 parent c192f04 commit 9ff24ac

5 files changed

Lines changed: 158 additions & 18 deletions

File tree

Sources/Rendering/WebGPU/BufferManager/index.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,13 @@ function vtkWebGPUBufferManager(publicAPI, model) {
317317
buffer.setStrideInBytes(
318318
vtkWebGPUTypes.getByteStrideFromBufferFormat(req.format)
319319
);
320-
buffer.setArrayInformation([{ offset: 0, format: req.format }]);
320+
buffer.setArrayInformation([
321+
{
322+
offset: 0,
323+
format: req.format,
324+
interpolation: req.interpolation,
325+
},
326+
]);
321327
}
322328

323329
buffer.setSourceTime(req.time);

Sources/Rendering/WebGPU/CellArrayMapper/index.js

Lines changed: 96 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -425,12 +425,26 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
425425
}
426426
};
427427

428+
publicAPI.isEdgePrimitive = () =>
429+
model.primitiveType === PrimitiveTypes.TriangleEdges ||
430+
model.primitiveType === PrimitiveTypes.TriangleStripEdges;
431+
432+
publicAPI.shouldSkipPass = () =>
433+
publicAPI.isEdgePrimitive() && (model.depthOnlyPass || model.selectionPass);
434+
428435
// Renders myself
429436
publicAPI.renderForPass = (renderEncoder, depthOnly = false) => {
430437
model.depthOnlyPass = depthOnly;
438+
model.selectionPass = renderEncoder?.getPipelineHash?.() === 'sel';
439+
if (publicAPI.shouldSkipPass()) {
440+
model.depthOnlyPass = false;
441+
model.selectionPass = false;
442+
return;
443+
}
431444
publicAPI.prepareToDraw(renderEncoder);
432445
model.renderEncoder.registerDrawCallback(model.pipeline, publicAPI.draw);
433446
model.depthOnlyPass = false;
447+
model.selectionPass = false;
434448
};
435449

436450
publicAPI.translucentPass = (prepass) => {
@@ -458,8 +472,10 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
458472
const ppty = actor.getProperty();
459473
const clippingPlanesMTime = model.renderable.getClippingPlanesMTime();
460474
const backfaceProperty = actor.getBackfaceProperty?.() ?? ppty;
475+
const selector = model.WebGPURenderer?.getSelector?.();
461476
const utime = model.UBO.getSendTime();
462477
if (
478+
!selector &&
463479
publicAPI.getMTime() <= utime &&
464480
ppty.getMTime() <= utime &&
465481
backfaceProperty.getMTime() <= utime &&
@@ -573,7 +589,13 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
573589
'Opacity',
574590
edgeLikeRepresentation ? ppty.getEdgeOpacity() : ppty.getOpacity()
575591
);
576-
model.UBO.setValue('PropID', model.WebGPUActor.getPropID());
592+
const runtimePropID = model.WebGPUActor.getPropID();
593+
model.UBO.setValue(
594+
'PropID',
595+
selector?.getPropIDForSelection
596+
? selector.getPropIDForSelection(runtimePropID, actor) + 1
597+
: runtimePropID
598+
);
577599
const cp = publicAPI.getCoincidentParameters();
578600
model.UBO.setValue('CoincidentFactor', cp.factor);
579601
model.UBO.setValue('CoincidentOffset', cp.offset);
@@ -1210,12 +1232,40 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
12101232

12111233
publicAPI.replaceShaderSelect = (hash, pipeline, vertexInput) => {
12121234
if (hash.includes('sel')) {
1235+
const selectBuffer = vertexInput.getBuffer('selectId');
1236+
if (selectBuffer) {
1237+
const vDesc = pipeline.getShaderDescription('vertex');
1238+
vDesc.addOutput(
1239+
'u32',
1240+
'attributeID',
1241+
selectBuffer.getArrayInformation()[0].interpolation
1242+
);
1243+
vDesc.addOutput(
1244+
'u32',
1245+
'compositeID',
1246+
selectBuffer.getArrayInformation()[0].interpolation
1247+
);
1248+
let code = vDesc.getCode();
1249+
code = vtkWebGPUShaderCache.substitute(code, '//VTK::Select::Impl', [
1250+
' output.compositeID = mapperUBO.PropID;',
1251+
' output.attributeID = selectId;',
1252+
]).result;
1253+
vDesc.setCode(code);
1254+
}
1255+
12131256
const fDesc = pipeline.getShaderDescription('fragment');
12141257
let code = fDesc.getCode();
1215-
// by default there are no composites, so just 0
1216-
code = vtkWebGPUShaderCache.substitute(code, '//VTK::Select::Impl', [
1217-
' var compositeID: u32 = 0u;',
1218-
]).result;
1258+
const selectImpl = selectBuffer
1259+
? [
1260+
' var compositeID: u32 = input.compositeID;',
1261+
' var attributeID: u32 = input.attributeID;',
1262+
]
1263+
: [' var compositeID: u32 = 0u;', ' var attributeID: u32 = 0u;'];
1264+
code = vtkWebGPUShaderCache.substitute(
1265+
code,
1266+
'//VTK::Select::Impl',
1267+
selectImpl
1268+
).result;
12191269
fDesc.setCode(code);
12201270
}
12211271
};
@@ -1451,6 +1501,43 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
14511501
} else {
14521502
vertexInput.removeBufferIfPresent('tcoord');
14531503
}
1504+
1505+
// --- Selection IDs ---
1506+
const selector = model.WebGPURenderer?.getSelector?.();
1507+
if (selector && !edges && indexBuffer) {
1508+
let selectIds = null;
1509+
if (
1510+
selector.getFieldAssociation() ===
1511+
FieldAssociations.FIELD_ASSOCIATION_POINTS
1512+
) {
1513+
selectIds = indexBuffer.getFlatIdToPointId();
1514+
} else if (
1515+
selector.getFieldAssociation() ===
1516+
FieldAssociations.FIELD_ASSOCIATION_CELLS
1517+
) {
1518+
selectIds = indexBuffer.getFlatIdToCellId();
1519+
}
1520+
1521+
if (selectIds) {
1522+
vertexInput.addBuffer(
1523+
device.getBufferManager().getBuffer({
1524+
hash: `sel${selector.getFieldAssociation()}I${indexBuffer.getMTime()}`,
1525+
usage: BufferUsage.RawVertex,
1526+
format: 'uint32',
1527+
interpolation: 'flat',
1528+
nativeArray:
1529+
selectIds instanceof Uint32Array
1530+
? selectIds
1531+
: Uint32Array.from(selectIds),
1532+
}),
1533+
['selectId']
1534+
);
1535+
} else {
1536+
vertexInput.removeBufferIfPresent('selectId');
1537+
}
1538+
} else {
1539+
vertexInput.removeBufferIfPresent('selectId');
1540+
}
14541541
};
14551542

14561543
publicAPI.updateTextures = () => {
@@ -1573,6 +1660,9 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
15731660
if (model.depthOnlyPass) {
15741661
pipelineHash += 'd';
15751662
}
1663+
if (model.selectionPass) {
1664+
pipelineHash += 's';
1665+
}
15761666

15771667
if (
15781668
model.primitiveType === PrimitiveTypes.TriangleEdges ||
@@ -1651,6 +1741,7 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
16511741

16521742
const DEFAULT_VALUES = {
16531743
depthOnlyPass: false,
1744+
selectionPass: false,
16541745
is2D: false,
16551746
cellArray: null,
16561747
currentInput: null,

Sources/Rendering/WebGPU/Glyph3DMapper/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,17 +86,20 @@ function vtkWebGPUGlyph3DCellArrayMapper(publicAPI, model) {
8686
publicAPI.replaceShaderSelect = (hash, pipeline, vertexInput) => {
8787
if (hash.includes('sel')) {
8888
const vDesc = pipeline.getShaderDescription('vertex');
89+
vDesc.addOutput('u32', 'attributeID', 'flat');
8990
vDesc.addOutput('u32', 'compositeID', 'flat');
9091
let code = vDesc.getCode();
9192
code = vtkWebGPUShaderCache.substitute(code, '//VTK::Select::Impl', [
92-
' output.compositeID = input.instanceIndex;',
93+
' output.compositeID = input.instanceIndex + 1u;',
94+
' output.attributeID = input.instanceIndex;',
9395
]).result;
9496
vDesc.setCode(code);
9597

9698
const fDesc = pipeline.getShaderDescription('fragment');
9799
code = fDesc.getCode();
98100
code = vtkWebGPUShaderCache.substitute(code, '//VTK::Select::Impl', [
99101
'var compositeID: u32 = input.compositeID;',
102+
'var attributeID: u32 = input.attributeID;',
100103
]).result;
101104
fDesc.setCode(code);
102105
}

Sources/Rendering/WebGPU/HardwareSelectionPass/index.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,16 @@ function vtkWebGPUHardwareSelectionPass(publicAPI, model) {
8787
const fDesc = pipeline.getShaderDescription('fragment');
8888
fDesc.addOutput('vec4<u32>', 'outColor');
8989
let code = fDesc.getCode();
90+
code = vtkWebGPUShaderCache.substitute(code, '//VTK::Select::Impl', [
91+
' var compositeID: u32 = 0u;',
92+
' var attributeID: u32 = compositeID;',
93+
]).result;
9094
code = vtkWebGPUShaderCache.substitute(
9195
code,
9296
'//VTK::RenderEncoder::Impl',
93-
['output.outColor = vec4<u32>(mapperUBO.PropID, compositeID, 0u, 0u);']
97+
[
98+
'output.outColor = vec4<u32>(mapperUBO.PropID, compositeID, attributeID, 0u);',
99+
]
94100
).result;
95101
fDesc.setCode(code);
96102
});

Sources/Rendering/WebGPU/HardwareSelector/index.js

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,15 @@ function getPixelInformationWithData(
4646
0
4747
);
4848

49-
if (actorid <= 0) {
49+
if (actorid <= 0 || actorid - 1 >= (buffdata.props?.length ?? 0)) {
5050
// the pixel did not hit any actor.
5151
return null;
5252
}
5353

5454
const info = {};
5555

56-
info.propID = actorid;
56+
info.propID = actorid - 1;
57+
info.prop = buffdata.props?.[info.propID];
5758

5859
let compositeID = convert(
5960
inDisplayPosition[0],
@@ -64,7 +65,17 @@ function getPixelInformationWithData(
6465
if (compositeID < 0 || compositeID > 0xffffff) {
6566
compositeID = 0;
6667
}
67-
info.compositeID = compositeID;
68+
info.compositeID = compositeID - 1;
69+
let attributeID = convert(
70+
inDisplayPosition[0],
71+
inDisplayPosition[1],
72+
buffdata,
73+
2
74+
);
75+
if (attributeID === 0 && compositeID !== 0) {
76+
attributeID = compositeID - 1;
77+
}
78+
info.attributeID = attributeID;
6879

6980
if (buffdata.captureZValues) {
7081
const offset =
@@ -179,9 +190,9 @@ function convertSelection(fieldassociation, dataMap, buffdata) {
179190
vtkErrorMacro('Unknown field association');
180191
}
181192
child.getProperties().propID = value.info.propID;
182-
const wprop = buffdata.webGPURenderer.getPropFromID(value.info.propID);
183-
child.getProperties().prop = wprop.getRenderable();
193+
child.getProperties().prop = value.info.prop;
184194
child.getProperties().compositeID = value.info.compositeID;
195+
child.getProperties().attributeID = value.info.attributeID;
185196
child.getProperties().pixelCount = value.pixelCount;
186197
if (buffdata.captureZValues) {
187198
child.getProperties().displayPosition = [
@@ -260,6 +271,17 @@ function vtkWebGPUHardwareSelector(publicAPI, model) {
260271
// Set our className
261272
model.classHierarchy.push('vtkWebGPUHardwareSelector');
262273

274+
publicAPI.getPropIDForSelection = (runtimePropID, prop = null) => {
275+
if (model._selectionPropMap.has(runtimePropID)) {
276+
return model._selectionPropMap.get(runtimePropID);
277+
}
278+
279+
const selectionPropID = model._selectionProps.length;
280+
model._selectionPropMap.set(runtimePropID, selectionPropID);
281+
model._selectionProps.push(prop);
282+
return selectionPropID;
283+
};
284+
263285
//----------------------------------------------------------------------------
264286
publicAPI.endSelection = () => {
265287
model.WebGPURenderer.setSelector(null);
@@ -296,12 +318,19 @@ function vtkWebGPUHardwareSelector(publicAPI, model) {
296318
// Initialize renderer for selection.
297319
// change the renderer's background to black, which will indicate a miss
298320
const originalSuppress = webGPURenderer.getSuppressClear();
321+
const originalSelector = webGPURenderer.getSelector();
322+
model._selectionPropMap.clear();
323+
model._selectionProps = [];
299324
webGPURenderer.setSuppressClear(true);
300-
301-
model._selectionPass.traverse(model._WebGPURenderWindow, webGPURenderer);
302-
303-
// restore original background
304-
webGPURenderer.setSuppressClear(originalSuppress);
325+
webGPURenderer.setSelector(publicAPI);
326+
327+
try {
328+
model._selectionPass.traverse(model._WebGPURenderWindow, webGPURenderer);
329+
} finally {
330+
// restore original renderer state
331+
webGPURenderer.setSelector(originalSelector);
332+
webGPURenderer.setSuppressClear(originalSuppress);
333+
}
305334

306335
const device = model._WebGPURenderWindow.getDevice();
307336
const texture = model._selectionPass.getColorTexture();
@@ -315,6 +344,7 @@ function vtkWebGPUHardwareSelector(publicAPI, model) {
315344
area: [0, 0, texture.getWidth() - 1, texture.getHeight() - 1],
316345
captureZValues: model.captureZValues,
317346
fieldAssociation: model.fieldAssociation,
347+
props: [...model._selectionProps],
318348
renderer,
319349
webGPURenderer,
320350
webGPURenderWindow: model._WebGPURenderWindow,
@@ -419,6 +449,8 @@ function vtkWebGPUHardwareSelector(publicAPI, model) {
419449

420450
const DEFAULT_VALUES = {
421451
// WebGPURenderWindow: null,
452+
_selectionPropMap: null,
453+
_selectionProps: null,
422454
};
423455

424456
// ----------------------------------------------------------------------------
@@ -430,6 +462,8 @@ export function extend(publicAPI, model, initialValues = {}) {
430462
vtkHardwareSelector.extend(publicAPI, model, initialValues);
431463

432464
model._selectionPass = vtkWebGPUHardwareSelectionPass.newInstance();
465+
model._selectionPropMap = new Map();
466+
model._selectionProps = [];
433467

434468
macro.setGet(publicAPI, model, ['_WebGPURenderWindow']);
435469
macro.moveToProtected(publicAPI, model, ['WebGPURenderWindow']);

0 commit comments

Comments
 (0)