Skip to content

Commit f5e712d

Browse files
Merge pull request #1613 from CapSoftware/cursor/editor-windows-playback-performance-f2d4
Editor windows playback performance
2 parents b64735c + e53808a commit f5e712d

File tree

11 files changed

+317
-53
lines changed

11 files changed

+317
-53
lines changed

apps/desktop/src-tauri/src/gpu_context.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ pub struct SharedGpuContext {
4949
static GPU: OnceCell<Option<SharedGpuContext>> = OnceCell::const_new();
5050

5151
async fn init_gpu_inner() -> Option<SharedGpuContext> {
52+
#[cfg(target_os = "windows")]
53+
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
54+
backends: wgpu::Backends::DX12 | wgpu::Backends::VULKAN,
55+
..Default::default()
56+
});
57+
#[cfg(not(target_os = "windows"))]
5258
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
5359

5460
let hardware_adapter = instance

apps/desktop/src/utils/webgpu-renderer.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ export async function isWebGPUSupported(): Promise<boolean> {
8181
return false;
8282
}
8383
try {
84-
const adapter = await navigator.gpu.requestAdapter();
84+
const adapter = await navigator.gpu.requestAdapter({
85+
powerPreference: "high-performance",
86+
});
8587
return adapter !== null;
8688
} catch {
8789
return false;
@@ -91,7 +93,9 @@ export async function isWebGPUSupported(): Promise<boolean> {
9193
export async function initWebGPU(
9294
canvas: OffscreenCanvas,
9395
): Promise<WebGPURenderer> {
94-
const adapter = await navigator.gpu.requestAdapter();
96+
const adapter = await navigator.gpu.requestAdapter({
97+
powerPreference: "high-performance",
98+
});
9599
if (!adapter) {
96100
throw new Error("No WebGPU adapter available");
97101
}

crates/editor/src/editor.rs

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -147,20 +147,48 @@ impl Renderer {
147147
break;
148148
}
149149
}
150-
match frame_renderer
151-
.render_immediate(
152-
current.segment_frames,
153-
current.uniforms,
150+
let nv12_result = frame_renderer
151+
.render_nv12(
152+
current.segment_frames.clone(),
153+
current.uniforms.clone(),
154154
&current.cursor,
155155
&mut layers,
156156
)
157-
.await
158-
{
159-
Ok(frame) => {
160-
(self.frame_cb)(EditorFrameOutput::Rgba(frame));
157+
.await;
158+
159+
match nv12_result {
160+
Ok(pipeline_frame) => {
161+
if let Some(prev) = pipeline_frame {
162+
(self.frame_cb)(EditorFrameOutput::Nv12(prev));
163+
}
164+
match frame_renderer.flush_pipeline_nv12().await {
165+
Some(Ok(current_frame)) => {
166+
(self.frame_cb)(EditorFrameOutput::Nv12(current_frame));
167+
}
168+
Some(Err(e)) => {
169+
tracing::warn!(error = %e, "Failed to flush NV12 pipeline frame");
170+
}
171+
None => {}
172+
}
161173
}
162174
Err(e) => {
163-
tracing::error!(error = %e, "Failed to render frame in editor");
175+
tracing::warn!(error = %e, "NV12 render failed, falling back to RGBA");
176+
match frame_renderer
177+
.render_immediate(
178+
current.segment_frames,
179+
current.uniforms,
180+
&current.cursor,
181+
&mut layers,
182+
)
183+
.await
184+
{
185+
Ok(frame) => {
186+
(self.frame_cb)(EditorFrameOutput::Rgba(frame));
187+
}
188+
Err(e) => {
189+
tracing::error!(error = %e, "Failed to render frame in editor");
190+
}
191+
}
164192
}
165193
}
166194

crates/gpu-converters/src/bgra_rgba/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ pub struct BGRAToRGBA {
1414

1515
impl BGRAToRGBA {
1616
pub async fn new() -> Result<Self, GpuConverterError> {
17+
#[cfg(target_os = "windows")]
18+
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
19+
backends: wgpu::Backends::DX12 | wgpu::Backends::VULKAN,
20+
..Default::default()
21+
});
22+
#[cfg(not(target_os = "windows"))]
1723
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
1824

1925
let adapter = instance

crates/gpu-converters/src/nv12_rgba/mod.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,14 @@ pub struct NV12ToRGBA {
1212
impl NV12ToRGBA {
1313
pub async fn new() -> Self {
1414
println!("NV12ToRGBA");
15+
#[cfg(target_os = "windows")]
16+
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
17+
backends: wgpu::Backends::DX12 | wgpu::Backends::VULKAN,
18+
..Default::default()
19+
});
20+
#[cfg(not(target_os = "windows"))]
1521
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
1622

17-
// Get adapter for GPU
1823
let adapter = instance
1924
.request_adapter(&wgpu::RequestAdapterOptions {
2025
power_preference: wgpu::PowerPreference::HighPerformance,

crates/gpu-converters/src/uyvy_rgba/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ pub struct UYVYToRGBA {
1414

1515
impl UYVYToRGBA {
1616
pub async fn new() -> Self {
17+
#[cfg(target_os = "windows")]
18+
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
19+
backends: wgpu::Backends::DX12 | wgpu::Backends::VULKAN,
20+
..Default::default()
21+
});
22+
#[cfg(not(target_os = "windows"))]
1723
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
1824

1925
let adapter = instance

crates/gpu-converters/src/yuyv_nv12/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ pub struct YUYVToNV12 {
1111

1212
impl YUYVToNV12 {
1313
pub async fn new() -> Result<Self, GpuConverterError> {
14+
#[cfg(target_os = "windows")]
15+
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
16+
backends: wgpu::Backends::DX12 | wgpu::Backends::VULKAN,
17+
..Default::default()
18+
});
19+
#[cfg(not(target_os = "windows"))]
1420
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
1521

1622
let adapter = instance

crates/gpu-converters/src/yuyv_rgba/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ pub struct YUYVToRGBA {
1313

1414
impl YUYVToRGBA {
1515
pub async fn new() -> Result<Self, GpuConverterError> {
16+
#[cfg(target_os = "windows")]
17+
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
18+
backends: wgpu::Backends::DX12 | wgpu::Backends::VULKAN,
19+
..Default::default()
20+
});
21+
#[cfg(not(target_os = "windows"))]
1622
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
1723

1824
let adapter = instance

crates/rendering/src/decoder/mod.rs

Lines changed: 121 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -667,38 +667,131 @@ pub async fn spawn_decoder(
667667

668668
#[cfg(target_os = "windows")]
669669
{
670-
let _ = force_ffmpeg;
671-
let (ready_tx, ready_rx) = oneshot::channel::<Result<DecoderInitResult, String>>();
672-
let (tx, rx) = mpsc::channel();
670+
if force_ffmpeg {
671+
info!(
672+
"Video '{}' using FFmpeg decoder (forced via experimental setting)",
673+
name
674+
);
675+
let (ready_tx, ready_rx) = oneshot::channel::<Result<DecoderInitResult, String>>();
676+
let (tx, rx) = mpsc::channel();
673677

674-
ffmpeg::FfmpegDecoder::spawn(name, path, fps, rx, ready_tx)
675-
.map_err(|e| format!("'{name}' FFmpeg decoder / {e}"))?;
678+
ffmpeg::FfmpegDecoder::spawn(name, path, fps, rx, ready_tx)
679+
.map_err(|e| format!("'{name}' FFmpeg decoder / {e}"))?;
676680

677-
match tokio::time::timeout(timeout_duration, ready_rx).await {
678-
Ok(Ok(Ok(init_result))) => {
679-
info!(
680-
"Video '{}' using {} decoder ({}x{})",
681-
name, init_result.decoder_type, init_result.width, init_result.height
681+
return match tokio::time::timeout(timeout_duration, ready_rx).await {
682+
Ok(Ok(Ok(init_result))) => {
683+
info!(
684+
"Video '{}' using {} decoder ({}x{})",
685+
name, init_result.decoder_type, init_result.width, init_result.height
686+
);
687+
let status = DecoderStatus {
688+
decoder_type: init_result.decoder_type,
689+
video_width: init_result.width,
690+
video_height: init_result.height,
691+
fallback_reason: None,
692+
};
693+
Ok(AsyncVideoDecoderHandle {
694+
sender: tx,
695+
offset,
696+
status,
697+
})
698+
}
699+
Ok(Ok(Err(e))) => Err(format!(
700+
"'{name}' FFmpeg decoder initialization failed: {e}"
701+
)),
702+
Ok(Err(e)) => Err(format!("'{name}' FFmpeg decoder channel closed: {e}")),
703+
Err(_) => Err(format!(
704+
"'{name}' FFmpeg decoder timed out after 30s initializing: {path_display}"
705+
)),
706+
};
707+
}
708+
709+
let mf_result = {
710+
let (ready_tx, ready_rx) = oneshot::channel::<Result<DecoderInitResult, String>>();
711+
let (tx, rx) = mpsc::channel();
712+
713+
match media_foundation::MFDecoder::spawn(name, path.clone(), fps, rx, ready_tx) {
714+
Ok(()) => match tokio::time::timeout(timeout_duration, ready_rx).await {
715+
Ok(Ok(Ok(init_result))) => {
716+
info!(
717+
"Video '{}' using {} decoder ({}x{})",
718+
name, init_result.decoder_type, init_result.width, init_result.height
719+
);
720+
let status = DecoderStatus {
721+
decoder_type: init_result.decoder_type,
722+
video_width: init_result.width,
723+
video_height: init_result.height,
724+
fallback_reason: None,
725+
};
726+
Ok(AsyncVideoDecoderHandle {
727+
sender: tx,
728+
offset,
729+
status,
730+
})
731+
}
732+
Ok(Ok(Err(e))) => Err(format!(
733+
"'{name}' MediaFoundation initialization failed: {e} ({path_display})"
734+
)),
735+
Ok(Err(e)) => Err(format!(
736+
"'{name}' MediaFoundation channel closed: {e} ({path_display})"
737+
)),
738+
Err(_) => Err(format!(
739+
"'{name}' MediaFoundation timed out after 30s initializing: {path_display}"
740+
)),
741+
},
742+
Err(e) => Err(format!(
743+
"'{name}' MediaFoundation spawn failed: {e} ({path_display})"
744+
)),
745+
}
746+
};
747+
748+
match mf_result {
749+
Ok(handle) => Ok(handle),
750+
Err(mf_error) => {
751+
tracing::warn!(
752+
name = name,
753+
error = %mf_error,
754+
"MediaFoundation failed, falling back to FFmpeg decoder"
682755
);
683-
let status = DecoderStatus {
684-
decoder_type: init_result.decoder_type,
685-
video_width: init_result.width,
686-
video_height: init_result.height,
687-
fallback_reason: None,
688-
};
689-
Ok(AsyncVideoDecoderHandle {
690-
sender: tx,
691-
offset,
692-
status,
693-
})
756+
757+
let (ready_tx, ready_rx) = oneshot::channel::<Result<DecoderInitResult, String>>();
758+
let (tx, rx) = mpsc::channel();
759+
760+
if let Err(e) = ffmpeg::FfmpegDecoder::spawn(name, path, fps, rx, ready_tx) {
761+
return Err(format!(
762+
"'{name}' decoder failed - MediaFoundation: {mf_error}, FFmpeg: {e}"
763+
));
764+
}
765+
766+
match tokio::time::timeout(timeout_duration, ready_rx).await {
767+
Ok(Ok(Ok(init_result))) => {
768+
info!(
769+
"Video '{}' using {} decoder ({}x{}) after MediaFoundation failure",
770+
name, init_result.decoder_type, init_result.width, init_result.height
771+
);
772+
let status = DecoderStatus {
773+
decoder_type: init_result.decoder_type,
774+
video_width: init_result.width,
775+
video_height: init_result.height,
776+
fallback_reason: Some(mf_error),
777+
};
778+
Ok(AsyncVideoDecoderHandle {
779+
sender: tx,
780+
offset,
781+
status,
782+
})
783+
}
784+
Ok(Ok(Err(e))) => Err(format!(
785+
"'{name}' decoder failed - MediaFoundation: {mf_error}, FFmpeg: {e}"
786+
)),
787+
Ok(Err(e)) => Err(format!(
788+
"'{name}' decoder failed - MediaFoundation: {mf_error}, FFmpeg channel: {e}"
789+
)),
790+
Err(_) => Err(format!(
791+
"'{name}' decoder failed - MediaFoundation: {mf_error}, FFmpeg timed out"
792+
)),
793+
}
694794
}
695-
Ok(Ok(Err(e))) => Err(format!(
696-
"'{name}' FFmpeg decoder initialization failed: {e}"
697-
)),
698-
Ok(Err(e)) => Err(format!("'{name}' FFmpeg decoder channel closed: {e}")),
699-
Err(_) => Err(format!(
700-
"'{name}' FFmpeg decoder timed out after 30s initializing: {path_display}"
701-
)),
702795
}
703796
}
704797

0 commit comments

Comments
 (0)