diff --git a/crates/compositor/src/cpu_frames_windows.rs b/crates/compositor/src/cpu_frames_windows.rs index ea0e921fb..b5a240bbe 100644 --- a/crates/compositor/src/cpu_frames_windows.rs +++ b/crates/compositor/src/cpu_frames_windows.rs @@ -24,9 +24,9 @@ use windows::Win32::Graphics::Dxgi::Common::{DXGI_FORMAT_NV12, DXGI_SAMPLE_DESC} /// Le flag d'algorithme de swscale. Bindgen ne génère pas les `SWS_*` d'algorithme (des /// macros), et leurs valeurs sont figées par l'ABI de libswscale. `POINT` (plus proche -/// voisin) est le choix honnête : la conversion se fait à dimensions ÉGALES, donc aucun -/// rééchantillonnage n'a lieu — seul le convertisseur de format travaille, et le filtre -/// choisi n'a aucun effet sur la sortie. +/// voisin) est le choix honnête : la conversion se fait à dimensions ÉGALES, donc la luma +/// n'est jamais rééchantillonnée. Seule exception : une source 4:2:2 ou 4:4:4 (H.264 +/// importé) perd sa chroma pleine en passant en NV12, et POINT la décime par voisin. const SWS_POINT: i32 = 0x10; /// Source de frames du backend CPU, attachée à un `Decoder` quand `Backend::Cpu`. diff --git a/crates/compositor/src/pipeline_windows.rs b/crates/compositor/src/pipeline_windows.rs index ea6e658b3..4c59cf73c 100644 --- a/crates/compositor/src/pipeline_windows.rs +++ b/crates/compositor/src/pipeline_windows.rs @@ -380,6 +380,21 @@ fn d3d11va_for_codec(codec_id: AVCodecID::Type) -> bool { codec_id == AVCodecID::AV_CODEC_ID_H264 } +/// Per-stream refinement of `d3d11va_for_codec`: FFmpeg only offers a D3D11 +/// NV12 surface for 8-bit 4:2:0 H.264. High 10 (yuv420p10le) and 4:2:2 / 4:4:4 +/// get none, `get_hw_format` rejects everything and `send_packet` fails with +/// AVERROR_INVALIDDATA, so those streams take the software `CpuFrames` path. +/// An unknown pixel format (`AV_PIX_FMT_NONE`) keeps the hardware path. +fn d3d11va_for_stream(codec_id: AVCodecID::Type, format: AVPixelFormat::Type) -> bool { + d3d11va_for_codec(codec_id) + && matches!( + format, + AVPixelFormat::AV_PIX_FMT_NONE + | AVPixelFormat::AV_PIX_FMT_YUV420P + | AVPixelFormat::AV_PIX_FMT_YUVJ420P + ) +} + unsafe fn require_decoder_id( codec_id: AVCodecID::Type, ) -> Result<(*const AVCodec, *mut AVCodecContext)> { @@ -847,10 +862,12 @@ impl Decoder { )?; allow_d3d11va_h264_baseline(resources.dctx); - // Hardware D3D11VA is the H.264 capture path. WARP has no video decoder - // (`tests/warp_device_cannot_decode.rs`). AV1/VP9 (legacy WebMs, #554) - // take the same software CpuFrames axis as Backend::Cpu. - let want_hw = gpu.backend != Backend::Cpu && d3d11va_for_codec(codec_id); + // Hardware D3D11VA is the H.264 8-bit 4:2:0 capture path. WARP has no video + // decoder (`tests/warp_device_cannot_decode.rs`). AV1/VP9 (legacy WebMs, #554) + // and H.264 High 10 / 4:2:2 / 4:4:4 (no D3D11 surface exists for them) take + // the same software CpuFrames axis as Backend::Cpu. + let want_hw = gpu.backend != Backend::Cpu + && d3d11va_for_stream(codec_id, (*codecpar).format as AVPixelFormat::Type); let cpu = if want_hw { None } else { @@ -2168,6 +2185,19 @@ mod tests { assert!(!d3d11va_for_codec(AVCodecID::AV_CODEC_ID_VP9)); } + #[test] + fn d3d11va_is_8bit_420_h264_only() { + use AVPixelFormat::*; + let h264 = AVCodecID::AV_CODEC_ID_H264; + assert!(d3d11va_for_stream(h264, AV_PIX_FMT_YUV420P)); + assert!(d3d11va_for_stream(h264, AV_PIX_FMT_YUVJ420P)); + assert!(d3d11va_for_stream(h264, AV_PIX_FMT_NONE)); + assert!(!d3d11va_for_stream(h264, AV_PIX_FMT_YUV420P10LE)); + assert!(!d3d11va_for_stream(h264, AV_PIX_FMT_YUV422P)); + assert!(!d3d11va_for_stream(h264, AV_PIX_FMT_YUV444P)); + assert!(!d3d11va_for_stream(AVCodecID::AV_CODEC_ID_AV1, AV_PIX_FMT_YUV420P)); + } + #[test] fn require_decoder_rejects_none() { let err = unsafe { require_decoder_id(AVCodecID::AV_CODEC_ID_NONE) }