From b69c91a5986c2e6dcafc77a334023c9bcc7eaacf Mon Sep 17 00:00:00 2001 From: iamdoubz <> Date: Sun, 5 Jul 2026 19:40:27 -0500 Subject: [PATCH] feat(commands): route AMD/Intel to DirectML ONNX engine + download_directml_package (P2) load_transcriber dispatches OnnxOpenVino/OnnxDirectML through one OnnxTranscriber block. download_and_extract_runtime is parameterized (sha + readiness) so the new stage_directml_runtime reuses it; hardware_status reports a directml package field. --- src-tauri/src/commands.rs | 98 ++++++++++++++++++++++++++++++++++----- 1 file changed, 86 insertions(+), 12 deletions(-) diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index af7fe59..e645b71 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -126,18 +126,22 @@ fn load_transcriber( // backend has a working accel path just because the hardware exists. let path = resolve_accel(backend); + // ONNX engine — NPU (OpenVINO EP) or AMD/Intel GPU (DirectML EP). Same model + // artifacts either way; the EP is chosen inside OnnxTranscriber::load from + // `backend`, and the resolved `backend` is reported so the UI shows the real + // engine. #[cfg(feature = "npu")] - if path == AccelPath::OnnxOpenVino { - use crate::transcription::{onnx_models, OnnxNpuTranscriber}; + if matches!(path, AccelPath::OnnxOpenVino | AccelPath::OnnxDirectML) { + use crate::transcription::{onnx_models, OnnxTranscriber}; if onnx_models::is_installed(onnx_models::DEFAULT_ONNX_MODEL) { let dir = onnx_models::model_dir(onnx_models::DEFAULT_ONNX_MODEL); - match OnnxNpuTranscriber::load(&dir, BackendId::Npu) { - Ok(t) => return Ok((Box::new(t), BackendId::Npu)), - Err(e) => tracing::warn!("NPU engine load failed ({e}); falling back to CPU"), + match OnnxTranscriber::load(&dir, backend) { + Ok(t) => return Ok((Box::new(t), backend)), + Err(e) => tracing::warn!("ONNX engine load failed ({e}); falling back to CPU"), } } else { tracing::warn!( - "NPU selected but ONNX model not installed; falling back to whisper.cpp" + "ONNX backend selected but model not installed; falling back to whisper.cpp" ); } } @@ -852,6 +856,12 @@ pub async fn hardware_status() -> WaResult { "runtimeReady": crate::paths::npu_runtime_ready(), "modelInstalled": npu_model_installed(), }, + // DirectML GPU package (P2): shares the ONNX model with the NPU path; + // only the runtime differs. Drives a future Settings ▸ Hardware indicator. + "directml": { + "runtimeReady": crate::paths::directml_runtime_ready(), + "modelInstalled": npu_model_installed(), + }, })) } @@ -928,7 +938,35 @@ async fn stage_npu_runtime(app: &AppHandle) -> WaResult<()> { } let url = std::env::var("WA_NPU_RUNTIME_URL").unwrap_or_else(|_| NPU_RUNTIME_URL.to_string()); - download_and_extract_runtime(app, &dir, &url).await + download_and_extract_runtime( + app, + &dir, + &url, + NPU_RUNTIME_SHA256, + crate::paths::npu_runtime_ready, + ) + .await +} + +/// Stages the DirectML ONNX Runtime into the app's DirectML runtime dir (P2). +/// Unlike the NPU bundle there's no hosted default yet, so the URL must come +/// from `WA_DIRECTML_RUNTIME_URL` — or drop `onnxruntime.dll` into +/// `runtime\directml\` manually (dev testing can also just set `ORT_DYLIB_PATH`). +#[cfg(feature = "npu")] +async fn stage_directml_runtime(app: &AppHandle) -> WaResult<()> { + if crate::paths::directml_runtime_ready() { + return Ok(()); + } + let dir = crate::paths::directml_runtime_dir(); + std::fs::create_dir_all(&dir).map_err(|e| WaError::new("directml", e.to_string()))?; + let url = std::env::var("WA_DIRECTML_RUNTIME_URL").map_err(|_| { + WaError::new( + "directml", + "no DirectML runtime: set WA_DIRECTML_RUNTIME_URL or place onnxruntime.dll in runtime\\directml\\", + ) + })?; + // Empty SHA: no published bundle to pin yet (ponytail — fill in once hosted). + download_and_extract_runtime(app, &dir, &url, "", crate::paths::directml_runtime_ready).await } #[cfg(feature = "npu")] @@ -966,10 +1004,21 @@ fn stage_npu_runtime_from_local( Ok(()) } -/// Downloads the runtime bundle (streaming progress + SHA-256 check) and unzips -/// its DLLs flat into `dir`. +/// Downloads a runtime bundle (streaming progress + SHA-256 check) and unzips +/// its DLLs flat into `dir`. `sha256` empty disables the integrity check; +/// `ready` is the on-disk readiness predicate for the target runtime (NPU or +/// DirectML), so this one function serves both. +// ponytail: progress event + error domain stay "npu"/"npu://download" even on +// the DirectML path — cosmetic only, no frontend consumer for a directml +// channel yet. Split them out when the Settings UI grows a DirectML indicator. #[cfg(feature = "npu")] -async fn download_and_extract_runtime(app: &AppHandle, dir: &Path, url: &str) -> WaResult<()> { +async fn download_and_extract_runtime( + app: &AppHandle, + dir: &Path, + url: &str, + sha256: &str, + ready: fn() -> bool, +) -> WaResult<()> { use futures_util::StreamExt; use sha2::{Digest, Sha256}; @@ -1002,7 +1051,7 @@ async fn download_and_extract_runtime(app: &AppHandle, dir: &Path, url: &str) -> drop(file); let digest = format!("{:x}", hasher.finalize()); - if !NPU_RUNTIME_SHA256.is_empty() && digest != NPU_RUNTIME_SHA256 { + if !sha256.is_empty() && digest != sha256 { let _ = std::fs::remove_file(&tmp); return Err(WaError::new("npu", "runtime bundle checksum mismatch")); } @@ -1016,7 +1065,7 @@ async fn download_and_extract_runtime(app: &AppHandle, dir: &Path, url: &str) -> .map_err(|e| WaError::new("npu", e))?; let _ = std::fs::remove_file(&tmp); - if !crate::paths::npu_runtime_ready() { + if !ready() { return Err(WaError::new( "npu", "runtime bundle extracted but onnxruntime.dll is missing", @@ -1070,6 +1119,31 @@ pub async fn download_npu_package(app: AppHandle) -> WaResult<()> { } } +/// Downloads everything the DirectML GPU engine needs — the same Whisper ONNX +/// model as the NPU path, plus the DirectML runtime (P2). The non-Vulkan GPU +/// path for AMD/Intel. +#[tauri::command] +pub async fn download_directml_package(app: AppHandle) -> WaResult<()> { + #[cfg(feature = "npu")] + { + download_npu_model(app.clone()).await?; // identical ONNX artifacts + stage_directml_runtime(&app).await?; + let _ = app.emit( + "directml://download", + serde_json::json!({ "stage": "done", "ready": crate::paths::directml_runtime_ready() }), + ); + Ok(()) + } + #[cfg(not(feature = "npu"))] + { + let _ = app; + Err(WaError::new( + "directml", + "this build has no ONNX/DirectML support", + )) + } +} + #[tauri::command] pub async fn list_models() -> WaResult> { let settings = load_settings();