SHA256
190 lines
5.6 KiB
C#
190 lines
5.6 KiB
C#
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();
|
|
}
|
|
}
|