SHA256
Merge branch 'reduce_memcopy'
This commit is contained in:
commit
54a4d2e7bb
4 files changed
+132
-32
No files matched your search
+57
-12
@@ -83,8 +83,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<long, byte[]> ImageQueue = new SortedList<long, byte[]>();
|
||||
internal SortedList<long, CapturedFrame> ImageQueue = new SortedList<long, CapturedFrame>();
|
||||
public Thread? RenderThread;
|
||||
public long passCount;
|
||||
public bool endRequested;
|
||||
@@ -251,7 +259,11 @@ namespace ADOFAIRenderer.Core
|
||||
|
||||
lock (ImageQueue)
|
||||
{
|
||||
ImageQueue[videoFrameIndex] = blackFrame;
|
||||
ImageQueue[videoFrameIndex] = new CapturedFrame
|
||||
{
|
||||
ManagedBuffer = blackFrame,
|
||||
Length = byteSize
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,7 +482,8 @@ namespace ADOFAIRenderer.Core
|
||||
|
||||
while (true)
|
||||
{
|
||||
byte[]? buffer = null;
|
||||
CapturedFrame frame = default;
|
||||
bool hasFrame = false;
|
||||
lock (ImageQueue)
|
||||
{
|
||||
if (ImageQueue.Count > 0)
|
||||
@@ -484,20 +497,32 @@ 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);
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
if (frame.ManagedBuffer.Length > 0)
|
||||
{
|
||||
videoFFmpeg.SendDataFromBuffer(frame.ManagedBuffer, frame.Length);
|
||||
ArrayPool<byte>.Shared.Return(frame.ManagedBuffer);
|
||||
}
|
||||
}
|
||||
else if (frame.NativePointer != IntPtr.Zero && frame.Length > 0)
|
||||
{
|
||||
videoFFmpeg.SendDataFromNativeBuffer(frame.NativePointer, frame.Length);
|
||||
|
||||
if (frame.ReadbackState != null)
|
||||
{
|
||||
_frameReader?.RecycleState(frame.ReadbackState);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -583,11 +608,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<byte>.Shared.Return(kvp.Value);
|
||||
ArrayPool<byte>.Shared.Return(kvp.ManagedBuffer);
|
||||
}
|
||||
if (kvp.ReadbackState != null)
|
||||
{
|
||||
_frameReader?.RecycleState(kvp.ReadbackState);
|
||||
}
|
||||
}
|
||||
ImageQueue.Clear();
|
||||
@@ -664,6 +693,22 @@ namespace ADOFAIRenderer.Core
|
||||
GCS.customLevelPaths = null;
|
||||
GCS.customLevelId = null;
|
||||
|
||||
lock (ImageQueue)
|
||||
{
|
||||
foreach (var kvp in ImageQueue.Values)
|
||||
{
|
||||
if (kvp.ManagedBuffer != null && kvp.ManagedBuffer.Length > 0)
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(kvp.ManagedBuffer);
|
||||
}
|
||||
if (kvp.ReadbackState != null)
|
||||
{
|
||||
_frameReader?.RecycleState(kvp.ReadbackState);
|
||||
}
|
||||
}
|
||||
ImageQueue.Clear();
|
||||
}
|
||||
|
||||
_frameReader?.Dispose();
|
||||
ADOBase.loader.LoadSceneWithTransition(WipeDirection.StartsFromRight, "scnEditor");
|
||||
}
|
||||
|
||||
@@ -44,6 +44,13 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
||||
_inputStream.Flush();
|
||||
}
|
||||
|
||||
public void SendDataFromNativeBuffer(IntPtr nativePtr, int length)
|
||||
{
|
||||
if (_inputStream == null) return;
|
||||
StreamUtil.WriteNativePtrToStream(nativePtr, length, _inputStream);
|
||||
_inputStream.Flush();
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (_ffmpegProcess == null || _ffmpegProcess.HasExited || _inputStream == null) return;
|
||||
|
||||
+39
-20
@@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using ADOFAIRenderer.Core;
|
||||
using ADOFAIRenderer.Util.FFmpeg;
|
||||
using MelonLoader;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
@@ -16,6 +16,7 @@ namespace ADOFAIRenderer.Util
|
||||
private const int InitialBufferSize = 16;
|
||||
|
||||
private readonly Stack<ReadbackRequestState> _statePool = new Stack<ReadbackRequestState>();
|
||||
private readonly List<ReadbackRequestState> _allocatedStates = new List<ReadbackRequestState>();
|
||||
private readonly object _poolLock = new object();
|
||||
private bool _isInitialized;
|
||||
private volatile bool _isDisposed;
|
||||
@@ -27,13 +28,14 @@ namespace ADOFAIRenderer.Util
|
||||
|
||||
private void InitializePool()
|
||||
{
|
||||
|
||||
lock (_poolLock)
|
||||
{
|
||||
if (_isInitialized) return;
|
||||
for (var i = 0; i < InitialBufferSize; i++)
|
||||
{
|
||||
_statePool.Push(new ReadbackRequestState(this));
|
||||
var state = new ReadbackRequestState(this);
|
||||
_statePool.Push(state);
|
||||
_allocatedStates.Add(state);
|
||||
}
|
||||
|
||||
_isInitialized = true;
|
||||
@@ -47,7 +49,17 @@ namespace ADOFAIRenderer.Util
|
||||
|
||||
lock (_poolLock)
|
||||
{
|
||||
state = _statePool.Count == 0 ? new ReadbackRequestState(this) : _statePool.Pop();
|
||||
if (_statePool.Count == 0)
|
||||
{
|
||||
state = new ReadbackRequestState(this);
|
||||
_allocatedStates.Add(state);
|
||||
}
|
||||
else
|
||||
{
|
||||
state = _statePool.Pop();
|
||||
}
|
||||
|
||||
state.IsInFlight = true;
|
||||
}
|
||||
|
||||
var bufferSize = texture.width * texture.height * 4; // RGBA = 4 channel
|
||||
@@ -67,6 +79,7 @@ namespace ADOFAIRenderer.Util
|
||||
|
||||
lock (_poolLock)
|
||||
{
|
||||
state.IsInFlight = false;
|
||||
if (_isDisposed)
|
||||
{
|
||||
state.Dispose();
|
||||
@@ -91,28 +104,32 @@ namespace ADOFAIRenderer.Util
|
||||
var byteSize = state.NativeBuffer.Length;
|
||||
var blackFrame = ArrayPool<byte>.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 +144,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,11 +159,12 @@ namespace ADOFAIRenderer.Util
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
_isDisposed = true;
|
||||
while (_statePool.Count > 0)
|
||||
foreach (var state in _allocatedStates.Where(state => !state.IsInFlight))
|
||||
{
|
||||
var state = _statePool.Pop();
|
||||
state.Dispose();
|
||||
}
|
||||
_statePool.Clear();
|
||||
_allocatedStates.Clear();
|
||||
_isInitialized = false;
|
||||
}
|
||||
}
|
||||
@@ -157,6 +175,7 @@ namespace ADOFAIRenderer.Util
|
||||
private readonly FrameReader _owner;
|
||||
public long FrameIndex { get; private set; }
|
||||
public NativeArray<byte> NativeBuffer;
|
||||
public bool IsInFlight { get; internal set; }
|
||||
|
||||
public readonly Action<AsyncGPUReadbackRequest> CachedCallback;
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using MelonLoader;
|
||||
|
||||
namespace ADOFAIRenderer.Util
|
||||
{
|
||||
public static class StreamUtil
|
||||
{
|
||||
public static void WriteNativePtrToStream(IntPtr nativePtr, int length, Stream destinationStream)
|
||||
{
|
||||
if (nativePtr == IntPtr.Zero)
|
||||
throw new ArgumentNullException(nameof(nativePtr));
|
||||
if (length <= 0) return;
|
||||
|
||||
unsafe
|
||||
{
|
||||
try
|
||||
{
|
||||
var span = new ReadOnlySpan<byte>((void*)nativePtr, length);
|
||||
destinationStream.Write(span);
|
||||
}
|
||||
catch (IOException err)
|
||||
{
|
||||
Melon<RenderMod>.Logger.Error($"FFmpeg 파이프가 예상치 못하게 닫혔습니다: {err.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user