From ea8d4c91ac8d789a5d271dcef3b7fbb6f4d327528559893529df06e16226a5e7 Mon Sep 17 00:00:00 2001 From: copecone Date: Mon, 12 Oct 2026 23:04:24 +0900 Subject: [PATCH] Fix Black Fallback Frame For Failed Readbacks --- Util/FrameReader.cs | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/Util/FrameReader.cs b/Util/FrameReader.cs index 612c8d9..6b83af2 100644 --- a/Util/FrameReader.cs +++ b/Util/FrameReader.cs @@ -154,14 +154,11 @@ namespace ADOFAIRenderer.Util } Melon.Logger.Warning($"[OOM Prevent] GPU Readback failed for frame {frame}. Queue deadlock prevented by sending a black frame."); - var byteSize = state.Width * state.Height * 3 / 2; - var blackFrame = ArrayPool.Shared.Rent(byteSize); - Array.Clear(blackFrame, 0, byteSize); - + var blackFrame = CreateBlackFrame(state, out var byteSize); var capFrame = new RenderManager.CapturedFrame { ManagedBuffer = blackFrame, Length = byteSize, - IsColorConverted = true + IsColorConverted = state.IsGPUConversion }; PushToQueue(frame, capFrame); RecycleState(state); @@ -190,6 +187,36 @@ namespace ADOFAIRenderer.Util } } + /// + /// Creates a black frame in the same format as the frames of this reader, from ArrayPool<byte>.Shared + /// (returned to the pool by the render thread after encoding). + /// + /// GPU conversion (NV12 / YUV420P, full range): Width × Height × 3 / 2 bytes. + /// Y plane (Width × Height bytes) = 0, U/V data (the rest) = 128. + /// 128 is the zero chroma value of the 8-bit full range: the U/V offset 0.5 of RGB2YUV_BT709_Full + /// in ColorConversion.compute, which the encoder input of this path uses. + /// Both NV12 (interleaved UV) and YUV420P (U plane, then V plane) store all chroma after the Y plane, + /// so the same fill applies to both. + /// No GPU conversion (RGBA input, converted by the encoder): Width × Height × 4 bytes, all 0. + /// + private static byte[] CreateBlackFrame(ReadbackRequestState state, out int byteSize) + { + var lumaSize = state.Width * state.Height; + byteSize = state.IsGPUConversion ? lumaSize * 3 / 2 : lumaSize * 4; + + var frame = ArrayPool.Shared.Rent(byteSize); + if (state.IsGPUConversion) + { + Array.Clear(frame, 0, lumaSize); + frame.AsSpan(lumaSize, byteSize - lumaSize).Fill(128); + } + else + { + Array.Clear(frame, 0, byteSize); + } + return frame; + } + internal void RecycleState(ReadbackRequestState state) { lock (_poolLock)