SHA256
Add Compute Shader Color Conversion
This commit is contained in:
7 files changed
+369
-32
No files matched your search
@@ -166,6 +166,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
||||
/// 매 프레임마다 할당/해제하는 오버헤드를 방지하기 위해 멤버 변수로 캐싱합니다.
|
||||
/// </summary>
|
||||
private AVFrame* _pHwFrame = null;
|
||||
private bool _loggedConversionMode;
|
||||
|
||||
/// <summary>생성 시 전달받은 인코딩 설정값 (불변).</summary>
|
||||
private readonly EncoderSettings _settings;
|
||||
@@ -303,6 +304,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
||||
// ── 기본 비디오 파라미터 설정 ──
|
||||
_pCodecContext->width = _settings.Width;
|
||||
_pCodecContext->height = _settings.Height;
|
||||
_pCodecContext->color_range = AVColorRange.AVCOL_RANGE_JPEG;
|
||||
// thread_count = 0: FFmpeg이 시스템 CPU 코어 수에 맞춰 자동으로 스레드를 할당합니다.
|
||||
// libvvenc의 경우에도 FFmpeg 래퍼가 thread_count를 VVenC 내부 스레드 수로 전달하므로,
|
||||
// 0(자동)을 설정해야 모든 코어를 활용한 병렬 인코딩이 가능합니다.
|
||||
@@ -665,6 +667,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
||||
_pDstFrame->format = (int)_dstPixFmt;
|
||||
_pDstFrame->width = _settings.Width;
|
||||
_pDstFrame->height = _settings.Height;
|
||||
_pDstFrame->color_range = AVColorRange.AVCOL_RANGE_JPEG;
|
||||
|
||||
if (ffmpeg.av_frame_get_buffer(_pDstFrame, 0) < 0)
|
||||
throw new Exception($"{_dstPixFmt} 프레임 버퍼를 할당하는 데 실패했습니다.");
|
||||
@@ -689,6 +692,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
||||
throw new Exception("소프트웨어 스케일러를 초기화하는 데 실패했습니다.");
|
||||
|
||||
_isInitialized = true;
|
||||
_loggedConversionMode = false;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
@@ -873,7 +877,24 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
||||
lock (_lock)
|
||||
{
|
||||
if (_disposed || !_isInitialized) return;
|
||||
WriteFrameFromBuffer((byte*)pointer.ToPointer());
|
||||
var preConvertedSize = _settings.Width * _settings.Height * 3 / 2;
|
||||
|
||||
if (!_loggedConversionMode)
|
||||
{
|
||||
_loggedConversionMode = true;
|
||||
Melon<RenderMod>.Logger.Msg(length == preConvertedSize
|
||||
? "[인코더] 고속 GPU 컬러 변환 활성화 (NV12/YUV420P Direct Write, sws_scale 우회)"
|
||||
: "[인코더] CPU 컬러 변환 폴백 활성화 (RGBA Input, sws_scale 사용)");
|
||||
}
|
||||
|
||||
if (length == preConvertedSize)
|
||||
{
|
||||
WritePreConvertedFrame((byte*)pointer.ToPointer());
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteFrameFromBuffer((byte*)pointer.ToPointer());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -888,11 +909,169 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
||||
if (_disposed || !_isInitialized) return;
|
||||
fixed (byte* pBuffer = buffer)
|
||||
{
|
||||
WriteFrameFromBuffer(pBuffer);
|
||||
var preConvertedSize = _settings.Width * _settings.Height * 3 / 2;
|
||||
|
||||
if (!_loggedConversionMode)
|
||||
{
|
||||
_loggedConversionMode = true;
|
||||
Melon<RenderMod>.Logger.Msg(length == preConvertedSize
|
||||
? "[인코더] 고속 GPU 컬러 변환 활성화 (NV12/YUV420P Direct Write, sws_scale 우회)"
|
||||
: "[인코더] CPU 컬러 변환 폴백 활성화 (RGBA Input, sws_scale 사용)");
|
||||
}
|
||||
|
||||
if (length == preConvertedSize)
|
||||
{
|
||||
WritePreConvertedFrame(pBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteFrameFromBuffer(pBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이미 NV12 혹은 YUV420P로 변환된 프레임 데이터를 받아서 다이렉트로 복사하여 인코딩합니다.
|
||||
/// </summary>
|
||||
private void WritePreConvertedFrame(byte* buffer)
|
||||
{
|
||||
ffmpeg.av_frame_make_writable(_pDstFrame);
|
||||
|
||||
var width = _settings.Width;
|
||||
var height = _settings.Height;
|
||||
|
||||
switch (_dstPixFmt)
|
||||
{
|
||||
case AVPixelFormat.AV_PIX_FMT_NV12 or AVPixelFormat.AV_PIX_FMT_QSV:
|
||||
{
|
||||
// Y Plane 복사
|
||||
var pSrcY = buffer;
|
||||
var pDstY = _pDstFrame->data[0];
|
||||
var dstStrideY = _pDstFrame->linesize[0];
|
||||
|
||||
if (width == dstStrideY)
|
||||
{
|
||||
Buffer.MemoryCopy(pSrcY, pDstY, dstStrideY * height, width * height);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var y = 0; y < height; y++)
|
||||
{
|
||||
Buffer.MemoryCopy(pSrcY + y * width, pDstY + y * dstStrideY, width, width);
|
||||
}
|
||||
}
|
||||
|
||||
// UV Plane 복사 (Y 평면 크기 이후 위치)
|
||||
var pSrcUV = buffer + width * height;
|
||||
var pDstUV = _pDstFrame->data[1];
|
||||
var dstStrideUV = _pDstFrame->linesize[1];
|
||||
var uvHeight = height / 2;
|
||||
|
||||
if (width == dstStrideUV)
|
||||
{
|
||||
Buffer.MemoryCopy(pSrcUV, pDstUV, dstStrideUV * uvHeight, width * uvHeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var y = 0; y < uvHeight; y++)
|
||||
{
|
||||
Buffer.MemoryCopy(pSrcUV + y * width, pDstUV + y * dstStrideUV, width, width);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case AVPixelFormat.AV_PIX_FMT_YUV420P:
|
||||
{
|
||||
// Y Plane 복사
|
||||
var pSrcY = buffer;
|
||||
var pDstY = _pDstFrame->data[0];
|
||||
var dstStrideY = _pDstFrame->linesize[0];
|
||||
|
||||
if (width == dstStrideY)
|
||||
{
|
||||
Buffer.MemoryCopy(pSrcY, pDstY, dstStrideY * height, width * height);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var y = 0; y < height; y++)
|
||||
{
|
||||
Buffer.MemoryCopy(pSrcY + y * width, pDstY + y * dstStrideY, width, width);
|
||||
}
|
||||
}
|
||||
|
||||
// U Plane 복사
|
||||
var pSrcU = buffer + width * height;
|
||||
var pDstU = _pDstFrame->data[1];
|
||||
var dstStrideU = _pDstFrame->linesize[1];
|
||||
var chromaWidth = width / 2;
|
||||
var chromaHeight = height / 2;
|
||||
|
||||
if (chromaWidth == dstStrideU)
|
||||
{
|
||||
Buffer.MemoryCopy(pSrcU, pDstU, dstStrideU * chromaHeight, chromaWidth * chromaHeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var y = 0; y < chromaHeight; y++)
|
||||
{
|
||||
Buffer.MemoryCopy(pSrcU + y * chromaWidth, pDstU + y * dstStrideU, chromaWidth, chromaWidth);
|
||||
}
|
||||
}
|
||||
|
||||
// V Plane 복사
|
||||
var pSrcV = pSrcU + chromaWidth * chromaHeight;
|
||||
var pDstV = _pDstFrame->data[2];
|
||||
var dstStrideV = _pDstFrame->linesize[2];
|
||||
|
||||
if (chromaWidth == dstStrideV)
|
||||
{
|
||||
Buffer.MemoryCopy(pSrcV, pDstV, dstStrideV * chromaHeight, chromaWidth * chromaHeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var y = 0; y < chromaHeight; y++)
|
||||
{
|
||||
Buffer.MemoryCopy(pSrcV + y * chromaWidth, pDstV + y * dstStrideV, chromaWidth, chromaWidth);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new NotSupportedException($"이 픽셀 포맷({_dstPixFmt})은 사전 변환된 버퍼 다이렉트 복사를 지원하지 않습니다.");
|
||||
}
|
||||
|
||||
// QSV 가속 처리 또는 인코더 전송
|
||||
if (_isQsvEncoder)
|
||||
{
|
||||
ffmpeg.av_frame_unref(_pHwFrame);
|
||||
try
|
||||
{
|
||||
int ret = ffmpeg.av_hwframe_get_buffer(_pCodecContext->hw_frames_ctx, _pHwFrame, 0);
|
||||
if (ret < 0)
|
||||
throw new Exception($"QSV 하드웨어 프레임 버퍼 할당 실패 — {GetFFmpegErrorString(ret)} (Code: {ret})");
|
||||
|
||||
ret = ffmpeg.av_hwframe_transfer_data(_pHwFrame, _pDstFrame, 0);
|
||||
if (ret < 0)
|
||||
throw new Exception($"QSV 프레임 업로드 실패 — {GetFFmpegErrorString(ret)} (Code: {ret})");
|
||||
|
||||
_pHwFrame->pts = _frameIndex++;
|
||||
SendFrameAndWritePackets(_pHwFrame);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ffmpeg.av_frame_unref(_pHwFrame);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_pDstFrame->pts = _frameIndex++;
|
||||
SendFrameAndWritePackets(_pDstFrame);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 실제 프레임 인코딩의 핵심 로직.
|
||||
///
|
||||
@@ -1008,7 +1187,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
||||
private void SendFrameAndWritePackets(AVFrame* pFrame)
|
||||
{
|
||||
// 인코더 내부 큐에 프레임 전달
|
||||
int response = ffmpeg.avcodec_send_frame(_pCodecContext, pFrame);
|
||||
var response = ffmpeg.avcodec_send_frame(_pCodecContext, pFrame);
|
||||
if (response < 0)
|
||||
{
|
||||
throw new Exception($"인코더에 프레임을 전송하는 도중 에러가 발생했습니다. (Code: {response})");
|
||||
@@ -1027,7 +1206,8 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (response < 0)
|
||||
|
||||
if (response < 0)
|
||||
{
|
||||
throw new Exception($"인코더에서 패킷을 수신하는 도중 에러가 발생했습니다. (Code: {response})");
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user