From 036216c0060feeafdeca09fa68b20aa19e122bd537be9b3b7ad82e188087a3f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=BB=A8=ED=8A=B8=EB=A1=A4DELTA?= Date: Fri, 29 May 2026 12:03:03 +0900 Subject: [PATCH] Move Memory Copy Handling from Main Thread to Render Thread --- Core/RenderManager.cs | 61 +++++++++++++++++++++++++++++++++++-------- Util/FrameReader.cs | 32 +++++++++++++---------- 2 files changed, 68 insertions(+), 25 deletions(-) diff --git a/Core/RenderManager.cs b/Core/RenderManager.cs index d71a2c2..a163986 100644 --- a/Core/RenderManager.cs +++ b/Core/RenderManager.cs @@ -79,8 +79,16 @@ namespace ADOFAIRenderer.Core } } + internal struct CapturedFrame + { + public IntPtr NativePointer; + public int Length; + public byte[]? ManagedBuffer; // Non-null if it's a managed buffer (e.g. black frame) + public ReadbackRequestState? ReadbackState; // Associated state to recycle + } + // Thread and Queue State - public SortedList ImageQueue = new SortedList(); + internal SortedList ImageQueue = new SortedList(); public Thread? RenderThread; public long passCount; public bool endRequested; @@ -202,7 +210,11 @@ namespace ADOFAIRenderer.Core lock (ImageQueue) { - ImageQueue[videoFrameIndex] = blackFrame; + ImageQueue[videoFrameIndex] = new CapturedFrame + { + ManagedBuffer = blackFrame, + Length = byteSize + }; } } @@ -416,7 +428,8 @@ namespace ADOFAIRenderer.Core while (true) { - byte[]? buffer = null; + CapturedFrame frame = default; + bool hasFrame = false; lock (ImageQueue) { if (ImageQueue.Count > 0) @@ -430,20 +443,42 @@ namespace ADOFAIRenderer.Core if (firstKey == passCount) { - buffer = ImageQueue[firstKey]; + frame = ImageQueue[firstKey]; ImageQueue.RemoveAt(0); passCount++; + hasFrame = true; } } } - if (buffer != null) + if (hasFrame) { - if (buffer.Length > 0) + if (frame.ManagedBuffer != null) { - var bufferSize = Configuration.RenderWidth * Configuration.RenderHeight * 4; - videoFFmpeg.SendDataFromBuffer(buffer, bufferSize); + if (frame.ManagedBuffer.Length > 0) + { + videoFFmpeg.SendDataFromBuffer(frame.ManagedBuffer, frame.Length); + ArrayPool.Shared.Return(frame.ManagedBuffer); + } + } + else if (frame.NativePointer != IntPtr.Zero && frame.Length > 0) + { + var buffer = FFmpegProcess.RentRawBytes(frame.Length); + unsafe + { + fixed (byte* destPtr = buffer) + { + Buffer.MemoryCopy((void*)frame.NativePointer, destPtr, buffer.Length, frame.Length); + } + } + + videoFFmpeg.SendDataFromBuffer(buffer, frame.Length); ArrayPool.Shared.Return(buffer); + + if (frame.ReadbackState != null) + { + _frameReader?.RecycleState(frame.ReadbackState); + } } continue; } @@ -514,11 +549,15 @@ namespace ADOFAIRenderer.Core // 1. 메모리 누수 방지: 큐에 렌트된 임시 프레임 버퍼 풀 반환 및 클리어 lock (ImageQueue) { - foreach (var kvp in ImageQueue) + foreach (var kvp in ImageQueue.Values) { - if (kvp.Value != null && kvp.Value.Length > 0) + if (kvp.ManagedBuffer != null && kvp.ManagedBuffer.Length > 0) { - ArrayPool.Shared.Return(kvp.Value); + ArrayPool.Shared.Return(kvp.ManagedBuffer); + } + if (kvp.ReadbackState != null) + { + _frameReader?.RecycleState(kvp.ReadbackState); } } ImageQueue.Clear(); diff --git a/Util/FrameReader.cs b/Util/FrameReader.cs index 6a05c91..e8074d8 100644 --- a/Util/FrameReader.cs +++ b/Util/FrameReader.cs @@ -91,28 +91,32 @@ namespace ADOFAIRenderer.Util var byteSize = state.NativeBuffer.Length; var blackFrame = ArrayPool.Shared.Rent(byteSize); Array.Clear(blackFrame, 0, byteSize); - PushToQueue(frame, blackFrame); + + var capFrame = new RenderManager.CapturedFrame + { + ManagedBuffer = blackFrame, + Length = byteSize + }; + PushToQueue(frame, capFrame); + RecycleState(state); } else { unsafe { - var ptr = state.NativeBuffer.GetUnsafeReadOnlyPtr(); - var rawBytes = FFmpegProcess.RentRawBytes(state.NativeBuffer.Length); - - fixed (byte* destPtr = rawBytes) + var ptr = (IntPtr)state.NativeBuffer.GetUnsafeReadOnlyPtr(); + var capFrame = new RenderManager.CapturedFrame { - Buffer.MemoryCopy(ptr, destPtr, rawBytes.Length, state.NativeBuffer.Length); - } - - PushToQueue(frame, rawBytes); + NativePointer = ptr, + Length = state.NativeBuffer.Length, + ReadbackState = state + }; + PushToQueue(frame, capFrame); } } - - RecycleState(state); } - private void RecycleState(ReadbackRequestState state) + internal void RecycleState(ReadbackRequestState state) { lock (_poolLock) { @@ -127,12 +131,12 @@ namespace ADOFAIRenderer.Util } } - private static void PushToQueue(long frame, byte[] rawBytes) + private static void PushToQueue(long frame, RenderManager.CapturedFrame capturedFrame) { if (!RenderManager.Instance) return; lock (RenderManager.Instance.ImageQueue) { - RenderManager.Instance.ImageQueue[frame] = rawBytes; + RenderManager.Instance.ImageQueue[frame] = capturedFrame; } }