Rendering Engine
A RenderingEngine
allows the user to create Viewports, associate these Viewports with onscreen HTML elements, and render data to these elements using an offscreen WebGL canvas.
It should be noted that RenderingEngine
is capable of rendering multiple viewports, and you don't need to
create multiple engines. However, multiple RenderingEngine
instances can be created, e.g., if you wish to have a multiple monitor setup, and use a separate WebGL context to render each monitor’s viewports.
In Cornerstone3D
we have built the RenderingEngine
from ground up, and we are utilizing vtk.js as the backbone of the rendering. vtk.js
is a 3D rendering library capable of using WebGL for GPU-accelerated rendering.
OnScreen and Offscreen Rendering
Previously in Cornerstone (legacy), we processed data in each viewport with a WebGL canvas. This doesn't scale well, as the number of viewports increases and for complex imaging use cases (e.g., synced viewports), we will end up with lots of updates to onscreen canvases and performance degrades as the number of viewports increases.
In Cornerstone3D
, we process data in an offscreen canvas. This means that
we have a big invisible canvas (offscreen) that includes all the onscreen canvases inside itself.
As the user manipulates the data, the corresponding pixels in the offscreen
canvas get updated, and at render time, we copy from offscreen to onscreen for each viewport. Since the copying process is much faster than re-rendering each viewport upon manipulation, we have addressed the performance degradation problem.
Shared Volume Mappers
vtk.js
provides standard rendering functionalities which we use for rendering. In addition, in Cornerstone3D
we have introduced Shared Volume Mappers
to enable re-using the texture for any viewport that might need it without duplicating the data.
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:
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:
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.
Instantiating a RenderingEngine
You can instantiate a RenderingEngine
by calling the new RenderingEngine()
method.
import { RenderingEngine } from '@cornerstonejs/core';
const renderingEngineId = 'myEngine';
const renderingEngine = new RenderingEngine(renderingEngineId);
Viewport Creation
You can then use two methods to create viewports: setViewports
or enable/disable
APIs.
For both methods, a ViewportInput object is passed as an argument.
PublicViewportInput = {
/** HTML element in the DOM */
element: HTMLDivElement
/** unique id for the viewport in the renderingEngine */
viewportId: string
/** type of the viewport VolumeViewport or StackViewport*/
type: ViewportType
/** options for the viewport */
defaultOptions: ViewportInputOptions
}
setViewports API
setViewports
method is suitable for creation of a set of viewports at once.
After setting the array of viewports, the renderingEngine
will adapt its
offScreen canvas size to the size of the provided canvases, and triggers the corresponding
events.
const viewportInput = [
// CT Volume Viewport - Axial
{
viewportId: 'ctAxial',
type: ViewportType.ORTHOGRAPHIC,
element: htmlElement1,
defaultOptions: {
orientation: Enums.OrientationAxis.AXIAL,
},
},
// CT Volume Viewport - Sagittal
{
viewportId: 'ctSagittal',
type: ViewportType.ORTHOGRAPHIC,
element: htmlElement2,
defaultOptions: {
orientation: Enums.OrientationAxis.SAGITTAL,
},
},
// CT Axial Stack Viewport
{
viewportId: 'ctStack',
type: ViewportType.STACK,
element: htmlElement3,
defaultOptions: {
orientation: Enums.OrientationAxis.AXIAL,
},
},
];
renderingEngine.setViewports(viewportInput);
Enable/Disable API
For having a full control over enabling/disabling each viewport separately, you
can use the enableElement
and disableElement
API. After enabling the element,
renderingEngine
adapts its size and state with the new element.
const viewport = {
viewportId: 'ctAxial',
type: ViewportType.ORTHOGRAPHIC,
element: element1,
defaultOptions: {
orientation: Enums.OrientationAxis.AXIAL,
},
};
renderingEngine.enableElement(viewport);
You can disable any viewport by using its viewportId
, after disabling,
renderingEngine will resize its offScreen canvas.
renderingEngine.disableElement(viewportId: string)