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 <noreply@anthropic.com>
This commit is contained in:
mani
2026-02-27 13:33:48 +01:00
parent 0da3d7e29a
commit f88cab9ed2

View File

@@ -4925,28 +4925,41 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
if (IsCrtShaderEnabled(state)) if (IsCrtShaderEnabled(state))
{ {
// VAAPI → OpenCL → CRT (NV12) → VAAPI (reverse=1) → QSV. // VAAPI → CPU → OpenCL (writable) → CRT (NV12) → CPU → QSV.
// Shader now works in NV12 throughout (no scale_opencl format break), // program_opencl inherits the hw frames context from its input link.
// so program_opencl output stays in the VAAPI-linked OpenCL pool. // If that context was created via hwmap:mode=read, the output images are
// reverse=1 to VAAPI mirrors the doOclTonemap+isVaInVaOut path. // CL_MEM_READ_ONLY and kernel writes are silently discarded (black output).
mainFilters.Add("hwmap=derive_device=opencl:mode=read"); // 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.AddRange(GetCrtShaderOclFilters(state));
mainFilters.Add("hwmap=derive_device=vaapi:mode=write:reverse=1"); mainFilters.Add("hwdownload");
mainFilters.Add("format=vaapi"); 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) else if (isQsvDecoder)
{ {
if (IsCrtShaderEnabled(state)) if (IsCrtShaderEnabled(state))
{ {
// QSV → OpenCL → CRT → QSV (reverse=1) // QSV → CPU → OpenCL (writable) → CRT (NV12) → CPU → QSV.
mainFilters.Add("hwmap=derive_device=opencl:mode=read"); // 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.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"); mainFilters.Add("format=qsv");
} }
} }