Skip to content

perf: Improve font drawing performance#978

Merged
daveallie merged 7 commits intocrosspoint-reader:masterfrom
jpirnay:perf-uifonts
Feb 20, 2026
Merged

perf: Improve font drawing performance#978
daveallie merged 7 commits intocrosspoint-reader:masterfrom
jpirnay:perf-uifonts

Conversation

@jpirnay
Copy link
Contributor

@jpirnay jpirnay commented Feb 18, 2026

Summary

  • renderChar checked is2Bit on every pixel inside the inner loop, even though the value is constant for the lifetime of a single glyph
  • Moved the branch above both loops so each path (2-bit antialiased / 1-bit monochrome) runs without a per-pixel conditional
  • Eliminates redundant work in the two inner loops that render font glyphs to the frame buffer, targeting renderChar and drawTextRotated90CW in GfxRenderer.cpp

Additional Context

  • Measured on device using a dedicated framebuffer benchmark (no display refresh). 100 repetitions of "The quick brown fox jumps".
Test Before After Change
drawText UI12 1,337 µs/call 1,024 µs/call −23%
drawText Bookerly14 2.174 µs / call 1,847 µs/call −15%

AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? PARTIALLY Claude did the analysis and wrote the benchmarks

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 18, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Refactored GfxRenderer's character rendering to use a template-based approach via renderCharImpl<TextRotation> enum, consolidating bitmap decoding and color mapping logic for both normal and 90° rotated text. Updated renderChar public API signature to accept non-const int* y parameter, replacing duplicate per-orientation rendering logic with a unified templated implementation.

Changes

Cohort / File(s) Summary
Template-based character rendering refactor
lib/GfxRenderer/GfxRenderer.cpp
Introduced templated renderCharImpl<TextRotation> to unify normal and rotated (90° CW) glyph rendering paths; consolidated bitmap decoding (2-bit and 1-bit) and color mapping logic. Updated renderChar public signature to use non-const int* y parameter. Simplified drawTextRotated90CW by delegating per-character rendering to templated implementation. Updated drawText to use non-const yPos for cursor mutation during rendering. Net change: +114/-129 lines.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • ngxson
  • osteotek
  • daveallie
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'perf: Improve font drawing performance' accurately summarizes the main change, which is optimizing the font rendering performance by eliminating redundant per-pixel conditionals.
Description check ✅ Passed The description clearly explains the optimization (moving a constant branch above inner loops), provides specific implementation details, includes performance benchmarks with measured improvements, and documents AI usage transparently.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@jpirnay jpirnay changed the title perf: Improve 2bit font drawing performance perf: Improve font drawing performance Feb 18, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad3759a and a6cd356.

📒 Files selected for processing (2)
  • lib/GfxRenderer/GfxRenderer.cpp
  • lib/GfxRenderer/GfxRenderer.h
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: cppcheck
  • GitHub Check: build
🔇 Additional comments (5)
lib/GfxRenderer/GfxRenderer.h (1)

39-40: LGTM!

The signature change from const int* to int* aligns with the new template-based implementation. Since renderChar is a private method, this change has no external API impact.

lib/GfxRenderer/GfxRenderer.cpp (4)

50-154: Good use of templates for compile-time optimization.

The if constexpr approach with TextRotation enum effectively eliminates runtime branching for rotation handling. Hoisting the is2Bit check outside the pixel loops (line 90 vs. inner loop) is the key optimization that achieves the reported 11-22% performance improvement.


200-226: LGTM!

The drawText function correctly delegates to the template-based rendering path. The removal of const from yPos aligns with the signature change, though the value remains unmodified in the normal rendering path.


874-901: Clean refactoring of rotated text rendering.

Replacing the inline rendering logic with a call to renderCharImpl<TextRotation::Rotated90CW> consolidates the implementation and ensures both normal and rotated paths share the same glyph rendering logic.


1006-1009: LGTM!

Clean delegation to the template implementation for the normal rendering path.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/GfxRenderer/GfxRenderer.cpp`:
- Line 109: The expression computing bmpVal is ambiguous due to operator
precedence: change the calculation so the 2-bit value is masked first and then
subtracted from 3; e.g., compute the extracted bits as (byte >> bit_index) & 0x3
(or parenthesize directly: 3 - ((byte >> bit_index) & 0x3)) and assign bmpVal
from that to make the intent explicit in GfxRenderer.cpp (variable bmpVal).

@jpirnay jpirnay requested a review from znelson February 19, 2026 06:48
znelson
znelson previously approved these changes Feb 19, 2026
daveallie
daveallie previously approved these changes Feb 19, 2026
ngxson
ngxson previously approved these changes Feb 19, 2026
Copy link
Contributor

@ngxson ngxson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about templating rotation too, but was a bit worry because it will take more flash space. Hopefully with #951, we will be able to apply the same optimization to other places

@jpirnay jpirnay dismissed stale reviews from ngxson, daveallie, and znelson via 2185e6c February 19, 2026 18:40
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a8bfa8 and 2185e6c.

📒 Files selected for processing (1)
  • lib/GfxRenderer/GfxRenderer.cpp
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build
  • GitHub Check: cppcheck
🔇 Additional comments (3)
lib/GfxRenderer/GfxRenderer.cpp (3)

212-216: LGTM: mutable y cursor aligns with new renderChar signature.


919-930: LGTM: rotated path now shares the optimized glyph renderer.


994-1005: No review notes for this change.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/GfxRenderer/GfxRenderer.cpp`:
- Around line 1029-1032: The implementation of renderChar has a mismatched
parameter type for y; update the function signature in GfxRenderer::renderChar
to use const int* y (matching the header declaration) so it matches the declared
interface and the call to renderCharImpl<TextRotation::None>(*this, renderMode,
fontFamily, cp, x, y, pixelState, style); ensure the parameter name and const
qualifier exactly match the header's declaration for renderChar to avoid
linkage/compilation errors.
- Around line 80-90: renderCharImpl currently reads the glyph bitmap directly
via fontData->bitmap (using offset) which bypasses compressed-font handling;
replace that logic by calling renderer.getGlyphBitmap(fontData, glyph), check
for a null return and return early if null, and then use the returned pointer
for subsequent rendering so compressed fonts (fontData->groups) are handled
correctly.

@daveallie daveallie merged commit 07d715e into crosspoint-reader:master Feb 20, 2026
6 checks passed
daveallie pushed a commit that referenced this pull request Feb 20, 2026
## Summary

* ``renderChar`` checked ``is2Bit`` on every pixel inside the inner
loop, even though the value is constant for the lifetime of a single
glyph
* Moved the branch above both loops so each path (2-bit antialiased /
1-bit monochrome) runs without a per-pixel conditional
* Eliminates redundant work in the two inner loops that render font
glyphs to the frame buffer, targeting ``renderChar`` and
``drawTextRotated90CW`` in ``GfxRenderer.cpp``

## Additional Context
* Measured on device using a dedicated framebuffer benchmark (no display
refresh). 100 repetitions of "The quick brown fox jumps".

| Test            | Before          | After           | Change  |
|-----------------|-----------------|-----------------|---------|
| drawText UI12   | 1,337 µs/call | 1,024 µs/call | −23%|
| drawText Bookerly14 | 2.174 µs / call | 1,847 µs/call | −15% |

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**PARTIALLY**_ Claude did
the analysis and wrote the benchmarks
@jpirnay jpirnay deleted the perf-uifonts branch February 20, 2026 09:26
lukestein pushed a commit to lukestein/crosspoint-reader that referenced this pull request Feb 20, 2026
## Summary

* ``renderChar`` checked ``is2Bit`` on every pixel inside the inner
loop, even though the value is constant for the lifetime of a single
glyph
* Moved the branch above both loops so each path (2-bit antialiased /
1-bit monochrome) runs without a per-pixel conditional
* Eliminates redundant work in the two inner loops that render font
glyphs to the frame buffer, targeting ``renderChar`` and
``drawTextRotated90CW`` in ``GfxRenderer.cpp``

## Additional Context
* Measured on device using a dedicated framebuffer benchmark (no display
refresh). 100 repetitions of "The quick brown fox jumps".

| Test            | Before          | After           | Change  |
|-----------------|-----------------|-----------------|---------|
| drawText UI12   | 1,337 µs/call | 1,024 µs/call | −23%|
| drawText Bookerly14 | 2.174 µs / call | 1,847 µs/call | −15% |

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**PARTIALLY**_ Claude did
the analysis and wrote the benchmarks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants