diff --git a/src-tauri/src/ai_processing.rs b/src-tauri/src/ai_processing.rs index ca2fdd06c..24c5cd269 100644 --- a/src-tauri/src/ai_processing.rs +++ b/src-tauri/src/ai_processing.rs @@ -379,7 +379,7 @@ pub async fn get_or_init_denoise_model( let _ = ort::init().with_name("AI-Denoise").commit(); let model_path = models_dir.join(DENOISE_FILENAME); - let session = Session::builder()?.commit_from_file(model_path)?; + let session = create_session(app_handle, model_path)?; let denoise_model = Arc::new(Mutex::new(session)); crate::register_exit_handler(); @@ -1508,3 +1508,49 @@ pub struct AiDepthMaskParameters { #[serde(default)] pub orientation_steps: Option, } + +fn create_session>( + app_handle: &tauri::AppHandle, + model_path: P, +) -> Result { + let settings = crate::app_settings::load_settings(app_handle.clone()).unwrap_or_default(); + let enable_hardware_acceleration = settings.enable_denoise_hardware_acceleration; + let path_ref = model_path.as_ref(); + + #[cfg(target_os = "macos")] + if enable_hardware_acceleration { + use ort::execution_providers::{ + CoreMLExecutionProvider, + coreml::{CoreMLComputeUnits, CoreMLModelFormat}, + }; + + // Attempt CoreML compilation + if let Ok(builder) = Session::builder() { + let provider = CoreMLExecutionProvider::default() + .with_subgraphs(true) + .with_model_format(CoreMLModelFormat::MLProgram) + .with_compute_units(CoreMLComputeUnits::All) + .build(); + + if let Ok(builder_with_ep) = builder.with_execution_providers([provider]) { + match builder_with_ep.commit_from_file(path_ref) { + Ok(session) => return Ok(session), + Err(e) => { + log::warn!( + "Hardware acceleration failed for {:?}, falling back to CPU. Error: {}", + path_ref.file_name().unwrap_or_default(), + e + ); + } + } + } + } + } + + #[cfg(not(target_os = "macos"))] + let _ = enable_hardware_acceleration; + + // Fallback to CPU execution + let builder = Session::builder()?; + builder.commit_from_file(path_ref) +} diff --git a/src-tauri/src/app_settings.rs b/src-tauri/src/app_settings.rs index da6e1ac50..8a178926f 100644 --- a/src-tauri/src/app_settings.rs +++ b/src-tauri/src/app_settings.rs @@ -320,6 +320,8 @@ pub struct AppSettings { #[serde(default)] pub use_full_dpi_rendering: Option, #[serde(default)] + pub enable_denoise_hardware_acceleration: bool, + #[serde(default)] pub high_res_zoom_multiplier: Option, #[serde(default)] pub enable_live_previews: Option, @@ -427,6 +429,7 @@ impl Default for AppSettings { editor_preview_resolution: Some(1920), enable_zoom_hifi: Some(true), use_full_dpi_rendering: Some(false), + enable_denoise_hardware_acceleration: false, enable_live_previews: Some(true), live_preview_quality: Some("high".to_string()), sort_criteria: None, diff --git a/src/components/modals/DenoiseModal.tsx b/src/components/modals/DenoiseModal.tsx index 1dfc24e0a..593925ded 100644 --- a/src/components/modals/DenoiseModal.tsx +++ b/src/components/modals/DenoiseModal.tsx @@ -235,6 +235,38 @@ export default function DenoiseModal({ const isBatch = targetPaths.length > 1; const mouseDownTarget = useRef(null); + const [elapsedTime, setElapsedTime] = useState(0); + const startTimeRef = useRef(null); + const timerIntervalRef = useRef(null); + + useEffect(() => { + if (isProcessing) { + startTimeRef.current = Date.now(); + setElapsedTime(0); + timerIntervalRef.current = setInterval(() => { + if (startTimeRef.current !== null) { + setElapsedTime(Date.now() - startTimeRef.current); + } + }, 50); + } else { + if (startTimeRef.current !== null) { + const duration = Date.now() - startTimeRef.current; + setElapsedTime(duration); + startTimeRef.current = null; + } + if (timerIntervalRef.current) { + clearInterval(timerIntervalRef.current); + timerIntervalRef.current = null; + } + } + + return () => { + if (timerIntervalRef.current) { + clearInterval(timerIntervalRef.current); + } + }; + }, [isProcessing]); + const methodOptions = useMemo>( () => [ { label: t('modals.denoise.methodAi'), value: 'ai' }, @@ -264,6 +296,7 @@ export default function DenoiseModal({ setMethod(isRaw ? 'ai' : 'bm3d'); setIntensity(isRaw ? 50 : 15); setIsMounted(true); + setElapsedTime(0); const timer = setTimeout(() => setShow(true), 10); return () => clearTimeout(timer); } else { @@ -352,6 +385,15 @@ export default function DenoiseModal({ return (
+ {elapsedTime > 0 && ( + +
+ + {t('modals.denoise.completedIn', { time: (elapsedTime / 1000).toFixed(2) })} + +
+
+ )} {savedPath && ( {currentStatusText} + {elapsedTime > 0 && ( + + {t('modals.denoise.elapsedTime', { time: (elapsedTime / 1000).toFixed(2) })} + + )} +
+ {osPlatform === 'macos' && ( + + handleProcessingSettingChange('enableDenoiseHardwareAcceleration', checked)} + /> + + )} + {osPlatform !== 'macos' && osPlatform !== 'windows' && (