Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Share GPU ressources across render windows #3051

Merged
merged 10 commits into from
Apr 11, 2024
166 changes: 166 additions & 0 deletions Examples/Rendering/ManyRenderWindows/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import '@kitware/vtk.js/favicon';

// Load the rendering pieces we want to use (for both WebGL and WebGPU)
import '@kitware/vtk.js/Rendering/Misc/RenderingAPIs';
import '@kitware/vtk.js/Rendering/Profiles/Volume';

import vtkColorTransferFunction from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction';
import vtkHttpDataSetReader from '@kitware/vtk.js/IO/Core/HttpDataSetReader';
import vtkInteractorStyleTrackballCamera from '@kitware/vtk.js/Interaction/Style/InteractorStyleTrackballCamera';
import vtkPiecewiseFunction from '@kitware/vtk.js/Common/DataModel/PiecewiseFunction';
import vtkRenderer from '@kitware/vtk.js/Rendering/Core/Renderer';
import vtkRenderWindow from '@kitware/vtk.js/Rendering/Core/RenderWindow';
import vtkRenderWindowInteractor from '@kitware/vtk.js/Rendering/Core/RenderWindowInteractor';
import vtkVolume from '@kitware/vtk.js/Rendering/Core/Volume';
import vtkVolumeMapper from '@kitware/vtk.js/Rendering/Core/VolumeMapper';

// Force the loading of HttpDataAccessHelper to support gzip decompression
import '@kitware/vtk.js/IO/Core/DataAccessHelper/HttpDataAccessHelper';

// ----------------------------------------------------------------------------
// Background colors
// ----------------------------------------------------------------------------

async function createActor() {
const actor = vtkVolume.newInstance();
const mapper = vtkVolumeMapper.newInstance();
mapper.setSampleDistance(0.7);
mapper.setVolumetricScatteringBlending(0);
mapper.setLocalAmbientOcclusion(0);
mapper.setLAOKernelSize(10);
mapper.setLAOKernelRadius(5);
mapper.setComputeNormalFromOpacity(true);
actor.setMapper(mapper);

const ctfun = vtkColorTransferFunction.newInstance();
ctfun.addRGBPoint(0, 0, 0, 0);
ctfun.addRGBPoint(95, 1.0, 1.0, 1.0);
ctfun.addRGBPoint(225, 0.66, 0.66, 0.5);
ctfun.addRGBPoint(255, 0.3, 0.3, 0.5);
const ofun = vtkPiecewiseFunction.newInstance();
ofun.addPoint(100.0, 0.0);
ofun.addPoint(255.0, 1.0);
actor.getProperty().setRGBTransferFunction(0, ctfun);
actor.getProperty().setScalarOpacity(0, ofun);
actor.getProperty().setInterpolationTypeToLinear();
actor.getProperty().setShade(true);
actor.getProperty().setAmbient(0.3);
actor.getProperty().setDiffuse(1);
actor.getProperty().setSpecular(1);
actor.setScale(0.003, 0.003, 0.003);
actor.setPosition(1, 1, -1.1);

const reader = vtkHttpDataSetReader.newInstance({ fetchGzip: true });
await reader.setUrl(`${__BASE_PATH__}/data/volume/LIDC2.vti`);
await reader.loadData();
const imageData = reader.getOutputData();

mapper.setInputData(imageData);

return actor;
}

const mainRenderWindow = vtkRenderWindow.newInstance();
const mainRenderWindowView = mainRenderWindow.newAPISpecificView();
mainRenderWindow.addView(mainRenderWindowView);

const rootContainer = document.createElement('div');
rootContainer.style.display = 'flex';
rootContainer.style['align-items'] = 'center';
rootContainer.style['justify-content'] = 'space-between';
rootContainer.style['flex-wrap'] = 'wrap';
document.body.appendChild(rootContainer);

function applyStyle(element) {
const width = Math.floor(200 + Math.random() * 200);
const height = Math.floor(200 + Math.random() * 200);
element.style.width = `${width}px`;
element.style.height = `${height}px`;
element.style.margin = '20px';
element.style.border = 'solid 2px #333';
return element;
}

function addRenderWindow() {
// Create a child renderwindow
const renderWindow = vtkRenderWindow.newInstance();
mainRenderWindow.addRenderWindow(renderWindow);

// Create the corresponding view
const renderWindowView = mainRenderWindowView.addMissingNode(renderWindow);

// Create a container for the view
const container = applyStyle(document.createElement('div'));
rootContainer.appendChild(container);
renderWindowView.setContainer(container);

// Resize the view to the size of the container
// Ideally, we would use a resize observer to resize the view and
// call resizeFromChildRenderWindows on the main view when the container is resized
const containerBounds = container.getBoundingClientRect();
const pixRatio = window.devicePixelRatio;
const dimensions = [
containerBounds.width * pixRatio,
containerBounds.height * pixRatio,
];
renderWindowView.setSize(...dimensions);
const canvas = renderWindowView.getCanvas();
canvas.style.width = `${container.clientWidth}px`;
canvas.style.height = `${container.clientHeight}px`;

// Add an interactor to the view
const interactor = vtkRenderWindowInteractor.newInstance();
interactor.setView(renderWindowView);
interactor.initialize();
interactor.bindEvents(canvas);
interactor.setInteractorStyle(
vtkInteractorStyleTrackballCamera.newInstance()
);

return renderWindow;
}

// ----------------------------------------------------------------------------
// Fill up page
// ----------------------------------------------------------------------------

const childRenderWindows = [];
createActor().then((actor) => {
// Main view has to be initialized before the first "render" from a child render window
// We initialize before creating the child render windows because the interactor initialization calls "render" on them
mainRenderWindowView.initialize();

for (let i = 0; i < 64; i++) {
const childRenderWindow = addRenderWindow();

// Create the corresponding renderer
const background = [
0.5 * Math.random() + 0.25,
0.5 * Math.random() + 0.25,
0.5 * Math.random() + 0.25,
];
const renderer = vtkRenderer.newInstance({ background });
childRenderWindow.addRenderer(renderer);

// Add the actor and reset camera
renderer.addActor(actor);
const camera = renderer.getActiveCamera();
camera.yaw(90);
camera.roll(90);
camera.azimuth(Math.random() * 360);
renderer.resetCamera();

childRenderWindows.push(childRenderWindow);
}

mainRenderWindowView.resizeFromChildRenderWindows();
mainRenderWindow.render();
});

// ----------------------------------------------------------------------------
// Globals
// ----------------------------------------------------------------------------

global.rw = mainRenderWindow;
global.glrw = mainRenderWindowView;
global.childRw = childRenderWindows;
17 changes: 17 additions & 0 deletions Sources/Rendering/Core/RenderWindow/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface IRenderWindowInitialValues {
interactor?: any,
neverRendered?: boolean,
numberOfLayers?: number
childRenderWindows?: vtkRenderWindow[],
}

interface IStatistics {
Expand Down Expand Up @@ -42,6 +43,12 @@ export interface vtkRenderWindow extends vtkObject {
*/
addRenderer(renderer: vtkRenderer): void;

/**
* Add a child render window
* @param {vtkRenderWindow} renderWindow The vtkRenderWindow instance.
*/
addRenderWindow(renderWindow: vtkRenderWindow): void;

/**
* Add renderer
* @param view
Expand Down Expand Up @@ -87,6 +94,16 @@ export interface vtkRenderWindow extends vtkObject {
*/
getRenderersByReference(): vtkRenderer[];

/**
*
*/
getChildRenderWindows(): vtkRenderWindow[];

/**
*
*/
getChildRenderWindowsByReference(): vtkRenderWindow[];

/**
*
*/
Expand Down
12 changes: 11 additions & 1 deletion Sources/Rendering/Core/RenderWindow/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,15 @@ function vtkRenderWindow(publicAPI, model) {
)
.filter((i) => !!i);
};

publicAPI.addRenderWindow = (child) => {
if (model.childRenderWindows.includes(child)) {
return false;
}
model.childRenderWindows.push(child);
publicAPI.modified();
return true;
};
}

// ----------------------------------------------------------------------------
Expand All @@ -156,6 +165,7 @@ const DEFAULT_VALUES = {
interactor: null,
neverRendered: true,
numberOfLayers: 1,
childRenderWindows: [],
};

// ----------------------------------------------------------------------------
Expand All @@ -173,7 +183,7 @@ export function extend(publicAPI, model, initialValues = {}) {
'defaultViewAPI',
]);
macro.get(publicAPI, model, ['neverRendered']);
macro.getArray(publicAPI, model, ['renderers']);
macro.getArray(publicAPI, model, ['renderers', 'childRenderWindows']);
bruyeret marked this conversation as resolved.
Show resolved Hide resolved
macro.moveToProtected(publicAPI, model, ['views']);
macro.event(publicAPI, model, 'completion');

Expand Down
6 changes: 3 additions & 3 deletions Sources/Rendering/OpenGL/ForwardPass/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ function vtkForwardPass(publicAPI, model) {
const numlayers = viewNode.getRenderable().getNumberOfLayers();

// iterate over renderers
const renderers = viewNode.getChildren();
const renderers = viewNode.getRenderable().getRenderersByReference();
for (let i = 0; i < numlayers; i++) {
for (let index = 0; index < renderers.length; index++) {
const renNode = renderers[index];
const ren = viewNode.getRenderable().getRenderers()[index];
const ren = renderers[index];
const renNode = viewNode.getViewNodeFor(ren);

if (ren.getDraw() && ren.getLayer() === i) {
// check for both opaque and volume actors
Expand Down
47 changes: 38 additions & 9 deletions Sources/Rendering/OpenGL/RenderWindow/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface IOpenGLRenderWindowInitialValues {
shaderCache?: null;
initialized?: boolean;
context?: WebGLRenderingContext | WebGL2RenderingContext;
context2D?: CanvasRenderingContext2D;
canvas?: HTMLCanvasElement;
cursorVisibility?: boolean;
cursor?: string;
Expand All @@ -40,13 +41,6 @@ export interface ICaptureOptions {
scale?: number
}

export interface I3DContextOptions {
preserveDrawingBuffer?: boolean;
depth?: boolean;
alpha?: boolean;
powerPreference?: string;
}

type vtkOpenGLRenderWindowBase = vtkObject & Omit<vtkAlgorithm,
| 'getInputData'
| 'setInputData'
Expand Down Expand Up @@ -228,9 +222,44 @@ export interface vtkOpenGLRenderWindow extends vtkOpenGLRenderWindowBase {

/**
*
* @param {I3DContextOptions} options
* @param {WebGLContextAttributes} options
*/
get3DContext(options: WebGLContextAttributes): Nullable<WebGLRenderingContext>;

/**
*
* @param {CanvasRenderingContext2DSettings} options
*/
get2DContext(options: CanvasRenderingContext2DSettings): Nullable<CanvasRenderingContext2D>;

/**
* Copy the content of the root parent, if there is one, to the canvas
*/
copyParentContent(): void;

/**
* Resize this render window using the size of its children
* The new size of the renderwindow is the size of the bounding box
* containing all the child render windows
*/
resizeFromChildRenderWindows(): void;

/**
* Returns the last ancestor of type vtkOpenGLRenderWindow if there is one
* If there is no parent vtkOpenGLRenderWindow, returns undefined
*/
getRootOpenGLRenderWindow(): vtkOpenGLRenderWindow | undefined;

/**
* The context 2D is created during initialization instead of the WebGL context
* when there is a parent render window
*/
getContext2D(): CanvasRenderingContext2D | undefined;

/**
*
*/
get3DContext(options: I3DContextOptions): Nullable<WebGLRenderingContext>;
setContext2D(context2D: CanvasRenderingContext2D | undefined): boolean;

/**
*
Expand Down
Loading
Loading