@cornerstonejs/core
Version:
Cornerstone3D Core
295 lines (294 loc) • 13.4 kB
JavaScript
import BaseRenderingEngine, { VIEWPORT_MIN_SIZE } from './BaseRenderingEngine.js';
import Events from '../enums/Events.js';
import eventTarget from '../eventTarget.js';
import triggerEvent from '../utilities/triggerEvent.js';
import viewportTypeUsesCustomRenderingPipeline from './helpers/viewportTypeUsesCustomRenderingPipeline.js';
import getOrCreateCanvas from './helpers/getOrCreateCanvas.js';
import { vtkOffscreenMultiRenderWindow } from './vtkClasses/index.js';
import { attachWebGLContextEvents } from './helpers/attachWebGLContextEvents.js';
import { setElementTouchActionNone } from './helpers/elementTouchAction.js';
import { getViewportClassForInput } from './helpers/viewportTypeToViewportClass.js';
class TiledRenderingEngine extends BaseRenderingEngine {
constructor(id) {
super(id);
this._renderFlaggedViewports = () => {
this._throwIfDestroyed();
if (this.offscreenMultiRenderWindow) {
this.performVtkDrawCall();
}
const viewports = this._getViewportsAsArray();
const eventDetailArray = [];
for (let i = 0; i < viewports.length; i++) {
const viewport = viewports[i];
if (this._needsRender.has(viewport.id)) {
const eventDetail = this.renderViewportUsingCustomOrVtkPipeline(viewport);
eventDetailArray.push(eventDetail);
viewport.setRendered();
this._needsRender.delete(viewport.id);
if (this._needsRender.size === 0) {
break;
}
}
}
this._animationFrameSet = false;
this._animationFrameHandle = null;
eventDetailArray.forEach((eventDetail) => {
if (!eventDetail?.element) {
return;
}
triggerEvent(eventDetail.element, Events.IMAGE_RENDERED, eventDetail);
});
};
if (!this.useCPURendering) {
this.ensureOffscreenResources();
}
}
ensureOffscreenResources() {
if (!this.offscreenMultiRenderWindow) {
this.offscreenMultiRenderWindow =
vtkOffscreenMultiRenderWindow.newInstance();
this.offScreenCanvasContainer = document.createElement('div');
this.offscreenMultiRenderWindow.setContainer(this.offScreenCanvasContainer);
attachWebGLContextEvents(this.offscreenMultiRenderWindow, this.id);
}
}
enableVTKjsDrivenViewport(viewportInputEntry) {
this.ensureOffscreenResources();
const viewports = this._getViewportsAsArray();
const viewportsDrivenByVtkJs = viewports.filter((vp) => viewportTypeUsesCustomRenderingPipeline(vp.type) === false);
const canvasesDrivenByVtkJs = viewportsDrivenByVtkJs.map((vp) => vp.canvas);
const canvas = getOrCreateCanvas(viewportInputEntry.element);
canvasesDrivenByVtkJs.push(canvas);
const { offScreenCanvasWidth, offScreenCanvasHeight } = this._resizeOffScreenCanvas(canvasesDrivenByVtkJs);
const xOffset = this._resize(viewportsDrivenByVtkJs, offScreenCanvasWidth, offScreenCanvasHeight);
const internalViewportEntry = { ...viewportInputEntry, canvas };
this.addVtkjsDrivenViewport(internalViewportEntry, {
offScreenCanvasWidth,
offScreenCanvasHeight,
xOffset,
});
}
addVtkjsDrivenViewport(viewportInputEntry, offscreenCanvasProperties) {
this.ensureOffscreenResources();
const { element, canvas, viewportId, type, defaultOptions } = viewportInputEntry;
element.tabIndex = -1;
setElementTouchActionNone(element);
const { offScreenCanvasWidth, offScreenCanvasHeight, xOffset } = offscreenCanvasProperties;
const { sxStartDisplayCoords, syStartDisplayCoords, sxEndDisplayCoords, syEndDisplayCoords, sx, sy, sWidth, sHeight, } = this._getViewportCoordsOnOffScreenCanvas(viewportInputEntry, offScreenCanvasWidth, offScreenCanvasHeight, xOffset);
this.offscreenMultiRenderWindow.addRenderer({
viewport: [
sxStartDisplayCoords,
syStartDisplayCoords,
sxEndDisplayCoords,
syEndDisplayCoords,
],
id: viewportId,
background: defaultOptions.background
? defaultOptions.background
: [0, 0, 0],
});
const viewportInput = {
id: viewportId,
element,
renderingEngineId: this.id,
type,
canvas,
sx,
sy,
sWidth,
sHeight,
defaultOptions: defaultOptions || {},
};
const ViewportClass = getViewportClassForInput(viewportInputEntry);
if (!ViewportClass) {
throw new Error(`Viewport Type ${type} is not supported`);
}
const viewport = new ViewportClass(viewportInput);
viewport.requestedType = viewportInputEntry.requestedType ?? type;
this._viewports.set(viewportId, viewport);
const eventDetail = {
element,
viewportId,
renderingEngineId: this.id,
};
if (!viewport.suppressEvents) {
triggerEvent(eventTarget, Events.ELEMENT_ENABLED, eventDetail);
}
}
setVtkjsDrivenViewports(viewportInputEntries) {
if (viewportInputEntries.length) {
const vtkDrivenCanvases = viewportInputEntries.map((vp) => getOrCreateCanvas(vp.element));
vtkDrivenCanvases.forEach((canvas) => {
const devicePixelRatio = window.devicePixelRatio || 1;
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width * devicePixelRatio;
canvas.height = rect.height * devicePixelRatio;
});
const { offScreenCanvasWidth, offScreenCanvasHeight } = this._resizeOffScreenCanvas(vtkDrivenCanvases);
let xOffset = 0;
for (let i = 0; i < viewportInputEntries.length; i++) {
const vtkDrivenViewportInputEntry = viewportInputEntries[i];
const canvas = vtkDrivenCanvases[i];
const internalViewportEntry = {
...vtkDrivenViewportInputEntry,
canvas,
};
this.addVtkjsDrivenViewport(internalViewportEntry, {
offScreenCanvasWidth,
offScreenCanvasHeight,
xOffset,
});
xOffset += canvas.width;
}
}
}
_resizeVTKViewports(vtkDrivenViewports, keepCamera = true, immediate = true) {
const canvasesDrivenByVtkJs = vtkDrivenViewports.map((vp) => {
return getOrCreateCanvas(vp.element);
});
canvasesDrivenByVtkJs.forEach((canvas) => {
const devicePixelRatio = window.devicePixelRatio || 1;
canvas.width = canvas.clientWidth * devicePixelRatio;
canvas.height = canvas.clientHeight * devicePixelRatio;
});
if (canvasesDrivenByVtkJs.length) {
const { offScreenCanvasWidth, offScreenCanvasHeight } = this._resizeOffScreenCanvas(canvasesDrivenByVtkJs);
this._resize(vtkDrivenViewports, offScreenCanvasWidth, offScreenCanvasHeight);
}
vtkDrivenViewports.forEach((vp) => {
const prevCamera = vp.getCamera();
const rotation = vp.getRotation();
const { flipHorizontal } = prevCamera;
const resizeResetViewport = vp;
if (resizeResetViewport.resetViewStateForResize) {
resizeResetViewport.resetViewStateForResize();
}
else {
resizeResetViewport.resetCameraForResize();
}
const displayArea = vp.getDisplayArea();
if (keepCamera) {
if (displayArea) {
if (flipHorizontal) {
vp.setCamera({ flipHorizontal });
}
if (rotation) {
vp.setViewPresentation({ rotation });
}
}
else {
vp.setCamera(prevCamera);
}
}
});
if (immediate) {
this.render();
}
}
performVtkDrawCall() {
const { offscreenMultiRenderWindow } = this;
const renderWindow = offscreenMultiRenderWindow.getRenderWindow();
const renderers = offscreenMultiRenderWindow.getRenderers();
if (!renderers.length) {
return;
}
for (let i = 0; i < renderers.length; i++) {
const { renderer, id } = renderers[i];
const viewportId = id.split('::')[0];
if (this._needsRender.has(viewportId)) {
renderer.setDraw(true);
}
else {
renderer.setDraw(false);
}
}
renderWindow.render();
for (let i = 0; i < renderers.length; i++) {
renderers[i].renderer.setDraw(false);
}
}
renderViewportUsingCustomOrVtkPipeline(viewport) {
let eventDetail;
if (viewport.sWidth < VIEWPORT_MIN_SIZE ||
viewport.sHeight < VIEWPORT_MIN_SIZE) {
console.warn('Viewport is too small', viewport.sWidth, viewport.sHeight);
return;
}
if (viewportTypeUsesCustomRenderingPipeline(viewport.type) === true) {
eventDetail =
viewport.customRenderViewportToCanvas();
}
else {
if (!this.offscreenMultiRenderWindow) {
throw new Error('GPU not available, and using a viewport with no custom render pipeline.');
}
const { offscreenMultiRenderWindow } = this;
const openGLRenderWindow = offscreenMultiRenderWindow.getOpenGLRenderWindow();
const context = openGLRenderWindow.get3DContext();
const offScreenCanvas = context.canvas;
eventDetail = this._renderViewportFromVtkCanvasToOnscreenCanvas(viewport, offScreenCanvas);
}
return eventDetail;
}
_renderViewportFromVtkCanvasToOnscreenCanvas(viewport, offScreenCanvas) {
const { element, canvas, sx, sy, sWidth, sHeight, id: viewportId, renderingEngineId, suppressEvents, } = viewport;
const { width: dWidth, height: dHeight } = canvas;
const onScreenContext = canvas.getContext('2d');
onScreenContext.drawImage(offScreenCanvas, sx, sy, sWidth, sHeight, 0, 0, dWidth, dHeight);
return {
element,
suppressEvents,
viewportId,
renderingEngineId,
viewportStatus: viewport.viewportStatus,
};
}
_resizeOffScreenCanvas(canvasesDrivenByVtkJs) {
const { offScreenCanvasContainer, offscreenMultiRenderWindow } = this;
const offScreenCanvasHeight = Math.max(...canvasesDrivenByVtkJs.map((canvas) => canvas.height));
let offScreenCanvasWidth = 0;
canvasesDrivenByVtkJs.forEach((canvas) => {
offScreenCanvasWidth += canvas.width;
});
offScreenCanvasContainer.width = offScreenCanvasWidth;
offScreenCanvasContainer.height = offScreenCanvasHeight;
offscreenMultiRenderWindow.resize();
return { offScreenCanvasWidth, offScreenCanvasHeight };
}
_resize(viewportsDrivenByVtkJs, offScreenCanvasWidth, offScreenCanvasHeight) {
let _xOffset = 0;
for (let i = 0; i < viewportsDrivenByVtkJs.length; i++) {
const viewport = viewportsDrivenByVtkJs[i];
const { sxStartDisplayCoords, syStartDisplayCoords, sxEndDisplayCoords, syEndDisplayCoords, sx, sy, sWidth, sHeight, } = this._getViewportCoordsOnOffScreenCanvas(viewport, offScreenCanvasWidth, offScreenCanvasHeight, _xOffset);
_xOffset += viewport.canvas.width;
viewport.sx = sx;
viewport.sy = sy;
viewport.sWidth = sWidth;
viewport.sHeight = sHeight;
const renderer = this.offscreenMultiRenderWindow.getRenderer(viewport.id);
renderer.setViewport(sxStartDisplayCoords, syStartDisplayCoords, sxEndDisplayCoords, syEndDisplayCoords);
}
return _xOffset;
}
_getViewportCoordsOnOffScreenCanvas(viewport, offScreenCanvasWidth, offScreenCanvasHeight, _xOffset) {
const { canvas } = viewport;
const { width: sWidth, height: sHeight } = canvas;
const sx = _xOffset;
const sy = 0;
const sxStartDisplayCoords = sx / offScreenCanvasWidth;
const syStartDisplayCoords = sy + (offScreenCanvasHeight - sHeight) / offScreenCanvasHeight;
const sWidthDisplayCoords = sWidth / offScreenCanvasWidth;
const sHeightDisplayCoords = sHeight / offScreenCanvasHeight;
return {
sxStartDisplayCoords,
syStartDisplayCoords,
sxEndDisplayCoords: sxStartDisplayCoords + sWidthDisplayCoords,
syEndDisplayCoords: syStartDisplayCoords + sHeightDisplayCoords,
sx,
sy,
sWidth,
sHeight,
};
}
}
export default TiledRenderingEngine;