Implement Filter-Before-Capture Structure

This commit is contained in:
copecone committed 2026-05-28 11:33:47 +09:00
1 parent 379c0360d7
commit ec06f99603
4 files changed
+70 -38

No files matched your search

+21 -12
View File
@@ -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)
+20 -13
View File
@@ -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,15 +173,16 @@ 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)
{
return;
}
FrameReader?.RequestFrameCapture(texture, currentFrame); var videoFrameIndex = (long)(currentFrame * effectiveVideoFps / targetFps);
if (videoFrameIndex > LastVideoFrameIndex)
{
LastVideoFrameIndex = videoFrameIndex;
LastRequestedVideoFrameIndex = videoFrameIndex;
FrameReader?.RequestFrameCapture(texture, videoFrameIndex);
}
/* /*
AsyncGPUReadback.Request(texture, 0, delegate(AsyncGPUReadbackRequest req) AsyncGPUReadback.Request(texture, 0, delegate(AsyncGPUReadbackRequest req)
+2 -2
View File
@@ -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();
} }
+27 -11
View File
@@ -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)
{
state.Dispose();
return;
}
state.Recycle();
lock (_poolLock) lock (_poolLock)
{ {
if (_isDisposed || !_isInitialized)
{
state.Dispose();
return;
}
state.Recycle();
_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();