From c4280ecf6021e8a1416b3e9bc382c063ddcdbf32 Mon Sep 17 00:00:00 2001 From: rickpeters Date: Sun, 27 Sep 2026 12:48:01 +0200 Subject: [PATCH 1/5] feat: VRAM booster for the Steam Machine --- AGENTS.md | 6 ++++++ CHANGELOG.md | 6 +++++- README.md | 3 +++ TECHNICAL.md | 24 +++++++++++++++++++++ lib/backend.sh | 3 ++- lib/menu.sh | 15 +++++++------ lib/vram-booster.sh | 51 +++++++++++++++++++++++++++++++++++++++++++++ steamify.sh | 4 ++-- ui/qml/Main.qml | 6 +++++- 9 files changed, 107 insertions(+), 11 deletions(-) create mode 100644 lib/vram-booster.sh diff --git a/AGENTS.md b/AGENTS.md index 63d2afd..63aa7f1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -231,6 +231,12 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine `linux-cachyos` is clang-built, `-bore` GCC-built: let DKMS pick the compiler, never pass `LLVM=1`. Test shutdown on the real machine for every new major kernel. +- VRAM booster (`vram`, `lib/vram-booster.sh`, a default sub-option of + `machine`, 2.3.0): CachyOS's `dmemcg-booster` (system + user service) and + `plasma-foreground-booster`, like SteamOS 3.9's VRAM management. The + latter only starts with `kcgroupsrc [Foreground Booster] autostart=true` + (set with `kset`). Disable removes only the packages it installed + (`state_get vram installed_pkgs`). - `Relogin=true` means a gamescope that fails to start is relaunched in a tight loop; keep that in mind when changing session handling. diff --git a/CHANGELOG.md b/CHANGELOG.md index f92b5fd..adbbaf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,13 @@ All notable changes, per version and per commit. Versions follow `steamify.sh`. Versions before 0.7.0 were numbered afterwards, one per merged pull request. +## 2.3.0 - 2026-09-27 + +- **feat: VRAM booster for the Steam Machine** + ## 2.2.0 - 2026-09-27 -- **docs: Changelog entries per commit for 2.2.0** +- `ef585c0` **docs: Changelog entries per commit for 2.2.0** - `32795bd` **feat(ui): Update badge on the progress screen** - `3c8ca65` **fix(ui): Never write the sudo password to a file; Update label on the progress screen** - The app gave `sudo` the password through a short-lived file; the helper diff --git a/README.md b/README.md index eb08c43..be8b5ff 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,9 @@ What Steamify can do on a Valve Steam Machine: - **Power-off fix** - on by default: shutting down really turns the Steam Machine off, also with the newest CachyOS kernel ([details](TECHNICAL.md#power-off-fix-steam-machine)). + - **VRAM booster** - on by default: the game you're playing keeps its + video memory and background apps make room, like SteamOS 3.9 + ([details](TECHNICAL.md#vram-booster-steam-machine)). - **Update BIOS** - installs Valve's newest Steam Machine BIOS. Never ticked by default, at your own risk, and only after two warnings ([details](TECHNICAL.md#bios-updates-steam-machine)). diff --git a/TECHNICAL.md b/TECHNICAL.md index 2bb7b10..cd75c37 100644 --- a/TECHNICAL.md +++ b/TECHNICAL.md @@ -236,6 +236,29 @@ Where Steam Machine support was set up before 2.2.0, the menu ticks the power-off fix as a new default sub-option (see feature versions below), so a normal run adds it. +## VRAM booster (Steam Machine) + +SteamOS 3.9 manages the dGPU's VRAM per cgroup: the game in front is +protected, background apps are evicted first. Without it a game that needs +most of the Steam Machine's 8 GB can be pushed into system RAM by the +desktop and other apps, and stutter. The kernel side is the `dmem` cgroup +controller (7.2); CachyOS packages the userspace side, which the **VRAM +booster** sub-option of Steam Machine support installs (ticked along with +it, can be unticked; new in 2.3.0, so existing setups get it ticked): + +- `dmemcg-booster`: `dmemcg-booster-system.service` enables the `dmem` + controller and sets `dmem.low` for the cgroup in front; + `dmemcg-booster-user.service` runs in the user session. +- `plasma-foreground-booster`: tells it which window is in front on the + desktop. It only starts with `autostart=true` under `[Foreground Booster]` + in `kcgroupsrc`, which the wizard sets (and reverts). + +Check it with `cat /sys/fs/cgroup/cgroup.subtree_control` (lists `dmem`) and +the `dmem.low` of the slices under `user@.service`. Turning it off +disables the services and removes the packages the wizard installed. +Not added: a fixed `dmem.max` cap for `app.slice` (as some guides do); only +worth it if the booster itself turns out to cause stutter. + ## Feature versions (updates) Each component has a feature version (`FEATURE_VERSION` in `lib/menu.sh`): @@ -327,6 +350,7 @@ immediately, which can turn into a loop - see | `lib/cec.sh` | HDMI-CEC: Valve's `cecd` and friends from its `holo` repository | | `lib/steam-machine.sh` | Steam Machine support: LED driver, LED access, steamos-manager | | `lib/fremont-poweroff.sh` | Steam Machine support: the power-off fix (DKMS module from `patches/`) | +| `lib/vram-booster.sh` | Steam Machine support: the VRAM booster (`dmemcg-booster`, `plasma-foreground-booster`) | | `patches/` | Module sources and patches the scripts build or apply (`patch_file`); see its README | | `.github/tools/bundle.sh` | Builds the single-file version (`dist/steamify.sh`), with `patches/` embedded | | `.github/workflows/bundle.yml` | Builds and checks it on every push; publishes it on `main` | diff --git a/lib/backend.sh b/lib/backend.sh index da6e867..fdd0241 100644 --- a/lib/backend.sh +++ b/lib/backend.sh @@ -162,7 +162,8 @@ backend_apply() { [[ "${WANTED[single]:-0}" == 1 ]] && WANTED[gaming]=1 [[ "${WANTED[gaming]:-0}" == 0 ]] && { WANTED[single]=0; WANTED[boot]=0; } [[ "${WANTED[poweroff]:-0}" == 1 ]] && WANTED[machine]=1 - [[ "${WANTED[machine]:-0}" == 0 ]] && WANTED[poweroff]=0 + [[ "${WANTED[vram]:-0}" == 1 ]] && WANTED[machine]=1 + [[ "${WANTED[machine]:-0}" == 0 ]] && { WANTED[poweroff]=0; WANTED[vram]=0; } [[ "${WANTED[kpin]:-0}" == 1 ]] && WANTED[machine]=1 [[ "${WANTED[machine]:-0}" == 0 ]] && WANTED[kpin]=0 [[ "${WANTED[hdmi]:-0}" == 1 ]] && { WANTED[machine]=1; WANTED[kpin]=1; } diff --git a/lib/menu.sh b/lib/menu.sh index 2d8baec..1202536 100644 --- a/lib/menu.sh +++ b/lib/menu.sh @@ -5,12 +5,12 @@ # Menu order. Components are turned on in this order and off in reverse; # gaming must come first (single user builds on it). -COMPONENTS=(gaming boot theme glyphs single launcher cec machine poweroff kpin hdmi bios) +COMPONENTS=(gaming boot theme glyphs single launcher cec machine poweroff vram kpin hdmi bios) # One-off actions rather than on/off components: never preselected, never # re-applied, not listed as on or off. ACTIONS=(bios) # Sub-options, shown indented under their parent and only while it's ticked. -declare -A PARENT=([boot]=gaming [poweroff]=machine [kpin]=machine [hdmi]=machine [bios]=machine) +declare -A PARENT=([boot]=gaming [poweroff]=machine [vram]=machine [kpin]=machine [hdmi]=machine [bios]=machine) # Never preselected on a first run: booting into the desktop is a choice, # gamescope is the default; HDMI-CEC is opt-in (it can wake the machine or # upset other devices on the TV, even on SteamOS), except on a Steam Machine, @@ -26,7 +26,7 @@ NO_PRESELECT=(boot cec kpin hdmi) FEATURE_BASELINE=2.1.0 declare -A FEATURE_VERSION=( [gaming]=2.1.0 [boot]=2.1.0 [theme]=2.1.0 [glyphs]=2.1.0 [single]=2.1.0 - [launcher]=2.1.0 [cec]=2.1.0 [machine]=2.2.0 [poweroff]=2.2.0 + [launcher]=2.1.0 [cec]=2.1.0 [machine]=2.2.0 [poweroff]=2.2.0 [vram]=2.3.0 [kpin]=2.1.0 [hdmi]=2.1.0 ) @@ -40,6 +40,7 @@ declare -A LABEL=( [cec]="HDMI-CEC: use Steam with the TV remote, TV on/off with the PC (experimental)" [machine]="Steam Machine support: LED bar driver, hardware settings in Steam" [poweroff]="Power-off fix: the Steam Machine stays off after shutting down" + [vram]="VRAM booster: the game in front keeps its VRAM, background apps make room" [kpin]="Pin the kernel to $PINNED_KERNEL_VER (untick for CachyOS's current kernel)" [hdmi]="HDMI refresh boost: highest refresh your HDMI display runs" [bios]="Update BIOS" @@ -48,7 +49,7 @@ declare -A CURRENT WANTED component_available() { case "$1" in - machine|poweroff) machine_available ;; + machine|poweroff|vram) machine_available ;; kpin) kpin_available ;; hdmi) hdmi_available ;; bios) bios_available ;; @@ -157,9 +158,11 @@ toggle_component() { if [[ "$c" == gaming && "${WANTED[gaming]}" == 0 ]]; then WANTED[single]=0; WANTED[boot]=0; fi # Where to boot to is part of the conversion, too. if [[ "$c" == boot && "${WANTED[boot]}" == 1 ]]; then WANTED[gaming]=1; fi - # The power-off fix is opt-out: ticked along with Steam Machine support. - if [[ "$c" == machine ]]; then WANTED[poweroff]=${WANTED[machine]}; fi + # The power-off fix and VRAM booster are opt-out: ticked along with + # Steam Machine support. + if [[ "$c" == machine ]]; then WANTED[poweroff]=${WANTED[machine]}; WANTED[vram]=${WANTED[machine]}; fi if [[ "$c" == poweroff && "${WANTED[poweroff]}" == 1 ]]; then WANTED[machine]=1; fi + if [[ "$c" == vram && "${WANTED[vram]}" == 1 ]]; then WANTED[machine]=1; fi if [[ "$c" == machine && "${WANTED[machine]}" == 0 ]]; then WANTED[kpin]=0; fi if [[ "$c" == kpin && "${WANTED[kpin]}" == 1 ]]; then WANTED[machine]=1; fi # HDMI refresh boost and the BIOS update sit under Steam Machine support; diff --git a/lib/vram-booster.sh b/lib/vram-booster.sh new file mode 100644 index 0000000..4815df1 --- /dev/null +++ b/lib/vram-booster.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# "VRAM booster" menu item (vram), a sub-option of Steam Machine support +# ticked along with it (opt-out), like SteamOS 3.9's dGPU VRAM management: +# the game in front keeps its VRAM, background apps are evicted first, so a +# game on the 8 GB GPU doesn't spill into system RAM and stutter. The kernel +# side is the dmem cgroup controller (7.2); CachyOS packages the userspace +# side: dmemcg-booster (a system service that enables the controller and +# sets the limits, plus a user service) and plasma-foreground-booster, which +# tells it which window is in front on the desktop. The latter only starts +# with "autostart" set in kcgroupsrc. +# Sourced by steamify.sh; not meant to be run on its own. + +VRAM_PKGS=(dmemcg-booster plasma-foreground-booster) + +vram_status() { + pacman -Q "${VRAM_PKGS[@]}" >/dev/null 2>&1 && + systemctl is-enabled -q dmemcg-booster-system.service 2>/dev/null && + systemctl --user is-enabled -q dmemcg-booster-user.service 2>/dev/null +} + +vram_enable() { + local p missing=() + info "Installing the VRAM booster (the game in front keeps its VRAM)..." + for p in "${VRAM_PKGS[@]}"; do pacman -Q "$p" >/dev/null 2>&1 || missing+=("$p"); done + if ((${#missing[@]})); then + sudo pacman -S --needed --noconfirm "${missing[@]}" || + { err "Installing ${missing[*]} failed."; return 1; } + state_set vram installed_pkgs "${missing[*]}" + fi + sudo systemctl enable --now dmemcg-booster-system.service >/dev/null 2>&1 || + { err "Starting dmemcg-booster failed."; return 1; } + systemctl --user enable --now dmemcg-booster-user.service >/dev/null 2>&1 || + { err "Starting dmemcg-booster for $USER failed."; return 1; } + kset vram kcgroupsrc "Foreground Booster" autostart true + # Started by the Plasma session from now on; start it in this one too. + [[ "${XDG_CURRENT_DESKTOP:-}" == KDE ]] && + systemctl --user start plasma-foreground-booster.service >/dev/null 2>&1 + ok "VRAM booster on: the game in front keeps its VRAM." +} + +vram_disable() { + local pkgs + systemctl --user disable --now plasma-foreground-booster.service dmemcg-booster-user.service >/dev/null 2>&1 + sudo systemctl disable --now dmemcg-booster-system.service >/dev/null 2>&1 + krevert vram + pkgs="$(state_get vram installed_pkgs)" + # shellcheck disable=SC2086 # a list of package names + [[ -n "$pkgs" ]] && sudo pacman -Rns --noconfirm $pkgs >/dev/null 2>&1 + state_clear vram + ok "VRAM booster off." +} diff --git a/steamify.sh b/steamify.sh index ea40854..971b826 100755 --- a/steamify.sh +++ b/steamify.sh @@ -15,11 +15,11 @@ set -uo pipefail # Release version, see CHANGELOG.md. -VERSION=2.2.0 +VERSION=2.3.0 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -for lib in common state packages login-manager single-user steam-desktop steam-machine fremont-poweroff hdmi-refresh cec boot-session vapor-theme steamos-extras bios desktop-shortcut wizard-shortcut menu backend; do +for lib in common state packages login-manager single-user steam-desktop steam-machine fremont-poweroff vram-booster hdmi-refresh cec boot-session vapor-theme steamos-extras bios desktop-shortcut wizard-shortcut menu backend; do # shellcheck source=/dev/null source "$SCRIPT_DIR/lib/$lib.sh" done diff --git a/ui/qml/Main.qml b/ui/qml/Main.qml index 9f2f700..4f3d0dd 100644 --- a/ui/qml/Main.qml +++ b/ui/qml/Main.qml @@ -72,6 +72,9 @@ ApplicationWindow { poweroff: { label: "Power-off fix", hint: "Stays off after shutting down", body: "With recent kernels the Steam Machine starts again right after shutting down: the firmware leaves a wake bit set, and newer kernels (7.2, and updates of 6.x, 7.0 and 7.1) no longer clear it. Valve's own kernel clears it; this small module does the same right before power-off.", changes: ["steamify-fremont-poweroff module for each installed kernel (DKMS)", "Only on a Steam Machine, only touches that one wake bit", "Off: recent kernels may start it again after shutting down"] }, + vram: { label: "VRAM booster", hint: "The game in front keeps its VRAM", + body: "Like SteamOS 3.9: the game you're playing keeps its video memory, and background apps are moved out first. Without it a game that needs most of the 8 GB can spill into system RAM and stutter.", + changes: ["dmemcg-booster (system and user service)", "plasma-foreground-booster, which tells it which window is in front", "Off: removed again, unless you had installed them yourself"] }, kpin: { label: "Pin the kernel", hint: "Untick for CachyOS's current kernel", body: "Keeps the Steam Machine on CachyOS kernel 7.1.6. Untick it to go back to CachyOS's current kernel; HDMI refresh boost goes with it.", changes: ["linux-cachyos from Steamify's release (signature checked)", "Kept in /var/cache/steamify/kernel", "Added to IgnorePkg"] }, @@ -250,8 +253,9 @@ ApplicationWindow { if (id === "hdmi" && !hdmiChoice && hdmiSaved.length) { openHdmiList(); return; } if (id === "hdmi" && w.hdmi && !nowOn("hdmi") && !hdmiChoice) { startHdmi(); return; } if (id === "hdmi" && !w.hdmi) hdmiChoice = ""; - if (id === "machine") w.poweroff = w.machine; + if (id === "machine") { w.poweroff = w.machine; w.vram = w.machine; } if (id === "poweroff" && w.poweroff) w.machine = true; + if (id === "vram" && w.vram) w.machine = true; if (id === "machine" && !w.machine) w.kpin = false; if (id === "kpin" && w.kpin) w.machine = true; if (id === "hdmi" && w.hdmi) { w.machine = true; w.kpin = true; } From 64536fe531f5582d878039938473c9a026dcbc8d Mon Sep 17 00:00:00 2001 From: rickpeters Date: Sun, 27 Sep 2026 12:49:49 +0200 Subject: [PATCH 2/5] feat: New badge for default sub-options added in this version --- AGENTS.md | 3 ++- lib/backend.sh | 1 + lib/menu.sh | 13 ++++++++----- steamify.sh | 4 +++- ui/qml/Main.qml | 12 +++++++----- 5 files changed, 21 insertions(+), 12 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 63aa7f1..14e0ca6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,7 +33,8 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine `VERSION` whenever what `_enable` sets up changes (a new component gets one too): installs recorded with an older version are ticked and re-applied ("update" in the plan and the app). New default - sub-options are ticked for installs whose parent is on (`feature_new`). + sub-options are ticked for installs whose parent is on (`feature_new`, + shown as "new"). Status still comes from the system; don't add ad-hoc `_repair` checks for new changes. - **Reversibility.** Every per-user KDE setting a component changes goes diff --git a/lib/backend.sh b/lib/backend.sh index fdd0241..76d0177 100644 --- a/lib/backend.sh +++ b/lib/backend.sh @@ -63,6 +63,7 @@ backend_status() { items+=",\"on\":$( [[ "$now" == 1 ]] && echo true || echo false)" items+=",\"wanted\":$( [[ "${WANTED[$c]:-0}" == 1 ]] && echo true || echo false)" items+=",\"update\":$(feature_outdated "$c" && echo true || echo false)" + items+=",\"new\":$(feature_new "$c" && echo true || echo false)" items+=",\"selectable\":$(component_selectable "$c" && echo true || echo false)}" done local cec="" f diff --git a/lib/menu.sh b/lib/menu.sh index 1202536..9c053eb 100644 --- a/lib/menu.sh +++ b/lib/menu.sh @@ -90,11 +90,14 @@ feature_outdated() { menu_label() { # The label, with "(update)" after the name when a newer version of it - # will be applied. - local l="${LABEL[$1]}" - if feature_outdated "$1"; then - if [[ "$l" == *:* ]]; then l="${l%%:*} ${c_yellow}(update)${c_reset}:${l#*:}" - else l+=" ${c_yellow}(update)${c_reset}"; fi + # will be applied, "(new)" for a default sub-option added since the + # last run. + local l="${LABEL[$1]}" b="" + if feature_outdated "$1"; then b="${c_yellow}(update)${c_reset}" + elif feature_new "$1"; then b="${c_green}(new)${c_reset}"; fi + if [[ -n "$b" ]]; then + if [[ "$l" == *:* ]]; then l="${l%%:*} $b:${l#*:}" + else l+=" $b"; fi fi printf '%s' "$l" } diff --git a/steamify.sh b/steamify.sh index 971b826..b34ef37 100755 --- a/steamify.sh +++ b/steamify.sh @@ -140,7 +140,9 @@ while true; do if [[ "$c" == boot ]]; then echo " - boot into: desktop (from the next boot)" elif is_action "$c"; then echo " - run: ${LABEL[$c]%%:*} at your own risk (checks, then asks twice more)" elif feature_outdated "$c"; then echo " - update: ${LABEL[$c]} (changed in this version)" - elif [[ "${CURRENT[$c]}" == 1 ]]; then echo " - re-apply: ${LABEL[$c]}"; else echo " - turn on: ${LABEL[$c]}"; fi + elif [[ "${CURRENT[$c]}" == 1 ]]; then echo " - re-apply: ${LABEL[$c]}" + elif feature_new "$c"; then echo " - turn on: ${LABEL[$c]} (new in this version)" + else echo " - turn on: ${LABEL[$c]}"; fi done ask_yn "Go ahead?" y || { info "Nothing changed."; continue; } diff --git a/ui/qml/Main.qml b/ui/qml/Main.qml index 4f3d0dd..2ca5d8b 100644 --- a/ui/qml/Main.qml +++ b/ui/qml/Main.qml @@ -268,7 +268,7 @@ ApplicationWindow { var it = items[i]; if (it.kind !== "toggle") continue; if (it.parent && !want[it.parent]) { if (it.on) p.push({ id: it.id, action: "off" }); continue; } - if (want[it.id] && !it.on) p.push({ id: it.id, action: "on" }); + if (want[it.id] && !it.on) p.push({ id: it.id, action: "on", isNew: !!it["new"] }); else if (!want[it.id] && it.on) p.push({ id: it.id, action: "off" }); else if (want[it.id] && (reapply || it.update)) p.push({ id: it.id, action: reapply ? "again" : "update" }); } @@ -631,11 +631,12 @@ ApplicationWindow { x: row.modelData.parent ? 46 : 16 width: controls.x - x - 16 opacity: row.modelData.kind === "action" && !(bios && bios.selectable) ? 0.6 : 1 - // The name, with an Update badge when a newer version of it will be applied. + // The name, with an Update badge when a newer version of it will be + // applied, New for a default sub-option added since the last run. Row { width: parent.width; spacing: 10 Text { text: label(row.modelData); color: t.textHi; font.family: t.body; font.pixelSize: 17; font.weight: Font.DemiBold; elide: Text.ElideRight width: Math.min(implicitWidth, parent.width - (upd.visible ? upd.width + parent.spacing : 0)) } - Chip { id: upd; visible: !!row.modelData.update; text: "Update"; fg: t.warn; bgc: t.warnBg; height: 20; anchors.verticalCenter: parent.verticalCenter } } + Chip { id: upd; visible: !!row.modelData.update || !!row.modelData["new"]; text: row.modelData.update ? "Update" : "New"; fg: row.modelData.update ? t.warn : t.good; bgc: row.modelData.update ? t.warnBg : t.goodBg; height: 20; anchors.verticalCenter: parent.verticalCenter } } Text { text: row.modelData.id === "bios" ? biosHint() : ((row.modelData.id === "hdmi" && hdmiRowHint()) || (texts[row.modelData.id] && texts[row.modelData.id].hint) || row.modelData.hint); color: t.mute; font.family: t.body; font.pixelSize: 13; elide: Text.ElideRight; width: parent.width } } // Right: every control ends on the same edge @@ -767,6 +768,7 @@ ApplicationWindow { Row { anchors.fill: parent; anchors.leftMargin: 18; spacing: 14 Chip { text: parent.parent.st[0]; fg: parent.parent.st[1]; bgc: parent.parent.st[2]; width: 84; anchors.verticalCenter: parent.verticalCenter } Text { text: (texts[parent.parent.modelData.id] || {}).label || parent.parent.modelData.id; color: t.textHi; font.family: t.body; font.pixelSize: 17; font.weight: Font.DemiBold; anchors.verticalCenter: parent.verticalCenter } + Chip { visible: !!parent.parent.modelData.isNew; text: "New"; fg: t.good; bgc: t.goodBg; height: 20; anchors.verticalCenter: parent.verticalCenter } } } } @@ -862,7 +864,7 @@ ApplicationWindow { border.width: parent.parent.st === "wait" || parent.parent.st === "run" ? 2 : 0; border.color: parent.parent.st === "run" ? t.accent : "#343f50" Text { anchors.centerIn: parent; text: parent.parent.parent.st === "ok" ? "✓" : (parent.parent.parent.st === "fail" ? "!" : ""); color: parent.parent.parent.st === "ok" ? t.good : t.bad; font.pixelSize: 13; font.weight: Font.Bold } RotationAnimator on rotation { running: parent.parent.parent.st === "run"; from: 0; to: 360; duration: 1000; loops: Animation.Infinite } } - // The step; an update shows the name with an Update badge, like the menu. + // The step; an update or new sub-option shows its badge, like the menu. Item { width: 380; height: parent.height readonly property var step: parent.parent.modelData readonly property bool update: step.action === "update" @@ -870,7 +872,7 @@ ApplicationWindow { Text { text: (({ on: "Turn on ", off: "Turn off ", again: "Re-apply ", desktop: "Boot into ", gaming: "Boot into ", check: "Download and check the ", flash: "Hand to fwupd: the " })[parent.parent.step.action] || "") + ((texts[parent.parent.step.id] || {}).label || parent.parent.step.id) color: parent.parent.parent.parent.st === "wait" ? t.faint : t.textHi; font.family: t.body; font.pixelSize: 16; font.weight: Font.DemiBold; anchors.verticalCenter: parent.verticalCenter; elide: Text.ElideRight width: Math.min(implicitWidth, parent.width - (stepUpd.visible ? stepUpd.width + parent.spacing : 0)) } - Chip { id: stepUpd; visible: parent.parent.update; text: "Update"; fg: t.warn; bgc: t.warnBg; height: 20; anchors.verticalCenter: parent.verticalCenter } } } + Chip { id: stepUpd; visible: parent.parent.update || !!parent.parent.step.isNew; text: parent.parent.update ? "Update" : "New"; fg: parent.parent.update ? t.warn : t.good; bgc: parent.parent.update ? t.warnBg : t.goodBg; height: 20; anchors.verticalCenter: parent.verticalCenter } } } Text { text: ({ wait: "Waiting", run: "Working…", ok: "Done", fail: "Problem" })[parent.parent.st]; color: parent.parent.st === "ok" ? t.good : (parent.parent.st === "fail" ? t.bad : "#b8c3d1"); font.family: t.body; font.pixelSize: 13; anchors.verticalCenter: parent.verticalCenter } } } From 83dcad1622752f7e8ebbee5e46c6884a9cbdaa03 Mon Sep 17 00:00:00 2001 From: rickpeters Date: Sun, 27 Sep 2026 12:51:47 +0200 Subject: [PATCH 3/5] feat: VRAM booster for every supported GPU, not only the Steam Machine --- AGENTS.md | 9 +++++---- CHANGELOG.md | 4 +++- README.md | 9 +++++---- TECHNICAL.md | 24 ++++++++++++++---------- lib/backend.sh | 3 +-- lib/menu.sh | 23 ++++++++++++----------- lib/vram-booster.sh | 16 +++++++++++----- ui/qml/Main.qml | 5 ++--- 8 files changed, 53 insertions(+), 40 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 14e0ca6..4556f40 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,8 +33,8 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine `VERSION` whenever what `_enable` sets up changes (a new component gets one too): installs recorded with an older version are ticked and re-applied ("update" in the plan and the app). New default - sub-options are ticked for installs whose parent is on (`feature_new`, - shown as "new"). + options are ticked for installs whose parent (top-level: `gaming`) is on + (`feature_new`, shown as "new"). Status still comes from the system; don't add ad-hoc `_repair` checks for new changes. - **Reversibility.** Every per-user KDE setting a component changes goes @@ -232,8 +232,9 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine `linux-cachyos` is clang-built, `-bore` GCC-built: let DKMS pick the compiler, never pass `LLVM=1`. Test shutdown on the real machine for every new major kernel. -- VRAM booster (`vram`, `lib/vram-booster.sh`, a default sub-option of - `machine`, 2.3.0): CachyOS's `dmemcg-booster` (system + user service) and +- VRAM booster (`vram`, `lib/vram-booster.sh`, top-level, ticked by + default, 2.3.0; only available when `/sys/fs/cgroup/dmem.capacity` lists a + region): CachyOS's `dmemcg-booster` (system + user service) and `plasma-foreground-booster`, like SteamOS 3.9's VRAM management. The latter only starts with `kcgroupsrc [Foreground Booster] autostart=true` (set with `kset`). Disable removes only the packages it installed diff --git a/CHANGELOG.md b/CHANGELOG.md index adbbaf3..2eeb285 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ one per merged pull request. ## 2.3.0 - 2026-09-27 -- **feat: VRAM booster for the Steam Machine** +- **feat: VRAM booster for every supported GPU, not only the Steam Machine** +- `fd162c1` **feat: New badge for default sub-options added in this version** +- `54ebddf` **feat: VRAM booster for the Steam Machine** ## 2.2.0 - 2026-09-27 diff --git a/README.md b/README.md index be8b5ff..b4c6e6c 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,11 @@ Steam Machine. 5. **Steamify shortcut** - "Steamify CachyOS" on the desktop and in the launcher opens the newest Steamify app, so you don't need the install command again. "Steamify Terminal" in the launcher opens the terminal menu. -6. **HDMI-CEC** (experimental; off by default, on by default on a Steam +6. **VRAM booster** - on by default: the game you're playing keeps its video + memory and background apps make room, like SteamOS 3.9. Shown for GPUs + whose driver supports it (AMD, Intel) on kernel 7.2 or newer + ([details](TECHNICAL.md#vram-booster)). +7. **HDMI-CEC** (experimental; off by default, on by default on a Steam Machine) - use Steam with your TV's remote, and the TV turns on and off with the PC. Needs a PC with CEC (like the Steam Machine) or a USB CEC adapter; most graphics cards, NVIDIA @@ -48,9 +52,6 @@ What Steamify can do on a Valve Steam Machine: - **Power-off fix** - on by default: shutting down really turns the Steam Machine off, also with the newest CachyOS kernel ([details](TECHNICAL.md#power-off-fix-steam-machine)). - - **VRAM booster** - on by default: the game you're playing keeps its - video memory and background apps make room, like SteamOS 3.9 - ([details](TECHNICAL.md#vram-booster-steam-machine)). - **Update BIOS** - installs Valve's newest Steam Machine BIOS. Never ticked by default, at your own risk, and only after two warnings ([details](TECHNICAL.md#bios-updates-steam-machine)). diff --git a/TECHNICAL.md b/TECHNICAL.md index cd75c37..ca5b3c6 100644 --- a/TECHNICAL.md +++ b/TECHNICAL.md @@ -236,15 +236,17 @@ Where Steam Machine support was set up before 2.2.0, the menu ticks the power-off fix as a new default sub-option (see feature versions below), so a normal run adds it. -## VRAM booster (Steam Machine) +## VRAM booster SteamOS 3.9 manages the dGPU's VRAM per cgroup: the game in front is protected, background apps are evicted first. Without it a game that needs -most of the Steam Machine's 8 GB can be pushed into system RAM by the -desktop and other apps, and stutter. The kernel side is the `dmem` cgroup -controller (7.2); CachyOS packages the userspace side, which the **VRAM -booster** sub-option of Steam Machine support installs (ticked along with -it, can be unticked; new in 2.3.0, so existing setups get it ticked): +most of the VRAM (8 GB on the Steam Machine) can be pushed into system RAM +by the desktop and other apps, and stutter. The kernel side is the `dmem` +cgroup controller (7.2); amdgpu and Intel's xe register their VRAM with it, +NVIDIA's driver doesn't. The **VRAM booster** option is only shown when +`/sys/fs/cgroup/dmem.capacity` lists a region (or it's already on), and is +ticked by default; new in 2.3.0, so setups with the SteamOS conversion on +get it ticked. CachyOS packages the userspace side, which it installs: - `dmemcg-booster`: `dmemcg-booster-system.service` enables the `dmem` controller and sets `dmem.low` for the cgroup in front; @@ -273,9 +275,11 @@ on the system itself; the version only decides about updates: "update". A setup from before versions were recorded counts as 2.1.0, so on a machine set up with 2.1.0, Steam Machine support (2.2.0) is updated. -- **A new default sub-option** (not in `NO_PRESELECT`) whose parent is on - and that was never turned on or off: ticked, so a normal run adds it. - Turning it off once records `off`, so it isn't ticked again. +- **A new default option** (not in `NO_PRESELECT`) whose parent is on (for + a top-level one: the SteamOS conversion) and that was never turned on or + off: ticked, so a normal run adds it. Turning it off once records `off`, + so it isn't ticked again. The menu and the app show it with a **New** + badge, and the plan says "new in this version". Set a component's entry to the new `VERSION` whenever what its `_enable` sets up changes. @@ -350,7 +354,7 @@ immediately, which can turn into a loop - see | `lib/cec.sh` | HDMI-CEC: Valve's `cecd` and friends from its `holo` repository | | `lib/steam-machine.sh` | Steam Machine support: LED driver, LED access, steamos-manager | | `lib/fremont-poweroff.sh` | Steam Machine support: the power-off fix (DKMS module from `patches/`) | -| `lib/vram-booster.sh` | Steam Machine support: the VRAM booster (`dmemcg-booster`, `plasma-foreground-booster`) | +| `lib/vram-booster.sh` | VRAM booster (`dmemcg-booster`, `plasma-foreground-booster`) | | `patches/` | Module sources and patches the scripts build or apply (`patch_file`); see its README | | `.github/tools/bundle.sh` | Builds the single-file version (`dist/steamify.sh`), with `patches/` embedded | | `.github/workflows/bundle.yml` | Builds and checks it on every push; publishes it on `main` | diff --git a/lib/backend.sh b/lib/backend.sh index 76d0177..326c763 100644 --- a/lib/backend.sh +++ b/lib/backend.sh @@ -163,8 +163,7 @@ backend_apply() { [[ "${WANTED[single]:-0}" == 1 ]] && WANTED[gaming]=1 [[ "${WANTED[gaming]:-0}" == 0 ]] && { WANTED[single]=0; WANTED[boot]=0; } [[ "${WANTED[poweroff]:-0}" == 1 ]] && WANTED[machine]=1 - [[ "${WANTED[vram]:-0}" == 1 ]] && WANTED[machine]=1 - [[ "${WANTED[machine]:-0}" == 0 ]] && { WANTED[poweroff]=0; WANTED[vram]=0; } + [[ "${WANTED[machine]:-0}" == 0 ]] && WANTED[poweroff]=0 [[ "${WANTED[kpin]:-0}" == 1 ]] && WANTED[machine]=1 [[ "${WANTED[machine]:-0}" == 0 ]] && WANTED[kpin]=0 [[ "${WANTED[hdmi]:-0}" == 1 ]] && { WANTED[machine]=1; WANTED[kpin]=1; } diff --git a/lib/menu.sh b/lib/menu.sh index 9c053eb..5c77a8d 100644 --- a/lib/menu.sh +++ b/lib/menu.sh @@ -5,12 +5,12 @@ # Menu order. Components are turned on in this order and off in reverse; # gaming must come first (single user builds on it). -COMPONENTS=(gaming boot theme glyphs single launcher cec machine poweroff vram kpin hdmi bios) +COMPONENTS=(gaming boot theme glyphs single launcher vram cec machine poweroff kpin hdmi bios) # One-off actions rather than on/off components: never preselected, never # re-applied, not listed as on or off. ACTIONS=(bios) # Sub-options, shown indented under their parent and only while it's ticked. -declare -A PARENT=([boot]=gaming [poweroff]=machine [vram]=machine [kpin]=machine [hdmi]=machine [bios]=machine) +declare -A PARENT=([boot]=gaming [poweroff]=machine [kpin]=machine [hdmi]=machine [bios]=machine) # Never preselected on a first run: booting into the desktop is a choice, # gamescope is the default; HDMI-CEC is opt-in (it can wake the machine or # upset other devices on the TV, even on SteamOS), except on a Steam Machine, @@ -37,10 +37,10 @@ declare -A LABEL=( [glyphs]="Install Steam Deck/Machine icons: Deck button icons in gaming mode" [single]="Single user mode: no password, lock screen or log out (SDDM)" [launcher]="Steamify shortcut: the app on the desktop, Steamify Terminal in the launcher" + [vram]="VRAM booster: the game in front keeps its VRAM, background apps make room" [cec]="HDMI-CEC: use Steam with the TV remote, TV on/off with the PC (experimental)" [machine]="Steam Machine support: LED bar driver, hardware settings in Steam" [poweroff]="Power-off fix: the Steam Machine stays off after shutting down" - [vram]="VRAM booster: the game in front keeps its VRAM, background apps make room" [kpin]="Pin the kernel to $PINNED_KERNEL_VER (untick for CachyOS's current kernel)" [hdmi]="HDMI refresh boost: highest refresh your HDMI display runs" [bios]="Update BIOS" @@ -49,7 +49,8 @@ declare -A CURRENT WANTED component_available() { case "$1" in - machine|poweroff|vram) machine_available ;; + machine|poweroff) machine_available ;; + vram) vram_available ;; kpin) kpin_available ;; hdmi) hdmi_available ;; bios) bios_available ;; @@ -103,9 +104,11 @@ menu_label() { } feature_new() { - # A default sub-option added after its parent was set up (e.g. the - # power-off fix under Steam Machine support): never turned on or off. - [[ -n "${PARENT[$1]:-}" && "${CURRENT[${PARENT[$1]}]:-0}" == 1 && "${CURRENT[$1]:-0}" == 0 ]] && + # A default option added after its parent was set up (e.g. the power-off + # fix under Steam Machine support; a top-level one counts the SteamOS + # conversion as its parent): never turned on or off. + local p="${PARENT[$1]:-gaming}" + [[ "$1" != gaming && "${CURRENT[$p]:-0}" == 1 && "${CURRENT[$1]:-0}" == 0 ]] && ! is_action "$1" && [[ " ${NO_PRESELECT[*]} " != *" $1 "* ]] && [[ -z "$(state_get features "$1")" ]] } @@ -161,11 +164,9 @@ toggle_component() { if [[ "$c" == gaming && "${WANTED[gaming]}" == 0 ]]; then WANTED[single]=0; WANTED[boot]=0; fi # Where to boot to is part of the conversion, too. if [[ "$c" == boot && "${WANTED[boot]}" == 1 ]]; then WANTED[gaming]=1; fi - # The power-off fix and VRAM booster are opt-out: ticked along with - # Steam Machine support. - if [[ "$c" == machine ]]; then WANTED[poweroff]=${WANTED[machine]}; WANTED[vram]=${WANTED[machine]}; fi + # The power-off fix is opt-out: ticked along with Steam Machine support. + if [[ "$c" == machine ]]; then WANTED[poweroff]=${WANTED[machine]}; fi if [[ "$c" == poweroff && "${WANTED[poweroff]}" == 1 ]]; then WANTED[machine]=1; fi - if [[ "$c" == vram && "${WANTED[vram]}" == 1 ]]; then WANTED[machine]=1; fi if [[ "$c" == machine && "${WANTED[machine]}" == 0 ]]; then WANTED[kpin]=0; fi if [[ "$c" == kpin && "${WANTED[kpin]}" == 1 ]]; then WANTED[machine]=1; fi # HDMI refresh boost and the BIOS update sit under Steam Machine support; diff --git a/lib/vram-booster.sh b/lib/vram-booster.sh index 4815df1..3a5619a 100644 --- a/lib/vram-booster.sh +++ b/lib/vram-booster.sh @@ -1,9 +1,10 @@ #!/bin/bash -# "VRAM booster" menu item (vram), a sub-option of Steam Machine support -# ticked along with it (opt-out), like SteamOS 3.9's dGPU VRAM management: -# the game in front keeps its VRAM, background apps are evicted first, so a -# game on the 8 GB GPU doesn't spill into system RAM and stutter. The kernel -# side is the dmem cgroup controller (7.2); CachyOS packages the userspace +# "VRAM booster" menu item (vram), ticked by default, like SteamOS 3.9's +# dGPU VRAM management: the game in front keeps its VRAM, background apps +# are evicted first, so a game that needs most of it doesn't spill into +# system RAM and stutter. The kernel side is the dmem cgroup controller +# (7.2; amdgpu and xe register their VRAM, NVIDIA's driver doesn't), so it's +# only offered when the kernel lists a region. CachyOS packages the userspace # side: dmemcg-booster (a system service that enables the controller and # sets the limits, plus a user service) and plasma-foreground-booster, which # tells it which window is in front on the desktop. The latter only starts @@ -12,6 +13,11 @@ VRAM_PKGS=(dmemcg-booster plasma-foreground-booster) +vram_available() { + # cgroupfs files have size 0, so read it. + grep -q . /sys/fs/cgroup/dmem.capacity 2>/dev/null || vram_status +} + vram_status() { pacman -Q "${VRAM_PKGS[@]}" >/dev/null 2>&1 && systemctl is-enabled -q dmemcg-booster-system.service 2>/dev/null && diff --git a/ui/qml/Main.qml b/ui/qml/Main.qml index 2ca5d8b..c13c01f 100644 --- a/ui/qml/Main.qml +++ b/ui/qml/Main.qml @@ -73,7 +73,7 @@ ApplicationWindow { body: "With recent kernels the Steam Machine starts again right after shutting down: the firmware leaves a wake bit set, and newer kernels (7.2, and updates of 6.x, 7.0 and 7.1) no longer clear it. Valve's own kernel clears it; this small module does the same right before power-off.", changes: ["steamify-fremont-poweroff module for each installed kernel (DKMS)", "Only on a Steam Machine, only touches that one wake bit", "Off: recent kernels may start it again after shutting down"] }, vram: { label: "VRAM booster", hint: "The game in front keeps its VRAM", - body: "Like SteamOS 3.9: the game you're playing keeps its video memory, and background apps are moved out first. Without it a game that needs most of the 8 GB can spill into system RAM and stutter.", + body: "Like SteamOS 3.9: the game you're playing keeps its video memory, and background apps are moved out first. Without it a game that needs most of the video memory can spill into system RAM and stutter. Shown for GPUs whose driver supports it (AMD, Intel) on kernel 7.2 or newer.", changes: ["dmemcg-booster (system and user service)", "plasma-foreground-booster, which tells it which window is in front", "Off: removed again, unless you had installed them yourself"] }, kpin: { label: "Pin the kernel", hint: "Untick for CachyOS's current kernel", body: "Keeps the Steam Machine on CachyOS kernel 7.1.6. Untick it to go back to CachyOS's current kernel; HDMI refresh boost goes with it.", @@ -253,9 +253,8 @@ ApplicationWindow { if (id === "hdmi" && !hdmiChoice && hdmiSaved.length) { openHdmiList(); return; } if (id === "hdmi" && w.hdmi && !nowOn("hdmi") && !hdmiChoice) { startHdmi(); return; } if (id === "hdmi" && !w.hdmi) hdmiChoice = ""; - if (id === "machine") { w.poweroff = w.machine; w.vram = w.machine; } + if (id === "machine") w.poweroff = w.machine; if (id === "poweroff" && w.poweroff) w.machine = true; - if (id === "vram" && w.vram) w.machine = true; if (id === "machine" && !w.machine) w.kpin = false; if (id === "kpin" && w.kpin) w.machine = true; if (id === "hdmi" && w.hdmi) { w.machine = true; w.kpin = true; } From a087757dc740be3547bb8b1fc781a8808b82f344 Mon Sep 17 00:00:00 2001 From: rickpeters Date: Sun, 27 Sep 2026 12:55:40 +0200 Subject: [PATCH 4/5] feat: VRAM booster only for GPUs with at least 2 GB of their own VRAM --- AGENTS.md | 2 +- CHANGELOG.md | 3 ++- TECHNICAL.md | 3 ++- lib/vram-booster.sh | 9 ++++++--- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 4556f40..e9319a4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -234,7 +234,7 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine new major kernel. - VRAM booster (`vram`, `lib/vram-booster.sh`, top-level, ticked by default, 2.3.0; only available when `/sys/fs/cgroup/dmem.capacity` lists a - region): CachyOS's `dmemcg-booster` (system + user service) and + VRAM region of at least 2 GB): CachyOS's `dmemcg-booster` (system + user service) and `plasma-foreground-booster`, like SteamOS 3.9's VRAM management. The latter only starts with `kcgroupsrc [Foreground Booster] autostart=true` (set with `kset`). Disable removes only the packages it installed diff --git a/CHANGELOG.md b/CHANGELOG.md index 2eeb285..d44d7fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ one per merged pull request. ## 2.3.0 - 2026-09-27 -- **feat: VRAM booster for every supported GPU, not only the Steam Machine** +- **feat: VRAM booster only for GPUs with at least 2 GB of their own VRAM** +- `3f97c85` **feat: VRAM booster for every supported GPU, not only the Steam Machine** - `fd162c1` **feat: New badge for default sub-options added in this version** - `54ebddf` **feat: VRAM booster for the Steam Machine** diff --git a/TECHNICAL.md b/TECHNICAL.md index ca5b3c6..b24155c 100644 --- a/TECHNICAL.md +++ b/TECHNICAL.md @@ -244,7 +244,8 @@ most of the VRAM (8 GB on the Steam Machine) can be pushed into system RAM by the desktop and other apps, and stutter. The kernel side is the `dmem` cgroup controller (7.2); amdgpu and Intel's xe register their VRAM with it, NVIDIA's driver doesn't. The **VRAM booster** option is only shown when -`/sys/fs/cgroup/dmem.capacity` lists a region (or it's already on), and is +`/sys/fs/cgroup/dmem.capacity` lists a VRAM region of at least 2 GB (a +dedicated GPU; an integrated one registers a small carve-out) or it's already on, and is ticked by default; new in 2.3.0, so setups with the SteamOS conversion on get it ticked. CachyOS packages the userspace side, which it installs: diff --git a/lib/vram-booster.sh b/lib/vram-booster.sh index 3a5619a..f0a37c2 100644 --- a/lib/vram-booster.sh +++ b/lib/vram-booster.sh @@ -4,7 +4,7 @@ # are evicted first, so a game that needs most of it doesn't spill into # system RAM and stutter. The kernel side is the dmem cgroup controller # (7.2; amdgpu and xe register their VRAM, NVIDIA's driver doesn't), so it's -# only offered when the kernel lists a region. CachyOS packages the userspace +# only offered when the kernel lists a VRAM region of at least 2 GB. CachyOS packages the userspace # side: dmemcg-booster (a system service that enables the controller and # sets the limits, plus a user service) and plasma-foreground-booster, which # tells it which window is in front on the desktop. The latter only starts @@ -12,10 +12,13 @@ # Sourced by steamify.sh; not meant to be run on its own. VRAM_PKGS=(dmemcg-booster plasma-foreground-booster) +VRAM_MIN_BYTES=$((2 * 1024 * 1024 * 1024)) vram_available() { - # cgroupfs files have size 0, so read it. - grep -q . /sys/fs/cgroup/dmem.capacity 2>/dev/null || vram_status + # Only for a GPU with its own VRAM: an integrated GPU registers a small + # carve-out too, but mostly uses system RAM, so there's little to boost. + awk -v min="$VRAM_MIN_BYTES" '$1 ~ /\/vram$/ && $2 >= min { found = 1 } END { exit !found }' \ + /sys/fs/cgroup/dmem.capacity 2>/dev/null || vram_status } vram_status() { From 91128917c8bdf1fdb7c2cf87e9f5949e3f0236d2 Mon Sep 17 00:00:00 2001 From: rickpeters Date: Sun, 27 Sep 2026 13:02:13 +0200 Subject: [PATCH 5/5] feat: VRAM booster greyed out with an NVIDIA card, with why --- AGENTS.md | 3 ++- CHANGELOG.md | 3 ++- README.md | 3 ++- TECHNICAL.md | 4 +++- lib/menu.sh | 7 +++++-- lib/vram-booster.sh | 22 ++++++++++++++++++++-- ui/qml/Main.qml | 14 +++++++++++--- 7 files changed, 45 insertions(+), 11 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index e9319a4..9c1678b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -234,7 +234,8 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine new major kernel. - VRAM booster (`vram`, `lib/vram-booster.sh`, top-level, ticked by default, 2.3.0; only available when `/sys/fs/cgroup/dmem.capacity` lists a - VRAM region of at least 2 GB): CachyOS's `dmemcg-booster` (system + user service) and + VRAM region of at least 2 GB; greyed out with an NVIDIA card, + `vram_selectable`, faked with `WIZARD_VRAM_FAKE_NVIDIA=1`): CachyOS's `dmemcg-booster` (system + user service) and `plasma-foreground-booster`, like SteamOS 3.9's VRAM management. The latter only starts with `kcgroupsrc [Foreground Booster] autostart=true` (set with `kset`). Disable removes only the packages it installed diff --git a/CHANGELOG.md b/CHANGELOG.md index d44d7fe..3f746d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ one per merged pull request. ## 2.3.0 - 2026-09-27 -- **feat: VRAM booster only for GPUs with at least 2 GB of their own VRAM** +- **feat: VRAM booster greyed out with an NVIDIA card, with why** +- `b0947d7` **feat: VRAM booster only for GPUs with at least 2 GB of their own VRAM** - `3f97c85` **feat: VRAM booster for every supported GPU, not only the Steam Machine** - `fd162c1` **feat: New badge for default sub-options added in this version** - `54ebddf` **feat: VRAM booster for the Steam Machine** diff --git a/README.md b/README.md index b4c6e6c..124e888 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,8 @@ Steam Machine. command again. "Steamify Terminal" in the launcher opens the terminal menu. 6. **VRAM booster** - on by default: the game you're playing keeps its video memory and background apps make room, like SteamOS 3.9. Shown for GPUs - whose driver supports it (AMD, Intel) on kernel 7.2 or newer + whose driver supports it (AMD, Intel) on kernel 7.2 or newer; greyed out + with an NVIDIA card until NVIDIA's driver supports it ([details](TECHNICAL.md#vram-booster)). 7. **HDMI-CEC** (experimental; off by default, on by default on a Steam Machine) - use Steam with your TV's diff --git a/TECHNICAL.md b/TECHNICAL.md index b24155c..9bbd3bc 100644 --- a/TECHNICAL.md +++ b/TECHNICAL.md @@ -243,7 +243,9 @@ protected, background apps are evicted first. Without it a game that needs most of the VRAM (8 GB on the Steam Machine) can be pushed into system RAM by the desktop and other apps, and stutter. The kernel side is the `dmem` cgroup controller (7.2); amdgpu and Intel's xe register their VRAM with it, -NVIDIA's driver doesn't. The **VRAM booster** option is only shown when +NVIDIA's driver doesn't: with an NVIDIA card the option is shown greyed +out, with why (`WIZARD_VRAM_FAKE_NVIDIA=1` fakes that for tests). Otherwise +the **VRAM booster** option is only shown when `/sys/fs/cgroup/dmem.capacity` lists a VRAM region of at least 2 GB (a dedicated GPU; an integrated one registers a small carve-out) or it's already on, and is ticked by default; new in 2.3.0, so setups with the SteamOS conversion on diff --git a/lib/menu.sh b/lib/menu.sh index 5c77a8d..2d8f2f2 100644 --- a/lib/menu.sh +++ b/lib/menu.sh @@ -109,7 +109,7 @@ feature_new() { # conversion as its parent): never turned on or off. local p="${PARENT[$1]:-gaming}" [[ "$1" != gaming && "${CURRENT[$p]:-0}" == 1 && "${CURRENT[$1]:-0}" == 0 ]] && - ! is_action "$1" && [[ " ${NO_PRESELECT[*]} " != *" $1 "* ]] && + ! is_action "$1" && component_selectable "$1" && [[ " ${NO_PRESELECT[*]} " != *" $1 "* ]] && [[ -z "$(state_get features "$1")" ]] } @@ -117,6 +117,7 @@ component_selectable() { # Greyed out and not tickable when it has nothing to do. case "$1" in bios) bios_selectable ;; + vram) vram_selectable ;; esac } @@ -145,10 +146,12 @@ detect_components() { launcher_repair && WANTED[launcher]=1 # Shows the current and newest BIOS version. bios_available && { bios_lookup_newest; LABEL[bios]="$(bios_label)"; } + # Greyed out with an NVIDIA card: say why. + component_available vram && ! vram_selectable && LABEL[vram]="VRAM booster: not supported by NVIDIA's driver yet" # First run: preselect the full SteamOS experience (never an action). if [[ "$any" == false ]]; then for c in "${COMPONENTS[@]}"; do - component_available "$c" && ! is_action "$c" && + component_available "$c" && component_selectable "$c" && ! is_action "$c" && [[ " ${NO_PRESELECT[*]} " != *" $c "* ]] && WANTED[$c]=1 done machine_available && WANTED[cec]=1 diff --git a/lib/vram-booster.sh b/lib/vram-booster.sh index f0a37c2..3941844 100644 --- a/lib/vram-booster.sh +++ b/lib/vram-booster.sh @@ -14,13 +14,31 @@ VRAM_PKGS=(dmemcg-booster plasma-foreground-booster) VRAM_MIN_BYTES=$((2 * 1024 * 1024 * 1024)) -vram_available() { +vram_supported() { + # WIZARD_VRAM_FAKE_NVIDIA=1 (tests): as with an NVIDIA card. + [[ -n "${WIZARD_VRAM_FAKE_NVIDIA:-}" ]] && return 1 # Only for a GPU with its own VRAM: an integrated GPU registers a small # carve-out too, but mostly uses system RAM, so there's little to boost. awk -v min="$VRAM_MIN_BYTES" '$1 ~ /\/vram$/ && $2 >= min { found = 1 } END { exit !found }' \ - /sys/fs/cgroup/dmem.capacity 2>/dev/null || vram_status + /sys/fs/cgroup/dmem.capacity 2>/dev/null } +vram_nvidia() { + # An NVIDIA display controller (PCI vendor 10de, class 03xxxx). + local d + [[ -n "${WIZARD_VRAM_FAKE_NVIDIA:-}" ]] && return 0 + for d in /sys/bus/pci/devices/*; do + [[ "$(cat "$d/vendor" 2>/dev/null)" == 0x10de && "$(cat "$d/class" 2>/dev/null)" == 0x03* ]] && return 0 + done + return 1 +} + +# Shown where it works, and greyed out with an NVIDIA card, whose driver +# doesn't register its VRAM with the kernel (yet): so it's clear why it +# can't be turned on. Hidden elsewhere (integrated graphics, older kernel). +vram_available() { vram_supported || vram_status || vram_nvidia; } +vram_selectable() { vram_supported || vram_status; } + vram_status() { pacman -Q "${VRAM_PKGS[@]}" >/dev/null 2>&1 && systemctl is-enabled -q dmemcg-booster-system.service 2>/dev/null && diff --git a/ui/qml/Main.qml b/ui/qml/Main.qml index c13c01f..1595498 100644 --- a/ui/qml/Main.qml +++ b/ui/qml/Main.qml @@ -45,6 +45,11 @@ ApplicationWindow { } // --- Texts per item (the backend's labels are the fallback) --- + // Replaces the explanation of an option that can't be turned on here. + readonly property var unsupported: ({ + vram: { body: "Not available with your NVIDIA graphics card: NVIDIA's driver doesn't tell Linux how its video memory is used yet, so there's nothing to steer. It works with AMD and Intel graphics cards, and shows up here as soon as NVIDIA's driver supports it.", + changes: ["Nothing: can't be turned on with NVIDIA's driver"] } + }) readonly property var texts: ({ gaming: { label: "SteamOS conversion", hint: "Boot into gaming mode, Steam on the desktop", body: "Boots straight into gaming mode. Switch to Desktop in Steam works, and Return to Gaming Mode on the desktop brings you back.", @@ -244,6 +249,8 @@ ApplicationWindow { function toggle(id) { hdmiNote = ""; + var cur = items.find(function (i) { return i.id === id; }); + if (cur && cur.kind === "toggle" && cur.selectable === false) return; var w = Object.assign({}, want); w[id] = !w[id]; if (id === "gaming" && !w.gaming) { w.single = false; } @@ -629,18 +636,19 @@ ApplicationWindow { anchors.verticalCenter: parent.verticalCenter; spacing: 2 x: row.modelData.parent ? 46 : 16 width: controls.x - x - 16 - opacity: row.modelData.kind === "action" && !(bios && bios.selectable) ? 0.6 : 1 + opacity: (row.modelData.kind === "action" && !(bios && bios.selectable)) || (row.modelData.kind === "toggle" && row.modelData.selectable === false) ? 0.6 : 1 // The name, with an Update badge when a newer version of it will be // applied, New for a default sub-option added since the last run. Row { width: parent.width; spacing: 10 Text { text: label(row.modelData); color: t.textHi; font.family: t.body; font.pixelSize: 17; font.weight: Font.DemiBold; elide: Text.ElideRight width: Math.min(implicitWidth, parent.width - (upd.visible ? upd.width + parent.spacing : 0)) } Chip { id: upd; visible: !!row.modelData.update || !!row.modelData["new"]; text: row.modelData.update ? "Update" : "New"; fg: row.modelData.update ? t.warn : t.good; bgc: row.modelData.update ? t.warnBg : t.goodBg; height: 20; anchors.verticalCenter: parent.verticalCenter } } - Text { text: row.modelData.id === "bios" ? biosHint() : ((row.modelData.id === "hdmi" && hdmiRowHint()) || (texts[row.modelData.id] && texts[row.modelData.id].hint) || row.modelData.hint); color: t.mute; font.family: t.body; font.pixelSize: 13; elide: Text.ElideRight; width: parent.width } + Text { text: row.modelData.id === "bios" ? biosHint() : row.modelData.selectable === false ? row.modelData.hint : ((row.modelData.id === "hdmi" && hdmiRowHint()) || (texts[row.modelData.id] && texts[row.modelData.id].hint) || row.modelData.hint); color: t.mute; font.family: t.body; font.pixelSize: 13; elide: Text.ElideRight; width: parent.width } } // Right: every control ends on the same edge Item { id: controls + opacity: row.modelData.kind === "toggle" && row.modelData.selectable === false ? 0.4 : 1 anchors.right: parent.right; anchors.rightMargin: 16; anchors.verticalCenter: parent.verticalCenter width: 180; height: 32 // choice @@ -682,7 +690,7 @@ ApplicationWindow { id: detail width: parent.width; height: parent.height - sys.height - 16; radius: 16; color: t.card readonly property var it: sel < rows.length ? rows[sel] : null - readonly property var tx: it ? (texts[it.id] || {}) : {} + readonly property var tx: it ? (it.selectable === false && unsupported[it.id] ? Object.assign({}, texts[it.id], unsupported[it.id]) : (texts[it.id] || {})) : {} // Long texts scroll instead of running out of the card; // each item starts at the top. onItChanged: detailFlick.contentY = 0