diff --git a/bin/blob-hw-gpu-accelerated b/bin/blob-hw-gpu-accelerated new file mode 100755 index 0000000..ff4e7d4 --- /dev/null +++ b/bin/blob-hw-gpu-accelerated @@ -0,0 +1,28 @@ +#!/bin/bash + +# blob:summary=Detect whether a DRM device with an accelerated driver is present. + +drm_path="${BLOB_DRM_PATH:-/sys/class/drm}" + +accelerated_drivers=( + amdgpu + i915 + nouveau + nvidia + nvidia-drm + radeon + xe +) + +shopt -s nullglob + +for card in "$drm_path"/card[0-9]*; do + [[ -L $card/device/driver ]] || continue + driver=$(basename "$(readlink -f "$card/device/driver")") + + for accelerated in "${accelerated_drivers[@]}"; do + [[ $driver == "$accelerated" ]] && exit 0 + done +done + +exit 1 diff --git a/default/hypr/envs.lua b/default/hypr/envs.lua index 8ecc0c4..b97eb6b 100644 --- a/default/hypr/envs.lua +++ b/default/hypr/envs.lua @@ -37,6 +37,7 @@ hl.env("PATH", table.concat(kept, ":")) -- Hardware-specific environment. require("default.hypr.nvidia") +require("default.hypr.renderer") hl.config({ xwayland = { diff --git a/default/hypr/renderer.lua b/default/hypr/renderer.lua new file mode 100644 index 0000000..107b123 --- /dev/null +++ b/default/hypr/renderer.lua @@ -0,0 +1,14 @@ +local paths = require("default.hypr.paths") + +local accelerated = paths.blob_path .. "/bin/blob-hw-gpu-accelerated" + +if not o.shell_succeeds(o.shell_quote(accelerated)) then + hl.env("LIBGL_ALWAYS_SOFTWARE", "1") + hl.env("WLR_NO_HARDWARE_CURSORS", "1") + + hl.config({ + cursor = { + no_hardware_cursors = true, + }, + }) +end diff --git a/default/sddm/hyprland-greeter.lua b/default/sddm/hyprland-greeter.lua index 5b14e02..f544f5e 100644 --- a/default/sddm/hyprland-greeter.lua +++ b/default/sddm/hyprland-greeter.lua @@ -1,6 +1,31 @@ -- Hyprland config for the SDDM Wayland greeter. SDDM starts the greeter itself -- once the compositor is up, so nothing here launches anything. +-- The greeter runs as SDDM's own user, with no BLOB_PATH and no read access to +-- the checkout, so it calls the copy install.sh puts in /usr/local/bin rather +-- than loading the Lua modules the session uses. A detector that is not there +-- yet reads as no acceleration, which is the fallback that always draws. +local function gpu_is_accelerated() + local pipe = io.popen("/usr/local/bin/blob-hw-gpu-accelerated >/dev/null 2>&1 && echo OK") + if not pipe then + return false + end + + local output = pipe:read("*a") or "" + pipe:close() + + return output:match("OK") ~= nil +end + +if not gpu_is_accelerated() then + hl.env("LIBGL_ALWAYS_SOFTWARE", "1") + hl.env("WLR_NO_HARDWARE_CURSORS", "1") +end + hl.config({ + cursor = { + no_hardware_cursors = true, + }, + misc = { disable_hyprland_logo = true, disable_splash_rendering = true, diff --git a/docs/commands.md b/docs/commands.md index feb4b7e..17e247b 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -206,6 +206,7 @@ of the `blob` listing. | `blob-hw-display` | Print the most likely display backlight device. | - | | `blob-hw-external` | Returns true when an external monitor is physically connected. | - | | `blob-hw-fingerprint` | Returns true when a fingerprint reader is present (hidden) | - | +| `blob-hw-gpu-accelerated` | Detect whether a DRM device with an accelerated driver is present. | - | | `blob-hw-hybrid-gpu` | Detect whether the system has an active hybrid GPU configuration | - | | `blob-hw-laptop` | Returns true when running on a laptop (has a lid or laptop chassis). | - | | `blob-hw-laptop-closed` | Returns true when the laptop lid is closed (hidden) | - | diff --git a/docs/install.md b/docs/install.md index 4074f25..c0d0761 100644 --- a/docs/install.md +++ b/docs/install.md @@ -86,10 +86,14 @@ with `--check`. | `/usr/local/share/wayland-sessions/blob.desktop` | the session SDDM offers | | `/etc/sddm.conf.d/zz-blob.conf` | Wayland greeter, run on Hyprland, stock theme, remember the last session | | `/etc/sddm/hyprland-greeter.lua` | the greeter's own tiny Hyprland config | + | `/usr/local/bin/blob-hw-gpu-accelerated` | the renderer check that config runs, where SDDM's user can read it | The greeter config matters more than it looks: SDDM's stock Wayland greeter runs on `weston`, which Blob does not install, so a default SDDM would fail - to draw anything. That greeter config has to be Lua: Hyprland dropped the old + to draw anything. The installer checks that the `CompositorCommand` named in + `zz-blob.conf` is actually installed and warns when it is not, because SDDM + with a compositor command it cannot run starts, fails, and leaves the machine + sitting at a text console with no explanation. That greeter config has to be Lua: Hyprland dropped the old hyprlang format, and a config it cannot parse leaves the greeter with no compositor and the screen black. `sddm.service` is enabled and the default systemd target is set to `graphical.target`, which an Arch install without a @@ -131,6 +135,52 @@ with `--check`. | `--autologin` | skip the SDDM prompt for this user | | `--no-launch` | leave the login screen for the next reboot | +## Choosing a renderer + +Hyprland renders through Mesa, and on an adapter Mesa has no driver for it exits +with `failed to create dri2 screen` instead of drawing. The greeter is Hyprland +too, so that is a black screen at boot and a second one after login, which is +how a machine with no real GPU behind its display arrives. + +`blob-hw-gpu-accelerated` decides which way to go. It reads the kernel driver of +each `/sys/class/drm/card*` from sysfs, not from `lspci`, which reads PCI config +space and resumes runtime-suspended GPUs. A card driven by `amdgpu`, `i915`, +`nouveau`, `nvidia`, `nvidia-drm`, `radeon` or `xe` is a real integrated or +dedicated GPU and hardware rendering is left alone. Everything else - `vmwgfx`, +`vboxvideo`, `qxl`, `bochs-drm`, `cirrus`, `simpledrm`, `vkms`, `virtio_gpu` - +is virtual or framebuffer-only, and two settings are applied: + +| Setting | Effect | +| --- | --- | +| `LIBGL_ALWAYS_SOFTWARE=1` | Mesa uses llvmpipe, which renders on the CPU | +| `WLR_NO_HARDWARE_CURSORS=1`, `cursor:no_hardware_cursors` | the cursor is drawn by the compositor, since there is no plane to put it on | + +llvmpipe is slow and it draws. A machine with a real GPU never touches it. + +Two places read the detector, because they run as different users: + +| Where | How | +| --- | --- | +| the session | `default/hypr/renderer.lua`, required from `envs.lua` beside `nvidia.lua`, through `$BLOB_PATH/bin` | +| the greeter | `default/sddm/hyprland-greeter.lua`, through `/usr/local/bin/blob-hw-gpu-accelerated` | + +The greeter runs as SDDM's own user, with no `BLOB_PATH` and no read access to +your home directory, so `install.sh` copies the detector to `/usr/local/bin`. +A detector that is not there yet reads as no acceleration, which is the fallback +that always draws. + +To add a driver to the accelerated list, edit `accelerated_drivers` in +`bin/blob-hw-gpu-accelerated` and rerun `./install.sh` so the greeter's copy +follows. `virtio_gpu` is deliberately not on it: QEMU only accelerates it when +the host enables virgl, and Mesa fails the same way when it does not. + +To see the difference by hand, from a console: + +```bash +Hyprland # fails with the dri2 error +LIBGL_ALWAYS_SOFTWARE=1 Hyprland # starts +``` + ## Uninstall `./uninstall.sh` removes the config, the user units, the `/etc` drop-ins, the diff --git a/install.sh b/install.sh index b089e19..d60615a 100755 --- a/install.sh +++ b/install.sh @@ -81,6 +81,7 @@ install_login_config install_session_entry install_autologin enable_display_manager +report_greeter_compositor report_login_conflicts report_desktop_essentials diff --git a/install/steps/login.sh b/install/steps/login.sh index f44a0a4..fa299fb 100644 --- a/install/steps/login.sh +++ b/install/steps/login.sh @@ -8,6 +8,7 @@ greeter_compositor_config=/etc/sddm/hyprland-greeter.lua # ISO, or a previous desktop left behind. blob_greeter_conf="$greeter_config_dir/zz-blob.conf" blob_autologin_conf="$greeter_config_dir/zzz-blob-autologin.conf" +greeter_gpu_detector=/usr/local/bin/blob-hw-gpu-accelerated # SDDM's stock Wayland greeter runs on weston, which Blob does not install. # Point it at Hyprland with a greeter-only config instead. That config is Lua: @@ -18,6 +19,8 @@ install_login_config() { "$greeter_compositor_config" "sddm/hyprland-greeter.lua" install_root_file "$repo_dir/default/sddm/zz-blob.conf" \ "$blob_greeter_conf" "sddm.conf.d/zz-blob.conf" + install_root_file "$repo_dir/bin/blob-hw-gpu-accelerated" \ + "$greeter_gpu_detector" "blob-hw-gpu-accelerated" 0755 } install_session_entry() { @@ -54,6 +57,22 @@ setting_value() { sed -n "s/^[[:space:]]*$key[[:space:]]*=[[:space:]]*//p" "$file" | tail -1 } +# SDDM runs CompositorCommand to get a Wayland greeter on screen. When that +# binary is not installed SDDM fails with no greeter at all, which reads as a +# machine that boots to a console for no reason, so name it here instead. +report_greeter_compositor() { + local command_line binary + + command_line="$(setting_value "$repo_dir/default/sddm/zz-blob.conf" CompositorCommand)" + binary="${command_line%% *}" + + [[ -n $binary ]] || return 0 + command -v "$binary" &>/dev/null && return 0 + + say "WARN the greeter's compositor command '$binary' is not installed" + say " SDDM cannot draw a greeter without it; it is set in $blob_greeter_conf" +} + # zz-blob.conf blanks Theme and Autologin, so a leftover drop-in cannot point the # greeter at a theme that was uninstalled or autolog into a session that no # longer exists. Either one leaves SDDM on a black screen it never falls back diff --git a/packages/blob.packages b/packages/blob.packages index 27dcc4d..23d6b04 100644 --- a/packages/blob.packages +++ b/packages/blob.packages @@ -6,6 +6,7 @@ # Compositor and shell hyprland +mesa quickshell uwsm sddm diff --git a/uninstall.sh b/uninstall.sh index 7862399..e11799d 100755 --- a/uninstall.sh +++ b/uninstall.sh @@ -8,6 +8,7 @@ session_entry="/usr/local/share/wayland-sessions/blob.desktop" root_files=( /etc/profile.d/blob.sh + /usr/local/bin/blob-hw-gpu-accelerated /etc/sddm.conf.d/zz-blob.conf /etc/sddm.conf.d/zzz-blob-autologin.conf /etc/sddm/hyprland-greeter.conf