SHA256
Implement Filter-Before-Capture Structure
This commit is contained in:
1 parent
379c0360d7
commit
ec06f99603
4 files changed
+64
-32
No files matched your search
+21
-12
@@ -219,7 +219,10 @@ namespace ADOFAIRenderer.Component
|
|||||||
}
|
}
|
||||||
PassCount = 0;
|
PassCount = 0;
|
||||||
IsRenderCancelled = false;
|
IsRenderCancelled = false;
|
||||||
|
RenderMod.IsRenderCancelled = false;
|
||||||
RenderMod.FrameCountForVideo = 0;
|
RenderMod.FrameCountForVideo = 0;
|
||||||
|
RenderMod.LastVideoFrameIndex = -1;
|
||||||
|
RenderMod.LastRequestedVideoFrameIndex = -1;
|
||||||
RenderMod.RenderCheck = true;
|
RenderMod.RenderCheck = true;
|
||||||
RenderMod.SkipCount = 0;
|
RenderMod.SkipCount = 0;
|
||||||
EndRequested = false;
|
EndRequested = false;
|
||||||
@@ -254,17 +257,13 @@ namespace ADOFAIRenderer.Component
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var quotient = targetFps / videoFps;
|
var effectiveVideoFps = Math.Min(videoFps, targetFps);
|
||||||
var isMultiple = Math.Abs(quotient - Math.Round(quotient)) < 1e-9;
|
var videoFFmpeg = new VideoFFmpeg(Configuration.RenderWidth, Configuration.RenderHeight, effectiveVideoFps, videoFps, selectedCodec);
|
||||||
var workingFps = isMultiple ? videoFps : targetFps;
|
|
||||||
var videoFFmpeg = new VideoFFmpeg(Configuration.RenderWidth, Configuration.RenderHeight, workingFps, videoFps, selectedCodec);
|
|
||||||
|
|
||||||
SetupCaptureDirectory();
|
SetupCaptureDirectory();
|
||||||
|
|
||||||
videoFFmpeg.Start(_currentOutPath, bitrate);
|
videoFFmpeg.Start(_currentOutPath, bitrate);
|
||||||
|
|
||||||
var num = isMultiple ? (long)Math.Round(quotient) : 1L;
|
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
byte[]? buffer = null;
|
byte[]? buffer = null;
|
||||||
@@ -273,11 +272,17 @@ namespace ADOFAIRenderer.Component
|
|||||||
if (ImageQueue.Count > 0)
|
if (ImageQueue.Count > 0)
|
||||||
{
|
{
|
||||||
var firstKey = ImageQueue.Keys[0];
|
var firstKey = ImageQueue.Keys[0];
|
||||||
|
|
||||||
|
if (firstKey > PassCount && !RenderMod.IsCapturing)
|
||||||
|
{
|
||||||
|
PassCount = firstKey;
|
||||||
|
}
|
||||||
|
|
||||||
if (firstKey == PassCount)
|
if (firstKey == PassCount)
|
||||||
{
|
{
|
||||||
buffer = ImageQueue[firstKey];
|
buffer = ImageQueue[firstKey];
|
||||||
ImageQueue.RemoveAt(0);
|
ImageQueue.RemoveAt(0);
|
||||||
PassCount += num;
|
PassCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -293,19 +298,17 @@ namespace ADOFAIRenderer.Component
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsRenderCancelled)
|
if (IsRenderCancelled || RenderMod.IsRenderCancelled)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (EndRequested)
|
if (EndRequested)
|
||||||
{
|
{
|
||||||
bool hasMore;
|
if (PassCount > RenderMod.LastRequestedVideoFrameIndex)
|
||||||
lock (ImageQueue)
|
|
||||||
{
|
{
|
||||||
hasMore = ImageQueue.Count > 0;
|
break;
|
||||||
}
|
}
|
||||||
if (!hasMore) break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Thread.Sleep(1);
|
Thread.Sleep(1);
|
||||||
@@ -327,6 +330,7 @@ namespace ADOFAIRenderer.Component
|
|||||||
{
|
{
|
||||||
RenderMod.IsRendering = RenderMod.IsCapturing = false;
|
RenderMod.IsRendering = RenderMod.IsCapturing = false;
|
||||||
IsRenderCancelled = true;
|
IsRenderCancelled = true;
|
||||||
|
RenderMod.IsRenderCancelled = true;
|
||||||
|
|
||||||
StopFFmpeg(false);
|
StopFFmpeg(false);
|
||||||
EndRender();
|
EndRender();
|
||||||
@@ -335,6 +339,11 @@ namespace ADOFAIRenderer.Component
|
|||||||
private void StopFFmpeg(bool success)
|
private void StopFFmpeg(bool success)
|
||||||
{
|
{
|
||||||
EndRequested = true;
|
EndRequested = true;
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
IsRenderCancelled = true;
|
||||||
|
RenderMod.IsRenderCancelled = true;
|
||||||
|
}
|
||||||
SkipPressToStart = false;
|
SkipPressToStart = false;
|
||||||
_renderSetup = false;
|
_renderSetup = false;
|
||||||
if (_escapeHandlerObj)
|
if (_escapeHandlerObj)
|
||||||
|
|||||||
+18
-11
@@ -98,24 +98,27 @@ namespace ADOFAIRenderer
|
|||||||
// 60회 초과 시 최후의 블랙 프레임 삽입하여 전체 파이프라인 마비 방지
|
// 60회 초과 시 최후의 블랙 프레임 삽입하여 전체 파이프라인 마비 방지
|
||||||
Melon<RenderMod>.Logger.Error($"[Render Skipped] Frame {RenderManager.FrameCount} rendering failed after 60 retries. Supplying black frame.");
|
Melon<RenderMod>.Logger.Error($"[Render Skipped] Frame {RenderManager.FrameCount} rendering failed after 60 retries. Supplying black frame.");
|
||||||
|
|
||||||
|
var currentFrame = FrameCountForVideo++;
|
||||||
var targetFps = RenderManager.Configuration.TargetFPS;
|
var targetFps = RenderManager.Configuration.TargetFPS;
|
||||||
var videoFps = RenderManager.Configuration.VideoFPS;
|
var videoFps = RenderManager.Configuration.VideoFPS;
|
||||||
var quotient = targetFps / videoFps;
|
var effectiveVideoFps = Math.Min(videoFps, targetFps);
|
||||||
var num = Math.Abs(quotient - Math.Round(quotient)) < 1e-9 ? (long)Math.Round(quotient) : 1L;
|
var videoFrameIndex = (long)(currentFrame * effectiveVideoFps / targetFps);
|
||||||
|
|
||||||
if (FrameCountForVideo % num == 0)
|
if (videoFrameIndex > LastVideoFrameIndex)
|
||||||
{
|
{
|
||||||
|
LastVideoFrameIndex = videoFrameIndex;
|
||||||
|
LastRequestedVideoFrameIndex = videoFrameIndex;
|
||||||
|
|
||||||
var byteSize = RenderManager.Configuration.RenderWidth * RenderManager.Configuration.RenderHeight * 4;
|
var byteSize = RenderManager.Configuration.RenderWidth * RenderManager.Configuration.RenderHeight * 4;
|
||||||
var blackFrame = System.Buffers.ArrayPool<byte>.Shared.Rent(byteSize);
|
var blackFrame = System.Buffers.ArrayPool<byte>.Shared.Rent(byteSize);
|
||||||
Array.Clear(blackFrame, 0, byteSize);
|
Array.Clear(blackFrame, 0, byteSize);
|
||||||
|
|
||||||
lock (RenderManager.ImageQueue)
|
lock (RenderManager.ImageQueue)
|
||||||
{
|
{
|
||||||
RenderManager.ImageQueue[FrameCountForVideo] = blackFrame;
|
RenderManager.ImageQueue[videoFrameIndex] = blackFrame;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FrameCountForVideo++;
|
|
||||||
RenderCheck = false; // 진행 허용
|
RenderCheck = false; // 진행 허용
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,6 +133,9 @@ namespace ADOFAIRenderer
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static long FrameCountForVideo;
|
public static long FrameCountForVideo;
|
||||||
|
public static long LastVideoFrameIndex = -1;
|
||||||
|
public static long LastRequestedVideoFrameIndex = -1;
|
||||||
|
public static bool IsRenderCancelled = false;
|
||||||
public static int ReadbackFailureCount;
|
public static int ReadbackFailureCount;
|
||||||
public static int ThrottleCount;
|
public static int ThrottleCount;
|
||||||
public static bool IsThrottling;
|
public static bool IsThrottling;
|
||||||
@@ -167,16 +173,17 @@ namespace ADOFAIRenderer
|
|||||||
|
|
||||||
var targetFps = RenderManager.Configuration.TargetFPS;
|
var targetFps = RenderManager.Configuration.TargetFPS;
|
||||||
var videoFps = RenderManager.Configuration.VideoFPS;
|
var videoFps = RenderManager.Configuration.VideoFPS;
|
||||||
var quotient = targetFps / videoFps;
|
var effectiveVideoFps = Math.Min(videoFps, targetFps);
|
||||||
var num = Math.Abs(quotient - Math.Round(quotient)) < 1e-9 ? (int)Math.Round(quotient) : 1;
|
|
||||||
|
|
||||||
if (currentFrame % num != 0)
|
var videoFrameIndex = (long)(currentFrame * effectiveVideoFps / targetFps);
|
||||||
|
|
||||||
|
if (videoFrameIndex > LastVideoFrameIndex)
|
||||||
{
|
{
|
||||||
return;
|
LastVideoFrameIndex = videoFrameIndex;
|
||||||
|
LastRequestedVideoFrameIndex = videoFrameIndex;
|
||||||
|
FrameReader?.RequestFrameCapture(texture, videoFrameIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
FrameReader?.RequestFrameCapture(texture, currentFrame);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
AsyncGPUReadback.Request(texture, 0, delegate(AsyncGPUReadbackRequest req)
|
AsyncGPUReadback.Request(texture, 0, delegate(AsyncGPUReadbackRequest req)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
|||||||
|
|
||||||
public bool IsEnd => _ffmpegProcess is { HasExited: true };
|
public bool IsEnd => _ffmpegProcess is { HasExited: true };
|
||||||
|
|
||||||
public void Execute(string command)
|
protected void Execute(string command)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(command))
|
if (string.IsNullOrEmpty(command))
|
||||||
{
|
{
|
||||||
@@ -39,7 +39,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
|||||||
public void SendDataFromBuffer(byte[] buffer, int length = -1)
|
public void SendDataFromBuffer(byte[] buffer, int length = -1)
|
||||||
{
|
{
|
||||||
if (_inputStream == null) return;
|
if (_inputStream == null) return;
|
||||||
int writeLength = length < 0 ? buffer.Length : length;
|
var writeLength = length < 0 ? buffer.Length : length;
|
||||||
_inputStream.Write(buffer, 0, writeLength);
|
_inputStream.Write(buffer, 0, writeLength);
|
||||||
_inputStream.Flush();
|
_inputStream.Flush();
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-7
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using ADOFAIRenderer.Component;
|
using ADOFAIRenderer.Component;
|
||||||
using ADOFAIRenderer.Util.FFmpeg;
|
using ADOFAIRenderer.Util.FFmpeg;
|
||||||
@@ -17,6 +18,7 @@ namespace ADOFAIRenderer.Util
|
|||||||
private readonly Stack<ReadbackRequestState> _statePool = new Stack<ReadbackRequestState>();
|
private readonly Stack<ReadbackRequestState> _statePool = new Stack<ReadbackRequestState>();
|
||||||
private readonly object _poolLock = new object();
|
private readonly object _poolLock = new object();
|
||||||
private bool _isInitialized;
|
private bool _isInitialized;
|
||||||
|
private volatile bool _isDisposed;
|
||||||
|
|
||||||
public FrameReader()
|
public FrameReader()
|
||||||
{
|
{
|
||||||
@@ -63,7 +65,16 @@ namespace ADOFAIRenderer.Util
|
|||||||
{
|
{
|
||||||
var frame = state.FrameIndex;
|
var frame = state.FrameIndex;
|
||||||
|
|
||||||
if (RenderManager.IsRenderCancelled)
|
lock (_poolLock)
|
||||||
|
{
|
||||||
|
if (_isDisposed)
|
||||||
|
{
|
||||||
|
state.Dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RenderMod.IsRenderCancelled)
|
||||||
{
|
{
|
||||||
RecycleState(state);
|
RecycleState(state);
|
||||||
return;
|
return;
|
||||||
@@ -72,9 +83,12 @@ namespace ADOFAIRenderer.Util
|
|||||||
if (request.hasError)
|
if (request.hasError)
|
||||||
{
|
{
|
||||||
RenderMod.ReadbackFailureCount++;
|
RenderMod.ReadbackFailureCount++;
|
||||||
Melon<RenderMod>.Logger.Warning($"[OOM Prevent] GPU Readback failed for frame {frame}. Queue deadlock prevented by sending an empty frame.");
|
Melon<RenderMod>.Logger.Warning($"[OOM Prevent] GPU Readback failed for frame {frame}. Queue deadlock prevented by sending a black frame.");
|
||||||
|
|
||||||
PushToQueue(frame, Array.Empty<byte>());
|
var byteSize = state.NativeBuffer.Length;
|
||||||
|
var blackFrame = ArrayPool<byte>.Shared.Rent(byteSize);
|
||||||
|
Array.Clear(blackFrame, 0, byteSize);
|
||||||
|
PushToQueue(frame, blackFrame);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -97,15 +111,15 @@ namespace ADOFAIRenderer.Util
|
|||||||
|
|
||||||
private void RecycleState(ReadbackRequestState state)
|
private void RecycleState(ReadbackRequestState state)
|
||||||
{
|
{
|
||||||
if (!_isInitialized)
|
lock (_poolLock)
|
||||||
|
{
|
||||||
|
if (_isDisposed || !_isInitialized)
|
||||||
{
|
{
|
||||||
state.Dispose();
|
state.Dispose();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.Recycle();
|
state.Recycle();
|
||||||
lock (_poolLock)
|
|
||||||
{
|
|
||||||
_statePool.Push(state);
|
_statePool.Push(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,6 +136,8 @@ namespace ADOFAIRenderer.Util
|
|||||||
{
|
{
|
||||||
lock (_poolLock)
|
lock (_poolLock)
|
||||||
{
|
{
|
||||||
|
if (_isDisposed) return;
|
||||||
|
_isDisposed = true;
|
||||||
while (_statePool.Count > 0)
|
while (_statePool.Count > 0)
|
||||||
{
|
{
|
||||||
var state = _statePool.Pop();
|
var state = _statePool.Pop();
|
||||||
|
|||||||
Reference in new issue
Block a user