Optimize ProGPU and Avalonia renderer memory residency#37
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b5123cb216
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| var lastData = Volatile.Read(ref _lastGlyphData); | ||
| if (lastData is not null && _lastGlyphIndex == glyphIndex) |
There was a problem hiding this comment.
Publish the glyph index and data as one atomic cache entry
When two threads request different glyphs from the same glyph-resident font, a reader can load the previous _lastGlyphData after a writer updates _lastGlyphIndex but before its Volatile.Write publishes the matching data. The fast path then returns another glyph's mini-sbix payload, causing intermittent missing or incorrect bitmap glyphs; publish an immutable index/data pair atomically or perform this lookup under _sync.
Useful? React with 👍 / 👎.
| else if (oldWidth < _maxAtlasSize) | ||
| { | ||
| newWidth = Math.Min(_maxAtlasSize, checked(oldWidth * 2)); | ||
| enterRightHandStrip = true; |
There was a problem hiding this comment.
Avoid entering a strip narrower than the pending path
With a non-power-of-two maximum atlas size, the final right-hand strip can be narrower than a raster that still fit the old width. For example, after a 512-wide atlas reaches a configured 1000-pixel height, growing its width to 1000 creates only a 488-pixel strip; a 500-pixel path triggers this branch, then GetOrCreatePath wraps _currentX back to 2 and starts near the top, overlapping existing texels. Only enter the strip when it can contain the pending raster, or repack into the expanded texture.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 477b5739b0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| return new BidiParagraph( | ||
| 0, | ||
| new sbyte[text.Length], | ||
| [new BidiRun(0, text.Length, 0)]); |
There was a problem hiding this comment.
Preserve numeric levels in the ASCII fast path
For an LTR or auto paragraph containing ASCII digits, such as abc 123, UAX #9 rule I1 assigns the European-number characters level 2, but this shortcut returns a single all-zero run. This produces incorrect BidiLevel metadata and merges numeric and surrounding text into one shaping run; restrict the shortcut to inputs whose resolved levels are actually zero or preserve the standard resolver's numeric levels.
Useful? React with 👍 / 👎.
| if (!FontApi.TryGetGlyphIndex(candidate, (uint)codepoint, out ushort glyphIndex) || | ||
| !TryLoadFont(candidate, glyphIndex, out var cachedFont) || | ||
| !cachedFont.Font.HasGlyph((uint)codepoint)) |
There was a problem hiding this comment.
Preserve symbol-cmap remapping before rejecting fonts
When the requested family uses a Windows symbol cmap, its ASCII characters are commonly stored at private-use values such as U+F041. TtfFont.HasGlyph handles this by remapping U+0041, but FontApi.TryGetGlyphIndex performs only the raw cmap lookup; this new prefilter therefore skips the requested symbol font before HasGlyph can run and may select an unrelated fallback instead. The metadata lookup needs the same symbol remapping semantics.
Useful? React with 👍 / 👎.
Summary
This PR completes the measured ProGPU memory-optimization lane and applies the
same resource-lifetime improvements to the Avalonia renderer and Silk.NET host.
It reduces retained font payloads, managed frame allocations, persistent WebGPU
reservations, pipeline/binding duplication, and atlas over-allocation without
changing rendered content, animation work, DPI/subpixel policy, or cache
invalidation semantics.
It also adds a repeatable ControlCatalog benchmark with a native Avalonia Skia
comparison host, explicit compositor/GPU residency metrics, deterministic
screenshots, and a detailed 29-step design and measurement record.
What changed
Font and text residency
of retaining avoidable whole-file managed arrays.
bitmap-glyph payloads on demand.
variable-font behavior, TTC support, and color emoji rendering.
verified level-zero ASCII resolver allocation path, and retain indexed
virtualized scans.
Core renderer residency
buffers geometrically from measured initial reservations.
immutable bind groups while preserving target format/sample-count pipeline
identity.
coverage at a 260x260 texture with a 256x256 usable packing area.
existing growth path for larger workloads.
only for the private sample offscreen-effects compositor.
Avalonia backend
repeated drawing operations with explicit invalidation and ownership rules.
the general path/stroke fallback for dashed and complex cases.
no longer allocates every iteration.
residency.
Benchmarking and documentation
allocation/GC/process metrics, compositor metrics, JSON output, and optional
screenshot capture.
ControlCatalog.
comparisons, performance noise, quality gates, research sources, and final
validation in
docs/SAMPLE_MEMORY_OPTIMIZATION.md.Measured results
(-86.3%).
(-91.2%) after demand-resident font loading.
(-87.5%).
layouts eliminated from the representative workload.
12,800 B less direct managed list-element reservation.
Step 4 baseline, with average throughput remaining in the same noise band.
Same-binary and reverse-order comparisons keep steady-state allocation and
compositor performance equivalent within run-to-run noise. Exact WebGPU buffer
and texture descriptors are used for small residency claims where macOS process
footprint/WindowServer noise is larger than the resource being removed.
API and behavior notes
CompositorOptionsgains independent initial-reservation controls for glyphatlases, glyph uniform staging, brushes, and gradient stops, plus explicit
PrecompileBasePipelines.CompositorMetrics,PathAtlas,GlyphAtlas,ComputeAccelerator, and theAvalonia diagnostics surface expose additive resource-residency counters.
target family on first use.
Rendering and performance validation
528 vector vertices, and 226 text vertices, matching the comparison workload.
segments, with no growth in the representative run.
texel coordinates.
same-frame reset/recompile path; the full supported resvg inventory passes 927/927
with 37 explicitly skipped fixtures.
path, clip, and animation output.
final benchmark or test runs.
Validation
ProGPU.Tests: 2,398 passed.ProGPU.Tests.Headless: 198 passed.ProGPU.Xaml.Tests: 249 passed.SampleControls, and the WinUI desktop sample build in Release.
git diff --checkis clean.The remaining build output is limited to the existing Avalonia private-API and
upstream obsolete Skia overload warnings.
Source provenance
https://github.com/wieslawsoltes/Avalonia.git, tagprogpu-avalonia-v12.0.5-preview.19, commit5378af03f17a4d9d2845882229ffed7f67350037.source overrides are stored in this repository; no source-repository commit
history was imported.
Research basis
The implementation follows primary architecture and API sources, including:
and Skia Graphite resource ownership
and Direct2D performance guidance
and texture cache
Parley
The detailed adopted/adapted/rejected analysis and reproducible measurements are
in
docs/SAMPLE_MEMORY_OPTIMIZATION.md.