UNPKG

@thewtex/vtk.js-esm

Version:

Visualization Toolkit for the Web

374 lines (309 loc) 13.6 kB
import { obj, get, getArray, setGet, newInstance as newInstance$1, vtkDebugMacro as vtkDebugMacro$1 } from '../../macro.js'; import vtkViewNode from '../SceneGraph/ViewNode.js'; import vtkWebGPUBindGroup from './BindGroup.js'; import vtkWebGPUFullScreenQuad from './FullScreenQuad.js'; import vtkWebGPUUniformBuffer from './UniformBuffer.js'; import { registerOverride } from './ViewNodeFactory.js'; import { i as identity } from '../../vendor/gl-matrix/esm/mat4.js'; import { b as scale, j as add, q as sub, r as len } from '../../vendor/gl-matrix/esm/vec3.js'; var vtkDebugMacro = vtkDebugMacro$1; var clearFragTemplate = "\n//VTK::Renderer::Dec\n\n//VTK::Mapper::Dec\n\n//VTK::TCoord::Dec\n\n//VTK::RenderEncoder::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 var computedColor: vec4<f32> = mapperUBO.BackgroundColor;\n\n //VTK::RenderEncoder::Impl\n return output;\n}\n"; // ---------------------------------------------------------------------------- // vtkWebGPURenderer methods // ---------------------------------------------------------------------------- /* eslint-disable no-bitwise */ function vtkWebGPURenderer(publicAPI, model) { // Set our className model.classHierarchy.push('vtkWebGPURenderer'); // Builds myself. publicAPI.buildPass = function (prepass) { if (prepass) { if (!model.renderable) { return; } // make sure we have a camera if (!model.renderable.isActiveCameraCreated()) { model.renderable.resetCamera(); } publicAPI.updateLights(); publicAPI.prepareNodes(); publicAPI.addMissingNode(model.renderable.getActiveCamera()); publicAPI.addMissingNodes(model.renderable.getViewPropsWithNestedProps()); publicAPI.removeUnusedNodes(); publicAPI.updateStabilizedMatrix(); } }; publicAPI.updateStabilizedMatrix = function () { // This method is designed to help with floating point // issues when rendering datasets that push the limits of // resolutions on float. // // One of the most common cases is when the dataset is located far // away from the origin relative to the clipping range we are looking // at. For that case we want to perform the floating point sensitive // multiplications on the CPU in double. To this end we want the // vertex rendering ops to look something like // // Compute shifted points and load those into the VBO // pointCoordsSC = WorldToStabilizedMatrix * pointCoords; // // In the vertex shader do the following // positionVC = StabilizedToDeviceMatrix * ModelToStabilizedMatrix*vertexIn; // // We use two matrices because it is expensive to change the // WorldToStabilized matrix as we have to reupload all pointCoords // So that matrix (MCSCMatrix) is fairly static, the Stabilized to // Device matrix is the one that gets updated every time the camera // changes. // // The basic idea is that we should translate the data so that // when the center of the view frustum moves a lot // we recenter it. The center of the view frustum is roughly // camPos + dirOfProj*(far + near)*0.5 var cam = model.renderable.getActiveCamera(); var clipRange = cam.getClippingRange(); var pos = cam.getPositionByReference(); var dop = cam.getDirectionOfProjectionByReference(); var center = []; var offset = []; scale(offset, dop, 0.5 * (clipRange[0] + clipRange[1])); add(center, pos, offset); sub(offset, center, model.stabilizedCenter); var length = len(offset); if (length / (clipRange[1] - clipRange[0]) > model.recenterThreshold) { model.stabilizedCenter = center; model.stabilizedTime.modified(); } }; publicAPI.updateLights = function () { var count = 0; var lights = model.renderable.getLightsByReference(); for (var index = 0; index < lights.length; ++index) { if (lights[index].getSwitch() > 0.0) { count++; } } if (!count) { vtkDebugMacro('No lights are on, creating one.'); model.renderable.createLight(); } return count; }; // register pipeline callbacks from a mapper publicAPI.registerPipelineCallback = function (pipeline, cb) { // if there is a matching pipeline just add the cb for (var i = 0; i < model.pipelineCallbacks.length; i++) { if (model.pipelineCallbacks[i].pipeline === pipeline) { model.pipelineCallbacks[i].callbacks.push(cb); return; } } model.pipelineCallbacks.push({ pipeline: pipeline, callbacks: [cb] }); }; publicAPI.updateUBO = function () { // make sure the data is up to date // has the camera changed? var cam = model.renderable.getActiveCamera(); var webgpuCamera = publicAPI.getViewNodeFor(cam); var utime = model.UBO.getSendTime(); if (model.parent.getMTime() > utime || publicAPI.getMTime() > utime || cam.getMTime() > utime || model.renderable.getMTime() > utime) { var keyMats = webgpuCamera.getKeyMatrices(publicAPI); model.UBO.setArray('WCVCMatrix', keyMats.wcvc); model.UBO.setArray('SCPCMatrix', keyMats.scpc); model.UBO.setArray('PCSCMatrix', keyMats.pcsc); model.UBO.setArray('SCVCMatrix', keyMats.scvc); model.UBO.setArray('VCPCMatrix', keyMats.vcpc); model.UBO.setArray('WCVCNormals', keyMats.normalMatrix); model.UBO.setValue('cameraParallel', cam.getParallelProjection()); var device = model.parent.getDevice(); model.UBO.sendIfNeeded(device); } }; publicAPI.scissorAndViewport = function (encoder) { var tsize = publicAPI.getYInvertedTiledSizeAndOrigin(); encoder.getHandle().setViewport(tsize.lowerLeftU, tsize.lowerLeftV, tsize.usize, tsize.vsize, 0.0, 1.0); // set scissor encoder.getHandle().setScissorRect(tsize.lowerLeftU, tsize.lowerLeftV, tsize.usize, tsize.vsize); }; publicAPI.bindUBO = function (renderEncoder) { renderEncoder.activateBindGroup(model.bindGroup); }; // Renders myself publicAPI.opaquePass = function (prepass) { if (prepass) { // clear last pipelines model.pipelineCallbacks = []; model.renderEncoder.begin(model.parent.getCommandEncoder()); publicAPI.updateUBO(); } else { publicAPI.scissorAndViewport(model.renderEncoder); publicAPI.clear(); // loop over registered pipelines for (var i = 0; i < model.pipelineCallbacks.length; i++) { var pStruct = model.pipelineCallbacks[i]; var pl = pStruct.pipeline; model.renderEncoder.setPipeline(pl); publicAPI.bindUBO(model.renderEncoder); for (var cb = 0; cb < pStruct.callbacks.length; cb++) { pStruct.callbacks[cb](model.renderEncoder); } } model.renderEncoder.end(); } }; publicAPI.clear = function () { if (model.renderable.getTransparent() || model.suppressClear) { return; } var device = model.parent.getDevice(); if (!model.clearFSQ) { model.clearFSQ = vtkWebGPUFullScreenQuad.newInstance(); model.clearFSQ.setDevice(device); model.clearFSQ.setPipelineHash('clearfsq'); model.clearFSQ.setFragmentShaderTemplate(clearFragTemplate); var ubo = vtkWebGPUUniformBuffer.newInstance(); ubo.setName('mapperUBO'); ubo.addEntry('BackgroundColor', 'vec4<f32>'); model.clearFSQ.setUBO(ubo); } var background = model.renderable.getBackgroundByReference(); model.clearFSQ.getUBO().setArray('BackgroundColor', background); model.clearFSQ.getUBO().sendIfNeeded(device); model.clearFSQ.render(model.renderEncoder, device); }; publicAPI.translucentPass = function (prepass) { if (prepass) { // clear last pipelines model.pipelineCallbacks = []; model.renderEncoder.begin(model.parent.getCommandEncoder()); } else { publicAPI.scissorAndViewport(model.renderEncoder); // loop over registered pipelines for (var i = 0; i < model.pipelineCallbacks.length; i++) { var pStruct = model.pipelineCallbacks[i]; var pl = pStruct.pipeline; model.renderEncoder.setPipeline(pl); publicAPI.bindUBO(model.renderEncoder); for (var cb = 0; cb < pStruct.callbacks.length; cb++) { pStruct.callbacks[cb](model.renderEncoder); } } model.renderEncoder.end(); } }; publicAPI.volumeDepthRangePass = function (prepass) { if (prepass) { // clear last pipelines model.pipelineCallbacks = []; model.renderEncoder.begin(model.parent.getCommandEncoder()); } else { publicAPI.scissorAndViewport(model.renderEncoder); // loop over registered pipelines for (var i = 0; i < model.pipelineCallbacks.length; i++) { var pStruct = model.pipelineCallbacks[i]; var pl = pStruct.pipeline; model.renderEncoder.setPipeline(pl); publicAPI.bindUBO(model.renderEncoder); for (var cb = 0; cb < pStruct.callbacks.length; cb++) { pStruct.callbacks[cb](model.renderEncoder); } } model.renderEncoder.end(); } }; publicAPI.getAspectRatio = function () { var size = model.parent.getSizeByReference(); var viewport = model.renderable.getViewportByReference(); return size[0] * (viewport[2] - viewport[0]) / ((viewport[3] - viewport[1]) * size[1]); }; publicAPI.getYInvertedTiledSizeAndOrigin = function () { var res = publicAPI.getTiledSizeAndOrigin(); var size = model.parent.getSizeByReference(); res.lowerLeftV = size[1] - res.vsize - res.lowerLeftV; return res; }; publicAPI.getTiledSizeAndOrigin = function () { var vport = model.renderable.getViewportByReference(); // if there is no window assume 0 1 var tileViewPort = [0.0, 0.0, 1.0, 1.0]; // find the lower left corner of the viewport, taking into account the // lower left boundary of this tile var vpu = vport[0] - tileViewPort[0]; var vpv = vport[1] - tileViewPort[1]; // store the result as a pixel value var ndvp = model.parent.normalizedDisplayToDisplay(vpu, vpv); var lowerLeftU = Math.round(ndvp[0]); var lowerLeftV = Math.round(ndvp[1]); // find the upper right corner of the viewport, taking into account the // lower left boundary of this tile var vpu2 = vport[2] - tileViewPort[0]; var vpv2 = vport[3] - tileViewPort[1]; var ndvp2 = model.parent.normalizedDisplayToDisplay(vpu2, vpv2); // now compute the size of the intersection of the viewport with the // current tile var usize = Math.round(ndvp2[0]) - lowerLeftU; var vsize = Math.round(ndvp2[1]) - lowerLeftV; if (usize < 0) { usize = 0; } if (vsize < 0) { vsize = 0; } return { usize: usize, vsize: vsize, lowerLeftU: lowerLeftU, lowerLeftV: lowerLeftV }; }; publicAPI.getPropFromID = function (id) { for (var i = 0; i < model.children.length; i++) { var res = model.children[i].getPropID ? model.children[i].getPropID() : -1; if (res === id) { return model.children[i]; } } return null; }; publicAPI.getStabilizedTime = function () { return model.stabilizedTime.getMTime(); }; publicAPI.releaseGraphicsResources = function () { if (model.selector !== null) { model.selector.releaseGraphicsResources(); } }; } // ---------------------------------------------------------------------------- // Object factory // ---------------------------------------------------------------------------- var DEFAULT_VALUES = { bindGroup: null, selector: null, renderEncoder: null, recenterThreshold: 20.0, suppressClear: false, stabilizedCenter: [0.0, 0.0, 0.0] }; // ---------------------------------------------------------------------------- function extend(publicAPI, model) { var initialValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; Object.assign(model, DEFAULT_VALUES, initialValues); // Inheritance vtkViewNode.extend(publicAPI, model, initialValues); model.UBO = vtkWebGPUUniformBuffer.newInstance(); model.UBO.setName('rendererUBO'); model.UBO.addEntry('WCVCMatrix', 'mat4x4<f32>'); model.UBO.addEntry('SCPCMatrix', 'mat4x4<f32>'); model.UBO.addEntry('PCSCMatrix', 'mat4x4<f32>'); model.UBO.addEntry('SCVCMatrix', 'mat4x4<f32>'); model.UBO.addEntry('VCPCMatrix', 'mat4x4<f32>'); model.UBO.addEntry('WCVCNormals', 'mat4x4<f32>'); model.UBO.addEntry('cameraParallel', 'u32'); model.bindGroup = vtkWebGPUBindGroup.newInstance(); model.bindGroup.setName('rendererBG'); model.bindGroup.setBindables([model.UBO]); model.tmpMat4 = identity(new Float64Array(16)); model.stabilizedTime = {}; obj(model.stabilizedTime, { mtime: 0 }); // Build VTK API get(publicAPI, model, ['bindGroup', 'stabilizedTime']); getArray(publicAPI, model, ['stabilizedCenter']); setGet(publicAPI, model, ['renderEncoder', 'selector', 'suppressClear', 'UBO']); // Object methods vtkWebGPURenderer(publicAPI, model); } // ---------------------------------------------------------------------------- var newInstance = newInstance$1(extend, 'vtkWebGPURenderer'); // ---------------------------------------------------------------------------- var index = { newInstance: newInstance, extend: extend }; // Register ourself to WebGPU backend if imported registerOverride('vtkRenderer', newInstance); export default index; export { extend, newInstance };