SHA256
Optimize Frame Readback and Encoding Pipeline
This commit is contained in:
3 files changed
+58
-111
No files matched your search
@@ -117,6 +117,7 @@ internal class RenderManager : MonoBehaviour
|
|||||||
public int Length;
|
public int Length;
|
||||||
public byte[]? ManagedBuffer; // Non-null if it's a managed buffer (e.g. black frame)
|
public byte[]? ManagedBuffer; // Non-null if it's a managed buffer (e.g. black frame)
|
||||||
public ReadbackRequestState? ReadbackState; // Associated state to recycle
|
public ReadbackRequestState? ReadbackState; // Associated state to recycle
|
||||||
|
public bool IsColorConverted;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thread and Queue State
|
// Thread and Queue State
|
||||||
@@ -546,7 +547,7 @@ internal class RenderManager : MonoBehaviour
|
|||||||
{
|
{
|
||||||
if (frame.ManagedBuffer.Length > 0)
|
if (frame.ManagedBuffer.Length > 0)
|
||||||
{
|
{
|
||||||
videoFFmpeg.SendDataFromBuffer(frame.ManagedBuffer, frame.Length);
|
videoFFmpeg.SendDataFromBuffer(frame.ManagedBuffer, frame.Length, frame.IsColorConverted);
|
||||||
Interlocked.Increment(ref framesSent);
|
Interlocked.Increment(ref framesSent);
|
||||||
|
|
||||||
ArrayPool<byte>.Shared.Return(frame.ManagedBuffer);
|
ArrayPool<byte>.Shared.Return(frame.ManagedBuffer);
|
||||||
@@ -554,7 +555,7 @@ internal class RenderManager : MonoBehaviour
|
|||||||
}
|
}
|
||||||
else if (frame.NativePointer != IntPtr.Zero && frame.Length > 0)
|
else if (frame.NativePointer != IntPtr.Zero && frame.Length > 0)
|
||||||
{
|
{
|
||||||
videoFFmpeg.SendDataFromNativeBuffer(frame.NativePointer, frame.Length);
|
videoFFmpeg.SendDataFromNativeBuffer(frame.NativePointer, frame.Length, frame.IsColorConverted);
|
||||||
Interlocked.Increment(ref framesSent);
|
Interlocked.Increment(ref framesSent);
|
||||||
|
|
||||||
if (frame.ReadbackState != null)
|
if (frame.ReadbackState != null)
|
||||||
|
|||||||
@@ -974,29 +974,22 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
|||||||
/// Encodes a frame from IntPtr (native pointer).
|
/// Encodes a frame from IntPtr (native pointer).
|
||||||
/// Can be used from external native plugins or custom capture sources.
|
/// Can be used from external native plugins or custom capture sources.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SendDataFromNativeBuffer(IntPtr pointer, int length)
|
public void SendDataFromNativeBuffer(IntPtr pointer, int length, bool isColorConverted)
|
||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
if (_disposed || !_isInitialized) return;
|
if (_disposed || !_isInitialized) return;
|
||||||
var preConvertedSize = _settings.Width * _settings.Height * 3 / 2;
|
|
||||||
|
|
||||||
if (!_loggedConversionMode)
|
if (!_loggedConversionMode)
|
||||||
{
|
{
|
||||||
_loggedConversionMode = true;
|
_loggedConversionMode = true;
|
||||||
Melon<RenderMod>.Logger.Msg(length == preConvertedSize
|
Melon<RenderMod>.Logger.Msg(isColorConverted
|
||||||
? "[Encoder] High-speed GPU color conversion enabled (NV12/YUV420P Direct Write, bypassing sws_scale)"
|
? "[Encoder] GPU color conversion enabled (NV12/YUV420P Direct Write, bypassing sws_scale)"
|
||||||
: "[Encoder] CPU color conversion fallback enabled (RGBA Input, using sws_scale)");
|
: "[Encoder] CPU color conversion fallback enabled (RGBA Input, using sws_scale)");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (length == preConvertedSize)
|
if (isColorConverted) WritePreConvertedFrame((byte*)pointer.ToPointer());
|
||||||
{
|
else WriteFrameFromBuffer((byte*)pointer.ToPointer());
|
||||||
WritePreConvertedFrame((byte*)pointer.ToPointer());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
WriteFrameFromBuffer((byte*)pointer.ToPointer());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1004,31 +997,23 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
|||||||
/// Encodes a frame from a managed byte[].
|
/// Encodes a frame from a managed byte[].
|
||||||
/// Pins the array using fixed keyword to prevent GC from moving it before passing the pointer.
|
/// Pins the array using fixed keyword to prevent GC from moving it before passing the pointer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SendDataFromBuffer(byte[] buffer, int length)
|
public void SendDataFromBuffer(byte[] buffer, int length, bool isColorConverted)
|
||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
if (_disposed || !_isInitialized) return;
|
if (_disposed || !_isInitialized) return;
|
||||||
fixed (byte* pBuffer = buffer)
|
fixed (byte* pBuffer = buffer)
|
||||||
{
|
{
|
||||||
var preConvertedSize = _settings.Width * _settings.Height * 3 / 2;
|
|
||||||
|
|
||||||
if (!_loggedConversionMode)
|
if (!_loggedConversionMode)
|
||||||
{
|
{
|
||||||
_loggedConversionMode = true;
|
_loggedConversionMode = true;
|
||||||
Melon<RenderMod>.Logger.Msg(length == preConvertedSize
|
Melon<RenderMod>.Logger.Msg(isColorConverted
|
||||||
? "[Encoder] High-speed GPU color conversion enabled (NV12/YUV420P Direct Write, bypassing sws_scale)"
|
? "[Encoder] High-speed GPU color conversion enabled (NV12/YUV420P Direct Write, bypassing sws_scale)"
|
||||||
: "[Encoder] CPU color conversion fallback enabled (RGBA Input, using sws_scale)");
|
: "[Encoder] CPU color conversion fallback enabled (RGBA Input, using sws_scale)");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (length == preConvertedSize)
|
if (isColorConverted) WritePreConvertedFrame(pBuffer);
|
||||||
{
|
else WriteFrameFromBuffer(pBuffer);
|
||||||
WritePreConvertedFrame(pBuffer);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
WriteFrameFromBuffer(pBuffer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1145,33 +1130,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
|||||||
throw new NotSupportedException($"This pixel format ({_dstPixFmt}) does not support direct copying of pre-converted buffers.");
|
throw new NotSupportedException($"This pixel format ({_dstPixFmt}) does not support direct copying of pre-converted buffers.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// QSV acceleration handling or encoder send
|
SubmitDstFrame();
|
||||||
if (_isQsvEncoder)
|
|
||||||
{
|
|
||||||
ffmpeg.av_frame_unref(_pHwFrame);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var ret = ffmpeg.av_hwframe_get_buffer(_pCodecContext->hw_frames_ctx, _pHwFrame, 0);
|
|
||||||
if (ret < 0)
|
|
||||||
throw new Exception($"Failed to allocate QSV hardware frame buffer — {GetFFmpegErrorString(ret)} (Code: {ret})");
|
|
||||||
|
|
||||||
ret = ffmpeg.av_hwframe_transfer_data(_pHwFrame, _pDstFrame, 0);
|
|
||||||
if (ret < 0)
|
|
||||||
throw new Exception($"Failed to upload QSV frame — {GetFFmpegErrorString(ret)} (Code: {ret})");
|
|
||||||
|
|
||||||
_pHwFrame->pts = _frameIndex++;
|
|
||||||
SendFrameAndWritePackets(_pHwFrame);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
ffmpeg.av_frame_unref(_pHwFrame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_pDstFrame->pts = _frameIndex++;
|
|
||||||
SendFrameAndWritePackets(_pDstFrame);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1209,6 +1168,11 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
|||||||
_pDstFrame->data, _pDstFrame->linesize
|
_pDstFrame->data, _pDstFrame->linesize
|
||||||
);
|
);
|
||||||
|
|
||||||
|
SubmitDstFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SubmitDstFrame()
|
||||||
|
{
|
||||||
if (_isQsvEncoder)
|
if (_isQsvEncoder)
|
||||||
{
|
{
|
||||||
// ── QSV hw_frames_ctx mode: System memory → GPU surface upload ──
|
// ── QSV hw_frames_ctx mode: System memory → GPU surface upload ──
|
||||||
|
|||||||
+40
-58
@@ -71,72 +71,55 @@ namespace ADOFAIRenderer.Util
|
|||||||
state.IsInFlight = true;
|
state.IsInFlight = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if GPU color conversion is enabled
|
// Check if GPU color conversion is available
|
||||||
bool useGPUConversion = ModAssets.ColorConversionShader;
|
var expectedFmt = AVPixelFormat.AV_PIX_FMT_RGBA;
|
||||||
AVPixelFormat expectedFmt = AVPixelFormat.AV_PIX_FMT_RGBA;
|
|
||||||
|
var kernelIndex = -1;
|
||||||
if (useGPUConversion)
|
if (ModAssets.ColorConversionShader)
|
||||||
{
|
{
|
||||||
expectedFmt = _targetFormat;
|
var kernalName = _targetFormat switch
|
||||||
// Compute Shader only supports NV12 and YUV420P; fallback to CPU for other formats
|
|
||||||
if (expectedFmt != AVPixelFormat.AV_PIX_FMT_NV12 && expectedFmt != AVPixelFormat.AV_PIX_FMT_YUV420P)
|
|
||||||
{
|
{
|
||||||
useGPUConversion = false;
|
AVPixelFormat.AV_PIX_FMT_NV12 => "CSMain_NV12",
|
||||||
}
|
AVPixelFormat.AV_PIX_FMT_YUV420P => "CSMain_YUV420P",
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
if (kernalName != null)
|
||||||
|
kernelIndex = ModAssets.ColorConversionShader.FindKernel(kernalName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var useGPUConversion = kernelIndex >= 0;
|
||||||
|
if (useGPUConversion) expectedFmt = _targetFormat;
|
||||||
|
|
||||||
var bufferSize = texture.width * texture.height * 4; // RGBA = 4 channel
|
var bufferSize = texture.width * texture.height * 4; // RGBA = 4 channel
|
||||||
state.Setup(currentFrame, bufferSize, useGPUConversion, expectedFmt, texture.width, texture.height);
|
state.Setup(currentFrame, bufferSize, useGPUConversion, expectedFmt, texture.width, texture.height);
|
||||||
|
|
||||||
if (useGPUConversion && state.GPUBuffer != null && ModAssets.ColorConversionShader)
|
if (useGPUConversion && state.GPUBuffer != null && ModAssets.ColorConversionShader)
|
||||||
{
|
{
|
||||||
int kernelIndex = -1;
|
ModAssets.ColorConversionShader.SetTexture(kernelIndex, SourceTexture, texture);
|
||||||
if (expectedFmt == AVPixelFormat.AV_PIX_FMT_NV12)
|
ModAssets.ColorConversionShader.SetBuffer(kernelIndex, DestinationBuffer, state.GPUBuffer);
|
||||||
{
|
ModAssets.ColorConversionShader.SetInt(Width, texture.width);
|
||||||
kernelIndex = ModAssets.ColorConversionShader.FindKernel("CSMain_NV12");
|
ModAssets.ColorConversionShader.SetInt(Height, texture.height);
|
||||||
}
|
|
||||||
else if (expectedFmt == AVPixelFormat.AV_PIX_FMT_YUV420P)
|
|
||||||
{
|
|
||||||
kernelIndex = ModAssets.ColorConversionShader.FindKernel("CSMain_YUV420P");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (kernelIndex >= 0)
|
var threadGroupsX = expectedFmt == AVPixelFormat.AV_PIX_FMT_NV12
|
||||||
{
|
? (texture.width + 31) / 32
|
||||||
ModAssets.ColorConversionShader.SetTexture(kernelIndex, SourceTexture, texture);
|
: (texture.width + 63) / 64;
|
||||||
ModAssets.ColorConversionShader.SetBuffer(kernelIndex, DestinationBuffer, state.GPUBuffer);
|
var threadGroupsY = (texture.height + 15) / 16;
|
||||||
ModAssets.ColorConversionShader.SetInt(Width, texture.width);
|
|
||||||
ModAssets.ColorConversionShader.SetInt(Height, texture.height);
|
|
||||||
|
|
||||||
var threadGroupsX = expectedFmt == AVPixelFormat.AV_PIX_FMT_NV12
|
ModAssets.ColorConversionShader.Dispatch(kernelIndex, threadGroupsX, threadGroupsY, 1);
|
||||||
? (texture.width + 31) / 32
|
|
||||||
: (texture.width + 63) / 64;
|
|
||||||
var threadGroupsY = (texture.height + 15) / 16;
|
|
||||||
|
|
||||||
ModAssets.ColorConversionShader.Dispatch(kernelIndex, threadGroupsX, threadGroupsY, 1);
|
AsyncGPUReadback.RequestIntoNativeArray(
|
||||||
|
ref state.NativeBuffer, state.GPUBuffer, state.CachedCallback
|
||||||
AsyncGPUReadback.Request(
|
);
|
||||||
state.GPUBuffer,
|
/*AsyncGPUReadback.Request(
|
||||||
state.CachedCallback
|
state.GPUBuffer,
|
||||||
);
|
state.CachedCallback
|
||||||
}
|
);*/
|
||||||
else
|
|
||||||
{
|
|
||||||
// If kernel is not found, fallback to standard RGBA texture readback
|
|
||||||
AsyncGPUReadback.RequestIntoNativeArray(
|
|
||||||
ref state.NativeBuffer,
|
|
||||||
texture,
|
|
||||||
0,
|
|
||||||
state.CachedCallback
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AsyncGPUReadback.RequestIntoNativeArray(
|
AsyncGPUReadback.RequestIntoNativeArray(
|
||||||
ref state.NativeBuffer,
|
ref state.NativeBuffer, texture, 0, state.CachedCallback
|
||||||
texture,
|
|
||||||
0,
|
|
||||||
state.CachedCallback
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,16 +152,14 @@ namespace ADOFAIRenderer.Util
|
|||||||
}
|
}
|
||||||
Melon<RenderMod>.Logger.Warning($"[OOM Prevent] GPU Readback failed for frame {frame}. Queue deadlock prevented by sending a black frame.");
|
Melon<RenderMod>.Logger.Warning($"[OOM Prevent] GPU Readback failed for frame {frame}. Queue deadlock prevented by sending a black frame.");
|
||||||
|
|
||||||
var byteSize = state.IsGPUConversion
|
var byteSize = state.Width * state.Height * 3 / 2;
|
||||||
? state.Width * state.Height * 3 / 2
|
|
||||||
: state.Width * state.Height * 4;
|
|
||||||
var blackFrame = ArrayPool<byte>.Shared.Rent(byteSize);
|
var blackFrame = ArrayPool<byte>.Shared.Rent(byteSize);
|
||||||
Array.Clear(blackFrame, 0, byteSize);
|
Array.Clear(blackFrame, 0, byteSize);
|
||||||
|
|
||||||
var capFrame = new RenderManager.CapturedFrame
|
var capFrame = new RenderManager.CapturedFrame
|
||||||
{
|
{
|
||||||
ManagedBuffer = blackFrame,
|
ManagedBuffer = blackFrame, Length = byteSize,
|
||||||
Length = byteSize
|
IsColorConverted = true
|
||||||
};
|
};
|
||||||
PushToQueue(frame, capFrame);
|
PushToQueue(frame, capFrame);
|
||||||
RecycleState(state);
|
RecycleState(state);
|
||||||
@@ -187,19 +168,20 @@ namespace ADOFAIRenderer.Util
|
|||||||
{
|
{
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
if (state.IsGPUConversion)
|
if (state.IsGPUConversion)
|
||||||
{
|
{
|
||||||
var data = request.GetData<byte>();
|
var data = request.GetData<byte>();
|
||||||
// Copy async request buffer data to persistent NativeBuffer memory
|
// Copy async request buffer data to persistent NativeBuffer memory
|
||||||
NativeArray<byte>.Copy(data, state.NativeBuffer, data.Length);
|
NativeArray<byte>.Copy(data, state.NativeBuffer, data.Length);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
var ptr = (IntPtr)state.NativeBuffer.GetUnsafeReadOnlyPtr();
|
var ptr = (IntPtr)state.NativeBuffer.GetUnsafeReadOnlyPtr();
|
||||||
var capFrame = new RenderManager.CapturedFrame
|
var capFrame = new RenderManager.CapturedFrame
|
||||||
{
|
{
|
||||||
NativePointer = ptr,
|
NativePointer = ptr, Length = state.NativeBuffer.Length,
|
||||||
Length = state.NativeBuffer.Length,
|
ReadbackState = state, IsColorConverted = state.IsGPUConversion
|
||||||
ReadbackState = state
|
|
||||||
};
|
};
|
||||||
PushToQueue(frame, capFrame);
|
PushToQueue(frame, capFrame);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user