diff --git a/packages/core/examples/sequentialrenderingengine/index.ts b/packages/core/examples/contextpoolrenderingengine/index.ts similarity index 92% rename from packages/core/examples/sequentialrenderingengine/index.ts rename to packages/core/examples/contextpoolrenderingengine/index.ts index 1e8a3e1d95..cf041782be 100644 --- a/packages/core/examples/sequentialrenderingengine/index.ts +++ b/packages/core/examples/contextpoolrenderingengine/index.ts @@ -21,11 +21,11 @@ const columns = parseInt(params.get('columns')) || 6; const count = rows * columns; setTitleAndDescription( - `${columns}x${rows} Grid with SequentialRenderingEngine`, - `Displays a ${columns}x${rows} grid of viewports using SequentialRenderingEngine with resize observer` + `${columns}x${rows} Grid with ContextPoolRenderingEngine`, + `Displays a ${columns}x${rows} grid of viewports using ContextPoolRenderingEngine with resize observer` ); -const renderingEngineId = 'mySequentialRenderingEngine'; +const renderingEngineId = 'myContextPoolRenderingEngine'; let renderingEngine: RenderingEngine; let resizeTimeout: number; @@ -113,7 +113,7 @@ async function run() { wadoRsRoot: 'https://d14fa38qiwhyfd.cloudfront.net/dicomweb', }); - // Instantiate a SequentialRenderingEngine + // Instantiate a ContextPoolRenderingEngine renderingEngine = new RenderingEngine(renderingEngineId); // Create viewport input array for all 36 viewports @@ -169,9 +169,9 @@ async function run() { info.style.padding = '10px'; info.style.borderRadius = '5px'; info.innerHTML = ` -

SequentialRenderingEngine Example

+

ContextPoolRenderingEngine Example

${count} viewports (${columns}x${rows} grid)

-

Using SequentialRenderingEngine for better performance with large viewport counts

+

Using ContextPoolRenderingEngine for better performance with large viewport counts

Resize the window to test resize observer

Avoids WebGL, browser and OS limits

`; diff --git a/packages/core/examples/stackBasic/index.ts b/packages/core/examples/stackBasic/index.ts index c34595a60d..52a09de1ee 100644 --- a/packages/core/examples/stackBasic/index.ts +++ b/packages/core/examples/stackBasic/index.ts @@ -36,7 +36,10 @@ content.appendChild(element); */ async function run() { // Init Cornerstone and related libraries - await initDemo(); + const config = (window as any).IS_TILED + ? { core: { renderingEngineMode: 'tiled' } } + : {}; + await initDemo(config); // Get Cornerstone imageIds and fetch metadata into RAM const imageIds = await createImageIdsAndCacheMetaData({ diff --git a/packages/core/examples/volumeBasic/index.ts b/packages/core/examples/volumeBasic/index.ts index 57c1b66519..6bca1b5637 100644 --- a/packages/core/examples/volumeBasic/index.ts +++ b/packages/core/examples/volumeBasic/index.ts @@ -39,7 +39,10 @@ content.appendChild(element); */ async function run() { // Init Cornerstone and related libraries - await initDemo(); + const config = (window as any).IS_TILED + ? { core: { renderingEngineMode: 'tiled' } } + : {}; + await initDemo(config); // Get Cornerstone imageIds and fetch metadata into RAM const imageIds = await createImageIdsAndCacheMetaData({ diff --git a/packages/core/examples/webGLContextPooling/index.ts b/packages/core/examples/webGLContextPooling/index.ts new file mode 100644 index 0000000000..4053567202 --- /dev/null +++ b/packages/core/examples/webGLContextPooling/index.ts @@ -0,0 +1,301 @@ +import type { Types } from '@cornerstonejs/core'; +import { + RenderingEngine, + Enums, + init as csRenderInit, + getRenderingEngine, +} from '@cornerstonejs/core'; +import { + createImageIdsAndCacheMetaData, + setTitleAndDescription, + initProviders, + initVolumeLoader, +} from '../../../../utils/demo/helpers'; +import * as cornerstoneTools from '@cornerstonejs/tools'; +import { init as csToolsInit } from '@cornerstonejs/tools'; +import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader'; + +const { + PanTool, + WindowLevelTool, + ZoomTool, + ToolGroupManager, + StackScrollTool, + Enums: csToolsEnums, + synchronizers, +} = cornerstoneTools; + +const { ViewportType } = Enums; +const { MouseBindings } = csToolsEnums; +const { createImageSliceSynchronizer } = synchronizers; + +// Define unique ids +const renderingEngineId = 'myRenderingEngine'; +const toolGroupId = 'STACK_TOOL_GROUP'; +const imageSliceSyncronizerId = 'IMAGE_SLICE_SYNCHRONIZER'; + +// Create viewport IDs for 8x8 grid +const viewportIds: string[] = []; +for (let row = 0; row < 8; row++) { + for (let col = 0; col < 8; col++) { + viewportIds.push(`STACK_VIEWPORT_${row}_${col}`); + } +} + +// ResizeObserver setup +let resizeTimeout: number; + +// Debounced resize handler +const handleResize = () => { + clearTimeout(resizeTimeout); + resizeTimeout = window.setTimeout(() => { + const renderingEngine = getRenderingEngine(renderingEngineId); + if (renderingEngine) { + renderingEngine.resize(true, false); + } + }, 50); // 50ms debounce +}; + +// Create ResizeObserver for dynamic resizing +const resizeObserver = new ResizeObserver(handleResize); + +// ======== Set up page ======== // +setTitleAndDescription( + 'WebGL Context Pooling - 8x8 Grid', + `Demonstrates an 8x8 grid of synchronized stack viewports with configurable WebGL context pooling, use left click to scroll through the stack in all viewports. The current number of WebGL contexts is set to ${ + localStorage.getItem('webglContextCount') || 1 + }.` +); + +const content = document.getElementById('content'); + +// Ensure no scroll overflow +document.body.style.overflow = 'hidden'; +content.style.overflow = 'hidden'; + +// Create WebGL context count control +const controlsDiv = document.createElement('div'); +controlsDiv.style.marginBottom = '10px'; + +const contextCountLabel = document.createElement('label'); +contextCountLabel.innerText = 'WebGL Context Count: '; +contextCountLabel.style.marginRight = '10px'; + +const contextCountDropdown = document.createElement('select'); +const contextOptions = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + +// Get stored value or default to 1 +const storedContextCount = localStorage.getItem('webglContextCount'); +const currentContextCount = storedContextCount + ? parseInt(storedContextCount) + : 1; + +contextOptions.forEach((value) => { + const option = document.createElement('option'); + option.value = value.toString(); + option.text = value.toString(); + if (value === currentContextCount) { + option.selected = true; + } + contextCountDropdown.appendChild(option); +}); + +contextCountDropdown.addEventListener('change', (e) => { + const newValue = (e.target as HTMLSelectElement).value; + localStorage.setItem('webglContextCount', newValue); + location.reload(); +}); + +controlsDiv.appendChild(contextCountLabel); +controlsDiv.appendChild(contextCountDropdown); +content.appendChild(controlsDiv); + +// Create viewport grid container +const viewportGrid = document.createElement('div'); +viewportGrid.style.display = 'grid'; +viewportGrid.style.gridTemplateColumns = 'repeat(8, 1fr)'; +viewportGrid.style.gridTemplateRows = 'repeat(8, 1fr)'; +viewportGrid.style.gap = '2px'; +viewportGrid.style.width = '100%'; +viewportGrid.style.height = 'calc(100vh - 200px)'; // Account for controls and text +viewportGrid.style.backgroundColor = '#000'; + +content.appendChild(viewportGrid); + +// Observe the viewport grid for resize events +resizeObserver.observe(viewportGrid); + +// Create viewport elements +const viewportElements: HTMLDivElement[] = []; +const sliceCountElements: HTMLDivElement[] = []; + +for (let i = 0; i < 64; i++) { + const viewportContainer = document.createElement('div'); + viewportContainer.style.position = 'relative'; + viewportContainer.style.width = '100%'; + viewportContainer.style.height = '100%'; + + const element = document.createElement('div'); + element.style.width = '100%'; + element.style.height = '100%'; + element.oncontextmenu = (e) => e.preventDefault(); + + // Create slice count display + const sliceCountDiv = document.createElement('div'); + sliceCountDiv.style.position = 'absolute'; + sliceCountDiv.style.top = '5px'; + sliceCountDiv.style.left = '5px'; + sliceCountDiv.style.color = 'white'; + sliceCountDiv.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'; + sliceCountDiv.style.padding = '2px 5px'; + sliceCountDiv.style.fontSize = '12px'; + sliceCountDiv.style.zIndex = '1000'; + sliceCountDiv.style.pointerEvents = 'none'; + sliceCountDiv.innerText = 'Slice: 0/0'; + + viewportContainer.appendChild(element); + viewportContainer.appendChild(sliceCountDiv); + viewportGrid.appendChild(viewportContainer); + + viewportElements.push(element); + sliceCountElements.push(sliceCountDiv); +} + +/** + * Update slice count display for a viewport + */ +function updateSliceCount( + viewportId: string, + currentIndex: number, + totalImages: number +) { + const viewportIndex = viewportIds.indexOf(viewportId); + if (viewportIndex !== -1) { + sliceCountElements[viewportIndex].innerText = `Slice: ${ + currentIndex + 1 + }/${totalImages}`; + } +} + +/** + * Runs the demo + */ +async function run() { + // Initialize providers and loaders + initProviders(); + cornerstoneDICOMImageLoader.init(); + initVolumeLoader(); + + // Initialize Cornerstone with custom WebGL context count + await csRenderInit({ + rendering: { + webGlContextCount: currentContextCount, + }, + }); + + // Initialize cornerstone tools + await csToolsInit(); + + // Add tools to Cornerstone3D + cornerstoneTools.addTool(PanTool); + cornerstoneTools.addTool(WindowLevelTool); + cornerstoneTools.addTool(StackScrollTool); + cornerstoneTools.addTool(ZoomTool); + + // Create a tool group for all viewports + const toolGroup = ToolGroupManager.createToolGroup(toolGroupId); + + // Add tools to the tool group + toolGroup.addTool(StackScrollTool.toolName); + + // Set the initial state of the tools + toolGroup.setToolActive(StackScrollTool.toolName, { + bindings: [ + { + mouseButton: MouseBindings.Primary, // Left Click + }, + ], + }); + + // Get Cornerstone imageIds and fetch metadata into RAM + const imageIds = await createImageIdsAndCacheMetaData({ + StudyInstanceUID: + '1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463', + SeriesInstanceUID: + '1.3.6.1.4.1.14519.5.2.1.7009.2403.226151125820845824875394858561', + wadoRsRoot: 'https://d14fa38qiwhyfd.cloudfront.net/dicomweb', + }); + + // Instantiate a rendering engine + const renderingEngine = new RenderingEngine(renderingEngineId); + + // Create the viewports + const viewportInputArray: Types.PublicViewportInput[] = viewportIds.map( + (viewportId, index) => ({ + viewportId, + type: ViewportType.STACK, + element: viewportElements[index], + defaultOptions: { + background: [0.1, 0.1, 0.1], + }, + }) + ); + + renderingEngine.setViewports(viewportInputArray); + + // Set the tool group on all viewports + viewportIds.forEach((viewportId) => { + toolGroup.addViewport(viewportId, renderingEngineId); + }); + + // Get all stack viewports + const stackViewports = renderingEngine.getStackViewports(); + + // Set the stack on each viewport + await Promise.all( + stackViewports.map(async (viewport) => { + await viewport.setStack(imageIds); + + // Update initial slice count + updateSliceCount( + viewport.id, + viewport.getCurrentImageIdIndex(), + imageIds.length + ); + + // Add event listener for stack scroll + viewport.element.addEventListener(Enums.Events.STACK_VIEWPORT_SCROLL, (( + evt: Types.EventTypes.StackViewportScrollEvent + ) => { + updateSliceCount( + viewport.id, + evt.detail.newImageIdIndex, + imageIds.length + ); + }) as EventListener); + }) + ); + + // Create and configure the image slice synchronizer + const imageSliceSynchronizer = createImageSliceSynchronizer( + imageSliceSyncronizerId + ); + + // Add all viewports to the synchronizer + viewportIds.forEach((viewportId) => { + imageSliceSynchronizer.add({ + renderingEngineId, + viewportId, + }); + }); + + // Enable stack prefetch for all viewports + stackViewports.forEach((viewport) => { + cornerstoneTools.utilities.stackPrefetch.enable(viewport.element); + }); + + // Render all viewports + renderingEngine.renderViewports(viewportIds); +} + +run(); diff --git a/packages/core/src/RenderingEngine/BaseRenderingEngine.ts b/packages/core/src/RenderingEngine/BaseRenderingEngine.ts index 9778c4c768..7a78cfb338 100644 --- a/packages/core/src/RenderingEngine/BaseRenderingEngine.ts +++ b/packages/core/src/RenderingEngine/BaseRenderingEngine.ts @@ -3,7 +3,6 @@ import renderingEngineCache from './renderingEngineCache'; import eventTarget from '../eventTarget'; import uuidv4 from '../utilities/uuidv4'; import triggerEvent from '../utilities/triggerEvent'; -import { vtkOffscreenMultiRenderWindow } from './vtkClasses'; import ViewportType from '../enums/ViewportType'; import BaseVolumeViewport from './BaseVolumeViewport'; import StackViewport from './StackViewport'; @@ -22,6 +21,7 @@ import type { IViewport, } from '../types/IViewport'; import { OrientationAxis } from '../enums'; +import type { VtkOffscreenMultiRenderWindow } from '../types'; // Rendering engines seem to not like rendering things less than 2 pixels per side export const VIEWPORT_MIN_SIZE = 2; @@ -39,8 +39,8 @@ abstract class BaseRenderingEngine { /** A flag which tells if the renderingEngine has been destroyed or not */ public hasBeenDestroyed: boolean; // eslint-disable-next-line @typescript-eslint/no-explicit-any - public offscreenMultiRenderWindow: any; - readonly offScreenCanvasContainer: HTMLDivElement; + public offscreenMultiRenderWindow: VtkOffscreenMultiRenderWindow; + public offScreenCanvasContainer: HTMLDivElement; protected _viewports: Map; protected _needsRender = new Set(); protected _animationFrameSet = false; @@ -62,15 +62,6 @@ abstract class BaseRenderingEngine { ); } - if (!this.useCPURendering) { - this.offscreenMultiRenderWindow = - vtkOffscreenMultiRenderWindow.newInstance(); - this.offScreenCanvasContainer = document.createElement('div'); - this.offscreenMultiRenderWindow.setContainer( - this.offScreenCanvasContainer - ); - } - this._viewports = new Map(); this.hasBeenDestroyed = false; } @@ -177,7 +168,10 @@ abstract class BaseRenderingEngine { !viewportTypeUsesCustomRenderingPipeline(viewport.type) && !this.useCPURendering ) { - this.offscreenMultiRenderWindow.removeRenderer(viewportId); + // Only remove renderer if offscreenMultiRenderWindow exists (not in ContextPoolRenderingEngine) + if (this.offscreenMultiRenderWindow) { + this.offscreenMultiRenderWindow.removeRenderer(viewportId); + } } // 5. Remove the requested viewport from the rendering engine @@ -445,11 +439,15 @@ abstract class BaseRenderingEngine { if (!this.useCPURendering) { const viewports = this._getViewportsAsArray(); viewports.forEach((vp) => { - this.offscreenMultiRenderWindow.removeRenderer(vp.id); + if (this.offscreenMultiRenderWindow) { + this.offscreenMultiRenderWindow.removeRenderer(vp.id); + } }); // Free up WebGL resources - this.offscreenMultiRenderWindow.delete(); + if (this.offscreenMultiRenderWindow) { + this.offscreenMultiRenderWindow.delete(); + } // Make sure all references go stale and are garbage collected. delete this.offscreenMultiRenderWindow; @@ -634,6 +632,29 @@ abstract class BaseRenderingEngine { triggerEvent(eventTarget, Events.ELEMENT_ENABLED, eventDetail); } + /** + * Returns the renderer for a given viewportId. + * @param viewportId - The Id of the viewport. + * @returns The renderer for the viewport. + */ + public getRenderer(viewportId) { + return this.offscreenMultiRenderWindow.getRenderer(viewportId); + } + + /** + * Returns the offscreen multi-render window used for rendering. + */ + public getOffscreenMultiRenderWindow( + viewportId?: string + ): VtkOffscreenMultiRenderWindow { + if (this.useCPURendering) { + throw new Error( + 'Offscreen multi render window is not available when using CPU rendering.' + ); + } + return this.offscreenMultiRenderWindow; + } + /** * Sets multiple viewports using custom rendering * pipelines to the `RenderingEngine`. @@ -804,17 +825,6 @@ abstract class BaseRenderingEngine { viewportInputEntries: NormalizedViewportInput[] ): void; - /** - * Renders the given viewport using its preferred method. - * This method must be implemented by subclasses to define their specific - * rendering approach. - * - * @param viewport - The viewport to render - */ - protected abstract renderViewportUsingCustomOrVtkPipeline( - viewport: IViewport - ): EventTypes.ImageRenderedEventDetail; - /** * Renders a particular `Viewport`'s on screen canvas. * This method must be implemented by subclasses to define how to copy diff --git a/packages/core/src/RenderingEngine/BaseVolumeViewport.ts b/packages/core/src/RenderingEngine/BaseVolumeViewport.ts index 6b55b5dca9..2d3a3edb0d 100644 --- a/packages/core/src/RenderingEngine/BaseVolumeViewport.ts +++ b/packages/core/src/RenderingEngine/BaseVolumeViewport.ts @@ -74,7 +74,8 @@ import imageIdToURI from '../utilities/imageIdToURI'; import uuidv4 from '../utilities/uuidv4'; import * as metaData from '../metaData'; import { getCameraVectors } from './helpers/getCameraVectors'; -import { isSequentialRenderingEngine } from './helpers/isSequentialRenderingEngine'; +import { isContextPoolRenderingEngine } from './helpers/isContextPoolRenderingEngine'; +import type vtkRenderer from '@kitware/vtk.js/Rendering/Core/Renderer'; /** * Abstract base class for volume viewports. VolumeViewports are used to render * 3D volumes from which various orientations can be viewed. Since VolumeViewports @@ -1603,7 +1604,7 @@ abstract class BaseVolumeViewport extends Viewport { * @public */ - public canvasToWorld = (canvasPos: Point2): Point3 => { + public canvasToWorldTiled = (canvasPos: Point2): Point3 => { const vtkCamera = this.getVtkActiveCamera() as vtkSlabCameraType; /** @@ -1631,7 +1632,7 @@ abstract class BaseVolumeViewport extends Viewport { vtkCamera.setIsPerformingCoordinateTransformation?.(true); const renderer = this.getRenderer(); - const displayCoords = this.getVtkDisplayCoords(canvasPos); + const displayCoords = this.getVtkDisplayCoordsTiled(canvasPos); const offscreenMultiRenderWindow = this.getRenderingEngine().offscreenMultiRenderWindow; const openGLRenderWindow = @@ -1648,7 +1649,7 @@ abstract class BaseVolumeViewport extends Viewport { return [worldCoord[0], worldCoord[1], worldCoord[2]]; }; - public canvasToWorldSequential = (canvasPos: Point2): Point3 => { + public canvasToWorldContextPool = (canvasPos: Point2): Point3 => { const vtkCamera = this.getVtkActiveCamera() as vtkSlabCameraType; /** @@ -1723,7 +1724,7 @@ abstract class BaseVolumeViewport extends Viewport { * @returns The corresponding display coordinates. * */ - public getVtkDisplayCoords = (canvasPos: Point2): Point3 => { + public getVtkDisplayCoordsTiled = (canvasPos: Point2): Point3 => { const devicePixelRatio = window.devicePixelRatio || 1; const canvasPosWithDPR = [ canvasPos[0] * devicePixelRatio, @@ -1744,7 +1745,7 @@ abstract class BaseVolumeViewport extends Viewport { return [displayCoord[0], displayCoord[1], 0]; }; - public getVtkDisplayCoordsSequential = (canvasPos: Point2): Point3 => { + public getVtkDisplayCoordsContextPool = (canvasPos: Point2): Point3 => { const devicePixelRatio = window.devicePixelRatio || 1; const canvasPosWithDPR = [ canvasPos[0] * devicePixelRatio, @@ -1768,7 +1769,7 @@ abstract class BaseVolumeViewport extends Viewport { * @returns The corresponding canvas coordinates. * @public */ - public worldToCanvas = (worldPos: Point3): Point2 => { + public worldToCanvasTiled = (worldPos: Point3): Point2 => { const vtkCamera = this.getVtkActiveCamera() as vtkSlabCameraType; /** @@ -1825,7 +1826,7 @@ abstract class BaseVolumeViewport extends Viewport { return canvasCoordWithDPR; }; - public worldToCanvasSequential = (worldPos: Point3): Point2 => { + public worldToCanvasContextPool = (worldPos: Point3): Point2 => { const vtkCamera = this.getVtkActiveCamera() as vtkSlabCameraType; /** @@ -1893,6 +1894,29 @@ abstract class BaseVolumeViewport extends Viewport { return canvasCoordWithDPR; }; + /** + * Get the renderer for this viewport - handles ContextPoolRenderingEngine + */ + public getRendererContextPool(): vtkRenderer { + const renderingEngine = this.getRenderingEngine(); + return renderingEngine.getRenderer(this.id); + } + + /** + * Returns the `vtkRenderer` responsible for rendering the `Viewport`. + * + * @returns The `vtkRenderer` for the `Viewport`. + */ + public getRendererTiled(): vtkRenderer { + const renderingEngine = this.getRenderingEngine(); + + if (!renderingEngine || renderingEngine.hasBeenDestroyed) { + throw new Error('Rendering engine has been destroyed'); + } + + return renderingEngine.offscreenMultiRenderWindow?.getRenderer(this.id); + } + /* * Checking if the imageURI is in the volumes that are being * rendered by the viewport. imageURI is the imageId without the schema @@ -2137,8 +2161,7 @@ abstract class BaseVolumeViewport extends Viewport { } private _configureRenderingPipeline() { - const renderingEngine = this.getRenderingEngine(); - const isSequential = isSequentialRenderingEngine(renderingEngine); + const isContextPool = isContextPoolRenderingEngine(); for (const key in this.renderingPipelineFunctions) { if ( @@ -2149,23 +2172,27 @@ abstract class BaseVolumeViewport extends Viewport { ) { const functions = this.renderingPipelineFunctions[key]; - this[key] = isSequential ? functions.sequential : functions.default; + this[key] = isContextPool ? functions.contextPool : functions.tiled; } } } protected renderingPipelineFunctions = { worldToCanvas: { - default: this.worldToCanvas, - sequential: this.worldToCanvasSequential, + tiled: this.worldToCanvasTiled, + contextPool: this.worldToCanvasContextPool, }, canvasToWorld: { - default: this.canvasToWorld, - sequential: this.canvasToWorldSequential, + tiled: this.canvasToWorldTiled, + contextPool: this.canvasToWorldContextPool, }, getVtkDisplayCoords: { - default: this.getVtkDisplayCoords, - sequential: this.getVtkDisplayCoordsSequential, + tiled: this.getVtkDisplayCoordsTiled, + contextPool: this.getVtkDisplayCoordsContextPool, + }, + getRenderer: { + tiled: this.getRendererTiled, + contextPool: this.getRendererContextPool, }, }; } diff --git a/packages/core/src/RenderingEngine/SequentialRenderingEngine.ts b/packages/core/src/RenderingEngine/ContextPoolRenderingEngine.ts similarity index 52% rename from packages/core/src/RenderingEngine/SequentialRenderingEngine.ts rename to packages/core/src/RenderingEngine/ContextPoolRenderingEngine.ts index 097fa15906..2e43b6f9da 100644 --- a/packages/core/src/RenderingEngine/SequentialRenderingEngine.ts +++ b/packages/core/src/RenderingEngine/ContextPoolRenderingEngine.ts @@ -1,16 +1,17 @@ import BaseRenderingEngine, { VIEWPORT_MIN_SIZE } from './BaseRenderingEngine'; +import WebGLContextPool from './WebGLContextPool'; +import { getConfiguration } from '../init'; import Events from '../enums/Events'; import eventTarget from '../eventTarget'; import triggerEvent from '../utilities/triggerEvent'; import ViewportType from '../enums/ViewportType'; import VolumeViewport from './VolumeViewport'; import StackViewport from './StackViewport'; +import VolumeViewport3D from './VolumeViewport3D'; import viewportTypeUsesCustomRenderingPipeline from './helpers/viewportTypeUsesCustomRenderingPipeline'; import getOrCreateCanvas from './helpers/getOrCreateCanvas'; import type IStackViewport from '../types/IStackViewport'; import type IVolumeViewport from '../types/IVolumeViewport'; -import VolumeViewport3D from './VolumeViewport3D'; -import type vtkRenderer from '@kitware/vtk.js/Rendering/Core/Renderer'; import type * as EventTypes from '../types/EventTypes'; import type { @@ -19,16 +20,28 @@ import type { NormalizedViewportInput, IViewport, } from '../types/IViewport'; +import type vtkRenderer from '@kitware/vtk.js/Rendering/Core/Renderer'; +import type { VtkOffscreenMultiRenderWindow } from '../types'; /** - * SequentialRenderingEngine extends BaseRenderingEngine to provide a different - * rendering strategy where viewports are rendered sequentially one at a time on the offscreen, - * rather than all at once. - * + * ContextPoolRenderingEngine extends BaseRenderingEngine to provide parallel rendering + * capabilities using multiple WebGL contexts for improved performance. * * @public */ -class SequentialRenderingEngine extends BaseRenderingEngine { +class ContextPoolRenderingEngine extends BaseRenderingEngine { + private contextPool: WebGLContextPool; + + constructor(id?: string) { + super(id); + const { rendering } = getConfiguration(); + const { webGlContextCount } = rendering; + + if (!this.useCPURendering) { + this.contextPool = new WebGLContextPool(webGlContextCount); + } + } + /** * Enables a viewport to be driven by the offscreen vtk.js rendering engine. * @@ -66,7 +79,20 @@ class SequentialRenderingEngine extends BaseRenderingEngine { element.tabIndex = -1; - this.offscreenMultiRenderWindow.addRenderer({ + // Assign viewport to a context + // Stack viewports get distributed across contexts, all others use context 0 + let contextIndex = 0; + if (type === ViewportType.STACK) { + const contexts = this.contextPool.getAllContexts(); + contextIndex = this._viewports.size % contexts.length; + } + this.contextPool.assignViewportToContext(viewportId, contextIndex); + + // Get the context and add the renderer + const contextData = this.contextPool.getContextByIndex(contextIndex); + + const { context: offscreenMultiRenderWindow } = contextData; + offscreenMultiRenderWindow.addRenderer({ viewport: [0, 0, 1, 1], id: viewportId, background: defaultOptions.background @@ -204,50 +230,80 @@ class SequentialRenderingEngine extends BaseRenderingEngine { /** * Renders all viewports. */ - protected _renderFlaggedViewports = () => { + protected _renderFlaggedViewports = (): void => { this._throwIfDestroyed(); 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); + const viewportsToRender = viewports.filter((vp) => + this._needsRender.has(vp.id) + ); - if (this._needsRender.size === 0) { - break; - } - } + if (viewportsToRender.length === 0) { + this._animationFrameSet = false; + this._animationFrameHandle = null; + return; } + // Render all viewports synchronously + const eventDetails = viewportsToRender.map((viewport) => { + const eventDetail = this.renderViewportUsingCustomOrVtkPipeline(viewport); + viewport.setRendered(); + this._needsRender.delete(viewport.id); + return eventDetail; + }); + this._animationFrameSet = false; this._animationFrameHandle = null; - eventDetailArray.forEach((eventDetail) => { - if (!eventDetail?.element) { - return; + // Trigger all events after rendering is complete + eventDetails.forEach((eventDetail) => { + if (eventDetail?.element) { + triggerEvent(eventDetail.element, Events.IMAGE_RENDERED, eventDetail); } - triggerEvent(eventDetail.element, Events.IMAGE_RENDERED, eventDetail); }); }; - /** - * Renders the given viewport - * using its proffered method. - * - * @param viewport - The viewport to render - */ - protected renderViewportUsingCustomOrVtkPipeline( + private renderViewportUsingCustomOrVtkPipeline( viewport: IViewport ): EventTypes.ImageRenderedEventDetail { - let eventDetail: EventTypes.ImageRenderedEventDetail; + // Handle custom rendering pipeline viewports + if (viewportTypeUsesCustomRenderingPipeline(viewport.type)) { + const eventDetail = + viewport.customRenderViewportToCanvas() as EventTypes.ImageRenderedEventDetail; + return eventDetail; + } + + // If using CPU rendering, throw error + if (this.useCPURendering) { + throw new Error( + 'GPU not available, and using a viewport with no custom render pipeline.' + ); + } + + // Get the context assigned to this viewport + const assignedContextIndex = this.contextPool.getContextIndexForViewport( + viewport.id + ); + + const contextData = + this.contextPool.getContextByIndex(assignedContextIndex); + + const { context, container } = contextData; + + const eventDetail = this._renderViewportWithContext( + viewport, + context, + container + ); + return eventDetail; + } + private _renderViewportWithContext( + viewport: IViewport, + offscreenMultiRenderWindow: VtkOffscreenMultiRenderWindow, + offScreenCanvasContainer: HTMLDivElement + ): EventTypes.ImageRenderedEventDetail { + // Check viewport size if ( viewport.sWidth < VIEWPORT_MIN_SIZE || viewport.sHeight < VIEWPORT_MIN_SIZE @@ -255,51 +311,59 @@ class SequentialRenderingEngine extends BaseRenderingEngine { console.warn('Viewport is too small', viewport.sWidth, viewport.sHeight); return; } - if (viewportTypeUsesCustomRenderingPipeline(viewport.type) === true) { - eventDetail = - viewport.customRenderViewportToCanvas() as EventTypes.ImageRenderedEventDetail; - } else { - if (this.useCPURendering) { - throw new Error( - 'GPU not available, and using a viewport with no custom render pipeline.' - ); - } - const { offscreenMultiRenderWindow } = this; - const renderWindow = offscreenMultiRenderWindow.getRenderWindow(); + if (viewportTypeUsesCustomRenderingPipeline(viewport.type)) { + return viewport.customRenderViewportToCanvas() as EventTypes.ImageRenderedEventDetail; + } - this._resizeOffScreenCanvasForSingleViewport(viewport.canvas); + if (this.useCPURendering) { + throw new Error( + 'GPU not available, and using a viewport with no custom render pipeline.' + ); + } - const renderer = offscreenMultiRenderWindow.getRenderer(viewport.id); + // Add renderer if not already present + if (!offscreenMultiRenderWindow.getRenderer(viewport.id)) { + offscreenMultiRenderWindow.addRenderer({ + viewport: [0, 0, 1, 1], + id: viewport.id, + background: viewport.defaultOptions?.background || [0, 0, 0], + }); + } - renderer.setViewport([0, 0, 1, 1]); + const renderWindow = offscreenMultiRenderWindow.getRenderWindow(); - const allRenderers = offscreenMultiRenderWindow.getRenderers(); + this._resizeOffScreenCanvasForViewport( + viewport.canvas, + offScreenCanvasContainer, + offscreenMultiRenderWindow + ); - allRenderers.forEach(({ renderer: r, id }) => { - r.setDraw(id === viewport.id); - }); + const renderer = offscreenMultiRenderWindow.getRenderer(viewport.id); + renderer.setViewport(0, 0, 1, 1); - const widgetRenderers = this.getWidgetRenderers(); + // Set only this renderer to draw + const allRenderers = offscreenMultiRenderWindow.getRenderers(); + allRenderers.forEach(({ renderer: r, id }) => { + r.setDraw(id === viewport.id); + }); - widgetRenderers.forEach((viewportId, renderer) => { - renderer.setDraw(viewportId === viewport.id); - }); + // Handle widget renderers + const widgetRenderers = this.getWidgetRenderers(); + widgetRenderers.forEach((viewportId, renderer) => { + renderer.setDraw(viewportId === viewport.id); + }); - renderWindow.render(); + renderWindow.render(); - allRenderers.forEach(({ renderer: r }) => r.setDraw(false)); + allRenderers.forEach(({ renderer: r }) => r.setDraw(false)); - const openGLRenderWindow = - offscreenMultiRenderWindow.getOpenGLRenderWindow(); - const context = openGLRenderWindow.get3DContext(); - const offScreenCanvas = context.canvas; + const openGLRenderWindow = + offscreenMultiRenderWindow.getOpenGLRenderWindow(); + const context = openGLRenderWindow.get3DContext(); + const offScreenCanvas = context.canvas; - eventDetail = this._renderViewportFromVtkCanvasToOnscreenCanvas( - viewport, - offScreenCanvas - ); - } + const eventDetail = this._copyToOnscreenCanvas(viewport, offScreenCanvas); return eventDetail; } @@ -312,6 +376,29 @@ class SequentialRenderingEngine extends BaseRenderingEngine { protected _renderViewportFromVtkCanvasToOnscreenCanvas( viewport: IViewport, offScreenCanvas: HTMLCanvasElement + ): EventTypes.ImageRenderedEventDetail { + return this._copyToOnscreenCanvas(viewport, offScreenCanvas); + } + + private _resizeOffScreenCanvasForViewport( + viewportCanvas: HTMLCanvasElement, + offScreenCanvasContainer: HTMLDivElement, + offscreenMultiRenderWindow: VtkOffscreenMultiRenderWindow + ): void { + const offScreenCanvasWidth = viewportCanvas.width; + const offScreenCanvasHeight = viewportCanvas.height; + + // @ts-expect-error + offScreenCanvasContainer.width = offScreenCanvasWidth; + // @ts-expect-error + offScreenCanvasContainer.height = offScreenCanvasHeight; + + offscreenMultiRenderWindow.resize(); + } + + private _copyToOnscreenCanvas( + viewport: IViewport, + offScreenCanvas: HTMLCanvasElement ): EventTypes.ImageRenderedEventDetail { const { element, @@ -320,11 +407,9 @@ class SequentialRenderingEngine extends BaseRenderingEngine { renderingEngineId, suppressEvents, } = viewport; - const { width: dWidth, height: dHeight } = canvas; const onScreenContext = canvas.getContext('2d'); - onScreenContext.drawImage( offScreenCanvas, 0, @@ -346,27 +431,6 @@ class SequentialRenderingEngine extends BaseRenderingEngine { }; } - private _resizeOffScreenCanvasForSingleViewport( - currentViewport: HTMLCanvasElement - ): { - offScreenCanvasWidth: number; - offScreenCanvasHeight: number; - } { - const { offScreenCanvasContainer, offscreenMultiRenderWindow } = this; - - const offScreenCanvasWidth = currentViewport.width; - const offScreenCanvasHeight = currentViewport.height; - - // @ts-expect-error - offScreenCanvasContainer.width = offScreenCanvasWidth; - // @ts-expect-error - offScreenCanvasContainer.height = offScreenCanvasHeight; - - offscreenMultiRenderWindow.resize(); - - return { offScreenCanvasWidth, offScreenCanvasHeight }; - } - private _resize( viewportsDrivenByVtkJs: (IStackViewport | IVolumeViewport)[] ): void { @@ -376,20 +440,21 @@ class SequentialRenderingEngine extends BaseRenderingEngine { viewport.sWidth = viewport.canvas.width; viewport.sHeight = viewport.canvas.height; - const renderer = this.offscreenMultiRenderWindow.getRenderer(viewport.id); - renderer.setViewport([0, 0, 1, 1]); + // Get the context assigned to this viewport + const contextIndex = this.contextPool.getContextIndexForViewport( + viewport.id + ); + + const contextData = this.contextPool.getContextByIndex(contextIndex); + const { context: offscreenMultiRenderWindow } = contextData; + const renderer = offscreenMultiRenderWindow.getRenderer(viewport.id); + + renderer.setViewport(0, 0, 1, 1); } } - /** - * - * @returns A map of widget renderers and their associated viewport IDs. - * This method iterates through all viewports, retrieves their widgets, - * and maps each widget's renderer to the viewport ID. - */ private getWidgetRenderers(): Map { const allViewports = this._getViewportsAsArray(); - const widgetRenderers = new Map(); allViewports.forEach((vp) => { @@ -404,6 +469,76 @@ class SequentialRenderingEngine extends BaseRenderingEngine { return widgetRenderers; } + + /** + * Get the renderer for a specific viewport + * @param viewportId - The ID of the viewport + * @returns The vtkRenderer instance or undefined + */ + public getRenderer(viewportId: string): vtkRenderer | undefined { + const contextIndex = + this.contextPool?.getContextIndexForViewport(viewportId); + + const contextData = this.contextPool.getContextByIndex(contextIndex); + + const { context: offscreenMultiRenderWindow } = contextData; + return offscreenMultiRenderWindow.getRenderer(viewportId); + } + + /** + * Disables the requested viewportId from the rendering engine. + * Calls the base implementation and then handles context-specific cleanup. + * + * @param viewportId - viewport Id + */ + public disableElement(viewportId: string): void { + const viewport = this.getViewport(viewportId); + if (!viewport) { + return; + } + super.disableElement(viewportId); + + if ( + !viewportTypeUsesCustomRenderingPipeline(viewport.type) && + !this.useCPURendering + ) { + const contextIndex = + this.contextPool.getContextIndexForViewport(viewportId); + + if (contextIndex !== undefined) { + const contextData = this.contextPool.getContextByIndex(contextIndex); + + if (contextData) { + const { context: offscreenMultiRenderWindow } = contextData; + offscreenMultiRenderWindow.removeRenderer(viewportId); + } + } + } + } + + public destroy(): void { + if (this.contextPool) { + this.contextPool.destroy(); + } + super.destroy(); + } + + public getOffscreenMultiRenderWindow( + viewportId: string + ): VtkOffscreenMultiRenderWindow { + if (this.useCPURendering) { + throw new Error( + 'Offscreen multi render window is not available when using CPU rendering.' + ); + } + + const contextIndex = + this.contextPool.getContextIndexForViewport(viewportId); + + const contextData = this.contextPool.getContextByIndex(contextIndex); + + return contextData.context; + } } -export default SequentialRenderingEngine; +export default ContextPoolRenderingEngine; diff --git a/packages/core/src/RenderingEngine/RenderingEngine.ts b/packages/core/src/RenderingEngine/RenderingEngine.ts index d0a245d66b..c8b7bdab66 100644 --- a/packages/core/src/RenderingEngine/RenderingEngine.ts +++ b/packages/core/src/RenderingEngine/RenderingEngine.ts @@ -1,19 +1,20 @@ import { getConfiguration } from '../init'; -import StandardRenderingEngine from './StandardRenderingEngine'; -import SequentialRenderingEngine from './SequentialRenderingEngine'; +import TiledRenderingEngine from './TiledRenderingEngine'; +import ContextPoolRenderingEngine from './ContextPoolRenderingEngine'; import type BaseRenderingEngine from './BaseRenderingEngine'; import type { IStackViewport, IVolumeViewport, IViewport, PublicViewportInput, + VtkOffscreenMultiRenderWindow, } from '../types'; import { RenderingEngineModeEnum } from '../enums'; class RenderingEngine { public hasBeenDestroyed: boolean; // eslint-disable-next-line @typescript-eslint/no-explicit-any - public offscreenMultiRenderWindow: any; + public offscreenMultiRenderWindow: VtkOffscreenMultiRenderWindow; private _implementation?: BaseRenderingEngine; constructor(id?: string) { @@ -21,17 +22,17 @@ class RenderingEngine { const renderingEngineMode = config?.rendering?.renderingEngineMode; switch (renderingEngineMode) { - case RenderingEngineModeEnum.Standard: - this._implementation = new StandardRenderingEngine(id); + case RenderingEngineModeEnum.Tiled: + this._implementation = new TiledRenderingEngine(id); break; - case RenderingEngineModeEnum.Next: - this._implementation = new SequentialRenderingEngine(id); + case RenderingEngineModeEnum.ContextPool: + this._implementation = new ContextPoolRenderingEngine(id); break; default: console.warn( `RenderingEngine: Unknown rendering engine mode "${renderingEngineMode}". Defaulting to Next rendering engine.` ); - this._implementation = new SequentialRenderingEngine(id); + this._implementation = new ContextPoolRenderingEngine(id); break; } } @@ -75,6 +76,11 @@ class RenderingEngine { public getVolumeViewports(): IVolumeViewport[] { return this._implementation.getVolumeViewports(); } + + public getRenderer(viewportId: string) { + return this._implementation.getRenderer(viewportId); + } + public fillCanvasWithBackgroundColor( canvas: HTMLCanvasElement, backgroundColor: [number, number, number] @@ -100,6 +106,12 @@ class RenderingEngine { public destroy(): void { return this._implementation.destroy(); } + + public getOffscreenMultiRenderWindow( + viewportId?: string + ): VtkOffscreenMultiRenderWindow { + return this._implementation.getOffscreenMultiRenderWindow(viewportId); + } } export default RenderingEngine; diff --git a/packages/core/src/RenderingEngine/StackViewport.ts b/packages/core/src/RenderingEngine/StackViewport.ts index 06903e20c0..2467782f0f 100644 --- a/packages/core/src/RenderingEngine/StackViewport.ts +++ b/packages/core/src/RenderingEngine/StackViewport.ts @@ -97,7 +97,7 @@ import uuidv4 from '../utilities/uuidv4'; import getSpacingInNormalDirection from '../utilities/getSpacingInNormalDirection'; import getClosestImageId from '../utilities/getClosestImageId'; import { adjustInitialViewUp } from '../utilities/adjustInitialViewUp'; -import { isSequentialRenderingEngine } from './helpers/isSequentialRenderingEngine'; +import { isContextPoolRenderingEngine } from './helpers/isContextPoolRenderingEngine'; const EPSILON = 1; // Slice Thickness @@ -235,8 +235,7 @@ class StackViewport extends Viewport { }; private _configureRenderingPipeline(value?: boolean) { - const renderingEngine = this.getRenderingEngine(); - const isSequential = isSequentialRenderingEngine(renderingEngine); + const isContextPool = isContextPoolRenderingEngine(); this.useCPURendering = value ?? getShouldUseCPURendering(); @@ -253,12 +252,12 @@ class StackViewport extends Viewport { } else { if ( typeof functions.gpu === 'object' && - functions.gpu.sequential && - functions.gpu.default + functions.gpu.tiled && + functions.gpu.contextPool ) { - this[key] = isSequential - ? functions.gpu.sequential - : functions.gpu.default; + this[key] = isContextPool + ? functions.gpu.contextPool + : functions.gpu.tiled; } else { this[key] = functions.gpu; } @@ -369,13 +368,6 @@ class StackViewport extends Viewport { */ public worldToCanvas: (worldPos: Point3) => Point2; - /** - * If the renderer is CPU based, throw an error. Otherwise, returns the `vtkRenderer` responsible for rendering the `Viewport`. - * - * @returns The `vtkRenderer` for the `Viewport`. - */ - public getRenderer: () => vtkRenderer; - /** * If the renderer is CPU based, throw an error. Otherwise, return the default * actor which is the first actor in the renderer. @@ -2844,7 +2836,7 @@ class StackViewport extends Viewport { return canvasPoint; }; - private canvasToWorldGPUSequential = (canvasPos: Point2): Point3 => { + private canvasToWorldGPUContextPool = (canvasPos: Point2): Point3 => { const renderer = this.getRenderer(); // Temporary setting the clipping range to the distance and distance + 0.1 @@ -2898,7 +2890,7 @@ class StackViewport extends Viewport { return [worldCoord[0], worldCoord[1], worldCoord[2]]; }; - private canvasToWorldGPU = (canvasPos: Point2): Point3 => { + private canvasToWorldGPUTiled = (canvasPos: Point2): Point3 => { const renderer = this.getRenderer(); // Temporary setting the clipping range to the distance and distance + 0.1 @@ -2944,7 +2936,7 @@ class StackViewport extends Viewport { return [worldCoord[0], worldCoord[1], worldCoord[2]]; }; - private worldToCanvasGPUSequential = (worldPos: Point3): Point2 => { + private worldToCanvasGPUContextPool = (worldPos: Point3): Point2 => { const renderer = this.getRenderer(); // Temporary setting the clipping range to the distance and distance + 0.1 @@ -2996,7 +2988,7 @@ class StackViewport extends Viewport { return canvasCoordWithDPR; }; - private worldToCanvasGPU = (worldPos: Point3): Point2 => { + private worldToCanvasGPUTiled = (worldPos: Point3): Point2 => { const renderer = this.getRenderer(); // Temporary setting the clipping range to the distance and distance + 0.1 @@ -3039,6 +3031,29 @@ class StackViewport extends Viewport { return canvasCoordWithDPR; }; + /** + * Get the renderer for this viewport - handles ContextPoolRenderingEngine + */ + public getRendererContextPool(): vtkRenderer { + const renderingEngine = this.getRenderingEngine(); + return renderingEngine.getRenderer(this.id); + } + + /** + * Returns the `vtkRenderer` responsible for rendering the `Viewport`. + * + * @returns The `vtkRenderer` for the `Viewport`. + */ + public getRendererTiled(): vtkRenderer { + const renderingEngine = this.getRenderingEngine(); + + if (!renderingEngine || renderingEngine.hasBeenDestroyed) { + throw new Error('Rendering engine has been destroyed'); + } + + return renderingEngine.offscreenMultiRenderWindow?.getRenderer(this.id); + } + private _getVOIRangeForCurrentImage() { const { windowCenter, windowWidth, voiLUTFunction } = this.csImage; @@ -3508,20 +3523,23 @@ class StackViewport extends Viewport { canvasToWorld: { cpu: this.canvasToWorldCPU, gpu: { - sequential: this.canvasToWorldGPUSequential, - default: this.canvasToWorldGPU, + tiled: this.canvasToWorldGPUTiled, + contextPool: this.canvasToWorldGPUContextPool, }, }, worldToCanvas: { cpu: this.worldToCanvasCPU, gpu: { - sequential: this.worldToCanvasGPUSequential, - default: this.worldToCanvasGPU, + tiled: this.worldToCanvasGPUTiled, + contextPool: this.worldToCanvasGPUContextPool, }, }, getRenderer: { cpu: () => this.getCPUFallbackError('getRenderer'), - gpu: super.getRenderer, + gpu: { + tiled: this.getRendererTiled, + contextPool: this.getRendererContextPool, + }, }, getDefaultActor: { cpu: () => this.getCPUFallbackError('getDefaultActor'), diff --git a/packages/core/src/RenderingEngine/StandardRenderingEngine.ts b/packages/core/src/RenderingEngine/TiledRenderingEngine.ts similarity index 97% rename from packages/core/src/RenderingEngine/StandardRenderingEngine.ts rename to packages/core/src/RenderingEngine/TiledRenderingEngine.ts index d3bc9ee756..9a2405573b 100644 --- a/packages/core/src/RenderingEngine/StandardRenderingEngine.ts +++ b/packages/core/src/RenderingEngine/TiledRenderingEngine.ts @@ -10,6 +10,7 @@ import getOrCreateCanvas from './helpers/getOrCreateCanvas'; import type IStackViewport from '../types/IStackViewport'; import type IVolumeViewport from '../types/IVolumeViewport'; import VolumeViewport3D from './VolumeViewport3D'; +import { vtkOffscreenMultiRenderWindow } from './vtkClasses'; import type * as EventTypes from '../types/EventTypes'; import type { @@ -59,7 +60,19 @@ interface ViewportDisplayCoords { * * @public */ -class StandardRenderingEngine extends BaseRenderingEngine { +class TiledRenderingEngine extends BaseRenderingEngine { + constructor(id?: string) { + super(id); + + if (!this.useCPURendering) { + this.offscreenMultiRenderWindow = + vtkOffscreenMultiRenderWindow.newInstance(); + this.offScreenCanvasContainer = document.createElement('div'); + this.offscreenMultiRenderWindow.setContainer( + this.offScreenCanvasContainer + ); + } + } /** * Enables a viewport to be driven by the offscreen vtk.js rendering engine. * @@ -577,12 +590,12 @@ class StandardRenderingEngine extends BaseRenderingEngine { // Updating the renderer for the viewport const renderer = this.offscreenMultiRenderWindow.getRenderer(viewport.id); - renderer.setViewport([ + renderer.setViewport( sxStartDisplayCoords, syStartDisplayCoords, sxEndDisplayCoords, - syEndDisplayCoords, - ]); + syEndDisplayCoords + ); } // Returns the final xOffset @@ -632,4 +645,4 @@ class StandardRenderingEngine extends BaseRenderingEngine { } } -export default StandardRenderingEngine; +export default TiledRenderingEngine; diff --git a/packages/core/src/RenderingEngine/WebGLContextPool.ts b/packages/core/src/RenderingEngine/WebGLContextPool.ts new file mode 100644 index 0000000000..def39ddeb2 --- /dev/null +++ b/packages/core/src/RenderingEngine/WebGLContextPool.ts @@ -0,0 +1,95 @@ +import { vtkOffscreenMultiRenderWindow } from './vtkClasses'; +import type { VtkOffscreenMultiRenderWindow } from '../types'; + +/** + * Manages a pool of WebGL contexts for parallel rendering. + * Enables us distribute viewports across multiple contexts + * for improved performance. + */ +class WebGLContextPool { + private contexts: VtkOffscreenMultiRenderWindow[] = []; + private offScreenCanvasContainers: HTMLDivElement[] = []; + private viewportToContext: Map = new Map(); + + /** + * Creates a pool with the specified number of WebGL contexts + * @param count - Number of contexts to create + */ + constructor(count: number) { + for (let i = 0; i < count; i++) { + const offscreenMultiRenderWindow = + vtkOffscreenMultiRenderWindow.newInstance(); + const container = document.createElement('div'); + offscreenMultiRenderWindow.setContainer(container); + + this.contexts.push(offscreenMultiRenderWindow); + this.offScreenCanvasContainers.push(container); + } + } + + /** + * Gets the context and container at the specified index + * @param index - Context index + * @returns Context and container, or null if index is invalid + */ + getContextByIndex(index: number): { + context: VtkOffscreenMultiRenderWindow; + container: HTMLDivElement; + } | null { + if (index >= 0 && index < this.contexts.length) { + return { + context: this.contexts[index], + container: this.offScreenCanvasContainers[index], + }; + } + return null; + } + + /** + * Associates a viewport with a specific context index + * @param viewportId - ID of the viewport + * @param contextIndex - Index of the context to assign + */ + assignViewportToContext(viewportId: string, contextIndex: number): void { + this.viewportToContext.set(viewportId, contextIndex); + } + + /** + * Gets the context index assigned to a viewport + * @param viewportId - ID of the viewport + * @returns Context index, or undefined if not assigned + */ + getContextIndexForViewport(viewportId: string): number | undefined { + return this.viewportToContext.get(viewportId); + } + + /** + * Gets all contexts in the pool + * @returns Array of all contexts + */ + getAllContexts(): VtkOffscreenMultiRenderWindow[] { + return this.contexts; + } + + /** + * Gets the number of contexts in the pool + * @returns Number of contexts + */ + getContextCount(): number { + return this.contexts.length; + } + + /** + * Cleans up all contexts and releases resources + */ + destroy(): void { + this.contexts.forEach((context: VtkOffscreenMultiRenderWindow) => { + context.delete(); + }); + this.contexts = []; + this.offScreenCanvasContainers = []; + this.viewportToContext.clear(); + } +} + +export default WebGLContextPool; diff --git a/packages/core/src/RenderingEngine/helpers/isSequentialRenderingEngine.ts b/packages/core/src/RenderingEngine/helpers/isContextPoolRenderingEngine.ts similarity index 51% rename from packages/core/src/RenderingEngine/helpers/isSequentialRenderingEngine.ts rename to packages/core/src/RenderingEngine/helpers/isContextPoolRenderingEngine.ts index 04e37fc415..60b5f6946b 100644 --- a/packages/core/src/RenderingEngine/helpers/isSequentialRenderingEngine.ts +++ b/packages/core/src/RenderingEngine/helpers/isContextPoolRenderingEngine.ts @@ -1,9 +1,10 @@ import { getConfiguration } from '../../init'; import { RenderingEngineModeEnum } from '../../enums'; -export function isSequentialRenderingEngine(renderingEngine): boolean { +export function isContextPoolRenderingEngine(): boolean { const config = getConfiguration(); return ( - config?.rendering?.renderingEngineMode === RenderingEngineModeEnum.Next + config?.rendering?.renderingEngineMode === + RenderingEngineModeEnum.ContextPool ); } diff --git a/packages/core/src/RenderingEngine/index.ts b/packages/core/src/RenderingEngine/index.ts index 0b57a53e99..0f8465684e 100644 --- a/packages/core/src/RenderingEngine/index.ts +++ b/packages/core/src/RenderingEngine/index.ts @@ -1,7 +1,7 @@ import RenderingEngine from './RenderingEngine'; import BaseRenderingEngine from './BaseRenderingEngine'; -import StandardRenderingEngine from './StandardRenderingEngine'; -import SequentialRenderingEngine from './SequentialRenderingEngine'; +import TiledRenderingEngine from './TiledRenderingEngine'; +import ContextPoolRenderingEngine from './ContextPoolRenderingEngine'; import getRenderingEngine from './getRenderingEngine'; import VolumeViewport from './VolumeViewport'; import StackViewport from './StackViewport'; @@ -12,8 +12,8 @@ export { getRenderingEngine, RenderingEngine, BaseRenderingEngine, - StandardRenderingEngine, - SequentialRenderingEngine, + TiledRenderingEngine, + ContextPoolRenderingEngine, VolumeViewport, VolumeViewport3D, StackViewport, diff --git a/packages/core/src/enums/RenderingEngineModeEnum.ts b/packages/core/src/enums/RenderingEngineModeEnum.ts index c9673c99f1..b289da1c1d 100644 --- a/packages/core/src/enums/RenderingEngineModeEnum.ts +++ b/packages/core/src/enums/RenderingEngineModeEnum.ts @@ -1,6 +1,6 @@ enum RenderingEngineModeEnum { - Standard = 'standard', - Next = 'next', + Tiled = 'tiled', + ContextPool = 'contextPool', } export default RenderingEngineModeEnum; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index acebb0ca13..140a992875 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -3,8 +3,8 @@ import * as CONSTANTS from './constants'; import { Events } from './enums'; import RenderingEngine, { BaseRenderingEngine, - StandardRenderingEngine, - SequentialRenderingEngine, + TiledRenderingEngine, + ContextPoolRenderingEngine, } from './RenderingEngine'; import createVolumeActor from './RenderingEngine/helpers/createVolumeActor'; import createVolumeMapper, { @@ -125,8 +125,8 @@ export { WSIViewport, RenderingEngine, BaseRenderingEngine, - StandardRenderingEngine, - SequentialRenderingEngine, + TiledRenderingEngine, + ContextPoolRenderingEngine, ImageVolume, Surface, // Helpers diff --git a/packages/core/src/init.ts b/packages/core/src/init.ts index f91422d033..7cfa41dc91 100644 --- a/packages/core/src/init.ts +++ b/packages/core/src/init.ts @@ -17,10 +17,16 @@ const defaultConfig: Cornerstone3DConfig = { strictZSpacingForVolumeViewport: true, /** * The rendering engine mode to use. - * 'next' is the new rendering engine that uses sequential rendering and has enhanced support/performance for multi-monitor and high resolution displays. - * 'standard' is the old rendering engine implementation, which we will eventually move away from. + * 'contextPool' is the a rendering engine that uses sequential rendering, pararllization and has enhanced support/performance for multi-monitor and high resolution displays. + * 'tiled' is a rendering engine that uses tiled rendering. */ - renderingEngineMode: RenderingEngineModeEnum.Next, + renderingEngineMode: RenderingEngineModeEnum.ContextPool, + + /** + * The number of WebGL contexts to create. This is used for parallel rendering. + * The default value is 7, which is suitable for mobile/desktop. + */ + webGlContextCount: 7, }, /** @@ -105,6 +111,11 @@ function init(configuration = config): boolean { // merge configs config = deepMerge(defaultConfig, configuration); + // mobile safe + if (config.isMobile) { + config.rendering.webGlContextCount = 1; + } + if (isIOS()) { if (configuration.rendering?.preferSizeOverAccuracy) { config.rendering.preferSizeOverAccuracy = true; diff --git a/packages/core/src/types/Cornerstone3DConfig.ts b/packages/core/src/types/Cornerstone3DConfig.ts index 977d88a1c5..24be843557 100644 --- a/packages/core/src/types/Cornerstone3DConfig.ts +++ b/packages/core/src/types/Cornerstone3DConfig.ts @@ -35,10 +35,16 @@ interface Cornerstone3DConfig { /** * The rendering engine mode to use. - * 'next' is the new rendering engine that uses sequential rendering and has enhanced support/performance for multi-monitor and high resolution displays. - * 'standard' is the old rendering engine implementation, which we will eventually move away from. + * 'contextPool' is the a rendering engine that uses sequential rendering, pararllization and has enhanced support/performance for multi-monitor and high resolution displays. + * 'tiled' is a rendering engine that uses tiled rendering. */ renderingEngineMode?: RenderingEngineModeType; + + /** + * The number of WebGL contexts to create. This is used for parallel rendering. + * The default value is 7, which is suitable for mobile/desktop. + */ + webGlContextCount?: number; }; /** diff --git a/packages/core/src/types/RenderingEngineMode.ts b/packages/core/src/types/RenderingEngineMode.ts index fa878e7700..0239161d01 100644 --- a/packages/core/src/types/RenderingEngineMode.ts +++ b/packages/core/src/types/RenderingEngineMode.ts @@ -1,3 +1,3 @@ -type RenderingEngineModeType = 'standard' | 'next'; +type RenderingEngineModeType = 'tiled' | 'contextPool'; export type { RenderingEngineModeType }; diff --git a/packages/core/src/types/VtkOffscreenMultiRenderWindow.ts b/packages/core/src/types/VtkOffscreenMultiRenderWindow.ts new file mode 100644 index 0000000000..10bdabdae9 --- /dev/null +++ b/packages/core/src/types/VtkOffscreenMultiRenderWindow.ts @@ -0,0 +1,45 @@ +import type { vtkObject } from '@kitware/vtk.js/interfaces'; +import type vtkStreamingOpenGLRenderWindow from '../RenderingEngine/vtkClasses/vtkStreamingOpenGLRenderWindow'; +import type vtkRenderer from '@kitware/vtk.js/Rendering/Core/Renderer'; +import type vtkRenderWindow from '@kitware/vtk.js/Rendering/Core/RenderWindow'; +import type vtkRenderWindowInteractor from '@kitware/vtk.js/Rendering/Core/RenderWindowInteractor'; + +import '@kitware/vtk.js/Common/Core/Points'; +import '@kitware/vtk.js/Common/Core/DataArray'; +import '@kitware/vtk.js/Common/DataModel/PolyData'; +import '@kitware/vtk.js/Rendering/Core/Actor'; +import '@kitware/vtk.js/Rendering/Core/Mapper'; + +type Viewport = [number, number, number, number]; + +interface RendererConfig { + id: string; + viewport: Viewport; + background?: [number, number, number]; +} + +export interface VtkOffscreenMultiRenderWindow extends vtkObject { + renderWindow: vtkRenderWindow; + getRenderWindow: () => vtkRenderWindow; + + openGLRenderWindow: ReturnType< + typeof vtkStreamingOpenGLRenderWindow.newInstance + >; + getOpenGLRenderWindow: () => ReturnType< + typeof vtkStreamingOpenGLRenderWindow.newInstance + >; + + interactor: vtkRenderWindowInteractor; + getInteractor: () => vtkRenderWindowInteractor; + + container: HTMLDivElement | null; + getContainer: () => HTMLDivElement | null; + + addRenderer: (config: RendererConfig) => void; + removeRenderer: (id: string) => void; + getRenderer: (id: string) => vtkRenderer; + getRenderers: () => Array<{ id: string; renderer: vtkRenderer }>; + resize: () => void; + setContainer: (el: HTMLDivElement) => void; + destroy: () => void; +} diff --git a/packages/core/src/types/index.ts b/packages/core/src/types/index.ts index 9fe2f445ed..4cef5b476b 100644 --- a/packages/core/src/types/index.ts +++ b/packages/core/src/types/index.ts @@ -162,6 +162,7 @@ import type JumpToSliceOptions from './JumpToSliceOptions'; import type GeometryLoaderFn from './GeometryLoaderFn'; import type { RenderingEngineModeType } from './RenderingEngineMode'; +import type { VtkOffscreenMultiRenderWindow } from './VtkOffscreenMultiRenderWindow'; export type { // config @@ -316,4 +317,5 @@ export type { VoxelManager, RLEVoxelMap, RenderingEngineModeType, + VtkOffscreenMultiRenderWindow, }; diff --git a/packages/core/src/utilities/renderToCanvasGPU.ts b/packages/core/src/utilities/renderToCanvasGPU.ts index 654a5eb6d0..89190fd1de 100644 --- a/packages/core/src/utilities/renderToCanvasGPU.ts +++ b/packages/core/src/utilities/renderToCanvasGPU.ts @@ -14,7 +14,8 @@ import type { Point3, } from '../types'; import { getRenderingEngine } from '../RenderingEngine/getRenderingEngine'; -import RenderingEngine from '../RenderingEngine'; +import type RenderingEngine from '../RenderingEngine'; +import TiledRenderingEngine from '../RenderingEngine/TiledRenderingEngine'; import isPTPrescaledWithSUV from './isPTPrescaledWithSUV'; import type { CanvasLoadPosition } from './loadImageToCanvas'; @@ -88,7 +89,7 @@ export default function renderToCanvasGPU( const temporaryCanvas = getOrCreateCanvas(element); const renderingEngine = (getRenderingEngine(renderingEngineId) as RenderingEngine) || - new RenderingEngine(renderingEngineId); + new TiledRenderingEngine(renderingEngineId); let viewport = renderingEngine.getViewport(viewportId); diff --git a/packages/docs/docs/concepts/cornerstone-core/renderingEngine.md b/packages/docs/docs/concepts/cornerstone-core/renderingEngine.md index 6fb38f46ed..3dfb346e66 100644 --- a/packages/docs/docs/concepts/cornerstone-core/renderingEngine.md +++ b/packages/docs/docs/concepts/cornerstone-core/renderingEngine.md @@ -30,6 +30,83 @@ canvas get updated, and at render time, we copy from offscreen to onscreen for e For instance for PET-CT fusion which has 3x3 layout which includes CT (Axial, Sagittal, Coronal), PET (Axial, Sagittal, Coronal) and Fusion (Axial, Sagittal, Coronal), we create two volume mappers for CT and PET individually, and for the Fusion viewports we re-use both created textures instead of re-creating a new one. +## Rendering Engine Implementations + +Cornerstone3D provides two rendering engine implementations to handle different use cases and overcome technical limitations: + +### TiledRenderingEngine + +The `TiledRenderingEngine` is the original implementation that uses a single, large offscreen canvas for all viewports. This approach: + +- Creates one massive offscreen canvas that grows horizontally as viewports are added +- Renders all viewports to specific coordinates on this single offscreen canvas +- Copies pixel data from the offscreen canvas to individual onscreen viewports + +**Limitations of TiledRenderingEngine:** + +- **Canvas Size Limits**: Browsers impose maximum canvas dimensions (e.g., 16,384px in Chrome). When the combined width of all viewports exceeds this limit, the offscreen canvas is silently cropped, causing severe visual artifacts, misaligned viewports, and blank viewports +- **Performance Degradation**: As the offscreen canvas approaches size limits, performance degrades significantly, especially on high-resolution displays or layouts with many viewports +- **Multi-Monitor Issues**: Practically impossible to use across multiple high-resolution monitors due to canvas size limitations +- **Memory Consumption**: Allocates a huge, memory-intensive offscreen canvas regardless of actual viewport usage + +**Advantages of TiledRenderingEngine:** + +- **Simplicity**: Straightforward implementation that works well for small numbers of viewports +- **Track Record**: Proven reliability for 5 years, and for most basic use cases, it performs adequately + +### ContextPoolRenderingEngine (SequentialRenderingEngine) + +The `ContextPoolRenderingEngine` (internally called `SequentialRenderingEngine`) fundamentally solves the limitations of the tiled approach by using a different rendering strategy: + +- Renders each viewport individually to a viewport-sized offscreen canvas +- Copies the result to the corresponding onscreen canvas +- Proceeds sequentially to the next viewport, reusing the same offscreen canvas +- Utilizes WebGL context pooling to render in batches (e.g., batches of 8 for 8 WebGL contexts) + +**Advantages of ContextPoolRenderingEngine:** + +- **No Canvas Size Limits**: The browser's maximum canvas size now applies to individual viewports, not the combined width +- **Improved Performance**: Consistent performance regardless of the number of viewports or display resolution +- **Better Memory Usage**: Avoids allocating massive offscreen canvases +- **Multi-Monitor Support**: Enables smooth performance across multiple high-resolution monitors +- **Enhanced Stability**: Reduces WebGL context loss associated with huge canvas surfaces + +### Configuring the Rendering Engine + +The `ContextPoolRenderingEngine` is now the default in Cornerstone3D. If you need to use the legacy `TiledRenderingEngine`, you can configure it during initialization: + +```js +import { init } from '@cornerstonejs/core'; + +// To use the legacy TiledRenderingEngine +init({ + rendering: { + renderingEngineMode: 'standard', + }, +}); + +// The ContextPoolRenderingEngine is used by default, or you can explicitly set it +init({ + rendering: { + renderingEngineMode: 'next', + }, +}); +``` + +For `ContextPoolRenderingEngine` you can also configure the number of WebGL contexts to use for batch rendering: + +```js +import { init } from '@cornerstonejs/core'; + +// To use the ContextPoolRenderingEngine with a specific number of WebGL contexts +init({ + rendering: { + renderingEngineMode: 'next', + webGLContextCount: 7, // Default is 7, can be adjusted based on your needs + }, +}); +``` + ## General usage After creating a renderingEngine, we can assign viewports to it for rendering. There are two main approach for creating `Stack` or `Volume` viewports which we will discuss now. diff --git a/packages/tools/examples/contourRendering/index.ts b/packages/tools/examples/contourRendering/index.ts index e3605dedef..44495d706e 100644 --- a/packages/tools/examples/contourRendering/index.ts +++ b/packages/tools/examples/contourRendering/index.ts @@ -70,7 +70,10 @@ content.append(instructions); */ async function run() { // Init Cornerstone and related libraries - await initDemo(); + const config = (window as any).IS_TILED + ? { core: { renderingEngineMode: 'tiled' } } + : {}; + await initDemo(config); // Add tools to Cornerstone3D cornerstoneTools.addTool(PanTool); diff --git a/packages/tools/examples/labelmapRendering/index.ts b/packages/tools/examples/labelmapRendering/index.ts index 584f45b16b..8e87bc70bf 100644 --- a/packages/tools/examples/labelmapRendering/index.ts +++ b/packages/tools/examples/labelmapRendering/index.ts @@ -71,7 +71,10 @@ content.appendChild(viewportGrid); */ async function run() { // Init Cornerstone and related libraries - await initDemo(); + const config = (window as any).IS_TILED + ? { core: { renderingEngineMode: 'tiled' } } + : {}; + await initDemo(config); // Define tool groups to add the segmentation display tool to const toolGroup = ToolGroupManager.createToolGroup(toolGroupId); diff --git a/packages/tools/examples/stackAnnotationTools/index.ts b/packages/tools/examples/stackAnnotationTools/index.ts index 977f1e75d5..37b90b4b09 100644 --- a/packages/tools/examples/stackAnnotationTools/index.ts +++ b/packages/tools/examples/stackAnnotationTools/index.ts @@ -211,7 +211,10 @@ addButtonToToolbar({ */ async function run() { // Init Cornerstone and related libraries - await initDemo(); + const config = (window as any).IS_TILED + ? { core: { renderingEngineMode: 'tiled' } } + : {}; + await initDemo(config); // Add tools to Cornerstone3D cornerstoneTools.addTool(LengthTool); diff --git a/packages/tools/examples/volumeAnnotationTools/index.ts b/packages/tools/examples/volumeAnnotationTools/index.ts index ecac301618..cbbeb3ee64 100644 --- a/packages/tools/examples/volumeAnnotationTools/index.ts +++ b/packages/tools/examples/volumeAnnotationTools/index.ts @@ -79,7 +79,10 @@ content.append(instructions); */ async function run() { // Init Cornerstone and related libraries - await initDemo(); + const config = (window as any).IS_TILED + ? { core: { renderingEngineMode: 'tiled' } } + : {}; + await initDemo(config); const toolGroupId = 'STACK_TOOL_GROUP_ID'; diff --git a/packages/tools/src/tools/OrientationMarkerTool.ts b/packages/tools/src/tools/OrientationMarkerTool.ts index af8a5c902f..a270cd4b04 100644 --- a/packages/tools/src/tools/OrientationMarkerTool.ts +++ b/packages/tools/src/tools/OrientationMarkerTool.ts @@ -257,7 +257,8 @@ class OrientationMarkerTool extends BaseTool { const renderWindow = viewport .getRenderingEngine() - .offscreenMultiRenderWindow.getRenderWindow(); + .getOffscreenMultiRenderWindow(viewport.id) + .getRenderWindow(); renderWindow.render(); viewport.getRenderingEngine().render(); @@ -314,7 +315,8 @@ class OrientationMarkerTool extends BaseTool { const renderer = viewport.getRenderer(); const renderWindow = viewport .getRenderingEngine() - .offscreenMultiRenderWindow.getRenderWindow(); + .getOffscreenMultiRenderWindow(viewportId) + .getRenderWindow(); const { enabled, diff --git a/tests/sequentialRenderingEngine.spec.ts b/tests/contextPoolRenderingEngine.spec.ts similarity index 59% rename from tests/sequentialRenderingEngine.spec.ts rename to tests/contextPoolRenderingEngine.spec.ts index ad0617cb6c..39dee3e4ef 100644 --- a/tests/sequentialRenderingEngine.spec.ts +++ b/tests/contextPoolRenderingEngine.spec.ts @@ -7,18 +7,16 @@ import { test.beforeEach(async ({ page, context }) => { await context.addInitScript(() => (window.IS_PLAYWRIGHT = true)); - await visitExample(page, 'sequentialRenderingEngine'); + await visitExample(page, 'contextPoolRenderingEngine'); }); -test.describe('Sequential Rendering Engine', async () => { - test('should display sequential rendering engine example', async ({ - page, - }) => { +test.describe('Context Pool Rendering Engine', async () => { + test('should display context pool rendering engine example', async ({ page }) => { const locator = page.locator('#viewportContainer'); await checkForScreenshot( page, locator, - screenShotPaths.sequentialRenderingEngine.viewport + screenShotPaths.contextPoolRenderingEngine.viewport ); }); }); diff --git a/tests/contourRenderingTiled.spec.ts b/tests/contourRenderingTiled.spec.ts new file mode 100644 index 0000000000..48e137c489 --- /dev/null +++ b/tests/contourRenderingTiled.spec.ts @@ -0,0 +1,25 @@ +import { test } from 'playwright-test-coverage'; +import { + checkForScreenshot, + visitExample, + screenShotPaths, +} from './utils/index'; + +test.beforeEach(async ({ page, context }) => { + await visitExample(page, 'contourRendering'); + await context.addInitScript(() => (window.IS_TILED = true)); +}); + +test.describe('Contour Rendering', async () => { + test('should add a contour as a segmentation to a volume viewport', async ({ + page, + }) => { + const canvas = await page.locator('canvas'); + + await checkForScreenshot( + page, + canvas, + screenShotPaths.contourRenderingTiled.viewport + ); + }); +}); diff --git a/tests/labelmapRenderingTiled.spec.ts b/tests/labelmapRenderingTiled.spec.ts new file mode 100644 index 0000000000..76a3ad4d04 --- /dev/null +++ b/tests/labelmapRenderingTiled.spec.ts @@ -0,0 +1,43 @@ +import { test } from 'playwright-test-coverage'; +import { + checkForScreenshot, + visitExample, + screenShotPaths, + reduceViewportsSize, + attemptAction, +} from './utils/index'; + +test.beforeEach(async ({ page, context }) => { + await visitExample(page, 'labelmapRendering', 0, false, false); + await context.addInitScript(() => (window.IS_TILED = true)); +}); + +test.describe('Labelmap Rendering', async () => { + test('should render the labelmap in axial/coronal/sagittal orientations', async ({ + page, + }) => { + await attemptAction(() => reduceViewportsSize(page), 1000, 10); + + const axial = await page.locator('canvas').nth(0); + const coronal = await page.locator('canvas').nth(1); + const sagittal = await page.locator('canvas').nth(2); + + await checkForScreenshot( + page, + axial, + screenShotPaths.labelmapRenderingTiled.axial + ); + + await checkForScreenshot( + page, + coronal, + screenShotPaths.labelmapRenderingTiled.coronal + ); + + await checkForScreenshot( + page, + sagittal, + screenShotPaths.labelmapRenderingTiled.sagittal + ); + }); +}); diff --git a/tests/screenshots/Mobile-Android/sequentialRenderingEngine.spec.ts/viewport.png b/tests/screenshots/Mobile-Android/contextPoolRenderingEngine.spec.ts/viewport.png similarity index 100% rename from tests/screenshots/Mobile-Android/sequentialRenderingEngine.spec.ts/viewport.png rename to tests/screenshots/Mobile-Android/contextPoolRenderingEngine.spec.ts/viewport.png diff --git a/tests/screenshots/Mobile-Android/contourRenderingTiled.spec.ts/viewport.png b/tests/screenshots/Mobile-Android/contourRenderingTiled.spec.ts/viewport.png new file mode 100644 index 0000000000..8a8777c82e Binary files /dev/null and b/tests/screenshots/Mobile-Android/contourRenderingTiled.spec.ts/viewport.png differ diff --git a/tests/screenshots/Mobile-Android/labelmapRenderingTiled.spec.ts/axial.png b/tests/screenshots/Mobile-Android/labelmapRenderingTiled.spec.ts/axial.png new file mode 100644 index 0000000000..7cd77bc243 Binary files /dev/null and b/tests/screenshots/Mobile-Android/labelmapRenderingTiled.spec.ts/axial.png differ diff --git a/tests/screenshots/Mobile-Android/labelmapRenderingTiled.spec.ts/coronal.png b/tests/screenshots/Mobile-Android/labelmapRenderingTiled.spec.ts/coronal.png new file mode 100644 index 0000000000..fed0a3a386 Binary files /dev/null and b/tests/screenshots/Mobile-Android/labelmapRenderingTiled.spec.ts/coronal.png differ diff --git a/tests/screenshots/Mobile-Android/labelmapRenderingTiled.spec.ts/sagittal.png b/tests/screenshots/Mobile-Android/labelmapRenderingTiled.spec.ts/sagittal.png new file mode 100644 index 0000000000..5cbbaf4bb3 Binary files /dev/null and b/tests/screenshots/Mobile-Android/labelmapRenderingTiled.spec.ts/sagittal.png differ diff --git a/tests/screenshots/Mobile-Android/stackAnnotation.spec.ts/lengthTool.png b/tests/screenshots/Mobile-Android/stackAnnotation.spec.ts/lengthTool.png new file mode 100644 index 0000000000..c38551a813 Binary files /dev/null and b/tests/screenshots/Mobile-Android/stackAnnotation.spec.ts/lengthTool.png differ diff --git a/tests/screenshots/Mobile-Android/stackAnnotationTiled.spec.ts/lengthTool.png b/tests/screenshots/Mobile-Android/stackAnnotationTiled.spec.ts/lengthTool.png new file mode 100644 index 0000000000..c38551a813 Binary files /dev/null and b/tests/screenshots/Mobile-Android/stackAnnotationTiled.spec.ts/lengthTool.png differ diff --git a/tests/screenshots/Mobile-Android/stackBasicTiled.spec.ts/viewport.png b/tests/screenshots/Mobile-Android/stackBasicTiled.spec.ts/viewport.png new file mode 100644 index 0000000000..a4272ac6b1 Binary files /dev/null and b/tests/screenshots/Mobile-Android/stackBasicTiled.spec.ts/viewport.png differ diff --git a/tests/screenshots/Mobile-Android/volumeAnnotation.spec.ts/lengthTool.png b/tests/screenshots/Mobile-Android/volumeAnnotation.spec.ts/lengthTool.png new file mode 100644 index 0000000000..7495db8dcb Binary files /dev/null and b/tests/screenshots/Mobile-Android/volumeAnnotation.spec.ts/lengthTool.png differ diff --git a/tests/screenshots/Mobile-Android/volumeAnnotationTiled.spec.ts/lengthTool.png b/tests/screenshots/Mobile-Android/volumeAnnotationTiled.spec.ts/lengthTool.png new file mode 100644 index 0000000000..7495db8dcb Binary files /dev/null and b/tests/screenshots/Mobile-Android/volumeAnnotationTiled.spec.ts/lengthTool.png differ diff --git a/tests/screenshots/Mobile-Android/volumeBasicTiled.spec.ts/viewport.png b/tests/screenshots/Mobile-Android/volumeBasicTiled.spec.ts/viewport.png new file mode 100644 index 0000000000..c150d675ee Binary files /dev/null and b/tests/screenshots/Mobile-Android/volumeBasicTiled.spec.ts/viewport.png differ diff --git a/tests/screenshots/Mobile-Safari/sequentialRenderingEngine.spec.ts/viewport.png b/tests/screenshots/Mobile-Safari/contextPoolRenderingEngine.spec.ts/viewport.png similarity index 100% rename from tests/screenshots/Mobile-Safari/sequentialRenderingEngine.spec.ts/viewport.png rename to tests/screenshots/Mobile-Safari/contextPoolRenderingEngine.spec.ts/viewport.png diff --git a/tests/screenshots/Mobile-Safari/contourRenderingTiled.spec.ts/viewport.png b/tests/screenshots/Mobile-Safari/contourRenderingTiled.spec.ts/viewport.png new file mode 100644 index 0000000000..1cdc2d125f Binary files /dev/null and b/tests/screenshots/Mobile-Safari/contourRenderingTiled.spec.ts/viewport.png differ diff --git a/tests/screenshots/Mobile-Safari/labelmapRenderingTiled.spec.ts/axial.png b/tests/screenshots/Mobile-Safari/labelmapRenderingTiled.spec.ts/axial.png new file mode 100644 index 0000000000..04b0969fdc Binary files /dev/null and b/tests/screenshots/Mobile-Safari/labelmapRenderingTiled.spec.ts/axial.png differ diff --git a/tests/screenshots/Mobile-Safari/labelmapRenderingTiled.spec.ts/coronal.png b/tests/screenshots/Mobile-Safari/labelmapRenderingTiled.spec.ts/coronal.png new file mode 100644 index 0000000000..15d9b65e41 Binary files /dev/null and b/tests/screenshots/Mobile-Safari/labelmapRenderingTiled.spec.ts/coronal.png differ diff --git a/tests/screenshots/Mobile-Safari/labelmapRenderingTiled.spec.ts/sagittal.png b/tests/screenshots/Mobile-Safari/labelmapRenderingTiled.spec.ts/sagittal.png new file mode 100644 index 0000000000..843c7666cb Binary files /dev/null and b/tests/screenshots/Mobile-Safari/labelmapRenderingTiled.spec.ts/sagittal.png differ diff --git a/tests/screenshots/Mobile-Safari/stackAnnotation.spec.ts/lengthTool.png b/tests/screenshots/Mobile-Safari/stackAnnotation.spec.ts/lengthTool.png new file mode 100644 index 0000000000..72e16b4413 Binary files /dev/null and b/tests/screenshots/Mobile-Safari/stackAnnotation.spec.ts/lengthTool.png differ diff --git a/tests/screenshots/Mobile-Safari/stackAnnotationTiled.spec.ts/lengthTool.png b/tests/screenshots/Mobile-Safari/stackAnnotationTiled.spec.ts/lengthTool.png new file mode 100644 index 0000000000..72e16b4413 Binary files /dev/null and b/tests/screenshots/Mobile-Safari/stackAnnotationTiled.spec.ts/lengthTool.png differ diff --git a/tests/screenshots/Mobile-Safari/stackBasicTiled.spec.ts/viewport.png b/tests/screenshots/Mobile-Safari/stackBasicTiled.spec.ts/viewport.png new file mode 100644 index 0000000000..15c15c84e6 Binary files /dev/null and b/tests/screenshots/Mobile-Safari/stackBasicTiled.spec.ts/viewport.png differ diff --git a/tests/screenshots/Mobile-Safari/volumeAnnotation.spec.ts/lengthTool.png b/tests/screenshots/Mobile-Safari/volumeAnnotation.spec.ts/lengthTool.png new file mode 100644 index 0000000000..79a90bd44f Binary files /dev/null and b/tests/screenshots/Mobile-Safari/volumeAnnotation.spec.ts/lengthTool.png differ diff --git a/tests/screenshots/Mobile-Safari/volumeAnnotationTiled.spec.ts/lengthTool.png b/tests/screenshots/Mobile-Safari/volumeAnnotationTiled.spec.ts/lengthTool.png new file mode 100644 index 0000000000..79a90bd44f Binary files /dev/null and b/tests/screenshots/Mobile-Safari/volumeAnnotationTiled.spec.ts/lengthTool.png differ diff --git a/tests/screenshots/Mobile-Safari/volumeBasicTiled.spec.ts/viewport.png b/tests/screenshots/Mobile-Safari/volumeBasicTiled.spec.ts/viewport.png new file mode 100644 index 0000000000..ca77e01f76 Binary files /dev/null and b/tests/screenshots/Mobile-Safari/volumeBasicTiled.spec.ts/viewport.png differ diff --git a/tests/screenshots/chromium/sequentialRenderingEngine.spec.ts/viewport.png b/tests/screenshots/chromium/contextPoolRenderingEngine.spec.ts/viewport.png similarity index 100% rename from tests/screenshots/chromium/sequentialRenderingEngine.spec.ts/viewport.png rename to tests/screenshots/chromium/contextPoolRenderingEngine.spec.ts/viewport.png diff --git a/tests/screenshots/chromium/contourRenderingTiled.spec.ts/viewport.png b/tests/screenshots/chromium/contourRenderingTiled.spec.ts/viewport.png new file mode 100644 index 0000000000..710a7e1440 Binary files /dev/null and b/tests/screenshots/chromium/contourRenderingTiled.spec.ts/viewport.png differ diff --git a/tests/screenshots/chromium/labelmapRenderingTiled.spec.ts/axial.png b/tests/screenshots/chromium/labelmapRenderingTiled.spec.ts/axial.png new file mode 100644 index 0000000000..c955ca6785 Binary files /dev/null and b/tests/screenshots/chromium/labelmapRenderingTiled.spec.ts/axial.png differ diff --git a/tests/screenshots/chromium/labelmapRenderingTiled.spec.ts/coronal.png b/tests/screenshots/chromium/labelmapRenderingTiled.spec.ts/coronal.png new file mode 100644 index 0000000000..b15c170980 Binary files /dev/null and b/tests/screenshots/chromium/labelmapRenderingTiled.spec.ts/coronal.png differ diff --git a/tests/screenshots/chromium/labelmapRenderingTiled.spec.ts/sagittal.png b/tests/screenshots/chromium/labelmapRenderingTiled.spec.ts/sagittal.png new file mode 100644 index 0000000000..091b25345b Binary files /dev/null and b/tests/screenshots/chromium/labelmapRenderingTiled.spec.ts/sagittal.png differ diff --git a/tests/screenshots/chromium/stackAnnotation.spec.ts/lengthTool.png b/tests/screenshots/chromium/stackAnnotation.spec.ts/lengthTool.png new file mode 100644 index 0000000000..c38551a813 Binary files /dev/null and b/tests/screenshots/chromium/stackAnnotation.spec.ts/lengthTool.png differ diff --git a/tests/screenshots/chromium/stackAnnotationTiled.spec.ts/lengthTool.png b/tests/screenshots/chromium/stackAnnotationTiled.spec.ts/lengthTool.png new file mode 100644 index 0000000000..c38551a813 Binary files /dev/null and b/tests/screenshots/chromium/stackAnnotationTiled.spec.ts/lengthTool.png differ diff --git a/tests/screenshots/chromium/stackBasicTiled.spec.ts/viewport.png b/tests/screenshots/chromium/stackBasicTiled.spec.ts/viewport.png new file mode 100644 index 0000000000..a4272ac6b1 Binary files /dev/null and b/tests/screenshots/chromium/stackBasicTiled.spec.ts/viewport.png differ diff --git a/tests/screenshots/chromium/volumeAnnotation.spec.ts/lengthTool.png b/tests/screenshots/chromium/volumeAnnotation.spec.ts/lengthTool.png new file mode 100644 index 0000000000..ac2368ceee Binary files /dev/null and b/tests/screenshots/chromium/volumeAnnotation.spec.ts/lengthTool.png differ diff --git a/tests/screenshots/chromium/volumeAnnotationTiled.spec.ts/lengthTool.png b/tests/screenshots/chromium/volumeAnnotationTiled.spec.ts/lengthTool.png new file mode 100644 index 0000000000..ac2368ceee Binary files /dev/null and b/tests/screenshots/chromium/volumeAnnotationTiled.spec.ts/lengthTool.png differ diff --git a/tests/screenshots/chromium/volumeBasicTiled.spec.ts/viewport.png b/tests/screenshots/chromium/volumeBasicTiled.spec.ts/viewport.png new file mode 100644 index 0000000000..c150d675ee Binary files /dev/null and b/tests/screenshots/chromium/volumeBasicTiled.spec.ts/viewport.png differ diff --git a/tests/screenshots/webkit/sequentialRenderingEngine.spec.ts/viewport.png b/tests/screenshots/webkit/contextPoolRenderingEngine.spec.ts/viewport.png similarity index 100% rename from tests/screenshots/webkit/sequentialRenderingEngine.spec.ts/viewport.png rename to tests/screenshots/webkit/contextPoolRenderingEngine.spec.ts/viewport.png diff --git a/tests/screenshots/webkit/contourRenderingTiled.spec.ts/viewport.png b/tests/screenshots/webkit/contourRenderingTiled.spec.ts/viewport.png new file mode 100644 index 0000000000..4ceb9d5511 Binary files /dev/null and b/tests/screenshots/webkit/contourRenderingTiled.spec.ts/viewport.png differ diff --git a/tests/screenshots/webkit/labelmapRenderingTiled.spec.ts/axial.png b/tests/screenshots/webkit/labelmapRenderingTiled.spec.ts/axial.png new file mode 100644 index 0000000000..b146955322 Binary files /dev/null and b/tests/screenshots/webkit/labelmapRenderingTiled.spec.ts/axial.png differ diff --git a/tests/screenshots/webkit/labelmapRenderingTiled.spec.ts/coronal.png b/tests/screenshots/webkit/labelmapRenderingTiled.spec.ts/coronal.png new file mode 100644 index 0000000000..a6e33ccb8e Binary files /dev/null and b/tests/screenshots/webkit/labelmapRenderingTiled.spec.ts/coronal.png differ diff --git a/tests/screenshots/webkit/labelmapRenderingTiled.spec.ts/sagittal.png b/tests/screenshots/webkit/labelmapRenderingTiled.spec.ts/sagittal.png new file mode 100644 index 0000000000..20bd0cf8b2 Binary files /dev/null and b/tests/screenshots/webkit/labelmapRenderingTiled.spec.ts/sagittal.png differ diff --git a/tests/screenshots/webkit/stackAnnotation.spec.ts/lengthTool.png b/tests/screenshots/webkit/stackAnnotation.spec.ts/lengthTool.png new file mode 100644 index 0000000000..72e16b4413 Binary files /dev/null and b/tests/screenshots/webkit/stackAnnotation.spec.ts/lengthTool.png differ diff --git a/tests/screenshots/webkit/stackAnnotationTiled.spec.ts/lengthTool.png b/tests/screenshots/webkit/stackAnnotationTiled.spec.ts/lengthTool.png new file mode 100644 index 0000000000..72e16b4413 Binary files /dev/null and b/tests/screenshots/webkit/stackAnnotationTiled.spec.ts/lengthTool.png differ diff --git a/tests/screenshots/webkit/stackBasicTiled.spec.ts/viewport.png b/tests/screenshots/webkit/stackBasicTiled.spec.ts/viewport.png new file mode 100644 index 0000000000..15c15c84e6 Binary files /dev/null and b/tests/screenshots/webkit/stackBasicTiled.spec.ts/viewport.png differ diff --git a/tests/screenshots/webkit/volumeAnnotation.spec.ts/lengthTool.png b/tests/screenshots/webkit/volumeAnnotation.spec.ts/lengthTool.png new file mode 100644 index 0000000000..e6fa107912 Binary files /dev/null and b/tests/screenshots/webkit/volumeAnnotation.spec.ts/lengthTool.png differ diff --git a/tests/screenshots/webkit/volumeAnnotationTiled.spec.ts/lengthTool.png b/tests/screenshots/webkit/volumeAnnotationTiled.spec.ts/lengthTool.png new file mode 100644 index 0000000000..e6fa107912 Binary files /dev/null and b/tests/screenshots/webkit/volumeAnnotationTiled.spec.ts/lengthTool.png differ diff --git a/tests/screenshots/webkit/volumeBasicTiled.spec.ts/viewport.png b/tests/screenshots/webkit/volumeBasicTiled.spec.ts/viewport.png new file mode 100644 index 0000000000..ca77e01f76 Binary files /dev/null and b/tests/screenshots/webkit/volumeBasicTiled.spec.ts/viewport.png differ diff --git a/tests/stackAnnotation.spec.ts b/tests/stackAnnotation.spec.ts new file mode 100644 index 0000000000..fce9bcaeb2 --- /dev/null +++ b/tests/stackAnnotation.spec.ts @@ -0,0 +1,23 @@ +import { test } from 'playwright-test-coverage'; +import { + visitExample, + checkForScreenshot, + screenShotPaths, + simulateDrag, +} from './utils/index'; + +test.beforeEach(async ({ page }) => { + await visitExample(page, 'stackAnnotationTools'); +}); + +test.describe('Stack Annotation Tools', async () => { + test('should draw a length measurement on the viewport', async ({ page }) => { + const locator = page.locator('.cornerstone-canvas').nth(0); + await simulateDrag(page, locator); + await checkForScreenshot( + page, + locator, + screenShotPaths.stackAnnotation.lengthTool + ); + }); +}); diff --git a/tests/stackAnnotationTiled.spec.ts b/tests/stackAnnotationTiled.spec.ts new file mode 100644 index 0000000000..e4534667bd --- /dev/null +++ b/tests/stackAnnotationTiled.spec.ts @@ -0,0 +1,24 @@ +import { test } from 'playwright-test-coverage'; +import { + visitExample, + checkForScreenshot, + screenShotPaths, + simulateDrag, +} from './utils/index'; + +test.beforeEach(async ({ page, context }) => { + await context.addInitScript(() => (window.IS_TILED = true)); + await visitExample(page, 'stackAnnotationTools'); +}); + +test.describe('Stack Annotation Tools - Tiled', async () => { + test('should draw a length measurement on the viewport', async ({ page }) => { + const locator = page.locator('.cornerstone-canvas').nth(0); + await simulateDrag(page, locator); + await checkForScreenshot( + page, + locator, + screenShotPaths.stackAnnotationTiled.lengthTool + ); + }); +}); diff --git a/tests/stackBasicTiled.spec.ts b/tests/stackBasicTiled.spec.ts new file mode 100644 index 0000000000..ae04aaed4f --- /dev/null +++ b/tests/stackBasicTiled.spec.ts @@ -0,0 +1,24 @@ +import { test } from 'playwright-test-coverage'; +import { + visitExample, + checkForScreenshot, + screenShotPaths, +} from './utils/index'; + +test.beforeEach(async ({ page, context }) => { + await visitExample(page, 'stackBasic'); + await context.addInitScript(() => (window.IS_TILED = true)); +}); + +test.describe('Basic Stack', async () => { + test('should display a single DICOM image in a Stack viewport', async ({ + page, + }) => { + const locator = page.locator('.cornerstone-canvas'); + await checkForScreenshot( + page, + locator, + screenShotPaths.stackBasicTiled.viewport + ); + }); +}); diff --git a/tests/utils/screenShotPaths.ts b/tests/utils/screenShotPaths.ts index f588a7a327..ae04c76e5c 100644 --- a/tests/utils/screenShotPaths.ts +++ b/tests/utils/screenShotPaths.ts @@ -14,6 +14,9 @@ const screenShotPaths = { stackBasic: { viewport: 'viewport.png', }, + stackBasicTiled: { + viewport: 'viewport.png', + }, stackToVolumeFusion: { viewport: 'viewport.png', }, @@ -50,6 +53,9 @@ const screenShotPaths = { volumeBasic: { viewport: 'viewport.png', }, + volumeBasicTiled: { + viewport: 'viewport.png', + }, ultrasoundColors: { slice1: 'slice1.png', slice2: 'slice2.png', @@ -116,9 +122,17 @@ const screenShotPaths = { coronal: 'coronal.png', sagittal: 'sagittal.png', }, + labelmapRenderingTiled: { + axial: 'axial.png', + coronal: 'coronal.png', + sagittal: 'sagittal.png', + }, contourRendering: { viewport: 'viewport.png', }, + contourRenderingTiled: { + viewport: 'viewport.png', + }, labelmapGlobalConfiguration: { defaultGlobalConfig: 'defaultGlobalConfig.png', toggleInactiveSegmentation: 'toggleInactiveSegmentation.png', @@ -194,9 +208,21 @@ const screenShotPaths = { afterReformat: 'mpr-reformat-after.png', afterInteraction: 'mpr-reformat-after-interaction.png', }, - sequentialRenderingEngine: { + contextPoolRenderingEngine: { viewport: 'viewport.png', }, + volumeAnnotation: { + lengthTool: 'lengthTool.png', + }, + volumeAnnotationTiled: { + lengthTool: 'lengthTool.png', + }, + stackAnnotation: { + lengthTool: 'lengthTool.png', + }, + stackAnnotationTiled: { + lengthTool: 'lengthTool.png', + }, }; export { screenShotPaths }; diff --git a/tests/volumeAnnotation.spec.ts b/tests/volumeAnnotation.spec.ts new file mode 100644 index 0000000000..fed7bf4504 --- /dev/null +++ b/tests/volumeAnnotation.spec.ts @@ -0,0 +1,23 @@ +import { test } from 'playwright-test-coverage'; +import { + visitExample, + checkForScreenshot, + screenShotPaths, + simulateDrag, +} from './utils/index'; + +test.beforeEach(async ({ page }) => { + await visitExample(page, 'volumeAnnotationTools'); +}); + +test.describe('Volume Annotation Tools', async () => { + test('should draw a length measurement on the viewport', async ({ page }) => { + const locator = page.locator('.cornerstone-canvas').nth(0); + await simulateDrag(page, locator); + await checkForScreenshot( + page, + locator, + screenShotPaths.volumeAnnotation.lengthTool + ); + }); +}); diff --git a/tests/volumeAnnotationTiled.spec.ts b/tests/volumeAnnotationTiled.spec.ts new file mode 100644 index 0000000000..c00cd95b33 --- /dev/null +++ b/tests/volumeAnnotationTiled.spec.ts @@ -0,0 +1,24 @@ +import { test } from 'playwright-test-coverage'; +import { + visitExample, + checkForScreenshot, + screenShotPaths, + simulateDrag, +} from './utils/index'; + +test.beforeEach(async ({ page, context }) => { + await context.addInitScript(() => (window.IS_TILED = true)); + await visitExample(page, 'volumeAnnotationTools'); +}); + +test.describe('Volume Annotation Tools - Tiled', async () => { + test('should draw a length measurement on the viewport', async ({ page }) => { + const locator = page.locator('.cornerstone-canvas').nth(0); + await simulateDrag(page, locator); + await checkForScreenshot( + page, + locator, + screenShotPaths.volumeAnnotationTiled.lengthTool + ); + }); +}); diff --git a/tests/volumeBasicTiled.spec.ts b/tests/volumeBasicTiled.spec.ts new file mode 100644 index 0000000000..f95eef83bb --- /dev/null +++ b/tests/volumeBasicTiled.spec.ts @@ -0,0 +1,25 @@ +import { test } from 'playwright-test-coverage'; +import { + visitExample, + checkForScreenshot, + screenShotPaths, +} from './utils/index'; + +test.beforeEach(async ({ page, context }) => { + await visitExample(page, 'volumeBasic'); + await context.addInitScript(() => (window.IS_TILED = true)); +}); + +test.describe('Basic Volume', async () => { + test('should display a single DICOM series in a Volume viewport.', async ({ + page, + }) => { + // Now take the screenshot + const locator = page.locator('.cornerstone-canvas'); + await checkForScreenshot( + page, + locator, + screenShotPaths.volumeBasicTiled.viewport + ); + }); +}); diff --git a/utils/ExampleRunner/example-info.json b/utils/ExampleRunner/example-info.json index de7013e3cf..e74bc869b1 100644 --- a/utils/ExampleRunner/example-info.json +++ b/utils/ExampleRunner/example-info.json @@ -153,13 +153,17 @@ "name": "Custom Web Worker Function", "description": "Demonstrates how to use the web worker manager to register and execute custom web worker functions off the main thread" }, - "sequentialrenderingengine": { - "name": "6x6 Grid with SequentialRenderingEngine", - "description": "Displays a 6x6 grid of viewports using SequentialRenderingEngine for better performance with large viewport counts" + "contextpoolrenderingengine": { + "name": "6x6 Grid with ContextPoolRenderingEngine", + "description": "Displays a 6x6 grid of viewports using ContextPoolRenderingEngine for better performance with large viewport counts" }, "ptctmultimonitor": { "name": "PET-CT Multi-Monitor Layout", - "description": "Demonstrates how to create a multi-monitor layout with PET-CT fusion using the Sequential RenderingEngine" + "description": "Demonstrates how to create a multi-monitor layout with PET-CT fusion using the ContextPoolRenderingEngine" + }, + "webGLContextPooling": { + "name": "WebGL Context Pooling", + "description": "Demonstrates how to use WebGL context pooling to render many viewports in sync using ContextPoolRenderingEngine" } }, "tools-basic": { diff --git a/utils/demo/helpers/index.js b/utils/demo/helpers/index.js index b399c7fcc5..220bf30626 100644 --- a/utils/demo/helpers/index.js +++ b/utils/demo/helpers/index.js @@ -20,6 +20,8 @@ import createInfoSection from './createInfoSection'; import downloadSurfacesData from './downloadSurfacesData'; import getLocalUrl from './getLocalUrl'; import initDemo from './initDemo'; +import initProviders from './initProviders'; +import initVolumeLoader from './initVolumeLoader'; import labelmapTools from './labelmapTools'; import setCtTransferFunctionForVolumeActor, { ctVoiRange, @@ -59,6 +61,8 @@ export { downloadSurfacesData, getLocalUrl, initDemo, + initProviders, + initVolumeLoader, labelmapTools, setCtTransferFunctionForVolumeActor, setPetColorMapTransferFunctionForVolumeActor,