SHA256
forked from ADOFAIEditorForum/ADOFAIRenderer
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()
|
private void Update()
|
||||||
{
|
{
|
||||||
RenderMod.OnFirstUpdate();
|
if (RenderManager.Instance)
|
||||||
|
{
|
||||||
|
RenderManager.Instance.OnPreRenderUpdate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,10 @@ namespace ADOFAIRenderer.Component
|
|||||||
private void LateUpdate()
|
private void LateUpdate()
|
||||||
{
|
{
|
||||||
var val = (MeshRenderer)_camQuadMeshField!.GetValue(scrCamera.instance);
|
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
|
// Singleton Accessors
|
||||||
public static RenderManager Instance { get; private set; } = null!;
|
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 int countdown;
|
||||||
public long frameCount;
|
public long frameCount;
|
||||||
public bool skipPressToStart;
|
public bool skipPressToStart;
|
||||||
@@ -26,6 +28,25 @@ namespace ADOFAIRenderer.Component
|
|||||||
public bool isRenderCancelled;
|
public bool isRenderCancelled;
|
||||||
public RenderSettings Configuration;
|
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 struct RenderSettings
|
||||||
{
|
{
|
||||||
public double TargetFPS;
|
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 SortedList<long, byte[]> ImageQueue = new SortedList<long, byte[]>();
|
||||||
public Thread? RenderThread;
|
public Thread? RenderThread;
|
||||||
public long passCount;
|
public long passCount;
|
||||||
@@ -100,12 +121,12 @@ namespace ADOFAIRenderer.Component
|
|||||||
scrController.instance.noFailInfiniteMargin = true;
|
scrController.instance.noFailInfiniteMargin = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderMod.TimeBase = Time.timeAsDouble;
|
timeBase = Time.timeAsDouble;
|
||||||
RenderMod.UnscaledTimeBase = Time.unscaledTimeAsDouble;
|
unscaledTimeBase = Time.unscaledTimeAsDouble;
|
||||||
RenderMod.DspTimeBase = AudioSettings.dspTime;
|
dspTimeBase = AudioSettings.dspTime;
|
||||||
RenderMod.FrameCountBase = Time.frameCount;
|
frameCountBase = Time.frameCount;
|
||||||
|
|
||||||
RenderMod.IsRendering = true;
|
isRendering = true;
|
||||||
countdown = 3;
|
countdown = 3;
|
||||||
wonFrame = -1;
|
wonFrame = -1;
|
||||||
|
|
||||||
@@ -120,10 +141,10 @@ namespace ADOFAIRenderer.Component
|
|||||||
scrController.instance!.StartCoroutine(SetupPlay());
|
scrController.instance!.StartCoroutine(SetupPlay());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!RenderMod.IsRendering) return;
|
if (!isRendering) return;
|
||||||
if (!scrController.instance || scrController.instance.state == States.Fail || scrController.instance.state == States.Fail2)
|
if (!scrController.instance || scrController.instance.state == States.Fail || scrController.instance.state == States.Fail2)
|
||||||
{
|
{
|
||||||
RenderMod.IsRendering = RenderMod.IsCapturing = false;
|
isRendering = isCapturing = false;
|
||||||
StopFFmpeg(false);
|
StopFFmpeg(false);
|
||||||
EndRender();
|
EndRender();
|
||||||
}
|
}
|
||||||
@@ -137,11 +158,131 @@ namespace ADOFAIRenderer.Component
|
|||||||
var extraFrames = (long)Math.Ceiling(Configuration.EndWaitTime * Configuration.TargetFPS);
|
var extraFrames = (long)Math.Ceiling(Configuration.EndWaitTime * Configuration.TargetFPS);
|
||||||
|
|
||||||
if (frameCount - wonFrame < extraFrames) return;
|
if (frameCount - wonFrame < extraFrames) return;
|
||||||
RenderMod.IsRendering = RenderMod.IsCapturing = false;
|
isRendering = isCapturing = false;
|
||||||
StopFFmpeg(true);
|
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()
|
public void SetupCaptureDirectory()
|
||||||
{
|
{
|
||||||
var renderDir = Path.Combine(MelonEnvironment.GameRootDirectory, "Render");
|
var renderDir = Path.Combine(MelonEnvironment.GameRootDirectory, "Render");
|
||||||
@@ -173,11 +314,11 @@ namespace ADOFAIRenderer.Component
|
|||||||
|
|
||||||
frameCount = 0;
|
frameCount = 0;
|
||||||
|
|
||||||
RenderMod.FrameReader?.Dispose();
|
_frameReader?.Dispose();
|
||||||
RenderMod.FrameReader = new FrameReader();
|
_frameReader = new FrameReader();
|
||||||
StartFFmpegThread();
|
StartFFmpegThread();
|
||||||
|
|
||||||
RenderMod.IsCapturing = true;
|
isCapturing = true;
|
||||||
scrController.instance.paused = scrController.instance.audioPaused = false;
|
scrController.instance.paused = scrController.instance.audioPaused = false;
|
||||||
scrController.instance.levelWasSkipped = true;
|
scrController.instance.levelWasSkipped = true;
|
||||||
|
|
||||||
@@ -226,12 +367,11 @@ namespace ADOFAIRenderer.Component
|
|||||||
}
|
}
|
||||||
passCount = 0;
|
passCount = 0;
|
||||||
isRenderCancelled = false;
|
isRenderCancelled = false;
|
||||||
RenderMod.IsRenderCancelled = false;
|
frameCountForVideo = 0;
|
||||||
RenderMod.FrameCountForVideo = 0;
|
lastVideoFrameIndex = -1;
|
||||||
RenderMod.LastVideoFrameIndex = -1;
|
lastRequestedVideoFrameIndex = -1;
|
||||||
RenderMod.LastRequestedVideoFrameIndex = -1;
|
renderCheck = true;
|
||||||
RenderMod.RenderCheck = true;
|
skipCount = 0;
|
||||||
RenderMod.SkipCount = 0;
|
|
||||||
endRequested = false;
|
endRequested = false;
|
||||||
endSuccessful = false;
|
endSuccessful = false;
|
||||||
|
|
||||||
@@ -280,7 +420,7 @@ namespace ADOFAIRenderer.Component
|
|||||||
{
|
{
|
||||||
var firstKey = ImageQueue.Keys[0];
|
var firstKey = ImageQueue.Keys[0];
|
||||||
|
|
||||||
if (firstKey > passCount && !RenderMod.IsCapturing)
|
if (firstKey > passCount && !isCapturing)
|
||||||
{
|
{
|
||||||
passCount = firstKey;
|
passCount = firstKey;
|
||||||
}
|
}
|
||||||
@@ -305,14 +445,14 @@ namespace ADOFAIRenderer.Component
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isRenderCancelled || RenderMod.IsRenderCancelled)
|
if (isRenderCancelled)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (endRequested)
|
if (endRequested)
|
||||||
{
|
{
|
||||||
if (passCount > RenderMod.LastRequestedVideoFrameIndex)
|
if (passCount > lastRequestedVideoFrameIndex)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -335,9 +475,8 @@ namespace ADOFAIRenderer.Component
|
|||||||
|
|
||||||
public void CancelRender()
|
public void CancelRender()
|
||||||
{
|
{
|
||||||
RenderMod.IsRendering = RenderMod.IsCapturing = false;
|
isRendering = isCapturing = false;
|
||||||
isRenderCancelled = true;
|
isRenderCancelled = true;
|
||||||
RenderMod.IsRenderCancelled = true;
|
|
||||||
|
|
||||||
StopFFmpeg(false);
|
StopFFmpeg(false);
|
||||||
EndRender();
|
EndRender();
|
||||||
@@ -349,7 +488,6 @@ namespace ADOFAIRenderer.Component
|
|||||||
if (!success)
|
if (!success)
|
||||||
{
|
{
|
||||||
isRenderCancelled = true;
|
isRenderCancelled = true;
|
||||||
RenderMod.IsRenderCancelled = true;
|
|
||||||
}
|
}
|
||||||
skipPressToStart = false;
|
skipPressToStart = false;
|
||||||
_renderSetup = false;
|
_renderSetup = false;
|
||||||
@@ -379,7 +517,7 @@ namespace ADOFAIRenderer.Component
|
|||||||
ImageQueue.Clear();
|
ImageQueue.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderMod.FrameReader?.Dispose();
|
_frameReader?.Dispose();
|
||||||
|
|
||||||
// 디버그 모니터 UI 정리
|
// 디버그 모니터 UI 정리
|
||||||
if (_debugMonitorObj)
|
if (_debugMonitorObj)
|
||||||
@@ -450,7 +588,7 @@ namespace ADOFAIRenderer.Component
|
|||||||
GCS.customLevelPaths = null;
|
GCS.customLevelPaths = null;
|
||||||
GCS.customLevelId = null;
|
GCS.customLevelId = null;
|
||||||
|
|
||||||
RenderMod.FrameReader?.Dispose();
|
_frameReader?.Dispose();
|
||||||
ADOBase.loader.LoadSceneWithTransition(WipeDirection.StartsFromRight, "scnEditor");
|
ADOBase.loader.LoadSceneWithTransition(WipeDirection.StartsFromRight, "scnEditor");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using ADOFAIRenderer.Component;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
|
|
||||||
namespace ADOFAIRenderer.Patch.ClassPatch
|
namespace ADOFAIRenderer.Patch.ClassPatch
|
||||||
@@ -12,7 +13,7 @@ namespace ADOFAIRenderer.Patch.ClassPatch
|
|||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
public static bool PrefixShow(EndscreenLanterns __instance, ref int __result)
|
public static bool PrefixShow(EndscreenLanterns __instance, ref int __result)
|
||||||
{
|
{
|
||||||
if (!RenderMod.IsRendering) return true;
|
if (!RenderManager.IsActive) return true;
|
||||||
if (__instance != null)
|
if (__instance != null)
|
||||||
{
|
{
|
||||||
__instance.Hide();
|
__instance.Hide();
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using ADOFAIRenderer.Component;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
|
|
||||||
namespace ADOFAIRenderer.Patch.ClassPatch
|
namespace ADOFAIRenderer.Patch.ClassPatch
|
||||||
@@ -8,7 +9,7 @@ namespace ADOFAIRenderer.Patch.ClassPatch
|
|||||||
// ReSharper disable once InconsistentNaming
|
// ReSharper disable once InconsistentNaming
|
||||||
public static void Postfix(ref SkipIntroBehavior __result)
|
public static void Postfix(ref SkipIntroBehavior __result)
|
||||||
{
|
{
|
||||||
if (RenderMod.IsRendering &&
|
if (RenderManager.IsActive &&
|
||||||
scrConductor.instance != null &&
|
scrConductor.instance != null &&
|
||||||
!scrConductor.instance.hasSongStarted)
|
!scrConductor.instance.hasSongStarted)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using ADOFAIRenderer.Component;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
@@ -22,7 +23,7 @@ namespace ADOFAIRenderer.Patch.ClassPatch
|
|||||||
var button = buttonObj.GetComponent<Button>()!;
|
var button = buttonObj.GetComponent<Button>()!;
|
||||||
// Clear persistent and non-persistent listeners from the original button
|
// Clear persistent and non-persistent listeners from the original button
|
||||||
button.onClick = new Button.ButtonClickedEvent();
|
button.onClick = new Button.ButtonClickedEvent();
|
||||||
button.onClick.AddListener(RenderMod.StartRender);
|
button.onClick.AddListener(RenderManager.StartRender);
|
||||||
|
|
||||||
// The button root has an Image component for the background (rounded box).
|
// The button root has an Image component for the background (rounded box).
|
||||||
// We want to change the icon sprite, which is on a child GameObject.
|
// We want to change the icon sprite, which is on a child GameObject.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
using ADOFAIRenderer.Component;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using MelonLoader;
|
|
||||||
|
|
||||||
namespace ADOFAIRenderer.Patch.ClassPatch
|
namespace ADOFAIRenderer.Patch.ClassPatch
|
||||||
{
|
{
|
||||||
@@ -11,35 +11,35 @@ namespace ADOFAIRenderer.Patch.ClassPatch
|
|||||||
[HarmonyPatch("TogglePauseGame")]
|
[HarmonyPatch("TogglePauseGame")]
|
||||||
public static bool PreventPause()
|
public static bool PreventPause()
|
||||||
{
|
{
|
||||||
return !RenderMod.IsRendering;
|
return !RenderManager.IsActive;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HarmonyPrefix]
|
[HarmonyPrefix]
|
||||||
[HarmonyPatch("PortalTravelAction")]
|
[HarmonyPatch("PortalTravelAction")]
|
||||||
public static bool PreventPortalTravel()
|
public static bool PreventPortalTravel()
|
||||||
{
|
{
|
||||||
return !RenderMod.IsRendering;
|
return !RenderManager.IsActive;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HarmonyPrefix]
|
[HarmonyPrefix]
|
||||||
[HarmonyPatch("FailAction")]
|
[HarmonyPatch("FailAction")]
|
||||||
public static bool PreventFailAction()
|
public static bool PreventFailAction()
|
||||||
{
|
{
|
||||||
return !RenderMod.IsRendering;
|
return !RenderManager.IsActive;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HarmonyPrefix]
|
[HarmonyPrefix]
|
||||||
[HarmonyPatch("Fail2Action")]
|
[HarmonyPatch("Fail2Action")]
|
||||||
public static bool PreventFail2Action()
|
public static bool PreventFail2Action()
|
||||||
{
|
{
|
||||||
return !RenderMod.IsRendering;
|
return !RenderManager.IsActive;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HarmonyPrefix]
|
[HarmonyPrefix]
|
||||||
[HarmonyPatch("OnPlayerDied")]
|
[HarmonyPatch("OnPlayerDied")]
|
||||||
public static bool PreventPlayerDied()
|
public static bool PreventPlayerDied()
|
||||||
{
|
{
|
||||||
return !RenderMod.IsRendering;
|
return !RenderManager.IsActive;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using ADOFAIRenderer.Component;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
|
|
||||||
namespace ADOFAIRenderer.Patch.ClassPatch
|
namespace ADOFAIRenderer.Patch.ClassPatch
|
||||||
@@ -10,7 +11,7 @@ namespace ADOFAIRenderer.Patch.ClassPatch
|
|||||||
[HarmonyPatch("SaveCustom")]
|
[HarmonyPatch("SaveCustom")]
|
||||||
public static bool PreventSaveCustom()
|
public static bool PreventSaveCustom()
|
||||||
{
|
{
|
||||||
return !RenderMod.IsRendering;
|
return !RenderManager.IsActive;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,7 @@ namespace ADOFAIRenderer.Patch.ClassPatch
|
|||||||
// ReSharper disable once InconsistentNaming
|
// ReSharper disable once InconsistentNaming
|
||||||
private static bool IgnoreInput(ref bool __result)
|
private static bool IgnoreInput(ref bool __result)
|
||||||
{
|
{
|
||||||
if (!RenderMod.IsRendering) return true;
|
if (!RenderManager.IsActive) return true;
|
||||||
|
|
||||||
__result = RenderManager.Instance && RenderManager.Instance.frameCount == 0 && RenderManager.Instance.countdown == 0;
|
__result = RenderManager.Instance && RenderManager.Instance.frameCount == 0 && RenderManager.Instance.countdown == 0;
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace ADOFAIRenderer.Patch.ClassPatch
|
|||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
private static void IgnoreInput(Text ___txt, Color ___defaultColor)
|
private static void IgnoreInput(Text ___txt, Color ___defaultColor)
|
||||||
{
|
{
|
||||||
if (!RenderMod.IsRendering) return;
|
if (!RenderManager.IsActive) return;
|
||||||
|
|
||||||
___txt.text = "Render";
|
___txt.text = "Render";
|
||||||
if (RenderManager.Instance)
|
if (RenderManager.Instance)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using ADOFAIRenderer.Component;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
|
|
||||||
namespace ADOFAIRenderer.Patch.ClassPatch
|
namespace ADOFAIRenderer.Patch.ClassPatch
|
||||||
@@ -11,7 +12,7 @@ namespace ADOFAIRenderer.Patch.ClassPatch
|
|||||||
// ReSharper disable once InconsistentNaming
|
// ReSharper disable once InconsistentNaming
|
||||||
public static void PreventEndscreenLanternsPostfix(scrUIController __instance)
|
public static void PreventEndscreenLanternsPostfix(scrUIController __instance)
|
||||||
{
|
{
|
||||||
if (RenderMod.IsRendering && __instance != null)
|
if (RenderManager.IsActive && __instance != null)
|
||||||
{
|
{
|
||||||
__instance.HideEndscreenLanterns();
|
__instance.HideEndscreenLanterns();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ namespace ADOFAIRenderer.Patch
|
|||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
public static bool Prefix(ref float __result)
|
public static bool Prefix(ref float __result)
|
||||||
{
|
{
|
||||||
if (!RenderMod.IsCapturing || !RenderPatch || !RenderManager.Instance) return true;
|
if (!RenderManager.IsCapturingActive || !RenderPatch || !RenderManager.Instance) return true;
|
||||||
__result = (float)(RenderManager.Instance.frameCount / RenderManager.Instance.Configuration.TargetFPS + RenderMod.TimeBase);
|
__result = (float)(RenderManager.Instance.frameCount / RenderManager.Instance.Configuration.TargetFPS + RenderManager.Instance.timeBase);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -27,8 +27,8 @@ namespace ADOFAIRenderer.Patch
|
|||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
public static bool Prefix(ref double __result)
|
public static bool Prefix(ref double __result)
|
||||||
{
|
{
|
||||||
if (!RenderMod.IsCapturing || !RenderPatch || !RenderManager.Instance) return true;
|
if (!RenderManager.IsCapturingActive || !RenderPatch || !RenderManager.Instance) return true;
|
||||||
__result = RenderManager.Instance.frameCount / RenderManager.Instance.Configuration.TargetFPS + RenderMod.TimeBase;
|
__result = RenderManager.Instance.frameCount / RenderManager.Instance.Configuration.TargetFPS + RenderManager.Instance.timeBase;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,8 +39,8 @@ namespace ADOFAIRenderer.Patch
|
|||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
public static bool Prefix(ref float __result)
|
public static bool Prefix(ref float __result)
|
||||||
{
|
{
|
||||||
if (!RenderMod.IsCapturing || !RenderPatch || !RenderManager.Instance) return true;
|
if (!RenderManager.IsCapturingActive || !RenderPatch || !RenderManager.Instance) return true;
|
||||||
__result = (float)(RenderManager.Instance.frameCount / RenderManager.Instance.Configuration.TargetFPS + RenderMod.UnscaledTimeBase);
|
__result = (float)(RenderManager.Instance.frameCount / RenderManager.Instance.Configuration.TargetFPS + RenderManager.Instance.unscaledTimeBase);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,8 +51,8 @@ namespace ADOFAIRenderer.Patch
|
|||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
public static bool Prefix(ref double __result)
|
public static bool Prefix(ref double __result)
|
||||||
{
|
{
|
||||||
if (!RenderMod.IsCapturing || !RenderPatch || !RenderManager.Instance) return true;
|
if (!RenderManager.IsCapturingActive || !RenderPatch || !RenderManager.Instance) return true;
|
||||||
__result = RenderManager.Instance.frameCount / RenderManager.Instance.Configuration.TargetFPS + RenderMod.UnscaledTimeBase;
|
__result = RenderManager.Instance.frameCount / RenderManager.Instance.Configuration.TargetFPS + RenderManager.Instance.unscaledTimeBase;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,10 +63,9 @@ namespace ADOFAIRenderer.Patch
|
|||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
public static bool Prefix(ref double __result)
|
public static bool Prefix(ref double __result)
|
||||||
{
|
{
|
||||||
if (!RenderMod.IsCapturing || !RenderPatch || !RenderManager.Instance) return true;
|
if (!RenderManager.IsCapturingActive || !RenderPatch || !RenderManager.Instance) return true;
|
||||||
__result = RenderManager.Instance.frameCount / RenderManager.Instance.Configuration.TargetFPS + RenderMod.DspTimeBase;
|
__result = RenderManager.Instance.frameCount / RenderManager.Instance.Configuration.TargetFPS + RenderManager.Instance.dspTimeBase;
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +75,7 @@ namespace ADOFAIRenderer.Patch
|
|||||||
// ReSharper disable once InconsistentNaming
|
// ReSharper disable once InconsistentNaming
|
||||||
public static bool Prefix(AudioSource __instance, double time)
|
public static bool Prefix(AudioSource __instance, double time)
|
||||||
{
|
{
|
||||||
return !RenderMod.IsRendering || !RenderPatch;
|
return !RenderManager.IsActive || !RenderPatch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,8 +85,8 @@ namespace ADOFAIRenderer.Patch
|
|||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
public static bool Prefix(ref int __result)
|
public static bool Prefix(ref int __result)
|
||||||
{
|
{
|
||||||
if (!RenderMod.IsCapturing || !RenderPatch || !RenderManager.Instance) return true;
|
if (!RenderManager.IsCapturingActive || !RenderPatch || !RenderManager.Instance) return true;
|
||||||
__result = (int)(RenderManager.Instance.frameCount + RenderMod.FrameCountBase);
|
__result = (int)(RenderManager.Instance.frameCount + RenderManager.Instance.frameCountBase);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,8 +97,8 @@ namespace ADOFAIRenderer.Patch
|
|||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
public static bool Prefix(ref float __result)
|
public static bool Prefix(ref float __result)
|
||||||
{
|
{
|
||||||
if (!RenderMod.IsCapturing || !RenderPatch || !RenderManager.Instance) return true;
|
if (RenderManager.Instance && (!RenderManager.IsCapturingActive || !RenderPatch || !RenderManager.Instance)) return true;
|
||||||
__result = (float)(1 / RenderManager.Instance.Configuration.TargetFPS);
|
__result = (float)(1 / RenderManager.Instance!.Configuration.TargetFPS);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,7 +109,7 @@ namespace ADOFAIRenderer.Patch
|
|||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
public static bool Prefix(ref float __result)
|
public static bool Prefix(ref float __result)
|
||||||
{
|
{
|
||||||
if (!RenderMod.IsCapturing || !RenderPatch || !RenderManager.Instance) return true;
|
if (!RenderManager.IsCapturingActive || !RenderPatch || !RenderManager.Instance) return true;
|
||||||
__result = (float)(1 / RenderManager.Instance.Configuration.TargetFPS);
|
__result = (float)(1 / RenderManager.Instance.Configuration.TargetFPS);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -122,7 +121,7 @@ namespace ADOFAIRenderer.Patch
|
|||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
public static bool Prefix(ref int __result)
|
public static bool Prefix(ref int __result)
|
||||||
{
|
{
|
||||||
if (!RenderMod.IsRendering || !RenderPatch || !RenderManager.Instance) return true;
|
if (!RenderManager.IsActive || !RenderPatch || !RenderManager.Instance) return true;
|
||||||
__result = RenderManager.Instance.Configuration.RenderWidth;
|
__result = RenderManager.Instance.Configuration.RenderWidth;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -134,7 +133,7 @@ namespace ADOFAIRenderer.Patch
|
|||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
public static bool Prefix(ref int __result)
|
public static bool Prefix(ref int __result)
|
||||||
{
|
{
|
||||||
if (!RenderMod.IsRendering || !RenderPatch || !RenderManager.Instance) return true;
|
if (!RenderManager.IsActive || !RenderPatch || !RenderManager.Instance) return true;
|
||||||
__result = RenderManager.Instance.Configuration.RenderHeight;
|
__result = RenderManager.Instance.Configuration.RenderHeight;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace ADOFAIRenderer.Patch
|
|||||||
{
|
{
|
||||||
public static bool Prefix()
|
public static bool Prefix()
|
||||||
{
|
{
|
||||||
return !RenderMod.IsRendering || (RenderManager.Instance && RenderManager.Instance.Configuration.ShowHitText);
|
return !RenderManager.IsActive || RenderManager.Instance is { Configuration: { ShowHitText: true } };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-197
@@ -1,52 +1,27 @@
|
|||||||
using System;
|
|
||||||
using System.Reflection;
|
|
||||||
using ADOFAIRenderer;
|
using ADOFAIRenderer;
|
||||||
using ADOFAIRenderer.Component;
|
using ADOFAIRenderer.Component;
|
||||||
using ADOFAIRenderer.Util;
|
using ADOFAIRenderer.Util;
|
||||||
using MelonLoader;
|
using MelonLoader;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.EventSystems;
|
using UnityEngine.EventSystems;
|
||||||
using Object = UnityEngine.Object;
|
|
||||||
|
|
||||||
[assembly: MelonInfo(typeof(RenderMod), "ADOFAI Renderer", "0.0.5-BETA", "ADOFAI Editor Forum")]
|
[assembly: MelonInfo(typeof(RenderMod), "ADOFAI Renderer", "0.0.5-BETA", "ADOFAI Editor Forum")]
|
||||||
[assembly: MelonGame("7th Beat Games", "A Dance of Fire and Ice")]
|
[assembly: MelonGame("7th Beat Games", "A Dance of Fire and Ice")]
|
||||||
|
|
||||||
namespace ADOFAIRenderer
|
namespace ADOFAIRenderer
|
||||||
{
|
{
|
||||||
|
|
||||||
public class RenderMod : MelonMod
|
public class RenderMod : MelonMod
|
||||||
{
|
{
|
||||||
static RenderMod()
|
|
||||||
{
|
|
||||||
ThrottleCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AssetBundle FontBundle = null!;
|
public static AssetBundle FontBundle = null!;
|
||||||
// ReSharper disable once MemberCanBePrivate.Global
|
|
||||||
public static AssetBundle SceneBundle = null!;
|
public static AssetBundle SceneBundle = null!;
|
||||||
public static string[] ModScenes = {};
|
public static string[] ModScenes = {};
|
||||||
|
|
||||||
private static double _beforeTime;
|
|
||||||
|
|
||||||
public static bool IsRendering = false;
|
|
||||||
public static bool IsCapturing = false;
|
|
||||||
|
|
||||||
public static double TimeBase;
|
|
||||||
public static double UnscaledTimeBase;
|
|
||||||
public static double DspTimeBase;
|
|
||||||
public static int FrameCountBase;
|
|
||||||
|
|
||||||
public static FrameReader? FrameReader;
|
|
||||||
|
|
||||||
public static bool RenderCheck = true;
|
|
||||||
public static int SkipCount;
|
|
||||||
|
|
||||||
public override void OnInitializeMelon()
|
public override void OnInitializeMelon()
|
||||||
{
|
{
|
||||||
var renderManagerObj = new GameObject("Render Manager");
|
var renderManagerObj = new GameObject("Render Manager");
|
||||||
var updateHookerObj = new GameObject("Update Hooker");
|
var updateHookerObj = new GameObject("Update Hooker");
|
||||||
Object.DontDestroyOnLoad( renderManagerObj );
|
UnityEngine.Object.DontDestroyOnLoad(renderManagerObj);
|
||||||
Object.DontDestroyOnLoad( updateHookerObj );
|
UnityEngine.Object.DontDestroyOnLoad(updateHookerObj);
|
||||||
|
|
||||||
renderManagerObj.AddComponent<RenderManager>();
|
renderManagerObj.AddComponent<RenderManager>();
|
||||||
updateHookerObj.AddComponent<FirstUpdateHook>();
|
updateHookerObj.AddComponent<FirstUpdateHook>();
|
||||||
@@ -71,175 +46,5 @@ namespace ADOFAIRenderer
|
|||||||
var newModule = EventSystem.current.gameObject.AddComponent<StandaloneInputModule>();
|
var newModule = EventSystem.current.gameObject.AddComponent<StandaloneInputModule>();
|
||||||
newModule.enabled = true;
|
newModule.enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void OnFirstUpdate()
|
|
||||||
{
|
|
||||||
if (_beforeTime == 0) _beforeTime = Time.realtimeSinceStartupAsDouble;
|
|
||||||
|
|
||||||
var realDeltaTime = Time.realtimeSinceStartupAsDouble - _beforeTime;
|
|
||||||
_beforeTime = Time.realtimeSinceStartupAsDouble;
|
|
||||||
|
|
||||||
if (!IsRendering) return;
|
|
||||||
|
|
||||||
// [렉으로 인한 프레임 렌더링 스킵 시 재시도 로직 (원본 모드 이식)]
|
|
||||||
if (RenderCheck && IsCapturing)
|
|
||||||
{
|
|
||||||
if (SkipCount < 60)
|
|
||||||
{
|
|
||||||
Melon<RenderMod>.Logger.Warning($"[Render Skipped] Frame {RenderManager.Instance.frameCount} rendering missed. Retrying... (Attempt {SkipCount + 1}/60)");
|
|
||||||
SkipCount++;
|
|
||||||
Time.timeScale = 0f; // 진행 시간 멈추기 (재시도)
|
|
||||||
return; // 업데이트 진행하지 않고 대기
|
|
||||||
}
|
|
||||||
|
|
||||||
// 60회 초과 시 최후의 블랙 프레임 삽입하여 전체 파이프라인 마비 방지
|
|
||||||
Melon<RenderMod>.Logger.Error($"[Render Skipped] Frame {RenderManager.Instance.frameCount} rendering failed after 60 retries. Supplying black frame.");
|
|
||||||
|
|
||||||
var currentFrame = FrameCountForVideo++;
|
|
||||||
var targetFps = RenderManager.Instance.Configuration.TargetFPS;
|
|
||||||
var videoFps = RenderManager.Instance.Configuration.VideoFPS;
|
|
||||||
var effectiveVideoFps = Math.Min(videoFps, targetFps);
|
|
||||||
var videoFrameIndex = (long)(currentFrame * effectiveVideoFps / targetFps);
|
|
||||||
|
|
||||||
if (videoFrameIndex > LastVideoFrameIndex)
|
|
||||||
{
|
|
||||||
LastVideoFrameIndex = videoFrameIndex;
|
|
||||||
LastRequestedVideoFrameIndex = videoFrameIndex;
|
|
||||||
|
|
||||||
var byteSize = RenderManager.Instance.Configuration.RenderWidth * RenderManager.Instance.Configuration.RenderHeight * 4;
|
|
||||||
var blackFrame = System.Buffers.ArrayPool<byte>.Shared.Rent(byteSize);
|
|
||||||
Array.Clear(blackFrame, 0, byteSize);
|
|
||||||
|
|
||||||
lock (RenderManager.Instance.ImageQueue)
|
|
||||||
{
|
|
||||||
RenderManager.Instance.ImageQueue[videoFrameIndex] = blackFrame;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderCheck = false; // 진행 허용
|
|
||||||
}
|
|
||||||
|
|
||||||
SkipCount = 0;
|
|
||||||
RenderCheck = true; // 다음 프레임 검사용으로 초기화
|
|
||||||
RenderManager.Instance.frameCount++;
|
|
||||||
|
|
||||||
if (realDeltaTime > 0)
|
|
||||||
{
|
|
||||||
// Time.timeScale = (float)(1 / RenderManager.Configuration.TargetFPS / realDeltaTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 ThrottleCount;
|
|
||||||
public static bool IsThrottling;
|
|
||||||
|
|
||||||
public static 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 (RenderManager.Instance.ImageQueue)
|
|
||||||
{
|
|
||||||
count = RenderManager.Instance.ImageQueue.Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count <= maxQueuedFrames) break;
|
|
||||||
|
|
||||||
if (!IsThrottling)
|
|
||||||
{
|
|
||||||
IsThrottling = true;
|
|
||||||
ThrottleCount++;
|
|
||||||
}
|
|
||||||
System.Threading.Thread.Sleep(1);
|
|
||||||
}
|
|
||||||
IsThrottling = false;
|
|
||||||
|
|
||||||
var currentFrame = FrameCountForVideo++;
|
|
||||||
|
|
||||||
var targetFps = RenderManager.Instance.Configuration.TargetFPS;
|
|
||||||
var videoFps = RenderManager.Instance.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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
AsyncGPUReadback.Request(texture, 0, delegate(AsyncGPUReadbackRequest req)
|
|
||||||
{
|
|
||||||
if (!IsCapturing) return;
|
|
||||||
|
|
||||||
byte[] rawBytes;
|
|
||||||
if (req.hasError)
|
|
||||||
{
|
|
||||||
ReadbackFailureCount++;
|
|
||||||
Melon<RenderMod>.Logger.Warning($"[OOM Prevent] GPU Readback failed for frame {currentFrame}. Queue deadlock prevented by sending an empty frame.");
|
|
||||||
rawBytes = Array.Empty<byte>(); // 1번 작업: 빈 배열로 대체하여 정체 방지
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var data = req.GetData<byte>();
|
|
||||||
rawBytes = FFmpegProcess.RentRawBytes(data); // 3번 작업: ArrayPool에서 대여
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
ReadbackFailureCount++;
|
|
||||||
Melon<RenderMod>.Logger.Error($"[OOM Prevent] Native conversion failed for frame {currentFrame}: {ex}");
|
|
||||||
rawBytes = Array.Empty<byte>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (RenderManager.ImageQueue)
|
|
||||||
{
|
|
||||||
RenderManager.ImageQueue[currentFrame] = rawBytes;
|
|
||||||
}
|
|
||||||
});*/
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void StartRender()
|
|
||||||
{
|
|
||||||
var targetMethod = typeof(scnEditor).GetMethod("TryApplicationQuit",
|
|
||||||
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
|
|
||||||
|
|
||||||
if (targetMethod == null || scnEditor.instance == null) return;
|
|
||||||
if (ADOBase.levelPath.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
scnEditor.instance.TryQuitToMenu();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (RenderManager.Instance)
|
|
||||||
{
|
|
||||||
RenderManager.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");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,7 +187,7 @@ namespace ADOFAIRenderer.UI
|
|||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
sb.AppendLine("<b><color=#00ccff>ADOFAI Renderer Realtime Monitor</color></b>");
|
sb.AppendLine("<b><color=#00ccff>ADOFAI Renderer Realtime Monitor</color></b>");
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
sb.AppendLine($"• Rendered Progress: <b>{(RenderManager.Instance ? RenderManager.Instance.frameCount : 0)}</b> frames");
|
sb.AppendLine($"• Rendered Progress: <b>{(RenderManager.Instance != null ? RenderManager.Instance.frameCount : 0)}</b> frames");
|
||||||
|
|
||||||
var queueCount = 0;
|
var queueCount = 0;
|
||||||
if (RenderManager.Instance)
|
if (RenderManager.Instance)
|
||||||
@@ -199,13 +199,13 @@ namespace ADOFAIRenderer.UI
|
|||||||
}
|
}
|
||||||
sb.AppendLine($"• Queued Buffers: <b>{queueCount}</b> / 60 frames");
|
sb.AppendLine($"• Queued Buffers: <b>{queueCount}</b> / 60 frames");
|
||||||
|
|
||||||
sb.AppendLine(RenderMod.IsThrottling
|
sb.AppendLine((RenderManager.Instance && RenderManager.Instance.isThrottling)
|
||||||
? "• Status: <b><color=yellow>THROTTLING (Safe Mode)</color></b>"
|
? "• Status: <b><color=yellow>THROTTLING (Safe Mode)</color></b>"
|
||||||
: "• Status: <b><color=green>NORMAL (Encoding)</color></b>");
|
: "• Status: <b><color=green>NORMAL (Encoding)</color></b>");
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
sb.AppendLine($"• Throttling Controls: {RenderMod.ThrottleCount} times");
|
sb.AppendLine($"• Throttling Controls: {(RenderManager.Instance ? RenderManager.Instance.throttleCount : 0)} times");
|
||||||
sb.AppendLine($"• Readback Hard Failures: <color=#ff3333>{RenderMod.ReadbackFailureCount}</color> times");
|
sb.AppendLine($"• Readback Hard Failures: <color=#ff3333>{(RenderManager.Instance ? RenderManager.Instance.readbackFailureCount : 0)}</color> times");
|
||||||
sb.AppendLine($"• Render Skip-Retry: {RenderMod.SkipCount} / 60");
|
sb.AppendLine($"• Render Skip-Retry: {(RenderManager.Instance ? RenderManager.Instance.skipCount : 0)} / 60");
|
||||||
|
|
||||||
var memoryMb = GC.GetTotalMemory(false) / (1024.0 * 1024.0);
|
var memoryMb = GC.GetTotalMemory(false) / (1024.0 * 1024.0);
|
||||||
sb.AppendLine($"• Mono Heap Memory: {memoryMb:F2} MB");
|
sb.AppendLine($"• Mono Heap Memory: {memoryMb:F2} MB");
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ namespace ADOFAIRenderer.UI
|
|||||||
private void Update()
|
private void Update()
|
||||||
{
|
{
|
||||||
// 렌더링이 더 이상 활성화되어 있지 않으면 자가 파괴
|
// 렌더링이 더 이상 활성화되어 있지 않으면 자가 파괴
|
||||||
if (!RenderMod.IsRendering)
|
if (!RenderManager.IsActive)
|
||||||
{
|
{
|
||||||
Destroy(gameObject);
|
Destroy(gameObject);
|
||||||
return;
|
return;
|
||||||
|
|||||||
+1
-1
@@ -51,7 +51,7 @@ namespace ADOFAIRenderer.UI
|
|||||||
public void OnClick()
|
public void OnClick()
|
||||||
{
|
{
|
||||||
if (IsAnyFieldEmpty()) return;
|
if (IsAnyFieldEmpty()) return;
|
||||||
if (RenderMod.IsRendering) return;
|
if (RenderManager.IsActive) return;
|
||||||
|
|
||||||
if (!double.TryParse(targetFPSInput.text, out var targetFPS) || targetFPS <= 0)
|
if (!double.TryParse(targetFPSInput.text, out var targetFPS) || targetFPS <= 0)
|
||||||
{
|
{
|
||||||
|
|||||||
+6
-3
@@ -42,7 +42,7 @@ namespace ADOFAIRenderer.Util
|
|||||||
|
|
||||||
public void RequestFrameCapture(Texture texture, long currentFrame)
|
public void RequestFrameCapture(Texture texture, long currentFrame)
|
||||||
{
|
{
|
||||||
if (!RenderMod.IsCapturing) return;
|
if (!RenderManager.IsCapturingActive) return;
|
||||||
ReadbackRequestState state;
|
ReadbackRequestState state;
|
||||||
|
|
||||||
lock (_poolLock)
|
lock (_poolLock)
|
||||||
@@ -74,7 +74,7 @@ namespace ADOFAIRenderer.Util
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RenderMod.IsRenderCancelled)
|
if (RenderManager.Instance && RenderManager.Instance.isRenderCancelled)
|
||||||
{
|
{
|
||||||
RecycleState(state);
|
RecycleState(state);
|
||||||
return;
|
return;
|
||||||
@@ -82,7 +82,10 @@ namespace ADOFAIRenderer.Util
|
|||||||
|
|
||||||
if (request.hasError)
|
if (request.hasError)
|
||||||
{
|
{
|
||||||
RenderMod.ReadbackFailureCount++;
|
if (RenderManager.Instance)
|
||||||
|
{
|
||||||
|
RenderManager.Instance.readbackFailureCount++;
|
||||||
|
}
|
||||||
Melon<RenderMod>.Logger.Warning($"[OOM Prevent] GPU Readback failed for frame {frame}. Queue deadlock prevented by sending a black frame.");
|
Melon<RenderMod>.Logger.Warning($"[OOM Prevent] GPU Readback failed for frame {frame}. Queue deadlock prevented by sending a black frame.");
|
||||||
|
|
||||||
var byteSize = state.NativeBuffer.Length;
|
var byteSize = state.NativeBuffer.Length;
|
||||||
|
|||||||
Reference in new issue
Block a user