Repository navigation
Conversation
A stopped CompositingRenderer skips its update pass, so nothing drains the pending visuals. A visual that leaves the tree in that state must not stay referenced by the renderer. Visuals that remain in the tree must stay queued until rendering resumes. The test fails: the renderer still references the removed visuals. HasPendingUpdateForUnitTests exposes the pending state so that the test does not depend on garbage collection.
A minimized or hidden window stops its renderer. A stopped CompositingRenderer skips its update pass, and that pass is the only place that clears the dirty and recalculate-children sets. Each visual that detached in that state stayed in the sets, so a window that kept changing its content while minimized retained every removed visual until rendering resumed. A visual reports itself dirty one last time when it detaches, after it has dropped its composition visual. The update pass ignores such an entry. Remove the visual from both sets at that point instead of adding it, and still request a composition update so that the detach is committed. Visuals that remain in the tree stay queued, so a restart still resumes the pending update.
NathanDrake2406
marked this pull request as ready for review
October 2, 2026 02:06
NuGet packagesThe packages for this pull request have been published (workflow run):
|
MrJul
approved these changes
Oct 9, 2026
MrJul
left a comment
Member
There was a problem hiding this comment.
The change makes sense now that we aren't processing the dirty collection when the renderer is stopped, UpdateCore was just skipping these visuals anyway.
LGTM!
NathanDrake2406
deleted the
fix/22098-renderer-dirty-visuals-while-stopped
branch
October 9, 2026 09:37
MrJul
pushed a commit
that referenced
this pull request
Oct 9, 2026
* test(rendering): cover visuals removed while rendering is stopped A stopped CompositingRenderer skips its update pass, so nothing drains the pending visuals. A visual that leaves the tree in that state must not stay referenced by the renderer. Visuals that remain in the tree must stay queued until rendering resumes. The test fails: the renderer still references the removed visuals. HasPendingUpdateForUnitTests exposes the pending state so that the test does not depend on garbage collection. * fix(rendering): release detached visuals from a stopped renderer A minimized or hidden window stops its renderer. A stopped CompositingRenderer skips its update pass, and that pass is the only place that clears the dirty and recalculate-children sets. Each visual that detached in that state stayed in the sets, so a window that kept changing its content while minimized retained every removed visual until rendering resumed. A visual reports itself dirty one last time when it detaches, after it has dropped its composition visual. The update pass ignores such an entry. Remove the visual from both sets at that point instead of adding it, and still request a composition update so that the detach is committed. Visuals that remain in the tree stay queued, so a restart still resumes the pending update. * chore: trim code comments
3 of 4 tasks
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.
What does the pull request do?
Stops
CompositingRendererfrom keeping references to visuals that leave the visual tree while rendering is stopped, for example while a window is minimized or hidden.What is the current behavior?
Since #21192,
CompositingRenderer.Update()returns early when its composition target is disabled. That update pass is the only place that clears_dirtyand_recalculateChildren, apart fromDispose().AddDirtyandRecalculateChildrencontinue to add visuals while the renderer is stopped.Visual.OnDetachedFromVisualTreeCorealso callsAddDirty(this)for every detached visual, afterDetachFromCompositor(). Each visual that is removed from a minimized window therefore stays referenced by the renderer until the window is restored.A window that replaces its content repeatedly while minimized, such as the lyrics view in #22098, retains every replaced visual and its data context. The issue reports
_dirty.Count = 33,703and_recalculateChildren.Count = 19,870in a dump.The early return is also in the 12.0 branch from 12.0.2, which matches the 12.0.4 report.
What is the updated/expected behavior with this PR?
_dirty/_recalculateChildrenuntil rendering resumesStartRenderingWhile stopped, the pending sets are now bounded by the size of the live visual tree.
How was the solution implemented (if it's not obvious)?
CompositingRendererowns both sets, so the fix is inAddDirty. When the visual has noCompositionVisual, the renderer removes the visual from both sets instead of adding it. It still queues a composition update, as before.This is safe because such an entry is already inert.
UpdateCoreskips dirty visuals without aCompositionVisual, andSynchronizeCompositionChildVisualsreturns early for them. The only caller that passes a visual without a composition visual is the detach path, and a visual that attaches again reports itself dirty again.Alternatives that were not chosen:
Stop()and resynchronize the whole tree inStart(). This drops pending updates for visuals that are still attached and costs a full tree walk.Start()is unchanged. It re-queues an update when a set is non-empty. A removal throughPanel.Childrenor a content change also queues the parent, which stays in the tree, so a restart still resumes the update.The test needs to observe the pending state without a GC-dependent assertion, so this PR adds the internal
HasPendingUpdateForUnitTests, next to the existingTriggerSceneInvalidatedForUnitTests.Validation:
Visuals_Removed_While_Rendering_Stopped_Are_Not_Kept_By_Rendererfails on the first commit and passes on the second. It also checks that visuals still in the tree stay queued and are updated afterStartRendering, and that hit testing no longer finds the removed visual.Avalonia.Base.UnitTests, by class:CompositorLifetimeTests(2 passed),CompositorInvalidationTests(6 passed),CompositorHitTestingTests(58 passed),VisualTests(33 passed).Not covered:
RecalculateChildrenand theStart()condition are not changed. Memory behavior on a real minimized window was not measured; the test asserts the renderer state directly.Checklist
Breaking changes
None. The change is internal to
CompositingRenderer.Obsoletions / Deprecations
None.
Fixed issues
Fixes #22098