Skip to content

perf(ui): cache paint state across widgets and add missing pyqtSlot coverage - #245

Open
gmmcosta15 wants to merge 17 commits into
devfrom
perf/button-paint-loop-fix
Open

perf(ui): cache paint state across widgets and add missing pyqtSlot coverage#245
gmmcosta15 wants to merge 17 commits into
devfrom
perf/button-paint-loop-fix

Conversation

@gmmcosta15

@gmmcosta15 gmmcosta15 commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Description

  • Feature
  • Bug fix
  • Code refactor
  • Documentation

Eliminates repeated per-frame CPU work in the paint-hot paths and event dispatchers.Measured on target hardware: paintEvent CPU down 57% (py-spy, 300s at 100Hz on RF50 / Raspberry Pi 5 aarch64, 160 -> 69 samples, 6.0 sigma, replicated), or 0.53% -> 0.23% of one core.

  • Cache paint state (QColor, QPen, QBrush, QFont, QFontMetrics, QPainterPath, scaled icons) as instance or module state instead of rebuilding it inside paintEvent, across the button, label, slider, progressbar, toggle, list and popup widget sets plus screensaver and list_model.
  • Replace 27 forced repaint() calls with update() where a synchronous flush was not required.
  • Region-limit the load spinner to its rotation-invariant arc rect, update(QRect) instead of full-widget update(), about 22x fewer dirty pixels per frame at 60 FPS.
  • Bypass SIP enum lookups in per-event and per-paint code paths.
  • Drop dead paint work, for example a pen set before drawPixmap, which ignores it.
  • O(1) file key lookup on the files page, previously a linear scan.
  • Fix the toggle button repaint loop (paint triggering its own invalidation).
  • Fix the toggle trail QPainterPath not being rebuilt on resize, a pre-existing bug surfaced by the new tests.
  • Add 91 @pyqtSlot decorators across 29 files, each signature resolved from the emitting pyqtSignal declaration.
  • Add a 562-line paint cache-invalidation oracle under tests/paint/.

Motivation

py-spy profiling on the target Raspberry Pi 5 showed paintEvent as the largest named Python frame while the UI sits idle (160 of 628 active samples). Those frames rebuilt QColor/QPen/QFont/QPainterPath objects and rescaled pixmaps that never change between frames. Smaller offenders in the same class (eventFilter, list-model SIP crossings, forced repaint()) were fixed in the same pass. No behavioural changes.

@gmmcosta15 gmmcosta15 added enhancement New feature or request. MajorUpdate An update that introduces major changes overall codeto the Refactor Enhancing code's readability, maintainability, and extensibility while addressing technical debt. labels Jun 25, 2026
@gmmcosta15 gmmcosta15 self-assigned this Jun 25, 2026
@gmmcosta15
gmmcosta15 requested a review from HugoCLSC June 25, 2026 09:27
@gmmcosta15
gmmcosta15 marked this pull request as draft July 3, 2026 15:31
@gmmcosta15 gmmcosta15 changed the title perf: eliminate per-frame allocations across all paint-hot paths and event dispatchers perf(ui): cache paint state across widgets and add missing pyqtSlot coverage Aug 24, 2026
@gmmcosta15
gmmcosta15 marked this pull request as ready for review August 24, 2026 14:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request. MajorUpdate An update that introduces major changes overall codeto the Refactor Enhancing code's readability, maintainability, and extensibility while addressing technical debt.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant