Skip to content

Shared WebGL context render window#3453

Open
PaulHax wants to merge 4 commits intoKitware:masterfrom
PaulHax:shared-render-window
Open

Shared WebGL context render window#3453
PaulHax wants to merge 4 commits intoKitware:masterfrom
PaulHax:shared-render-window

Conversation

@PaulHax
Copy link
Copy Markdown
Collaborator

@PaulHax PaulHax commented Mar 26, 2026

Are folks intersted in putting this in the vtk.js repo? Its an uncommon use case. We could get away with simply doing something to keep vtkOpenGLRenderWindow from resizing the canvas and put SharedRenderWindow in another repo/package.

Context

This PR adds an OpenGL shared-context rendering path for vtk.js so it can render into a WebGL2RenderingContext owned by another library, instead of always creating and managing its own canvas/context.

The immediate use case is interleaving vtk.js content with host renderers such as MapLibre custom layers, including depth-tested rendering into the host scene. vtk.js also needs an explicit way to opt out of canvas DOM management when the canvas is externally owned.

Results

Before this change:

  • vtkOpenGLRenderWindow assumed ownership of the canvas and could resize/restyle it.

After this change:

  • vtk.js can render into a host-owned WebGL2 context through vtkSharedRenderWindow.createFromContext(canvas, gl).

Changes

  • Added manageCanvas to vtkOpenGLRenderWindow (default true) so externally owned canvases can opt out of vtk.js canvas sizing/styling.

  • Added vtkSharedRenderWindow for rendering into an externally provided WebGL2RenderingContext.

  • Added vtkSharedRenderer for the shared-context rendering path.

  • Scoped the vtkSharedRenderer override to the shared render-window instance rather than changing the global OpenGL view-node factory. This avoids leaking shared-context behavior into unrelated normal OpenGL render windows.

  • Added a MapLibre example showing shared-context rendering on terrain.

  • Documentation and TypeScript definitions were updated to match those changes

PR and Code Checklist

  • semantic-release commit messages
  • Run npm run reformat to have correctly formatted code

Testing

npm run example -- SharedContext

  • This change adds or fixes unit tests
  • Tested environment:
    • OS: Linux
    • Browser: Chrome
image

@PaulHax PaulHax force-pushed the shared-render-window branch 2 times, most recently from 93f4a3a to 5e23b91 Compare March 26, 2026 03:29
Copy link
Copy Markdown
Member

@finetjul finetjul left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sankhesh I do not have any opinion on the approach, I leave it to you.

Comment thread Sources/Rendering/OpenGL/ViewNodeFactory/index.js Outdated
@daker
Copy link
Copy Markdown
Collaborator

daker commented Mar 26, 2026

@sedghi do you have an opnion on this ?

@finetjul finetjul requested a review from sankhesh March 26, 2026 15:39
@PaulHax PaulHax force-pushed the shared-render-window branch 5 times, most recently from ca02f1f to 8ed472d Compare April 8, 2026 15:23
@PaulHax PaulHax requested a review from Copilot May 7, 2026 15:01
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a shared-WebGL2-context rendering path to vtk.js, enabling vtk.js to render into an externally owned WebGL2RenderingContext (e.g., MapLibre custom layers) rather than always creating/managing its own canvas/context.

Changes:

  • Added manageCanvas (default true) to vtkOpenGLRenderWindow to allow opting out of canvas resizing/styling when the canvas is externally owned.
  • Introduced vtkSharedRenderWindow.createFromContext(canvas, gl) plus vtkSharedRenderer to support shared-context rendering while keeping overrides local to the shared render-window instance.
  • Added SharedRenderWindow unit tests (GL state reset, host framebuffer rendering, host survival) and a MapLibre shared-context example.

Reviewed changes

Copilot reviewed 9 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
Sources/Rendering/SceneGraph/ViewNodeFactory/index.js Switches override lookup to support prototype-inherited mappings (in), enabling per-instance override chains.
Sources/Rendering/OpenGL/ViewNodeFactory/index.js Makes OpenGL view-node mappings inheritable per factory instance via Object.create(CLASS_MAPPING) and adds instance-local registerOverride.
Sources/Rendering/OpenGL/RenderWindow/index.js Adds manageCanvas gating for canvas size/display updates and blocks resize-based screenshot capture when canvas management is disabled.
Sources/Rendering/OpenGL/RenderWindow/index.d.ts Exposes manageCanvas in initial values and adds getManageCanvas / setManageCanvas.
Sources/Rendering/OpenGL/SharedRenderWindow/index.js New shared-context render window with GL-state reset, size sync from drawing buffer, and render-callback integration.
Sources/Rendering/OpenGL/SharedRenderWindow/index.d.ts Type definitions for vtkSharedRenderWindow API (renderShared, callbacks, autoClear controls, createFromContext).
Sources/Rendering/OpenGL/SharedRenderer/index.js New renderer variant that respects shared-window autoClear behavior and uses tiled viewport/scissor.
Sources/Rendering/OpenGL/SharedRenderer/index.d.ts Type definitions for vtkSharedRenderer.
Sources/Rendering/OpenGL/SharedRenderWindow/test/testSharedRenderWindow.js Baseline image test + validation that shared renderer override remains local + WebGL1 rejection + manageCanvas behavior.
Sources/Rendering/OpenGL/SharedRenderWindow/test/testSharedRenderWindowGLState.js Tests that renderShared() resets GL state and renders into a currently bound host framebuffer.
Sources/Rendering/OpenGL/SharedRenderWindow/test/testSharedRenderWindowHostSurvival.js Tests host rendering can resume after renderShared() when host state is rebound.
Examples/Rendering/SharedContext/index.js MapLibre terrain example demonstrating shared-context depth-tested interleaving.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Sources/Rendering/OpenGL/SharedRenderWindow/index.js
@PaulHax PaulHax force-pushed the shared-render-window branch 3 times, most recently from 6fd51df to c19dc9c Compare May 7, 2026 16:38
PaulHax added 2 commits May 7, 2026 12:56
Move registerOverride from extend() into
the main function body for both ViewNodeFactory and SharedRenderWindow.
@PaulHax PaulHax force-pushed the shared-render-window branch from c19dc9c to 5d1f87a Compare May 7, 2026 16:58
Copy link
Copy Markdown
Collaborator

@sankhesh sankhesh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM overall.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be done only iff manageCanvas is true?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Added the manageCanvas guard to match the one at line 164.


function getDefaultDrawBuffers(gl) {
const framebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING);
return framebuffer ? [gl.COLOR_ATTACHMENT0] : [gl.BACK];
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an assumption here. If the external application has bound multiple FBO attachments, assuming attachment 0 might be wrong.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to query DRAW_BUFFER0 to gl.MAX_DRAW_BUFFERS and respect the host's actual draw-buffer state, with [COLOR_ATTACHMENT0] as a fallback when every slot is NONE.

gl.enable(gl.SCISSOR_TEST);
gl.scissor(ts.lowerLeftU, ts.lowerLeftV, ts.usize, ts.vsize);
gl.viewport(ts.lowerLeftU, ts.lowerLeftV, ts.usize, ts.vsize);
gl.enable(gl.DEPTH_TEST);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the state be saved and reset after VTK is done rendering?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SharedRenderer is assuming a contract where host is responsible for rebinding its GL state after renderShared(). This is the same contract used by three.js (renderer.resetState()), Babylon (engine.wipeCaches()), and deck.gl/luma.gl. A full state save/restore would add a lot of getParameter calls per render and create a heavier contract.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really. You only need to query and cache the state parameters that are being force-changed by vtk. This is what we do, for example in vtkExternalOpenGLRenderWindow that follows the same idea.

model.classHierarchy.push('vtkSharedRenderer');

publicAPI.clear = () => {
const gl = model.context;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it guaranteed that model.context will be valid always when we get here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its guaranteed. Same assuption as parent vtkOpenGLRenderer.clear()

Gate the screenshot-path canvas display toggle on manageCanvas, matching
the existing guard at line 164 so externally owned canvases are never
restyled.

In getDefaultDrawBuffers, query the host's actual DRAW_BUFFER0..N state
when an FBO is bound rather than assuming COLOR_ATTACHMENT0. Falls back
to [COLOR_ATTACHMENT0] when all slots are NONE so callers that left the
default state untouched still get a valid render target.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants