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
+2
-197
@@ -1,52 +1,27 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using ADOFAIRenderer;
|
||||
using ADOFAIRenderer.Component;
|
||||
using ADOFAIRenderer.Util;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
[assembly: MelonInfo(typeof(RenderMod), "ADOFAI Renderer", "0.0.5-BETA", "ADOFAI Editor Forum")]
|
||||
[assembly: MelonGame("7th Beat Games", "A Dance of Fire and Ice")]
|
||||
|
||||
namespace ADOFAIRenderer
|
||||
{
|
||||
|
||||
public class RenderMod : MelonMod
|
||||
{
|
||||
static RenderMod()
|
||||
{
|
||||
ThrottleCount = 0;
|
||||
}
|
||||
|
||||
public static AssetBundle FontBundle = null!;
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
public static AssetBundle SceneBundle = null!;
|
||||
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()
|
||||
{
|
||||
var renderManagerObj = new GameObject("Render Manager");
|
||||
var updateHookerObj = new GameObject("Update Hooker");
|
||||
Object.DontDestroyOnLoad( renderManagerObj );
|
||||
Object.DontDestroyOnLoad( updateHookerObj );
|
||||
UnityEngine.Object.DontDestroyOnLoad(renderManagerObj);
|
||||
UnityEngine.Object.DontDestroyOnLoad(updateHookerObj);
|
||||
|
||||
renderManagerObj.AddComponent<RenderManager>();
|
||||
updateHookerObj.AddComponent<FirstUpdateHook>();
|
||||
@@ -71,175 +46,5 @@ namespace ADOFAIRenderer
|
||||
var newModule = EventSystem.current.gameObject.AddComponent<StandaloneInputModule>();
|
||||
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");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user