SHA256
Improve Code Consistency Between RenderMod and RenderManager
This commit is contained in:
1 parent
d4b9e4b89c
commit
ddd0cc1d39
18 files changed
+225
-269
No files matched your search
@@ -7,7 +7,10 @@ namespace ADOFAIRenderer.Component
|
||||
{
|
||||
private void Update()
|
||||
{
|
||||
RenderMod.OnFirstUpdate();
|
||||
if (RenderManager.Instance)
|
||||
{
|
||||
RenderManager.Instance.OnPreRenderUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,10 @@ namespace ADOFAIRenderer.Component
|
||||
private void LateUpdate()
|
||||
{
|
||||
var val = (MeshRenderer)_camQuadMeshField!.GetValue(scrCamera.instance);
|
||||
RenderMod.OnPostRender(val.material.mainTexture as RenderTexture);
|
||||
if (RenderManager.Instance)
|
||||
{
|
||||
RenderManager.Instance.OnPostRender(val.material.mainTexture as RenderTexture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+165
-27
@@ -17,8 +17,10 @@ namespace ADOFAIRenderer.Component
|
||||
{
|
||||
// Singleton Accessors
|
||||
public static RenderManager Instance { get; private set; } = null!;
|
||||
public static bool IsActive => Instance && Instance.isRendering;
|
||||
public static bool IsCapturingActive => Instance && Instance.isCapturing;
|
||||
|
||||
// Core Render Configuration and Path (OOP Instance Fields)
|
||||
// Core Render Configuration and Path
|
||||
public int countdown;
|
||||
public long frameCount;
|
||||
public bool skipPressToStart;
|
||||
@@ -26,6 +28,25 @@ namespace ADOFAIRenderer.Component
|
||||
public bool isRenderCancelled;
|
||||
public RenderSettings Configuration;
|
||||
|
||||
// Render Sessions State (OOP Instance Fields)
|
||||
public bool isRendering;
|
||||
public bool isCapturing;
|
||||
public double timeBase;
|
||||
public double unscaledTimeBase;
|
||||
public double dspTimeBase;
|
||||
public int frameCountBase;
|
||||
|
||||
private FrameReader? _frameReader;
|
||||
public bool renderCheck = true;
|
||||
public int skipCount;
|
||||
|
||||
public long frameCountForVideo;
|
||||
public long lastVideoFrameIndex = -1;
|
||||
public long lastRequestedVideoFrameIndex = -1;
|
||||
public int readbackFailureCount;
|
||||
public int throttleCount;
|
||||
public bool isThrottling;
|
||||
|
||||
public struct RenderSettings
|
||||
{
|
||||
public double TargetFPS;
|
||||
@@ -54,7 +75,7 @@ namespace ADOFAIRenderer.Component
|
||||
}
|
||||
}
|
||||
|
||||
// Thread and Queue State (OOP Instance Fields)
|
||||
// Thread and Queue State
|
||||
public SortedList<long, byte[]> ImageQueue = new SortedList<long, byte[]>();
|
||||
public Thread? RenderThread;
|
||||
public long passCount;
|
||||
@@ -100,12 +121,12 @@ namespace ADOFAIRenderer.Component
|
||||
scrController.instance.noFailInfiniteMargin = true;
|
||||
}
|
||||
|
||||
RenderMod.TimeBase = Time.timeAsDouble;
|
||||
RenderMod.UnscaledTimeBase = Time.unscaledTimeAsDouble;
|
||||
RenderMod.DspTimeBase = AudioSettings.dspTime;
|
||||
RenderMod.FrameCountBase = Time.frameCount;
|
||||
timeBase = Time.timeAsDouble;
|
||||
unscaledTimeBase = Time.unscaledTimeAsDouble;
|
||||
dspTimeBase = AudioSettings.dspTime;
|
||||
frameCountBase = Time.frameCount;
|
||||
|
||||
RenderMod.IsRendering = true;
|
||||
isRendering = true;
|
||||
countdown = 3;
|
||||
wonFrame = -1;
|
||||
|
||||
@@ -120,10 +141,10 @@ namespace ADOFAIRenderer.Component
|
||||
scrController.instance!.StartCoroutine(SetupPlay());
|
||||
}
|
||||
|
||||
if (!RenderMod.IsRendering) return;
|
||||
if (!isRendering) return;
|
||||
if (!scrController.instance || scrController.instance.state == States.Fail || scrController.instance.state == States.Fail2)
|
||||
{
|
||||
RenderMod.IsRendering = RenderMod.IsCapturing = false;
|
||||
isRendering = isCapturing = false;
|
||||
StopFFmpeg(false);
|
||||
EndRender();
|
||||
}
|
||||
@@ -137,11 +158,131 @@ namespace ADOFAIRenderer.Component
|
||||
var extraFrames = (long)Math.Ceiling(Configuration.EndWaitTime * Configuration.TargetFPS);
|
||||
|
||||
if (frameCount - wonFrame < extraFrames) return;
|
||||
RenderMod.IsRendering = RenderMod.IsCapturing = false;
|
||||
isRendering = isCapturing = false;
|
||||
StopFFmpeg(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPreRenderUpdate()
|
||||
{
|
||||
if (!isRendering) return;
|
||||
|
||||
// [렉으로 인한 프레임 렌더링 스킵 시 재시도 로직 (원본 모드 이식)]
|
||||
if (renderCheck && isCapturing)
|
||||
{
|
||||
if (skipCount < 60)
|
||||
{
|
||||
Melon<RenderMod>.Logger.Warning($"[Render Skipped] Frame {frameCount} rendering missed. Retrying... (Attempt {skipCount + 1}/60)");
|
||||
skipCount++;
|
||||
Time.timeScale = 0f; // 진행 시간 멈추기 (재시도)
|
||||
return; // 업데이트 진행하지 않고 대기
|
||||
}
|
||||
|
||||
// 60회 초과 시 최후의 블랙 프레임 삽입하여 전체 파이프라인 마비 방지
|
||||
Melon<RenderMod>.Logger.Error($"[Render Skipped] Frame {frameCount} rendering failed after 60 retries. Supplying black frame.");
|
||||
|
||||
var currentFrame = frameCountForVideo++;
|
||||
var targetFps = Configuration.TargetFPS;
|
||||
var videoFps = Configuration.VideoFPS;
|
||||
var effectiveVideoFps = Math.Min(videoFps, targetFps);
|
||||
var videoFrameIndex = (long)(currentFrame * effectiveVideoFps / targetFps);
|
||||
|
||||
if (videoFrameIndex > lastVideoFrameIndex)
|
||||
{
|
||||
lastVideoFrameIndex = videoFrameIndex;
|
||||
lastRequestedVideoFrameIndex = videoFrameIndex;
|
||||
|
||||
var byteSize = Configuration.RenderWidth * Configuration.RenderHeight * 4;
|
||||
var blackFrame = System.Buffers.ArrayPool<byte>.Shared.Rent(byteSize);
|
||||
Array.Clear(blackFrame, 0, byteSize);
|
||||
|
||||
lock (ImageQueue)
|
||||
{
|
||||
ImageQueue[videoFrameIndex] = blackFrame;
|
||||
}
|
||||
}
|
||||
|
||||
renderCheck = false; // 진행 허용
|
||||
}
|
||||
|
||||
skipCount = 0;
|
||||
renderCheck = true; // 다음 프레임 검사용으로 초기화
|
||||
frameCount++;
|
||||
}
|
||||
|
||||
// ReSharper disable once Unity.IncorrectMethodSignature
|
||||
public void OnPostRender(RenderTexture? texture)
|
||||
{
|
||||
if (!isCapturing || !texture) return;
|
||||
|
||||
renderCheck = false; // 이번 프레임 정상 렌더링 완료
|
||||
|
||||
// [Backpressure Throttling (2번 작업)]
|
||||
const int maxQueuedFrames = 60;
|
||||
isThrottling = false;
|
||||
|
||||
while (isCapturing && isRendering)
|
||||
{
|
||||
int count;
|
||||
lock (ImageQueue)
|
||||
{
|
||||
count = ImageQueue.Count;
|
||||
}
|
||||
|
||||
if (count <= maxQueuedFrames) break;
|
||||
|
||||
if (!isThrottling)
|
||||
{
|
||||
isThrottling = true;
|
||||
throttleCount++;
|
||||
}
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
isThrottling = false;
|
||||
|
||||
var currentFrame = frameCountForVideo++;
|
||||
|
||||
var targetFps = Configuration.TargetFPS;
|
||||
var videoFps = Configuration.VideoFPS;
|
||||
var effectiveVideoFps = Math.Min(videoFps, targetFps);
|
||||
|
||||
var videoFrameIndex = (long)(currentFrame * effectiveVideoFps / targetFps);
|
||||
|
||||
if (videoFrameIndex > lastVideoFrameIndex)
|
||||
{
|
||||
lastVideoFrameIndex = videoFrameIndex;
|
||||
lastRequestedVideoFrameIndex = videoFrameIndex;
|
||||
_frameReader?.RequestFrameCapture(texture, videoFrameIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void StartRender()
|
||||
{
|
||||
var targetMethod = typeof(scnEditor).GetMethod("TryApplicationQuit",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
|
||||
|
||||
if (targetMethod == null || scnEditor.instance == null) return;
|
||||
if (ADOBase.levelPath.IsNullOrEmpty())
|
||||
{
|
||||
scnEditor.instance.TryQuitToMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Instance != null)
|
||||
{
|
||||
Instance.levelPath = ADOBase.levelPath;
|
||||
}
|
||||
|
||||
scnEditor.instance.CheckUnsavedChanges(() =>
|
||||
{
|
||||
var quitDelegate =
|
||||
(Func<bool>)Delegate.CreateDelegate(typeof(Func<bool>), scnEditor.instance, targetMethod);
|
||||
|
||||
Application.wantsToQuit -= quitDelegate;
|
||||
ADOBase.loader.LoadSceneWithTransition(WipeDirection.StartsFromRight, "Assets/Scenes/RenderSettingsScene.unity");
|
||||
});
|
||||
}
|
||||
|
||||
public void SetupCaptureDirectory()
|
||||
{
|
||||
var renderDir = Path.Combine(MelonEnvironment.GameRootDirectory, "Render");
|
||||
@@ -173,11 +314,11 @@ namespace ADOFAIRenderer.Component
|
||||
|
||||
frameCount = 0;
|
||||
|
||||
RenderMod.FrameReader?.Dispose();
|
||||
RenderMod.FrameReader = new FrameReader();
|
||||
_frameReader?.Dispose();
|
||||
_frameReader = new FrameReader();
|
||||
StartFFmpegThread();
|
||||
|
||||
RenderMod.IsCapturing = true;
|
||||
isCapturing = true;
|
||||
scrController.instance.paused = scrController.instance.audioPaused = false;
|
||||
scrController.instance.levelWasSkipped = true;
|
||||
|
||||
@@ -226,12 +367,11 @@ namespace ADOFAIRenderer.Component
|
||||
}
|
||||
passCount = 0;
|
||||
isRenderCancelled = false;
|
||||
RenderMod.IsRenderCancelled = false;
|
||||
RenderMod.FrameCountForVideo = 0;
|
||||
RenderMod.LastVideoFrameIndex = -1;
|
||||
RenderMod.LastRequestedVideoFrameIndex = -1;
|
||||
RenderMod.RenderCheck = true;
|
||||
RenderMod.SkipCount = 0;
|
||||
frameCountForVideo = 0;
|
||||
lastVideoFrameIndex = -1;
|
||||
lastRequestedVideoFrameIndex = -1;
|
||||
renderCheck = true;
|
||||
skipCount = 0;
|
||||
endRequested = false;
|
||||
endSuccessful = false;
|
||||
|
||||
@@ -280,7 +420,7 @@ namespace ADOFAIRenderer.Component
|
||||
{
|
||||
var firstKey = ImageQueue.Keys[0];
|
||||
|
||||
if (firstKey > passCount && !RenderMod.IsCapturing)
|
||||
if (firstKey > passCount && !isCapturing)
|
||||
{
|
||||
passCount = firstKey;
|
||||
}
|
||||
@@ -305,14 +445,14 @@ namespace ADOFAIRenderer.Component
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isRenderCancelled || RenderMod.IsRenderCancelled)
|
||||
if (isRenderCancelled)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (endRequested)
|
||||
{
|
||||
if (passCount > RenderMod.LastRequestedVideoFrameIndex)
|
||||
if (passCount > lastRequestedVideoFrameIndex)
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -335,9 +475,8 @@ namespace ADOFAIRenderer.Component
|
||||
|
||||
public void CancelRender()
|
||||
{
|
||||
RenderMod.IsRendering = RenderMod.IsCapturing = false;
|
||||
isRendering = isCapturing = false;
|
||||
isRenderCancelled = true;
|
||||
RenderMod.IsRenderCancelled = true;
|
||||
|
||||
StopFFmpeg(false);
|
||||
EndRender();
|
||||
@@ -349,7 +488,6 @@ namespace ADOFAIRenderer.Component
|
||||
if (!success)
|
||||
{
|
||||
isRenderCancelled = true;
|
||||
RenderMod.IsRenderCancelled = true;
|
||||
}
|
||||
skipPressToStart = false;
|
||||
_renderSetup = false;
|
||||
@@ -379,7 +517,7 @@ namespace ADOFAIRenderer.Component
|
||||
ImageQueue.Clear();
|
||||
}
|
||||
|
||||
RenderMod.FrameReader?.Dispose();
|
||||
_frameReader?.Dispose();
|
||||
|
||||
// 디버그 모니터 UI 정리
|
||||
if (_debugMonitorObj)
|
||||
@@ -450,7 +588,7 @@ namespace ADOFAIRenderer.Component
|
||||
GCS.customLevelPaths = null;
|
||||
GCS.customLevelId = null;
|
||||
|
||||
RenderMod.FrameReader?.Dispose();
|
||||
_frameReader?.Dispose();
|
||||
ADOBase.loader.LoadSceneWithTransition(WipeDirection.StartsFromRight, "scnEditor");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user