From 9da655e210442bd185409088d128f85aa135bc42 Mon Sep 17 00:00:00 2001 From: Jason Dove <1695733+jasongdove@users.noreply.github.com> Date: Mon, 15 Sep 2025 15:47:15 -0500 Subject: [PATCH] keep at least 10 bit color throughout nvidia tonemap pipeline (#2423) --- .../Filter/Cuda/TonemapCudaFilter.cs | 19 +++++++- .../Pipeline/NvidiaPipelineBuilder.cs | 48 +++++++++++++++---- 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/ErsatzTV.FFmpeg/Filter/Cuda/TonemapCudaFilter.cs b/ErsatzTV.FFmpeg/Filter/Cuda/TonemapCudaFilter.cs index 878d3adfe..9a4c88e79 100644 --- a/ErsatzTV.FFmpeg/Filter/Cuda/TonemapCudaFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/Cuda/TonemapCudaFilter.cs @@ -4,8 +4,23 @@ namespace ErsatzTV.FFmpeg.Filter.Cuda; public class TonemapCudaFilter(FFmpegState ffmpegState, IPixelFormat desiredPixelFormat) : BaseFilter { - public override string Filter => - $"libplacebo=tonemapping={ffmpegState.TonemapAlgorithm}:colorspace=bt709:color_primaries=bt709:color_trc=bt709:format={desiredPixelFormat.FFmpegName},hwupload_cuda"; + public override string Filter + { + get + { + // vulkan => cuda only works with 8-bit and 16-bit, not 10-bit + string vulkanOutputFormat = desiredPixelFormat.FFmpegName; + string cudaFormat = string.Empty; + if (desiredPixelFormat.BitDepth == 10) + { + vulkanOutputFormat = "p016"; + cudaFormat = ",scale_cuda=format=p010"; + } + + return + $"libplacebo=tonemapping={ffmpegState.TonemapAlgorithm}:colorspace=bt709:color_primaries=bt709:color_trc=bt709:format={vulkanOutputFormat},hwupload_cuda{cudaFormat}"; + } + } public override FrameState NextState(FrameState currentState) => currentState with diff --git a/ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs index ad55c3864..8077c3461 100644 --- a/ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs @@ -209,8 +209,8 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder currentState = SetCrop(videoInputFile, desiredState, currentState); SetStillImageLoop(videoInputFile, videoStream, ffmpegState, desiredState, pipelineSteps); - if (currentState.BitDepth == 8 && context.HasSubtitleOverlay || context.HasWatermark || - context.HasGraphicsEngine) + if (currentState.BitDepth == 8 && (context.HasSubtitleOverlay || context.HasWatermark || + context.HasGraphicsEngine)) { Option desiredPixelFormat = Some((IPixelFormat)new PixelFormatYuv420P()); @@ -282,7 +282,11 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder currentState, watermarkOverlayFilterSteps); - currentState = SetGraphicsEngine(graphicsEngineInput, currentState, graphicsEngineOverlayFilterSteps); + currentState = SetGraphicsEngine( + graphicsEngineInput, + currentState, + desiredState, + graphicsEngineOverlayFilterSteps); // after everything else is done, apply the encoder if (pipelineSteps.OfType().All(e => e.Kind != StreamKind.Video)) @@ -649,18 +653,42 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder private static FrameState SetGraphicsEngine( Option graphicsEngineInput, FrameState currentState, + FrameState desiredState, List graphicsEngineOverlayFilterSteps) { foreach (GraphicsEngineInput graphicsEngine in graphicsEngineInput) { - graphicsEngine.FilterSteps.Add(new PixelFormatFilter(new PixelFormatYuva420P())); + if (currentState.BitDepth == 8) + { + graphicsEngine.FilterSteps.Add(new PixelFormatFilter(new PixelFormatYuva420P())); - graphicsEngine.FilterSteps.Add( - new HardwareUploadCudaFilter(currentState with { FrameDataLocation = FrameDataLocation.Software })); + graphicsEngine.FilterSteps.Add( + new HardwareUploadCudaFilter(currentState with { FrameDataLocation = FrameDataLocation.Software })); - var graphicsEngineFilter = new OverlayGraphicsEngineCudaFilter(); - graphicsEngineOverlayFilterSteps.Add(graphicsEngineFilter); - currentState = graphicsEngineFilter.NextState(currentState); + var graphicsEngineFilter = new OverlayGraphicsEngineCudaFilter(); + graphicsEngineOverlayFilterSteps.Add(graphicsEngineFilter); + currentState = graphicsEngineFilter.NextState(currentState); + } + else + { + foreach (IPixelFormat desiredPixelFormat in desiredState.PixelFormat) + { + IPixelFormat pf = desiredPixelFormat; + if (desiredPixelFormat is PixelFormatNv12 nv12) + { + foreach (IPixelFormat availablePixelFormat in AvailablePixelFormats.ForPixelFormat( + nv12.Name, + null)) + { + pf = availablePixelFormat; + } + } + + var graphicsEngineFilter = new OverlayGraphicsEngineFilter(pf); + graphicsEngineOverlayFilterSteps.Add(graphicsEngineFilter); + currentState = graphicsEngineFilter.NextState(currentState); + } + } } return currentState; @@ -787,7 +815,7 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder { if (ffmpegState.IsHdrTonemap) { - var filter = new TonemapCudaFilter(ffmpegState, new PixelFormatNv12(FFmpegFormat.YUV420P)); + var filter = new TonemapCudaFilter(ffmpegState, pixelFormat); currentState = filter.NextState(currentState); videoStream.ResetColorParams(ColorParams.Default); videoInputFile.FilterSteps.Add(filter);