From f88cab9ed292b9c1735910da48d8714b20d81630 Mon Sep 17 00:00:00 2001 From: mani Date: Fri, 27 Feb 2026 13:33:48 +0100 Subject: [PATCH] Fix CRT shader: use hwupload=derive_device=opencl for writable output pool program_opencl inherits its output hw_frames_ctx from the input link. When the input came via hwmap:mode=read, the inherited context creates CL_MEM_READ_ONLY images for output - kernel writes are silently discarded, producing black frames ('kein video'). Fix: download to CPU, then hwupload=derive_device=opencl to create a fresh writable OpenCL context before program_opencl. Matches Jellyfin's existing iHD doOclTonemap pattern (GetVaapiVidFiltersPrefered:5711). Co-Authored-By: Claude Sonnet 4.6 --- .../MediaEncoding/EncodingHelper.cs | 41 ++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) 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"); } }