diff --git a/apps/desktop/src-tauri/src/gpu_context.rs b/apps/desktop/src-tauri/src/gpu_context.rs index 275cc48e1a2..d9f8ac62217 100644 --- a/apps/desktop/src-tauri/src/gpu_context.rs +++ b/apps/desktop/src-tauri/src/gpu_context.rs @@ -49,6 +49,12 @@ pub struct SharedGpuContext { static GPU: OnceCell> = OnceCell::const_new(); async fn init_gpu_inner() -> Option { + #[cfg(target_os = "windows")] + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { + backends: wgpu::Backends::DX12 | wgpu::Backends::VULKAN, + ..Default::default() + }); + #[cfg(not(target_os = "windows"))] let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default()); let hardware_adapter = instance diff --git a/apps/desktop/src/utils/webgpu-renderer.ts b/apps/desktop/src/utils/webgpu-renderer.ts index b33f859e784..3ab05955aae 100644 --- a/apps/desktop/src/utils/webgpu-renderer.ts +++ b/apps/desktop/src/utils/webgpu-renderer.ts @@ -81,7 +81,9 @@ export async function isWebGPUSupported(): Promise { return false; } try { - const adapter = await navigator.gpu.requestAdapter(); + const adapter = await navigator.gpu.requestAdapter({ + powerPreference: "high-performance", + }); return adapter !== null; } catch { return false; @@ -91,7 +93,9 @@ export async function isWebGPUSupported(): Promise { export async function initWebGPU( canvas: OffscreenCanvas, ): Promise { - const adapter = await navigator.gpu.requestAdapter(); + const adapter = await navigator.gpu.requestAdapter({ + powerPreference: "high-performance", + }); if (!adapter) { throw new Error("No WebGPU adapter available"); } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index f378538a08f..d7ce869c9df 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -147,20 +147,48 @@ impl Renderer { break; } } - match frame_renderer - .render_immediate( - current.segment_frames, - current.uniforms, + let nv12_result = frame_renderer + .render_nv12( + current.segment_frames.clone(), + current.uniforms.clone(), ¤t.cursor, &mut layers, ) - .await - { - Ok(frame) => { - (self.frame_cb)(EditorFrameOutput::Rgba(frame)); + .await; + + match nv12_result { + Ok(pipeline_frame) => { + if let Some(prev) = pipeline_frame { + (self.frame_cb)(EditorFrameOutput::Nv12(prev)); + } + match frame_renderer.flush_pipeline_nv12().await { + Some(Ok(current_frame)) => { + (self.frame_cb)(EditorFrameOutput::Nv12(current_frame)); + } + Some(Err(e)) => { + tracing::warn!(error = %e, "Failed to flush NV12 pipeline frame"); + } + None => {} + } } Err(e) => { - tracing::error!(error = %e, "Failed to render frame in editor"); + tracing::warn!(error = %e, "NV12 render failed, falling back to RGBA"); + match frame_renderer + .render_immediate( + current.segment_frames, + current.uniforms, + ¤t.cursor, + &mut layers, + ) + .await + { + Ok(frame) => { + (self.frame_cb)(EditorFrameOutput::Rgba(frame)); + } + Err(e) => { + tracing::error!(error = %e, "Failed to render frame in editor"); + } + } } } diff --git a/crates/gpu-converters/src/bgra_rgba/mod.rs b/crates/gpu-converters/src/bgra_rgba/mod.rs index 52e11dcca4e..6c2dbb05d1d 100644 --- a/crates/gpu-converters/src/bgra_rgba/mod.rs +++ b/crates/gpu-converters/src/bgra_rgba/mod.rs @@ -14,6 +14,12 @@ pub struct BGRAToRGBA { impl BGRAToRGBA { pub async fn new() -> Result { + #[cfg(target_os = "windows")] + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { + backends: wgpu::Backends::DX12 | wgpu::Backends::VULKAN, + ..Default::default() + }); + #[cfg(not(target_os = "windows"))] let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default()); let adapter = instance diff --git a/crates/gpu-converters/src/nv12_rgba/mod.rs b/crates/gpu-converters/src/nv12_rgba/mod.rs index 1ab46be9eea..037e9df8e9a 100644 --- a/crates/gpu-converters/src/nv12_rgba/mod.rs +++ b/crates/gpu-converters/src/nv12_rgba/mod.rs @@ -12,9 +12,14 @@ pub struct NV12ToRGBA { impl NV12ToRGBA { pub async fn new() -> Self { println!("NV12ToRGBA"); + #[cfg(target_os = "windows")] + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { + backends: wgpu::Backends::DX12 | wgpu::Backends::VULKAN, + ..Default::default() + }); + #[cfg(not(target_os = "windows"))] let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default()); - // Get adapter for GPU let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::HighPerformance, diff --git a/crates/gpu-converters/src/uyvy_rgba/mod.rs b/crates/gpu-converters/src/uyvy_rgba/mod.rs index 84fa3e86149..4181cb430ad 100644 --- a/crates/gpu-converters/src/uyvy_rgba/mod.rs +++ b/crates/gpu-converters/src/uyvy_rgba/mod.rs @@ -14,6 +14,12 @@ pub struct UYVYToRGBA { impl UYVYToRGBA { pub async fn new() -> Self { + #[cfg(target_os = "windows")] + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { + backends: wgpu::Backends::DX12 | wgpu::Backends::VULKAN, + ..Default::default() + }); + #[cfg(not(target_os = "windows"))] let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default()); let adapter = instance diff --git a/crates/gpu-converters/src/yuyv_nv12/mod.rs b/crates/gpu-converters/src/yuyv_nv12/mod.rs index 62dcbb0bf51..6ab6482f19d 100644 --- a/crates/gpu-converters/src/yuyv_nv12/mod.rs +++ b/crates/gpu-converters/src/yuyv_nv12/mod.rs @@ -11,6 +11,12 @@ pub struct YUYVToNV12 { impl YUYVToNV12 { pub async fn new() -> Result { + #[cfg(target_os = "windows")] + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { + backends: wgpu::Backends::DX12 | wgpu::Backends::VULKAN, + ..Default::default() + }); + #[cfg(not(target_os = "windows"))] let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default()); let adapter = instance diff --git a/crates/gpu-converters/src/yuyv_rgba/mod.rs b/crates/gpu-converters/src/yuyv_rgba/mod.rs index 30d9c3d86c7..50602a9ee43 100644 --- a/crates/gpu-converters/src/yuyv_rgba/mod.rs +++ b/crates/gpu-converters/src/yuyv_rgba/mod.rs @@ -13,6 +13,12 @@ pub struct YUYVToRGBA { impl YUYVToRGBA { pub async fn new() -> Result { + #[cfg(target_os = "windows")] + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { + backends: wgpu::Backends::DX12 | wgpu::Backends::VULKAN, + ..Default::default() + }); + #[cfg(not(target_os = "windows"))] let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default()); let adapter = instance diff --git a/crates/rendering/src/decoder/mod.rs b/crates/rendering/src/decoder/mod.rs index 1782b5bd865..64d6e892621 100644 --- a/crates/rendering/src/decoder/mod.rs +++ b/crates/rendering/src/decoder/mod.rs @@ -667,38 +667,131 @@ pub async fn spawn_decoder( #[cfg(target_os = "windows")] { - let _ = force_ffmpeg; - let (ready_tx, ready_rx) = oneshot::channel::>(); - let (tx, rx) = mpsc::channel(); + if force_ffmpeg { + info!( + "Video '{}' using FFmpeg decoder (forced via experimental setting)", + name + ); + let (ready_tx, ready_rx) = oneshot::channel::>(); + let (tx, rx) = mpsc::channel(); - ffmpeg::FfmpegDecoder::spawn(name, path, fps, rx, ready_tx) - .map_err(|e| format!("'{name}' FFmpeg decoder / {e}"))?; + ffmpeg::FfmpegDecoder::spawn(name, path, fps, rx, ready_tx) + .map_err(|e| format!("'{name}' FFmpeg decoder / {e}"))?; - match tokio::time::timeout(timeout_duration, ready_rx).await { - Ok(Ok(Ok(init_result))) => { - info!( - "Video '{}' using {} decoder ({}x{})", - name, init_result.decoder_type, init_result.width, init_result.height + return match tokio::time::timeout(timeout_duration, ready_rx).await { + Ok(Ok(Ok(init_result))) => { + info!( + "Video '{}' using {} decoder ({}x{})", + name, init_result.decoder_type, init_result.width, init_result.height + ); + let status = DecoderStatus { + decoder_type: init_result.decoder_type, + video_width: init_result.width, + video_height: init_result.height, + fallback_reason: None, + }; + Ok(AsyncVideoDecoderHandle { + sender: tx, + offset, + status, + }) + } + Ok(Ok(Err(e))) => Err(format!( + "'{name}' FFmpeg decoder initialization failed: {e}" + )), + Ok(Err(e)) => Err(format!("'{name}' FFmpeg decoder channel closed: {e}")), + Err(_) => Err(format!( + "'{name}' FFmpeg decoder timed out after 30s initializing: {path_display}" + )), + }; + } + + let mf_result = { + let (ready_tx, ready_rx) = oneshot::channel::>(); + let (tx, rx) = mpsc::channel(); + + match media_foundation::MFDecoder::spawn(name, path.clone(), fps, rx, ready_tx) { + Ok(()) => match tokio::time::timeout(timeout_duration, ready_rx).await { + Ok(Ok(Ok(init_result))) => { + info!( + "Video '{}' using {} decoder ({}x{})", + name, init_result.decoder_type, init_result.width, init_result.height + ); + let status = DecoderStatus { + decoder_type: init_result.decoder_type, + video_width: init_result.width, + video_height: init_result.height, + fallback_reason: None, + }; + Ok(AsyncVideoDecoderHandle { + sender: tx, + offset, + status, + }) + } + Ok(Ok(Err(e))) => Err(format!( + "'{name}' MediaFoundation initialization failed: {e} ({path_display})" + )), + Ok(Err(e)) => Err(format!( + "'{name}' MediaFoundation channel closed: {e} ({path_display})" + )), + Err(_) => Err(format!( + "'{name}' MediaFoundation timed out after 30s initializing: {path_display}" + )), + }, + Err(e) => Err(format!( + "'{name}' MediaFoundation spawn failed: {e} ({path_display})" + )), + } + }; + + match mf_result { + Ok(handle) => Ok(handle), + Err(mf_error) => { + tracing::warn!( + name = name, + error = %mf_error, + "MediaFoundation failed, falling back to FFmpeg decoder" ); - let status = DecoderStatus { - decoder_type: init_result.decoder_type, - video_width: init_result.width, - video_height: init_result.height, - fallback_reason: None, - }; - Ok(AsyncVideoDecoderHandle { - sender: tx, - offset, - status, - }) + + let (ready_tx, ready_rx) = oneshot::channel::>(); + let (tx, rx) = mpsc::channel(); + + if let Err(e) = ffmpeg::FfmpegDecoder::spawn(name, path, fps, rx, ready_tx) { + return Err(format!( + "'{name}' decoder failed - MediaFoundation: {mf_error}, FFmpeg: {e}" + )); + } + + match tokio::time::timeout(timeout_duration, ready_rx).await { + Ok(Ok(Ok(init_result))) => { + info!( + "Video '{}' using {} decoder ({}x{}) after MediaFoundation failure", + name, init_result.decoder_type, init_result.width, init_result.height + ); + let status = DecoderStatus { + decoder_type: init_result.decoder_type, + video_width: init_result.width, + video_height: init_result.height, + fallback_reason: Some(mf_error), + }; + Ok(AsyncVideoDecoderHandle { + sender: tx, + offset, + status, + }) + } + Ok(Ok(Err(e))) => Err(format!( + "'{name}' decoder failed - MediaFoundation: {mf_error}, FFmpeg: {e}" + )), + Ok(Err(e)) => Err(format!( + "'{name}' decoder failed - MediaFoundation: {mf_error}, FFmpeg channel: {e}" + )), + Err(_) => Err(format!( + "'{name}' decoder failed - MediaFoundation: {mf_error}, FFmpeg timed out" + )), + } } - Ok(Ok(Err(e))) => Err(format!( - "'{name}' FFmpeg decoder initialization failed: {e}" - )), - Ok(Err(e)) => Err(format!("'{name}' FFmpeg decoder channel closed: {e}")), - Err(_) => Err(format!( - "'{name}' FFmpeg decoder timed out after 30s initializing: {path_display}" - )), } } diff --git a/crates/rendering/src/layers/display.rs b/crates/rendering/src/layers/display.rs index 05d545ce0a4..fcca1100d97 100644 --- a/crates/rendering/src/layers/display.rs +++ b/crates/rendering/src/layers/display.rs @@ -472,21 +472,119 @@ impl DisplayLayer { PixelFormat::Nv12 => { let screen_frame = &segment_frames.screen_frame; - if !self.prefer_cpu_conversion { + #[cfg(target_os = "windows")] + let d3d11_zero_copy_succeeded = { + let mut succeeded = false; + if !self.prefer_cpu_conversion + && let (Some(y_handle), Some(uv_handle)) = ( + screen_frame.d3d11_y_handle(), + screen_frame.d3d11_uv_handle(), + ) + && self + .yuv_converter + .convert_nv12_from_d3d11_shared_handles( + device, + queue, + y_handle, + uv_handle, + actual_width, + actual_height, + ) + .is_ok() + && self.yuv_converter.output_texture().is_some() + { + self.pending_copy = Some(PendingTextureCopy { + width: actual_width, + height: actual_height, + dst_texture_index: next_texture, + }); + succeeded = true; + } + succeeded + }; + + #[cfg(target_os = "windows")] + if d3d11_zero_copy_succeeded { + true + } else if !self.prefer_cpu_conversion { if let (Some(y_data), Some(uv_data)) = (screen_frame.y_plane(), screen_frame.uv_plane()) { let y_stride = screen_frame.y_stride(); let uv_stride = screen_frame.uv_stride(); - #[cfg(target_os = "windows")] - let convert_width = actual_width; - #[cfg(target_os = "windows")] - let convert_height = actual_height; - #[cfg(not(target_os = "windows"))] - let convert_width = frame_size.x; - #[cfg(not(target_os = "windows"))] - let convert_height = frame_size.y; + let convert_result = self.yuv_converter.convert_nv12_to_encoder( + device, + queue, + encoder, + y_data, + uv_data, + actual_width, + actual_height, + y_stride, + uv_stride, + ); + + match convert_result { + Ok(_) => { + if self.yuv_converter.output_texture().is_some() { + self.pending_copy = Some(PendingTextureCopy { + width: actual_width, + height: actual_height, + dst_texture_index: next_texture, + }); + true + } else { + false + } + } + Err(_) => false, + } + } else { + false + } + } else if let (Some(y_data), Some(uv_data)) = + (screen_frame.y_plane(), screen_frame.uv_plane()) + { + let y_stride = screen_frame.y_stride(); + let uv_stride = screen_frame.uv_stride(); + let convert_result = self.yuv_converter.convert_nv12_cpu( + device, + queue, + y_data, + uv_data, + actual_width, + actual_height, + y_stride, + uv_stride, + ); + + match convert_result { + Ok(_) => { + if self.yuv_converter.output_texture().is_some() { + self.pending_copy = Some(PendingTextureCopy { + width: actual_width, + height: actual_height, + dst_texture_index: next_texture, + }); + true + } else { + false + } + } + Err(_) => false, + } + } else { + false + } + + #[cfg(not(target_os = "windows"))] + if !self.prefer_cpu_conversion { + if let (Some(y_data), Some(uv_data)) = + (screen_frame.y_plane(), screen_frame.uv_plane()) + { + let y_stride = screen_frame.y_stride(); + let uv_stride = screen_frame.uv_stride(); let convert_result = self.yuv_converter.convert_nv12_to_encoder( device, @@ -494,8 +592,8 @@ impl DisplayLayer { encoder, y_data, uv_data, - convert_width, - convert_height, + frame_size.x, + frame_size.y, y_stride, uv_stride, ); @@ -504,8 +602,8 @@ impl DisplayLayer { Ok(_) => { if self.yuv_converter.output_texture().is_some() { self.pending_copy = Some(PendingTextureCopy { - width: convert_width, - height: convert_height, + width: frame_size.x, + height: frame_size.y, dst_texture_index: next_texture, }); true diff --git a/crates/rendering/src/lib.rs b/crates/rendering/src/lib.rs index fdf8129fead..5690609fec5 100644 --- a/crates/rendering/src/lib.rs +++ b/crates/rendering/src/lib.rs @@ -979,6 +979,12 @@ impl RenderVideoConstants { .map(|c| XY::new(c.width, c.height)), }; + #[cfg(target_os = "windows")] + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { + backends: wgpu::Backends::DX12 | wgpu::Backends::VULKAN, + ..Default::default() + }); + #[cfg(not(target_os = "windows"))] let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default()); let hardware_adapter = instance