646 lines
28 KiB
Bash
Executable file
646 lines
28 KiB
Bash
Executable file
#!/usr/bin/env bash
|
||
# ==============================================================================
|
||
# DaVinci Resolve Installer for Omarchy (Arch Linux + Hyprland + Intel Arc)
|
||
#
|
||
# Adapted from the NVIDIA-Open variant for systems with an Intel Arc GPU
|
||
# using the Xe kernel driver. Covers all current Arc-class hardware:
|
||
# - Alchemist Xe-HPG dGPU (Arc Axxx, e.g. A770) — xe driver
|
||
# - Battlemage Xe2 dGPU (Arc B580/B570, late 2024) — xe driver
|
||
# - Panther Lake Xe3-LPG iGPU (Arc B360/B370/B380/B390, — xe driver
|
||
# Core Ultra 300 series, launched Jan 2026)
|
||
# Older iGPUs (UHD/Iris on Gen 9–12.5) sit on the i915 driver and are
|
||
# also detected; they're left visible to OpenCL but not preferred for
|
||
# pinning when an xe device is available.
|
||
#
|
||
# IMPORTANT — SUPPORT CAVEAT
|
||
# Blackmagic does NOT officially support Intel GPUs on Linux. Resolve uses
|
||
# OpenCL (or CUDA on NVIDIA) for compute. With intel-compute-runtime
|
||
# installed, the Arc shows up as an OpenCL device and basic editing /
|
||
# playback / transcode often work, but specific effects, Neural Engine,
|
||
# Fairlight FX, and noise reduction may fall back to CPU or fail. Treat
|
||
# this as community/experimental — not a supported configuration.
|
||
#
|
||
# What this script does (same shape as the NVIDIA version):
|
||
# 1. Finds the Resolve ZIP in ~/Downloads/
|
||
# 2. Installs system + Intel GPU runtime dependencies
|
||
# 3. Extracts the ZIP → .run → squashfs-root (AppImage payload)
|
||
# 4. Replaces bundled glib/gio/gmodule with system versions (ABI-safe)
|
||
# 5. Keeps vendor libc++/libc++abi (removing these breaks Resolve)
|
||
# 6. Installs to /opt/resolve with RPATH patching for all ELF binaries
|
||
# 7. Ensures legacy libcrypt.so.1 is available (Arch dropped it)
|
||
# 8. Installs desktop entries, icons, and udev rules
|
||
# 9. Creates an XWayland wrapper script for Hyprland with Intel/OpenCL hints
|
||
#
|
||
# Prerequisites:
|
||
# - Omarchy (Arch Linux) with kernel 6.12+ (Xe driver). 6.19+ recommended
|
||
# for stable Battlemage support.
|
||
# - DaVinci Resolve Linux ZIP downloaded to ~/Downloads/
|
||
# - Internet connection
|
||
# ==============================================================================
|
||
|
||
set -euo pipefail
|
||
|
||
log(){ echo -e "▶ $*"; }
|
||
warn(){ echo -e "⚠️ $*" >&2; }
|
||
err(){ echo -e "❌ $*" >&2; exit 1; }
|
||
|
||
# ==================== Preflight: kernel + GPU ====================
|
||
KVER_FULL="$(uname -r)"
|
||
KMAJ="${KVER_FULL%%.*}"; KREST="${KVER_FULL#*.}"; KMIN="${KREST%%.*}"
|
||
if (( KMAJ < 6 )) || { (( KMAJ == 6 )) && (( KMIN < 12 )); }; then
|
||
warn "Kernel ${KVER_FULL} is older than 6.12 — Arc B580 (Battlemage) needs the Xe driver from 6.12+. Continue at your own risk."
|
||
else
|
||
log "Kernel ${KVER_FULL} OK for Xe driver"
|
||
fi
|
||
|
||
if lspci -nn 2>/dev/null | grep -qiE 'Intel.*(Arc|Battlemage|Alchemist|DG2)'; then
|
||
log "Intel Arc GPU detected"
|
||
else
|
||
warn "No Intel Arc GPU detected via lspci — script will continue but you may not have GPU acceleration"
|
||
fi
|
||
|
||
# ==================== Locate ZIP ====================
|
||
ZIP_DIR="${HOME}/Downloads"
|
||
shopt -s nullglob
|
||
ZIP_FILES=("${ZIP_DIR}"/DaVinci_Resolve*_Linux.zip)
|
||
shopt -u nullglob
|
||
if [[ ${#ZIP_FILES[@]} -eq 0 ]]; then
|
||
err "Put the official DaVinci Resolve Linux ZIP in ${ZIP_DIR}"
|
||
fi
|
||
# Pick the newest ZIP by mtime without parsing `ls` output.
|
||
RESOLVE_ZIP=""
|
||
RESOLVE_ZIP_MTIME=0
|
||
for _zf in "${ZIP_FILES[@]}"; do
|
||
_m=$(stat -c%Y "${_zf}" 2>/dev/null || echo 0)
|
||
if (( _m > RESOLVE_ZIP_MTIME )); then
|
||
RESOLVE_ZIP_MTIME=${_m}
|
||
RESOLVE_ZIP="${_zf}"
|
||
fi
|
||
done
|
||
[[ -n "${RESOLVE_ZIP}" ]] || err "Could not determine newest ZIP file"
|
||
log "Using installer ZIP: ${RESOLVE_ZIP}"
|
||
|
||
# ==================== Package Installation ====================
|
||
if [[ "${RESOLVE_FULL_UPGRADE:-0}" == "1" ]]; then
|
||
log "Updating system packages (RESOLVE_FULL_UPGRADE=1)..."
|
||
sudo pacman -Syu --noconfirm
|
||
else
|
||
log "Skipping full system upgrade (set RESOLVE_FULL_UPGRADE=1 to enable)"
|
||
sudo pacman -Sy --noconfirm
|
||
fi
|
||
|
||
# Build/extraction tools (same as NVIDIA variant)
|
||
log "Installing required tools..."
|
||
if ! sudo pacman -S --needed --noconfirm unzip patchelf libarchive xdg-user-dirs desktop-file-utils file gtk-update-icon-cache; then
|
||
warn "Some optional tools failed to install, continuing anyway..."
|
||
fi
|
||
|
||
# Runtime dependencies — Intel-specific stack added on top of the common set:
|
||
# libxcrypt-compat: Provides legacy libcrypt.so.1 (Arch moved to v2)
|
||
# ffmpeg4.4: Older FFmpeg version Resolve links against
|
||
# glu / gtk2 / fuse2: Same as NVIDIA variant (UI / AppImage compat)
|
||
# mesa: OpenGL stack (Iris driver for Intel)
|
||
# vulkan-intel: ANV — Vulkan ICD for Intel
|
||
# intel-media-driver: iHD VA-API driver (HW decode/encode H.264/HEVC/AV1)
|
||
# intel-compute-runtime: NEO — OpenCL (and Level Zero) ICD for Intel GPUs.
|
||
# This is what Resolve will actually use for compute.
|
||
# level-zero-loader: Loader for Intel oneAPI Level Zero (used by NEO)
|
||
# ocl-icd: Generic OpenCL ICD loader (libOpenCL.so.1)
|
||
# clinfo: Sanity tool — used at the end to confirm Resolve
|
||
# will see the Arc as an OpenCL device.
|
||
log "Installing Intel GPU + Resolve runtime dependencies..."
|
||
INTEL_RUNTIME_PKGS=(
|
||
libxcrypt-compat ffmpeg4.4 glu fuse2
|
||
mesa vulkan-intel intel-media-driver
|
||
intel-compute-runtime level-zero-loader ocl-icd clinfo
|
||
)
|
||
# Note: NOT installing gtk2 — verified via ldd that Resolve 21 does not link
|
||
# libgtk-x11-2.0. It links libgdk_pixbuf-2.0 (a different package, gdk-pixbuf2,
|
||
# already pulled in by virtually any desktop install). The gtk2 dep in older
|
||
# Resolve install scripts was a leftover from when Resolve had GTK file
|
||
# dialogs — modern Resolve is fully Qt.
|
||
if ! sudo pacman -S --needed --noconfirm "${INTEL_RUNTIME_PKGS[@]}"; then
|
||
err "pacman failed to install one or more runtime dependencies — re-run with the offending package name to see the error"
|
||
fi
|
||
# Compute-stack packages are non-negotiable: without them Resolve sees no
|
||
# OpenCL device and crashes with "Unsupported GPU Processing Mode" on first
|
||
# launch. Verify each is actually present before continuing.
|
||
for pkg in intel-compute-runtime level-zero-loader ocl-icd clinfo; do
|
||
pacman -Q "$pkg" >/dev/null 2>&1 || err "Required package not installed: ${pkg}"
|
||
done
|
||
log "Intel compute stack confirmed installed"
|
||
|
||
# Resolve's built-in extras downloader expects RHEL-style cert path
|
||
if [[ ! -e /etc/pki/tls ]]; then
|
||
sudo mkdir -p /etc/pki
|
||
sudo ln -sf /etc/ssl /etc/pki/tls
|
||
fi
|
||
|
||
# ==================== Extraction ====================
|
||
NEEDED_GB=10
|
||
FREE_KB=$(df --output=avail -k "${ZIP_DIR}" | tail -n1); FREE_GB=$((FREE_KB/1024/1024))
|
||
(( FREE_GB >= NEEDED_GB )) || err "Not enough free space in ${ZIP_DIR}: ${FREE_GB} GiB < ${NEEDED_GB} GiB"
|
||
|
||
WORKDIR="$(mktemp -d -p "${ZIP_DIR}" .resolve-extract-XXXXXXXX)"
|
||
cleanup() {
|
||
if [[ -n "${WORKDIR:-}" && -d "${WORKDIR}" ]]; then
|
||
log "Cleaning up temporary directory..."
|
||
rm -rf "${WORKDIR}" 2>/dev/null || true
|
||
fi
|
||
}
|
||
trap cleanup EXIT
|
||
log "Unpacking ZIP to ${WORKDIR}…"
|
||
unzip -q "${RESOLVE_ZIP}" -d "${WORKDIR}"
|
||
|
||
RUN_FILE="$(find "${WORKDIR}" -maxdepth 2 -type f -name 'DaVinci_Resolve_*_Linux.run' | head -n1 || true)"
|
||
[[ -n "${RUN_FILE}" ]] || err "Could not find the .run installer in the ZIP"
|
||
chmod +x "${RUN_FILE}"
|
||
|
||
EX_DIR="$(dirname "${RUN_FILE}")"
|
||
log "Extracting AppImage payload…"
|
||
if ! ( cd "${EX_DIR}" && "./$(basename "${RUN_FILE}")" --appimage-extract >/dev/null ); then
|
||
err "Failed to extract AppImage payload"
|
||
fi
|
||
APPDIR="${EX_DIR}/squashfs-root"
|
||
[[ -d "${APPDIR}" ]] || err "Extraction failed (no squashfs-root)"
|
||
|
||
chmod -R u+rwX,go+rX,go-w "${APPDIR}" || warn "Could not normalize all permissions"
|
||
[[ -s "${APPDIR}/bin/resolve" ]] || err "resolve binary missing or zero-size"
|
||
|
||
# ==================== ABI-Safe Library Replacement ====================
|
||
# (Identical to NVIDIA variant — these are GPU-agnostic.)
|
||
pushd "${APPDIR}" >/dev/null
|
||
|
||
declare -A GLIB_LIBS=(
|
||
["/usr/lib/libglib-2.0.so.0"]="libs/libglib-2.0.so.0"
|
||
["/usr/lib/libgio-2.0.so.0"]="libs/libgio-2.0.so.0"
|
||
["/usr/lib/libgmodule-2.0.so.0"]="libs/libgmodule-2.0.so.0"
|
||
)
|
||
for syslib in "${!GLIB_LIBS[@]}"; do
|
||
target="${GLIB_LIBS[$syslib]}"
|
||
if [[ -e "${syslib}" ]]; then
|
||
rm -f "${target}" || true
|
||
ln -sf "${syslib}" "${target}" || warn "Failed to symlink ${syslib}"
|
||
else
|
||
warn "System library ${syslib} not found, keeping bundled version"
|
||
fi
|
||
done
|
||
|
||
if [[ -d "share/panels" ]]; then
|
||
pushd "share/panels" >/dev/null
|
||
tar -zxf dvpanel-framework-linux-x86_64.tgz 2>/dev/null || true
|
||
mkdir -p "${APPDIR}/libs"
|
||
find . -maxdepth 1 -type f -name '*.so' -exec mv -f {} "${APPDIR}/libs" \; 2>/dev/null || true
|
||
if [[ -d lib ]]; then
|
||
find lib -type f -name '*.so*' -exec mv -f {} "${APPDIR}/libs" \; 2>/dev/null || true
|
||
fi
|
||
popd >/dev/null
|
||
fi
|
||
|
||
rm -f "AppRun" "AppRun*" 2>/dev/null || true
|
||
rm -rf "installer" "installer*" 2>/dev/null || true
|
||
mkdir -p "bin"
|
||
ln -sf "../BlackmagicRAWPlayer/BlackmagicRawAPI" "bin/" 2>/dev/null || true
|
||
popd >/dev/null
|
||
|
||
# ==================== Install to /opt/resolve ====================
|
||
log "Installing Resolve to /opt/resolve…"
|
||
sudo rm -rf /opt/resolve
|
||
sudo mkdir -p /opt/resolve
|
||
if command -v rsync >/dev/null 2>&1; then
|
||
sudo rsync -a --delete "${APPDIR}/" /opt/resolve/
|
||
else
|
||
sudo cp -a "${APPDIR}/." /opt/resolve/
|
||
fi
|
||
sudo mkdir -p /opt/resolve/.license
|
||
|
||
log "Applying RPATH with patchelf (this may take a while for large libraries)…"
|
||
RPATH_DIRS=( "libs" "libs/plugins/sqldrivers" "libs/plugins/xcbglintegrations" "libs/plugins/imageformats"
|
||
"libs/plugins/platforms" "libs/Fusion" "plugins" "bin"
|
||
"BlackmagicRAWSpeedTest/BlackmagicRawAPI" "BlackmagicRAWSpeedTest/plugins/platforms"
|
||
"BlackmagicRAWSpeedTest/plugins/imageformats" "BlackmagicRAWSpeedTest/plugins/mediaservice"
|
||
"BlackmagicRAWSpeedTest/plugins/audio" "BlackmagicRAWSpeedTest/plugins/xcbglintegrations"
|
||
"BlackmagicRAWSpeedTest/plugins/bearer"
|
||
"BlackmagicRAWPlayer/BlackmagicRawAPI" "BlackmagicRAWPlayer/plugins/mediaservice"
|
||
"BlackmagicRAWPlayer/plugins/imageformats" "BlackmagicRAWPlayer/plugins/audio"
|
||
"BlackmagicRAWPlayer/plugins/platforms" "BlackmagicRAWPlayer/plugins/xcbglintegrations"
|
||
"BlackmagicRAWPlayer/plugins/bearer"
|
||
"Onboarding/plugins/xcbglintegrations" "Onboarding/plugins/qtwebengine"
|
||
"Onboarding/plugins/platforms" "Onboarding/plugins/imageformats"
|
||
"DaVinci Control Panels Setup/plugins/platforms"
|
||
"DaVinci Control Panels Setup/plugins/imageformats"
|
||
"DaVinci Control Panels Setup/plugins/bearer"
|
||
"DaVinci Control Panels Setup/AdminUtility/PlugIns/DaVinciKeyboards"
|
||
"DaVinci Control Panels Setup/AdminUtility/PlugIns/DaVinciPanels" )
|
||
RPATH_ABS=""; for p in "${RPATH_DIRS[@]}"; do RPATH_ABS+="/opt/resolve/${p}:"; done; RPATH_ABS+="\$ORIGIN"
|
||
if command -v patchelf >/dev/null 2>&1; then
|
||
PATCH_COUNT=0
|
||
PATCH_FAIL=0
|
||
PATCH_SKIP=0
|
||
while IFS= read -r -d '' f; do
|
||
FILE_INFO="$(file -b "$f" 2>/dev/null)"
|
||
if [[ "${FILE_INFO}" =~ ELF.*executable ]] || [[ "${FILE_INFO}" =~ ELF.*shared\ object ]]; then
|
||
CURRENT_RPATH="$(patchelf --print-rpath "$f" 2>/dev/null || true)"
|
||
if [[ "${CURRENT_RPATH}" == "${RPATH_ABS}" ]]; then
|
||
((PATCH_SKIP++)) || true
|
||
continue
|
||
fi
|
||
if sudo patchelf --set-rpath "${RPATH_ABS}" "$f" 2>/dev/null; then
|
||
((PATCH_COUNT++)) || true
|
||
else
|
||
((PATCH_FAIL++)) || true
|
||
FILE_SIZE=$(stat -c%s "$f" 2>/dev/null || echo 0)
|
||
if (( FILE_SIZE > 33554432 )); then
|
||
warn "Failed to patch large file: ${f##/opt/resolve/}"
|
||
fi
|
||
fi
|
||
fi
|
||
done < <(find /opt/resolve -type f -print0)
|
||
log "Patched RPATH: ${PATCH_COUNT} files (${PATCH_FAIL} failures, ${PATCH_SKIP} already correct)"
|
||
else
|
||
warn "patchelf not found, skipping RPATH patching"
|
||
fi
|
||
|
||
# Legacy libcrypt fix (same as NVIDIA variant)
|
||
sudo pacman -S --needed --noconfirm libxcrypt-compat || true
|
||
sudo ldconfig || true
|
||
if [[ -e /usr/lib/libcrypt.so.1 ]]; then
|
||
sudo ln -sf /usr/lib/libcrypt.so.1 /opt/resolve/libs/libcrypt.so.1
|
||
fi
|
||
|
||
# ==================== Desktop Integration ====================
|
||
log "Installing desktop entries and icons..."
|
||
declare -A DESKTOP_FILES=(
|
||
["/opt/resolve/share/DaVinciResolve.desktop"]="/usr/share/applications/DaVinciResolve.desktop"
|
||
["/opt/resolve/share/DaVinciControlPanelsSetup.desktop"]="/usr/share/applications/DaVinciControlPanelsSetup.desktop"
|
||
["/opt/resolve/share/blackmagicraw-player.desktop"]="/usr/share/applications/blackmagicraw-player.desktop"
|
||
["/opt/resolve/share/blackmagicraw-speedtest.desktop"]="/usr/share/applications/blackmagicraw-speedtest.desktop"
|
||
)
|
||
for src in "${!DESKTOP_FILES[@]}"; do
|
||
dest="${DESKTOP_FILES[$src]}"
|
||
if [[ -f "${src}" ]]; then
|
||
sudo install -D -m 0644 "${src}" "${dest}"
|
||
else
|
||
warn "Desktop file not found: ${src}"
|
||
fi
|
||
done
|
||
|
||
declare -A ICON_FILES=(
|
||
["/opt/resolve/graphics/DV_Resolve.png"]="/usr/share/icons/hicolor/128x128/apps/davinci-resolve.png"
|
||
["/opt/resolve/graphics/DV_Panels.png"]="/usr/share/icons/hicolor/128x128/apps/davinci-resolve-panels-setup.png"
|
||
["/opt/resolve/graphics/blackmagicraw-player_256x256_apps.png"]="/usr/share/icons/hicolor/256x256/apps/blackmagicraw-player.png"
|
||
["/opt/resolve/graphics/blackmagicraw-speedtest_256x256_apps.png"]="/usr/share/icons/hicolor/256x256/apps/blackmagicraw-speedtest.png"
|
||
)
|
||
for src in "${!ICON_FILES[@]}"; do
|
||
dest="${ICON_FILES[$src]}"
|
||
if [[ -f "${src}" ]]; then
|
||
sudo install -D -m 0644 "${src}" "${dest}"
|
||
else
|
||
warn "Icon file not found: ${src}"
|
||
fi
|
||
done
|
||
|
||
sudo update-desktop-database >/dev/null 2>&1 || true
|
||
sudo gtk-update-icon-cache -f /usr/share/icons/hicolor >/dev/null 2>&1 || true
|
||
|
||
for r in 99-BlackmagicDevices.rules 99-ResolveKeyboardHID.rules 99-DavinciPanel.rules; do
|
||
if [[ -f "/opt/resolve/share/etc/udev/rules.d/${r}" ]]; then
|
||
sudo install -D -m 0644 "/opt/resolve/share/etc/udev/rules.d/${r}" "/usr/lib/udev/rules.d/${r}"
|
||
fi
|
||
done
|
||
sudo udevadm control --reload-rules && sudo udevadm trigger || true
|
||
|
||
# ==================== XWayland Wrapper Script (Intel Arc) ====================
|
||
#
|
||
# Resolve still needs XWayland under Hyprland. Intel-specific bits:
|
||
# - OCL_ICD_VENDORS: Force the OpenCL ICD search dir.
|
||
# - NEOReadDebugKeys=1 +
|
||
# OverrideGpuAddressSpace=48:
|
||
# Workaround for OpenCL init failures on
|
||
# Battlemage. Applied ONLY if Battlemage is
|
||
# detected — Xe3 (Panther Lake) and Alchemist
|
||
# don't need it.
|
||
# - LIBVA_DRIVER_NAME=iHD: Pin Intel media driver for VA-API decode.
|
||
#
|
||
# GPU SELECTION (BDF-based, not index-based)
|
||
# The wrapper enumerates Intel GPUs at launch and picks one:
|
||
# - If a discrete Arc is present (PCI bus != 00, e.g. Battlemage B580 on
|
||
# 03:00.0), pick it.
|
||
# - Otherwise pick the only xe device available (covers Panther Lake
|
||
# laptops with just the Xe3 iGPU, etc.).
|
||
# The chosen device's PCI BDF is exported as ZE_AFFINITY_MASK in
|
||
# "DDDD:BB:DD.F" form (e.g. "0000:03:00.0"). NEO's numeric Level Zero index
|
||
# is NOT a reliable function of BDF order — on hybrid systems NEO often
|
||
# enumerates the discrete card at index 0 even though its BDF sorts later —
|
||
# so we use the BDF format which NEO matches against the actual device.
|
||
# Override with RESOLVE_GPU_BDF=0000:XX:YY.Z if needed; disable pinning
|
||
# entirely with RESOLVE_NO_PIN=1.
|
||
cat << 'EOF' | sudo tee /usr/local/bin/resolve-intel-arc >/dev/null
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
|
||
# Clear stale single-instance Qt lockfiles
|
||
if [[ -r /tmp ]]; then
|
||
for lockfile in /tmp/qtsingleapp-DaVinci*lockfile; do
|
||
[[ -f "$lockfile" ]] && rm -f "$lockfile" 2>/dev/null || true
|
||
done
|
||
fi
|
||
|
||
# Force XWayland under Hyprland/Wayland
|
||
export QT_QPA_PLATFORM=xcb
|
||
export QT_AUTO_SCREEN_SCALE_FACTOR=1
|
||
|
||
# Intel OpenCL / Level Zero (ICD search dir)
|
||
export OCL_ICD_VENDORS="${OCL_ICD_VENDORS:-/etc/OpenCL/vendors}"
|
||
|
||
# Intel VA-API (HW decode)
|
||
export LIBVA_DRIVER_NAME=iHD
|
||
|
||
# --- Detect Intel GPUs and pick the Arc to pin to ---
|
||
# NEO/Level Zero enumerates ALL Intel GPUs it supports (both i915 and xe
|
||
# backends), sorted by PCI BDF. The picker mirrors that ordering so the
|
||
# index we emit matches what ZE_AFFINITY_MASK expects.
|
||
#
|
||
# Selection priority:
|
||
# 1. xe-driven device on a non-SoC PCI bus (a discrete Battlemage like B580)
|
||
# 2. any xe-driven device (covers Panther Lake Xe3 iGPU)
|
||
# 3. first Intel device found (last-resort fallback)
|
||
arc_pick() {
|
||
local -a entries=()
|
||
local d vendor driver bdf
|
||
for d in /sys/class/drm/card[0-9]*; do
|
||
# Skip connector subentries like card0-DP-2
|
||
[[ "$d" =~ /card[0-9]+$ ]] || continue
|
||
[[ -e "$d/device/uevent" ]] || continue
|
||
vendor="$(cat "$d/device/vendor" 2>/dev/null || echo)"
|
||
[[ "$vendor" == "0x8086" ]] || continue
|
||
driver="$(awk -F= '/^DRIVER=/{print $2}' "$d/device/uevent")"
|
||
bdf="$(basename "$(readlink -f "$d/device")")"
|
||
entries+=("$bdf|$driver")
|
||
done
|
||
((${#entries[@]})) || return 1
|
||
|
||
# Sort by BDF (purely cosmetic — selection priorities below don't depend
|
||
# on order, and ZE_AFFINITY_MASK takes the BDF directly)
|
||
IFS=$'\n' read -rd '' -a entries < <(printf '%s\n' "${entries[@]}" | sort; printf '\0') || true
|
||
|
||
local picked_bdf="" picked_drv=""
|
||
local n=${#entries[@]} i e b drv
|
||
|
||
# Priority 1: discrete xe device (NOT on PCI bus 00 — those are SoC iGPUs)
|
||
for ((i=0; i<n; i++)); do
|
||
e="${entries[i]}"; b="${e%%|*}"; drv="${e##*|}"
|
||
if [[ "$drv" == "xe" && ! "$b" =~ ^0000:00: ]]; then
|
||
picked_bdf="$b"; picked_drv="$drv"; break
|
||
fi
|
||
done
|
||
# Priority 2: any xe device (covers Panther Lake Xe3-LPG iGPU)
|
||
if [[ -z "$picked_bdf" ]]; then
|
||
for ((i=0; i<n; i++)); do
|
||
e="${entries[i]}"; b="${e%%|*}"; drv="${e##*|}"
|
||
if [[ "$drv" == "xe" ]]; then
|
||
picked_bdf="$b"; picked_drv="$drv"; break
|
||
fi
|
||
done
|
||
fi
|
||
# Priority 3: first Intel device (last-resort fallback)
|
||
if [[ -z "$picked_bdf" ]]; then
|
||
picked_bdf="${entries[0]%%|*}"
|
||
picked_drv="${entries[0]##*|}"
|
||
fi
|
||
|
||
echo "$picked_bdf" "$picked_drv"
|
||
}
|
||
|
||
if [[ "${RESOLVE_NO_PIN:-0}" != "1" ]]; then
|
||
if [[ -n "${RESOLVE_GPU_BDF:-}" ]]; then
|
||
export ZE_AFFINITY_MASK="${RESOLVE_GPU_BDF}"
|
||
echo "Resolve: ZE_AFFINITY_MASK=${ZE_AFFINITY_MASK} (manual override via RESOLVE_GPU_BDF)" >&2
|
||
ARC_BDF="${RESOLVE_GPU_BDF}"
|
||
elif read -r ARC_BDF ARC_DRV < <(arc_pick); then
|
||
export ZE_AFFINITY_MASK="${ARC_BDF}"
|
||
echo "Resolve: pinning to Arc GPU at ${ARC_BDF} (driver=${ARC_DRV})" >&2
|
||
else
|
||
echo "Resolve: no Intel GPU found; not pinning ZE_AFFINITY_MASK" >&2
|
||
fi
|
||
fi
|
||
|
||
# OpenCL init workaround is specific to discrete Battlemage Xe2 silicon
|
||
# (Arc B5x0/B7x0 dGPU). Intel reuses the "Arc B-series" brand for the Xe3-LPG
|
||
# iGPUs in Panther Lake (B360/B370/B380/B390, launched Jan 2026), but those
|
||
# don't need — and don't want — this debug key. Gate on lspci match AND a
|
||
# non-SoC PCI bus, since iGPUs always live on 0000:00:02.0.
|
||
if lspci -nn 2>/dev/null | grep -qi 'Battlemage' \
|
||
&& [[ -n "${ARC_BDF:-}" && ! "${ARC_BDF}" =~ ^0000:00: ]]; then
|
||
export NEOReadDebugKeys=1
|
||
export OverrideGpuAddressSpace=48
|
||
fi
|
||
|
||
exec /opt/resolve/bin/resolve "$@"
|
||
EOF
|
||
sudo chmod +x /usr/local/bin/resolve-intel-arc
|
||
|
||
# Convenience symlink at /usr/bin/davinci-resolve
|
||
if [[ ! -e /usr/bin/davinci-resolve ]]; then
|
||
if [[ -x /usr/local/bin/resolve-intel-arc ]]; then
|
||
echo -e '#!/usr/bin/env bash\nexec /usr/local/bin/resolve-intel-arc "$@"' | sudo tee /usr/bin/davinci-resolve >/dev/null
|
||
else
|
||
echo -e '#!/usr/bin/env bash\nexec /opt/resolve/bin/resolve "$@"' | sudo tee /usr/bin/davinci-resolve >/dev/null
|
||
fi
|
||
sudo chmod +x /usr/bin/davinci-resolve
|
||
fi
|
||
|
||
# Point system .desktop entries at the wrapper
|
||
WRAPPER="/usr/local/bin/resolve-intel-arc"
|
||
if [[ -f /usr/share/applications/DaVinciResolve.desktop ]]; then
|
||
sudo sed -i "s|^Exec=.*|Exec=${WRAPPER} %U|" /usr/share/applications/DaVinciResolve.desktop
|
||
fi
|
||
if [[ -f /usr/share/applications/DaVinciResolveCaptureLogs.desktop ]]; then
|
||
sudo sed -i "s|^Exec=.*|Exec=${WRAPPER} %U|" /usr/share/applications/DaVinciResolveCaptureLogs.desktop
|
||
fi
|
||
sudo update-desktop-database >/dev/null 2>&1 || true
|
||
|
||
# User-level .desktop overrides system entry (survives reinstalls)
|
||
mkdir -p "${HOME}/.local/share/applications"
|
||
cat > "${HOME}/.local/share/applications/davinci-resolve-wrapper.desktop" << EOF
|
||
[Desktop Entry]
|
||
Type=Application
|
||
Name=DaVinci Resolve
|
||
Comment=DaVinci Resolve via XWayland wrapper (Intel Arc)
|
||
Exec=${WRAPPER} %U
|
||
TryExec=${WRAPPER}
|
||
Terminal=false
|
||
Icon=davinci-resolve
|
||
Categories=AudioVideo;Video;Audio;Graphics;
|
||
StartupWMClass=resolve
|
||
X-GNOME-UsesNotifications=true
|
||
EOF
|
||
|
||
update-desktop-database "${HOME}/.local/share/applications" >/dev/null 2>&1 || true
|
||
sudo gtk-update-icon-cache -f /usr/share/icons/hicolor >/dev/null 2>&1 || true
|
||
|
||
# ==================== Audio backend fix (DeckLink → ALSA) ====================
|
||
#
|
||
# Resolve ships with `Local.Audio.Type = DeckLink` as the default in its
|
||
# system-wide config template at /opt/resolve/share/default-config.dat.
|
||
# That's correct for users with a Blackmagic DeckLink capture/playback card,
|
||
# but on systems without one Resolve aborts on first launch. Patch both the
|
||
# template (so future first-launches are correct) and any existing user
|
||
# config (so the current install isn't broken).
|
||
log "Switching Resolve audio backend default from DeckLink to ALSA..."
|
||
TEMPLATE=/opt/resolve/share/default-config.dat
|
||
if [[ -f "${TEMPLATE}" ]] && grep -q '^Local\.Audio\.Type = DeckLink$' "${TEMPLATE}"; then
|
||
sudo sed -i 's|^Local\.Audio\.Type = DeckLink$|Local.Audio.Type = ALSA|' "${TEMPLATE}"
|
||
log " Patched system template ${TEMPLATE}"
|
||
fi
|
||
USER_CFG="${HOME}/.local/share/DaVinciResolve/configs/config.dat"
|
||
if [[ -f "${USER_CFG}" ]] && grep -q '^Local\.Audio\.Type = DeckLink$' "${USER_CFG}"; then
|
||
cp "${USER_CFG}" "${USER_CFG}.bak.$(date +%s)"
|
||
sed -i 's|^Local\.Audio\.Type = DeckLink$|Local.Audio.Type = ALSA|' "${USER_CFG}"
|
||
log " Patched existing user config ${USER_CFG} (backup .bak.<timestamp> created)"
|
||
fi
|
||
|
||
# ==================== snd-aloop (the actual render-blocker fix) ====================
|
||
#
|
||
# Resolve's audio engine opens raw ALSA hardware via snd_pcm_open("hw:%d", ...)
|
||
# — it never goes through ALSA's plugin layer (default/pulse/pipewire) and it
|
||
# enumerates EVERY card under /dev/snd/controlC[0-32] looking for a usable PCM.
|
||
# When every real ALSA card on the system is owned/contested by PipeWire's
|
||
# session manager, Resolve's enumeration loops forever, the render queue
|
||
# never spawns the encoder, and the user sees:
|
||
# - alsa device meters "flickering" in wireplumber (each enumeration cycle
|
||
# briefly opens controlC*; PipeWire reacts)
|
||
# - render that "won't start" with no error in ResolveDebug.txt
|
||
# Confirmed via strace: 14000+ SNDRV_CTL_IOCTL_PCM_INFO ENXIO ioctls and
|
||
# 47000+ /dev/snd/controlCN ENOENT opens during the failed render attempt,
|
||
# concentrated AFTER the user clicks Render — i.e. a tight retry loop.
|
||
#
|
||
# THE FIX: load the kernel's snd-aloop module. It exposes a virtual ALSA
|
||
# loopback card that PipeWire ignores (no ACP profile, not auto-acquired).
|
||
# Resolve's enumerator finds it, can fully own it, settles on it, and the
|
||
# render proceeds. Side effect: a "Loopback" device shows up in alsamixer
|
||
# and pavucontrol — harmless.
|
||
#
|
||
# Skipped if RESOLVE_NO_ALOOP=1 (e.g. user already has a dedicated audio
|
||
# interface that Resolve uses). Persistent across reboots via
|
||
# /etc/modules-load.d/.
|
||
if [[ "${RESOLVE_NO_ALOOP:-0}" == "1" ]]; then
|
||
log "Skipping snd-aloop setup (RESOLVE_NO_ALOOP=1)"
|
||
else
|
||
log "Setting up snd-aloop (virtual ALSA card so Resolve render can start)..."
|
||
if ! lsmod | grep -qE '^snd_aloop'; then
|
||
if sudo modprobe snd-aloop 2>/dev/null; then
|
||
log " snd-aloop loaded for the current session"
|
||
else
|
||
warn " modprobe snd-aloop failed — kernel may lack the module."
|
||
warn " On Arch this is part of linux/linux-zen/linux-lts; verify: modinfo snd-aloop"
|
||
fi
|
||
else
|
||
log " snd-aloop already loaded"
|
||
fi
|
||
ALOOP_CONF=/etc/modules-load.d/snd-aloop.conf
|
||
if [[ ! -f "${ALOOP_CONF}" ]] || ! grep -qx 'snd-aloop' "${ALOOP_CONF}" 2>/dev/null; then
|
||
echo 'snd-aloop' | sudo tee "${ALOOP_CONF}" >/dev/null
|
||
log " Wrote ${ALOOP_CONF} (autoloads at boot)"
|
||
else
|
||
log " ${ALOOP_CONF} already configured"
|
||
fi
|
||
|
||
# Bridge snd-aloop capture → default sink so monitor audio is audible.
|
||
# Without this, Resolve writes to the loopback and the audio goes nowhere
|
||
# (loopback is a black hole until something captures the other side).
|
||
# The bridge is a PipeWire loopback module loaded from user config; it
|
||
# tracks the default sink so headphone/HDMI switching keeps working.
|
||
ALOOP_BRIDGE_DIR="${HOME}/.config/pipewire/pipewire.conf.d"
|
||
ALOOP_BRIDGE_FILE="${ALOOP_BRIDGE_DIR}/50-resolve-aloop-bridge.conf"
|
||
mkdir -p "${ALOOP_BRIDGE_DIR}"
|
||
if [[ ! -f "${ALOOP_BRIDGE_FILE}" ]]; then
|
||
cat > "${ALOOP_BRIDGE_FILE}" <<'EOF'
|
||
# DaVinci Resolve aloop monitor bridge — managed by install-davinci-resolve-intel-arc.sh
|
||
# Bridges snd-aloop's capture side to the system default sink so Resolve's
|
||
# monitor audio is audible while editing. Without this, Resolve renders fine
|
||
# but you hear nothing during playback. Remove this file + restart PipeWire
|
||
# to disable.
|
||
context.modules = [
|
||
{ name = libpipewire-module-loopback
|
||
args = {
|
||
node.description = "DaVinci Resolve aloop monitor bridge"
|
||
capture.props = {
|
||
node.name = "resolve-aloop-capture"
|
||
target.object = "alsa_input.platform-snd_aloop.0.analog-stereo"
|
||
node.passive = true
|
||
}
|
||
playback.props = {
|
||
node.name = "resolve-aloop-playback"
|
||
media.class = "Stream/Output/Audio"
|
||
}
|
||
}
|
||
}
|
||
]
|
||
EOF
|
||
log " Wrote ${ALOOP_BRIDGE_FILE} (PipeWire loopback bridge)"
|
||
# Reload user PipeWire services so the new conf is picked up
|
||
if systemctl --user is-active --quiet pipewire 2>/dev/null; then
|
||
systemctl --user restart pipewire pipewire-pulse wireplumber 2>/dev/null || true
|
||
log " Reloaded user PipeWire services"
|
||
fi
|
||
else
|
||
log " ${ALOOP_BRIDGE_FILE} already in place"
|
||
fi
|
||
fi
|
||
|
||
# ==================== Post-install OpenCL sanity check ====================
|
||
echo
|
||
if command -v clinfo >/dev/null 2>&1; then
|
||
if clinfo -l 2>/dev/null | grep -qiE 'Arc|Intel.*Graphics|Battlemage|Alchemist'; then
|
||
log "OpenCL: Intel GPU is visible to clinfo — Resolve should see it for compute."
|
||
else
|
||
warn "OpenCL: no Intel GPU listed by clinfo. Resolve will likely fall back to CPU."
|
||
warn " Try: clinfo -l (and check that intel-compute-runtime is installed)"
|
||
fi
|
||
else
|
||
warn "clinfo not installed — skipped OpenCL visibility check"
|
||
fi
|
||
|
||
# ==================== Reset stale Resolve user configs ====================
|
||
#
|
||
# Resolve defaults to GPU Processing Mode = CUDA on Linux. If a previous
|
||
# launch couldn't find an OpenCL device (e.g. Intel compute runtime was
|
||
# missing), Resolve writes out a config snapshot and segfaults before the
|
||
# UI ever appears with "Unsupported GPU Processing Mode". Subsequent
|
||
# launches reuse that broken snapshot and crash the same way — even after
|
||
# the OpenCL stack is fixed. The fix is to delete configs/ and logs/ so
|
||
# Resolve goes through first-launch onboarding again with a working GPU
|
||
# stack present.
|
||
#
|
||
# We only wipe if we detect that prior-crash marker (or RESOLVE_RESET_CONFIG=1
|
||
# is set explicitly). Project databases under "Resolve Disk Database/" and
|
||
# "Resolve Project Library/" are NOT touched.
|
||
RESOLVE_USER_DIR="${HOME}/.local/share/DaVinciResolve"
|
||
PRIOR_CRASH=0
|
||
if [[ -f "${RESOLVE_USER_DIR}/logs/ResolveDebug.txt" ]] \
|
||
&& grep -q 'Unsupported GPU Processing Mode' "${RESOLVE_USER_DIR}/logs/ResolveDebug.txt" 2>/dev/null; then
|
||
PRIOR_CRASH=1
|
||
fi
|
||
if (( PRIOR_CRASH )) || [[ "${RESOLVE_RESET_CONFIG:-0}" == "1" ]]; then
|
||
if (( PRIOR_CRASH )); then
|
||
log "Detected prior crashed launch ('Unsupported GPU Processing Mode')."
|
||
else
|
||
log "RESOLVE_RESET_CONFIG=1 set — forcing config reset."
|
||
fi
|
||
log "Resetting Resolve configs and logs to first-launch state. Project databases preserved."
|
||
rm -rf "${RESOLVE_USER_DIR}/configs" "${RESOLVE_USER_DIR}/logs" 2>/dev/null || true
|
||
fi
|
||
|
||
echo
|
||
echo "✅ DaVinci Resolve installed to /opt/resolve"
|
||
echo " (vendor libc++ kept; libcrypt.so.1 ensured; Intel Arc OpenCL/VA-API enabled)"
|
||
echo " Launch from your app menu, or run: resolve-intel-arc"
|
||
echo " GPU pinning is automatic — discrete Arc preferred over iGPU."
|
||
echo " Override: RESOLVE_GPU_BDF=0000:XX:YY.Z resolve-intel-arc"
|
||
echo " Disable: RESOLVE_NO_PIN=1 resolve-intel-arc"
|
||
echo " Force config reset on next install: RESOLVE_RESET_CONFIG=1 ./install-davinci-resolve-intel-arc.sh"
|
||
echo " Skip snd-aloop module setup: RESOLVE_NO_ALOOP=1 ./install-davinci-resolve-intel-arc.sh"
|
||
echo " Logs: ~/.local/share/DaVinciResolve/logs/ResolveDebug.txt"
|
||
echo " Note: Intel Arc on Linux is unsupported by Blackmagic — expect quirks."
|
||
echo
|