SHA256
Fix FFmpegNativeDecoder to Consider Color Range When Decoding Video
This commit is contained in:
3 files changed
+420
-10
No files matched your search
@@ -0,0 +1,73 @@
|
|||||||
|
using System.IO;
|
||||||
|
using ADOFAIRenderer.Core;
|
||||||
|
using ADOFAIRenderer.Util;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
|
namespace ADOFAIRenderer.Patch.Game
|
||||||
|
{
|
||||||
|
[HarmonyPatch(typeof(scnGame))]
|
||||||
|
// ReSharper disable once InconsistentNaming
|
||||||
|
public static class scnGamePatch
|
||||||
|
{
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch("UpdateVideo")]
|
||||||
|
public static bool Prefix(scnGame __instance)
|
||||||
|
{
|
||||||
|
// Only replace when rendering is active
|
||||||
|
if (!RenderManager.IsActive)
|
||||||
|
{
|
||||||
|
// Remove FFmpeg player if it was attached previously
|
||||||
|
var oldPlayer = __instance.videoBG.gameObject.GetComponent<FFmpegVideoBGPlayer>();
|
||||||
|
if (oldPlayer != null)
|
||||||
|
{
|
||||||
|
UnityEngine.Object.Destroy(oldPlayer);
|
||||||
|
}
|
||||||
|
// Re-enable native player component
|
||||||
|
__instance.videoBG.enabled = true;
|
||||||
|
return true; // Execute original
|
||||||
|
}
|
||||||
|
|
||||||
|
string text = __instance.levelData.miscSettings.GetString("bgVideo");
|
||||||
|
if (string.IsNullOrEmpty(text))
|
||||||
|
{
|
||||||
|
__instance.videoBG.gameObject.SetActive(false);
|
||||||
|
return false; // Skip original
|
||||||
|
}
|
||||||
|
|
||||||
|
__instance.videoBG.gameObject.SetActive(true);
|
||||||
|
|
||||||
|
// We can only play external video files via FFmpegNativeDecoder.
|
||||||
|
// Internal/Bundle level background videos are built-in VideoClips, so we fallback to native Unity VideoPlayer.
|
||||||
|
if (ADOBase.isInternalLevel || ADOBase.isBundleLevel)
|
||||||
|
{
|
||||||
|
__instance.videoBG.enabled = true;
|
||||||
|
return true; // Execute original
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom level video path
|
||||||
|
string videoPath = Path.Combine(Path.GetDirectoryName(ADOBase.levelPath), text);
|
||||||
|
if (!File.Exists(videoPath))
|
||||||
|
{
|
||||||
|
return true; // Let the original print error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable native VideoPlayer component (to prevent it from attempting real-time playback and sync errors)
|
||||||
|
__instance.videoBG.enabled = false;
|
||||||
|
|
||||||
|
// Add or retrieve our custom FFmpeg video background player
|
||||||
|
var player = __instance.videoBG.gameObject.GetComponent<FFmpegVideoBGPlayer>();
|
||||||
|
if (player == null)
|
||||||
|
{
|
||||||
|
player = __instance.videoBG.gameObject.AddComponent<FFmpegVideoBGPlayer>();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isLooping = __instance.levelData.miscSettings.GetBool("loopVideo");
|
||||||
|
|
||||||
|
// In custom levels, ADOFAI's videoBG targetTexture is pre-allocated by the game to render background filters.
|
||||||
|
// We initialize the player with the target RenderTexture.
|
||||||
|
player.Init(videoPath, __instance.videoBG.targetTexture, isLooping);
|
||||||
|
|
||||||
|
return false; // Skip original UpdateVideo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -60,6 +60,9 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
|||||||
/// <summary>비디오 원본 코덱의 픽셀 포맷.</summary>
|
/// <summary>비디오 원본 코덱의 픽셀 포맷.</summary>
|
||||||
public AVPixelFormat PixelFormat => _pCodecContext != null ? _pCodecContext->pix_fmt : AVPixelFormat.AV_PIX_FMT_NONE;
|
public AVPixelFormat PixelFormat => _pCodecContext != null ? _pCodecContext->pix_fmt : AVPixelFormat.AV_PIX_FMT_NONE;
|
||||||
|
|
||||||
|
/// <summary>비디오 원본 코덱의 색상 범위.</summary>
|
||||||
|
public AVColorRange ColorRange => _pCodecContext != null ? _pCodecContext->color_range : AVColorRange.AVCOL_RANGE_UNSPECIFIED;
|
||||||
|
|
||||||
public FFmpegNativeDecoder(string videoPath)
|
public FFmpegNativeDecoder(string videoPath)
|
||||||
{
|
{
|
||||||
Open(videoPath);
|
Open(videoPath);
|
||||||
@@ -255,7 +258,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
|||||||
/// <param name="vflip">Unity 텍스처 시스템에 맞게 상하 flip을 적용할지 여부</param>
|
/// <param name="vflip">Unity 텍스처 시스템에 맞게 상하 flip을 적용할지 여부</param>
|
||||||
/// <param name="timestamp">출력된 프레임의 타임스탬프 (초)</param>
|
/// <param name="timestamp">출력된 프레임의 타임스탬프 (초)</param>
|
||||||
/// <returns>프레임을 성공적으로 디코딩하여 복사했으면 true, 재생이 끝났거나 실패했으면 false</returns>
|
/// <returns>프레임을 성공적으로 디코딩하여 복사했으면 true, 재생이 끝났거나 실패했으면 false</returns>
|
||||||
public bool TryReadFrame(byte* outBuffer, int outStride, bool vflip, out double timestamp)
|
private bool TryReadFrame(byte* outBuffer, int outStride, bool vflip, out double timestamp)
|
||||||
{
|
{
|
||||||
timestamp = 0;
|
timestamp = 0;
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
@@ -362,7 +365,8 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
|||||||
/// 비디오 원본이 YUV420p 계열(YUV420P, YUVJ420P)이면 CPU 컬러 변환(sws_scale)을 우회하고 메모리를 다이렉트로 복사하여 속도가 매우 빠릅니다.
|
/// 비디오 원본이 YUV420p 계열(YUV420P, YUVJ420P)이면 CPU 컬러 변환(sws_scale)을 우회하고 메모리를 다이렉트로 복사하여 속도가 매우 빠릅니다.
|
||||||
/// 그 외 포맷일 경우 자동으로 YUV420p로 변환 후 복사합니다.
|
/// 그 외 포맷일 경우 자동으로 YUV420p로 변환 후 복사합니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool TryReadYUV420PFrame(
|
// ReSharper disable once InconsistentNaming
|
||||||
|
private bool TryReadYUV420PFrame(
|
||||||
byte* yBuffer, int yStride,
|
byte* yBuffer, int yStride,
|
||||||
byte* uBuffer, int uStride,
|
byte* uBuffer, int uStride,
|
||||||
byte* vBuffer, int vStride,
|
byte* vBuffer, int vStride,
|
||||||
@@ -373,7 +377,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
|||||||
{
|
{
|
||||||
if (!_isOpened || _disposed) return false;
|
if (!_isOpened || _disposed) return false;
|
||||||
|
|
||||||
AVFrame* frame = null;
|
AVFrame* frame;
|
||||||
|
|
||||||
if (_hasCachedFrame)
|
if (_hasCachedFrame)
|
||||||
{
|
{
|
||||||
@@ -477,6 +481,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once InconsistentNaming
|
||||||
public bool TryReadYUV420PFrame(
|
public bool TryReadYUV420PFrame(
|
||||||
NativeArray<byte> yBuffer,
|
NativeArray<byte> yBuffer,
|
||||||
NativeArray<byte> uBuffer,
|
NativeArray<byte> uBuffer,
|
||||||
@@ -506,7 +511,8 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
|||||||
/// 비디오 원본이 NV12 계열이면 CPU 컬러 변환(sws_scale)을 우회하고 메모리를 다이렉트로 복사하여 속도가 매우 빠릅니다.
|
/// 비디오 원본이 NV12 계열이면 CPU 컬러 변환(sws_scale)을 우회하고 메모리를 다이렉트로 복사하여 속도가 매우 빠릅니다.
|
||||||
/// 그 외 포맷일 경우 자동으로 NV12로 변환 후 복사합니다.
|
/// 그 외 포맷일 경우 자동으로 NV12로 변환 후 복사합니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool TryReadNV12Frame(
|
// ReSharper disable once InconsistentNaming
|
||||||
|
private bool TryReadNV12Frame(
|
||||||
byte* yBuffer, int yStride,
|
byte* yBuffer, int yStride,
|
||||||
byte* uvBuffer, int uvStride,
|
byte* uvBuffer, int uvStride,
|
||||||
out double timestamp)
|
out double timestamp)
|
||||||
@@ -516,7 +522,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
|||||||
{
|
{
|
||||||
if (!_isOpened || _disposed) return false;
|
if (!_isOpened || _disposed) return false;
|
||||||
|
|
||||||
AVFrame* frame = null;
|
AVFrame* frame;
|
||||||
|
|
||||||
if (_hasCachedFrame)
|
if (_hasCachedFrame)
|
||||||
{
|
{
|
||||||
@@ -607,6 +613,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once InconsistentNaming
|
||||||
public bool TryReadNV12Frame(
|
public bool TryReadNV12Frame(
|
||||||
NativeArray<byte> yBuffer,
|
NativeArray<byte> yBuffer,
|
||||||
NativeArray<byte> uvBuffer,
|
NativeArray<byte> uvBuffer,
|
||||||
@@ -633,12 +640,12 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="count">버릴 프레임 개수</param>
|
/// <param name="count">버릴 프레임 개수</param>
|
||||||
/// <returns>실제로 건너뛴 프레임 개수</returns>
|
/// <returns>실제로 건너뛴 프레임 개수</returns>
|
||||||
public int SkipFrames(int count)
|
private void SkipFrames(int count)
|
||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
if (!_isOpened || _disposed) return 0;
|
if (!_isOpened || _disposed) return;
|
||||||
if (count <= 0) return 0;
|
if (count <= 0) return;
|
||||||
|
|
||||||
int skipped = 0;
|
int skipped = 0;
|
||||||
if (_hasCachedFrame)
|
if (_hasCachedFrame)
|
||||||
@@ -678,7 +685,6 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
|||||||
throw new Exception($"프레임 건너뛰기 중 오류 발생: {GetFFmpegErrorString(ret)}");
|
throw new Exception($"프레임 건너뛰기 중 오류 발생: {GetFFmpegErrorString(ret)}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return skipped;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -696,7 +702,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
|||||||
double targetFrameIndex = timeSeconds * FPS;
|
double targetFrameIndex = timeSeconds * FPS;
|
||||||
|
|
||||||
// 이미 해당 인덱스이고 매우 인접한 미래라면 seek 대신 skip 처리 (I-프레임 찾기 오버헤드 방지)
|
// 이미 해당 인덱스이고 매우 인접한 미래라면 seek 대신 skip 처리 (I-프레임 찾기 오버헤드 방지)
|
||||||
bool useSkip = targetFrameIndex >= _currentFrameIndex && (targetFrameIndex - _currentFrameIndex) < 60;
|
bool useSkip = targetFrameIndex >= _currentFrameIndex && targetFrameIndex - _currentFrameIndex < 60;
|
||||||
if (useSkip)
|
if (useSkip)
|
||||||
{
|
{
|
||||||
SkipFrames((int)(targetFrameIndex - _currentFrameIndex));
|
SkipFrames((int)(targetFrameIndex - _currentFrameIndex));
|
||||||
|
|||||||
@@ -0,0 +1,331 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using ADOFAIRenderer.Util.FFmpeg;
|
||||||
|
using MelonLoader;
|
||||||
|
using UnityEngine;
|
||||||
|
using FFmpeg.AutoGen;
|
||||||
|
|
||||||
|
namespace ADOFAIRenderer.Util
|
||||||
|
{
|
||||||
|
// ReSharper disable once InconsistentNaming
|
||||||
|
public class FFmpegVideoBGPlayer : MonoBehaviour
|
||||||
|
{
|
||||||
|
private enum GPUMode
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
YUV420P,
|
||||||
|
NV12
|
||||||
|
}
|
||||||
|
|
||||||
|
private FFmpegNativeDecoder? _decoder;
|
||||||
|
|
||||||
|
// CPU fallback rendering
|
||||||
|
private Texture2D? _textureRGBA;
|
||||||
|
|
||||||
|
// GPU conversion textures
|
||||||
|
private Texture2D? _textureY;
|
||||||
|
private Texture2D? _textureU;
|
||||||
|
private Texture2D? _textureV;
|
||||||
|
private Texture2D? _textureUV;
|
||||||
|
|
||||||
|
// Compute Shader variables
|
||||||
|
private ComputeShader? _computeShader;
|
||||||
|
private RenderTexture? _renderTexture;
|
||||||
|
private int _kernelIndex = -1;
|
||||||
|
|
||||||
|
private double _currentFrameTimestamp;
|
||||||
|
private double _nextFrameTimestamp;
|
||||||
|
private GPUMode _gpuMode = GPUMode.None;
|
||||||
|
private RenderTexture? _targetTexture;
|
||||||
|
private bool _isLooping;
|
||||||
|
private bool _isInitialized;
|
||||||
|
|
||||||
|
public void Init(string videoPath, RenderTexture targetTexture, bool loop)
|
||||||
|
{
|
||||||
|
CleanUp();
|
||||||
|
|
||||||
|
_targetTexture = targetTexture;
|
||||||
|
_isLooping = loop;
|
||||||
|
|
||||||
|
if (!File.Exists(videoPath))
|
||||||
|
{
|
||||||
|
Melon<RenderMod>.Logger.Warning($"[FFmpegVideoBGPlayer] 비디오 파일이 존재하지 않습니다: {videoPath}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_decoder = new FFmpegNativeDecoder(videoPath);
|
||||||
|
_computeShader = RenderMod.ColorConversionShader;
|
||||||
|
|
||||||
|
int w = _decoder.Width;
|
||||||
|
int h = _decoder.Height;
|
||||||
|
AVPixelFormat pixFmt = _decoder.PixelFormat;
|
||||||
|
|
||||||
|
bool gpuInitSuccess = false;
|
||||||
|
|
||||||
|
if (_computeShader != null)
|
||||||
|
{
|
||||||
|
if (pixFmt == AVPixelFormat.AV_PIX_FMT_NV12)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_kernelIndex = _computeShader.FindKernel("CSMain_NV12ToRGBA");
|
||||||
|
|
||||||
|
_textureY = new Texture2D(w, h, TextureFormat.R8, false);
|
||||||
|
_textureUV = new Texture2D(w / 2, h / 2, TextureFormat.RG16, false);
|
||||||
|
ConfigureTexture(_textureY);
|
||||||
|
ConfigureTexture(_textureUV);
|
||||||
|
|
||||||
|
_gpuMode = GPUMode.NV12;
|
||||||
|
gpuInitSuccess = true;
|
||||||
|
Melon<RenderMod>.Logger.Msg($"[FFmpegVideoBGPlayer] Compute Shader NV12 모드 초기화 완료");
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
else if (pixFmt == AVPixelFormat.AV_PIX_FMT_YUV420P || pixFmt == AVPixelFormat.AV_PIX_FMT_YUVJ420P)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_kernelIndex = _computeShader.FindKernel("CSMain_YUV420PToRGBA");
|
||||||
|
|
||||||
|
_textureY = new Texture2D(w, h, TextureFormat.R8, false);
|
||||||
|
_textureU = new Texture2D(w / 2, h / 2, TextureFormat.R8, false);
|
||||||
|
_textureV = new Texture2D(w / 2, h / 2, TextureFormat.R8, false);
|
||||||
|
ConfigureTexture(_textureY);
|
||||||
|
ConfigureTexture(_textureU);
|
||||||
|
ConfigureTexture(_textureV);
|
||||||
|
|
||||||
|
_gpuMode = GPUMode.YUV420P;
|
||||||
|
gpuInitSuccess = true;
|
||||||
|
Melon<RenderMod>.Logger.Msg($"[FFmpegVideoBGPlayer] Compute Shader YUV420P 모드 초기화 완료");
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gpuInitSuccess && _computeShader != null)
|
||||||
|
{
|
||||||
|
_renderTexture = new RenderTexture(w, h, 0, RenderTextureFormat.ARGB32);
|
||||||
|
_renderTexture.enableRandomWrite = true;
|
||||||
|
_renderTexture.Create();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_textureRGBA = new Texture2D(w, h, TextureFormat.RGBA32, false);
|
||||||
|
ConfigureTexture(_textureRGBA);
|
||||||
|
_gpuMode = GPUMode.None;
|
||||||
|
Melon<RenderMod>.Logger.Warning($"[FFmpegVideoBGPlayer] GPU 가속 미지원 또는 로드 실패로 CPU 폴백(RGBA)으로 진행합니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentFrameTimestamp = 0.0;
|
||||||
|
_nextFrameTimestamp = 0.0;
|
||||||
|
_isInitialized = true;
|
||||||
|
|
||||||
|
Melon<RenderMod>.Logger.Msg($"[FFmpegVideoBGPlayer] 초기화 성공 - 해상도: {w}x{h}, FPS: {_decoder.FPS:F2}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Melon<RenderMod>.Logger.Error($"[FFmpegVideoBGPlayer] 초기화 중 에러 발생: {ex}");
|
||||||
|
CleanUp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfigureTexture(Texture2D tex)
|
||||||
|
{
|
||||||
|
tex.wrapMode = TextureWrapMode.Clamp;
|
||||||
|
tex.filterMode = FilterMode.Bilinear;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (!_isInitialized || _decoder == null) return;
|
||||||
|
|
||||||
|
scrConductor cond = scrConductor.instance;
|
||||||
|
if (cond == null || !cond.hasSongStarted) return;
|
||||||
|
|
||||||
|
double num4 = (cond.separateCountdownTime ? (cond.crotchetAtStart * (double)ADOBase.conductor.adjustedCountdownTicks) : 0.0);
|
||||||
|
double vidOffset = scrVfxPlus.instance ? scrVfxPlus.instance.vidOffset : 0.0;
|
||||||
|
double targetVideoTime = cond.songposition_minusi - num4 + vidOffset;
|
||||||
|
|
||||||
|
if (targetVideoTime < 0)
|
||||||
|
{
|
||||||
|
if (_targetTexture != null)
|
||||||
|
{
|
||||||
|
RenderTexture active = RenderTexture.active;
|
||||||
|
RenderTexture.active = _targetTexture;
|
||||||
|
GL.Clear(true, true, Color.black);
|
||||||
|
RenderTexture.active = active;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (targetVideoTime < _currentFrameTimestamp || targetVideoTime - _currentFrameTimestamp > 1.0)
|
||||||
|
{
|
||||||
|
_decoder.SeekToTime(targetVideoTime);
|
||||||
|
_currentFrameTimestamp = targetVideoTime;
|
||||||
|
_nextFrameTimestamp = targetVideoTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool frameUpdated = false;
|
||||||
|
while (targetVideoTime >= _nextFrameTimestamp)
|
||||||
|
{
|
||||||
|
double timestamp;
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
if (_gpuMode == GPUMode.NV12 && _textureY != null && _textureUV != null)
|
||||||
|
{
|
||||||
|
success = _decoder.TryReadNV12Frame(
|
||||||
|
_textureY.GetRawTextureData<byte>(),
|
||||||
|
_textureUV.GetRawTextureData<byte>(),
|
||||||
|
out timestamp
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else if (_gpuMode == GPUMode.YUV420P && _textureY != null && _textureU != null && _textureV != null)
|
||||||
|
{
|
||||||
|
success = _decoder.TryReadYUV420PFrame(
|
||||||
|
_textureY.GetRawTextureData<byte>(),
|
||||||
|
_textureU.GetRawTextureData<byte>(),
|
||||||
|
_textureV.GetRawTextureData<byte>(),
|
||||||
|
out timestamp
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_textureRGBA != null)
|
||||||
|
{
|
||||||
|
success = _decoder.TryReadFrame(
|
||||||
|
_textureRGBA.GetRawTextureData<byte>(),
|
||||||
|
vflip: true,
|
||||||
|
out timestamp
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
_currentFrameTimestamp = timestamp;
|
||||||
|
_nextFrameTimestamp = timestamp + (1.0 / _decoder.FPS);
|
||||||
|
frameUpdated = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_isLooping)
|
||||||
|
{
|
||||||
|
_decoder.SeekToTime(0);
|
||||||
|
_currentFrameTimestamp = 0;
|
||||||
|
_nextFrameTimestamp = 1.0 / _decoder.FPS;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frameUpdated)
|
||||||
|
{
|
||||||
|
RenderToTarget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Melon<RenderMod>.Logger.Error($"[FFmpegVideoBGPlayer] Update 재생 중 에러 발생: {ex}");
|
||||||
|
_isInitialized = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderToTarget()
|
||||||
|
{
|
||||||
|
if (_decoder == null || _targetTexture == null) return;
|
||||||
|
|
||||||
|
int w = _decoder.Width;
|
||||||
|
int h = _decoder.Height;
|
||||||
|
|
||||||
|
if (_gpuMode == GPUMode.NV12 && _textureY != null && _textureUV != null && _computeShader != null && _renderTexture != null)
|
||||||
|
{
|
||||||
|
_textureY.Apply();
|
||||||
|
_textureUV.Apply();
|
||||||
|
|
||||||
|
_computeShader.SetTexture(_kernelIndex, "_YTex", _textureY);
|
||||||
|
_computeShader.SetTexture(_kernelIndex, "_UVTex", _textureUV);
|
||||||
|
_computeShader.SetTexture(_kernelIndex, "_Result", _renderTexture);
|
||||||
|
_computeShader.SetInt("Width", w);
|
||||||
|
_computeShader.SetInt("Height", h);
|
||||||
|
|
||||||
|
int threadGroupsX = (w + 7) / 8;
|
||||||
|
int threadGroupsY = (h + 7) / 8;
|
||||||
|
_computeShader.Dispatch(_kernelIndex, threadGroupsX, threadGroupsY, 1);
|
||||||
|
|
||||||
|
Graphics.Blit(_renderTexture, _targetTexture);
|
||||||
|
}
|
||||||
|
else if (_gpuMode == GPUMode.YUV420P && _textureY != null && _textureU != null && _textureV != null && _computeShader != null && _renderTexture != null)
|
||||||
|
{
|
||||||
|
_textureY.Apply();
|
||||||
|
_textureU.Apply();
|
||||||
|
_textureV.Apply();
|
||||||
|
|
||||||
|
_computeShader.SetTexture(_kernelIndex, "_YTex", _textureY);
|
||||||
|
_computeShader.SetTexture(_kernelIndex, "_UTex", _textureU);
|
||||||
|
_computeShader.SetTexture(_kernelIndex, "_VTex", _textureV);
|
||||||
|
_computeShader.SetTexture(_kernelIndex, "_Result", _renderTexture);
|
||||||
|
_computeShader.SetInt("Width", w);
|
||||||
|
_computeShader.SetInt("Height", h);
|
||||||
|
|
||||||
|
int threadGroupsX = (w + 7) / 8;
|
||||||
|
int threadGroupsY = (h + 7) / 8;
|
||||||
|
_computeShader.Dispatch(_kernelIndex, threadGroupsX, threadGroupsY, 1);
|
||||||
|
|
||||||
|
Graphics.Blit(_renderTexture, _targetTexture);
|
||||||
|
}
|
||||||
|
else if (_textureRGBA != null)
|
||||||
|
{
|
||||||
|
_textureRGBA.Apply();
|
||||||
|
Graphics.Blit(_textureRGBA, _targetTexture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
CleanUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CleanUp()
|
||||||
|
{
|
||||||
|
_isInitialized = false;
|
||||||
|
|
||||||
|
if (_decoder != null)
|
||||||
|
{
|
||||||
|
_decoder.Dispose();
|
||||||
|
_decoder = null;
|
||||||
|
Melon<RenderMod>.Logger.Msg("[FFmpegVideoBGPlayer] 디코더 해제 완료.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_textureRGBA != null)
|
||||||
|
{
|
||||||
|
Destroy(_textureRGBA);
|
||||||
|
_textureRGBA = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_textureY != null) Destroy(_textureY);
|
||||||
|
if (_textureU != null) Destroy(_textureU);
|
||||||
|
if (_textureV != null) Destroy(_textureV);
|
||||||
|
if (_textureUV != null) Destroy(_textureUV);
|
||||||
|
|
||||||
|
_textureY = null;
|
||||||
|
_textureU = null;
|
||||||
|
_textureV = null;
|
||||||
|
_textureUV = null;
|
||||||
|
|
||||||
|
if (_renderTexture != null)
|
||||||
|
{
|
||||||
|
_renderTexture.Release();
|
||||||
|
Destroy(_renderTexture);
|
||||||
|
_renderTexture = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user