aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/modules/sound/Osd.qml
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-29 12:12:35 +0100
committerDanilo M. <danix@danix.xyz>2026-10-29 12:12:35 +0100
commit4d1c5acf5748b9a9b9b40cfb6ee1118593bacecd (patch)
tree2f1e9d8b41100fe500ee0458248f99d882339e08 /desktop/modules/sound/Osd.qml
parent0a7db3b48cce5f064c20f280e345a8bf1feb5ecb (diff)
downloadquickshell-4d1c5acf5748b9a9b9b40cfb6ee1118593bacecd.tar.gz
quickshell-4d1c5acf5748b9a9b9b40cfb6ee1118593bacecd.zip
sound: drop the OSD's canvas grain, it broke Qt's garbage collector
The desktop shell's recurring 100% CPU spin is a Qt bug set off by the volume OSD. Its grain Canvas called createImageData on every paint, and Qt 6.11 subtracts that pixel buffer from the V4 unmanaged-heap counter without having added it. The first GC after an OSD show wrapped the size_t counter, and from then on every JS allocation ran a full GC until a restart. The OSD shows on volume keys, track changes and resume, which is why the spin followed all three. Found by running each always-on unit of the desktop in its own qs with GC stats: only the sound module wrapped, 3 s after a resume, and a volume nudge reproduced it on demand. Qt's qml tool with a bare Canvas confirms it is createImageData alone: 17,856 of 17,856 GCs wrapped, the same canvas drawing with fillRect 0 of 5. With the grain removed, 8 OSD shows under forced GCs left the counter intact. The grain was alpha 14/255, so the OSD looks nearly the same. vram.sh's comment blamed the fdinfo parse for the underflow; corrected, and the lesson is in AGENTS.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'desktop/modules/sound/Osd.qml')
-rw-r--r--desktop/modules/sound/Osd.qml33
1 files changed, 4 insertions, 29 deletions
diff --git a/desktop/modules/sound/Osd.qml b/desktop/modules/sound/Osd.qml
index f60f5ce..e66d559 100644
--- a/desktop/modules/sound/Osd.qml
+++ b/desktop/modules/sound/Osd.qml
@@ -186,9 +186,10 @@ Scope {
color: Qt.alpha(Theme.base, 0.82)
}
- // Frost on the scrim, faked: a faint top-down sheen and fixed
- // grain. The art is opaque, so the compositor's frosting
- // cannot show here without costing the scrim its legibility.
+ // Frost on the scrim, faked: a faint top-down sheen. The art is
+ // opaque, so the compositor's frosting cannot show here
+ // without costing the scrim its legibility. No grain: see
+ // createImageData in AGENTS.md.
Rectangle {
anchors.fill: parent
radius: bgMask.radius
@@ -197,32 +198,6 @@ Scope {
GradientStop { position: 0.6; color: "transparent" }
}
}
-
- // ponytail: grain painted once with Canvas; a ShaderEffect
- // would animate or scale better but needs a compiled .qsb.
- Canvas {
- anchors.fill: parent
- layer.enabled: true
- layer.effect: MultiEffect {
- maskEnabled: true
- maskSource: bgMask
- maskThresholdMin: 0.5
- maskSpreadAtMin: 1.0
- }
- onPaint: {
- if (width <= 0 || height <= 0) return;
- const ctx = getContext("2d");
- const img = ctx.createImageData(width, height);
- for (let i = 0; i < img.data.length; i += 4) {
- const v = Math.random() * 255;
- img.data[i] = v;
- img.data[i + 1] = v;
- img.data[i + 2] = v;
- img.data[i + 3] = 14;
- }
- ctx.putImageData(img, 0, 0);
- }
- }
}
HoverHandler {