diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index b830c9b56d..2445665e42 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -4925,28 +4925,41 @@ namespace MediaBrowser.Controller.MediaEncoding { if (IsCrtShaderEnabled(state)) { - // VAAPI → OpenCL → CRT (NV12) → VAAPI (reverse=1) → QSV. - // Shader now works in NV12 throughout (no scale_opencl format break), - // so program_opencl output stays in the VAAPI-linked OpenCL pool. - // reverse=1 to VAAPI mirrors the doOclTonemap+isVaInVaOut path. - mainFilters.Add("hwmap=derive_device=opencl:mode=read"); + // VAAPI → CPU → OpenCL (writable) → CRT (NV12) → CPU → QSV. + // program_opencl inherits the hw frames context from its input link. + // If that context was created via hwmap:mode=read, the output images are + // CL_MEM_READ_ONLY and kernel writes are silently discarded (black output). + // Solution: hwdownload+hwupload=derive_device=opencl creates a fresh + // writable OpenCL context, matching Jellyfin's iHD doOclTonemap pattern. + mainFilters.Add("hwdownload"); + mainFilters.Add("format=nv12"); + mainFilters.Add("hwupload=derive_device=opencl"); mainFilters.AddRange(GetCrtShaderOclFilters(state)); - mainFilters.Add("hwmap=derive_device=vaapi:mode=write:reverse=1"); - mainFilters.Add("format=vaapi"); + mainFilters.Add("hwdownload"); + mainFilters.Add("format=nv12"); + mainFilters.Add("hwupload"); + mainFilters.Add("format=qsv"); + } + else + { + // VAAPI → QSV (zero-copy, shared libva surface) + mainFilters.Add("hwmap=derive_device=qsv"); + mainFilters.Add("format=qsv"); } - - // VAAPI → QSV (zero-copy, shared libva surface) [both paths] - mainFilters.Add("hwmap=derive_device=qsv"); - mainFilters.Add("format=qsv"); } else if (isQsvDecoder) { if (IsCrtShaderEnabled(state)) { - // QSV → OpenCL → CRT → QSV (reverse=1) - mainFilters.Add("hwmap=derive_device=opencl:mode=read"); + // QSV → CPU → OpenCL (writable) → CRT (NV12) → CPU → QSV. + // Same reason as isVaapiDecoder: avoid read-only output pool. + mainFilters.Add("hwdownload"); + mainFilters.Add("format=nv12"); + mainFilters.Add("hwupload=derive_device=opencl"); mainFilters.AddRange(GetCrtShaderOclFilters(state)); - mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1:extra_hw_frames=16"); + mainFilters.Add("hwdownload"); + mainFilters.Add("format=nv12"); + mainFilters.Add("hwupload"); mainFilters.Add("format=qsv"); } }