Files
video-v1/godot-projects/vavcore-demo/scripts/VavCorePlayer.cs

861 lines
32 KiB
C#

using Godot;
using System;
using System.Runtime.InteropServices;
// VavCore 데이터 구조체들
[StructLayout(LayoutKind.Sequential)]
public struct VavCoreVideoFrame
{
// Legacy CPU fields (for backward compatibility)
public IntPtr y_plane; // uint8_t*
public IntPtr u_plane; // uint8_t*
public IntPtr v_plane; // uint8_t*
public int y_stride; // Y plane stride
public int u_stride; // U plane stride
public int v_stride; // V plane stride
// Frame metadata
public int width; // Frame width
public int height; // Frame height
public ulong timestamp_us; // Timestamp in microseconds
public ulong frame_number; // Frame sequence number
// Surface type and data (we'll use CPU mode for now)
public int surface_type; // VavCoreSurfaceType (0 = CPU)
// Union data - we'll only use the first 64 bytes for CPU data
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public ulong[] surface_data; // Union as array of ulongs
}
public partial class VavCorePlayer : Control
{
// VavCore DLL Import - Use actual file system path
private const string VavCoreDll = "VavCore.dll";
[DllImport(VavCoreDll, CallingConvention = CallingConvention.Cdecl)]
private static extern int vavcore_initialize();
[DllImport(VavCoreDll, CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr vavcore_create_player();
[DllImport(VavCoreDll, CallingConvention = CallingConvention.Cdecl)]
private static extern int vavcore_open_file(IntPtr player, string filePath);
[DllImport(VavCoreDll, CallingConvention = CallingConvention.Cdecl)]
private static extern bool vavcore_is_open(IntPtr player);
[DllImport(VavCoreDll, CallingConvention = CallingConvention.Cdecl)]
private static extern void vavcore_destroy_player(IntPtr player);
[DllImport(VavCoreDll, CallingConvention = CallingConvention.Cdecl)]
private static extern void vavcore_cleanup();
[DllImport(VavCoreDll, CallingConvention = CallingConvention.Cdecl)]
private static extern int vavcore_decode_next_frame(IntPtr player, out VavCoreVideoFrame frame);
[DllImport(VavCoreDll, CallingConvention = CallingConvention.Cdecl)]
private static extern void vavcore_free_frame(ref VavCoreVideoFrame frame);
// VavCore Player 인스턴스
private IntPtr _vavCorePlayer = IntPtr.Zero;
private string _currentVideoPath = string.Empty;
private bool _isInitialized = false;
// Godot 노드들
private TextureRect _videoTexture;
private ShaderMaterial _yuvShaderMaterial;
// GPU 텍스처들
private ImageTexture _yTexture;
private ImageTexture _uTexture;
private ImageTexture _vTexture;
// 텍스처 캐싱 최적화
private int _cachedFrameWidth = 0;
private int _cachedFrameHeight = 0;
private bool _texturesInitialized = false;
// 재생 상태 관리
private bool _isPlaying = false;
private bool _isPaused = false;
private Timer _playbackTimer;
private double _targetFrameRate = 30.0; // 기본 30fps
public override void _Ready()
{
GD.Print("VavCorePlayer: Initializing...");
// VavCore 라이브러리 초기화
InitializeVavCore();
// 비디오 출력용 TextureRect 생성
SetupVideoTexture();
}
private void InitializeVavCore()
{
try
{
GD.Print("VavCorePlayer: Initializing VavCore library...");
// DLL 경로 확인
string dllPath = System.IO.Path.Combine(System.Environment.CurrentDirectory, "VavCore.dll");
GD.Print($"VavCorePlayer: Looking for DLL at: {dllPath}");
GD.Print($"VavCorePlayer: DLL exists: {System.IO.File.Exists(dllPath)}");
GD.Print("VavCorePlayer: Calling vavcore_initialize()...");
int initResult = vavcore_initialize();
GD.Print($"VavCorePlayer: vavcore_initialize() returned: {initResult}");
if (initResult == 0) // VAVCORE_SUCCESS = 0
{
_isInitialized = true;
GD.Print("VavCorePlayer: VavCore initialized successfully!");
// VavCore 플레이어 인스턴스 생성
GD.Print("VavCorePlayer: Creating VavCore player instance...");
_vavCorePlayer = vavcore_create_player();
GD.Print($"VavCorePlayer: vavcore_create_player() returned: {_vavCorePlayer}");
if (_vavCorePlayer != IntPtr.Zero)
{
GD.Print("VavCorePlayer: VavCore player created successfully!");
}
else
{
GD.PrintErr("VavCorePlayer: Failed to create VavCore player!");
_isInitialized = false;
}
}
else
{
GD.PrintErr("VavCorePlayer: Failed to initialize VavCore!");
}
}
catch (Exception ex)
{
GD.PrintErr($"VavCorePlayer: Exception during initialization: {ex.Message}");
GD.PrintErr($"VavCorePlayer: Exception type: {ex.GetType().Name}");
GD.PrintErr($"VavCorePlayer: Stack trace: {ex.StackTrace}");
}
}
private void SetupVideoTexture()
{
// 비디오 출력용 TextureRect 노드 생성
_videoTexture = new TextureRect();
_videoTexture.Name = "VideoTexture";
// Fill the entire parent control
_videoTexture.SetAnchorsAndOffsetsPreset(Control.LayoutPreset.FullRect);
_videoTexture.ExpandMode = TextureRect.ExpandModeEnum.FitWidthProportional;
_videoTexture.StretchMode = TextureRect.StretchModeEnum.KeepAspectCentered;
// 가시성 강제 설정
_videoTexture.Visible = true;
_videoTexture.Modulate = Colors.White; // 완전 불투명
_videoTexture.ZIndex = 100; // 최상위에 표시
// 투명 배경으로 설정
_videoTexture.SelfModulate = Colors.White;
// GPU 셰이더 설정
SetupGPUShader();
AddChild(_videoTexture);
// 재생 타이머 설정
_playbackTimer = new Timer();
_playbackTimer.Name = "PlaybackTimer";
_playbackTimer.WaitTime = 1.0 / _targetFrameRate; // 30fps = ~0.033초
_playbackTimer.Timeout += OnPlaybackTimerTimeout;
AddChild(_playbackTimer);
// 디버그 정보 출력
GD.Print($"VavCorePlayer: Video texture setup complete");
GD.Print($"VavCorePlayer: TextureRect position: {_videoTexture.Position}");
GD.Print($"VavCorePlayer: TextureRect size: {_videoTexture.Size}");
GD.Print($"VavCorePlayer: TextureRect visible: {_videoTexture.Visible}");
GD.Print($"VavCorePlayer: GPU shader setup complete");
}
public bool LoadVideo(string videoPath)
{
if (!_isInitialized || _vavCorePlayer == IntPtr.Zero)
{
GD.PrintErr("VavCorePlayer: Not initialized!");
return false;
}
try
{
GD.Print($"VavCorePlayer: Loading video: {videoPath}");
// Godot 리소스 경로를 실제 파일 경로로 변환
string realPath = ProjectSettings.GlobalizePath(videoPath);
GD.Print($"VavCorePlayer: Real path: {realPath}");
int result = vavcore_open_file(_vavCorePlayer, realPath);
if (result == 0) // VAVCORE_SUCCESS = 0
{
_currentVideoPath = realPath; // 성공시 경로 저장
// 텍스처 캐시 초기화
_texturesInitialized = false;
_cachedFrameWidth = 0;
_cachedFrameHeight = 0;
GD.Print("VavCorePlayer: Video loaded successfully!");
// 비디오가 로드되었는지 확인
bool isOpen = vavcore_is_open(_vavCorePlayer);
GD.Print($"VavCorePlayer: Video is open: {isOpen}");
return true;
}
else
{
GD.PrintErr("VavCorePlayer: Failed to load video!");
return false;
}
}
catch (Exception ex)
{
GD.PrintErr($"VavCorePlayer: Exception during video loading: {ex.Message}");
return false;
}
}
public bool IsVideoLoaded()
{
if (!_isInitialized || _vavCorePlayer == IntPtr.Zero)
return false;
try
{
return vavcore_is_open(_vavCorePlayer);
}
catch (Exception ex)
{
GD.PrintErr($"VavCorePlayer: Exception checking video status: {ex.Message}");
return false;
}
}
public override void _ExitTree()
{
GD.Print("VavCorePlayer: Cleaning up...");
// VavCore 리소스 정리
if (_vavCorePlayer != IntPtr.Zero)
{
vavcore_destroy_player(_vavCorePlayer);
_vavCorePlayer = IntPtr.Zero;
}
if (_isInitialized)
{
vavcore_cleanup();
_isInitialized = false;
}
GD.Print("VavCorePlayer: Cleanup complete");
}
// 재생 제어 메서드들
public void StartPlayback()
{
if (!IsVideoLoaded())
{
GD.PrintErr("VavCorePlayer: Cannot start playback - no video loaded");
return;
}
_isPlaying = true;
_isPaused = false;
_playbackTimer.Start();
GD.Print("VavCorePlayer: Playback started");
}
public void PausePlayback()
{
_isPaused = true;
_playbackTimer.Stop();
GD.Print("VavCorePlayer: Playback paused");
}
public void StopPlayback()
{
_isPlaying = false;
_isPaused = false;
_playbackTimer.Stop();
// 처음부터 다시 재생하기 위해 비디오를 다시 로드
if (_vavCorePlayer != IntPtr.Zero && !string.IsNullOrEmpty(_currentVideoPath))
{
GD.Print("VavCorePlayer: Reloading video to reset position");
// 현재 파일 경로 저장
string currentPath = _currentVideoPath;
// 비디오 다시 로드 (내부적으로 seek to beginning과 동일한 효과)
int result = vavcore_open_file(_vavCorePlayer, currentPath);
if (result == 0)
{
GD.Print("VavCorePlayer: Video reset to beginning successfully");
}
else
{
GD.PrintErr($"VavCorePlayer: Failed to reset video position: {result}");
}
}
GD.Print("VavCorePlayer: Playback stopped");
}
public bool IsPlaying()
{
return _isPlaying && !_isPaused;
}
// 타이머 콜백 - 매 프레임마다 호출됨
private void OnPlaybackTimerTimeout()
{
if (!_isPlaying || _isPaused || !IsVideoLoaded())
return;
try
{
DecodeAndDisplayNextFrame();
}
catch (Exception ex)
{
GD.PrintErr($"VavCorePlayer: Error during frame decode: {ex.Message}");
StopPlayback();
}
}
// 다음 프레임 디코딩 및 표시
private void DecodeAndDisplayNextFrame()
{
VavCoreVideoFrame frame = new VavCoreVideoFrame();
// 구조체 초기화
frame.surface_data = new ulong[16];
int result = vavcore_decode_next_frame(_vavCorePlayer, out frame);
GD.Print($"VavCorePlayer: Decode result: {result}");
if (result == 0) // VAVCORE_SUCCESS
{
GD.Print($"VavCorePlayer: Decoded frame {frame.frame_number} ({frame.width}x{frame.height})");
GD.Print($"VavCorePlayer: Y-plane: {frame.y_plane}, U-plane: {frame.u_plane}, V-plane: {frame.v_plane}");
GD.Print($"VavCorePlayer: Y-stride: {frame.y_stride}, U-stride: {frame.u_stride}, V-stride: {frame.v_stride}");
// YUV 데이터가 유효한지 확인
if (frame.y_plane != IntPtr.Zero && frame.width > 0 && frame.height > 0)
{
// GPU에서 YUV 데이터를 직접 처리하여 표시
DisplayFrameGPU(frame);
}
else
{
GD.PrintErr("VavCorePlayer: Invalid frame data received");
}
// 프레임 메모리 해제
vavcore_free_frame(ref frame);
}
else if (result == 1) // VAVCORE_END_OF_STREAM
{
GD.Print("VavCorePlayer: End of video reached");
StopPlayback();
}
else
{
GD.PrintErr($"VavCorePlayer: Frame decode failed with error: {result}");
StopPlayback();
}
}
// 폴백 이미지 생성 (GPU 셰이더 사용 불가시)
private Image CreateFallbackImage(VavCoreVideoFrame frame)
{
// GPU 셰이더가 사용 가능한 경우 null 반환 (셰이더가 처리)
if (_yuvShaderMaterial != null)
{
return null;
}
// GPU 셰이더 사용 불가시 CPU 방식으로 폴백
GD.PrintErr("VavCorePlayer: GPU shader not available, using CPU fallback");
// 간단한 그레이스케일 변환 (성능 최적화)
try
{
var image = Image.CreateEmpty(frame.width, frame.height, false, Image.Format.Rgb8);
unsafe
{
byte* yPtr = (byte*)frame.y_plane.ToPointer();
for (int y = 0; y < frame.height; y++)
{
for (int x = 0; x < frame.width; x++)
{
int yIndex = y * frame.y_stride + x;
byte yVal = yPtr[yIndex];
// Y 값만 사용하여 그레이스케일로 표시 (고속 처리)
float gray = yVal / 255.0f;
var color = new Color(gray, gray, gray, 1.0f);
image.SetPixel(x, y, color);
}
}
}
return image;
}
catch (Exception ex)
{
GD.PrintErr($"VavCorePlayer: Fallback image creation error: {ex.Message}");
return null;
}
}
// GPU 셰이더 설정
private void SetupGPUShader()
{
try
{
// YUV to RGB 셰이더 로드
var shader = GD.Load<Shader>("res://shaders/yuv_to_rgb.gdshader");
if (shader == null)
{
GD.PrintErr("VavCorePlayer: Failed to load YUV shader");
return;
}
// 셰이더 머티리얼 생성
_yuvShaderMaterial = new ShaderMaterial();
_yuvShaderMaterial.Shader = shader;
GD.Print("VavCorePlayer: GPU shader loaded successfully");
}
catch (Exception ex)
{
GD.PrintErr($"VavCorePlayer: Error setting up GPU shader: {ex.Message}");
}
}
// GPU에서 YUV 프레임을 직접 처리하여 표시
private void DisplayFrameGPU(VavCoreVideoFrame frame)
{
try
{
// TextureRect 크기가 0이면 다시 설정
if (_videoTexture.Size.X <= 0 || _videoTexture.Size.Y <= 0)
{
GD.Print("VavCorePlayer: TextureRect size is 0, forcing resize...");
// 부모 컨테이너 크기 확인
var parentSize = GetParent<Control>().Size;
GD.Print($"VavCorePlayer: Parent size: {parentSize}");
// 강제로 크기 설정
_videoTexture.Size = parentSize;
_videoTexture.Position = Vector2.Zero;
GD.Print($"VavCorePlayer: TextureRect resized to: {_videoTexture.Size}");
}
// YUV 데이터를 GPU 텍스처로 변환
bool success = CreateYUVTextures(frame);
if (success && _yuvShaderMaterial != null)
{
// 셰이더에 YUV 텍스처 할당
_yuvShaderMaterial.SetShaderParameter("y_texture", _yTexture);
_yuvShaderMaterial.SetShaderParameter("u_texture", _uTexture);
_yuvShaderMaterial.SetShaderParameter("v_texture", _vTexture);
// 메쉬 기반 렌더링을 위한 더미 텍스처 생성
var dummyImage = Image.CreateEmpty(frame.width, frame.height, false, Image.Format.Rgb8);
var dummyTexture = ImageTexture.CreateFromImage(dummyImage);
// TextureRect에 셰이더 머티리얼 적용
_videoTexture.Texture = dummyTexture;
_videoTexture.Material = _yuvShaderMaterial;
GD.Print($"VavCorePlayer: GPU YUV frame displayed successfully ({frame.width}x{frame.height})");
GD.Print($"VavCorePlayer: TextureRect final size: {_videoTexture.Size}");
}
else
{
// GPU 처리 실패시 빨간색 에러 표시
var errorImage = Image.CreateEmpty(frame.width, frame.height, false, Image.Format.Rgb8);
for (int y = 0; y < frame.height; y++)
{
for (int x = 0; x < frame.width; x++)
{
errorImage.SetPixel(x, y, Colors.Red);
}
}
var errorTexture = ImageTexture.CreateFromImage(errorImage);
_videoTexture.Texture = errorTexture;
_videoTexture.Material = null; // 셰이더 비활성화
GD.PrintErr($"VavCorePlayer: GPU YUV processing failed, showing error image");
}
}
catch (Exception ex)
{
GD.PrintErr($"VavCorePlayer: Error displaying GPU frame: {ex.Message}");
}
}
// YUV 데이터로 GPU 텍스처 생성
private bool CreateYUVTextures(VavCoreVideoFrame frame)
{
if (frame.y_plane == IntPtr.Zero || frame.width <= 0 || frame.height <= 0)
{
GD.PrintErr("VavCorePlayer: Invalid frame data for GPU texture creation");
return false;
}
if (frame.u_plane == IntPtr.Zero || frame.v_plane == IntPtr.Zero)
{
GD.PrintErr("VavCorePlayer: Missing UV plane data for GPU texture");
return false;
}
GD.Print($"VavCorePlayer: Creating GPU YUV textures - Y stride: {frame.y_stride}, U stride: {frame.u_stride}, V stride: {frame.v_stride}");
// YUV420P 메모리 연속성 분석
long yAddr = (long)frame.y_plane;
long uAddr = (long)frame.u_plane;
long vAddr = (long)frame.v_plane;
long ySize = frame.width * frame.height;
long uSize = (frame.width / 2) * (frame.height / 2);
long vSize = (frame.width / 2) * (frame.height / 2);
GD.Print($"VavCorePlayer: Memory layout analysis:");
GD.Print($" Y plane: 0x{yAddr:X} (size: {ySize} bytes)");
GD.Print($" U plane: 0x{uAddr:X} (size: {uSize} bytes)");
GD.Print($" V plane: 0x{vAddr:X} (size: {vSize} bytes)");
GD.Print($" Y->U gap: {uAddr - (yAddr + ySize)} bytes");
GD.Print($" U->V gap: {vAddr - (uAddr + uSize)} bytes");
// 연속 메모리 공간인지 확인
bool isContiguous = (uAddr == yAddr + ySize) && (vAddr == uAddr + uSize);
bool hasOptimalStrides = frame.y_stride == frame.width && frame.u_stride == (frame.width / 2) && frame.v_stride == (frame.width / 2);
GD.Print($"VavCorePlayer: YUV planes contiguous: {isContiguous}");
GD.Print($"VavCorePlayer: Optimal strides: {hasOptimalStrides}");
if (isContiguous && hasOptimalStrides)
{
GD.Print("VavCorePlayer: Attempting single-block YUV420P copy!");
return CreateSingleBlockYUVTexture(frame);
}
try
{
// 텍스처 캐싱 최적화: 해상도가 바뀌었거나 첫 번째 프레임인 경우에만 재생성
bool needsResize = !_texturesInitialized ||
_cachedFrameWidth != frame.width ||
_cachedFrameHeight != frame.height;
if (needsResize)
{
GD.Print($"VavCorePlayer: CREATING new textures - {frame.width}x{frame.height}");
_cachedFrameWidth = frame.width;
_cachedFrameHeight = frame.height;
_texturesInitialized = true;
// Y 평면 텍스처 생성
var yImage = CreatePlaneImageBlockCopy(frame.y_plane, frame.width, frame.height, frame.y_stride);
if (yImage != null)
{
_yTexture = ImageTexture.CreateFromImage(yImage);
GD.Print($"VavCorePlayer: Y texture CREATED - {yImage.GetWidth()}x{yImage.GetHeight()}");
}
else
{
GD.PrintErr("VavCorePlayer: Failed to create Y plane image");
return false;
}
// U 평면 텍스처 생성
int uvWidth = frame.width / 2;
int uvHeight = frame.height / 2;
var uImage = CreatePlaneImageBlockCopy(frame.u_plane, uvWidth, uvHeight, frame.u_stride);
if (uImage != null)
{
_uTexture = ImageTexture.CreateFromImage(uImage);
GD.Print($"VavCorePlayer: U texture CREATED - {uImage.GetWidth()}x{uImage.GetHeight()}");
}
else
{
GD.PrintErr("VavCorePlayer: Failed to create U plane image");
return false;
}
// V 평면 텍스처 생성
var vImage = CreatePlaneImageBlockCopy(frame.v_plane, uvWidth, uvHeight, frame.v_stride);
if (vImage != null)
{
_vTexture = ImageTexture.CreateFromImage(vImage);
GD.Print($"VavCorePlayer: V texture CREATED - {vImage.GetWidth()}x{vImage.GetHeight()}");
}
else
{
GD.PrintErr("VavCorePlayer: Failed to create V plane image");
return false;
}
}
else
{
// 텍스처 업데이트만 수행 (훨씬 빠름!)
var yImage = CreatePlaneImageBlockCopy(frame.y_plane, frame.width, frame.height, frame.y_stride);
var uImage = CreatePlaneImageBlockCopy(frame.u_plane, frame.width / 2, frame.height / 2, frame.u_stride);
var vImage = CreatePlaneImageBlockCopy(frame.v_plane, frame.width / 2, frame.height / 2, frame.v_stride);
if (yImage != null && uImage != null && vImage != null)
{
_yTexture.Update(yImage);
_uTexture.Update(uImage);
_vTexture.Update(vImage);
// GD.Print("VavCorePlayer: Textures UPDATED efficiently");
}
else
{
GD.PrintErr("VavCorePlayer: Failed to update textures");
return false;
}
}
GD.Print($"VavCorePlayer: GPU YUV textures created successfully (3-block method)");
return true;
}
catch (Exception ex)
{
GD.PrintErr($"VavCorePlayer: GPU texture creation error: {ex.Message}");
GD.PrintErr($"VavCorePlayer: Stack trace: {ex.StackTrace}");
return false;
}
}
// 단일 평면 데이터로 이미지 생성 (최적화된 버전)
private Image CreatePlaneImage(IntPtr planeData, int width, int height, int stride)
{
try
{
// R8 포맷 사용 (단일 체널, 8-bit)
var image = Image.CreateEmpty(width, height, false, Image.Format.R8);
GD.Print($"VavCorePlayer: Creating plane image - Size: {width}x{height}, Stride: {stride}, Format: R8");
unsafe
{
byte* srcPtr = (byte*)planeData.ToPointer();
// 직접 픽셀 데이터 복사 (고속 처리)
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int srcIndex = y * stride + x;
byte value = srcPtr[srcIndex];
// R8 포맷: 빨간 채널에만 값 설정
var color = new Color(value / 255.0f, 0.0f, 0.0f, 1.0f);
image.SetPixel(x, y, color);
}
}
}
GD.Print($"VavCorePlayer: Plane image created successfully - Format: {image.GetFormat()}");
return image;
}
catch (Exception ex)
{
GD.PrintErr($"VavCorePlayer: Error creating plane image: {ex.Message}");
return null;
}
}
// 블록 메모리 복사를 위한 최고속 평면 이미지 생성
private Image CreatePlaneImageBlockCopy(IntPtr planeData, int width, int height, int stride)
{
try
{
GD.Print($"VavCorePlayer: Block copy - Width: {width}, Height: {height}, Stride: {stride}");
// 케이스 1: 스트라이드가 폭과 같은 경우 - 전체 블록 복사
if (stride == width)
{
GD.Print("VavCorePlayer: Using full block copy (stride == width)");
int totalBytes = width * height;
var imageData = new byte[totalBytes];
unsafe
{
byte* srcPtr = (byte*)planeData.ToPointer();
fixed (byte* dstPtr = imageData)
{
// 전체 메모리 블록을 한 번에 복사 (최고속)
Buffer.MemoryCopy(srcPtr, dstPtr, totalBytes, totalBytes);
}
}
var image = Image.CreateFromData(width, height, false, Image.Format.R8, imageData);
GD.Print($"VavCorePlayer: Block copy completed - {totalBytes} bytes");
return image;
}
// 케이스 2: 스트라이드가 폭보다 큰 경우 - 라인별 복사 (하지만 memcpy 사용)
else
{
GD.Print($"VavCorePlayer: Using line-by-line memcpy (stride {stride} > width {width})");
var imageData = new byte[width * height];
unsafe
{
byte* srcPtr = (byte*)planeData.ToPointer();
fixed (byte* dstPtr = imageData)
{
// 라인별 고속 메모리 복사
for (int y = 0; y < height; y++)
{
byte* srcLine = srcPtr + (y * stride);
byte* dstLine = dstPtr + (y * width);
// 한 라인을 memcpy로 고속 복사
Buffer.MemoryCopy(srcLine, dstLine, width, width);
}
}
}
var image = Image.CreateFromData(width, height, false, Image.Format.R8, imageData);
GD.Print($"VavCorePlayer: Line memcpy completed - {height} lines of {width} bytes");
return image;
}
}
catch (Exception ex)
{
GD.PrintErr($"VavCorePlayer: Error in block copy: {ex.Message}");
// 폴백: 셰이더 기반 스트라이드 처리 시도
return CreatePlaneImageWithShaderStride(planeData, width, height, stride);
}
}
// 셰이더에서 스트라이드를 처리하는 방식 (실험적)
private Image CreatePlaneImageWithShaderStride(IntPtr planeData, int width, int height, int stride)
{
try
{
GD.Print($"VavCorePlayer: Shader stride mode - Creating {stride}x{height} texture for {width}x{height} content");
// 스트라이드 전체 데이터를 텍스처로 업로드
int totalBytes = stride * height;
var imageData = new byte[totalBytes];
unsafe
{
byte* srcPtr = (byte*)planeData.ToPointer();
fixed (byte* dstPtr = imageData)
{
// 전체 스트라이드 데이터를 블록 복사
Buffer.MemoryCopy(srcPtr, dstPtr, totalBytes, totalBytes);
}
}
// 스트라이드 크기로 텍스처 생성 (셰이더에서 UV 좌표 조정 필요)
var image = Image.CreateFromData(stride, height, false, Image.Format.R8, imageData);
GD.Print($"VavCorePlayer: Shader stride texture created - {stride}x{height} (actual content: {width}x{height})");
return image;
}
catch (Exception ex)
{
GD.PrintErr($"VavCorePlayer: Error in shader stride mode: {ex.Message}");
// 최종 폴백: 기존 방식
return CreatePlaneImage(planeData, width, height, stride);
}
}
private bool CreateSingleBlockYUVTexture(VavCoreVideoFrame frame)
{
try
{
var startTime = DateTime.Now;
long ySize = frame.width * frame.height;
long uSize = (frame.width / 2) * (frame.height / 2);
long vSize = (frame.width / 2) * (frame.height / 2);
long totalSize = ySize + uSize + vSize;
GD.Print($"VavCorePlayer: TRUE single-block copy - Total size: {totalSize} bytes");
// 진정한 단일 블록 복사: 전체 YUV420P 데이터를 하나의 텍스처로
var yuvData = new byte[totalSize];
unsafe
{
byte* srcPtr = (byte*)frame.y_plane.ToPointer();
fixed (byte* dstPtr = yuvData)
{
Buffer.MemoryCopy(srcPtr, dstPtr, totalSize, totalSize);
}
}
var copyTime = DateTime.Now;
GD.Print($"VavCorePlayer: ONLY ONE Buffer.MemoryCopy completed in {(copyTime - startTime).TotalMilliseconds:F2}ms");
// 전체 YUV 데이터를 하나의 1D 텍스처로 생성 (셰이더에서 오프셋 계산)
var yuvImage = Image.CreateFromData((int)totalSize, 1, false, Image.Format.R8, yuvData);
// 텍스처 캐싱 최적화 적용
ImageTexture yuvTexture;
if (_material.GetShaderParameter("yuv_texture").AsGodotObject() == null)
{
yuvTexture = ImageTexture.CreateFromImage(yuvImage);
GD.Print("VavCorePlayer: Single YUV texture CREATED");
}
else
{
yuvTexture = _material.GetShaderParameter("yuv_texture").As<ImageTexture>();
yuvTexture.Update(yuvImage);
// GD.Print("VavCorePlayer: Single YUV texture UPDATED efficiently");
}
var textureTime = DateTime.Now;
GD.Print($"VavCorePlayer: Single YUV texture creation completed in {(textureTime - copyTime).TotalMilliseconds:F2}ms");
// 셰이더에 YUV 오프셋과 크기 정보 전달
_material.SetShaderParameter("yuv_texture", yuvTexture);
_material.SetShaderParameter("y_offset", 0);
_material.SetShaderParameter("u_offset", (int)ySize);
_material.SetShaderParameter("v_offset", (int)(ySize + uSize));
_material.SetShaderParameter("y_size", (int)ySize);
_material.SetShaderParameter("u_size", (int)uSize);
_material.SetShaderParameter("v_size", (int)vSize);
_material.SetShaderParameter("frame_width", frame.width);
_material.SetShaderParameter("frame_height", frame.height);
var finalTime = DateTime.Now;
GD.Print($"VavCorePlayer: TRUE single-block method total time: {(finalTime - startTime).TotalMilliseconds:F2}ms");
GD.Print("VavCorePlayer: TRUE single-block YUV420P copy SUCCESS - ZERO additional copies!");
return true;
}
catch (Exception ex)
{
GD.PrintErr($"VavCorePlayer: Single-block copy failed: {ex.Message}");
GD.PrintErr($"VavCorePlayer: Falling back to 3-block method");
return false;
}
}
}