diff --git a/bin/omarchy-hw-nvidia-display b/bin/omarchy-hw-nvidia-display new file mode 100755 index 00000000000..fabb8ed6dc4 --- /dev/null +++ b/bin/omarchy-hw-nvidia-display @@ -0,0 +1,42 @@ +#!/bin/bash + +# omarchy:summary=Detect whether an NVIDIA GPU drives the display. + +# The boot VGA device owns the firmware display, which on a working system is +# the GPU driving the active display. Forcing NVIDIA-only video-decode +# settings (e.g. LIBVA_DRIVER_NAME) while another GPU drives the display +# breaks hardware video playback, so only claim NVIDIA when it owns boot VGA. +# When an NVIDIA GPU exists but no display device exposes the boot_vga +# attribute at all, claim NVIDIA to preserve the previous behavior on setups +# without VGA arbitration info. +# +# Reads cached sysfs IDs rather than lspci, which reads PCI config space and +# resumes runtime-suspended GPUs. +pci_devices_path="${OMARCHY_PCI_DEVICES_PATH:-/sys/bus/pci/devices}" + +shopt -s nullglob + +saw_nvidia=false +saw_boot_attr=false + +for device in "$pci_devices_path"/*; do + [[ $(< "$device/class") == 0x03* ]] || continue + [[ $(< "$device/vendor") == "0x10de" ]] && saw_nvidia=true + # NB: `$(< file)` reads nothing when combined with other redirections, + # so guard readability separately instead of `2>/dev/null` inline. + [[ -r $device/boot_vga ]] || continue + # Any display device exposing boot_vga makes the setup determinable, + # including a boot_vga=0 that rules NVIDIA out. + saw_boot_attr=true + [[ $(< "$device/boot_vga") == "1" ]] || continue + [[ $(< "$device/vendor") == "0x10de" ]] && exit 0 +done + +# No NVIDIA GPU at all: nothing to claim. +[[ $saw_nvidia == "false" ]] && exit 1 + +# NVIDIA exists but no display device exposes boot_vga at all: +# undeterminable, keep the previous behavior. +[[ $saw_boot_attr == "false" ]] && exit 0 + +exit 1 diff --git a/default/hypr/nvidia.lua b/default/hypr/nvidia.lua index 663fe79e1bb..7a72ff8e61e 100644 --- a/default/hypr/nvidia.lua +++ b/default/hypr/nvidia.lua @@ -3,6 +3,7 @@ local paths = require("default.hypr.paths") local nvidia = paths.omarchy_path .. "/bin/omarchy-hw-nvidia" local nvidia_gsp = paths.omarchy_path .. "/bin/omarchy-hw-nvidia-gsp" local nvidia_without_gsp = paths.omarchy_path .. "/bin/omarchy-hw-nvidia-without-gsp" +local nvidia_display = paths.omarchy_path .. "/bin/omarchy-hw-nvidia-display" -- These detectors read cached sysfs IDs rather than shelling out to lspci. -- lspci reads PCI config space, which resumes a runtime-suspended GPU, and on a @@ -10,8 +11,13 @@ local nvidia_without_gsp = paths.omarchy_path .. "/bin/omarchy-hw-nvidia-without if o.shell_succeeds(o.shell_quote(nvidia)) then if o.shell_succeeds(o.shell_quote(nvidia_gsp)) then hl.env("NVD_BACKEND", "direct") - hl.env("LIBVA_DRIVER_NAME", "nvidia") hl.env("__GLX_VENDOR_LIBRARY_NAME", "nvidia") + -- Only force the NVIDIA VA-API driver when NVIDIA drives the display. + -- On hybrid GPUs whose display runs on another GPU this breaks hardware + -- video playback in Chromium and Electron (grey or black video). + if o.shell_succeeds(o.shell_quote(nvidia_display)) then + hl.env("LIBVA_DRIVER_NAME", "nvidia") + end elseif o.shell_succeeds(o.shell_quote(nvidia_without_gsp)) then hl.env("NVD_BACKEND", "egl") hl.env("__GLX_VENDOR_LIBRARY_NAME", "nvidia") diff --git a/test/shell.d/hw-nvidia-test.sh b/test/shell.d/hw-nvidia-test.sh index da461ce44ce..ecae054a9b5 100755 --- a/test/shell.d/hw-nvidia-test.sh +++ b/test/shell.d/hw-nvidia-test.sh @@ -7,7 +7,9 @@ source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" tmp_dir=$(mktemp -d) trap 'rm -rf "$tmp_dir"' EXIT -# Each argument is a PCI device as "vendor:device:class", in sysfs's own format. +# Each argument is a PCI device as "vendor:device:class[:boot_vga]", in +# sysfs's own format. The boot_vga part is omitted when the device exposes no +# such attribute. write_pci_devices() { rm -rf "$tmp_dir/devices" mkdir -p "$tmp_dir/devices" @@ -18,9 +20,12 @@ write_pci_devices() { local slot slot=$(printf '0000:%02x:00.0' "$index") mkdir -p "$tmp_dir/devices/$slot" - printf '%s\n' "${spec%%:*}" >"$tmp_dir/devices/$slot/vendor" + printf '%s\n' "$(cut -d: -f1 <<<"$spec")" >"$tmp_dir/devices/$slot/vendor" printf '%s\n' "$(cut -d: -f2 <<<"$spec")" >"$tmp_dir/devices/$slot/device" - printf '%s\n' "${spec##*:}" >"$tmp_dir/devices/$slot/class" + printf '%s\n' "$(cut -d: -f3 <<<"$spec")" >"$tmp_dir/devices/$slot/class" + if [[ $spec == *:*:*:* ]]; then + printf '%s\n' "$(cut -d: -f4 <<<"$spec")" >"$tmp_dir/devices/$slot/boot_vga" + fi index=$((index + 1)) done } @@ -30,15 +35,16 @@ hw_nvidia() { } assert_detects() { - local description="$1" nvidia="$2" gsp="$3" without_gsp="$4" + local description="$1" nvidia="$2" gsp="$3" without_gsp="$4" display="$5" local command - for command in nvidia gsp without-gsp; do + for command in nvidia gsp without-gsp display; do local expected case $command in nvidia) expected=$nvidia ;; gsp) expected=$gsp ;; without-gsp) expected=$without_gsp ;; + display) expected=$display ;; esac local detector=nvidia @@ -54,46 +60,67 @@ assert_detects() { pass "$description" } -# AMD Cezanne integrated graphics. -write_pci_devices 0x1002:0x15e7:0x030000 -assert_detects "a machine without an NVIDIA GPU detects nothing" no no no +# AMD Cezanne integrated graphics, driving the display. +write_pci_devices 0x1002:0x15e7:0x030000:1 +assert_detects "a machine without an NVIDIA GPU detects nothing" no no no no # NVIDIA GA106M [RTX 3060 Mobile] alongside AMD Cezanne, the pair from issue #6660. -write_pci_devices 0x1002:0x15e7:0x030000 0x10de:0x2560:0x030200 -assert_detects "a hybrid Ampere laptop detects a GSP GPU" yes yes no +write_pci_devices 0x1002:0x15e7:0x030000:1 0x10de:0x2560:0x030200:0 +assert_detects "a hybrid Ampere laptop detects a GSP GPU without an NVIDIA display" yes yes no no + +# NVIDIA AD107M [RTX 4060 Mobile] alongside AMD HawkPoint, display on AMD. +write_pci_devices 0x1002:0x1900:0x030000:1 0x10de:0x28e0:0x030000:0 +assert_detects "a hybrid Ada laptop with the display on AMD detects no NVIDIA display" yes yes no no + +# ROG Zephyrus G14 GA403WW (Ryzen AI 9 HX 370 + RTX 5080 Max-Q), display on +# AMD: the AMD Display controller (class 0x038000) exposes no boot_vga, +# while NVIDIA reports boot_vga=0, so the back-compat fallback must not +# fire (see #10589 discussion). The AMD device ID is illustrative; only +# vendor, class, and boot_vga presence matter here. +write_pci_devices 0x1002:0x1111:0x038000 0x10de:0x2c02:0x030000:0 +assert_detects "a hybrid Blackwell laptop whose AMD display exposes no boot_vga detects no NVIDIA display" yes yes no no # NVIDIA TU117M [GTX 1650 Mobile], the first generation with GSP firmware. -write_pci_devices 0x10de:0x1f91:0x030000 -assert_detects "Turing is the oldest generation with GSP firmware" yes yes no +write_pci_devices 0x10de:0x1f91:0x030000:1 +assert_detects "Turing is the oldest generation with GSP firmware" yes yes no yes + +# NVIDIA TU117M driving the display of a hybrid Intel+NVIDIA laptop. +write_pci_devices 0x8086:0x9bc4:0x030000:0 0x10de:0x1f91:0x030000:1 +assert_detects "a muxed hybrid with the display on NVIDIA detects an NVIDIA display" yes yes no yes # NVIDIA GV100 [TITAN V], the newest generation without GSP firmware. -write_pci_devices 0x10de:0x1d81:0x030000 -assert_detects "Volta is the newest generation without GSP firmware" yes no yes +write_pci_devices 0x10de:0x1d81:0x030000:1 +assert_detects "Volta is the newest generation without GSP firmware" yes no yes yes # NVIDIA GP104 [GTX 1080]. -write_pci_devices 0x10de:0x1b80:0x030000 -assert_detects "Pascal detects a GPU without GSP firmware" yes no yes +write_pci_devices 0x10de:0x1b80:0x030000:1 +assert_detects "Pascal detects a GPU without GSP firmware" yes no yes yes # NVIDIA GM108M [GeForce 830M], the oldest part the 580xx driver supports. -write_pci_devices 0x10de:0x1340:0x030000 -assert_detects "Maxwell detects a GPU without GSP firmware" yes no yes +write_pci_devices 0x10de:0x1340:0x030000:1 +assert_detects "Maxwell detects a GPU without GSP firmware" yes no yes yes # NVIDIA GK110 [GTX 780]. Kepler predates GSP but also predates 580xx, so # claiming it here would install a driver that cannot drive it. -write_pci_devices 0x10de:0x1004:0x030000 -assert_detects "Kepler is too old for either driver" yes no no +write_pci_devices 0x10de:0x1004:0x030000:1 +assert_detects "Kepler is too old for either driver" yes no no yes # NVIDIA GF100 [GTX 470], older still. -write_pci_devices 0x10de:0x06cd:0x030000 -assert_detects "Fermi is too old for either driver" yes no no +write_pci_devices 0x10de:0x06cd:0x030000:1 +assert_detects "Fermi is too old for either driver" yes no no yes # NVIDIA GB203 [RTX 5080], newer than every other device ID here. -write_pci_devices 0x10de:0x2c02:0x030000 -assert_detects "Blackwell detects a GSP GPU" yes yes no +write_pci_devices 0x10de:0x2c02:0x030000:1 +assert_detects "Blackwell detects a GSP GPU" yes yes no yes + +# NVIDIA GA106M without any boot VGA info: undeterminable, so keep the +# previous behavior and claim the display. +write_pci_devices 0x10de:0x2560:0x030000 +assert_detects "an NVIDIA GPU without boot VGA info keeps the display claim" yes yes no yes # The GA106 audio function carries the NVIDIA vendor ID but is not a GPU. write_pci_devices 0x10de:0x228e:0x040300 -assert_detects "a non-display NVIDIA function is not a GPU" no no no +assert_detects "a non-display NVIDIA function is not a GPU" no no no no write_pci_devices -assert_detects "a machine with no PCI devices detects nothing" no no no +assert_detects "a machine with no PCI devices detects nothing" no no no no