diff --git a/src/display.sh b/src/display.sh deleted file mode 100644 index 511eb649..00000000 --- a/src/display.sh +++ /dev/null @@ -1,1077 +0,0 @@ -#!/usr/bin/env bash -set -Eeuo pipefail - -# Docker environment variables - -: "${GPU:="N"}" # GPU acceleration -: "${VGA:="virtio"}" # VGA adapter -: "${DISPLAY:="web"}" # Display type -: "${LOSSY:="N"}" # Lossy VNC compression -: "${VNC_PORT:="5900"}" # VNC port -: "${RENDERNODE:=""}" # Render node -: "${VRAM_SIZE:="4G"}" # VirtIO GPU memory budget - -# Sanitize variables -VGA=$(strip "$VGA") -LOSSY=$(strip "$LOSSY") -DISPLAY=$(strip "$DISPLAY") -VNC_PORT=$(strip "$VNC_PORT") -RENDERNODE=$(strip "$RENDERNODE") -VRAM_SIZE=$(strip "$VRAM_SIZE") -WSS_SOCKET="${WSS_SOCKET:-$QEMU_DIR/vnc-ws.sock}" - -VGA_DEVICE="${VGA%%,*}" -VGA_OPTIONS="${VGA#"$VGA_DEVICE"}" - -case "${VGA_DEVICE,,}" in - "std" | "vga" ) - VGA_DEVICE="VGA" - VGA_ARG="-device" ;; - "vmware" | "vmware-svga" ) - VGA_DEVICE="vmware-svga" - VGA_ARG="-device" ;; - "virtio" ) - VGA_DEVICE="virtio-vga" - VGA_ARG="-device" ;; - "virtio-"* ) - VGA_DEVICE="${VGA_DEVICE,,}" - VGA_ARG="-device" ;; - * ) - VGA_ARG="-vga" ;; -esac - -VGA="${VGA_DEVICE}${VGA_OPTIONS}" -VGA_ARG+=" ${VGA}" - -# QEMU accepts a VNC display number rather than a TCP port, -# so translate the configured port back to its :N display index. -port=$(( VNC_PORT - 5900 )) - -LOSSY_OPT="" -enabled "$LOSSY" && LOSSY_OPT=",lossy=on" - -# Preserve the historic :0 setting as an alias for the managed web display. -[[ "$DISPLAY" == ":0" ]] && DISPLAY="web" - -case "${DISPLAY,,}" in - - "vnc" ) - DISPLAY_OPTS="-display vnc=:${port}${LOSSY_OPT} ${VGA_ARG}" ;; - "web" ) - DISPLAY_OPTS="-display vnc=:${port},websocket=unix:${WSS_SOCKET}${LOSSY_OPT} ${VGA_ARG}" ;; - "disabled" ) - DISPLAY_OPTS="-display none ${VGA_ARG}" ;; - "none" ) - DISPLAY_OPTS="-display none -vga none" ;; - *) - DISPLAY_OPTS="-display ${DISPLAY} ${VGA_ARG}" ;; - -esac - -gpuSetupFailure() { - - local reason="$1" - - error "$reason" - exit 87 -} - -vmwareLibraryPath() { - - local library="$1" - local path="" - - if command -v ldconfig >/dev/null 2>&1; then - path="$(ldconfig -p 2>/dev/null | awk -v library="$library" '$1 == library && !found { print $NF; found = 1 }')" - if [ -n "$path" ] && [ -r "$path" ]; then - printf '%s\n' "$path" - return 0 - fi - fi - - for path in /usr/lib/*/"$library" /usr/lib/"$library" /usr/lib64/"$library" \ - /usr/local/lib/*/"$library" /usr/local/lib/"$library" /usr/local/lib64/"$library"; do - [ -r "$path" ] || continue - printf '%s\n' "$path" - return 0 - done - - return 1 -} - -vmwareLibraryReady() { - - local library="$1" - local path="" - local dependencies="" - VMWARE_LIBRARY_REASON="" - - if ! path="$(vmwareLibraryPath "$library")"; then - VMWARE_LIBRARY_REASON="the $library runtime library is not available in the container" - return 1 - fi - - if command -v ldd >/dev/null 2>&1; then - dependencies="$(ldd "$path" 2>&1 || true)" - if grep -q '=> not found' <<< "$dependencies"; then - VMWARE_LIBRARY_REASON="$library has missing runtime dependencies" - return 1 - fi - fi - - return 0 -} - -# VMVGA lets DXVK select the Vulkan device itself, so this render node is only -# a host-GPU sanity check and is never passed to QEMU or DXVK. -vmwareRenderNodeReady() { - - local node gpu_fd - VMWARE_RENDER_NODE="" - VMWARE_RENDER_NODE_FOUND="N" - VMWARE_RENDER_REASON="" - - if [ ! -d /dev/dri ]; then - VMWARE_RENDER_REASON="'/dev/dri' was not added to the devices section of your compose file" - return 1 - fi - - for node in /dev/dri/renderD*; do - - [ -e "$node" ] || continue - VMWARE_RENDER_NODE_FOUND="Y" - [ -c "$node" ] || continue - - if { exec {gpu_fd}<>"$node"; } 2>/dev/null; then - { exec {gpu_fd}>&-; } 2>/dev/null || true - VMWARE_RENDER_NODE="$node" - return 0 - fi - - done - - if [[ "$VMWARE_RENDER_NODE_FOUND" != "Y" ]]; then - VMWARE_RENDER_REASON="/dev/dri is available, but no GPU render nodes were found" - else - VMWARE_RENDER_REASON="no accessible GPU render node was found" - fi - - return 1 -} - -vmwareVersionAtLeast() { - - local version="$1" - local required_major="$2" - local required_minor="$3" - local major minor - - IFS='.' read -r major minor _ <<< "$version" - [[ "$major" =~ ^[0-9]+$ && "$minor" =~ ^[0-9]+$ ]] || return 1 - - (( major > required_major || (major == required_major && minor >= required_minor) )) -} - -# Match the VMVGA renderer preflight: Vulkan 1.3, its headless WSI instance -# extensions, and the device extensions required by the DXVK D3D9 backend. -vmwareVulkanReady() { - - local summary details loader selected extension - local api type name driver - VMWARE_VULKAN_REASON="" - - if ! command -v vulkaninfo >/dev/null 2>&1; then - VMWARE_VULKAN_REASON="vulkaninfo is not available in the container" - return 1 - fi - - if ! summary="$(vulkaninfo --summary 2>&1)"; then - VMWARE_VULKAN_REASON="Vulkan device enumeration failed" - return 1 - fi - - loader="$(sed -n 's/^Vulkan Instance Version:[[:space:]]*//p' <<< "$summary")" - if [ -z "$loader" ] || ! vmwareVersionAtLeast "$loader" 1 3; then - VMWARE_VULKAN_REASON="Vulkan 1.3 or newer is required by VMVGA" - return 1 - fi - - selected="$(awk ' - function emit() { - if (found || !in_gpu || api == "" || type == "") { - return - } - - split(api, version, ".") - if (type != "PHYSICAL_DEVICE_TYPE_CPU" && - (version[1] > 1 || (version[1] == 1 && version[2] >= 3))) { - print api "|" type "|" name "|" driver - found = 1 - } - } - - /^GPU[0-9]+:/ { - emit() - in_gpu = 1 - api = "" - type = "" - name = "" - driver = "" - next - } - - in_gpu && /^[[:space:]]*apiVersion[[:space:]]*=/ { - api = $0 - sub(/^.*=[[:space:]]*/, "", api) - if (match(api, /\([0-9]+\.[0-9]+(\.[0-9]+)?\)/)) { - api = substr(api, RSTART + 1, RLENGTH - 2) - } else { - sub(/[[:space:]].*$/, "", api) - } - next - } - - in_gpu && /^[[:space:]]*deviceType[[:space:]]*=/ { - type = $0 - sub(/^.*=[[:space:]]*/, "", type) - sub(/[[:space:]].*$/, "", type) - next - } - - in_gpu && /^[[:space:]]*deviceName[[:space:]]*=/ { - name = $0 - sub(/^.*=[[:space:]]*/, "", name) - next - } - - in_gpu && /^[[:space:]]*driverName[[:space:]]*=/ { - driver = $0 - sub(/^.*=[[:space:]]*/, "", driver) - next - } - - END { emit() } - ' <<< "$summary")" - - if [ -z "$selected" ]; then - VMWARE_VULKAN_REASON="no Vulkan 1.3 capable hardware device was found" - return 1 - fi - - IFS='|' read -r api type name driver <<< "$selected" - - if ! details="$(vulkaninfo 2>&1)"; then - VMWARE_VULKAN_REASON="Vulkan capability enumeration failed" - return 1 - fi - - for extension in VK_KHR_surface VK_EXT_headless_surface; do - if ! grep -Eq "^[[:space:]]*${extension}[[:space:]:]" <<< "$details"; then - VMWARE_VULKAN_REASON="the Vulkan loader does not support $extension required by VMVGA" - return 1 - fi - done - - for extension in VK_KHR_maintenance5 VK_EXT_robustness2; do - if ! grep -Eq "^[[:space:]]*${extension}[[:space:]:]" <<< "$details"; then - VMWARE_VULKAN_REASON="no Vulkan device exposes $extension required by VMVGA" - return 1 - fi - done - - VMWARE_VULKAN_API="$api" - VMWARE_VULKAN_DEVICE="$name" - VMWARE_VULKAN_DRIVER="$driver" - return 0 -} - -vmwareGpuSetup() { - - VMWARE_LIBRARY_REASON="" - VMWARE_RENDER_REASON="" - VMWARE_VULKAN_REASON="" - - if ! vmwareRenderNodeReady; then - gpuSetupFailure "VMware GPU acceleration requires a usable host GPU, but $VMWARE_RENDER_REASON" - fi - - local library - for library in libvulkan.so.1 libdxvk_d3d9.so.0 libdxvk_d3d11.so.0; do - if ! vmwareLibraryReady "$library"; then - gpuSetupFailure "VMware GPU acceleration is unavailable because $VMWARE_LIBRARY_REASON" - fi - done - - if ! vmwareVulkanReady; then - gpuSetupFailure "VMware GPU acceleration is unavailable because $VMWARE_VULKAN_REASON" - fi - - echo - info "Hardware rendering enabled:" - info - info "Device: ${VMWARE_VULKAN_DEVICE:-GPU}" - [ -n "${VMWARE_VULKAN_DRIVER:-}" ] && info "Driver: $VMWARE_VULKAN_DRIVER" - info "Vulkan: $VMWARE_VULKAN_API" - info "Backend: DXVK" - info "Render: automatic" - info - - return 0 -} - -enabled "$GPU" || return 0 - -msg="Configuring display drivers..." -enabled "$DEBUG" && echo "$msg" - -if [[ "$ARCH" != "amd64" ]]; then - gpuSetupFailure "GPU acceleration is only supported for the AMD64 platform" -fi - -if [[ "${VGA_DEVICE,,}" == "vmware-svga" ]]; then - vmwareGpuSetup - return 0 -fi - -if [[ "${BOOT_MODE:-}" == "windows_legacy" ]]; then - gpuSetupFailure "GPU acceleration is not supported by your Windows version" - return 0 -fi - -case "${VGA_DEVICE,,}" in - "none" ) - VGA_DEVICE="virtio-gpu-gl" ;; - "virtio-vga" ) - VGA_DEVICE="virtio-vga-gl" ;; - "virtio-gpu" ) - VGA_DEVICE="virtio-gpu-gl" ;; - "virtio-vga-gl"* | "virtio-gpu-gl"* ) ;; - * ) - gpuSetupFailure "GPU acceleration requires a VirtIO GPU display, but VGA='$VGA'" - return 0 ;; -esac - -VGA="${VGA_DEVICE}${VGA_OPTIONS}" - -VRAM_SIZE="${VRAM_SIZE// /}" -[ -z "$VRAM_SIZE" ] && VRAM_SIZE="4G" - -# Match the size conventions used by RAM_SIZE: small bare values are GiB, -# while larger bare values remain MiB for compatibility with numeric settings. -if [ -z "${VRAM_SIZE//[0-9. ]}" ]; then - [ "${VRAM_SIZE%%.*}" -lt "130" ] && VRAM_SIZE="${VRAM_SIZE}G" || VRAM_SIZE="${VRAM_SIZE}M" -fi - -VRAM_SIZE=$(echo "${VRAM_SIZE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g') -if ! VRAM_BYTES=$(numfmt --from=iec "$VRAM_SIZE" 2>/dev/null); then - error "Invalid VRAM_SIZE: $VRAM_SIZE" - exit 16 -fi - -# The host-visible PCI aperture must be a positive power-of-two size. Requiring -# whole MiB also keeps the renderer's advertised VRAM value exact. -if (( VRAM_BYTES < 1048576 || (VRAM_BYTES & (VRAM_BYTES - 1)) != 0 )); then - error "VRAM_SIZE must be a power-of-two size of at least 1M: $VRAM_SIZE" - exit 16 -fi - -VKR_DEVICE_MEMORY_LIMIT_BYTES="$VRAM_BYTES" -override_vram_size="$(( VRAM_BYTES / 1048576 ))" -export VKR_DEVICE_MEMORY_LIMIT_BYTES override_vram_size - -# Return the PCI vendor for a usable DRM render node. Any malformed, missing, -# inaccessible or disappearing node is rejected before hardware rendering is enabled. - -gpuNodeVendor() { - - local node="$1" - - local render_name="${node##*/}" - [[ "$render_name" =~ ^renderD[0-9]{3}$ ]] || return 1 - - local render_number="${render_name#renderD}" - (( 10#$render_number >= 128 )) || return 1 - [ -c "$node" ] || return 1 - - local gpu_fd - if ! { exec {gpu_fd}<>"$node"; } 2>/dev/null; then - return 1 - fi - - { exec {gpu_fd}>&-; } 2>/dev/null || true - - local vendor_file="/sys/class/drm/${render_name}/device/vendor" - [ -r "$vendor_file" ] || return 1 - - if ! IFS= read -r GPU_VENDOR < "$vendor_file"; then - return 1 - fi - - GPU_VENDOR="${GPU_VENDOR,,}" - return 0 -} - -# qemu-render omits Mesa's legacy i915 Gallium driver. Reject the Gen3 Intel -# devices that have no Crocus or Iris fallback before enabling EGL rendering. - -intelMesaReady() { - - local node="$1" - local render_name="${node##*/}" - local device="" - local device_file="/sys/class/drm/${render_name}/device/device" - - [ -r "$device_file" ] || return 1 - IFS= read -r device < "$device_file" || return 1 - - case "${device,,}" in - "0x2582" | "0x258a" | "0x2592" | "0x2772" | "0x27a2" | "0x27ae" | \ - "0x29b2" | "0x29c2" | "0x29d2" | "0xa001" | "0xa011" ) return 1 ;; - esac - - return 0 -} - -vulkanLibraryAvailable() { - - local library="$1" - - compgen -G "/usr/lib/*/${library}" >/dev/null 2>&1 \ - || [ -e "/usr/lib/${library}" ] \ - || [ -e "/usr/lib64/${library}" ] -} - -vulkanManifestAvailable() { - - local manifest="$1" - - compgen -G "/etc/vulkan/icd.d/${manifest}*.json" >/dev/null 2>&1 \ - || compgen -G "/usr/share/vulkan/icd.d/${manifest}*.json" >/dev/null 2>&1 -} - -vulkanRuntimeReady() { - - local summary details selected extensions api type - local external_memory_fd external_memory_dma_buf - local prime="" - local vendor="${GPU_VENDOR,,}" - local device="${GPU_DEVICE,,}" - VULKAN_REASON="" - - if ! command -v vulkaninfo >/dev/null 2>&1; then - VULKAN_REASON="vulkaninfo is not available in the container" - return 1 - fi - - if [ -z "$device" ]; then - VULKAN_REASON="the selected GPU PCI device ID cannot be determined" - return 1 - fi - - # Mesa can scope Vulkan enumeration to the exact DRM device by PCI address. - # Keep this probe local: QEMU still receives the selected render node normally. - case "$vendor" in - "0x8086" | "0x1002" ) - if [[ "$GPU_PCI_SLOT" =~ ^[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-7]$ ]]; then - prime="pci-${GPU_PCI_SLOT//[:.]/_}!" - fi ;; - esac - - if [ -n "$prime" ]; then - if ! summary="$(DRI_PRIME="$prime" vulkaninfo --summary 2>&1)"; then - VULKAN_REASON="Vulkan device enumeration failed for the selected GPU" - return 1 - fi - elif ! summary="$(vulkaninfo --summary 2>&1)"; then - VULKAN_REASON="Vulkan device enumeration failed" - return 1 - fi - - selected="$(awk -v want_vendor="$vendor" -v want_device="$device" ' - function emit() { - if (!found && in_gpu && tolower(vendor) == want_vendor && - tolower(device) == want_device && api != "" && type != "") { - print api "|" type - found = 1 - } - } - - /^GPU[0-9]+:/ { - emit() - in_gpu = 1 - vendor = "" - device = "" - api = "" - type = "" - next - } - - in_gpu && /^[[:space:]]*apiVersion[[:space:]]*=/ { - api = $0 - sub(/^.*=[[:space:]]*/, "", api) - if (match(api, /\([0-9]+\.[0-9]+(\.[0-9]+)?\)/)) { - api = substr(api, RSTART + 1, RLENGTH - 2) - } else { - sub(/[[:space:]].*$/, "", api) - } - next - } - - in_gpu && /^[[:space:]]*vendorID[[:space:]]*=/ { - vendor = $0 - sub(/^.*=[[:space:]]*/, "", vendor) - sub(/[[:space:]].*$/, "", vendor) - next - } - - in_gpu && /^[[:space:]]*deviceID[[:space:]]*=/ { - device = $0 - sub(/^.*=[[:space:]]*/, "", device) - sub(/[[:space:]].*$/, "", device) - next - } - - in_gpu && /^[[:space:]]*deviceType[[:space:]]*=/ { - type = $0 - sub(/^.*=[[:space:]]*/, "", type) - sub(/[[:space:]].*$/, "", type) - next - } - - END { emit() } - ' <<< "$summary")" - - if [ -z "$selected" ]; then - VULKAN_REASON="the selected GPU is not available through Vulkan" - return 1 - fi - - IFS='|' read -r api type <<< "$selected" - if [[ "$type" == "PHYSICAL_DEVICE_TYPE_CPU" ]]; then - VULKAN_REASON="the selected Vulkan device is a software CPU renderer" - return 1 - fi - - local major minor - IFS='.' read -r major minor _ <<< "$api" - if ! [[ "$major" =~ ^[0-9]+$ && "$minor" =~ ^[0-9]+$ ]]; then - VULKAN_REASON="the selected GPU reports an invalid Vulkan API version '$api'" - return 1 - fi - - if (( major < 1 || (major == 1 && minor < 1) )); then - VULKAN_REASON="the selected GPU supports Vulkan $major.$minor, but Venus requires Vulkan 1.1 or newer" - return 1 - fi - - if [ -n "$prime" ]; then - if ! details="$(DRI_PRIME="$prime" vulkaninfo 2>&1)"; then - VULKAN_REASON="Vulkan capability enumeration failed for the selected GPU" - return 1 - fi - elif ! details="$(vulkaninfo 2>&1)"; then - VULKAN_REASON="Vulkan capability enumeration failed" - return 1 - fi - - extensions="$(awk -v want_vendor="$vendor" -v want_device="$device" ' - function finish() { - if (!found && in_gpu && tolower(vendor) == want_vendor && - tolower(device) == want_device) { - print external_memory_fd "|" external_memory_dma_buf - found = 1 - } - } - - /^GPU[0-9]+:/ { - finish() - in_gpu = 1 - vendor = "" - device = "" - external_memory_fd = 0 - external_memory_dma_buf = 0 - next - } - - in_gpu && /^[[:space:]]*vendorID[[:space:]]*=/ { - vendor = $0 - sub(/^.*=[[:space:]]*/, "", vendor) - sub(/[[:space:]].*$/, "", vendor) - next - } - - in_gpu && /^[[:space:]]*deviceID[[:space:]]*=/ { - device = $0 - sub(/^.*=[[:space:]]*/, "", device) - sub(/[[:space:]].*$/, "", device) - next - } - - in_gpu && /VK_KHR_external_memory_fd/ { - external_memory_fd = 1 - next - } - - in_gpu && /VK_EXT_external_memory_dma_buf/ { - external_memory_dma_buf = 1 - next - } - - END { - finish() - } - ' <<< "$details")" - - IFS='|' read -r external_memory_fd external_memory_dma_buf <<< "$extensions" - - if [[ "$external_memory_fd" != "1" ]]; then - VULKAN_REASON="the selected GPU does not support VK_KHR_external_memory_fd required by Venus" - return 1 - fi - - if [[ "$external_memory_dma_buf" != "1" ]]; then - VULKAN_REASON="the selected GPU does not support VK_EXT_external_memory_dma_buf required by Helios" - return 1 - fi - - return 0 -} - -mesaVulkanReady() { - - local vendor="$1" - local library manifest - VULKAN_REASON="" - - if ! vulkanLibraryAvailable "libvulkan.so.1"; then - VULKAN_REASON="the Vulkan loader is not available in the container" - return 1 - fi - - case "$vendor" in - "0x8086" ) - for library in libvulkan_intel.so libvulkan_intel_hasvk.so; do - if ! vulkanLibraryAvailable "$library"; then - VULKAN_REASON="the Intel Vulkan driver library '$library' is not available in the container" - return 1 - fi - done - - for manifest in intel_icd intel_hasvk_icd; do - if ! vulkanManifestAvailable "$manifest"; then - VULKAN_REASON="the Intel Vulkan ICD '$manifest' is not available in the container" - return 1 - fi - done ;; - - "0x1002" ) - if ! vulkanLibraryAvailable "libvulkan_radeon.so"; then - VULKAN_REASON="the AMD Vulkan driver library 'libvulkan_radeon.so' is not available in the container" - return 1 - fi - - if ! vulkanManifestAvailable "radeon_icd"; then - VULKAN_REASON="the AMD Vulkan ICD 'radeon_icd' is not available in the container" - return 1 - fi ;; - esac - - vulkanRuntimeReady || return 1 - - return 0 -} - -# NVIDIA uses the proprietary host driver injected by NVIDIA Container Toolkit -# rather than a Mesa Gallium driver from qemu-minimal. Require the complete EGL -# and GBM path before selecting an NVIDIA render node. Venus additionally needs -# the Vulkan loader, NVIDIA ICD and NVIDIA Vulkan userspace libraries. - -nvidiaDriverVersion() { - - local data="" - NVIDIA_DRIVER_VERSION="" - - if [ -r /proc/driver/nvidia/version ]; then - data="$(head -n 1 /proc/driver/nvidia/version 2>/dev/null || true)" - elif [ -r /sys/module/nvidia/version ]; then - data="$(cat /sys/module/nvidia/version 2>/dev/null || true)" - fi - - if [[ "$data" =~ ([0-9]{3,})\.([0-9]+)(\.[0-9]+)? ]]; then - NVIDIA_DRIVER_VERSION="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}${BASH_REMATCH[3]:-}" - return 0 - fi - - return 1 -} - -nvidiaVulkanReady() { - - local icd="" - local major minor - - if ! nvidiaDriverVersion; then - NVIDIA_REASON="the NVIDIA driver version cannot be determined" - return 1 - fi - -if ! [[ "$NVIDIA_DRIVER_VERSION" =~ ^([0-9]+)\.([0-9]+) ]]; then - NVIDIA_REASON="the NVIDIA driver version '$NVIDIA_DRIVER_VERSION' could not be parsed" - return 1 -fi -major="${BASH_REMATCH[1]}" -minor="${BASH_REMATCH[2]}" - - if (( major < 570 || (major == 570 && minor < 86) )); then - NVIDIA_REASON="NVIDIA driver $NVIDIA_DRIVER_VERSION is older than the 570.86 minimum required by Venus" - return 1 - fi - - if ! compgen -G '/usr/lib/*/libvulkan.so.1' >/dev/null 2>&1 \ - && [ ! -e /usr/lib/libvulkan.so.1 ] \ - && [ ! -e /usr/lib64/libvulkan.so.1 ]; then - NVIDIA_REASON="the Vulkan loader is not available in the container" - return 1 - fi - - for icd in /etc/vulkan/icd.d/nvidia_icd*.json /usr/share/vulkan/icd.d/nvidia_icd*.json; do - [ -r "$icd" ] && break - icd="" - done - - if [ -z "$icd" ]; then - NVIDIA_REASON="the NVIDIA Vulkan ICD is not available in the container" - return 1 - fi - - if ! compgen -G '/usr/lib/*/libGLX_nvidia.so.*' >/dev/null 2>&1 \ - && ! compgen -G '/usr/lib/*/nvidia/*/libGLX_nvidia.so.*' >/dev/null 2>&1 \ - && ! compgen -G '/usr/lib/nvidia/*/libGLX_nvidia.so.*' >/dev/null 2>&1 \ - && ! compgen -G '/usr/lib/libGLX_nvidia.so.*' >/dev/null 2>&1 \ - && ! compgen -G '/usr/lib64/nvidia/*/libGLX_nvidia.so.*' >/dev/null 2>&1 \ - && ! compgen -G '/usr/lib64/libGLX_nvidia.so.*' >/dev/null 2>&1; then - NVIDIA_REASON="the NVIDIA Vulkan driver library is not available in the container" - return 1 - fi - - if ! compgen -G '/usr/lib/*/libnvidia-glvkspirv.so.*' >/dev/null 2>&1 \ - && ! compgen -G '/usr/lib/*/nvidia/*/libnvidia-glvkspirv.so.*' >/dev/null 2>&1 \ - && ! compgen -G '/usr/lib/nvidia/*/libnvidia-glvkspirv.so.*' >/dev/null 2>&1 \ - && ! compgen -G '/usr/lib/libnvidia-glvkspirv.so.*' >/dev/null 2>&1 \ - && ! compgen -G '/usr/lib64/nvidia/*/libnvidia-glvkspirv.so.*' >/dev/null 2>&1 \ - && ! compgen -G '/usr/lib64/libnvidia-glvkspirv.so.*' >/dev/null 2>&1; then - NVIDIA_REASON="the NVIDIA Vulkan SPIR-V compiler library is not available in the container" - return 1 - fi - - if ! vulkanRuntimeReady; then - NVIDIA_REASON="$VULKAN_REASON" - return 1 - fi - - return 0 -} - -nvidiaGpuReady() { - - local modeset="" - NVIDIA_REASON="" - - if ! compgen -G '/usr/lib/*/libEGL_nvidia.so.*' >/dev/null 2>&1 \ - && ! compgen -G '/usr/lib/libEGL_nvidia.so.*' >/dev/null 2>&1 \ - && ! compgen -G '/usr/lib64/libEGL_nvidia.so.*' >/dev/null 2>&1; then - NVIDIA_REASON="the NVIDIA EGL driver is not available in the container" - return 1 - fi - - if ! compgen -G '/usr/lib/*/libnvidia-egl-gbm.so.*' >/dev/null 2>&1 \ - && ! compgen -G '/usr/lib/libnvidia-egl-gbm.so.*' >/dev/null 2>&1 \ - && ! compgen -G '/usr/lib64/libnvidia-egl-gbm.so.*' >/dev/null 2>&1; then - NVIDIA_REASON="the NVIDIA EGL GBM platform library is not available in the container" - return 1 - fi - - if ! compgen -G '/usr/lib/*/gbm/nvidia-drm_gbm.so' >/dev/null 2>&1 \ - && [ ! -e /usr/lib/gbm/nvidia-drm_gbm.so ] \ - && [ ! -e /usr/lib64/gbm/nvidia-drm_gbm.so ]; then - NVIDIA_REASON="the NVIDIA GBM backend is not available in the container" - return 1 - fi - - if [ ! -r /usr/share/glvnd/egl_vendor.d/10_nvidia.json ] \ - || [ ! -r /usr/share/egl/egl_external_platform.d/15_nvidia_gbm.json ]; then - NVIDIA_REASON="the NVIDIA EGL vendor configuration is not available in the container" - return 1 - fi - - if [ ! -r /sys/module/nvidia_drm/parameters/modeset ] \ - || ! IFS= read -r modeset < /sys/module/nvidia_drm/parameters/modeset; then - NVIDIA_REASON="the nvidia-drm KMS state cannot be determined" - return 1 - fi - - case "${modeset,,}" in - "y" | "1" ) ;; - * ) - NVIDIA_REASON="nvidia-drm modesetting is disabled" - return 1 ;; - esac - - return 0 -} - -hostBlobsSupported() { - - kernelAtLeast 6 13 -} - -venusGuestPatRequired() { - - local cpu_vendor driver device="" - - # TCG does not use the Intel KVM guest-PAT quirk. - disabled "${KVM:-}" && return 1 - - isIntelCpu || return 1 - - case "$GPU_VENDOR" in - "0x1002" | "0x10de" ) - # RADV/NVIDIA dGPU on an Intel CPU. - return 0 ;; - "0x8086" ) - driver=$(readlink -f "/sys/class/drm/${RENDER_NAME}/device/driver" 2>/dev/null || true) - driver="${driver##*/}" - [[ "$driver" == "xe" ]] && return 0 - - if [ -r "/sys/class/drm/${RENDER_NAME}/device/device" ]; then - IFS= read -r device < "/sys/class/drm/${RENDER_NAME}/device/device" || device="" - device="${device,,}" - fi - - # Meteor Lake requires guest PAT even when it is still using i915. - case "$device" in - "0x7d40" | "0x7d45" | "0x7d55" | "0x7d60" | "0x7dd5" ) return 0 ;; - esac ;; - esac - - return 1 -} - -venusGuestPatReady() { - - VULKAN_PAT_REASON="" - venusGuestPatRequired || return 0 - - if ! hasFlag "ss"; then - VULKAN_PAT_REASON="the Intel CPU cannot safely honor guest PAT because self-snoop is unavailable" - return 1 - fi - - if ! kernelAtLeast 6 16; then - VULKAN_PAT_REASON="Linux 6.16 or newer is required for guest PAT support on this Intel CPU/GPU combination" - return 1 - fi - - VIRTGPU_GUEST_PAT="Y" - return 0 -} - -GPU_VENDOR="" -NVIDIA_NODE="" -NVIDIA_REASON="" -VULKAN_REASON="" -VULKAN_PAT_REASON="" -VIRTGPU_GUEST_PAT="" - -if [ -n "$RENDERNODE" ]; then - - if ! gpuNodeVendor "$RENDERNODE"; then - gpuSetupFailure "GPU render node '$RENDERNODE' is unavailable or inaccessible" - return 0 - fi - - case "$GPU_VENDOR" in - "0x8086" ) - if ! intelMesaReady "$RENDERNODE"; then - gpuSetupFailure "Intel GPU at $RENDERNODE is not supported by qemu-render" - return 0 - fi ;; - "0x1002" ) ;; - "0x10de" ) - if ! nvidiaGpuReady; then - gpuSetupFailure "NVIDIA GPU at $RENDERNODE cannot be used for hardware rendering because $NVIDIA_REASON" - return 0 - fi ;; - * ) - gpuSetupFailure "Unsupported GPU at $RENDERNODE" - return 0 ;; - esac - -else - - if [ ! -d /dev/dri ]; then - gpuSetupFailure "GPU acceleration was requested, but '/dev/dri' was not added to the devices section of your compose file" - return 0 - fi - - RENDER_NODE_FOUND="N" - - for node in /dev/dri/renderD*; do - - [ -e "$node" ] || continue - RENDER_NODE_FOUND="Y" - - if ! gpuNodeVendor "$node"; then - continue - fi - - case "$GPU_VENDOR" in - "0x8086" ) - if intelMesaReady "$node"; then - RENDERNODE="$node" - break - fi ;; - "0x1002" ) - RENDERNODE="$node" - break ;; - "0x10de" ) - NVIDIA_NODE="$node" - if nvidiaGpuReady; then - RENDERNODE="$node" - break - fi ;; - esac - - done - - if [ -z "$RENDERNODE" ]; then - - if [ -n "$NVIDIA_NODE" ] && [ -n "$NVIDIA_REASON" ]; then - gpuSetupFailure "NVIDIA GPU at $NVIDIA_NODE cannot be used for hardware rendering because $NVIDIA_REASON" - elif [[ "$RENDER_NODE_FOUND" != "Y" ]]; then - gpuSetupFailure "/dev/dri is available, but no GPU render nodes were found" - else - gpuSetupFailure "No usable GPU render node found" - fi - - return 0 - fi - -fi - -# Re-read the selected node after auto-detection so the vendor name and device -# number below are based on the final render node and survive hotplug races. -if ! gpuNodeVendor "$RENDERNODE"; then - gpuSetupFailure "GPU render node '$RENDERNODE' became unavailable" - return 0 -fi - -RENDER_NAME="${RENDERNODE##*/}" -CARD_NUMBER="${RENDER_NAME#renderD}" -GPU_DEVICE="" -GPU_DRIVER="" - -if [ -r "/sys/class/drm/${RENDER_NAME}/device/device" ]; then - IFS= read -r GPU_DEVICE < "/sys/class/drm/${RENDER_NAME}/device/device" || GPU_DEVICE="" - GPU_DEVICE="${GPU_DEVICE,,}" -fi - -GPU_DRIVER=$(readlink -f "/sys/class/drm/${RENDER_NAME}/device/driver" 2>/dev/null || true) -GPU_DRIVER="${GPU_DRIVER##*/}" -GPU_DEVICE_NAME="" -GPU_PCI_SLOT=$(readlink -f "/sys/class/drm/${RENDER_NAME}/device" 2>/dev/null || true) -GPU_PCI_SLOT="${GPU_PCI_SLOT##*/}" - -if [[ "$GPU_PCI_SLOT" =~ ^[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-7]$ ]]; then - GPU_DEVICE_NAME=$(lspci -D -s "$GPU_PCI_SLOT" -vmm 2>/dev/null \ - | sed -n 's/^Device:[[:space:]]*//p' | head -n 1 || true) -fi - -case "$GPU_VENDOR" in - "0x8086" ) GPU_NAME="Intel" ;; - "0x1002" ) GPU_NAME="AMD" ;; - "0x10de" ) GPU_NAME="NVIDIA" ;; - * ) GPU_NAME="GPU" ;; -esac - -if [ ! -d /dev/dri ]; then - mkdir -m 755 /dev/dri 2>/dev/null || true -fi - -# Derive the matching DRM card from the validated render node number. -CARD_DEVICE="/dev/dri/card$((10#$CARD_NUMBER - 128))" - -# Containers normally have no udev, so reconstruct the matching DRM card and -# render character devices from the render-node minor number when necessary. -if [ ! -c "$CARD_DEVICE" ]; then - if mknod "$CARD_DEVICE" c 226 $((10#$CARD_NUMBER - 128)) 2>/dev/null; then - chmod 666 "$CARD_DEVICE" 2>/dev/null || true - fi -fi - -if [ ! -c "$RENDERNODE" ]; then - if mknod "$RENDERNODE" c 226 "$((10#$CARD_NUMBER))" 2>/dev/null; then - chmod 666 "$RENDERNODE" 2>/dev/null || true - fi -fi - -if ! gpuNodeVendor "$RENDERNODE"; then - gpuSetupFailure "GPU render node '$RENDERNODE' became unavailable" - return 0 -fi - -if ! hostBlobsSupported; then - gpuSetupFailure "Windows GPU acceleration requires virtio-gpu host blobs (Linux 6.13+ host kernel)" -fi - -VGA+=",hostmem=$VRAM_BYTES,max_hostmem=$VRAM_BYTES,blob=true" -VGA+=",host3d_blob_limit=$VRAM_BYTES" - -case "$GPU_VENDOR" in - "0x8086" | "0x1002" ) - if ! mesaVulkanReady "$GPU_VENDOR"; then - gpuSetupFailure "Windows GPU acceleration requires Vulkan via Venus, but $VULKAN_REASON" - fi ;; - "0x10de" ) - if ! nvidiaVulkanReady; then - gpuSetupFailure "Windows GPU acceleration requires Vulkan via Venus, but $NVIDIA_REASON" - fi ;; -esac - -if ! venusGuestPatReady; then - gpuSetupFailure "Windows GPU acceleration requires Vulkan via Venus, but $VULKAN_PAT_REASON" -fi - -VGA+=",venus=true" - -echo -info "Hardware rendering enabled:" -info - -info "Device: $GPU_NAME${GPU_DEVICE_NAME:+ $GPU_DEVICE_NAME}" - -if [ -n "$GPU_DEVICE" ]; then - info "PCI ID: ${GPU_VENDOR#0x}:${GPU_DEVICE#0x}" -fi - -info "Driver: ${GPU_DRIVER:-unknown}" - -if [[ "$GPU_VENDOR" == "0x10de" ]]; then - nvidiaDriverVersion || NVIDIA_DRIVER_VERSION="unknown" - info "Version: $NVIDIA_DRIVER_VERSION" -else - MESA_VERSION="$(dpkg-query -W -f='${Provides}\n' qemu-render 2>/dev/null \ - | sed -n 's/.*libgbm1 (= \([^)]*\)).*/\1/p' || true)" - - [ -n "$MESA_VERSION" ] && info "Mesa: $MESA_VERSION" -fi - -info "Render: $RENDERNODE" - -DISPLAY_OPTS="-display egl-headless,rendernode=$RENDERNODE" -DISPLAY_OPTS+=" -device $VGA" - -[[ "${DISPLAY,,}" == "vnc" ]] && DISPLAY_OPTS+=" -vnc :${port}${LOSSY_OPT}" -[[ "${DISPLAY,,}" == "web" ]] && DISPLAY_OPTS+=" -vnc :${port},websocket=unix:${WSS_SOCKET}${LOSSY_OPT}" - -return 0