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
@@ -0,0 +1,189 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ADOFAIRenderer.API;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ADOFAIRenderer.Core;
|
||||
|
||||
internal class FrameHookPipeline : IDisposable
|
||||
{
|
||||
private RenderTexture? _pingRT;
|
||||
private RenderTexture? _pongRT;
|
||||
private RenderTexture? _lastOutputRT;
|
||||
private FrameHookContext? _reusableContext;
|
||||
private int _width;
|
||||
private int _height;
|
||||
private RenderTextureFormat _format;
|
||||
|
||||
public RenderTexture? Process(
|
||||
RenderTexture sourceTexture,
|
||||
long frameIndex,
|
||||
long renderFrameIndex,
|
||||
double videoTime,
|
||||
double renderFps,
|
||||
double videoFps,
|
||||
IReadOnlyList<Action<FrameHookContext>> hooks)
|
||||
{
|
||||
if (hooks.Count == 0 || !sourceTexture || !sourceTexture.IsCreated())
|
||||
return sourceTexture;
|
||||
|
||||
EnsureBuffers(sourceTexture);
|
||||
|
||||
if (!_pingRT || !_pongRT || !_pingRT.IsCreated() || !_pongRT.IsCreated())
|
||||
return sourceTexture;
|
||||
|
||||
var prevActive = RenderTexture.active;
|
||||
try
|
||||
{
|
||||
// Inter-frame ping-pong safety: start with the buffer that was NOT used as the output
|
||||
// of the previous frame, ensuring the buffer currently being read back is not immediately overwritten.
|
||||
var firstDestination = _lastOutputRT == _pingRT ? _pongRT : _pingRT;
|
||||
|
||||
if (_reusableContext == null)
|
||||
{
|
||||
_reusableContext = new FrameHookContext(
|
||||
sourceTexture,
|
||||
_pingRT,
|
||||
_pongRT,
|
||||
firstDestination,
|
||||
frameIndex,
|
||||
renderFrameIndex,
|
||||
videoTime,
|
||||
renderFps,
|
||||
videoFps
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
_reusableContext.Reset(
|
||||
sourceTexture,
|
||||
_pingRT,
|
||||
_pongRT,
|
||||
firstDestination,
|
||||
frameIndex,
|
||||
renderFrameIndex,
|
||||
videoTime,
|
||||
renderFps,
|
||||
videoFps
|
||||
);
|
||||
}
|
||||
|
||||
for (var i = 0; i < hooks.Count; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
hooks[i](_reusableContext);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Melon<RenderMod>.Logger.Error($"[FrameHook] Error executing frame hook #{i} on frame {frameIndex}: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
var result = _reusableContext.Current;
|
||||
_lastOutputRT = _reusableContext.HasModified ? result : null;
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
RenderTexture.active = prevActive;
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureBuffers(RenderTexture sourceTexture)
|
||||
{
|
||||
var width = sourceTexture.width;
|
||||
var height = sourceTexture.height;
|
||||
var format = sourceTexture.format;
|
||||
|
||||
if (_pingRT && _pongRT &&
|
||||
_width == width && _height == height && _format == format &&
|
||||
_pingRT.IsCreated() && _pongRT.IsCreated())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ReleaseBuffers();
|
||||
|
||||
_width = width;
|
||||
_height = height;
|
||||
_format = format;
|
||||
|
||||
try
|
||||
{
|
||||
var desc = sourceTexture.descriptor;
|
||||
desc.depthBufferBits = 0;
|
||||
desc.msaaSamples = 1;
|
||||
desc.useMipMap = false;
|
||||
desc.autoGenerateMips = false;
|
||||
|
||||
var supportsUav = SystemInfo.supportsComputeShaders &&
|
||||
SystemInfo.SupportsRandomWriteOnRenderTextureFormat(desc.colorFormat);
|
||||
desc.enableRandomWrite = supportsUav;
|
||||
|
||||
_pingRT = new RenderTexture(desc)
|
||||
{
|
||||
name = "ADOFAIRenderer_PingBuffer",
|
||||
filterMode = sourceTexture.filterMode,
|
||||
wrapMode = sourceTexture.wrapMode
|
||||
};
|
||||
_pingRT.Create();
|
||||
|
||||
_pongRT = new RenderTexture(desc)
|
||||
{
|
||||
name = "ADOFAIRenderer_PongBuffer",
|
||||
filterMode = sourceTexture.filterMode,
|
||||
wrapMode = sourceTexture.wrapMode
|
||||
};
|
||||
_pongRT.Create();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ReleaseBuffers();
|
||||
Melon<RenderMod>.Logger.Warning($"[FrameHook] Failed to create descriptor-based PingPong RTs: {ex.Message}. Falling back to standard ARGB32.");
|
||||
_pingRT = new RenderTexture(width, height, 0, RenderTextureFormat.ARGB32)
|
||||
{
|
||||
name = "ADOFAIRenderer_PingBuffer",
|
||||
filterMode = sourceTexture.filterMode,
|
||||
wrapMode = sourceTexture.wrapMode
|
||||
};
|
||||
_pingRT.Create();
|
||||
|
||||
_pongRT = new RenderTexture(width, height, 0, RenderTextureFormat.ARGB32)
|
||||
{
|
||||
name = "ADOFAIRenderer_PongBuffer",
|
||||
filterMode = sourceTexture.filterMode,
|
||||
wrapMode = sourceTexture.wrapMode
|
||||
};
|
||||
_pongRT.Create();
|
||||
}
|
||||
}
|
||||
|
||||
public void ReleaseBuffers()
|
||||
{
|
||||
if (_pingRT)
|
||||
{
|
||||
if (_pingRT.IsCreated()) _pingRT.Release();
|
||||
UnityEngine.Object.Destroy(_pingRT);
|
||||
_pingRT = null;
|
||||
}
|
||||
|
||||
if (_pongRT)
|
||||
{
|
||||
if (_pongRT.IsCreated()) _pongRT.Release();
|
||||
UnityEngine.Object.Destroy(_pongRT);
|
||||
_pongRT = null;
|
||||
}
|
||||
|
||||
_width = 0;
|
||||
_height = 0;
|
||||
_lastOutputRT = null;
|
||||
_reusableContext = null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ReleaseBuffers();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user