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