From 96467a1c56eb74259bbc5b412357abb3e61e8d18 Mon Sep 17 00:00:00 2001 From: nnduc Date: Mon, 19 Oct 2026 23:21:54 +0700 Subject: [PATCH] adaptive sync --- .zshrc | 1 + MangoHud/MangoHud.conf | 22 ++ README.md | 49 +++- i3/config | 11 +- i3/scripts/freeze-toggle | 19 ++ i3/scripts/guide-freeze | 50 ++++ mesa/drirc | 7 + nvim/lazy-lock.json | 12 +- pipewire/minimal.conf | 476 --------------------------------------- pipewire/pipewire.conf | 345 ---------------------------- sway/config | 1 - sway/scripts/lock-now | 4 +- sway/scripts/vrr-follow | 14 -- waybar/config | 1 + xorg/20-amdgpu.conf | 5 + 15 files changed, 169 insertions(+), 848 deletions(-) create mode 100644 MangoHud/MangoHud.conf create mode 100755 i3/scripts/freeze-toggle create mode 100755 i3/scripts/guide-freeze create mode 100644 mesa/drirc delete mode 100755 pipewire/minimal.conf delete mode 100755 pipewire/pipewire.conf delete mode 100755 sway/scripts/vrr-follow create mode 100644 xorg/20-amdgpu.conf diff --git a/.zshrc b/.zshrc index 8492894..88a1e16 100755 --- a/.zshrc +++ b/.zshrc @@ -35,3 +35,4 @@ export PATH="$HOME/.local/bin:$PATH" alias ls='ls --color=auto' alias doc='docker-compose' alias cla='cd /home/nnduc/.cache/ && claude' +alias wa="watch -n1 "sensors | grep -E 'Tctl|edge|junction|mem:|Composite'"" diff --git a/MangoHud/MangoHud.conf b/MangoHud/MangoHud.conf new file mode 100644 index 0000000..0510301 --- /dev/null +++ b/MangoHud/MangoHud.conf @@ -0,0 +1,22 @@ +# Shift_R+F12 shows/hides the HUD +no_display +toggle_hud=Shift_R+F12 +fps +frametime +frame_timing +gpu_stats +cpu_stats +# diagnostics: log starts 20s after launch, Shift_L+F2 toggles logging +output_folder=/home/nnduc/mangologs +autostart_log=20 +log_interval=0 +toggle_logging=Shift_L+F2 +gpu_core_clock +gpu_mem_clock +vram +ram +cpu_mhz +# fps +fps_limit=80,60,120,0 +fps_limit_method=early +toggle_fps_limit=Shift_L+F1 diff --git a/README.md b/README.md index a2e8750..3de38e2 100755 --- a/README.md +++ b/README.md @@ -75,8 +75,51 @@ echo 3 | sudo tee /sys/class/drm/card*/device/pp_dpm_mclk with `adaptive_sync off` games run without it. - To turn off FreeSync for a single game (Xorg): `adaptive_sync=false %command%` (Steam launch options). +- Login greeters (SDDM, LightDM, ...) are fullscreen and mostly static, so VRR + drops to the bottom of the range and flickers. `mesa/drirc` disables it for + any `*greeter*` executable; install system-wide (greeters don't read `~/.drirc`): +```bash +sudo cp xorg/20-amdgpu.conf /etc/X11/xorg.conf.d/ +sudo cp mesa/drirc /etc/drirc +``` ![stones](./screenshots/Screenshot_20240503_095414.png) +## Shader cache (Steam) + +With Steam's *Shader Pre-caching* off (it re-downloads caches for every game +almost daily), Steam points Mesa at a temporary +`~/.local/share/Steam/shader_cache_temp_dir_*` and deletes it when the game +exits, so every session recompiles shaders (hitches everywhere, every time). +Steam only sets the legacy `MESA_GLSL_CACHE_DIR`, so the newer variable wins. +Set it session-wide (i3 is started by SDDM, `environment.d` doesn't reach +Steam). The cache lives in `/var/cache` so `~/.cache` cleaners don't wipe it, +with one private subdir per user: Mesa creates its dirs `0700`, so a single +shared dir can't work (and would let users poison each other's shaders). +`/etc/environment` can't expand `$USER`, so use `/etc/profile.d` (SDDM's +X11/Wayland sessions and TTY logins source it). Run as your normal user, not +from a root shell (`~` and `$USER` must be yours), with Steam closed: +```bash +sudo mkdir -p /var/cache/mesa_shader_cache +sudo chmod 1777 /var/cache/mesa_shader_cache # like /tmp: anyone creates own subdir +mv ~/.cache/mesa_shader_cache /var/cache/mesa_shader_cache/$USER # no sudo: keeps ownership +sudo sed -i '/^MESA_SHADER_CACHE_/d' /etc/environment +sudo tee /etc/profile.d/mesa-shader-cache.sh >/dev/null <<'EOF' +export MESA_SHADER_CACHE_DIR="/var/cache/mesa_shader_cache/$USER" +export MESA_SHADER_CACHE_MAX_SIZE=10G +export PRESSURE_VESSEL_FILESYSTEMS_RW=/var/cache/mesa_shader_cache +EOF +``` +The last line matters: Steam runs games (native and Proton) in the Steam +Linux Runtime container, which shares `$HOME` but gives the game its own empty +`/var`. Without it the cache is written to a tmpfs inside the container and +thrown away when the game exits. +Re-login, then check it's yours and persists after playing (should print > 0): +```bash +echo $MESA_SHADER_CACHE_DIR; ls -ld $MESA_SHADER_CACHE_DIR # owner must be you +find $MESA_SHADER_CACHE_DIR -mmin -60 -type f | wc -l +``` +Deleting the cache is harmless: shaders just recompile (stutter) on first use. + ## Installing dependencies Package availability differs between distros — Arch has everything below in the @@ -191,9 +234,9 @@ sudo emerge --ask games-util/gamemode Adaptive sync (sway): the desktop runs at a fixed 240 Hz with VRR off, because sway only renders on change and idle frame rates fall below the panel's 48 Hz -floor (out of LFC range → flicker). `sway/scripts/vrr-follow` turns VRR on for -DP-1 only while the visible workspace has a fullscreen window; `lock-now` turns -it off while locked. Needs `jq`. +floor (out of LFC range → flicker). gamemode turns VRR on for DP-1 while a +game runs and off when it exits (install `gamemode/gamemode.ini` to +`~/.config/gamemode.ini`); `lock-now` turns it off while locked. ### GPU monitoring & tuning diff --git a/i3/config b/i3/config index c1f0097..be52813 100755 --- a/i3/config +++ b/i3/config @@ -20,6 +20,11 @@ font pango:NotoSans 12 # text rendering and scalability on retina/hidpi displays (thanks to pango). #font pango:DejaVu Sans Mono 8 +# Clear leftover Wayland env (e.g. from a previous Hyprland/sway session) out of +# systemd/dbus so dbus-activated apps like Thunar don't try to use a dead compositor. +# Must run before anything dbus-activated starts. +exec --no-startup-id sh -c "systemctl --user unset-environment WAYLAND_DISPLAY HYPRLAND_INSTANCE_SIGNATURE SWAYSOCK; dbus-update-activation-environment --systemd DISPLAY XAUTHORITY XDG_CURRENT_DESKTOP=i3 XDG_SESSION_TYPE=x11; pkill -x Thunar" + # Start XDG autostart .desktop files using dex. See also # https://wiki.archlinux.org/index.php/XDG_Autostart exec --no-startup-id dex --autostart --environment i3 @@ -58,6 +63,7 @@ exec --no-startup-id /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1 exec --no-startup-id ~/.config/i3/scripts/wallpaper-rotate $wallpaper_dir exec --no-startup-id pasystray exec --no-startup-id polybar +exec --no-startup-id ~/.config/i3/scripts/guide-freeze set $refresh_i3status killall -SIGUSR1 i3status @@ -99,7 +105,7 @@ floating_modifier $mod #xrandr --output HDMI-2 --auto --right-of eDP1 # High refresh rates can flicker on AMD (VRAM clock switching), so run the # desktop at 120Hz. See README "High refresh rate flicker". -exec --no-startup-id xrandr --output DisplayPort-0 --mode 1920x1080 --rate 120 +exec --no-startup-id xrandr --output DisplayPort-0 --mode 1920x1080 --rate 240 # start a terminal #bindsym $mod+Return exec i3-sensible-terminal @@ -108,6 +114,9 @@ bindsym $mod+Return exec $terminal # kill focused window bindsym $mod+Shift+q kill +# freeze / resume focused window (SIGSTOP / SIGCONT) +bindsym $mod+m exec --no-startup-id ~/.config/i3/scripts/freeze-toggle + # start dmenu (a program launcher) #bindsym $mod+d exec --no-startup-id j4-dmenu-desktop #bindsym $mod+d exec --no-startup-id dmenu diff --git a/i3/scripts/freeze-toggle b/i3/scripts/freeze-toggle new file mode 100755 index 0000000..3399b0f --- /dev/null +++ b/i3/scripts/freeze-toggle @@ -0,0 +1,19 @@ +#!/bin/bash +# Freeze / resume the focused window's process tree (SIGSTOP / SIGCONT). +pid=$(xdotool getactivewindow getwindowpid 2>/dev/null) || exit 1 + +# collect pid plus all descendants (Wine/Proton games spawn several) +descendants() { + local p + for p in $(pgrep -P "$1"); do + echo "$p" + descendants "$p" + done +} +pids="$pid $(descendants "$pid")" +name=$(ps -o comm= -p "$pid") + +case "$(ps -o state= -p "$pid")" in + T*) kill -CONT $pids && notify-send -t 1500 "Resumed" "$name ($pid)" ;; + *) kill -STOP $pids && notify-send -t 1500 "Frozen" "$name ($pid)" ;; +esac diff --git a/i3/scripts/guide-freeze b/i3/scripts/guide-freeze new file mode 100755 index 0000000..a523b15 --- /dev/null +++ b/i3/scripts/guide-freeze @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# Freeze/resume the focused window on the controller's Home/guide button (BTN_MODE). +# Stdlib only: reads raw evdev events. Gamepads are readable by the seat user. +import glob, os, select, signal, struct, subprocess, time + +EV_KEY, BTN_MODE = 1, 0x13c +EVENT = struct.Struct('llHHi') # struct input_event +LONG_BITS = struct.calcsize('l') * 8 +CMD = [os.path.expanduser('~/.config/i3/scripts/freeze-toggle')] + +signal.signal(signal.SIGCHLD, signal.SIG_IGN) # auto-reap child processes + +def has_btn_mode(path): + try: + with open(f'/sys/class/input/{os.path.basename(path)}/device/capabilities/key') as f: + words = f.read().split() + except OSError: + return False + bits = 0 + for w in words: + bits = bits << LONG_BITS | int(w, 16) + return bits >> BTN_MODE & 1 + +fds = {} # fd -> device path +last = 0.0 +rescan = 0.0 +while True: + if time.monotonic() >= rescan: # pick up hotplugged controllers + for path in glob.glob('/dev/input/event*'): + if path not in fds.values() and has_btn_mode(path): + try: + fds[os.open(path, os.O_RDONLY)] = path + except OSError: + pass + rescan = time.monotonic() + 2 + ready, _, _ = select.select(list(fds), [], [], 2) + for fd in ready: + try: + data = os.read(fd, EVENT.size * 64) + except OSError: # controller unplugged + os.close(fd) + del fds[fd] + continue + for sec, usec, type_, code, value in EVENT.iter_unpack(data): + if type_ == EV_KEY and code == BTN_MODE and value == 1: + now = sec + usec / 1e6 + if now - last < 0.4: # GuliKit's Home button double-fires ~10ms apart + continue + last = now + subprocess.Popen(CMD, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) diff --git a/mesa/drirc b/mesa/drirc new file mode 100644 index 0000000..6864cb7 --- /dev/null +++ b/mesa/drirc @@ -0,0 +1,7 @@ + + + + + + diff --git a/nvim/lazy-lock.json b/nvim/lazy-lock.json index c583990..9a16249 100755 --- a/nvim/lazy-lock.json +++ b/nvim/lazy-lock.json @@ -2,19 +2,19 @@ "Comment.nvim": { "branch": "master", "commit": "e30b7f2008e52442154b66f7c519bfd2f1e32acb" }, "coq.artifacts": { "branch": "artifacts", "commit": "d545035d06408f2d68cd03bc401425e42c298fc4" }, "coq.thirdparty": { "branch": "3p", "commit": "a021e24f1ff538d339aece0de80d621b68862701" }, - "coq_nvim": { "branch": "coq", "commit": "df4eb27b425568cea17f01a25a07cf447066ce81" }, - "fzf-lua": { "branch": "main", "commit": "05e44d38de0a79c11fba5f7bf8138791b1dbdd1e" }, + "coq_nvim": { "branch": "coq", "commit": "0cdbf99893729c5ffa74064382efc53364d7045d" }, + "fzf-lua": { "branch": "main", "commit": "78b85d2a522b1e6a2d154216b3e532a1df1ad6d7" }, "gruvbox.nvim": { "branch": "main", "commit": "154eb5ff5b96d0641307113fa385eaf0d36d9796" }, "julia-vim": { "branch": "master", "commit": "52b30547346b21bc93acda28d626795667f9e087" }, "lazy.nvim": { "branch": "main", "commit": "306a05526ada86a7b30af95c5cc81ffba93fef97" }, "lualine.nvim": { "branch": "master", "commit": "221ce6b2d999187044529f49da6554a92f740a96" }, "nvim-autopairs": { "branch": "master", "commit": "430522f95fe4fb7c511ec64f8c1a90cc6a66c05c" }, "nvim-bqf": { "branch": "main", "commit": "c282a62bec6c0621a1ef5132aa3f4c9fc4dcc2c7" }, - "nvim-lspconfig": { "branch": "master", "commit": "16286347bdba1333c7d124d9de9fe6630731b2b2" }, - "nvim-tree.lua": { "branch": "master", "commit": "b2aadda94b107480c48e548d6db51c6840b7b33c" }, - "nvim-web-devicons": { "branch": "master", "commit": "5f032a85be210cd1c6ac98861eb3b187ff3bd5eb" }, + "nvim-lspconfig": { "branch": "master", "commit": "a9bb4d5f4276aa5cddc015faa3ecc5a7b5a26b14" }, + "nvim-tree.lua": { "branch": "master", "commit": "478c69c0fe253caea88de9c5e138bfa77395a59a" }, + "nvim-web-devicons": { "branch": "master", "commit": "58447c1fca354bbf184425e4a8d01deecbd6f3c4" }, "vim-fugitive": { "branch": "master", "commit": "3b753cf8c6a4dcde6edee8827d464ba9b8c4a6f0" }, "vim-ime": { "branch": "master", "commit": "79e3b02eb50a57305113550be7ca163b267b5e30" }, "viml-surround": { "branch": "master", "commit": "98358bad347aa2b8a180c2edde84c3bd8a51fdd1" }, - "vimtex": { "branch": "master", "commit": "cb81778aee678b513af3de7f1ca5943e7ee31b2f" } + "vimtex": { "branch": "master", "commit": "16a5609d17a436db7f48046d91747fd6a9d75c74" } } diff --git a/pipewire/minimal.conf b/pipewire/minimal.conf deleted file mode 100755 index 482bcce..0000000 --- a/pipewire/minimal.conf +++ /dev/null @@ -1,476 +0,0 @@ -# Simple daemon config file for PipeWire version "1.2.3" # -# -# Copy and edit this file in /etc/pipewire for system-wide changes -# or in ~/.config/pipewire for local changes. -# -# It is also possible to place a file with an updated section in -# /etc/pipewire/minimal.conf.d/ for system-wide changes or in -# ~/.config/pipewire/minimal.conf.d/ for local changes. -# - -context.properties = { - ## Configure properties in the system. - #library.name.system = support/libspa-support - #context.data-loop.library.name.system = support/libspa-support - #support.dbus = true - #link.max-buffers = 64 - link.max-buffers = 16 # version < 3 clients can't handle more - #mem.warn-mlock = false - #mem.allow-mlock = true - #mem.mlock-all = false - #clock.power-of-two-quantum = true - #log.level = 2 - #cpu.zero.denormals = false - - core.daemon = true # listening for socket connections - core.name = pipewire-0 # core name and socket name - - ## Properties for the DSP configuration. - default.clock.rate = 48000 - #default.clock.allowed-rates = [ 48000 ] - #default.clock.quantum = 1024 - #default.clock.min-quantum = 32 - #default.clock.max-quantum = 2048 - #default.clock.quantum-limit = 8192 - #default.clock.quantum-floor = 4 - #default.video.width = 640 - #default.video.height = 480 - #default.video.rate.num = 25 - #default.video.rate.denom = 1 - # - settings.check-quantum = true - settings.check-rate = true - - # This config can use udev or hardcoded ALSA devices. Make sure to - # change the alsa device below when disabling udev - minimal.use-udev = true - - # Load the pulseaudio emulation daemon - minimal.use-pulse = true -} - -context.properties.rules = [ - { matches = [ { cpu.vm.name = !null } ] - actions = { - update-props = { - # These overrides are only applied when running in a vm. - default.clock.min-quantum = 1024 - } - } - } -] - -context.spa-libs = { - # = - # - # Used to find spa factory names. It maps an spa factory name - # regular expression to a library name that should contain - # that factory. - # - audio.convert.* = audioconvert/libspa-audioconvert - audio.adapt = audioconvert/libspa-audioconvert - api.alsa.* = alsa/libspa-alsa - support.* = support/libspa-support -} - -context.modules = [ - #{ name = - # ( args = { = ... } ) - # ( flags = [ ( ifexists ) ( nofail ) ] ) - # ( condition = [ { = ... } ... ] ) - #} - # - # Loads a module with the given parameters. - # If ifexists is given, the module is ignored when it is not found. - # If nofail is given, module initialization failures are ignored. - # - - # Uses realtime scheduling to boost the audio thread priorities. This uses - # RTKit if the user doesn't have permission to use regular realtime - # scheduling. - { name = libpipewire-module-rt - args = { - nice.level = -11 - rt.prio = 88 - #rt.time.soft = -1 - #rt.time.hard = -1 - } - flags = [ ifexists nofail ] - } - - # The native communication protocol. - { name = libpipewire-module-protocol-native } - - # The profile module. Allows application to access profiler - # and performance data. It provides an interface that is used - # by pw-top and pw-profiler. - { name = libpipewire-module-profiler } - - # Allows applications to create metadata objects. It creates - # a factory for Metadata objects. - { name = libpipewire-module-metadata } - - # Creates a factory for making nodes that run in the - # context of the PipeWire server. - { name = libpipewire-module-spa-node-factory } - - { name = libpipewire-module-spa-device-factory } - - # Allows creating nodes that run in the context of the - # client. Is used by all clients that want to provide - # data to PipeWire. - { name = libpipewire-module-client-node } - - # The access module can perform access checks and block - # new clients. - { name = libpipewire-module-access - args = { - # access.allowed to list an array of paths of allowed - # apps. - #access.allowed = [ - # /usr/bin/pipewire-media-session - #] - - # An array of rejected paths. - #access.rejected = [ ] - - # An array of paths with restricted access. - #access.restricted = [ ] - - # Anything not in the above lists gets assigned the - # access.force permission. - #access.force = flatpak - } - } - - # Makes a factory for wrapping nodes in an adapter with a - # converter and resampler. - { name = libpipewire-module-adapter } - - # Makes a factory for creating links between ports. - { name = libpipewire-module-link-factory } - - { name = libpipewire-module-protocol-pulse - condition = [ { minimal.use-pulse = true } ] - } -] - -pulse.properties = { - # the addresses this server listens on - server.address = [ - "unix:native" - ] -} - -stream.properties = { - adapter.auto-port-config = { mode = dsp } -} - -context.objects = [ - #{ factory = - # ( args = { = ... } ) - # ( flags = [ ( nofail ) ] ) - # ( condition = [ { = ... } ... ] ) - #} - # - # Creates an object from a PipeWire factory with the given parameters. - # If nofail is given, errors are ignored (and no object is created). - # - #{ factory = spa-node-factory args = { factory.name = videotestsrc node.name = videotestsrc node.description = videotestsrc "Spa:Pod:Object:Param:Props:patternType" = 1 } } - #{ factory = spa-device-factory args = { factory.name = api.jack.device foo=bar } flags = [ nofail ] } - #{ factory = spa-device-factory args = { factory.name = api.alsa.enum.udev } } - #{ factory = spa-node-factory args = { factory.name = api.alsa.seq.bridge node.name = Internal-MIDI-Bridge } } - #{ factory = adapter args = { factory.name = audiotestsrc node.name = my-test node.description = audiotestsrc } } - #{ factory = spa-node-factory args = { factory.name = api.vulkan.compute.source node.name = my-compute-source } } - - # Make a default metadata store - { factory = metadata - args = { - metadata.name = default - # metadata.values = [ - # { key = default.audio.sink value = { name = somesink } } - # { key = default.audio.source value = { name = somesource } } - # ] - } - } - - # A default dummy driver. This handles nodes marked with the "node.always-process" - # property when no other driver is currently active. JACK clients need this. - { factory = spa-node-factory - args = { - factory.name = support.node.driver - node.name = Dummy-Driver - node.group = pipewire.dummy - priority.driver = 20000 - } - } - { factory = spa-node-factory - args = { - factory.name = support.node.driver - node.name = Freewheel-Driver - priority.driver = 19000 - node.group = pipewire.freewheel - node.freewheel = true - #freewheel.wait = 10 - } - } - - # This creates a ALSA udev device that will enumerate all - # ALSA devices. Because it is using ACP and has the auto-profile - # property set, this will enable a profile and create associated - # nodes, which will be automatically configured to their best - # configuration with the auto-port-config settings. - # Extra node and device params can be given with node.param and - # device.param prefixes. - { factory = spa-device-factory - args = { - factory.name = api.alsa.enum.udev - alsa.use-acp = true - device.object.properties = { - api.acp.auto-profile = true - api.acp.auto-port = true - device.object.properties = { - node.adapter = audio.adapt - resample.disable = false - adapter.auto-port-config = { - mode = dsp - monitor = false - control = false - position = preserve # unknown, aux - } - #node.param.Props = { - # channelVolumes = [ 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.6 ] - #} - } - #device.param.Profile = { - # #idx = 0 - # name = pro-audio - #} - } - } - condition = [ { minimal.use-udev = true } ] - } - - # This creates a single PCM source device for the given - # alsa device path hw:0. You can change source to sink - # to make a sink in the same way. - { factory = adapter - args = { - factory.name = api.alsa.pcm.source - node.name = "system" - node.description = "system" - media.class = "Audio/Source" - api.alsa.path = "hw:4" - #api.alsa.period-size = 0 - #api.alsa.period-num = 0 - #api.alsa.headroom = 0 - #api.alsa.start-delay = 0 - #api.alsa.disable-mmap = false - #api.alsa.disable-batch = false - #api.alsa.use-chmap = false - #api.alsa.multirate = true - #latency.internal.rate = 0 - #latency.internal.ns = 0 - #clock.name = api.alsa.0 - node.suspend-on-idle = false - #audio.format = "S32" - #audio.rate = 48000 - #audio.allowed-rates = [ ] - #audio.channels = 4 - #audio.position = [ FL FR RL RR ] - #resample.quality = 4 - resample.disable = true - #monitor.channel-volumes = false - #channelmix.normalize = false - #channelmix.mix-lfe = true - #channelmix.upmix = true - #channelmix.upmix-method = psd # none, simple - #channelmix.lfe-cutoff = 150 - #channelmix.fc-cutoff = 12000 - #channelmix.rear-delay = 12.0 - #channelmix.stereo-widen = 0.0 - #channelmix.hilbert-taps = 0 - #channelmix.disable = false - #dither.noise = 0 - #node.param.Props = { - # params = [ - # audio.channels 6 - # ] - #} - adapter.auto-port-config = { - mode = dsp - monitor = false - control = false - position = unknown # aux, preserve - } - #node.param.PortConfig = { - # direction = Output - # mode = dsp - # format = { - # mediaType = audio - # mediaSubtype = raw - # format = F32 - # rate = 48000 - # channels = 4 - # position = [ FL FR RL RR ] - # } - #} - #node.param.Props = { - # channelVolumes = [ 0.5 0.4 0.3 0.5 ] - #} - } - condition = [ { minimal.use-udev = false } ] - } - { factory = adapter - args = { - factory.name = api.alsa.pcm.sink - node.name = "system" - node.description = "system" - media.class = "Audio/Sink" - api.alsa.path = "hw:4" - #api.alsa.period-size = 0 - #api.alsa.period-num = 0 - #api.alsa.headroom = 0 - #api.alsa.start-delay = 0 - #api.alsa.disable-mmap = false - #api.alsa.disable-batch = false - #api.alsa.use-chmap = false - #api.alsa.multirate = true - #latency.internal.rate = 0 - #latency.internal.ns = 0 - #clock.name = api.alsa.0 - node.suspend-on-idle = false - #audio.format = "S32" - #audio.rate = 48000 - #audio.allowed-rates = [ ] - #audio.channels = 2 - #audio.position = "FL,FR" - #resample.quality = 4 - resample.disable = true - #channelmix.normalize = false - #channelmix.mix-lfe = true - #channelmix.upmix = true - #channelmix.upmix-method = psd # none, simple - #channelmix.lfe-cutoff = 150 - #channelmix.fc-cutoff = 12000 - #channelmix.rear-delay = 12.0 - #channelmix.stereo-widen = 0.0 - #channelmix.hilbert-taps = 0 - #channelmix.disable = false - #dither.noise = 0 - #node.param.Props = { - # params = [ - # audio.format S16 - # ] - #} - adapter.auto-port-config = { - mode = dsp - monitor = false - control = false - position = unknown # aux, preserve - } - #node.param.PortConfig = { - # direction = Input - # mode = dsp - # monitor = true - # format = { - # mediaType = audio - # mediaSubtype = raw - # format = F32 - # rate = 48000 - # channels = 4 - # } - #} - #node.param.Props = { - # channelVolumes = [ 0.5 0.4 0.3 0.5 ] - #} - } - condition = [ { minimal.use-udev = false } ] - } - # This creates a new Source node. It will have input ports - # that you can link, to provide audio for this source. - #{ factory = adapter - # args = { - # factory.name = support.null-audio-sink - # node.name = "my-mic" - # node.description = "Microphone" - # media.class = "Audio/Source/Virtual" - # audio.position = "FL,FR" - # monitor.passthrough = true - # adapter.auto-port-config = { - # mode = dsp - # monitor = true - # position = preserve # unknown, aux, preserve - # } - # } - #} - # This creates a new link between the source and the virtual - # source ports. - #{ factory = link-factory - # args = { - # link.output.node = system - # link.output.port = capture_1 - # link.input.node = my-mic - # link.input.port = input_FL - # } - #} - #{ factory = link-factory - # args = { - # link.output.node = system - # link.output.port = capture_2 - # link.input.node = my-mic - # link.input.port = input_FR - # } - #} -] - -context.exec = [ - #{ path = - # ( args = "" | [ ... ] ) - # ( condition = [ { = ... } ... ] ) - #} - # - # Execute the given program with arguments. - # - # You can optionally start the pulseaudio-server here as well - # but it is better to start it as a systemd service. - # It can be interesting to start another daemon here that listens - # on another address with the -a option (eg. -a tcp:4713). - # - ##{ path = "/usr/bin/pipewire" args = "-c pipewire-pulse.conf" } -] - -node.rules = [ - { matches = [ - { - # all keys must match the value. ! negates. ~ starts regex. - #alsa.card_name = "ICUSBAUDIO7D" - #api.alsa.pcm.stream = "playback" - } - ] - actions = { - update-props = { - #node.name = "alsa_playback.ICUSBAUDIO7D" - } - } - } -] -device.rules = [ - { matches = [ - { - #alsa.card_name = "ICUSBAUDIO7D" - } - ] - actions = { - update-props = { - #device.name = "alsa_card.ICUSBAUDIO7D" - #api.acp.auto-profile = false - #api.acp.auto-port = false - #device.param.Profile = { - # #idx = 0 - # name = off - #} - } - } - } -] diff --git a/pipewire/pipewire.conf b/pipewire/pipewire.conf deleted file mode 100755 index 8b08f9c..0000000 --- a/pipewire/pipewire.conf +++ /dev/null @@ -1,345 +0,0 @@ -# Daemon config file for PipeWire version "1.2.3" # -# -# Copy and edit this file in /etc/pipewire for system-wide changes -# or in ~/.config/pipewire for local changes. -# -# It is also possible to place a file with an updated section in -# /etc/pipewire/pipewire.conf.d/ for system-wide changes or in -# ~/.config/pipewire/pipewire.conf.d/ for local changes. -# - -context.properties = { - ## Configure properties in the system. - #library.name.system = support/libspa-support - #context.data-loop.library.name.system = support/libspa-support - #support.dbus = true - #link.max-buffers = 64 - link.max-buffers = 16 # version < 3 clients can't handle more - mem.warn-mlock = true # Gentoo should have good RLIMITs now - #mem.allow-mlock = true - #mem.mlock-all = false - #clock.power-of-two-quantum = true - #log.level = 2 - #cpu.zero.denormals = false - - #loop.rt-prio = -1 # -1 = use module-rt prio, 0 disable rt - #loop.class = data.rt - #thread.affinity = [ 0 1 ] # optional array of CPUs - #context.num-data-loops = 1 # -1 = num-cpus, 0 = no data loops - # - #context.data-loops = [ - # { loop.rt-prio = -1 - # loop.class = [ data.rt audio.rt ] - # #library.name.system = support/libspa-support - # thread.name = data-loop.0 - # #thread.affinity = [ 0 1 ] # optional array of CPUs - # } - #] - - core.daemon = true # listening for socket connections - core.name = pipewire-0 # core name and socket name - - ## Properties for the DSP configuration. - default.clock.rate = 44100 - default.clock.rates = [ 44100 96000 192000 384000] - default.clock.allowed-rates = [ 44100 96000 192000 384000] - #default.clock.quantum = 1024 - #default.clock.min-quantum = 32 - #default.clock.max-quantum = 2048 - #default.clock.quantum-limit = 8192 - #default.clock.quantum-floor = 4 - #default.video.width = 640 - #default.video.height = 480 - #default.video.rate.num = 25 - #default.video.rate.denom = 1 - # - #settings.check-quantum = false - #settings.check-rate = false - - # keys checked below to disable module loading - module.x11.bell = true - # enables autoloading of access module, when disabled an alternative - # access module needs to be loaded. - module.access = true - # enables autoloading of module-jackdbus-detect - module.jackdbus-detect = true -} - -context.properties.rules = [ - { matches = [ { cpu.vm.name = !null } ] - actions = { - update-props = { - # These overrides are only applied when running in a vm. - default.clock.min-quantum = 1024 - } - } - } -] - -context.spa-libs = { - # = - # - # Used to find spa factory names. It maps an spa factory name - # regular expression to a library name that should contain - # that factory. - # - audio.convert.* = audioconvert/libspa-audioconvert - avb.* = avb/libspa-avb - api.alsa.* = alsa/libspa-alsa - api.v4l2.* = v4l2/libspa-v4l2 - api.libcamera.* = libcamera/libspa-libcamera - api.bluez5.* = bluez5/libspa-bluez5 - api.vulkan.* = vulkan/libspa-vulkan - api.jack.* = jack/libspa-jack - support.* = support/libspa-support - video.convert.* = videoconvert/libspa-videoconvert - #videotestsrc = videotestsrc/libspa-videotestsrc - #audiotestsrc = audiotestsrc/libspa-audiotestsrc -} - -context.modules = [ - #{ name = - # ( args = { = ... } ) - # ( flags = [ ( ifexists ) ( nofail ) ] ) - # ( condition = [ { = ... } ... ] ) - #} - # - # Loads a module with the given parameters. - # If ifexists is given, the module is ignored when it is not found. - # If nofail is given, module initialization failures are ignored. - # If condition is given, the module is loaded only when the context - # properties all match the match rules. - # - - # Uses realtime scheduling to boost the audio thread priorities. This uses - # RTKit if the user doesn't have permission to use regular realtime - # scheduling. You can also clamp utilisation values to improve scheduling - # on embedded and heterogeneous systems, e.g. Arm big.LITTLE devices. - { name = libpipewire-module-rt - args = { - nice.level = -11 - rt.prio = 88 - #rt.time.soft = -1 - #rt.time.hard = -1 - #uclamp.min = 0 - #uclamp.max = 1024 - } - flags = [ ifexists nofail ] - } - - # The native communication protocol. - { name = libpipewire-module-protocol-native - args = { - # List of server Unix sockets, and optionally permissions - #sockets = [ { name = "pipewire-0" }, { name = "pipewire-0-manager" } ] - } - } - - # The profile module. Allows application to access profiler - # and performance data. It provides an interface that is used - # by pw-top and pw-profiler. - { name = libpipewire-module-profiler } - - # Allows applications to create metadata objects. It creates - # a factory for Metadata objects. - { name = libpipewire-module-metadata } - - # Creates a factory for making devices that run in the - # context of the PipeWire server. - { name = libpipewire-module-spa-device-factory } - - # Creates a factory for making nodes that run in the - # context of the PipeWire server. - { name = libpipewire-module-spa-node-factory } - - # Allows creating nodes that run in the context of the - # client. Is used by all clients that want to provide - # data to PipeWire. - { name = libpipewire-module-client-node } - - # Allows creating devices that run in the context of the - # client. Is used by the session manager. - { name = libpipewire-module-client-device } - - # The portal module monitors the PID of the portal process - # and tags connections with the same PID as portal - # connections. - { name = libpipewire-module-portal - flags = [ ifexists nofail ] - } - - # The access module can perform access checks and block - # new clients. - { name = libpipewire-module-access - args = { - # Socket-specific access permissions - #access.socket = { pipewire-0 = "default", pipewire-0-manager = "unrestricted" } - - # Deprecated legacy mode (not socket-based), - # for now enabled by default if access.socket is not specified - #access.legacy = true - } - condition = [ { module.access = true } ] - } - - # Makes a factory for wrapping nodes in an adapter with a - # converter and resampler. - { name = libpipewire-module-adapter } - - # Makes a factory for creating links between ports. - { name = libpipewire-module-link-factory } - - # Provides factories to make session manager objects. - { name = libpipewire-module-session-manager } - - # Use libcanberra to play X11 Bell - { name = libpipewire-module-x11-bell - args = { - #sink.name = "@DEFAULT_SINK@" - #sample.name = "bell-window-system" - #x11.display = null - #x11.xauthority = null - } - flags = [ ifexists nofail ] - condition = [ { module.x11.bell = true } ] - } - { name = libpipewire-module-jackdbus-detect - args = { - #jack.library = libjack.so.0 - #jack.server = null - #jack.client-name = PipeWire - #jack.connect = true - #tunnel.mode = duplex # source|sink|duplex - source.props = { - #audio.channels = 2 - #midi.ports = 1 - #audio.position = [ FL FR ] - # extra sink properties - } - sink.props = { - #audio.channels = 2 - #midi.ports = 1 - #audio.position = [ FL FR ] - # extra sink properties - } - } - flags = [ ifexists nofail ] - condition = [ { module.jackdbus-detect = true } ] - } -] - -context.objects = [ - #{ factory = - # ( args = { = ... } ) - # ( flags = [ ( nofail ) ] ) - # ( condition = [ { = ... } ... ] ) - #} - # - # Creates an object from a PipeWire factory with the given parameters. - # If nofail is given, errors are ignored (and no object is created). - # If condition is given, the object is created only when the context properties - # all match the match rules. - # - #{ factory = spa-node-factory args = { factory.name = videotestsrc node.name = videotestsrc node.description = videotestsrc "Spa:Pod:Object:Param:Props:patternType" = 1 } } - #{ factory = spa-device-factory args = { factory.name = api.jack.device foo=bar } flags = [ nofail ] } - #{ factory = spa-device-factory args = { factory.name = api.alsa.enum.udev } } - #{ factory = spa-node-factory args = { factory.name = api.alsa.seq.bridge node.name = Internal-MIDI-Bridge } } - #{ factory = adapter args = { factory.name = audiotestsrc node.name = my-test node.description = audiotestsrc } } - #{ factory = spa-node-factory args = { factory.name = api.vulkan.compute.source node.name = my-compute-source } } - - # A default dummy driver. This handles nodes marked with the "node.always-process" - # property when no other driver is currently active. JACK clients need this. - { factory = spa-node-factory - args = { - factory.name = support.node.driver - node.name = Dummy-Driver - node.group = pipewire.dummy - node.sync-group = sync.dummy - priority.driver = 200000 - #clock.id = monotonic # realtime | tai | monotonic-raw | boottime - #clock.name = "clock.system.monotonic" - } - } - { factory = spa-node-factory - args = { - factory.name = support.node.driver - node.name = Freewheel-Driver - priority.driver = 190000 - node.group = pipewire.freewheel - node.sync-group = sync.dummy - node.freewheel = true - #freewheel.wait = 10 - } - } - - # This creates a new Source node. It will have input ports - # that you can link, to provide audio for this source. - #{ factory = adapter - # args = { - # factory.name = support.null-audio-sink - # node.name = "my-mic" - # node.description = "Microphone" - # media.class = "Audio/Source/Virtual" - # audio.position = "FL,FR" - # monitor.passthrough = true - # } - #} - - # This creates a single PCM source device for the given - # alsa device path hw:0. You can change source to sink - # to make a sink in the same way. - #{ factory = adapter - # args = { - # factory.name = api.alsa.pcm.source - # node.name = "alsa-source" - # node.description = "PCM Source" - # media.class = "Audio/Source" - # api.alsa.path = "hw:0" - # api.alsa.period-size = 1024 - # api.alsa.headroom = 0 - # api.alsa.disable-mmap = false - # api.alsa.disable-batch = false - # audio.format = "S16LE" - # audio.rate = 48000 - # audio.channels = 2 - # audio.position = "FL,FR" - # } - #} - - # Use the metadata factory to create metadata and some default values. - #{ factory = metadata - # args = { - # metadata.name = my-metadata - # metadata.values = [ - # { key = default.audio.sink value = { name = somesink } } - # { key = default.audio.source value = { name = somesource } } - # ] - # } - #} -] - -context.exec = [ - #{ path = - # ( args = "" | [ ... ] ) - # ( condition = [ { = ... } ... ] ) - #} - # - # Execute the given program with arguments. - # If condition is given, the program is executed only when the context - # properties all match the match rules. - # - # You can optionally start the session manager here, - # but it is better to start it as a systemd service. - # Run the session manager with -h for options. - # - #{ path = "/usr/bin/pipewire-media-session" args = "" - # condition = [ { exec.session-manager = null } { exec.session-manager = true } ] } - # - # You can optionally start the pulseaudio-server here as well - # but it is better to start it as a systemd service. - # It can be interesting to start another daemon here that listens - # on another address with the -a option (eg. -a tcp:4713). - # - #{ path = "/usr/bin/pipewire" args = [ "-c" "pipewire-pulse.conf" ] - # condition = [ { exec.pipewire-pulse = null } { exec.pipewire-pulse = true } ] } -] diff --git a/sway/config b/sway/config index 2f26caa..6a3ba77 100755 --- a/sway/config +++ b/sway/config @@ -39,7 +39,6 @@ exec --no-startup-id ~/.config/sway/scripts/wallpaper-rotate $wallpaper_dir # adaptive_sync on the desktop makes sway stutter/flicker; 120Hz avoids AMD # VRAM clock flicker at high refresh (see README). output DP-1 mode 1920x1080@240Hz adaptive_sync off allow_tearing yes -exec_always pkill -xf '/bin/sh /home/nnduc/.config/sway/scripts/vrr-follow'; ~/.config/sway/scripts/vrr-follow #output DP-1 mode 1920x1080@240Hz adaptive_sync on allow_tearing yes ### Idle configuration diff --git a/sway/scripts/lock-now b/sway/scripts/lock-now index 16d78b0..e9a17fa 100755 --- a/sway/scripts/lock-now +++ b/sway/scripts/lock-now @@ -3,6 +3,6 @@ wall_dir="${1:?wallpaper directory required}" img=$(find "$wall_dir" -type f \( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" \) | shuf -n1) swaymsg -q output DP-1 adaptive_sync off # lock screen redraws rarely: fixed 240 Hz swaylock -f ${img:+-i "$img"} -# once unlocked, let vrr-follow decide again (in background so swayidle/suspend aren't held up) +# once unlocked, turn VRR back on if a game (gamemode) is still running ( while pgrep -x swaylock >/dev/null; do sleep 1; done - ~/.config/sway/scripts/vrr-follow once ) & + gamemoded -s | grep -q 'is active' && swaymsg -q output DP-1 adaptive_sync on ) & diff --git a/sway/scripts/vrr-follow b/sway/scripts/vrr-follow deleted file mode 100755 index 5fc9f27..0000000 --- a/sway/scripts/vrr-follow +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh -# adaptive_sync on DP-1 only while the visible workspace shows a fullscreen window -OUT=DP-1 -update() { - if [ "$(swaymsg -t get_tree | jq --arg o "$OUT" ' - .nodes[] | select(.name==$o) as $out | $out.nodes[] - | select(.name==$out.current_workspace) - | [.. | objects | select((.fullscreen_mode? // 0) > 0)] | length')" -gt 0 ] - then s=on; else s=off; fi - [ "$s" != "$last" ] && swaymsg -q output "$OUT" adaptive_sync "$s" && last=$s -} -[ "$1" = once ] && { update; exit; } -update -swaymsg -t subscribe -m '["workspace","window"]' | while read -r _; do update; done diff --git a/waybar/config b/waybar/config index c3cb358..b40c247 100755 --- a/waybar/config +++ b/waybar/config @@ -23,6 +23,7 @@ "clock": { // "timezone": "America/New_York", "tooltip-format": "{:%Y %B}\n{calendar}", + "format": "{:%Y-%m-%d %H:%M}", "format-alt": "{:%Y-%m-%d %H:%M}" }, "bluetooth": { diff --git a/xorg/20-amdgpu.conf b/xorg/20-amdgpu.conf new file mode 100644 index 0000000..806ea1d --- /dev/null +++ b/xorg/20-amdgpu.conf @@ -0,0 +1,5 @@ +Section "Device" + Identifier "AMD" + Driver "amdgpu" + Option "VariableRefresh" "true" +EndSection