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
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
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
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
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
Milestone
No milestone
Development
Successfully merging this pull request may close these issues.
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.
Description
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.
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.