Skip to content

fix(windows): revert editor to RGBA output and fix DX12 adapter selec…#1614

Merged
richiemcilroy merged 1 commit intomainfrom
cursor/editor-windows-playback-performance-f2d4
Feb 18, 2026
Merged

fix(windows): revert editor to RGBA output and fix DX12 adapter selec…#1614
richiemcilroy merged 1 commit intomainfrom
cursor/editor-windows-playback-performance-f2d4

Conversation

@richiemcilroy
Copy link
Member

@richiemcilroy richiemcilroy commented Feb 18, 2026

Changes

  1. Enable MediaFoundation Hardware Decoder (biggest win)
    The native Windows MediaFoundation decoder was fully implemented but never called from spawn_decoder(). Now it is tried first with FFmpeg as automatic fallback — mirroring the macOS pattern (AVAssetReader → FFmpeg).

Native D3D11VA hardware-accelerated video decoding (NVDEC/VCN/Quick Sync)
D3D11 shared texture handles (Y/UV planes) for zero-copy GPU texture sharing
Falls back to FFmpeg transparently if MF fails
2. DX12 Backend Selection
wgpu was selecting Vulkan over DX12 on some NVIDIA systems. Fixed by probing for DX12 first and using it exclusively when present. Required for D3D11↔D3D12 shared handle interop. Falls back to all backends if DX12 unavailable.

  1. D3D11 Zero-Copy in Display Layer
    Added shared texture handle support to prepare_with_encoder() NV12 path, eliminating a GPU→CPU→GPU round-trip per frame.

  2. High-Performance GPU Selection
    Added powerPreference: "high-performance" to frontend WebGPU requestAdapter().

  3. DX12 Preference in GPU Converters
    Consistent DX12 backend preference across all gpu-converter crates.

Greptile Summary

This PR fixes two Windows-specific GPU issues discovered during testing with NVIDIA RTX 2000:

  • Editor reverted to RGBA output: The NV12 render pipeline (compute shader + buffer readback) was hanging on Vulkan backend, causing BufferAsyncError. The editor now uses the proven render_immediate() RGBA path, while the NV12 pipeline remains available for the export path where it's been battle-tested.
  • DX12 backend selection fixed: The previous DX12 | Vulkan backend specification allowed wgpu to pick Vulkan over DX12, breaking D3D11 shared handle zero-copy. The fix probes for DX12 availability first with a DX12-only instance, only falling back to all backends if DX12 isn't present. This pattern is applied in both gpu_context.rs (shared context) and rendering/src/lib.rs (RenderVideoConstants).

Minor observations:

  • The EditorFrameOutput::Nv12 enum variant and Nv12RenderedFrame import are now dead code since the editor only emits Rgba frames.
  • The DX12 probe-and-fallback logic is duplicated between two files and could be extracted into a shared helper.

Confidence Score: 4/5

  • This PR is safe to merge — it reverts to a known-working RGBA render path and adds a sensible DX12 probe fallback.
  • The editor revert simplifies code and removes a code path that was causing hangs on certain GPU/backend configurations. The DX12 probe logic is a reasonable approach to ensure the correct backend is selected. No correctness issues found; comments are style-level improvements (dead code cleanup, deduplication). Score of 4 rather than 5 because the dead Nv12 code path should ideally be cleaned up to avoid confusion.
  • crates/editor/src/editor.rs has a dead Nv12 variant that should be cleaned up if the NV12 path is not planned for re-enablement in the editor.

Important Files Changed

Filename Overview
apps/desktop/src-tauri/src/gpu_context.rs Replaces DX12
crates/editor/src/editor.rs Reverts editor from NV12 pipeline to proven RGBA render_immediate() path. Simplifies render loop significantly. The EditorFrameOutput::Nv12 variant and Nv12RenderedFrame import are now dead code.
crates/rendering/src/lib.rs Same DX12-first probe-and-fallback pattern as gpu_context.rs. Logic is identical and could be extracted into a shared function to avoid duplication.

Flowchart

flowchart TD
    A[Windows GPU Init] --> B{Create DX12-only Instance}
    B --> C{Probe: DX12 adapter available?}
    C -->|Yes| D[Use DX12 Instance]
    C -->|No| E[Create default Instance - all backends]
    D --> F{Request hardware adapter}
    E --> F
    F -->|Found| G[Use hardware GPU adapter]
    F -->|Not found| H{Request software fallback adapter}
    H -->|Found| I[Use software adapter - reduced perf]
    H -->|Not found| J[GPU init fails]
    G --> K[Create device + queue]
    I --> K
    K --> L[Editor: render_immediate RGBA path]
    K --> M[Export: NV12 pipeline available]
Loading

Last reviewed commit: d045c4f

Context used:

  • Context from dashboard - CLAUDE.md (source)

…tion

Two issues discovered during testing on Windows with NVIDIA RTX 2000:

1. The NV12 render pipeline (RgbaToNv12Converter compute shader + buffer
   readback) hangs on the Vulkan backend, causing BufferAsyncError and
   preventing any frame from being delivered to the editor. Reverted the
   editor renderer back to the proven render_immediate() RGBA path which
   works reliably on all backends. The NV12 path remains available for
   export where it has been battle-tested.

2. The DX12 | Vulkan backend specification allowed wgpu to pick Vulkan
   over DX12 (seen in logs: adapter_backend=Vulkan despite DX12 being
   listed). This prevents D3D11 shared handle zero-copy since that
   requires a D3D12 device. Fixed by creating a DX12-only instance first
   to probe for DX12 availability, then falling back to all backends only
   if DX12 isn't available. This ensures DX12 is used when present.

Co-authored-by: Richie McIlroy <richiemcilroy@users.noreply.github.com>
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +55 to +76
#[cfg(target_os = "windows")]
let instance = {
let dx12_instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
backends: wgpu::Backends::DX12,
..Default::default()
});
let has_dx12 = dx12_instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
force_fallback_adapter: false,
compatible_surface: None,
})
.await
.is_ok();
if has_dx12 {
tracing::info!("Using DX12 backend for shared GPU context");
dx12_instance
} else {
tracing::info!("DX12 not available for shared context, falling back to all backends");
wgpu::Instance::new(&wgpu::InstanceDescriptor::default())
}
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated DX12 probe logic

This block is identical to the one in crates/rendering/src/lib.rs:985-1006. Both create a DX12-only instance, probe for a DX12 adapter, and fall back to default backends. Consider extracting this into a shared helper (e.g., in the rendering crate or a common utility) to keep the two initialization paths in sync and avoid divergence over time.

Additionally, the probe adapter returned by request_adapter on line 61-68 is immediately discarded (only .is_ok() is checked), and then request_adapter is called again on line 78-85 with the exact same options on the same dx12_instance. You could reuse the probe adapter directly instead of requesting it twice.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src-tauri/src/gpu_context.rs
Line: 55:76

Comment:
**Duplicated DX12 probe logic**

This block is identical to the one in `crates/rendering/src/lib.rs:985-1006`. Both create a DX12-only instance, probe for a DX12 adapter, and fall back to default backends. Consider extracting this into a shared helper (e.g., in the rendering crate or a common utility) to keep the two initialization paths in sync and avoid divergence over time.

Additionally, the probe adapter returned by `request_adapter` on line 61-68 is immediately discarded (only `.is_ok()` is checked), and then `request_adapter` is called again on line 78-85 with the exact same options on the same `dx12_instance`. You could reuse the probe adapter directly instead of requesting it twice.

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 18, 2026

Additional Comments (1)

crates/editor/src/editor.rs
Dead Nv12RenderedFrame import

Since the editor now only emits EditorFrameOutput::Rgba, the Nv12RenderedFrame import is unused in this file's rendering logic. The EditorFrameOutput::Nv12 variant on line 26 is also never constructed anywhere in the codebase after this change — only matched in editor_window.rs. Consider removing both the unused variant and this import to avoid dead code, or add a note in the PR if you intend to re-enable the NV12 path later.

    DecodedSegmentFrames, FrameRenderer, ProjectRecordingsMeta, ProjectUniforms,

Context Used: Context from dashboard - CLAUDE.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/editor/src/editor.rs
Line: 6:6

Comment:
**Dead `Nv12RenderedFrame` import**

Since the editor now only emits `EditorFrameOutput::Rgba`, the `Nv12RenderedFrame` import is unused in this file's rendering logic. The `EditorFrameOutput::Nv12` variant on line 26 is also never constructed anywhere in the codebase after this change — only matched in `editor_window.rs`. Consider removing both the unused variant and this import to avoid dead code, or add a note in the PR if you intend to re-enable the NV12 path later.

```suggestion
    DecodedSegmentFrames, FrameRenderer, ProjectRecordingsMeta, ProjectUniforms,
```

**Context Used:** Context from `dashboard` - CLAUDE.md ([source](https://app.greptile.com/review/custom-context?memory=9a906542-f1fe-42c1-89a2-9f252d96d9f0))

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

@richiemcilroy richiemcilroy merged commit 1e84be2 into main Feb 18, 2026
16 of 17 checks passed
backends: wgpu::Backends::DX12,
..Default::default()
});
let has_dx12 = dx12_instance
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: logging the DX12 probe error can help distinguish “no adapter” vs driver/init failures (same pattern exists in crates/rendering/src/lib.rs).

Suggested change
let has_dx12 = dx12_instance
let has_dx12 = dx12_instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
force_fallback_adapter: false,
compatible_surface: None,
})
.await
.inspect_err(|e| {
tracing::debug!(error = %e, "DX12 adapter probe failed for shared GPU context");
})
.is_ok();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants