Repository navigation
Conversation
vtkOpenGLImageMapper, vtkOpenGLImageResliceMapper and vtkOpenGLImageCPRMapper each keep their image quad's vertex buffer and VAO in model.tris, a vtkOpenGLHelper instance. None of them called model.tris.releaseGraphicsResources(), so every removed or deleted image slice left its vertex buffer and VAO behind. The mappers now release tris in unregisterGraphicsResources(), the function each of them already calls both when the mapper moves to a different render window and when the mapper is deleted. That matches how VolumeMapper releases its own tris helper. New tests render one image slice with a real transfer function and piecewise function, so the color and opacity textures stay on the render window's shared cache. One test per mapper removes the slice, and another moves its renderer to a second render window and checks the slice still draws there. Both check that the live GL object count returns to what it was before.
sankhesh
self-requested a review
October 14, 2026 16:23
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
vtkOpenGLImageMapper,vtkOpenGLImageResliceMapperandvtkOpenGLImageCPRMappereach own amodel.trishelper, an instance ofvtkOpenGLHelperthat holds the image quad's vertex buffer (CABO) and VAO. None of the three ever callsmodel.tris.releaseGraphicsResources(). Every removed or deleted image slice mapper, and every mapper moved to a different render window, leaves its vertex buffer and VAO behind.This surfaced while testing #3683, which fixes the same three mappers' default lookup texture leak. That is a different bug with a different cause, and the two PRs don't depend on each other.
Confirmed at master,
009a4b2a9be3164820803a8d76065b45bbae0be3.Root cause
Links are pinned to master at
009a4b2a9be3164820803a8d76065b45bbae0be3.vtkOpenGLHelper.releaseGraphicsResourcesalready does the work: it releases the VAO and the CABO, which owns the vertex buffer (Helper L34-L38).unregisterGraphicsResources(renderWindow)function that releases the mapper's own non-shared resources before unregistering the shared ones, and it never touchesmodel.tris: ImageMapper L57-L69 releasesmodel.openGLTexturethe same way; ImageResliceMapper L122-L127 and ImageCPRMapper L37-L42 only unregister shared resources, since neither owns a non-shared texture of its own.model.tris.setOpenGLRenderWindowrepointstrisat the new one: ImageMapper L79-L92, ImageResliceMapper L142-L155, ImageCPRMapper L52-L69. When the mapper is deleted, it runs from the delete chain: ImageMapper L1596-L1600, ImageResliceMapper L2015-L2027, ImageCPRMapper L1413-L1417. Neither path freesmodel.tris, so its VAO and vertex buffer from the old context are never released.vtkOpenGLVolumeMapperdoes not have this bug. It also owns amodel.trishelper, and its own cleanup already callsmodel.tris.releaseGraphicsResources(openGLRenderWindow)(VolumeMapper L1206-L1219), called from the same two places: the render window switch (VolumeMapper L137-L154) and delete (VolumeMapper L1221-L1235). This PR gives the three image mappers the same shape.removeUnusedNodes, which deletes it exactly the way an explicitmapper.delete()does. I could not find a way to reach the render-window-switch branch itself, theoldOglRenderWindow !== model._openGLRenderWindowcheck inside each mapper's ownbuildPass, while the mapper's view node survives. Every move of a renderer or a child render window between two render windows deletes the old view node and builds a fresh one under the new parent, with nothing stale to rebuild. I confirmed this by instrumenting that branch directly: across both ways of moving a renderer, it never ran.Results
All numbers come from the new tests, run on master and on this branch. Each scene is one image slice with a real RGB transfer function and piecewise function on the property, so the color and opacity lookup textures land on the render window's shared cache and are already released correctly on removal. That isolates the count to the two GL objects this PR touches: the tris VAO and its vertex buffer.
All three new tests fail on master with the same assertion error,
expected 2 to be +0: one VAO and one vertex buffer short of the empty-scene baseline. All three pass on this branch.Each mapper also has a second test that moves its renderer to an independent second render window instead of removing the actor, then checks the slice still draws there and the first window's object count drops back to what it was before the move. On master, these three also fail, off by the same 2 objects, for the same reason:
removeUnusedNodesprunes the renderer that left through the normal delete path, not through the render-window-switch branch. In every run, with or without the fix, the slice draws correctly in the second window, because a moved renderer's view node is always built fresh. All six tests pass on this branch.The full suite passes on this branch: 233 test files (226 passed, 7 skipped), 660 tests (647 passed, 13 skipped), 0 failed, in 136 seconds. It ran in headless Chromium with SwiftShader (
CI=1) in themcr.microsoft.com/playwright:v1.60.0-nobleimage.Changes
vtkOpenGLImageMapper,vtkOpenGLImageResliceMapperandvtkOpenGLImageCPRMapper:unregisterGraphicsResourcesnow also callsmodel.tris.releaseGraphicsResources(renderWindow). This is the function each mapper already calls both on a render window switch and on delete, so the single call covers both cases without a new call site.Sources/Rendering/OpenGL/{ImageMapper,ImageResliceMapper,ImageCPRMapper}/test/testReleaseGraphicsResources.js. One test per mapper builds one image slice with a real transfer function and piecewise function, renders it, removes the actor from the renderer, and checks that the tracked GL object count returns to the pre-render baseline. A second test per mapper moves its renderer to a second, independent render window, checks the slice still draws there by reading the pixel at the viewport's center, and checks the first window's object count returns to what it was before the move.Notes for review
getRGBTransferFunction()andgetPiecewiseFunction()return null and the mapper falls into the still-open default-lookup-texture leak in the same three mappers, which would add more leaked objects to the count and make the result say nothing specific abouttris.model.tris.releaseGraphicsResources(renderWindow)passes the render window through even thoughvtkOpenGLHelper.releaseGraphicsResourcesignores the argument today. That matches the existing call tomodel.openGLTexture.releaseGraphicsResources(renderWindow)right next to it inImageMapper, and howVolumeMappercalls the same method on its owntris.model.tristhere could leave a surviving mapper's quad empty:ImageMapperandImageCPRMapperrebuild the quad only when their inputs change, whileImageResliceMapperalso rebuilds when the quad has no elements. I could not find one. Moving a renderer between two independent render windows, and moving a child render window between two root windows, both delete the old view node and build a fresh one elsewhere; instrumenting the switch branch directly showed zero hits across either operation. If you know a path that reaches it, the same!model.tris.getCABO().getElementCount()checkImageResliceMapperuses would cover the other two, and I can add it here.removeUnusedNodes, the same delete pathmapper.delete()and an explicitremoveActoralready use. That is still a real, user-reachable way to hit the original leak, so the tests stay as a second, independent check of it.vtkOpenGLImageResliceMapperreleases its label outline textures with no render window argument, and the three mappers' still-open default-lookup-texture leak. Both are tracked separately and neither is part of this change.PR Checklist
oxfmt --checkpasses,oxlintreports 0 errors with 3 pre-existing warnings unrelated to this change, andcommitlintpasses on the commit message)