SHA256
Add Custom Frame Modification Hook API for Video Rendering
This commit is contained in:
5 files changed
+961
-1
No files matched your search
+71
-1
@@ -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();
|
||||
|
||||
|
||||
Reference in new issue
Block a user