Refactor per-draw render data allocations with a binary opcode stream#21366
Open
ZehMatt wants to merge 13 commits into
Open
Refactor per-draw render data allocations with a binary opcode stream#21366ZehMatt wants to merge 13 commits into
ZehMatt wants to merge 13 commits into
Conversation
First step of the Drawing/Nodes binary stream refactor.
Encodes the opcode stream field-by-field via BinaryPrimitives to avoid unsafe and blittability assumptions.
|
You can test this PR using the following package version. |
|
You can test this PR using the following package version. |
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.
This work stems from the comment #20885 (comment) , this is not exactly how WPF is doing it, the unsafe keyword for example has been avoided, also I wasn't a huge fan of where #20885 was going.
What does the pull request do?
Replaces the render data representation. Until now every
DrawingContextdraw or push call recorded a heap-allocated node object (RenderDataLineNode,RenderDataRectangleNode, the push nodes, …) into aPooledInlineList<IRenderDataItem>.This PR replaces those objects with a flat binary opcode stream plus a resource table. This is the approach WPF uses for its MILCMD render data.
It is an internal change: no public API, rendering output, or behaviour changes.
What is the current behavior?
Every recorded draw/push allocates a node object. For a visual whose content changes each frame (animations, custom-drawn controls) that is one GC-tracked allocation per draw call, every frame - gen0 churn proportional to the draw-call count.
What is the updated/expected behavior with this PR?
Render data is recorded as a
byte[]opcode stream plus a resource table. Recording, server-side replay, hit-testing and bounds calculation all walk the byte stream directly, with no per-draw object allocation.Rendering, hit-testing and visual bounds are unchanged, covered by the render-data contract tests merged in #21341 and the existing render tests, plus new unit tests added here, resource table, the three walkers, serialization round-trip, deep-nesting fallback.
The changes were measured locally with a stress test of ~9k draw calls per frame using mixed primitives.
master:
PR:
The code for the test: https://gist.github.com/ZehMatt/6d033ecd016335b8b02f649a6666286c , its quite evident that this saves quite a bit of memory, in my testing there was no GC pressure anymore in the rendering path, now the major contributor to GC cycles is elsewhere.
How was the solution implemented (if it's not obvious)?
New types under
Rendering/Composition/Drawing/:RenderDataOpcode- one value per draw/push operation, plusPop.RenderDataWriter/RenderDataReader- the byte codec. Blittable payload structs (Point,Rect,RoundedRect,Matrix,BoxShadow,RenderOptions, …) are bulk-copied through awhere T : unmanagedgeneric helper; the constraint is a compile-time guard against a payload type silently becoming non-blittable.RenderDataResources- interns the non-blittable operands (brushes, pens, geometries, bitmaps, custom ops) tointhandles referenced from the payloads.RenderDataStream- owns the opcode stream and resource table, with the recording API and the replay / hit-test / bounds walkers. Push/Pop are inline opcodes; the walkersstackalloctheir scope stack, sized from a max-push-depth tracked whilerecording.
The four consumers were switched over:
RenderDataDrawingContext(recording),CompositionRenderData(client + hit-testing + serialization),ServerCompositionRenderData(server replay + bounds),ImmediateRenderDataSceneBrushContentand the old node classes andIRenderDataItemdeleted.The branch is structured as small standalone commits, so it should be easy to review the changes commit by commit.
Checklist
Fixed issues
Addresses some points of #19363 in regards to rendering.
Final Note
I'm glad that #20885 wasn't merged, this is definitely the cleaner solution to the problem and its backed up by the data.