Add Custom Frame Modification Hook API for Video Rendering

This commit is contained in:
copecone committed 2026-10-09 20:31:23 +09:00
1 parent 5d805b85ad
commit 34d766cb4d
5 files changed
+961 -1

No files matched your search

+71 -1
View File
@@ -37,6 +37,7 @@ internal static class RenderContext
public static string? OutputPath;
public static bool Notification = true;
public static readonly List<Action<FrameHookContext>> SessionFrameHooks = new();
private static readonly Func<bool> EndConditionDefault = () => scrController.instance.state == States.Won;
public static Func<bool> EndCondition = EndConditionDefault;
@@ -64,6 +65,7 @@ internal static class RenderContext
OutputPath = null;
Notification = true;
SessionFrameHooks.Clear();
EndCondition = EndConditionDefault;
OnComplete = OnCompleteDefault;
@@ -103,6 +105,49 @@ internal class RenderManager : MonoBehaviour
private AudioSyncCalculator SyncCalculator { get; set; } = null!;
private FrameReader? _frameReader;
private FrameHookPipeline? _frameHookPipeline;
private readonly List<Action<FrameHookContext>> _activeHooksCache = new();
private int _cachedGlobalHooksVersion = -1;
private bool _hasActiveHooksCached;
public bool HasActiveFrameHooks
{
get
{
EnsureActiveHooksUpToDate();
return _hasActiveHooksCached;
}
}
private void EnsureActiveHooksUpToDate()
{
var globalVersion = RenderSession.GlobalHooksVersion;
if (_cachedGlobalHooksVersion == globalVersion) return;
_cachedGlobalHooksVersion = globalVersion;
_activeHooksCache.Clear();
if (RenderContext.SessionFrameHooks.Count > 0)
{
_activeHooksCache.AddRange(RenderContext.SessionFrameHooks);
}
RenderSession.CopyGlobalFrameHooksTo(_activeHooksCache);
_hasActiveHooksCached = _activeHooksCache.Count > 0;
}
private List<Action<FrameHookContext>> GetActiveFrameHooks()
{
EnsureActiveHooksUpToDate();
return _activeHooksCache;
}
private void ResetFrameHookState()
{
_frameHookPipeline?.Dispose();
_frameHookPipeline = null;
_activeHooksCache.Clear();
_cachedGlobalHooksVersion = -1;
_hasActiveHooksCached = false;
}
public long frameCountForVideo;
public long lastVideoFrameIndex = -1;
@@ -160,6 +205,8 @@ internal class RenderManager : MonoBehaviour
AudioSpectrumGenerator.Clear();
ResetFrameHookState();
if (_activeVideoFFmpeg != null)
{
Melon<RenderMod>.Logger.Msg("Game exit or RenderManager destruction detected: Disposing FFmpeg native encoder resources.");
@@ -316,7 +363,23 @@ internal class RenderManager : MonoBehaviour
{
lastVideoFrameIndex = videoFrameIndex;
lastRequestedVideoFrameIndex = videoFrameIndex;
_frameReader?.RequestFrameCapture(texture, videoFrameIndex);
var targetTexture = texture;
if (HasActiveFrameHooks && _frameHookPipeline != null)
{
var videoTime = effectiveVideoFps > 0 ? videoFrameIndex / effectiveVideoFps : 0.0;
targetTexture = _frameHookPipeline.Process(
texture,
videoFrameIndex,
currentFrame,
videoTime,
renderFPS,
effectiveVideoFps,
GetActiveFrameHooks()
) ?? texture;
}
_frameReader?.RequestFrameCapture(targetTexture, videoFrameIndex);
}
}
@@ -387,6 +450,10 @@ internal class RenderManager : MonoBehaviour
_frameReader?.Dispose();
_frameReader = null;
_frameHookPipeline?.Dispose();
_frameHookPipeline = new FrameHookPipeline();
_cachedGlobalHooksVersion = -1;
EnsureActiveHooksUpToDate();
try
{
StartFFmpegThread();
@@ -395,6 +462,7 @@ internal class RenderManager : MonoBehaviour
catch (Exception ex)
{
Melon<RenderMod>.Logger.Error($"Encoder initialization error: {ex}");
ResetFrameHookState();
CommonWarningDialogUI.ShowDialog(
"Encoder Error",
$"Failed to initialize video encoder.\nError: {ex.Message}",
@@ -687,6 +755,7 @@ internal class RenderManager : MonoBehaviour
}
_frameReader?.Dispose();
ResetFrameHookState();
// Clean up Debug Monitor UI
if (_debugMonitorObj)
@@ -776,6 +845,7 @@ internal class RenderManager : MonoBehaviour
}
_frameReader?.Dispose();
ResetFrameHookState();
AudioMixer?.Reset(); AudioMixer = null;
SyncCalculator.Reset();