@thewtex/vtk.js-esm
Version:
Visualization Toolkit for the Web
372 lines (311 loc) • 13.4 kB
JavaScript
import _toConsumableArray from '@babel/runtime/helpers/toConsumableArray';
import macro from '../../macro.js';
import vtkPolyData from '../../Common/DataModel/PolyData.js';
import vtkProperty from '../Core/Property.js';
import vtkRenderPass from '../SceneGraph/RenderPass.js';
import vtkWebGPUBufferManager from './BufferManager.js';
import vtkWebGPUMapperHelper from './MapperHelper.js';
import vtkWebGPURenderEncoder from './RenderEncoder.js';
import vtkWebGPUShaderCache from './ShaderCache.js';
import vtkWebGPUTexture from './Texture.js';
import vtkWebGPUVolumePassFSQ from './VolumePassFSQ.js';
var Representation = vtkProperty.Representation;
var BufferUsage = vtkWebGPUBufferManager.BufferUsage,
PrimitiveTypes = vtkWebGPUBufferManager.PrimitiveTypes; // The volume rendering pass consists of two sub passes. The first
// (depthRange) renders polygonal cubes for the volumes to compute min and
// max bounds in depth for the image. This is then fed into the second pass
// (final) which actually does the raycasting between those bounds sampling
// the volumes along the way. So the first pass tends to be very fast whicle
// the second is where most of the work is done.
// given x then y then z ordering
//
// 2-----3
// / | / |
// 6-----7 |
// | | | |
// | 0-----1
// |/ |/
// 4-----5
//
var cubeFaceTriangles = [[0, 4, 6], [0, 6, 2], [1, 3, 7], [1, 7, 5], [0, 5, 4], [0, 1, 5], [2, 6, 7], [2, 7, 3], [0, 3, 1], [0, 2, 3], [4, 5, 7], [4, 7, 6]];
var DepthBoundsFS = "\n//VTK::Renderer::Dec\n\n//VTK::Select::Dec\n\n//VTK::VolumePass::Dec\n\n//VTK::TCoord::Dec\n\n//VTK::RenderEncoder::Dec\n\n//VTK::Mapper::Dec\n\n//VTK::IOStructs::Dec\n\n[[stage(fragment)]]\nfn main(\n//VTK::IOStructs::Input\n)\n//VTK::IOStructs::Output\n{\n var output : fragmentOutput;\n\n //VTK::Select::Impl\n\n //VTK::TCoord::Impl\n\n //VTK::VolumePass::Impl\n\n // use the minimum (closest) of the current value and the zbuffer\n // the blend func will then take the max to find the farthest stop value\n var stopval: f32 = min(input.fragPos.z, textureLoad(opaquePassDepthTexture, vec2<i32>(i32(input.fragPos.x), i32(input.fragPos.y)), 0).r);\n\n //VTK::RenderEncoder::Impl\n return output;\n}\n";
/* eslint-disable no-undef */
/* eslint-disable no-bitwise */
// ----------------------------------------------------------------------------
function vtkWebGPUVolumePass(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkWebGPUVolumePass');
publicAPI.traverse = function (renNode, viewNode) {
if (model.deleted) {
return;
} // we just render our delegates in order
model.currentParent = viewNode; // first render the boxes to generate a min max depth
// map for all the volumes
publicAPI.renderDepthBounds(renNode, viewNode); // then perform the ray casting using the depth bounds texture
if (!model.finalEncoder) {
publicAPI.createFinalEncoder(viewNode);
}
publicAPI.finalPass(viewNode, renNode);
};
publicAPI.finalPass = function (viewNode, renNode) {
model.finalEncoder.setColorTextureView(0, model.colorTextureView);
model.finalEncoder.attachTextureViews();
renNode.setRenderEncoder(model.finalEncoder);
model.finalEncoder.begin(viewNode.getCommandEncoder());
renNode.scissorAndViewport(model.finalEncoder);
model.fullScreenQuad.setWebGPURenderer(renNode);
model.fullScreenQuad.setVolumes(model.volumes);
model.fullScreenQuad.render(model.finalEncoder, viewNode.getDevice());
model.finalEncoder.end();
};
publicAPI.renderDepthBounds = function (renNode, viewNode) {
publicAPI.updateDepthPolyData(renNode);
var pd = model._boundsPoly;
var cells = pd.getPolys();
var hash = cells.getMTime(); // points
var points = pd.getPoints();
var buffRequest = {
hash: hash + points.getMTime(),
dataArray: points,
source: points,
cells: cells,
primitiveType: PrimitiveTypes.Triangles,
representation: Representation.SURFACE,
time: Math.max(points.getMTime(), cells.getMTime()),
usage: BufferUsage.PointArray,
format: 'float32x4',
packExtra: true
};
var buff = viewNode.getDevice().getBufferManager().getBuffer(buffRequest);
model._mapper.getVertexInput().addBuffer(buff, ['vertexBC']);
model._mapper.setNumberOfVertices(buff.getSizeInBytes() / buff.getStrideInBytes());
publicAPI.drawDepthRange(renNode, viewNode);
};
publicAPI.updateDepthPolyData = function (renNode) {
// check mtimes first
var update = false;
for (var i = 0; i < model.volumes.length; i++) {
var mtime = model.volumes[i].getMTime();
if (!model._lastMTimes[i] || mtime !== model._lastMTimes[i]) {
update = true;
model._lastMTimes[i] = mtime;
}
} // also check stabilized time
var stime = renNode.getStabilizedTime();
if (!model._lastMTimes[model.volumes.length] || stime !== model._lastMTimes[model.volumes.length]) {
update = true;
model._lastMTimes[model.volumes.length] = stime;
} // if no need to update then return
if (!update) {
return;
} // rebuild
var center = renNode.getStabilizedCenterByReference();
var numPts = model.volumes.length * 8;
var points = new Float64Array(numPts * 3);
var numTris = model.volumes.length * 12;
var polys = new Uint16Array(numTris * 4); // add points and cells
for (var _i = 0; _i < model.volumes.length; _i++) {
model.volumes[_i].getBoundingCubePoints(points, _i * 24);
var cellIdx = _i * 12 * 4;
var offset = _i * 8;
for (var t = 0; t < 12; t++) {
polys[cellIdx++] = 3;
polys[cellIdx++] = offset + cubeFaceTriangles[t][0];
polys[cellIdx++] = offset + cubeFaceTriangles[t][1];
polys[cellIdx++] = offset + cubeFaceTriangles[t][2];
}
}
for (var p = 0; p < points.length; p += 3) {
points[p] -= center[0];
points[p + 1] -= center[1];
points[p + 2] -= center[2];
}
model._boundsPoly.getPoints().setData(points, 3);
model._boundsPoly.getPoints().modified();
model._boundsPoly.getPolys().setData(polys, 1);
model._boundsPoly.getPolys().modified();
model._boundsPoly.modified();
};
publicAPI.drawDepthRange = function (renNode, viewNode) {
var device = viewNode.getDevice(); // copy current depth buffer to
if (!model.depthRangeEncoder) {
publicAPI.createDepthRangeEncoder();
model.depthRangeTexture = vtkWebGPUTexture.newInstance();
model.depthRangeTexture.create(device, {
width: viewNode.getCanvas().width,
height: viewNode.getCanvas().height,
format: 'r16float',
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.SAMPLED
});
var maxView = model.depthRangeTexture.createView();
maxView.setName('maxTexture');
model.depthRangeEncoder.setColorTextureView(0, maxView);
model.depthRangeTexture2 = vtkWebGPUTexture.newInstance();
model.depthRangeTexture2.create(device, {
width: viewNode.getCanvas().width,
height: viewNode.getCanvas().height,
format: 'r16float',
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.SAMPLED
});
var minView = model.depthRangeTexture2.createView();
minView.setName('minTexture');
model.depthRangeEncoder.setColorTextureView(1, minView);
model._mapper.setDevice(viewNode.getDevice());
model._mapper.setTextureViews([model.depthTextureView]);
} else {
model.depthRangeTexture.resizeToMatch(model.colorTextureView.getTexture());
model.depthRangeTexture2.resizeToMatch(model.colorTextureView.getTexture());
}
model.depthRangeEncoder.attachTextureViews();
publicAPI.setCurrentOperation('volumeDepthRangePass');
renNode.setRenderEncoder(model.depthRangeEncoder);
renNode.volumeDepthRangePass(true);
model._mapper.setWebGPURenderer(renNode);
model._mapper.build(model.depthRangeEncoder, device);
model._mapper.registerToDraw();
renNode.volumeDepthRangePass(false);
};
publicAPI.createDepthRangeEncoder = function () {
model.depthRangeEncoder = vtkWebGPURenderEncoder.newInstance();
model.depthRangeEncoder.setPipelineHash('volr');
model.depthRangeEncoder.setReplaceShaderCodeFunction(function (pipeline) {
var fDesc = pipeline.getShaderDescription('fragment');
fDesc.addOutput('vec4<f32>', 'outColor1');
fDesc.addOutput('vec4<f32>', 'outColor2');
var code = fDesc.getCode();
code = vtkWebGPUShaderCache.substitute(code, '//VTK::RenderEncoder::Impl', ['output.outColor1 = vec4<f32>(stopval, 0.0, 0.0, 0.0);', 'output.outColor2 = vec4<f32>(input.fragPos.z, 0.0, 0.0, 0.0);']).result;
fDesc.setCode(code);
});
model.depthRangeEncoder.setDescription({
colorAttachments: [{
view: null,
loadValue: [0.0, 0.0, 0.0, 0.0],
storeOp: 'store'
}, {
view: null,
loadValue: [1.0, 1.0, 1.0, 1.0],
storeOp: 'store'
}]
});
model.depthRangeEncoder.setPipelineSettings({
primitive: {
cullMode: 'none'
},
fragment: {
targets: [{
format: 'r16float',
blend: {
color: {
srcFactor: 'one',
dstFactor: 'one',
operation: 'max'
},
alpha: {
srcfactor: 'one',
dstFactor: 'one',
operation: 'max'
}
}
}, {
format: 'r16float',
blend: {
color: {
srcFactor: 'one',
dstFactor: 'one',
operation: 'min'
},
alpha: {
srcfactor: 'one',
dstFactor: 'one',
operation: 'min'
}
}
}]
}
});
};
publicAPI.createFinalEncoder = function (viewNode) {
model.fullScreenQuad = vtkWebGPUVolumePassFSQ.newInstance();
model.fullScreenQuad.setDevice(viewNode.getDevice());
model.fullScreenQuad.setTextureViews(_toConsumableArray(model.depthRangeEncoder.getColorTextureViews()));
model.finalEncoder = vtkWebGPURenderEncoder.newInstance();
model.finalEncoder.setDescription({
colorAttachments: [{
view: null,
loadValue: 'load',
storeOp: 'store'
}]
});
model.finalEncoder.setReplaceShaderCodeFunction(function (pipeline) {
var fDesc = pipeline.getShaderDescription('fragment');
fDesc.addOutput('vec4<f32>', 'outColor');
var code = fDesc.getCode();
code = vtkWebGPUShaderCache.substitute(code, '//VTK::RenderEncoder::Impl', ['output.outColor = vec4<f32>(computedColor.rgb*computedColor.a, computedColor.a);']).result;
fDesc.setCode(code);
});
model.finalEncoder.setPipelineHash('volpf');
model.finalEncoder.setPipelineSettings({
primitive: {
cullMode: 'none'
},
fragment: {
targets: [{
format: 'bgra8unorm',
blend: {
color: {
srcFactor: 'one',
dstFactor: 'one-minus-src-alpha'
},
alpha: {
srcfactor: 'one',
dstFactor: 'one-minus-src-alpha'
}
}
}]
}
});
}; // marks modified when needed
publicAPI.setVolumes = function (val) {
if (!model.volumes || model.volumes.length !== val.length) {
model.volumes = _toConsumableArray(val);
publicAPI.modified();
return;
}
for (var i = 0; i < val.length; i++) {
if (val[i] !== model.volumes[i]) {
model.volumes = _toConsumableArray(val);
publicAPI.modified();
return;
}
}
};
} // ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------
var DEFAULT_VALUES = {
colorTextureView: null,
depthTextureView: null,
volumes: null
}; // ----------------------------------------------------------------------------
function extend(publicAPI, model) {
var initialValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
Object.assign(model, DEFAULT_VALUES, initialValues); // Build VTK API
vtkRenderPass.extend(publicAPI, model, initialValues);
model._mapper = vtkWebGPUMapperHelper.newInstance();
model._mapper.setFragmentShaderTemplate(DepthBoundsFS);
model._mapper.getShaderReplacements().set('replaceShaderVolumePass', function (hash, pipeline, vertexInput) {
var fDesc = pipeline.getShaderDescription('fragment');
fDesc.addBuiltinInput('vec4<f32>', '[[builtin(position)]] fragPos');
});
model._boundsPoly = vtkPolyData.newInstance();
model._lastMTimes = [];
macro.setGet(publicAPI, model, ['colorTextureView', 'depthTextureView']); // Object methods
vtkWebGPUVolumePass(publicAPI, model);
} // ----------------------------------------------------------------------------
var newInstance = macro.newInstance(extend, 'vtkWebGPUVolumePass'); // ----------------------------------------------------------------------------
var vtkWebGPUVolumePass$1 = {
newInstance: newInstance,
extend: extend
};
export default vtkWebGPUVolumePass$1;
export { extend, newInstance };