Ship the OpenVINO and WebGPU runtimes in every desktop package

The Windows installer and the Flatpak carried no ONNX Runtime, so they
ran every model on tract's one core; the Arch package left it to an
optional dependency. Each now installs two builds under runtimes/ —
Intel's OpenVINO build and the generic WebGPU one, both with the CPU
provider — fetched by tools/fetch-bundled-runtimes.sh from PyPI wheels
pinned by SHA-256, pruned to the native libraries (81 + 31 MB on Linux,
67 + 42 MB on Windows), licence texts beside them.

darkroom-desktop searches runtimes/openvino and runtimes/webgpu under
each place a package installs to; the engine opens all it finds and
keeps the one that fits the GPU, so a CUDA or ROCm runtime installed
beside them still wins on its vendor's card. On Windows the chosen
runtime's directory goes on PATH, because Intel's build leaves OpenVINO's
DLLs for the loader to find there.

The Windows image gains unzip; the installer smoke test checks both
runtimes landed.
This commit is contained in:
2026-10-04 21:00:15 -04:00
parent 2ced6f114f
commit cb97ebe7ac
9 changed files with 210 additions and 13 deletions
+6
View File
@@ -499,6 +499,12 @@ jobs:
WANT=$(ls docs/manual/media | wc -l)
GOT=$(ls "$INST/manual/media" | wc -l)
[ "$GOT" = "$WANT" ] || { echo "FAIL: expected $WANT manual pictures, installed $GOT"; exit 1; }
# Both bundled runtimes, each with its provider beside it
# (tools/fetch-bundled-runtimes.sh).
for f in openvino/onnxruntime.dll openvino/onnxruntime_providers_openvino.dll \
openvino/openvino.dll webgpu/onnxruntime.dll webgpu/dxcompiler.dll; do
[ -f "$INST/runtimes/$f" ] || { echo "FAIL: runtimes/$f not installed"; exit 1; }
done
wine reg query 'HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\DarkRoom' 2>/dev/null \
| grep -q DisplayVersion || { echo "FAIL: no uninstall registry key"; exit 1; }
wine "$INST/darkroom.exe" --version 2>/dev/null | grep -q '^darkroom-desktop ' \
+20 -7
View File
@@ -106,24 +106,37 @@ fn main() -> anyhow::Result<()> {
/// providers, or against the wrong cuDNN — and a system copy whose providers
/// do not load is not a problem, only a slower app: the probe builds a real
/// session before believing a provider.
///
/// The order breaks ties only. The engine opens every runtime on this list
/// and loads the one whose providers fit the GPU (inference.md §3.2), so a
/// package's bundled builds — `runtimes/openvino` and `runtimes/webgpu`
/// beside each place a package installs to, from
/// `tools/fetch-bundled-runtimes.sh` — sit beside a CUDA or ROCm runtime
/// without hiding it.
fn runtime_dirs() -> Vec<PathBuf> {
// A place a package installs to, and the bundled runtimes under it.
fn packaged(dirs: &mut Vec<PathBuf>, base: PathBuf) {
dirs.push(base.join("runtimes/openvino"));
dirs.push(base.join("runtimes/webgpu"));
dirs.push(base);
}
let mut dirs = Vec::new();
if let Some(dir) = std::env::var_os("DARKROOM_ORT_DIR") {
dirs.push(PathBuf::from(dir));
}
if let Ok(exe) = std::env::current_exe() {
if let Some(bin) = exe.parent() {
dirs.push(bin.to_path_buf());
dirs.push(bin.join("../lib/darkroom"));
packaged(&mut dirs, bin.to_path_buf());
packaged(&mut dirs, bin.join("../lib/darkroom"));
}
}
dirs.push(dr_ui::inference::user_runtime_dir());
#[cfg(target_os = "linux")]
dirs.extend([
PathBuf::from("/app/lib/darkroom"),
PathBuf::from("/usr/lib/darkroom"),
PathBuf::from("/usr/lib"),
]);
{
packaged(&mut dirs, PathBuf::from("/app/lib/darkroom"));
packaged(&mut dirs, PathBuf::from("/usr/lib/darkroom"));
dirs.push(PathBuf::from("/usr/lib"));
}
// An app bundle keeps its libraries in `Contents/Frameworks`, beside
// the `Contents/MacOS` the executable is in; then Homebrew's
// `onnxruntime`, Apple silicon's prefix before Intel's. Homebrew's build
+13
View File
@@ -144,6 +144,19 @@ fn install_best(dirs: &[PathBuf]) -> Option<Runtime> {
std::env::set_var("ADSP_LIBRARY_PATH", dir);
}
// Windows looks for a provider's own dependencies — OpenVINO's DLLs,
// which Intel's build leaves beside it — on the DLL search path, not in
// the provider's directory. Intel's Python shim prepends to `PATH` for
// the same reason; so does this, before any provider loads.
#[cfg(target_os = "windows")]
if let Some(dir) = chosen.path.parent() {
let old = std::env::var_os("PATH").unwrap_or_default();
let dirs = std::iter::once(dir.to_path_buf()).chain(std::env::split_paths(&old));
if let Ok(path) = std::env::join_paths(dirs) {
std::env::set_var("PATH", path);
}
}
log::info!(
"inference: ONNX Runtime {} from {}",
chosen.version,
+3
View File
@@ -31,6 +31,9 @@ ENV DEBIAN_FRONTEND=noninteractive \
# ---------------------------------------------------------------------------
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl git git-lfs \
# The bundled ONNX Runtime builds arrive as wheels, which are zips
# (tools/fetch-bundled-runtimes.sh).
unzip \
# A *host* C compiler as well as the cross one: build scripts and
# proc-macros are compiled for Linux and linked with `cc`, whatever
# the target. Without it the very first build script fails with
+7
View File
@@ -90,6 +90,13 @@ for f in "${REPO}/docs/manual/media"/*; do
done
echo "==> staged the manual and $(ls "${STAGE}/manual/media" | wc -l) picture(s)"
# The two ONNX Runtime builds the app chooses between at launch
# (docs/dev/inference.md §3.2): Intel's OpenVINO build for an Intel GPU and
# the WebGPU build — D3D12 — for any other, both carrying the CPU provider.
# Beside the executable under `runtimes\`, where darkroom-desktop looks.
"${REPO}/tools/fetch-bundled-runtimes.sh" windows "${STAGE}/runtimes"
echo "==> staged $(ls "${STAGE}/runtimes"/* | wc -l) runtime file(s)"
# One installer in the output directory, the one just built. The directory
# is cached between CI runs, so after a version bump a glob over it would find
# two and the smoke test would hand Wine both names as one path.
+13 -6
View File
@@ -15,14 +15,17 @@ license=('GPL-3.0-or-later')
# Runtime: Vulkan for wgpu, and a Secret Service implementation for the
# Nextcloud credentials (FR-NC-2) — gnome-keyring or kwallet both provide it.
depends=('vulkan-icd-loader' 'fontconfig' 'libxkbcommon')
makedepends=('cargo' 'git')
# ONNX Runtime is loaded from /usr/lib at launch if a package put it there
# (docs/inference.md §3): the CPU build is 8–10× the built-in tract, the
# ROCm build adds the MIGraphX rung on an AMD GPU. Neither is required.
makedepends=('cargo' 'git' 'curl' 'unzip')
# Two ONNX Runtime builds ship in /usr/lib/darkroom/runtimes — Intel's
# OpenVINO build and the generic WebGPU one, both 8–10× the built-in tract
# on the CPU alone — and the app opens every runtime it finds and keeps the
# one that fits the GPU (docs/dev/inference.md §3.2). The ROCm build in
# /usr/lib adds the MIGraphX rung on an AMD GPU and outranks both there;
# Intel's OpenCL driver is what lets OpenVINO reach an Intel GPU.
optdepends=('gnome-keyring: store Nextcloud credentials'
'kwallet: store Nextcloud credentials'
'onnxruntime-cpu: run the neural models on every core'
'onnxruntime-rocm: run the neural models on an AMD GPU')
'onnxruntime-rocm: run the neural models on an AMD GPU'
'intel-compute-runtime: run the neural models on an Intel GPU')
options=('!lto') # the workspace sets its own LTO in Cargo.toml
_repo="$(cd "${startdir}/.." && pwd)"
@@ -59,6 +62,10 @@ package() {
install -Dm644 "README.md" "${pkgdir}/usr/share/doc/${pkgname}/README.md"
# The bundled runtimes, where darkroom-desktop's search finds them
# (`runtimes/` under /usr/lib/darkroom). Pinned wheels, checked by hash.
./tools/fetch-bundled-runtimes.sh linux "${pkgdir}/usr/lib/darkroom/runtimes"
# The manual: the rendered page and its pictures, where the app's Help
# opens it (dr_ui::manual, through dr_plat::system_data_dirs). Offline by
# design — the help sheet's "See it" links land here, on a machine that
@@ -185,6 +185,15 @@ modules:
install -Dm644 "models/scene/$m" "/app/share/darkroom/models/$m"
done
# The two runtimes the app chooses between at launch
# (docs/dev/inference.md §3.2): Intel's OpenVINO build and the generic
# WebGPU one, each with ONNX Runtime's CPU provider — 8–10× the
# built-in tract on any machine. Pinned wheels, fetched over the
# build's network. In the sandbox the OpenVINO rung finds no Intel
# OpenCL driver and the probe settles on the CPU; WebGPU reaches the
# GPU through the runtime's Vulkan.
- ./tools/fetch-bundled-runtimes.sh linux /app/lib/darkroom/runtimes
- install -Dm644 README.md /app/share/doc/darkroom/README.md
sources:
+7
View File
@@ -84,6 +84,12 @@ Section "DarkRoom" SecMain
SetOutPath "$INSTDIR\manual"
File /r "${STAGE}\manual\*"
; ONNX Runtime, twice: Intel's OpenVINO build and the WebGPU build. The
; application opens both and keeps the one that fits the GPU
; (docs/dev/inference.md §3.2); each also runs the models on every core.
SetOutPath "$INSTDIR\runtimes"
File /r "${STAGE}\runtimes\*"
WriteUninstaller "$INSTDIR\uninstall.exe"
; Add/Remove Programs. HKCU, to match the per-user install.
@@ -123,6 +129,7 @@ Section "Uninstall"
Delete "$INSTDIR\uninstall.exe"
RMDir /r "$INSTDIR\models"
RMDir /r "$INSTDIR\manual"
RMDir /r "$INSTDIR\runtimes"
RMDir "$INSTDIR"
Delete "$SMPROGRAMS\${NAME}\${NAME}.lnk"
+132
View File
@@ -0,0 +1,132 @@
#!/usr/bin/env bash
# Fetch the two ONNX Runtime builds a desktop package ships
# (docs/dev/inference.md §3.2), each into its own directory:
#
# DEST/openvino Intel's build: the OpenVINO rung on an Intel GPU
# DEST/webgpu Microsoft's WebGPU build: the generic rung on any other
#
# ./tools/fetch-bundled-runtimes.sh {linux|windows} DEST
#
# Only one runtime loads per process; the app opens every one it finds and
# keeps the one that fits the device's GPU (`dr_inference_engine::api`).
# Both carry ONNX Runtime's CPU provider, which is the floor either way.
# A CUDA or ROCm runtime is never bundled (§3.1) — the user's own, found
# beside these, outranks both on its vendor's GPU.
#
# The wheels are PyPI's, pinned by SHA-256: the option names the engine
# sets were read from these versions' source (CLAUDE.md, "Providers").
# Only the native libraries are kept — not the Python bindings, not
# OpenVINO's CPU plugin (ONNX Runtime's CPU provider is the floor), not
# the duplicate versioned copies a wheel holds as files.
#
# Licences: ONNX Runtime MIT, OpenVINO Apache-2.0, oneTBB Apache-2.0, the
# DirectX shader compiler (Windows WebGPU) LLVM/MIT; the texts go beside
# the libraries.
set -euo pipefail
PLATFORM="${1:?usage: fetch-bundled-runtimes.sh linux|windows DEST}"
DEST="${2:?usage: fetch-bundled-runtimes.sh linux|windows DEST}"
PYPI="https://files.pythonhosted.org/packages"
case "${PLATFORM}" in
linux)
ORT_OPENVINO="${PYPI}/08/07/f225999919f56506b603aaa3ff837ad563ab26f86906ed7fa7e5abcd849e/onnxruntime_openvino-1.24.1-cp313-cp313-manylinux_2_28_x86_64.whl"
ORT_OPENVINO_SHA=2c3bb73e68ac27f4891af8a595c1faf574ec68b772e6583c90a0b997a1822782
ORT_WEBGPU="${PYPI}/7a/4e/782b2457b863e1748866b82d918323e61c2d0108a01906a278e7a86a3a55/onnxruntime_webgpu-1.27.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl"
ORT_WEBGPU_SHA=3eb30b487d2b428d2e5c1ac538177ab6336cce9b204197135dc5a16753b0258e
# The Linux wheel carries OpenVINO itself, under the names the provider
# was linked against.
OPENVINO_KEEP=(libonnxruntime.so.1.24.1 libonnxruntime_providers_shared.so
libonnxruntime_providers_openvino.so libopenvino.so.2541
libopenvino_onnx_frontend.so.2541 libopenvino_intel_gpu_plugin.so
libtbb.so.12 libtbbmalloc.so)
WEBGPU_KEEP=(libonnxruntime.so.1.27.0 libonnxruntime_providers_shared.so)
;;
windows)
ORT_OPENVINO="${PYPI}/3e/92/46ae2cd565961a89189900f385bb2f13a9fa731ea4674001d23720fbb1e0/onnxruntime_openvino-1.24.1-cp313-cp313-win_amd64.whl"
ORT_OPENVINO_SHA=434bf49aa71393c577a456c9d76c98e6d6958a833fa0876793e3d5437b5a511a
ORT_WEBGPU="${PYPI}/dd/f3/6294f9617e97035771593d604729a420a848150054cc1c422a47a4915412/onnxruntime_webgpu-1.27.0-cp313-cp313-win_amd64.whl"
ORT_WEBGPU_SHA=c45377099fcf23ae87427eb52e2b1d35415cabb4e3419dc645f9ee08f730e9aa
# The Windows wheel leaves OpenVINO to the `openvino` wheel; its DLLs
# go in the same directory, which the engine puts on the DLL search
# path when it chooses this runtime.
OPENVINO_LIBS="${PYPI}/3c/e5/da52a86cc5f1c86871002712429cdcca0c0dbff12dfbce730b05db60340b/openvino-2025.4.1-20426-cp313-cp313-win_amd64.whl"
OPENVINO_LIBS_SHA=a28eef35e3ed497c3238eb8f3d1ee90647c449707a8b0a7630758cd15555d8dd
OPENVINO_KEEP=(onnxruntime.dll onnxruntime_providers_shared.dll
onnxruntime_providers_openvino.dll openvino.dll
openvino_onnx_frontend.dll openvino_intel_gpu_plugin.dll
tbb12.dll tbbmalloc.dll)
WEBGPU_KEEP=(onnxruntime.dll onnxruntime_providers_shared.dll dxcompiler.dll dxil.dll)
;;
*)
echo "error: platform is linux or windows, not ${PLATFORM}" >&2
exit 2
;;
esac
WORK="$(mktemp -d)"
trap 'rm -rf "${WORK}"' EXIT
# fetch URL SHA256 DIR: download a wheel, check it, unpack it into DIR.
fetch() {
local url="$1" sha="$2" dir="$3" whl
whl="${WORK}/$(basename "${url}")"
echo "==> $(basename "${url}")"
curl -fsSL -o "${whl}" "${url}"
echo "${sha} ${whl}" | sha256sum -c --quiet - || {
echo "error: $(basename "${url}") does not match its pinned SHA-256" >&2
exit 1
}
mkdir -p "${dir}"
unzip -q -o "${whl}" -d "${dir}"
}
# keep FROM TO NAMES...: copy only NAMES, every one of which must exist.
keep() {
local from="$1" to="$2" name
shift 2
mkdir -p "${to}"
for name in "$@"; do
local src
src="$(find "${from}" -name "${name}" -type f | head -1)"
[[ -n "${src}" ]] || {
echo "error: ${name} is not in the wheel" >&2
exit 1
}
install -m755 "${src}" "${to}/${name}"
done
}
fetch "${ORT_OPENVINO}" "${ORT_OPENVINO_SHA}" "${WORK}/openvino"
if [[ -n "${OPENVINO_LIBS:-}" ]]; then
fetch "${OPENVINO_LIBS}" "${OPENVINO_LIBS_SHA}" "${WORK}/openvino"
fi
fetch "${ORT_WEBGPU}" "${ORT_WEBGPU_SHA}" "${WORK}/webgpu"
rm -rf "${DEST}/openvino" "${DEST}/webgpu"
keep "${WORK}/openvino" "${DEST}/openvino" "${OPENVINO_KEEP[@]}"
keep "${WORK}/webgpu" "${DEST}/webgpu" "${WEBGPU_KEEP[@]}"
# The wheels' own licence and notice files — ONNX Runtime keeps its in the
# package directory, OpenVINO in `dist-info` — beside what they cover,
# each under the name of the directory it came from: two wheels unpacked
# into one directory both carry a `LICENSE`.
for rt in openvino webgpu; do
find "${WORK}/${rt}" -maxdepth 3 -type f \
\( -iname 'LICENSE*' -o -iname 'NOTICE*' -o -iname 'ThirdPartyNotices*' \) |
while IFS= read -r f; do
wheel="$(basename "$(dirname "${f}")" .dist-info)"
[[ "${wheel}" == licenses ]] && wheel="$(basename "$(dirname "$(dirname "${f}")")" .dist-info)"
install -m644 "${f}" "${DEST}/${rt}/${wheel}.$(basename "${f}")"
done
done
# The Linux wheel bundles OpenVINO without its licence; Apache-2.0 asks
# for the text beside the binaries. From the tag the libraries were built at.
if [[ "${PLATFORM}" == linux ]]; then
curl -fsSL -o "${DEST}/openvino/openvino-2025.4.1.LICENSE" \
"https://raw.githubusercontent.com/openvinotoolkit/openvino/2025.4.1/LICENSE"
echo "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4 ${DEST}/openvino/openvino-2025.4.1.LICENSE" |
sha256sum -c --quiet -
fi
du -sh "${DEST}/openvino" "${DEST}/webgpu"