Repository navigation
DiagnosticTextRenderer: Make numbers monospace (big perf win) - #21860
Conversation
|
You can test this PR using the following package version. |
|
|
@cla-avalonia agree |
|
Splitting this into multiple pieces with the static part shaped once is most likely the biggest performance gain: The changing part is all digits, and these can be laid out with a fixed column width. So in the end, you can calculate the column width and height(FontMetrics.LineSpacing) once. |
But even in this version, the (Unless I'm misunderstanding your message?) |
|
The whole concept of measuring the FPS text each tick is just wrong. Reserve space for the maximum number of digits and only redraw the digits that are changing each tick. Everything is fixed with that approach. So you never need to work with strings. |
|
I re-ran the numbers (CPU and GPU clocks are fixed now, which makes it slightly easier to get a proper feel for averages) Baseline: avg ~50 FPS Since the Or were you referring to a different sort of change? Apologies if I'm misunderstanding something; this is my first time working with Avalonia, and C# for that matter. (My role in the new Driver Station project has been focused on getting a stripped-down Linux build running on the Driver Hub to run the DS software, but at this point I've already exhausted basically every OS-level optimization possible and the DS app is still significantly low framerate even when just e.g. scrolling the settings tab with the touchscreen, so I decided to start investigating potential performance fixes in Avalonia itself to hopefully get performance ultimately up-to-par with what teams will expect based on the old Android-based DS that was generally quite smooth.) |
|
Your implementation uses the wrong cell width for centering. In DrawAsciiText the cell is maxNumberCharGlyphWidth, but the centering is computed against maxCharGlyphWidth, which is the widest printable ASCII glyph (@, W, or %), not the widest digit. So every digit is drawn well to the right of its cell while the advance stays at the digit width. Digits overlap the following glyph, and the last one hangs outside the background rect that MeasureAsciiText sized. FrameTimeGraph's Min/Avg/Max labels go through the same path and get the same corruption. The fix is to center against the digit cell, after which maxCharGlyphWidth is unused and can go. All this is font-dependent, and ideally we embed a font that only contains the needed glyphs so we control what font is used. The height computation of the renderer can be simplified. Compute it directly in the constructor from glyphTypeface.Metrics.LineSpacing and skip touching the runs at all. The one optimization I mentioned before is still valid but is not required to be able to merge this PR. Splitting the line into a static part shaped once plus a fixed digit grid. |
…m metrics - Digits were centered against the widest printable ASCII glyph instead of the digit cell, so each digit was drawn right of its cell, overlapped the next glyph and the last one left the background rect. Center against the digit cell and drop the unused max glyph width. - Compute the line height once from the font metrics (LineSpacing scaled to the em size) instead of scanning glyph run bounds, which already resolve to that value. - Add tests for fixed digit width, cell centering, stable trailing text and the metrics-based height.
- Shape the static labels once into single glyph runs and draw the frame number, FPS and visual counts as zero-padded digits in fixed cells. A frame no longer formats a string or measures text. - Rebuild the memory text only when the pool sizes change, and compute the background rect width once. - RenderFps takes the raw memory and visual counts instead of a preformatted string. Counters now have a fixed width (frame 8 digits, FPS 3, visual counts 5) and clamp instead of growing. - Hoist the translucent layer brush into a static field.
NuGet packagesThe packages for this pull request have been published (workflow run):
|
…iaUI#21860) * DiagnosticTextRenderer: Make numbers monospace * Center diagnostic digits in their own cell and derive line height from metrics - Digits were centered against the widest printable ASCII glyph instead of the digit cell, so each digit was drawn right of its cell, overlapped the next glyph and the last one left the background rect. Center against the digit cell and drop the unused max glyph width. - Compute the line height once from the font metrics (LineSpacing scaled to the em size) instead of scanning glyph run bounds, which already resolve to that value. - Add tests for fixed digit width, cell centering, stable trailing text and the metrics-based height. * Draw the FPS line from shaped labels and a fixed digit grid - Shape the static labels once into single glyph runs and draw the frame number, FPS and visual counts as zero-padded digits in fixed cells. A frame no longer formats a string or measures text. - Rebuild the memory text only when the pool sizes change, and compute the background rect width once. - RenderFps takes the raw memory and visual counts instead of a preformatted string. Counters now have a fixed width (frame 8 digits, FPS 3, visual counts 5) and clamp instead of growing. - Hoist the translucent layer brush into a static field. --------- Co-authored-by: Benedikt Stebner <Gillibald@users.noreply.github.com>
* DiagnosticTextRenderer: Make numbers monospace * Center diagnostic digits in their own cell and derive line height from metrics - Digits were centered against the widest printable ASCII glyph instead of the digit cell, so each digit was drawn right of its cell, overlapped the next glyph and the last one left the background rect. Center against the digit cell and drop the unused max glyph width. - Compute the line height once from the font metrics (LineSpacing scaled to the em size) instead of scanning glyph run bounds, which already resolve to that value. - Add tests for fixed digit width, cell centering, stable trailing text and the metrics-based height. * Draw the FPS line from shaped labels and a fixed digit grid - Shape the static labels once into single glyph runs and draw the frame number, FPS and visual counts as zero-padded digits in fixed cells. A frame no longer formats a string or measures text. - Rebuild the memory text only when the pool sizes change, and compute the background rect width once. - RenderFps takes the raw memory and visual counts instead of a preformatted string. Counters now have a fixed width (frame 8 digits, FPS 3, visual counts 5) and clamp instead of growing. - Hoist the translucent layer brush into a static field. --------- Co-authored-by: Benedikt Stebner <Gillibald@users.noreply.github.com>
What does the pull request do?
Makes the FPS overlay cheap to draw and stops its numbers from jumping around.
The overlay formatted and measured a new string every frame, and because digits have different widths, the text shifted as values changed. On a Rockchip PX30 (Cortex-A35, Mali-G31, Linux DRM, NativeAOT, RenderDemo "Spring Animation" page) the first step of this PR, monospaced digits, took the frame rate from the 30-40 range to the 90-100 range.
Changes:
DiagnosticTextRendererlays out digits in equal-width cells (the widest digit) and centers each glyph in its cell. The line height now comes from the font metrics instead of scanning glyph bounds.DiagnosticTextRenderergainsShape(text shaped once into a single glyph run) andDrawDigits(zero-padded number drawn in the digit grid without formatting a string).FpsCountershapes its labels once and draws the frame number, FPS and visual counts withDrawDigits. The memory text is rebuilt only when the pool sizes change, the background width is computed once, and the layer brush is a static field. A frame no longer formats or measures any text.FrameTimeGraphlabels get the digit cells throughDrawAsciiText.What is the updated/expected behavior with this PR?
Open the FPS overlay: numbers stay in place and digits no longer overlap the following text or leave the background rect.
Checklist
Breaking changes
None for public API. The overlay counters now have a fixed width and clamp instead of growing: frame number 8 digits, FPS 3, visited/rendered visuals 5 (was 4, minimum).
Obsoletions / Deprecations
None
Fixed issues
None filed
Assisted-by: Gemini 3.1 Pro
🤖 Generated with Claude Code