d4r: official NVIDIA DLSS on AMD GPUs under Linux and Proton

This commit is contained in:
ayo committed 2026-09-27 15:26:56 -04:00
commit 118173ecd7
128 files changed
+31333

No files matched your search

+170
View File
@@ -0,0 +1,170 @@
#!/usr/bin/env bash
set -euo pipefail
# Launches a Windows game through a GE-Proton build's integrated OptiScaler
# (PROTON_USE_OPTISCALER) with OptiScaler's DLSS backend routed into the d4r
# shim, i.e. official NVIDIA DLSS executed through the Wine CUDA bridge and
# ZLUDA on the AMD GPU.
#
# usage: run_game_d4r_optiscaler_proton.sh PROTON_DIR COMPAT_DATA_DIR GAME_EXE [ARGS...]
#
# The Proton installation and game directory are not modified. The prefix's
# system32/umu/OptiScaler.ini (rewritten by GE-Proton from
# PROTON_OPTISCALER_CONFIG) is restored when the game exits, and the real
# DXVK-NVAPI is placed next to the identity bridge as d4r_nvapi64_real.dll.
# With D4R_VKD3D_DIR (a directory holding d3d12.dll and d3d12core.dll, e.g. the
# d4r-patched vkd3d-proton), those replace Proton's vkd3d-proton in the prefix
# for this launch; the prefix's previous copies are backed up to the log
# directory and restored when the game exits.
# With D4R_UE_ENGINE_INI (the game's Saved/Config/Windows/Engine.ini) and
# D4R_UE_CVARS ("r.A=0;r.B=1"), those console variables are added to its
# [SystemSettings] for this launch only: an existing file is backed up to the
# log directory and restored on exit, a new one is removed again. Unreal games
# apply tonemapper sharpening and chromatic fringe after the upscaler; e.g.
# Townfall's r.Tonemapper.Sharpen and r.SceneColorFringeQuality turn DLSS's
# clean thin lines into haloed, stippled and beaded ones.
# Logs go to D4R_GAME_LOG_DIR (default: a timestamped capture directory).
if [[ $# -lt 3 ]]; then
printf 'usage: %s PROTON_DIR COMPAT_DATA_DIR GAME_EXE [ARGS...]\n' "$0" >&2
exit 2
fi
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
PROTON_DIR="$(realpath "$1")"
COMPAT_DIR="$(realpath "$2")"
GAME_EXE="$(realpath "$3")"
shift 3
# User settings from d4r.ini (config/d4r.ini.default documents them); explicit environment wins.
# D4R_NO_CONFIG=1 ignores the file (automated tests); D4R_CONFIG picks another file.
if [[ "${D4R_NO_CONFIG:-0}" != 1 ]]; then
python3 "$ROOT/scripts/d4r_config.py" --print >&2 || true
D4R_CONFIG_EXPORTS="$(python3 "$ROOT/scripts/d4r_config.py")" || exit 2
eval "$D4R_CONFIG_EXPORTS"
fi
source "$ROOT/scripts/d4r_proton_env.sh"
PREFIX="$COMPAT_DIR/pfx"
UMU="$PREFIX/drive_c/windows/system32/umu"
LOG_DIR="${D4R_GAME_LOG_DIR:-$HOME/.cache/d4r-dlss-captures/game-$(basename "$GAME_EXE" .exe)-$(date +%Y%m%d-%H%M%S)}"
mkdir -p "$LOG_DIR"
[[ -d "$PREFIX" ]] || { printf 'No Proton prefix at %s\n' "$PREFIX" >&2; exit 2; }
# A second launch would join the running wineserver, and whichever run exits
# first would restore its OptiScaler.ini backup underneath the other.
SERVER_DIR="/tmp/.wine-$(id -u)/server-$(stat -c '%D' "$PREFIX" | sed 's/^0*//')-$(printf '%x' "$(stat -c '%i' "$PREFIX")")"
if [[ -S "$SERVER_DIR/socket" ]] && fuser "$SERVER_DIR/socket" >/dev/null 2>&1; then
printf 'A wineserver is already running for %s; close that game first\n' "$PREFIX" >&2
exit 2
fi
"$ROOT/scripts/build_d4r_nvapi_identity.sh" >/dev/null
NVAPI_REAL="$PROTON_DIR/files/lib/wine/nvapi/x86_64-windows/nvapi64.dll"
[[ -f "$NVAPI_REAL" ]] || { printf 'Cannot find DXVK-NVAPI at %s\n' "$NVAPI_REAL" >&2; exit 2; }
cp -f "$NVAPI_REAL" "$PREFIX/drive_c/windows/system32/d4r_nvapi64_real.dll"
# Temporary launcher: identical to the Proton build except that the NVAPI copy
# installs the identity bridge (so OptiScaler enables its DLSS backend).
LAUNCHER_DIR="$(mktemp -d "${TMPDIR:-/tmp}/d4r-game-proton.XXXXXXXX")"
for entry in "$PROTON_DIR"/*; do
[[ "$(basename "$entry")" == proton ]] || ln -s "$entry" "$LAUNCHER_DIR/$(basename "$entry")"
done
cp -f "$PROTON_DIR/proton" "$LAUNCHER_DIR/proton"
LAUNCHER="$LAUNCHER_DIR/proton" python3 - <<'PY'
import os
from pathlib import Path
path = Path(os.environ["LAUNCHER"])
source = path.read_text()
variants = [
'g_proton.arch_pe_dir("wine/nvapi", False) + "nvapi64.dll"',
'g_proton.arch_pe_dir("wine/" + nvapi_path, False) + "nvapi64.dll"',
]
for old in variants:
target = f'try_copy({old}, "drive_c/windows/system32",'
if source.count(target) == 1:
new = f'try_copy(os.environ.get("D4R_NVAPI_IDENTITY_DLL", {old}), "drive_c/windows/system32",'
path.write_text(source.replace(target, new))
break
else:
raise SystemExit("Proton NVAPI copy point not found; cannot stage identity bridge")
if os.environ.get("D4R_VKD3D_DIR"):
source = path.read_text()
old = 'try_copy(g_proton.arch_pe_dir("wine/vkd3d-proton", False) + f + ".dll", "drive_c/windows/system32",'
new = ('try_copy(os.path.join(os.environ["D4R_VKD3D_DIR"], f + ".dll"), "drive_c/windows/system32",')
if source.count(old) != 1:
raise SystemExit("Proton vkd3d-proton copy point not found; cannot stage D4R_VKD3D_DIR")
path.write_text(source.replace(old, new))
PY
# GE-Proton rewrites OptiScaler.ini from PROTON_OPTISCALER_CONFIG; keep the
# user's configuration and put it back afterwards.
INI="$UMU/OptiScaler.ini"
[[ -f "$INI" ]] && cp -f "$INI" "$LOG_DIR/OptiScaler.ini.before"
VKD3D_BACKUP="$LOG_DIR/vkd3d-prefix-backup"
if [[ -n "${D4R_VKD3D_DIR:-}" ]]; then
export D4R_VKD3D_DIR="$(realpath "$D4R_VKD3D_DIR")"
for dll in d3d12.dll d3d12core.dll; do
[[ -f "$D4R_VKD3D_DIR/$dll" ]] || { printf 'D4R_VKD3D_DIR lacks %s\n' "$dll" >&2; exit 2; }
done
mkdir -p "$VKD3D_BACKUP"
for dll in d3d12.dll d3d12core.dll; do
[[ -f "$PREFIX/drive_c/windows/system32/$dll" ]] && cp -f "$PREFIX/drive_c/windows/system32/$dll" "$VKD3D_BACKUP/$dll"
done
fi
ENGINE_INI="${D4R_UE_ENGINE_INI:-}"
ENGINE_INI_STATE=""
restore_prefix() {
[[ -f "$LOG_DIR/OptiScaler.ini.before" ]] && cp -f "$LOG_DIR/OptiScaler.ini.before" "$INI"
case "$ENGINE_INI_STATE" in
restore) cp -f "$LOG_DIR/Engine.ini.before" "$ENGINE_INI" ;;
remove) rm -f "$ENGINE_INI" ;;
esac
if [[ -n "${D4R_VKD3D_DIR:-}" ]]; then
for dll in d3d12.dll d3d12core.dll; do
[[ -f "$VKD3D_BACKUP/$dll" ]] && cp -f "$VKD3D_BACKUP/$dll" "$PREFIX/drive_c/windows/system32/$dll"
done
fi
rm -rf "$LAUNCHER_DIR"
}
trap restore_prefix EXIT
if [[ -n "$ENGINE_INI" && -n "${D4R_UE_CVARS:-}" ]]; then
if [[ -f "$ENGINE_INI" ]]; then
cp -f "$ENGINE_INI" "$LOG_DIR/Engine.ini.before"
ENGINE_INI_STATE=restore
else
mkdir -p "$(dirname "$ENGINE_INI")"
ENGINE_INI_STATE=remove
fi
{ printf '\n[SystemSettings]\n'; tr ';' '\n' <<<"$D4R_UE_CVARS"; printf '\n'; } >> "$ENGINE_INI"
fi
OPTI_CONFIG="Upscalers.Dx12Upscaler=dlss;DLSS.Enabled=true"
OPTI_CONFIG+=";Libraries.NvngxPath=$(d4r_winpath "$D4R_RUNTIME_DIR/bin/d4r_nvngx.dll")"
OPTI_CONFIG+=";Libraries.NvngxDlssPath=$(d4r_winpath "$D4R_RUNTIME_DIR/bin")"
# Game-side DLSS inputs stay hooked; only NGX loads outside the game directory
# are redirected, which lets the shim request the official core by an
# exe-relative path.
OPTI_CONFIG+=";Inputs.EnableDlssInputs=true;Hooks.HookOriginalNvngxOnly=true"
OPTI_CONFIG+=";NvApi.OverrideNvapiDll=false;FrameGen.Enabled=false;FrameGen.FGInput=nofg;FrameGen.FGOutput=nofg"
OPTI_CONFIG+=";Log.LogToFile=true;Log.LogLevel=${D4R_OPTISCALER_LOG_LEVEL:-2}"
# Detailed FPS overlay with frame-time graph (the user's own INI default too).
OPTI_CONFIG+=";Menu.ShowFps=${D4R_OPTISCALER_SHOW_FPS:-true};Menu.FpsOverlayType=${D4R_OPTISCALER_FPS_OVERLAY_TYPE:-3}"
OPTI_CONFIG+="${D4R_EXTRA_OPTISCALER_CONFIG:+;$D4R_EXTRA_OPTISCALER_CONFIG}"
export PROTON_USE_OPTISCALER=1
export PROTON_OPTISCALER_CONFIG="$OPTI_CONFIG"
export D4R_NVAPI_IDENTITY_DLL="$ROOT/build/nvapi64.dll"
export DXVK_CONFIG="${DXVK_CONFIG:-dxgi.customVendorId = 10de}"
export D4R_SHIM_LOG="$(d4r_winpath "$LOG_DIR/d4r_nvngx.log")"
export STEAM_COMPAT_CLIENT_INSTALL_PATH="${STEAM_COMPAT_CLIENT_INSTALL_PATH:-$HOME/.local/share/Steam}"
export STEAM_COMPAT_DATA_PATH="$COMPAT_DIR"
export PROTON_LOG=1 PROTON_LOG_DIR="$LOG_DIR"
export WINEDEBUG="${WINEDEBUG:--all}"
# Games load dozens of modules per launch; do not capture them unless asked.
export D4R_CUDA_CAPTURE="${D4R_CUDA_CAPTURE:-0}"
printf 'd4r game launch: %s\nlogs: %s\n' "$GAME_EXE" "$LOG_DIR"
status=0
(cd "$(dirname "$GAME_EXE")" && "$LAUNCHER_DIR/proton" waitforexitandrun "$GAME_EXE" "$@") \
>"$LOG_DIR/launcher.log" 2>&1 || status=$?
cp -f "$UMU/OptiScaler.log" "$LOG_DIR/OptiScaler.log" 2>/dev/null || true
printf 'game exited with status %s\n' "$status"
exit "$status"