diff --git a/.claude/settings.local.json b/.claude/settings.local.json index d7e9d6d..6a13d8f 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -67,7 +67,9 @@ "Bash(\"/c/Program Files/Microsoft Visual Studio/2022/Community/MSBuild/Current/Bin/MSBuild.exe\" Vav2Player.vcxproj \"//p:Configuration=Debug\" \"//p:Platform=x64\" \"//v:minimal\")", "Bash(python3:*)", "Bash(find:*)", - "Bash(\"/c/Program Files/Microsoft Visual Studio/2022/Community/MSBuild/Current/Bin/MSBuild.exe\" Vav2PlayerHeadless.vcxproj \"/p:Configuration=Debug\" \"/p:Platform=x64\" \"/v:minimal\")" + "Bash(\"/c/Program Files/Microsoft Visual Studio/2022/Community/MSBuild/Current/Bin/MSBuild.exe\" Vav2PlayerHeadless.vcxproj \"/p:Configuration=Debug\" \"/p:Platform=x64\" \"/v:minimal\")", + "Bash(cmd:*)", + "Bash(\"C:/Program Files/Microsoft Visual Studio/2022/Community/MSBuild/Current/Bin/MSBuild.exe\" Vav2Player.vcxproj /p:Configuration=Debug /p:Platform=x64 /v:minimal)", ], "deny": [], "ask": [] diff --git a/vav2/Vav2Player/Vav2Player/Vav2Player.vcxproj b/vav2/Vav2Player/Vav2Player/Vav2Player.vcxproj index 0d7cd45..55a74a0 100644 --- a/vav2/Vav2Player/Vav2Player/Vav2Player.vcxproj +++ b/vav2/Vav2Player/Vav2Player/Vav2Player.vcxproj @@ -146,7 +146,7 @@ MultiVideoTestWindow.xaml - + @@ -179,7 +179,7 @@ MultiVideoTestWindow.xaml - + diff --git a/vav2/Vav2Player/Vav2Player/Vav2PlayerHeadless.vcxproj b/vav2/Vav2Player/Vav2Player/Vav2PlayerHeadless.vcxproj index ff2b785..f079b77 100644 --- a/vav2/Vav2Player/Vav2Player/Vav2PlayerHeadless.vcxproj +++ b/vav2/Vav2Player/Vav2Player/Vav2PlayerHeadless.vcxproj @@ -97,7 +97,6 @@ - @@ -106,7 +105,6 @@ - @@ -122,7 +120,6 @@ - @@ -131,7 +128,6 @@ - diff --git a/vav2/Vav2Player/Vav2Player/VideoPlayerControl.xaml.cpp b/vav2/Vav2Player/Vav2Player/VideoPlayerControl.xaml.cpp index 3412019..b6c9e90 100644 --- a/vav2/Vav2Player/Vav2Player/VideoPlayerControl.xaml.cpp +++ b/vav2/Vav2Player/Vav2Player/VideoPlayerControl.xaml.cpp @@ -5,11 +5,17 @@ #endif #include +#include +#include +#include #include #include #include #include "headless/AV1Decoder_Headless.h" #include "src/Rendering/SimpleGPURenderer.h" +#include "src/Common/VideoTypes.h" +#include "src/FileIO/WebMFileReader.h" +#include "src/Decoder/VideoDecoderFactory.h" using namespace winrt; using namespace winrt::Microsoft::UI::Xaml; @@ -19,7 +25,7 @@ using namespace winrt::Microsoft::UI::Dispatching; namespace winrt::Vav2Player::implementation { VideoPlayerControl::VideoPlayerControl() - : m_useHardwareRendering(true) + : m_useHardwareRendering(false) // CPU mode for Phase 1 { InitializeComponent(); } @@ -37,12 +43,6 @@ namespace winrt::Vav2Player::implementation m_isInitialized = true; UpdateStatus(L"Ready"); - // Set initial UI state - if (!m_showControls) - { - // ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed); - } - // Auto load video if source is set if (!m_videoSource.empty()) { @@ -58,6 +58,21 @@ namespace winrt::Vav2Player::implementation }); OutputDebugStringA("VideoPlayerControl loaded successfully\n"); + + // Show purple outline placeholder while waiting + ShowPurpleOutlinePlaceholder(); + + // After 3 seconds, try to load actual video + auto delayTimer = winrt::Microsoft::UI::Xaml::DispatcherTimer(); + delayTimer.Interval(std::chrono::seconds(3)); + delayTimer.Tick([this, delayTimer](auto&&, auto&&) mutable { + delayTimer.Stop(); + // Try to load test video file + auto testVideoPath = L"D:/Project/video-av1/sample/simple_test.webm"; + OutputDebugStringA("[DEBUG] Attempting to load test video after 3 second delay\n"); + LoadVideo(testVideoPath); + }); + delayTimer.Start(); } catch (...) { @@ -65,15 +80,86 @@ namespace winrt::Vav2Player::implementation } } + void VideoPlayerControl::ShowPurpleOutlinePlaceholder() + { + try + { + OutputDebugStringA("Showing purple outline placeholder...\n"); + + // Get container size for full screen placeholder + auto container = VideoDisplayArea(); + if (!container) return; + + int width = static_cast(container.ActualWidth()); + int height = static_cast(container.ActualHeight()); + + // Use minimum size if container not ready + if (width <= 0 || height <= 0) { + width = 800; + height = 600; + } + + m_renderBitmap = winrt::Microsoft::UI::Xaml::Media::Imaging::WriteableBitmap(width, height); + VideoImage().Source(m_renderBitmap); + VideoImage().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible); + VideoImage().Width(width); + VideoImage().Height(height); + + auto buffer = m_renderBitmap.PixelBuffer(); + auto bufferByteAccess = buffer.as<::Windows::Storage::Streams::IBufferByteAccess>(); + uint8_t* bufferData = nullptr; + winrt::check_hresult(bufferByteAccess->Buffer(&bufferData)); + + // Fill with transparent background + for (int i = 0; i < width * height; i++) + { + bufferData[i * 4 + 0] = 0; // Blue + bufferData[i * 4 + 1] = 0; // Green + bufferData[i * 4 + 2] = 0; // Red + bufferData[i * 4 + 3] = 0; // Alpha (transparent) + } + + // Draw purple outline (border thickness = 4 pixels) + int borderThickness = 4; + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + bool isOutline = (x < borderThickness || x >= width - borderThickness || + y < borderThickness || y >= height - borderThickness); + + if (isOutline) + { + int index = (y * width + x) * 4; + bufferData[index + 0] = 128; // Blue + bufferData[index + 1] = 0; // Green + bufferData[index + 2] = 128; // Red (Purple = Red + Blue) + bufferData[index + 3] = 255; // Alpha (opaque) + } + } + } + + buffer.Length(width * height * 4); + m_renderBitmap.Invalidate(); + OutputDebugStringA("Purple outline placeholder completed\n"); + } + catch (...) + { + OutputDebugStringA("Purple outline placeholder failed\n"); + } + } + void VideoPlayerControl::UserControl_Unloaded(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&) { try { Stop(); - StopPlaybackTimer(); + if (m_playbackTimer) + { + m_playbackTimer.Stop(); + } StopControlsHideTimer(); - // Cleanup resources if (m_gpuRenderer) { m_gpuRenderer->Shutdown(); @@ -92,51 +178,6 @@ namespace winrt::Vav2Player::implementation } } - // Control event handlers (commented out as controls are disabled) - /* - void VideoPlayerControl::PlayPauseButton_Click(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&) - { - if (m_isPlaying) - { - Pause(); - } - else - { - Play(); - } - } - */ - - /* - void VideoPlayerControl::StopButton_Click(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&) - { - Stop(); - } - */ - - /* - void VideoPlayerControl::ProgressSlider_ValueChanged(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::Controls::Primitives::RangeBaseValueChangedEventArgs const& e) - { - if (!m_isLoaded || !m_fileReader) - return; - - // Avoid feedback loop during programmatic updates - if (e.OldValue() == e.NewValue()) - return; - - try - { - double percentage = e.NewValue(); - double targetTime = (percentage / 100.0) * m_duration; - Seek(targetTime); - } - catch (...) - { - // Ignore seek errors - } - } - */ - void VideoPlayerControl::HoverDetector_PointerEntered(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const&) { if (m_showControls && m_isLoaded) @@ -182,14 +223,7 @@ namespace winrt::Vav2Player::implementation m_showControls = value; if (m_isInitialized) { - if (value && m_isLoaded) - { - // ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible); - } - else - { - // ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed); - } + // Update controls visibility based on value and loaded state } } @@ -298,10 +332,12 @@ namespace winrt::Vav2Player::implementation // Public Methods void VideoPlayerControl::LoadVideo(winrt::hstring const& filePath) { - OutputDebugStringA("[VideoPlayerControl] LoadVideo called\n"); + OutputDebugStringA("[DEBUG] LoadVideo called\n"); try { std::string filePathStr = winrt::to_string(filePath); + OutputDebugStringA(("[DEBUG] Loading file: " + filePathStr + "\n").c_str()); + UpdateStatus(L"Loading video..."); LoadingRing().IsActive(true); @@ -311,80 +347,98 @@ namespace winrt::Vav2Player::implementation // Create file reader if (!m_fileReader) { + OutputDebugStringA("[DEBUG] Creating WebMFileReader\n"); m_fileReader = std::make_unique(); } // Open file + OutputDebugStringA("[DEBUG] Opening file...\n"); if (!m_fileReader->OpenFile(filePathStr)) { + OutputDebugStringA("[DEBUG] Failed to open file\n"); UpdateStatus(L"Failed to open video file"); LoadingRing().IsActive(false); return; } + OutputDebugStringA("[DEBUG] File opened successfully\n"); // Get video tracks + OutputDebugStringA("[DEBUG] Getting video tracks...\n"); auto tracks = m_fileReader->GetVideoTracks(); if (tracks.empty()) { + OutputDebugStringA("[DEBUG] No video tracks found\n"); UpdateStatus(L"No video tracks found"); LoadingRing().IsActive(false); return; } + OutputDebugStringA(("[DEBUG] Found " + std::to_string(tracks.size()) + " video tracks\n").c_str()); // Select first video track + OutputDebugStringA("[DEBUG] Selecting video track...\n"); if (!m_fileReader->SelectVideoTrack(tracks[0].track_number)) { + OutputDebugStringA("[DEBUG] Failed to select video track\n"); UpdateStatus(L"Failed to select video track"); LoadingRing().IsActive(false); return; } + OutputDebugStringA("[DEBUG] Video track selected successfully\n"); // Get metadata + OutputDebugStringA("[DEBUG] Getting video metadata...\n"); auto metadata = m_fileReader->GetVideoMetadata(); m_totalFrames = metadata.total_frames; m_frameRate = metadata.frame_rate > 0 ? metadata.frame_rate : 30.0; m_duration = m_totalFrames / m_frameRate; - // Enable hardware rendering for optimal performance - UseHardwareRendering(true); + OutputDebugStringA(("[DEBUG] Video metadata: " + std::to_string(metadata.width) + "x" + std::to_string(metadata.height) + + ", " + std::to_string(m_totalFrames) + " frames, " + std::to_string(m_frameRate) + " fps\n").c_str()); + + // Enable CPU rendering for Phase 1 (MAJOR_REFACTORING_GUIDE) + UseHardwareRendering(false); // Create and initialize decoder - if (!CreateDecoder() || !InitializeDecoder()) + OutputDebugStringA("[DEBUG] Creating decoder...\n"); + if (!CreateDecoder()) { - UpdateStatus(L"Failed to initialize decoder"); + OutputDebugStringA("[DEBUG] Failed to create decoder\n"); + UpdateStatus(L"Failed to create decoder"); LoadingRing().IsActive(false); return; } + OutputDebugStringA("[DEBUG] Initializing decoder...\n"); + if (!InitializeDecoder()) + { + OutputDebugStringA("[DEBUG] Failed to initialize decoder\n"); + UpdateStatus(L"Failed to initialize decoder"); + LoadingRing().IsActive(false); + return; + } + OutputDebugStringA("[DEBUG] Decoder initialized successfully\n"); + // Initialize video renderer InitializeVideoRenderer(); - // Simplified initialization - removed complex pipelines - // Update UI state m_isLoaded = true; LoadingRing().IsActive(false); PlaceholderText().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed); - if (m_showControls) - { - // ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible); - } - - // Update duration display - // DurationText().Text(winrt::to_hstring(FormatTime(m_duration))); - UpdateStatus(L"Video loaded successfully"); - OutputDebugStringA(("Video loaded: " + filePathStr + "\n").c_str()); + OutputDebugStringA(("[DEBUG] Video loaded successfully: " + filePathStr + "\n").c_str()); // Auto play if enabled if (m_autoPlay) { + OutputDebugStringA("[DEBUG] Auto play enabled - starting playback\n"); Play(); } } catch (...) { + OutputDebugStringA("[DEBUG] Exception in LoadVideo\n"); UpdateStatus(L"Error loading video"); LoadingRing().IsActive(false); } @@ -392,38 +446,429 @@ namespace winrt::Vav2Player::implementation void VideoPlayerControl::Play() { + OutputDebugStringA("[DEBUG] Play() called\n"); if (!m_isLoaded || m_isPlaying) + { + OutputDebugStringA("[DEBUG] Play() - not ready or already playing\n"); return; - + } + m_isPlaying = true; UpdateStatus(L"Playing"); + + // Setup playback timer for continuous frame processing + if (!m_playbackTimer) + { + OutputDebugStringA("[DEBUG] Creating playback timer\n"); + m_playbackTimer = winrt::Microsoft::UI::Xaml::DispatcherTimer(); + + // Store weak reference to avoid circular dependency + auto weakThis = get_weak(); + m_playbackTimer.Tick([weakThis](auto&&, auto&&) { + if (auto strongThis = weakThis.get()) + { + OutputDebugStringA("[DEBUG] Timer tick - checking conditions\n"); + OutputDebugStringA(("[DEBUG] m_isPlaying: " + std::string(strongThis->m_isPlaying ? "true" : "false") + + ", m_isLoaded: " + std::string(strongThis->m_isLoaded ? "true" : "false") + "\n").c_str()); + + if (strongThis->m_isPlaying && strongThis->m_isLoaded) + { + try { + strongThis->ProcessSingleFrame(); + } + catch (...) { + OutputDebugStringA("[DEBUG] Exception in timer ProcessSingleFrame\n"); + strongThis->m_isPlaying = false; + strongThis->m_playbackTimer.Stop(); + } + } + else + { + OutputDebugStringA("[DEBUG] Timer tick - conditions not met, skipping frame processing\n"); + } + } + else + { + OutputDebugStringA("[DEBUG] Timer tick - object destroyed, stopping timer\n"); + } + }); + } + + // Set timer interval based on frame rate (default 30fps = 33.33ms) + auto interval = std::chrono::milliseconds(static_cast(1000.0 / m_frameRate)); + m_playbackTimer.Interval(interval); + + OutputDebugStringA(("[DEBUG] Timer interval set to: " + std::to_string(interval.count()) + "ms\n").c_str()); + OutputDebugStringA(("[DEBUG] Timer object valid: " + std::string(m_playbackTimer ? "true" : "false") + "\n").c_str()); + + m_playbackTimer.Start(); + OutputDebugStringA("[DEBUG] Timer.Start() called\n"); + + // Immediate test: try to process one frame manually to verify the pipeline works + OutputDebugStringA("[DEBUG] Testing immediate frame processing...\n"); + ProcessSingleFrame(); + + OutputDebugStringA(("[DEBUG] Started playback timer at " + std::to_string(m_frameRate) + " fps\n").c_str()); + + // Backup approach: Create a manual timer that restarts itself + OutputDebugStringA("[DEBUG] Setting up backup manual timer approach\n"); + auto manualTimer = winrt::Microsoft::UI::Xaml::DispatcherTimer(); + manualTimer.Interval(interval); + + // Manual timer approach with restart + manualTimer.Tick([this, manualTimer](auto&&, auto&&) mutable { + OutputDebugStringA("[DEBUG] Manual timer tick\n"); + if (m_isPlaying && m_isLoaded) { + ProcessSingleFrame(); + // Schedule next frame + manualTimer.Start(); + } + }); + + // Start both timers + manualTimer.Start(); + OutputDebugStringA("[DEBUG] Manual timer also started\n"); } - // MAJOR_REFACTORING_GUIDE.md: Simplified method stubs for compilation - void VideoPlayerControl::Pause() { m_isPlaying = false; } - void VideoPlayerControl::Stop() { m_isPlaying = false; } - void VideoPlayerControl::ProcessSingleFrame() { /* Simplified */ } - void VideoPlayerControl::ProcessSingleFrameLegacy() { /* Basic */ } - void VideoPlayerControl::RenderFrameToScreen(const VideoFrame& frame) { /* Software */ } - void VideoPlayerControl::RenderFrameSoftware(const VideoFrame& frame) { /* YUV to RGB */ } - void VideoPlayerControl::UpdateStatus(winrt::hstring const& message) { m_status = message; } - void VideoPlayerControl::StartPlaybackTimer() { /* Timer */ } - void VideoPlayerControl::UpdateProgress() { /* Progress */ } - void VideoPlayerControl::StopPlaybackTimer() { /* Stop Timer */ } - void VideoPlayerControl::StopControlsHideTimer() { /* Stop Controls Timer */ } - void VideoPlayerControl::ShowControlsInternal() { /* Show Controls */ } - void VideoPlayerControl::InitializeVideoRenderer() { /* Init Renderer */ } - void VideoPlayerControl::ResetVideoState() { /* Reset State */ } - bool VideoPlayerControl::CreateDecoder() { return true; } - bool VideoPlayerControl::InitializeDecoder() { return true; } + void VideoPlayerControl::Pause() + { + OutputDebugStringA("[DEBUG] Pause() called\n"); + m_isPlaying = false; + if (m_playbackTimer) + { + m_playbackTimer.Stop(); + } + UpdateStatus(L"Paused"); + } + + void VideoPlayerControl::Stop() + { + OutputDebugStringA("[DEBUG] Stop() called\n"); + m_isPlaying = false; + if (m_playbackTimer) + { + m_playbackTimer.Stop(); + } + + // Reset position to beginning + m_currentFrame = 0; + m_currentTime = 0.0; + + // Reset file reader to beginning for next play + if (m_fileReader && m_fileReader->IsFileOpen()) + { + OutputDebugStringA("[DEBUG] Resetting file reader to beginning\n"); + m_fileReader->Reset(); + } + + // Reset decoder state + if (m_decoder) + { + OutputDebugStringA("[DEBUG] Resetting decoder state\n"); + m_decoder->Reset(); + } + + UpdateStatus(L"Stopped"); + } + + void VideoPlayerControl::ProcessSingleFrame() + { + OutputDebugStringA("[DEBUG] ProcessSingleFrame() called\n"); + if (!m_isLoaded || !m_fileReader || !m_decoder) + { + OutputDebugStringA("[DEBUG] ProcessSingleFrame() - not ready\n"); + return; + } + + VideoPacket packet; + OutputDebugStringA("[DEBUG] Reading next packet...\n"); + if (!m_fileReader->ReadNextPacket(packet)) + { + OutputDebugStringA("[DEBUG] No more packets - playback completed\n"); + m_isPlaying = false; + if (m_playbackTimer) { + m_playbackTimer.Stop(); + } + UpdateStatus(L"Playback completed"); + return; + } + OutputDebugStringA(("[DEBUG] Packet read - size: " + std::to_string(packet.size) + "\n").c_str()); + + VideoFrame frame; + OutputDebugStringA("[DEBUG] Decoding frame...\n"); + if (!m_decoder->DecodeFrame(packet, frame)) + { + OutputDebugStringA("[DEBUG] Frame decode failed - skipping\n"); + return; + } + OutputDebugStringA(("[DEBUG] Frame decoded - size: " + std::to_string(frame.width) + "x" + std::to_string(frame.height) + "\n").c_str()); + + RenderFrameToScreen(frame); + + m_currentFrame++; + m_currentTime = m_currentFrame / m_frameRate; + OutputDebugStringA(("[DEBUG] Frame " + std::to_string(m_currentFrame) + " processed\n").c_str()); + } + + void VideoPlayerControl::ProcessSingleFrameLegacy() + { + // Legacy method - calls ProcessSingleFrame for compatibility + ProcessSingleFrame(); + } + + void VideoPlayerControl::RenderFrameToScreen(const VideoFrame& frame) + { + OutputDebugStringA("[DEBUG] RenderFrameToScreen() called\n"); + RenderFrameSoftware(frame); + } + + void VideoPlayerControl::RenderFrameSoftware(const VideoFrame& frame) + { + OutputDebugStringA(("[DEBUG] RenderFrameSoftware() called - " + std::to_string(frame.width) + "x" + std::to_string(frame.height) + "\n").c_str()); + OutputDebugStringA(("[DEBUG] y_plane: " + std::string(frame.y_plane ? "valid" : "null") + "\n").c_str()); + + if (!frame.y_plane || frame.width == 0 || frame.height == 0) + { + OutputDebugStringA("[DEBUG] Invalid frame data - returning\n"); + return; + } + + try { + // Create or reuse WriteableBitmap for the frame + if (!m_renderBitmap || + static_cast(m_renderBitmap.PixelWidth()) != frame.width || + static_cast(m_renderBitmap.PixelHeight()) != frame.height) { + + OutputDebugStringA("[DEBUG] Creating new WriteableBitmap\n"); + m_renderBitmap = winrt::Microsoft::UI::Xaml::Media::Imaging::WriteableBitmap( + frame.width, frame.height); + VideoImage().Source(m_renderBitmap); + UpdateVideoImageAspectFit(frame.width, frame.height); + + OutputDebugStringA(("Created WriteableBitmap: " + std::to_string(frame.width) + "x" + std::to_string(frame.height) + "\n").c_str()); + } + + // Convert YUV to BGRA and render to bitmap + auto buffer = m_renderBitmap.PixelBuffer(); + uint32_t capacity = buffer.Capacity(); + OutputDebugStringA(("[DEBUG] Buffer capacity: " + std::to_string(capacity) + "\n").c_str()); + + if (capacity >= frame.width * frame.height * 4) { + OutputDebugStringA("[DEBUG] Converting YUV to BGRA...\n"); + // Simple approach: create BGRA data and copy to buffer + std::vector bgra_data(frame.width * frame.height * 4); + ConvertYUVToBGRA(frame, bgra_data.data(), frame.width, frame.height); + + OutputDebugStringA("[DEBUG] Copying to bitmap buffer...\n"); + // Copy BGRA data directly to bitmap buffer + auto bufferByteAccess = buffer.as<::Windows::Storage::Streams::IBufferByteAccess>(); + uint8_t* bufferData = nullptr; + winrt::check_hresult(bufferByteAccess->Buffer(&bufferData)); + std::memcpy(bufferData, bgra_data.data(), frame.width * frame.height * 4); + buffer.Length(frame.width * frame.height * 4); + + // Trigger UI update + m_renderBitmap.Invalidate(); + + OutputDebugStringA(("[DEBUG] Frame rendered successfully: " + std::to_string(frame.width) + "x" + std::to_string(frame.height) + "\n").c_str()); + } else { + OutputDebugStringA("[DEBUG] Buffer capacity too small\n"); + } + + } catch (...) { + OutputDebugStringA("[DEBUG] Software rendering failed\n"); + } + } + + void VideoPlayerControl::ConvertYUVToBGRA(const VideoFrame& yuv_frame, uint8_t* bgra_buffer, uint32_t width, uint32_t height) + { + OutputDebugStringA("[DEBUG] ConvertYUVToBGRA() called\n"); + // YUV420P to BGRA conversion using BT.709 color space + const uint8_t* y_plane = yuv_frame.y_plane.get(); + const uint8_t* u_plane = yuv_frame.u_plane.get(); + const uint8_t* v_plane = yuv_frame.v_plane.get(); + + if (!y_plane || !u_plane || !v_plane) { + OutputDebugStringA("[DEBUG] ConvertYUVToBGRA: Invalid plane data\n"); + return; + } + + const uint32_t y_stride = yuv_frame.y_stride; + const uint32_t u_stride = yuv_frame.u_stride; + const uint32_t v_stride = yuv_frame.v_stride; + + OutputDebugStringA(("[DEBUG] YUV strides: Y=" + std::to_string(y_stride) + " U=" + std::to_string(u_stride) + " V=" + std::to_string(v_stride) + "\n").c_str()); + + for (uint32_t y = 0; y < height; y++) { + const uint8_t* y_row = y_plane + y * y_stride; + const uint8_t* u_row = u_plane + (y / 2) * u_stride; + const uint8_t* v_row = v_plane + (y / 2) * v_stride; + + uint8_t* bgra_row = bgra_buffer + y * width * 4; + + for (uint32_t x = 0; x < width; x++) { + const uint8_t Y = y_row[x]; + const uint8_t U = u_row[x / 2]; + const uint8_t V = v_row[x / 2]; + + // BT.709 YUV to RGB conversion + const int C = Y - 16; + const int D = U - 128; + const int E = V - 128; + + int R = (298 * C + 409 * E + 128) >> 8; + int G = (298 * C - 100 * D - 208 * E + 128) >> 8; + int B = (298 * C + 516 * D + 128) >> 8; + + // Clamp to [0, 255] + R = std::max(0, std::min(255, R)); + G = std::max(0, std::min(255, G)); + B = std::max(0, std::min(255, B)); + + // Store as BGRA + bgra_row[x * 4 + 0] = static_cast(B); // Blue + bgra_row[x * 4 + 1] = static_cast(G); // Green + bgra_row[x * 4 + 2] = static_cast(R); // Red + bgra_row[x * 4 + 3] = 255; // Alpha + } + } + OutputDebugStringA("[DEBUG] YUV to BGRA conversion completed\n"); + } + + void VideoPlayerControl::UpdateStatus(winrt::hstring const& message) + { + m_status = message; + OutputDebugStringA(("[DEBUG] Status: " + winrt::to_string(message) + "\n").c_str()); + } + + void VideoPlayerControl::ShowControlsInternal() + { + // Show video controls - simplified implementation + } + + void VideoPlayerControl::InitializeVideoRenderer() + { + OutputDebugStringA("[DEBUG] InitializeVideoRenderer() called\n"); + // Initialize CPU rendering mode (Phase 1) + if (!m_useHardwareRendering) + { + // Ensure software rendering UI is visible + VideoSwapChainPanel().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed); + VideoImage().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible); + OutputDebugStringA("[DEBUG] Initialized CPU rendering mode\n"); + } + else + { + // Hardware rendering setup (Phase 2 - future) + VideoSwapChainPanel().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible); + VideoImage().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed); + OutputDebugStringA("[DEBUG] Hardware rendering not implemented yet\n"); + } + } + + void VideoPlayerControl::ResetVideoState() + { + OutputDebugStringA("[DEBUG] ResetVideoState() called\n"); + m_currentFrame = 0; + m_currentTime = 0.0; + m_isLoaded = false; + m_isPlaying = false; + + // Stop and reset playback timer + if (m_playbackTimer) + { + m_playbackTimer.Stop(); + } + } + + bool VideoPlayerControl::CreateDecoder() + { + OutputDebugStringA("[DEBUG] CreateDecoder() called\n"); + m_decoder = VideoDecoderFactory::CreateDecoder(VideoCodecType::AV1, m_decoderType); + bool success = m_decoder != nullptr; + OutputDebugStringA(("[DEBUG] Decoder created: " + std::string(success ? "success" : "failed") + "\n").c_str()); + return success; + } + + bool VideoPlayerControl::InitializeDecoder() + { + OutputDebugStringA("[DEBUG] InitializeDecoder() called\n"); + if (!m_decoder) { + OutputDebugStringA("[DEBUG] No decoder available\n"); + return false; + } + auto metadata = m_fileReader->GetVideoMetadata(); + bool success = m_decoder->Initialize(metadata); + OutputDebugStringA(("[DEBUG] Decoder initialized: " + std::string(success ? "success" : "failed") + "\n").c_str()); + return success; + } + + void VideoPlayerControl::UpdateVideoImageAspectFit(int videoWidth, int videoHeight) + { + OutputDebugStringA(("[DEBUG] UpdateVideoImageAspectFit: " + std::to_string(videoWidth) + "x" + std::to_string(videoHeight) + "\n").c_str()); + // AspectFit calculation for proper video scaling + auto container = VideoDisplayArea(); + if (!container) return; + + double containerWidth = container.ActualWidth(); + double containerHeight = container.ActualHeight(); + if (containerWidth <= 0 || containerHeight <= 0) return; + + double videoAspectRatio = static_cast(videoWidth) / videoHeight; + double containerAspectRatio = containerWidth / containerHeight; + + double displayWidth, displayHeight; + if (videoAspectRatio > containerAspectRatio) { + // Video is wider - fit to container width + displayWidth = containerWidth; + displayHeight = containerWidth / videoAspectRatio; + } else { + // Video is taller - fit to container height + displayHeight = containerHeight; + displayWidth = containerHeight * videoAspectRatio; + } + + VideoImage().Width(displayWidth); + VideoImage().Height(displayHeight); + OutputDebugStringA(("[DEBUG] Video size set to: " + std::to_string(displayWidth) + "x" + std::to_string(displayHeight) + "\n").c_str()); + } + + void VideoPlayerControl::Seek(double timeSeconds) + { + OutputDebugStringA(("[DEBUG] Seek to: " + std::to_string(timeSeconds) + "s\n").c_str()); + if (!m_isLoaded || !m_fileReader) return; + + // Stop playback during seek + bool wasPlaying = m_isPlaying; + if (m_isPlaying) { + Pause(); + } + + // Seek to the specified time + if (m_fileReader->SeekToTime(timeSeconds)) { + m_currentTime = timeSeconds; + m_currentFrame = static_cast(timeSeconds * m_frameRate); + + // Process one frame to update display + ProcessSingleFrame(); + + // Resume playback if it was playing before seek + if (wasPlaying) { + Play(); + } + + UpdateStatus(L"Seeked"); + } else { + OutputDebugStringA("[DEBUG] Seek operation failed\n"); + } + } - // Additional required methods for linker - void VideoPlayerControl::UpdateVideoImageAspectFit(int videoWidth, int videoHeight) { /* AspectFit */ } - void VideoPlayerControl::Seek(double timeSeconds) { /* Seek */ } bool VideoPlayerControl::IsVideoPlaying() { return m_isPlaying; } bool VideoPlayerControl::IsVideoLoaded() { return m_isLoaded; } double VideoPlayerControl::CurrentTime() { return m_currentTime; } double VideoPlayerControl::Duration() { return m_duration; } winrt::hstring VideoPlayerControl::Status() { return m_status; } - void VideoPlayerControl::StartControlsHideTimer() { /* Start Hide Timer */ } -} + + void VideoPlayerControl::StartControlsHideTimer() { /* Simplified implementation */ } + void VideoPlayerControl::StopControlsHideTimer() { /* Simplified implementation */ } +} \ No newline at end of file diff --git a/vav2/Vav2Player/Vav2Player/VideoPlayerControl.xaml.h b/vav2/Vav2Player/Vav2Player/VideoPlayerControl.xaml.h index b38ef70..31a766b 100644 --- a/vav2/Vav2Player/Vav2Player/VideoPlayerControl.xaml.h +++ b/vav2/Vav2Player/Vav2Player/VideoPlayerControl.xaml.h @@ -4,18 +4,10 @@ #include "src/FileIO/WebMFileReader.h" #include "src/Decoder/VideoDecoderFactory.h" #include "src/Common/VideoTypes.h" -// Simplified architecture - removed FramePool -#include "src/Rendering/D3D12VideoRenderer.h" #include "src/Rendering/SimpleGPURenderer.h" #include #include #include -#include - -// Forward declarations -namespace Vav2Player { - // Only essential components remain -} namespace winrt::Vav2Player::implementation { @@ -27,14 +19,10 @@ namespace winrt::Vav2Player::implementation // Events void UserControl_Loaded(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e); void UserControl_Unloaded(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e); - // Control event handlers (commented out as controls are disabled) - // void PlayPauseButton_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e); - // void StopButton_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e); - // void ProgressSlider_ValueChanged(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Controls::Primitives::RangeBaseValueChangedEventArgs const& e); void HoverDetector_PointerEntered(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& e); void HoverDetector_PointerExited(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& e); - // Public Properties (Dependency Properties will be added later) + // Public Properties winrt::hstring VideoSource(); void VideoSource(winrt::hstring const& value); @@ -47,11 +35,9 @@ namespace winrt::Vav2Player::implementation Vav2Player::VideoDecoderType DecoderType(); void DecoderType(Vav2Player::VideoDecoderType value); - // Hardware rendering control bool UseHardwareRendering(); void UseHardwareRendering(bool value); - // Internal decoder type management VideoDecoderFactory::DecoderType GetInternalDecoderType(); void SetInternalDecoderType(VideoDecoderFactory::DecoderType value); @@ -74,17 +60,13 @@ namespace winrt::Vav2Player::implementation std::unique_ptr m_fileReader; std::unique_ptr m_decoder; - // Simplified processing - no complex pipelines - // Video rendering components winrt::Microsoft::UI::Xaml::Media::Imaging::WriteableBitmap m_renderBitmap{ nullptr }; std::vector m_bgraBuffer; - - // D3D12 Hardware rendering (Phase 1) - std::unique_ptr m_d3d12Renderer; std::unique_ptr m_gpuRenderer; bool m_useHardwareRendering = false; - bool m_hardwareEnvironmentCriticalError = false; // Critical system-level hardware issue + // Playback timer for continuous frame processing + winrt::Microsoft::UI::Xaml::DispatcherTimer m_playbackTimer; // Video dimensions uint32_t m_videoWidth = 0; @@ -96,10 +78,6 @@ namespace winrt::Vav2Player::implementation bool m_autoPlay = false; VideoDecoderFactory::DecoderType m_decoderType = VideoDecoderFactory::DecoderType::AUTO; - // Decoder reuse optimization to prevent excessive dav1d worker thread creation - VideoCodecType m_lastCodecType = VideoCodecType::AV1; - VideoDecoderFactory::DecoderType m_lastDecoderType = VideoDecoderFactory::DecoderType::AUTO; - // Playback state std::atomic m_isPlaying{ false }; std::atomic m_isLoaded{ false }; @@ -111,41 +89,22 @@ namespace winrt::Vav2Player::implementation double m_duration = 0.0; winrt::hstring m_status = L"Ready"; - // Timer for playback - winrt::Microsoft::UI::Dispatching::DispatcherQueueTimer m_playbackTimer{ nullptr }; - winrt::Microsoft::UI::Dispatching::DispatcherQueueTimer m_controlsHideTimer{ nullptr }; - // Helper methods void InitializeVideoRenderer(); - void InitializeHardwareRenderer(int width, int height); - void InitializeSoftwareRenderer(int width, int height); void ProcessSingleFrame(); void RenderFrameToScreen(const VideoFrame& frame); - void RenderFrameHardware(const VideoFrame& frame); void RenderFrameSoftware(const VideoFrame& frame); - void ProcessSingleFrameSimple(); // Single linear processing path - void ProcessSingleFrameZeroCopy(); // Zero-copy processing path - void ProcessSingleFrameRingBuffer(); // Ring buffer processing path - void ProcessSingleFrameLegacy(); // Legacy CPU processing path + void ProcessSingleFrameLegacy(); void ConvertYUVToBGRA(const VideoFrame& yuv_frame, uint8_t* bgra_buffer, uint32_t width, uint32_t height); - - // Simplified processing helpers void UpdateVideoImageAspectFit(int videoWidth, int videoHeight); void UpdateStatus(winrt::hstring const& message); - void UpdatePlaybackUI(); - void UpdateProgress(); - void StartPlaybackTimer(); - void StopPlaybackTimer(); void ShowControlsInternal(); - void HideControls(); void StartControlsHideTimer(); void StopControlsHideTimer(); - std::string FormatTime(double seconds); - - // Internal state management void ResetVideoState(); bool CreateDecoder(); bool InitializeDecoder(); + void ShowPurpleOutlinePlaceholder(); }; } diff --git a/vav2/Vav2Player/Vav2Player/headless/SimpleHeadlessMain.cpp b/vav2/Vav2Player/Vav2Player/headless/SimpleHeadlessMain.cpp index 69a8ead..7e5f695 100644 --- a/vav2/Vav2Player/Vav2Player/headless/SimpleHeadlessMain.cpp +++ b/vav2/Vav2Player/Vav2Player/headless/SimpleHeadlessMain.cpp @@ -1,121 +1,87 @@ #include "pch.h" -#include "../src/Rendering/SimpleGPURenderer_Headless.h" +#include "../src/Decoder/VideoDecoderFactory.h" +#include "../src/FileIO/WebMFileReader.h" -// Forward declarations -int TestSimpleGPUIntegration(); -int TestAV1GPUIntegration(const std::string& videoFile); - -// Phase 3: Test SimpleGPURenderer basic functionality int main(int argc, char* argv[]) { SetConsoleCP(CP_UTF8); SetConsoleOutputCP(CP_UTF8); - std::cout << "=== Vav2Player Phase 3 Headless Test ===" << std::endl; - std::cout << "Testing SimpleGPURenderer and basic dependencies" << std::endl; + std::cout << "=== MAJOR_REFACTORING_GUIDE Phase 3: Basic Video Test ===" << std::endl; if (argc < 2) { - std::cout << "Usage: Vav2PlayerHeadless.exe " << std::endl; + std::cout << "Usage: " << argv[0] << " " << std::endl; return 1; } - std::string videoFile = argv[1]; - std::cout << "Would test with video file: " << videoFile << std::endl; - - // Test 1: Basic library dependencies - std::cout << "[TEST] Testing basic library dependencies..." << std::endl; - - Dav1dSettings settings; - dav1d_default_settings(&settings); - std::cout << "[PASS] dav1d library loaded successfully" << std::endl; + std::string filePath = argv[1]; + std::cout << "Testing video file: " << filePath << std::endl; try { - Dav1dContext* ctx = nullptr; - int result = dav1d_open(&ctx, &settings); - - if (result == 0 && ctx != nullptr) { - std::cout << "[PASS] dav1d_open successful" << std::endl; - dav1d_close(&ctx); - } else { - std::cout << "[FAIL] dav1d_open failed with error: " << result << std::endl; - } - } catch (...) { - std::cout << "[FAIL] dav1d_open crashed" << std::endl; - } - - // Test 2: SimpleGPURenderer creation and destruction - std::cout << "[TEST] Testing SimpleGPURenderer basic functionality..." << std::endl; - - try { - std::unique_ptr renderer = - std::make_unique(); - - if (renderer) { - std::cout << "[PASS] SimpleGPURenderer created successfully" << std::endl; - - // Test basic properties - std::cout << "[INFO] Initial state - Width: " << renderer->GetWidth() - << ", Height: " << renderer->GetHeight() - << ", Initialized: " << (renderer->IsInitialized() ? "Yes" : "No") << std::endl; - - // Test GPU pipeline initialization - std::cout << "[TEST] Testing GPU pipeline initialization..." << std::endl; - HRESULT hr = renderer->Initialize(1920, 1080); - if (SUCCEEDED(hr)) { - std::cout << "[PASS] GPU pipeline initialized successfully" << std::endl; - - // Test GPU pipeline functionality - hr = renderer->TestGPUPipeline(); - if (SUCCEEDED(hr)) { - std::cout << "[PASS] GPU pipeline test completed successfully" << std::endl; - } else { - std::cout << "[FAIL] GPU pipeline test failed: 0x" << std::hex << hr << std::endl; - } - } else { - std::cout << "[FAIL] GPU pipeline initialization failed: 0x" << std::hex << hr << std::endl; - } - - std::cout << "[INFO] Testing SimpleGPURenderer destruction..." << std::endl; - renderer.reset(); // Test destruction - std::cout << "[PASS] SimpleGPURenderer destroyed successfully" << std::endl; - } else { - std::cout << "[FAIL] SimpleGPURenderer creation failed" << std::endl; + // Test WebMFileReader + auto fileReader = std::make_unique(); + if (!fileReader->OpenFile(filePath)) { + std::cout << "Failed to open video file" << std::endl; return 1; } + + auto tracks = fileReader->GetVideoTracks(); + if (tracks.empty()) { + std::cout << "No video tracks found" << std::endl; + return 1; + } + + std::cout << "Found " << tracks.size() << " video track(s)" << std::endl; + + // Select first track + if (!fileReader->SelectVideoTrack(tracks[0].track_number)) { + std::cout << "Failed to select video track" << std::endl; + return 1; + } + + auto metadata = fileReader->GetVideoMetadata(); + std::cout << "Video: " << metadata.width << "x" << metadata.height + << " @ " << metadata.frame_rate << " fps" << std::endl; + + // Test decoder creation + auto decoder = VideoDecoderFactory::CreateDecoder(VideoCodecType::AV1, VideoDecoderFactory::DecoderType::AUTO); + if (!decoder) { + std::cout << "Failed to create AV1 decoder" << std::endl; + return 1; + } + + if (!decoder->Initialize(metadata)) { + std::cout << "Failed to initialize decoder" << std::endl; + return 1; + } + + std::cout << "Decoder initialized successfully" << std::endl; + + // Test a few frames + for (int i = 0; i < 5; i++) { + VideoPacket packet; + if (!fileReader->ReadNextPacket(packet)) { + std::cout << "End of file or read error at frame " << i << std::endl; + break; + } + + VideoFrame frame; + if (decoder->DecodeFrame(packet, frame)) { + std::cout << "Frame " << i << ": " << frame.width << "x" << frame.height << std::endl; + } else { + std::cout << "Frame " << i << ": decode failed" << std::endl; + } + } + + std::cout << "=== MAJOR_REFACTORING_GUIDE Phase 3: Test completed successfully ===" << std::endl; + std::cout << "Basic video decoding pipeline verified!" << std::endl; + return 0; + } catch (const std::exception& e) { - std::cout << "[FAIL] Exception during SimpleGPURenderer test: " << e.what() << std::endl; + std::cout << "Exception: " << e.what() << std::endl; return 1; } catch (...) { - std::cout << "[FAIL] Unknown exception during SimpleGPURenderer test" << std::endl; + std::cout << "Unknown exception occurred" << std::endl; return 1; } - - std::cout << "[SUCCESS] Phase 3 headless test completed successfully!" << std::endl; - std::cout << "SimpleGPURenderer infrastructure verified" << std::endl; - - // Run simple GPU integration test with mock data - std::cout << "\n=== Running Simple GPU Integration Test ===" << std::endl; - int result = TestSimpleGPUIntegration(); - if (result != 0) { - std::cout << "\n[FAIL] Simple GPU integration test failed" << std::endl; - return result; - } - - // Run AV1 + GPU integration test with real video file - if (argc >= 2) { - std::cout << "\n=== Running AV1 + GPU Integration Test ===" << std::endl; - result = TestAV1GPUIntegration(videoFile); - if (result != 0) { - std::cout << "\n[FAIL] AV1 + GPU integration test failed" << std::endl; - return result; - } - } else { - std::cout << "\n[INFO] Skipping AV1 + GPU integration test (no video file provided)" << std::endl; - } - - std::cout << "\nGPU pipeline foundation is ready for full implementation" << std::endl; - std::cout << "Press Enter to exit..."; - std::cin.get(); - - return 0; -} \ No newline at end of file +} diff --git a/vav2/Vav2Player/Vav2Player/src/Common/COMWrapper.h b/vav2/Vav2Player/Vav2Player/src/Common/COMWrapper.h deleted file mode 100644 index a82848d..0000000 --- a/vav2/Vav2Player/Vav2Player/src/Common/COMWrapper.h +++ /dev/null @@ -1,87 +0,0 @@ -#pragma once - -#include - -namespace Vav2Player { - -// Simple RAII wrapper for COM objects -template -class COMWrapper { -public: - COMWrapper() : ptr_(nullptr) {} - - explicit COMWrapper(T* ptr) : ptr_(ptr) { - if (ptr_) ptr_->AddRef(); - } - - COMWrapper(const COMWrapper& other) : ptr_(other.ptr_) { - if (ptr_) ptr_->AddRef(); - } - - COMWrapper(COMWrapper&& other) noexcept : ptr_(other.ptr_) { - other.ptr_ = nullptr; - } - - ~COMWrapper() { - if (ptr_) ptr_->Release(); - } - - COMWrapper& operator=(const COMWrapper& other) { - if (this != &other) { - if (ptr_) ptr_->Release(); - ptr_ = other.ptr_; - if (ptr_) ptr_->AddRef(); - } - return *this; - } - - COMWrapper& operator=(COMWrapper&& other) noexcept { - if (this != &other) { - if (ptr_) ptr_->Release(); - ptr_ = other.ptr_; - other.ptr_ = nullptr; - } - return *this; - } - - // ComPtr-like interface - T* Get() const { return ptr_; } - T** GetAddressOf() { Reset(); return &ptr_; } - T* operator->() const { return ptr_; } - T& operator*() const { return *ptr_; } - - void Reset() { - if (ptr_) { - ptr_->Release(); - ptr_ = nullptr; - } - } - - void Attach(T* ptr) { - if (ptr_) ptr_->Release(); - ptr_ = ptr; - } - - T* Detach() { - T* temp = ptr_; - ptr_ = nullptr; - return temp; - } - - explicit operator bool() const { return ptr_ != nullptr; } - - // QueryInterface helper - template - HRESULT As(COMWrapper& result) const { - return ptr_ ? ptr_->QueryInterface(__uuidof(U), reinterpret_cast(result.GetAddressOf())) : E_POINTER; - } - -private: - T* ptr_; -}; - -// Type alias -template -using ComPtr = COMWrapper; - -} // namespace Vav2Player \ No newline at end of file diff --git a/vav2/Vav2Player/Vav2Player/src/Common/ComPtrReplacements.h b/vav2/Vav2Player/Vav2Player/src/Common/ComPtrReplacements.h deleted file mode 100644 index 06d8e43..0000000 --- a/vav2/Vav2Player/Vav2Player/src/Common/ComPtrReplacements.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include -#include - -namespace Vav2Player { - -// COM object deleter for std::shared_ptr -struct COMDeleter { - template - void operator()(T* ptr) const { - if (ptr) { - ptr->Release(); - } - } -}; - -// Type alias for COM objects with std::shared_ptr -template -using com_ptr = std::shared_ptr; - -// Helper function to create COM shared_ptr -template -com_ptr make_com_ptr(T* ptr) { - return com_ptr(ptr, COMDeleter{}); -} - -// Helper function for QueryInterface -template -com_ptr com_cast(const com_ptr& ptr) { - if (!ptr) return nullptr; - - T* result = nullptr; - HRESULT hr = ptr->QueryInterface(__uuidof(T), reinterpret_cast(&result)); - if (SUCCEEDED(hr) && result) { - return make_com_ptr(result); - } - return nullptr; -} - -// Helper for .Get() equivalent -template -T* get_raw_ptr(const com_ptr& ptr) { - return ptr.get(); -} - -// Helper for .GetAddressOf() equivalent -template -T** get_address_of(com_ptr& ptr) { - ptr.reset(); // Release current object - return reinterpret_cast(&ptr); // This is unsafe but needed for COM APIs -} - -} // namespace Vav2Player \ No newline at end of file diff --git a/vav2/Vav2Player/Vav2Player/src/Common/PermissionUtils.cpp b/vav2/Vav2Player/Vav2Player/src/Common/PermissionUtils.cpp deleted file mode 100644 index 0969dd5..0000000 --- a/vav2/Vav2Player/Vav2Player/src/Common/PermissionUtils.cpp +++ /dev/null @@ -1,253 +0,0 @@ -#include "pch.h" -#include "PermissionUtils.h" -#include -#include -#include -#include -#include -#include -#include - -namespace Vav2Player { - -PermissionUtils::PermissionResult PermissionUtils::CheckDirectoryCreatePermission(const std::filesystem::path& directory_path) { - try { - // 1. If directory already exists, check write permission - if (std::filesystem::exists(directory_path)) { - if (TestDirectoryWrite(directory_path)) { - OutputDebugStringA(("[PermissionUtils] Directory exists and writable: " + directory_path.string() + "\n").c_str()); - return PermissionResult::Granted; - } else { - OutputDebugStringA(("[PermissionUtils] Directory exists but not writable: " + directory_path.string() + "\n").c_str()); - return PermissionResult::Denied; - } - } - - // 2. Check creation permission in parent directory - std::filesystem::path parent_path = directory_path.parent_path(); - if (!std::filesystem::exists(parent_path)) { - // Recursively check if parent directory doesn't exist - auto parent_result = CheckDirectoryCreatePermission(parent_path); - if (parent_result != PermissionResult::Granted) { - return parent_result; - } - } - - // 3. Actually test temporary directory creation - std::filesystem::path test_dir = parent_path / ("test_permission_" + std::to_string(GetTickCount64())); - - std::error_code ec; - bool created = std::filesystem::create_directory(test_dir, ec); - - if (created && !ec) { - // Creation successful, cleanup - std::filesystem::remove(test_dir, ec); - OutputDebugStringA(("[PermissionUtils] Directory creation test successful: " + directory_path.string() + "\n").c_str()); - return PermissionResult::Granted; - } else { - OutputDebugStringA(("[PermissionUtils] Directory creation test failed: " + directory_path.string() + ", Error: " + ec.message() + "\n").c_str()); - return PermissionResult::Denied; - } - - } catch (const std::exception& e) { - OutputDebugStringA(("[PermissionUtils] Exception in CheckDirectoryCreatePermission: " + std::string(e.what()) + "\n").c_str()); - return PermissionResult::Error; - } -} - -PermissionUtils::PermissionResult PermissionUtils::CheckFileWritePermission(const std::filesystem::path& file_path) { - try { - // 1. Check directory existence - std::filesystem::path parent_dir = file_path.parent_path(); - if (!std::filesystem::exists(parent_dir)) { - auto dir_result = CheckDirectoryCreatePermission(parent_dir); - if (dir_result != PermissionResult::Granted) { - return dir_result; - } - } - - // 2. Test temporary file creation - std::filesystem::path test_file = parent_dir / ("test_write_" + std::to_string(GetTickCount64()) + ".tmp"); - - std::ofstream test_stream(test_file, std::ios::binary); - if (test_stream.is_open()) { - test_stream << "permission test"; - test_stream.close(); - - // Cleanup - std::error_code ec; - std::filesystem::remove(test_file, ec); - - OutputDebugStringA(("[PermissionUtils] File write test successful: " + file_path.string() + "\n").c_str()); - return PermissionResult::Granted; - } else { - OutputDebugStringA(("[PermissionUtils] File write test failed: " + file_path.string() + "\n").c_str()); - return PermissionResult::Denied; - } - - } catch (const std::exception& e) { - OutputDebugStringA(("[PermissionUtils] Exception in CheckFileWritePermission: " + std::string(e.what()) + "\n").c_str()); - return PermissionResult::Error; - } -} - -bool PermissionUtils::IsRunningAsAdmin() { - BOOL is_admin = FALSE; - PSID admin_group = nullptr; - SID_IDENTIFIER_AUTHORITY nt_authority = SECURITY_NT_AUTHORITY; - - if (AllocateAndInitializeSid(&nt_authority, 2, - SECURITY_BUILTIN_DOMAIN_RID, - DOMAIN_ALIAS_RID_ADMINS, - 0, 0, 0, 0, 0, 0, - &admin_group)) { - - if (!::CheckTokenMembership(nullptr, admin_group, &is_admin)) { - is_admin = FALSE; - } - FreeSid(admin_group); - } - - OutputDebugStringA(is_admin ? "[PermissionUtils] Running as administrator\n" : "[PermissionUtils] Not running as administrator\n"); - return is_admin == TRUE; -} - -bool PermissionUtils::RequestAdminRestart() { - wchar_t exe_path[MAX_PATH]; - DWORD path_length = GetModuleFileNameW(nullptr, exe_path, MAX_PATH); - - if (path_length == 0 || path_length == MAX_PATH) { - OutputDebugStringA("[PermissionUtils] Failed to get executable path for restart\n"); - return false; - } - - // Request administrator permission with ShellExecute - HINSTANCE result = ShellExecuteW(nullptr, L"runas", exe_path, nullptr, nullptr, SW_SHOWNORMAL); - - bool success = reinterpret_cast(result) > 32; - if (success) { - OutputDebugStringA("[PermissionUtils] Requested admin restart\n"); - // Terminate current process - PostQuitMessage(0); - } else { - OutputDebugStringA("[PermissionUtils] Failed to request admin restart\n"); - } - - return success; -} - -bool PermissionUtils::ShowPermissionDialog(const std::wstring& message, const std::wstring& title) { - int result = MessageBoxW(nullptr, message.c_str(), title.c_str(), - MB_YESNO | MB_ICONWARNING | MB_TOPMOST); - - return result == IDYES; -} - -std::filesystem::path PermissionUtils::GetSafeOutputDirectory() { - // Try safe directories in priority order - std::vector safe_directories; - - // 1. User documents folder - wchar_t* documents_path = nullptr; - if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Documents, 0, nullptr, &documents_path))) { - safe_directories.push_back(std::filesystem::path(documents_path) / "Vav2Player"); - CoTaskMemFree(documents_path); - } - - // 2. User temporary folder - wchar_t temp_path[MAX_PATH]; - if (GetTempPathW(MAX_PATH, temp_path)) { - safe_directories.push_back(std::filesystem::path(temp_path) / "Vav2Player"); - } - - // 3. Current user profile folder - wchar_t* profile_path = nullptr; - if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Profile, 0, nullptr, &profile_path))) { - safe_directories.push_back(std::filesystem::path(profile_path) / "Vav2Player"); - CoTaskMemFree(profile_path); - } - - // 4. Same directory as executable - wchar_t exe_path[MAX_PATH]; - if (GetModuleFileNameW(nullptr, exe_path, MAX_PATH)) { - std::filesystem::path exe_dir = std::filesystem::path(exe_path).parent_path(); - safe_directories.push_back(exe_dir / "output"); - } - - // Check permissions for each directory - for (const auto& dir : safe_directories) { - if (CheckDirectoryCreatePermission(dir) == PermissionResult::Granted) { - OutputDebugStringA(("[PermissionUtils] Found safe directory: " + dir.string() + "\n").c_str()); - return dir; - } - } - - // Default value when all attempts fail - OutputDebugStringA("[PermissionUtils] No safe directory found, using fallback\n"); - return std::filesystem::path("C:\\temp\\Vav2Player"); -} - -PermissionUtils::PermissionResult PermissionUtils::CheckAndHandlePermissions(const std::filesystem::path& output_directory) { - OutputDebugStringA(("[PermissionUtils] Checking permissions for: " + output_directory.string() + "\n").c_str()); - - // 1. Basic permission check - auto result = CheckDirectoryCreatePermission(output_directory); - - if (result == PermissionResult::Granted) { - return result; - } - - // 2. Handle case when permission is denied - std::wstring message; - - if (result == PermissionResult::Denied) { - std::wstring dir_path = std::wstring(output_directory.string().begin(), output_directory.string().end()); - message = L"No write permission to output directory:\n" + dir_path + - L"\n\nWould you like to restart with administrator privileges?\n(Selecting No will use a safe location)"; - } else { - std::wstring dir_path = std::wstring(output_directory.string().begin(), output_directory.string().end()); - message = L"An error occurred while checking directory permissions:\n" + dir_path + - L"\n\nWould you like to restart with administrator privileges?"; - } - - // 3. Provide choice to user - if (ShowPermissionDialog(message, L"Directory Permission Required")) { - // Request restart with administrator privileges - if (RequestAdminRestart()) { - // Restart request successful (app will terminate) - return PermissionResult::Granted; - } else { - // Restart failed - use safe directory - return PermissionResult::Denied; - } - } else { - // User denied administrator privileges - use safe directory - return PermissionResult::Denied; - } -} - -bool PermissionUtils::TestDirectoryWrite(const std::filesystem::path& directory_path) { - try { - std::filesystem::path test_file = directory_path / ("write_test_" + std::to_string(GetTickCount64()) + ".tmp"); - - std::ofstream test_stream(test_file, std::ios::binary); - if (!test_stream.is_open()) { - return false; - } - - test_stream << "write permission test"; - test_stream.close(); - - // Cleanup - std::error_code ec; - std::filesystem::remove(test_file, ec); - - return true; - - } catch (const std::exception&) { - return false; - } -} - - -} // namespace Vav2Player \ No newline at end of file diff --git a/vav2/Vav2Player/Vav2Player/src/Common/PermissionUtils.h b/vav2/Vav2Player/Vav2Player/src/Common/PermissionUtils.h deleted file mode 100644 index 0f960b1..0000000 --- a/vav2/Vav2Player/Vav2Player/src/Common/PermissionUtils.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once -#include -#include -#include - -namespace Vav2Player { - -class PermissionUtils { -public: - // 권한 체크 결과 - enum class PermissionResult { - Granted, // 권한 있음 - Denied, // 권한 없음 - Error // 체크 중 오류 발생 - }; - - // 디렉토리 생성 권한 체크 - static PermissionResult CheckDirectoryCreatePermission(const std::filesystem::path& directory_path); - - // 파일 쓰기 권한 체크 - static PermissionResult CheckFileWritePermission(const std::filesystem::path& file_path); - - // 관리자 권한으로 실행 중인지 확인 - static bool IsRunningAsAdmin(); - - // 관리자 권한으로 재시작 요청 - static bool RequestAdminRestart(); - - // 사용자에게 권한 관련 메시지 표시 - static bool ShowPermissionDialog(const std::wstring& message, const std::wstring& title = L"권한 필요"); - - // 안전한 출력 디렉토리 제안 - static std::filesystem::path GetSafeOutputDirectory(); - - // 권한 체크 및 처리 (통합 함수) - static PermissionResult CheckAndHandlePermissions(const std::filesystem::path& output_directory); - -private: - // 디렉토리에 대한 실제 쓰기 테스트 - static bool TestDirectoryWrite(const std::filesystem::path& directory_path); - -}; - -} // namespace Vav2Player \ No newline at end of file diff --git a/vav2/Vav2Player/Vav2Player/src/Common/StdComPtr.h b/vav2/Vav2Player/Vav2Player/src/Common/StdComPtr.h deleted file mode 100644 index 9ef45b9..0000000 --- a/vav2/Vav2Player/Vav2Player/src/Common/StdComPtr.h +++ /dev/null @@ -1,161 +0,0 @@ -#pragma once - -#include -#include - -namespace Vav2Player { - -// Lightweight std-based ComPtr replacement -// Maintains Microsoft::WRL::ComPtr compatibility while using std libraries -template -class StdComPtr { -private: - T* ptr_; - -public: - StdComPtr() : ptr_(nullptr) {} - - explicit StdComPtr(T* ptr) : ptr_(ptr) {} - - StdComPtr(const StdComPtr& other) : ptr_(other.ptr_) { - if (ptr_) ptr_->AddRef(); - } - - StdComPtr(StdComPtr&& other) noexcept : ptr_(other.ptr_) { - other.ptr_ = nullptr; - } - - ~StdComPtr() { - if (ptr_) ptr_->Release(); - } - - StdComPtr& operator=(const StdComPtr& other) { - if (this != &other) { - if (ptr_) ptr_->Release(); - ptr_ = other.ptr_; - if (ptr_) ptr_->AddRef(); - } - return *this; - } - - StdComPtr& operator=(StdComPtr&& other) noexcept { - if (this != &other) { - if (ptr_) ptr_->Release(); - ptr_ = other.ptr_; - other.ptr_ = nullptr; - } - return *this; - } - - StdComPtr& operator=(T* ptr) { - if (ptr_) ptr_->Release(); - ptr_ = ptr; - if (ptr_) ptr_->AddRef(); - return *this; - } - - // Microsoft::WRL::ComPtr compatible interface - T* Get() const { return ptr_; } - T** GetAddressOf() { - Reset(); - return &ptr_; - } - T* const* GetAddressOf() const { - return &ptr_; - } - T** ReleaseAndGetAddressOf() { - Reset(); - return &ptr_; - } - T* operator->() const { return ptr_; } - T& operator*() const { return *ptr_; } - explicit operator bool() const { return ptr_ != nullptr; } - - // Address-of operator for COM API compatibility - T** operator&() { - Reset(); - return &ptr_; - } - - // Comparison operators - bool operator==(std::nullptr_t) const { return ptr_ == nullptr; } - bool operator!=(std::nullptr_t) const { return ptr_ != nullptr; } - bool operator==(const StdComPtr& other) const { return ptr_ == other.ptr_; } - bool operator!=(const StdComPtr& other) const { return ptr_ != other.ptr_; } - bool operator<(const StdComPtr& other) const { return ptr_ < other.ptr_; } - - void Reset() { - if (ptr_) { - ptr_->Release(); - ptr_ = nullptr; - } - } - - void Attach(T* ptr) { - if (ptr_) ptr_->Release(); - ptr_ = ptr; - } - - T* Detach() { - T* temp = ptr_; - ptr_ = nullptr; - return temp; - } - - template - HRESULT As(StdComPtr* result) const { - return ptr_ ? ptr_->QueryInterface(__uuidof(U), reinterpret_cast(result->GetAddressOf())) : E_POINTER; - } - - // Handle COM QueryInterface with raw pointer-to-pointer - template - HRESULT As(U** result) const { - return ptr_ ? ptr_->QueryInterface(__uuidof(U), reinterpret_cast(result)) : E_POINTER; - } - - // Direct QueryInterface support - HRESULT QueryInterface(const IID& riid, void** ppvObject) const { - return ptr_ ? ptr_->QueryInterface(riid, ppvObject) : E_POINTER; - } - - // Support for IID_PPV_ARGS macro compatibility - template - HRESULT As(const IID& riid, void** ppvObject) const { - return ptr_ ? ptr_->QueryInterface(riid, ppvObject) : E_POINTER; - } -}; - -// Type alias for easy migration -template -using ComPtr = StdComPtr; - -// Helper function to make IID_PPV_ARGS work with StdComPtr -template -void** IID_PPV_ARGS_Helper(StdComPtr* pp) { - return reinterpret_cast(pp->GetAddressOf()); -} - -// Global comparison operators for symmetry -template -bool operator==(std::nullptr_t, const StdComPtr& ptr) { return ptr == nullptr; } - -template -bool operator!=(std::nullptr_t, const StdComPtr& ptr) { return ptr != nullptr; } - -} // namespace Vav2Player - -// Allow IID_PPV_ARGS to work with Vav2Player::StdComPtr -namespace { - template - void** IID_PPV_ARGS_Helper(Vav2Player::StdComPtr* pp) { - return reinterpret_cast(pp->GetAddressOf()); - } -} - -// Macro to easily switch between Microsoft WRL and std version -#ifdef USE_STD_COMPTR - #define COMPTR_NAMESPACE Vav2Player -#else - #include - #define COMPTR_NAMESPACE Microsoft::WRL -#endif \ No newline at end of file diff --git a/vav2/Vav2Player/Vav2Player/src/Decoder/AV1Decoder.cpp b/vav2/Vav2Player/Vav2Player/src/Decoder/AV1Decoder.cpp deleted file mode 100644 index a8daaa6..0000000 --- a/vav2/Vav2Player/Vav2Player/src/Decoder/AV1Decoder.cpp +++ /dev/null @@ -1,1371 +0,0 @@ -#include "pch.h" -#include "AV1Decoder.h" -#include "../Rendering/D3D12VideoRenderer.h" -// Note: DirectTextureAllocator include removed during Phase 1 simplification -#include -#include - -namespace Vav2Player { - -AV1Decoder::AV1Decoder() - : m_dav1d_context(nullptr) - , m_initialized(false) { - // Note: m_directTextureAllocator removed during Phase 1 simplification - // Initialize default AV1 settings with conservative thread usage - m_av1_settings.max_frame_delay = 1; - m_av1_settings.num_threads = std::min(4, (int)std::thread::hardware_concurrency()); // Limit to 4 threads max - m_av1_settings.apply_grain = true; - m_av1_settings.all_layers = false; -} - -AV1Decoder::~AV1Decoder() { - Cleanup(); -} - -bool AV1Decoder::Initialize(const VideoMetadata& metadata) { - if (m_initialized) { - Cleanup(); - } - - m_metadata = metadata; - - if (!InitializeDav1d()) { - LogError("Failed to initialize dav1d decoder"); - return false; - } - - m_initialized = true; - ResetStats(); - return true; -} - -void AV1Decoder::Cleanup() { - if (!m_initialized) return; - - CleanupDav1d(); - m_initialized = false; -} - -bool AV1Decoder::IsInitialized() const { - return m_initialized; -} - -bool AV1Decoder::DecodeFrame(const VideoPacket& input_packet, VideoFrame& output_frame) { - if (!input_packet.IsValid()) { - return false; - } - - return DecodeFrame(input_packet.data.get(), input_packet.size, output_frame); -} - -bool AV1Decoder::DecodeFrame(const uint8_t* packet_data, size_t packet_size, VideoFrame& output_frame) { - if (!m_initialized || !packet_data || packet_size == 0) { - IncrementDecodeErrors(); - return false; - } - - auto start_time = std::chrono::high_resolution_clock::now(); - - // Create dav1d input data (using memory copy method) - Dav1dData dav1d_data; - uint8_t* buffer = dav1d_data_create(&dav1d_data, packet_size); - if (!buffer) { - LogError("Failed to create dav1d data", -1); - IncrementDecodeErrors(); - return false; - } - - // Copy packet data to dav1d buffer - memcpy(buffer, packet_data, packet_size); - - // Send data to decoder - int res = dav1d_send_data(m_dav1d_context, &dav1d_data); - if (res < 0) { - LogError("Failed to send data to decoder", res); - IncrementDecodeErrors(); - return false; - } - - // Get decoded picture - Dav1dPicture dav1d_picture = {}; - res = dav1d_get_picture(m_dav1d_context, &dav1d_picture); - m_lastDecodeResult = res; // Store result for EAGAIN differentiation - - if (res < 0) { - if (res != DAV1D_ERR(EAGAIN)) { - LogError("Failed to get decoded picture", res); - IncrementDecodeErrors(); - } - // EAGAIN means no frame is ready yet, but it's not an error - just return false to try again - return false; - } - - // Convert dav1d picture to VideoFrame - if (!ConvertDav1dPicture(dav1d_picture, output_frame)) { - dav1d_picture_unref(&dav1d_picture); - IncrementDecodeErrors(); - return false; - } - - // Release picture reference - dav1d_picture_unref(&dav1d_picture); - - auto end_time = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration_cast(end_time - start_time); - double decode_time_ms = duration.count() / 1000.0; - - UpdateDecodingStats(decode_time_ms, packet_size); - IncrementFramesDecoded(); - - return true; -} - -bool AV1Decoder::Reset() { - if (!m_initialized) return false; - - // Flush internal buffers of dav1d context - dav1d_flush(m_dav1d_context); - - // Reset statistics - ResetStats(); - - return true; -} - -bool AV1Decoder::Flush() { - if (!m_initialized) return false; - - // Retrieve all remaining frames from decoder - // Used to process delayed frames at the end of stream - while (true) { - Dav1dPicture picture = {}; - int res = dav1d_get_picture(m_dav1d_context, &picture); - if (res < 0) { - // Exit when no more frames available or error occurs - break; - } - - // Release frame reference if available (currently ignored) - // In actual streaming, these frames should be processed - dav1d_picture_unref(&picture); - } - - return true; -} - -std::string AV1Decoder::GetCodecName() const { - return "AV1"; -} - -VideoCodecType AV1Decoder::GetCodecType() const { - return VideoCodecType::AV1; -} - -std::string AV1Decoder::GetVersion() const { - return AV1Utils::GetDav1dVersion(); -} - -IVideoDecoder::DecoderStats AV1Decoder::GetStats() const { - return m_stats; -} - -void AV1Decoder::ResetStats() { - m_stats = {}; -} - -bool AV1Decoder::SetOption(const std::string& key, const std::string& value) { - // Process AV1-specific options - if (key == "max_frame_delay") { - try { - m_av1_settings.max_frame_delay = std::stoi(value); - return true; - } catch (...) { - return false; - } - } - else if (key == "num_threads") { - try { - m_av1_settings.num_threads = std::stoi(value); - return true; - } catch (...) { - return false; - } - } - else if (key == "apply_grain") { - m_av1_settings.apply_grain = (value == "true" || value == "1"); - return true; - } - else if (key == "all_layers") { - m_av1_settings.all_layers = (value == "true" || value == "1"); - return true; - } - - return false; // Unsupported option -} - -std::string AV1Decoder::GetOption(const std::string& key) const { - if (key == "max_frame_delay") { - return std::to_string(m_av1_settings.max_frame_delay); - } - else if (key == "num_threads") { - return std::to_string(m_av1_settings.num_threads); - } - else if (key == "apply_grain") { - return m_av1_settings.apply_grain ? "true" : "false"; - } - else if (key == "all_layers") { - return m_av1_settings.all_layers ? "true" : "false"; - } - - return ""; -} - -void AV1Decoder::SetAV1Settings(const AV1Settings& settings) { - m_av1_settings = settings; - - if (m_initialized) { - // Update dav1d settings when runtime configuration changes - SetupDav1dSettings(); - } -} - -AV1Decoder::AV1Settings AV1Decoder::GetAV1Settings() const { - return m_av1_settings; -} - -bool AV1Decoder::InitializeDav1d() { - // Initialize dav1d default settings - dav1d_default_settings(&m_dav1d_settings); - - if (!SetupDav1dSettings()) { - return false; - } - - // Safety validation: ensure critical settings are within valid ranges - if (m_dav1d_settings.n_threads < 0 || m_dav1d_settings.n_threads > 64) { - LogError("Invalid thread count: " + std::to_string(m_dav1d_settings.n_threads)); - m_dav1d_settings.n_threads = 0; // Auto-detect - } - - if (m_dav1d_settings.max_frame_delay < 0 || m_dav1d_settings.max_frame_delay > 256) { - LogError("Invalid frame delay: " + std::to_string(m_dav1d_settings.max_frame_delay)); - m_dav1d_settings.max_frame_delay = 1; // Safe default - } - - // CRITICAL: Ensure allocator callbacks are properly set (dav1d requires BOTH to be non-NULL) - if (!m_dav1d_settings.allocator.alloc_picture_callback) { - LogError("FATAL: alloc_picture_callback is NULL - dav1d_open will fail with EINVAL"); - return false; - } - if (!m_dav1d_settings.allocator.release_picture_callback) { - LogError("FATAL: release_picture_callback is NULL - dav1d_open will fail with EINVAL"); - return false; - } - - std::cout << "[AV1Decoder] Allocator callbacks verified: alloc=" - << (void*)m_dav1d_settings.allocator.alloc_picture_callback - << " release=" << (void*)m_dav1d_settings.allocator.release_picture_callback << std::endl; - - std::cout << "[AV1Decoder] Initializing dav1d with validated settings" << std::endl; - std::cout << "[AV1Decoder] - n_threads: " << m_dav1d_settings.n_threads << std::endl; - std::cout << "[AV1Decoder] - max_frame_delay: " << m_dav1d_settings.max_frame_delay << std::endl; - std::cout << "[AV1Decoder] - apply_grain: " << m_dav1d_settings.apply_grain << std::endl; - std::cout << "[AV1Decoder] - all_layers: " << m_dav1d_settings.all_layers << std::endl; - - // SAFETY: Try to call dav1d_version first to ensure library is accessible - try { - const char* version = dav1d_version(); - std::cout << "[AV1Decoder] dav1d version: " << (version ? version : "unknown") << std::endl; - } catch (...) { - LogError("Failed to access dav1d library functions"); - return false; - } - - // Create dav1d context with exception handling - std::cout << "[AV1Decoder] Calling dav1d_open..." << std::endl; - int res = -1; - try { - res = dav1d_open(&m_dav1d_context, &m_dav1d_settings); - std::cout << "[AV1Decoder] dav1d_open returned: " << res << std::endl; - } catch (...) { - LogError("Exception occurred during dav1d_open"); - return false; - } - - if (res < 0) { - LogError("Failed to open dav1d context", res); - return false; - } - - return true; -} - -void AV1Decoder::CleanupDav1d() { - if (m_dav1d_context) { - dav1d_close(&m_dav1d_context); - m_dav1d_context = nullptr; - } -} - -bool AV1Decoder::SetupDav1dSettings() { - // Convert AV1 settings to dav1d settings - m_dav1d_settings.max_frame_delay = m_av1_settings.max_frame_delay; - m_dav1d_settings.n_threads = m_av1_settings.num_threads; - m_dav1d_settings.apply_grain = m_av1_settings.apply_grain; - m_dav1d_settings.all_layers = m_av1_settings.all_layers; - - // Advanced optimization settings for dav1d (available in current version) - m_dav1d_settings.strict_std_compliance = m_av1_settings.strict_std_compliance ? 1 : 0; - m_dav1d_settings.operating_point = m_av1_settings.operating_point ? 1 : 0; - - // Set inloop filters based on performance preference - if (m_av1_settings.enable_inloop_filters) { - m_dav1d_settings.inloop_filters = DAV1D_INLOOPFILTER_ALL; // Enable for quality - } else { - m_dav1d_settings.inloop_filters = DAV1D_INLOOPFILTER_NONE; // Disable for max performance - } - - // SAFETY: Use default allocator - dav1d requires non-NULL allocator callbacks - // Setting to NULL causes DAV1D_ERR(EINVAL) in dav1d_open - // Leave allocator as default (initialized by dav1d_default_settings) - - // Only set DirectTextureAllocator if it's properly validated - // DISABLED for now to prevent crashes - enable only after thorough testing - bool use_direct_texture_allocator = false; - - // Note: DirectTextureAllocator integration removed during Phase 1 simplification - // Use default allocator (safest option) - std::cout << "[AV1Decoder] Using default allocator (DirectTextureAllocator disabled)" << std::endl; - // Keep default allocator set by dav1d_default_settings() - do NOT modify - - std::cout << "[AV1Decoder] Applied dav1d settings: threads=" << m_dav1d_settings.n_threads - << ", max_delay=" << m_dav1d_settings.max_frame_delay - << ", strict_compliance=" << m_dav1d_settings.strict_std_compliance - << ", inloop_filters=" << static_cast(m_dav1d_settings.inloop_filters) - << ", apply_grain=" << m_dav1d_settings.apply_grain << std::endl; - - return true; -} - -bool AV1Decoder::ConvertDav1dPicture(const Dav1dPicture& dav1d_pic, VideoFrame& output_frame) { - output_frame.Reset(); - - // Set basic information - output_frame.width = static_cast(dav1d_pic.p.w); - output_frame.height = static_cast(dav1d_pic.p.h); - output_frame.color_space = ConvertDav1dPixelFormat(dav1d_pic); - - // Allocate YUV420P data - if (!output_frame.AllocateYUV420P(output_frame.width, output_frame.height)) { - LogError("Failed to allocate YUV420P frame memory"); - return false; - } - - // Get YUV data pointers from dav1d picture - const uint8_t* src_y = static_cast(dav1d_pic.data[0]); - const uint8_t* src_u = static_cast(dav1d_pic.data[1]); - const uint8_t* src_v = static_cast(dav1d_pic.data[2]); - - if (!src_y || !src_u || !src_v) { - LogError("Invalid dav1d picture data pointers"); - return false; - } - - // Get stride information - ptrdiff_t src_y_stride = dav1d_pic.stride[0]; - ptrdiff_t src_u_stride = dav1d_pic.stride[1]; - ptrdiff_t src_v_stride = dav1d_pic.stride[1]; // U/V have same stride - - // Copy Y plane (line by line) - uint8_t* dst_y = output_frame.y_plane.get(); - for (uint32_t y = 0; y < output_frame.height; ++y) { - memcpy(dst_y + y * output_frame.y_stride, - src_y + y * src_y_stride, - output_frame.width); - } - - // Copy U plane (line by line) - uint8_t* dst_u = output_frame.u_plane.get(); - uint32_t uv_height = output_frame.height / 2; - uint32_t uv_width = output_frame.width / 2; - for (uint32_t y = 0; y < uv_height; ++y) { - memcpy(dst_u + y * output_frame.u_stride, - src_u + y * src_u_stride, - uv_width); - } - - // Copy V plane (line by line) - uint8_t* dst_v = output_frame.v_plane.get(); - for (uint32_t y = 0; y < uv_height; ++y) { - memcpy(dst_v + y * output_frame.v_stride, - src_v + y * src_v_stride, - uv_width); - } - - // Set frame metadata - output_frame.is_valid = true; - - return true; -} - -void AV1Decoder::UpdateDecodingStats(double decode_time_ms, size_t input_bytes) { - UpdateDecodeTime(decode_time_ms); - AddBytesProcessed(input_bytes); -} - -ColorSpace AV1Decoder::ConvertDav1dPixelFormat(const Dav1dPicture& pic) { - // Convert dav1d pixel format to ColorSpace - switch (pic.p.layout) { - case DAV1D_PIXEL_LAYOUT_I420: - return ColorSpace::YUV420P; - case DAV1D_PIXEL_LAYOUT_I422: - return ColorSpace::YUV422P; - case DAV1D_PIXEL_LAYOUT_I444: - return ColorSpace::YUV444P; - default: - // Default to YUV420P for unsupported formats - LogError("Unsupported pixel layout: " + std::to_string(static_cast(pic.p.layout))); - return ColorSpace::YUV420P; - } -} - -std::string AV1Decoder::GetDav1dErrorString(int error_code) { - // Convert dav1d error code to meaningful string - switch (error_code) { - case DAV1D_ERR(EAGAIN): - return "EAGAIN (need more data)"; - case DAV1D_ERR(EINVAL): - return "EINVAL (invalid parameter)"; - case DAV1D_ERR(ENOMEM): - return "ENOMEM (out of memory)"; - case DAV1D_ERR(EIO): - return "EIO (input/output error)"; - default: - return "dav1d error: " + std::to_string(error_code); - } -} - -void AV1Decoder::LogError(const std::string& message, int error_code) { - std::string full_message = "[AV1Decoder] " + message; - if (error_code != 0) { - full_message += " (" + GetDav1dErrorString(error_code) + ")"; - } - - // TODO: Integrate with actual logging system - std::cerr << full_message << std::endl; -} - -// AV1Utils implementation -namespace AV1Utils { - -OBUType GetOBUType(const uint8_t* data, size_t size) { - // TODO: Implement AV1 OBU header parsing - if (!data || size < 1) return OBUType::UNKNOWN; - - uint8_t obu_type = (data[0] >> 3) & 0x0F; - return static_cast(obu_type); -} - -bool IsKeyFrame(const uint8_t* data, size_t size) { - // TODO: Implement AV1 key frame detection logic - return false; -} - -std::string GetDav1dVersion() { - // Get dav1d library version information - const char* version = dav1d_version(); - return version ? std::string("dav1d ") + version : "dav1d (version unknown)"; -} - -std::string GetDav1dCopyright() { - return "Copyright (c) 2018-2024, VideoLAN and dav1d authors"; -} - -} // namespace AV1Utils - -/* -// Optimized decoding methods using memory pool - REMOVED in Phase 1 simplification -ScopedFrame AV1Decoder::DecodeFramePooled(const VideoPacket& input_packet) { - return DecodeFramePooled(input_packet.data.get(), input_packet.size); -} - -ScopedFrame AV1Decoder::DecodeFramePooled(const uint8_t* packet_data, size_t packet_size) { - if (!m_initialized || !packet_data || packet_size == 0) { - return ScopedFrame(FramePool::PooledFrame{}); - } - - auto decode_start = std::chrono::high_resolution_clock::now(); - - // Prepare dav1d data structure - Dav1dData data; - uint8_t* buffer = dav1d_data_create(&data, packet_size); - if (!buffer) { - m_stats.decode_errors++; - return ScopedFrame(FramePool::PooledFrame{}); - } - - // Copy packet data (can be optimized with zero-copy in the future) - memcpy(buffer, packet_data, packet_size); - - // Send data to decoder - int result = dav1d_send_data(m_dav1d_context, &data); - if (result < 0 && result != -EAGAIN) { - m_stats.decode_errors++; - return ScopedFrame(FramePool::PooledFrame{}); - } - - // Get decoded frame - // WARNING: Dav1dPicture initialization required to prevent assertion error - Dav1dPicture picture = {}; - result = dav1d_get_picture(m_dav1d_context, &picture); - if (result < 0) { - if (result != -EAGAIN) { - m_stats.decode_errors++; - } - return ScopedFrame(FramePool::PooledFrame{}); - } - - // Determine ColorSpace - ColorSpace color_space = ColorSpace::YUV420P; - if (picture.p.layout == DAV1D_PIXEL_LAYOUT_I422) { - color_space = ColorSpace::YUV422P; - } else if (picture.p.layout == DAV1D_PIXEL_LAYOUT_I444) { - color_space = ColorSpace::YUV444P; - } - - // Allocate frame from memory pool - auto pooled_frame = FramePool::GetInstance().AcquireFrame( - picture.p.w, picture.p.h, color_space); - - if (!pooled_frame.frame) { - dav1d_picture_unref(&picture); - m_stats.decode_errors++; - return ScopedFrame(FramePool::PooledFrame{}); - } - - // Copy data from Dav1dPicture to VideoFrame - if (!ConvertDav1dPicture(picture, *pooled_frame.frame)) { - dav1d_picture_unref(&picture); - m_stats.decode_errors++; - return ScopedFrame(FramePool::PooledFrame{}); - } - - // Set metadata - pooled_frame.frame->is_valid = true; - pooled_frame.frame->timestamp_seconds = 0.0; // To be set by caller - pooled_frame.frame->frame_index = 0; // To be set by caller - - // Measure decoding time - auto decode_end = std::chrono::high_resolution_clock::now(); - auto decode_duration = std::chrono::duration(decode_end - decode_start); - - // Update statistics - m_stats.frames_decoded++; - double decode_time = decode_duration.count(); - m_total_decode_time_ms += decode_time; - m_stats.avg_decode_time_ms = m_total_decode_time_ms / m_stats.frames_decoded; - - // Clean up dav1d resources - dav1d_picture_unref(&picture); - - std::cout << "[AV1Decoder] Pooled decode successful - " << pooled_frame.frame->width - << "x" << pooled_frame.frame->height << " in " << decode_time << "ms" << std::endl; - - return ScopedFrame(std::move(pooled_frame)); -} -*/ - -// Zero-copy decoding implementation -void AV1Decoder::DummyFreeCallback(const uint8_t* data, void* cookie) { - // WARNING: Critical zero-copy memory management considerations - // - // This callback is called when dav1d has finished using the packet data. - // However, in the current implementation, packet ownership belongs to the caller (VideoPacket), - // so we do nothing here (dummy callback). - // - // Important considerations: - // 1. Original packet_data must remain valid until this callback is called - // 2. VideoPacket lifetime must be guaranteed until decoding completion - // 3. Additional synchronization may be needed in multi-threaded environments - // - // Future improvements: - // - Change callback implementation if actual memory deallocation is needed - // - Consider using reference counting or smart pointers - - (void)data; (void)cookie; // Prevent unused parameter warnings -} - -bool AV1Decoder::DecodeFrameZeroCopy(const uint8_t* packet_data, size_t packet_size, VideoFrame& output_frame) { - // SAFETY CHECK: Verify essential conditions for zero-copy usage - if (!m_initialized || !packet_data || packet_size == 0) { - LogError("Invalid input for zero-copy decoding"); - IncrementDecodeErrors(); - return false; - } - - auto decode_start = std::chrono::high_resolution_clock::now(); - - // CRITICAL: Zero-copy memory wrapping - // WARNING: packet_data may still be referenced by dav1d after this function returns! - // Caller must guarantee the lifetime of packet_data - Dav1dData dav1d_data; - int wrap_result = dav1d_data_wrap(&dav1d_data, packet_data, packet_size, DummyFreeCallback, nullptr); - if (wrap_result < 0) { - LogError("Failed to wrap data for zero-copy decoding", wrap_result); - IncrementDecodeErrors(); - return false; - } - - // Send data to decoder - int res = dav1d_send_data(m_dav1d_context, &dav1d_data); - if (res < 0) { - LogError("Failed to send data to decoder", res); - IncrementDecodeErrors(); - return false; - } - - // Decode frame - // WARNING: Dav1dPicture initialization required to prevent assertion error - Dav1dPicture dav1d_picture = {}; - res = dav1d_get_picture(m_dav1d_context, &dav1d_picture); - if (res < 0) { - if (res != -EAGAIN) { - LogError("Failed to get decoded picture", res); - IncrementDecodeErrors(); - } - return false; - } - - // Convert dav1d picture to VideoFrame - bool convert_success = ConvertDav1dPicture(dav1d_picture, output_frame); - - // Clean up dav1d resources - dav1d_picture_unref(&dav1d_picture); - - if (!convert_success) { - LogError("Failed to convert dav1d picture to VideoFrame"); - IncrementDecodeErrors(); - return false; - } - - // Update performance statistics - auto decode_end = std::chrono::high_resolution_clock::now(); - auto decode_duration = std::chrono::duration(decode_end - decode_start); - UpdateDecodingStats(decode_duration.count(), packet_size); - - std::cout << "[AV1Decoder] Zero-copy decode successful - " << output_frame.width - << "x" << output_frame.height << " in " << decode_duration.count() << "ms" << std::endl; - - return true; -} - -/* -ScopedFrame AV1Decoder::DecodeFramePooledZeroCopy(const uint8_t* packet_data, size_t packet_size) { - // SAFETY CHECK: Considerations when using zero-copy with memory pool combination - if (!m_initialized || !packet_data || packet_size == 0) { - return ScopedFrame(FramePool::PooledFrame{}); - } - - auto decode_start = std::chrono::high_resolution_clock::now(); - - // CRITICAL: Zero-copy + memory pool optimization combination - // WARNING: Must consider both packet_data lifetime and frame reuse from pool - Dav1dData data; - int wrap_result = dav1d_data_wrap(&data, packet_data, packet_size, DummyFreeCallback, nullptr); - if (wrap_result < 0) { - m_stats.decode_errors++; - return ScopedFrame(FramePool::PooledFrame{}); - } - - // Send data to decoder - int result = dav1d_send_data(m_dav1d_context, &data); - if (result < 0 && result != -EAGAIN) { - m_stats.decode_errors++; - return ScopedFrame(FramePool::PooledFrame{}); - } - - // Get decoded frame - // WARNING: Dav1dPicture initialization required to prevent assertion error - Dav1dPicture picture = {}; - result = dav1d_get_picture(m_dav1d_context, &picture); - if (result < 0) { - if (result != -EAGAIN) { - m_stats.decode_errors++; - } - return ScopedFrame(FramePool::PooledFrame{}); - } - - // Determine ColorSpace - ColorSpace color_space = ColorSpace::YUV420P; - if (picture.p.layout == DAV1D_PIXEL_LAYOUT_I422) { - color_space = ColorSpace::YUV422P; - } else if (picture.p.layout == DAV1D_PIXEL_LAYOUT_I444) { - color_space = ColorSpace::YUV444P; - } - - // Allocate frame from memory pool - auto pooled_frame = FramePool::GetInstance().AcquireFrame( - picture.p.w, picture.p.h, color_space); - - if (!pooled_frame.frame) { - dav1d_picture_unref(&picture); - m_stats.decode_errors++; - return ScopedFrame(FramePool::PooledFrame{}); - } - - // Copy data from Dav1dPicture to VideoFrame - if (!ConvertDav1dPicture(picture, *pooled_frame.frame)) { - dav1d_picture_unref(&picture); - m_stats.decode_errors++; - return ScopedFrame(FramePool::PooledFrame{}); - } - - // Set metadata - pooled_frame.frame->is_valid = true; - pooled_frame.frame->timestamp_seconds = 0.0; // To be set by caller - - // Update statistics - auto decode_end = std::chrono::high_resolution_clock::now(); - auto decode_duration = std::chrono::duration(decode_end - decode_start); - - m_stats.frames_decoded++; - double decode_time = decode_duration.count(); - m_total_decode_time_ms += decode_time; - m_stats.avg_decode_time_ms = m_total_decode_time_ms / m_stats.frames_decoded; - - // Clean up dav1d resources - dav1d_picture_unref(&picture); - - std::cout << "[AV1Decoder] Zero-copy pooled decode successful - " << pooled_frame.frame->width - << "x" << pooled_frame.frame->height << " in " << decode_time << "ms" << std::endl; - - return ScopedFrame(std::move(pooled_frame)); -} -*/ - -bool AV1Decoder::DecodeFrameToGPU(const uint8_t* packet_data, size_t packet_size, - uint8_t* yMappedBuffer, uint8_t* uMappedBuffer, uint8_t* vMappedBuffer, - uint32_t yRowPitch, uint32_t uRowPitch, uint32_t vRowPitch, - uint32_t videoWidth, uint32_t videoHeight) -{ - // Safety checks - if (!m_initialized || !packet_data || packet_size == 0) { - LogError("Invalid input for GPU direct decoding"); - IncrementDecodeErrors(); - return false; - } - - if (!yMappedBuffer || !uMappedBuffer || !vMappedBuffer) { - LogError("Invalid GPU mapped buffers provided"); - IncrementDecodeErrors(); - return false; - } - - auto decode_start = std::chrono::high_resolution_clock::now(); - - // Prepare zero-copy packet (directly referenced by dav1d) - Dav1dData data = {}; - if (dav1d_data_wrap(&data, packet_data, packet_size, DummyFreeCallback, nullptr) < 0) { - LogError("Failed to wrap packet data for GPU decoding"); - IncrementDecodeErrors(); - return false; - } - - // Send packet to dav1d - int ret = dav1d_send_data(m_dav1d_context, &data); - if (ret < 0 && ret != -EAGAIN) { - LogError("Failed to send data to dav1d for GPU decoding: " + std::to_string(ret)); - IncrementDecodeErrors(); - return false; - } - - // Get decoded frame - Dav1dPicture picture = {}; - ret = dav1d_get_picture(m_dav1d_context, &picture); - if (ret < 0) { - if (ret != -EAGAIN) { - LogError("Failed to get decoded picture for GPU decoding: " + std::to_string(ret)); - IncrementDecodeErrors(); - } - return false; - } - - // Validate frame dimensions - if (picture.p.w != (int)videoWidth || picture.p.h != (int)videoHeight) { - LogError("Frame dimension mismatch: expected " + std::to_string(videoWidth) + "x" + - std::to_string(videoHeight) + ", got " + std::to_string(picture.p.w) + "x" + - std::to_string(picture.p.h)); - dav1d_picture_unref(&picture); - IncrementDecodeErrors(); - return false; - } - - // Validate pixel format (must be YUV420P for now) - if (picture.p.layout != DAV1D_PIXEL_LAYOUT_I420) { - LogError("Unsupported pixel format for GPU direct decoding. Only YUV420P supported."); - dav1d_picture_unref(&picture); - IncrementDecodeErrors(); - return false; - } - - // Calculate UV dimensions - uint32_t uvWidth = (videoWidth + 1) / 2; - uint32_t uvHeight = (videoHeight + 1) / 2; - - // Direct copy to GPU mapped buffers - bool copySuccess = true; - - try { - // Copy Y plane - const uint8_t* ySrc = (const uint8_t*)picture.data[0]; - for (uint32_t y = 0; y < videoHeight && copySuccess; y++) { - memcpy(yMappedBuffer + y * yRowPitch, - ySrc + y * picture.stride[0], - videoWidth); - } - - // Copy U plane - const uint8_t* uSrc = (const uint8_t*)picture.data[1]; - for (uint32_t y = 0; y < uvHeight && copySuccess; y++) { - memcpy(uMappedBuffer + y * uRowPitch, - uSrc + y * picture.stride[1], - uvWidth); - } - - // Copy V plane - const uint8_t* vSrc = (const uint8_t*)picture.data[2]; - for (uint32_t y = 0; y < uvHeight && copySuccess; y++) { - memcpy(vMappedBuffer + y * vRowPitch, - vSrc + y * picture.stride[1], - uvWidth); - } - } - catch (...) { - LogError("Exception during GPU buffer copy"); - copySuccess = false; - } - - // Cleanup - dav1d_picture_unref(&picture); - - if (!copySuccess) { - IncrementDecodeErrors(); - return false; - } - - // Update statistics - auto decode_end = std::chrono::high_resolution_clock::now(); - auto decode_duration = std::chrono::duration(decode_end - decode_start); - - m_stats.frames_decoded++; - double decode_time = decode_duration.count(); - m_total_decode_time_ms += decode_time; - m_stats.avg_decode_time_ms = m_total_decode_time_ms / m_stats.frames_decoded; - - std::cout << "[AV1Decoder] GPU direct decode successful - " << videoWidth << "x" << videoHeight - << " in " << decode_time << "ms (Zero-copy to GPU)" << std::endl; - - return true; -} - -bool AV1Decoder::DecodeFrameToRingBuffer(const uint8_t* packet_data, size_t packet_size, - uint32_t bufferIndex, - uint8_t* yMappedBuffer, uint8_t* uMappedBuffer, uint8_t* vMappedBuffer, - uint32_t yRowPitch, uint32_t uRowPitch, uint32_t vRowPitch, - uint32_t videoWidth, uint32_t videoHeight) -{ - if (!m_initialized || !packet_data || packet_size == 0) { - LogError("DecodeFrameToRingBuffer: Invalid parameters or decoder not initialized"); - return false; - } - - if (!yMappedBuffer || !uMappedBuffer || !vMappedBuffer) { - LogError("DecodeFrameToRingBuffer: Invalid ring buffer pointers for buffer index " + std::to_string(bufferIndex)); - return false; - } - - auto decode_start = std::chrono::high_resolution_clock::now(); - - // Create dav1d data (zero-copy) - Dav1dData data; - if (dav1d_data_wrap(&data, packet_data, packet_size, DummyFreeCallback, nullptr) < 0) { - LogError("DecodeFrameToRingBuffer: Failed to wrap packet data for ring buffer " + std::to_string(bufferIndex)); - IncrementDecodeErrors(); - return false; - } - - // Send packet to dav1d - int ret = dav1d_send_data(m_dav1d_context, &data); - if (ret < 0 && ret != -EAGAIN) { - LogError("DecodeFrameToRingBuffer: Failed to send data to dav1d for buffer " + std::to_string(bufferIndex) + ": " + std::to_string(ret)); - IncrementDecodeErrors(); - return false; - } - - // Get decoded frame - Dav1dPicture picture = {}; - ret = dav1d_get_picture(m_dav1d_context, &picture); - if (ret < 0) { - if (ret != -EAGAIN) { - LogError("DecodeFrameToRingBuffer: Failed to get decoded picture for buffer " + std::to_string(bufferIndex) + ": " + std::to_string(ret)); - IncrementDecodeErrors(); - } - return false; - } - - // Direct copy to ring buffer (GPU memory) - bool copySuccess = true; - - // Copy Y plane to ring buffer - const uint8_t* ySrc = (const uint8_t*)picture.data[0]; - for (uint32_t y = 0; y < videoHeight && copySuccess; y++) { - if (ySrc + y * picture.stride[0] + videoWidth <= (const uint8_t*)picture.data[0] + picture.stride[0] * picture.p.h) { - memcpy(yMappedBuffer + y * yRowPitch, - ySrc + y * picture.stride[0], - videoWidth); - } - else { - copySuccess = false; - LogError("DecodeFrameToRingBuffer: Y plane copy bounds check failed for buffer " + std::to_string(bufferIndex)); - break; - } - } - - // Copy U plane to ring buffer - if (copySuccess && picture.data[1]) { - const uint8_t* uSrc = (const uint8_t*)picture.data[1]; - uint32_t uvWidth = videoWidth / 2; - uint32_t uvHeight = videoHeight / 2; - - for (uint32_t y = 0; y < uvHeight && copySuccess; y++) { - if (uSrc + y * picture.stride[1] + uvWidth <= (const uint8_t*)picture.data[1] + picture.stride[1] * (picture.p.h / 2)) { - memcpy(uMappedBuffer + y * uRowPitch, - uSrc + y * picture.stride[1], - uvWidth); - } - else { - copySuccess = false; - LogError("DecodeFrameToRingBuffer: U plane copy bounds check failed for buffer " + std::to_string(bufferIndex)); - break; - } - } - } - - // Copy V plane to ring buffer - if (copySuccess && picture.data[2]) { - const uint8_t* vSrc = (const uint8_t*)picture.data[2]; - uint32_t uvWidth = videoWidth / 2; - uint32_t uvHeight = videoHeight / 2; - - for (uint32_t y = 0; y < uvHeight && copySuccess; y++) { - if (vSrc + y * picture.stride[1] + uvWidth <= (const uint8_t*)picture.data[2] + picture.stride[1] * (picture.p.h / 2)) { - memcpy(vMappedBuffer + y * vRowPitch, - vSrc + y * picture.stride[1], - uvWidth); - } - else { - copySuccess = false; - LogError("DecodeFrameToRingBuffer: V plane copy bounds check failed for buffer " + std::to_string(bufferIndex)); - break; - } - } - } - - // Release dav1d picture - dav1d_picture_unref(&picture); - - if (!copySuccess) { - IncrementDecodeErrors(); - return false; - } - - // Performance measurement and statistics update - auto decode_end = std::chrono::high_resolution_clock::now(); - double decode_time = std::chrono::duration(decode_end - decode_start).count(); - - UpdateDecodingStats(decode_time, packet_size); - - m_stats.frames_decoded++; - m_total_decode_time_ms += decode_time; - m_stats.avg_decode_time_ms = m_total_decode_time_ms / m_stats.frames_decoded; - - std::cout << "[AV1Decoder] Ring Buffer decode successful - Buffer[" << bufferIndex << "] " - << videoWidth << "x" << videoHeight << " in " << decode_time << "ms (Zero-copy to Ring Buffer)" - << std::endl; - - return true; -} - -bool AV1Decoder::DecodeFrameWithGPUCopy(const uint8_t* packet_data, size_t packet_size, - D3D12VideoRenderer* renderer, uint32_t bufferIndex, - uint32_t videoWidth, uint32_t videoHeight) -{ - if (!m_initialized || !packet_data || packet_size == 0 || !renderer) { - LogError("DecodeFrameWithGPUCopy: Invalid parameters or decoder not initialized"); - return false; - } - - auto decode_start = std::chrono::high_resolution_clock::now(); - - // Get mapped buffers from ring buffer system - uint8_t* yMappedBuffer = renderer->GetYMappedBuffer(bufferIndex); - uint8_t* uMappedBuffer = renderer->GetUMappedBuffer(bufferIndex); - uint8_t* vMappedBuffer = renderer->GetVMappedBuffer(bufferIndex); - - if (!yMappedBuffer || !uMappedBuffer || !vMappedBuffer) { - LogError("DecodeFrameWithGPUCopy: Failed to get mapped buffers for buffer " + std::to_string(bufferIndex)); - return false; - } - - // First decode to ring buffer (CPU memcpy) - bool decodeSuccess = DecodeFrameToRingBuffer(packet_data, packet_size, bufferIndex, - yMappedBuffer, uMappedBuffer, vMappedBuffer, - renderer->GetYRowPitch(), renderer->GetURowPitch(), renderer->GetVRowPitch(), - videoWidth, videoHeight); - - if (!decodeSuccess) { - LogError("DecodeFrameWithGPUCopy: Ring buffer decode failed for buffer " + std::to_string(bufferIndex)); - return false; - } - - // Execute GPU copy using compute shader - HRESULT hr = renderer->CopyYUVPlanesGPU(bufferIndex, videoWidth, videoHeight); - if (FAILED(hr)) { - LogError("DecodeFrameWithGPUCopy: GPU copy failed for buffer " + std::to_string(bufferIndex)); - return false; - } - - auto decode_end = std::chrono::high_resolution_clock::now(); - double decode_time = std::chrono::duration(decode_end - decode_start).count(); - - std::cout << "[AV1Decoder] GPU copy decode successful - Buffer[" << bufferIndex << "] " - << videoWidth << "x" << videoHeight << " in " << decode_time << "ms (Ring Buffer + GPU Copy)" - << std::endl; - - return true; -} - -bool AV1Decoder::DecodeFrameDirectTexture(const uint8_t* packet_data, size_t packet_size, - DirectTextureAllocator* textureAllocator) -{ - if (!m_initialized || !packet_data || packet_size == 0 || !textureAllocator) { - LogError("DecodeFrameDirectTexture: Invalid parameters or decoder not initialized"); - return false; - } - - auto decode_start = std::chrono::high_resolution_clock::now(); - - // Store the texture allocator for this decode operation - m_directTextureAllocator = textureAllocator; - - // Decode frame using standard dav1d pipeline first - Dav1dData data = {}; - dav1d_data_wrap(&data, packet_data, packet_size, DummyFreeCallback, nullptr); - - // Send data to decoder - int ret = dav1d_send_data(m_dav1d_context, &data); - if (ret < 0) { - LogError("DecodeFrameDirectTexture: dav1d_send_data failed", ret); - dav1d_data_unref(&data); - return false; - } - - // Get decoded picture using default allocator - Dav1dPicture picture = {}; - ret = dav1d_get_picture(m_dav1d_context, &picture); - if (ret < 0) { - if (ret != -11) { // -11 is EAGAIN (no picture available yet) - LogError("DecodeFrameDirectTexture: dav1d_get_picture failed", ret); - } - return false; - } - - // At this point, the picture data should already be in GPU textures via DirectTextureAllocator! - // No additional memory copy needed if allocator was properly set - - // Clean up dav1d picture - dav1d_picture_unref(&picture); - - // Performance measurement - auto decode_end = std::chrono::high_resolution_clock::now(); - double decode_time = std::chrono::duration(decode_end - decode_start).count(); - - // Update statistics - UpdateDecodingStats(decode_time, packet_size); - - std::cout << "[AV1Decoder] Direct Texture decode successful - " - << picture.p.w << "x" << picture.p.h << " in " << decode_time << "ms (Zero-copy to GPU Texture)" - << std::endl; - - // Note: Don't call dav1d_picture_unref here - let the allocator handle lifetime - // The texture remains valid until the next frame or allocator shutdown - - return true; -} - -bool AV1Decoder::SupportsDirectTextureMapping() const -{ - // Direct Texture Mapping requires: - // 1. Initialized dav1d context - // 2. 8-bit YUV420 support (most common format) - // 3. D3D12 compatible environment - return m_initialized && m_dav1d_context != nullptr; -} - -// Note: DirectTextureAllocator methods removed during Phase 1 simplification - -// Note: PrepareDirectTextureAllocator removed during Phase 1 simplification - -// Resolution-based optimized settings implementation -AV1Decoder::AV1Settings AV1Decoder::GetOptimalSettingsForResolution(uint32_t width, uint32_t height) -{ - AV1Settings settings; - - // Calculate video complexity score - uint64_t pixelCount = static_cast(width) * height; - uint32_t maxHWThreads = std::thread::hardware_concurrency(); - - // 4K and above (3840x2160+): Maximum performance optimization - if (width >= 3840 && height >= 2160) { - settings.max_frame_delay = 2; // Allow more delay for better throughput - settings.num_threads = std::min(16U, maxHWThreads); // Max threads for 4K - settings.apply_grain = false; // Disable grain for performance - settings.strict_std_compliance = false; // Allow optimizations - settings.all_layers = false; // Process only target layer - settings.operating_point = false; // Disable for performance - settings.enable_inloop_filters = false; // Disable heavy filters for max performance - - std::cout << "[AV1Decoder] Applied 4K optimization settings" << std::endl; - } - // 1440p (2560x1440): Balanced high performance - else if (width >= 2560 && height >= 1440) { - settings.max_frame_delay = 2; - settings.num_threads = std::min(12U, maxHWThreads); - settings.apply_grain = false; // Still disable for performance - settings.strict_std_compliance = false; - settings.all_layers = false; - settings.operating_point = false; - settings.enable_inloop_filters = false; // Disable for performance - - std::cout << "[AV1Decoder] Applied 1440p optimization settings" << std::endl; - } - // 1080p (1920x1080): Standard optimization - else if (width >= 1920 && height >= 1080) { - settings.max_frame_delay = 1; - settings.num_threads = std::min(8U, maxHWThreads); - settings.apply_grain = true; // Enable grain for quality - settings.strict_std_compliance = false; - settings.all_layers = false; - settings.operating_point = false; - settings.enable_inloop_filters = true; // Enable for balanced quality/performance - - std::cout << "[AV1Decoder] Applied 1080p optimization settings" << std::endl; - } - // Lower resolutions: Quality-focused - else { - settings.max_frame_delay = 1; - settings.num_threads = std::min(4U, maxHWThreads); - settings.apply_grain = true; // Maintain quality - settings.strict_std_compliance = true; // Standard compliance - settings.all_layers = false; - settings.operating_point = false; - settings.enable_inloop_filters = true; // Enable all filters for quality - - std::cout << "[AV1Decoder] Applied standard optimization settings" << std::endl; - } - - std::cout << "[AV1Decoder] Optimized for " << width << "x" << height - << " (" << pixelCount / 1000000.0 << "MP)" - << " - threads:" << settings.num_threads - << " delay:" << settings.max_frame_delay - << " grain:" << (settings.apply_grain ? "on" : "off") - << " filters:" << (settings.enable_inloop_filters ? "on" : "off") << std::endl; - - return settings; -} - -void AV1Decoder::ApplyOptimalSettingsForResolution(uint32_t width, uint32_t height) -{ - AV1Settings optimalSettings = GetOptimalSettingsForResolution(width, height); - SetAV1Settings(optimalSettings); - - // Re-setup dav1d if already initialized - if (m_initialized && m_dav1d_context) { - std::cout << "[AV1Decoder] Re-initializing dav1d with optimized settings..." << std::endl; - - // Close existing context - CleanupDav1d(); - - // Reinitialize with new settings - if (!InitializeDav1d()) { - std::cout << "[AV1Decoder] Warning: Failed to reinitialize with optimized settings, reverting to defaults" << std::endl; - - // Fallback to default settings - dav1d_default_settings(&m_dav1d_settings); - if (dav1d_open(&m_dav1d_context, &m_dav1d_settings) < 0) { - m_initialized = false; - } - } - } -} - -ScopedFrame AV1Decoder::DecodeFrameEnhancedZeroCopy(const uint8_t* packet_data, size_t packet_size) { - // Enhanced zero-copy with automatic packet size prediction and pooling - if (!m_initialized || !packet_data || packet_size == 0) { - return ScopedFrame(FramePool::PooledFrame{}); - } - - auto decode_start = std::chrono::high_resolution_clock::now(); - - // Update packet pool size prediction - PacketPool& packetPool = GetGlobalPacketPool(); - packetPool.UpdateSizePrediction(packet_size); - - // Use zero-copy decoding for minimal overhead - Dav1dData dav1d_data; - dav1d_data_wrap(&dav1d_data, packet_data, packet_size, DummyFreeCallback, nullptr); - - // Send data to decoder - int res = dav1d_send_data(m_dav1d_context, &dav1d_data); - if (res < 0) { - LogError("Failed to send data to decoder", res); - m_stats.decode_errors++; - return ScopedFrame(FramePool::PooledFrame{}); - } - - // Get decoded picture with proper initialization - Dav1dPicture picture = {}; - res = dav1d_get_picture(m_dav1d_context, &picture); - if (res < 0) { - if (res != -EAGAIN) { - LogError("Failed to get decoded picture", res); - m_stats.decode_errors++; - } - return ScopedFrame(FramePool::PooledFrame{}); - } - - // Determine color space - ColorSpace color_space = ColorSpace::YUV420P; - if (picture.p.layout == DAV1D_PIXEL_LAYOUT_I422) { - color_space = ColorSpace::YUV422P; - } else if (picture.p.layout == DAV1D_PIXEL_LAYOUT_I444) { - color_space = ColorSpace::YUV444P; - } - - // Get frame from pool based on picture dimensions - auto pooled_frame = FramePool::GetInstance().AcquireFrame( - picture.p.w, picture.p.h, color_space - ); - - if (!pooled_frame.frame) { - LogError("Failed to acquire frame from pool"); - dav1d_picture_unref(&picture); - m_stats.decode_errors++; - return ScopedFrame(FramePool::PooledFrame{}); - } - - // Convert dav1d picture to pooled frame - bool convert_success = ConvertDav1dPicture(picture, *pooled_frame.frame); - dav1d_picture_unref(&picture); - - if (!convert_success) { - LogError("Failed to convert dav1d picture to pooled frame"); - m_stats.decode_errors++; - return ScopedFrame(FramePool::PooledFrame{}); - } - - // Update performance statistics - auto decode_end = std::chrono::high_resolution_clock::now(); - auto decode_duration = std::chrono::duration(decode_end - decode_start); - UpdateDecodingStats(decode_duration.count(), packet_size); - - // Update frame metadata - pooled_frame.frame->is_valid = true; - pooled_frame.frame->timestamp_seconds = 0.0; // To be set by caller - - return ScopedFrame(std::move(pooled_frame)); -} - -ScopedFrame AV1Decoder::DecodePacketFromPool(PacketPool::PooledPacket& pooled_packet) { - // Optimized decoding using packet pool for enhanced memory management - if (!m_initialized || !pooled_packet.IsValid()) { - return ScopedFrame(FramePool::PooledFrame{}); - } - - auto decode_start = std::chrono::high_resolution_clock::now(); - - // Use packet pool data directly with zero-copy - uint8_t* packet_data = pooled_packet.GetData(); - size_t packet_size = pooled_packet.GetSize(); - - // Update packet pool statistics - PacketPool& packetPool = GetGlobalPacketPool(); - packetPool.UpdateSizePrediction(packet_size); - - // Zero-copy data wrapping - Dav1dData dav1d_data; - dav1d_data_wrap(&dav1d_data, packet_data, packet_size, DummyFreeCallback, nullptr); - - // Send data to decoder - int res = dav1d_send_data(m_dav1d_context, &dav1d_data); - if (res < 0) { - LogError("Failed to send pooled packet to decoder", res); - m_stats.decode_errors++; - return ScopedFrame(FramePool::PooledFrame{}); - } - - // Get decoded picture - Dav1dPicture picture = {}; - res = dav1d_get_picture(m_dav1d_context, &picture); - if (res < 0) { - if (res != -EAGAIN) { - LogError("Failed to get picture from pooled packet", res); - m_stats.decode_errors++; - } - return ScopedFrame(FramePool::PooledFrame{}); - } - - // Determine color space - ColorSpace color_space = ColorSpace::YUV420P; - if (picture.p.layout == DAV1D_PIXEL_LAYOUT_I422) { - color_space = ColorSpace::YUV422P; - } else if (picture.p.layout == DAV1D_PIXEL_LAYOUT_I444) { - color_space = ColorSpace::YUV444P; - } - - // Acquire frame from pool - auto pooled_frame = FramePool::GetInstance().AcquireFrame( - picture.p.w, picture.p.h, color_space - ); - - if (!pooled_frame.frame) { - LogError("Failed to acquire frame from pool for pooled packet"); - dav1d_picture_unref(&picture); - m_stats.decode_errors++; - return ScopedFrame(FramePool::PooledFrame{}); - } - - // Convert with optimized path for pooled frames - bool convert_success = ConvertDav1dPicture(picture, *pooled_frame.frame); - dav1d_picture_unref(&picture); - - if (!convert_success) { - LogError("Failed to convert pooled packet picture"); - m_stats.decode_errors++; - return ScopedFrame(FramePool::PooledFrame{}); - } - - // Update performance statistics with packet metadata - auto decode_end = std::chrono::high_resolution_clock::now(); - auto decode_duration = std::chrono::duration(decode_end - decode_start); - UpdateDecodingStats(decode_duration.count(), packet_size); - - // Update frame metadata from packet pool - pooled_frame.frame->frame_index = pooled_packet.frame_index; - pooled_frame.frame->timestamp_seconds = pooled_packet.timestamp_seconds; - pooled_frame.frame->is_valid = true; - - return ScopedFrame(std::move(pooled_frame)); -} - -} // namespace Vav2Player \ No newline at end of file diff --git a/vav2/Vav2Player/Vav2Player/src/Decoder/AV1Decoder.h b/vav2/Vav2Player/Vav2Player/src/Decoder/AV1Decoder.h deleted file mode 100644 index acd4048..0000000 --- a/vav2/Vav2Player/Vav2Player/src/Decoder/AV1Decoder.h +++ /dev/null @@ -1,156 +0,0 @@ -#pragma once -#include "IVideoDecoder.h" -// Simplified architecture - removed complex pools -#include -#include -#include - -namespace Vav2Player { - -// AV1 video decoder implementation class -// Decodes AV1 video streams to YUV frames using the dav1d library -class AV1Decoder : public IVideoDecoder { -public: - AV1Decoder(); - ~AV1Decoder() override; - - // Prevent copying - AV1Decoder(const AV1Decoder&) = delete; - AV1Decoder& operator=(const AV1Decoder&) = delete; - - // IVideoDecoder interface implementation - bool Initialize(const VideoMetadata& metadata) override; - void Cleanup() override; - bool IsInitialized() const override; - - bool DecodeFrame(const VideoPacket& input_packet, VideoFrame& output_frame) override; - bool DecodeFrame(const uint8_t* packet_data, size_t packet_size, VideoFrame& output_frame) override; - - // Note: Pool-based methods removed during Phase 1 simplification - - // Zero-copy decoding methods (decode without memory copying) - // Warning: packet_data lifetime must be maintained until decoding completion! - // dav1d may queue packets internally, so packet data can be referenced - // asynchronously even after the call returns immediately - bool DecodeFrameZeroCopy(const uint8_t* packet_data, size_t packet_size, VideoFrame& output_frame); - // Note: DecodeFramePooledZeroCopy removed during Phase 1 simplification - - // Note: Enhanced zero-copy and packet pool methods removed during Phase 1 simplification - - // Direct GPU decoding method (direct output to D3D12 mapped buffers) - bool DecodeFrameToGPU(const uint8_t* packet_data, size_t packet_size, - uint8_t* yMappedBuffer, uint8_t* uMappedBuffer, uint8_t* vMappedBuffer, - uint32_t yRowPitch, uint32_t uRowPitch, uint32_t vRowPitch, - uint32_t videoWidth, uint32_t videoHeight); - - // Ring Buffer supported GPU decoding method - bool DecodeFrameToRingBuffer(const uint8_t* packet_data, size_t packet_size, - uint32_t bufferIndex, - uint8_t* yMappedBuffer, uint8_t* uMappedBuffer, uint8_t* vMappedBuffer, - uint32_t yRowPitch, uint32_t uRowPitch, uint32_t vRowPitch, - uint32_t videoWidth, uint32_t videoHeight); - - // Compute Shader based GPU copy optimization method - bool DecodeFrameWithGPUCopy(const uint8_t* packet_data, size_t packet_size, - class D3D12VideoRenderer* renderer, uint32_t bufferIndex, - uint32_t videoWidth, uint32_t videoHeight); - - // Note: Direct texture mapping methods removed during Phase 1 simplification - - bool Reset() override; - bool Flush() override; - - // Check EAGAIN state (when AV1 characteristics require more packets) - bool IsWaitingForMoreData() const { return m_lastDecodeResult == DAV1D_ERR(EAGAIN); } - - std::string GetCodecName() const override; - VideoCodecType GetCodecType() const override; - std::string GetVersion() const override; - - DecoderStats GetStats() const override; - void ResetStats() override; - - // AV1 specific options - bool SetOption(const std::string& key, const std::string& value) override; - std::string GetOption(const std::string& key) const override; - - // AV1 dedicated methods - struct AV1Settings { - int max_frame_delay = 1; // Maximum frame delay (lower means less delay) - int num_threads = 0; // Number of decoding threads (0 = auto) - bool apply_grain = true; // Apply film grain synthesis - bool all_layers = false; // Decode all spatial/temporal layers - - // Advanced dav1d optimization settings - bool strict_std_compliance = true; // Standard compliance (false = performance optimization) - bool operating_point = false; // Operating point processing - bool enable_inloop_filters = true; // Enable in-loop filters (false = performance priority) - }; - - void SetAV1Settings(const AV1Settings& settings); - AV1Settings GetAV1Settings() const; - - // Resolution-based optimized settings - static AV1Settings GetOptimalSettingsForResolution(uint32_t width, uint32_t height); - void ApplyOptimalSettingsForResolution(uint32_t width, uint32_t height); - -private: - // dav1d related members - Dav1dContext* m_dav1d_context; - Dav1dSettings m_dav1d_settings; - AV1Settings m_av1_settings; - - // Note: Direct texture allocator removed during Phase 1 simplification - - // Initialization state - bool m_initialized; - VideoMetadata m_metadata; - - // Track decoding results (for EAGAIN distinction) - int m_lastDecodeResult = 0; - - // Members for performance measurement - std::chrono::high_resolution_clock::time_point m_decode_start_time; - double m_total_decode_time_ms = 0.0; - - // Internal helper methods - bool InitializeDav1d(); - void CleanupDav1d(); - bool SetupDav1dSettings(); - - bool ConvertDav1dPicture(const Dav1dPicture& dav1d_pic, VideoFrame& output_frame); - void UpdateDecodingStats(double decode_time_ms, size_t input_bytes); - - // Convert dav1d pixel format to VideoTypes format - ColorSpace ConvertDav1dPixelFormat(const Dav1dPicture& pic); - - // Error handling - std::string GetDav1dErrorString(int error_code); - void LogError(const std::string& message, int error_code = 0); - - // Zero-copy decoding support - static void DummyFreeCallback(const uint8_t* data, void* cookie); -}; - -// AV1 related utility functions -namespace AV1Utils { - // AV1 OBU (Open Bitstream Unit) type analysis - enum class OBUType { - SEQUENCE_HEADER = 1, - TEMPORAL_DELIMITER = 2, - FRAME_HEADER = 3, - FRAME = 6, - TILE_GROUP = 4, - METADATA = 5, - UNKNOWN = -1 - }; - - OBUType GetOBUType(const uint8_t* data, size_t size); - bool IsKeyFrame(const uint8_t* data, size_t size); - - // dav1d version information - std::string GetDav1dVersion(); - std::string GetDav1dCopyright(); -} - -} // namespace Vav2Player \ No newline at end of file diff --git a/vav2/Vav2Player/Vav2Player/src/Rendering/SimpleGPURenderer_Headless.cpp b/vav2/Vav2Player/Vav2Player/src/Rendering/SimpleGPURenderer_Headless.cpp deleted file mode 100644 index e891f0d..0000000 --- a/vav2/Vav2Player/Vav2Player/src/Rendering/SimpleGPURenderer_Headless.cpp +++ /dev/null @@ -1,336 +0,0 @@ -#include "pch.h" -#include "SimpleGPURenderer_Headless.h" -#include - -#pragma comment(lib, "d3d12.lib") -#pragma comment(lib, "dxgi.lib") - -namespace Vav2Player { - -SimpleGPURenderer_Headless::SimpleGPURenderer_Headless() - : m_fenceValue(1) -{ - m_fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); -} - -SimpleGPURenderer_Headless::~SimpleGPURenderer_Headless() -{ - Shutdown(); - if (m_fenceEvent) - { - CloseHandle(m_fenceEvent); - m_fenceEvent = nullptr; - } -} - -HRESULT SimpleGPURenderer_Headless::Initialize(uint32_t width, uint32_t height) -{ - if (m_initialized) - return S_OK; - - m_width = width; - m_height = height; - - HRESULT hr = S_OK; - - // 1. Create D3D12 device - hr = CreateDevice(); - if (FAILED(hr)) return hr; - - // 2. Create command queue - hr = CreateCommandQueue(); - if (FAILED(hr)) return hr; - - // 3. Create synchronization objects - hr = CreateSynchronizationObjects(); - if (FAILED(hr)) return hr; - - m_initialized = true; - std::cout << "[SimpleGPURenderer_Headless] Initialized successfully (" << width << "x" << height << ")" << std::endl; - return S_OK; -} - -void SimpleGPURenderer_Headless::Shutdown() -{ - if (!m_initialized) - return; - - // Wait for GPU to finish - WaitForGPU(); - - // Reset COM objects - m_yTexture.Reset(); - m_uTexture.Reset(); - m_vTexture.Reset(); - m_commandList.Reset(); - m_commandAllocator.Reset(); - m_fence.Reset(); - m_commandQueue.Reset(); - m_device.Reset(); - - m_initialized = false; - std::cout << "[SimpleGPURenderer_Headless] Shutdown completed" << std::endl; -} - -HRESULT SimpleGPURenderer_Headless::RenderVideoFrame(const VideoFrame& frame) -{ - if (!m_initialized) - return E_FAIL; - - // Basic frame validation - if (frame.width == 0 || frame.height == 0) - return E_INVALIDARG; - - std::cout << "[SimpleGPURenderer_Headless] Would render frame (" - << frame.width << "x" << frame.height << ")" << std::endl; - return S_OK; -} - -HRESULT SimpleGPURenderer_Headless::TestGPUPipeline() -{ - if (!m_initialized) - return E_FAIL; - - std::cout << "[SimpleGPURenderer_Headless] Testing GPU pipeline..." << std::endl; - - // Test command list recording - HRESULT hr = m_commandAllocator->Reset(); - if (FAILED(hr)) - { - std::cout << "[FAIL] Command allocator reset failed: 0x" << std::hex << hr << std::endl; - return hr; - } - - hr = m_commandList->Reset(m_commandAllocator.Get(), nullptr); - if (FAILED(hr)) - { - std::cout << "[FAIL] Command list reset failed: 0x" << std::hex << hr << std::endl; - return hr; - } - - // Close command list - hr = m_commandList->Close(); - if (FAILED(hr)) - { - std::cout << "[FAIL] Command list close failed: 0x" << std::hex << hr << std::endl; - return hr; - } - - // Execute empty command list (just to test pipeline) - ID3D12CommandList* commandLists[] = { m_commandList.Get() }; - m_commandQueue->ExecuteCommandLists(1, commandLists); - - // Wait for completion - hr = WaitForGPU(); - if (FAILED(hr)) - { - std::cout << "[FAIL] GPU wait failed: 0x" << std::hex << hr << std::endl; - return hr; - } - - std::cout << "[PASS] GPU pipeline test completed successfully" << std::endl; - - // Test compute shader compilation - hr = TestComputeShaderCompilation(); - if (FAILED(hr)) - { - std::cout << "[FAIL] Compute shader compilation test failed" << std::endl; - return hr; - } - - return S_OK; -} - -HRESULT SimpleGPURenderer_Headless::CreateDevice() -{ - // Enable debug layer in debug builds -#ifdef _DEBUG - ComPtr debugController; - if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)))) - { - debugController->EnableDebugLayer(); - } -#endif - - // Create device - HRESULT hr = D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&m_device)); - if (FAILED(hr)) - { - std::cout << "[SimpleGPURenderer_Headless] Failed to create D3D12 device: 0x" << std::hex << hr << std::endl; - return hr; - } - - std::cout << "[SimpleGPURenderer_Headless] D3D12 device created successfully" << std::endl; - return S_OK; -} - -HRESULT SimpleGPURenderer_Headless::CreateCommandQueue() -{ - D3D12_COMMAND_QUEUE_DESC queueDesc = {}; - queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; - queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; - - HRESULT hr = m_device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&m_commandQueue)); - if (FAILED(hr)) - { - std::cout << "[SimpleGPURenderer_Headless] Failed to create command queue: 0x" << std::hex << hr << std::endl; - return hr; - } - - return S_OK; -} - -HRESULT SimpleGPURenderer_Headless::CreateSynchronizationObjects() -{ - // Create fence - HRESULT hr = m_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence)); - if (FAILED(hr)) - { - std::cout << "[SimpleGPURenderer_Headless] Failed to create fence: 0x" << std::hex << hr << std::endl; - return hr; - } - - // Create command allocator - hr = m_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&m_commandAllocator)); - if (FAILED(hr)) - { - std::cout << "[SimpleGPURenderer_Headless] Failed to create command allocator: 0x" << std::hex << hr << std::endl; - return hr; - } - - // Create command list - hr = m_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_commandAllocator.Get(), nullptr, IID_PPV_ARGS(&m_commandList)); - if (FAILED(hr)) - { - std::cout << "[SimpleGPURenderer_Headless] Failed to create command list: 0x" << std::hex << hr << std::endl; - return hr; - } - - // Close the command list initially - hr = m_commandList->Close(); - if (FAILED(hr)) - { - std::cout << "[SimpleGPURenderer_Headless] Failed to close initial command list: 0x" << std::hex << hr << std::endl; - return hr; - } - - std::cout << "[SimpleGPURenderer_Headless] Synchronization objects created successfully" << std::endl; - return S_OK; -} - -HRESULT SimpleGPURenderer_Headless::WaitForGPU() -{ - if (!m_commandQueue || !m_fence || !m_fenceEvent) - return E_FAIL; - - // Signal the fence - HRESULT hr = m_commandQueue->Signal(m_fence.Get(), m_fenceValue); - if (FAILED(hr)) return hr; - - // Wait for fence completion - if (m_fence->GetCompletedValue() < m_fenceValue) - { - hr = m_fence->SetEventOnCompletion(m_fenceValue, m_fenceEvent); - if (FAILED(hr)) return hr; - - WaitForSingleObject(m_fenceEvent, INFINITE); - } - - m_fenceValue++; - return S_OK; -} - -HRESULT SimpleGPURenderer_Headless::TestComputeShaderCompilation() -{ - std::cout << "[SimpleGPURenderer_Headless] Testing compute shader compilation..." << std::endl; - - // Define the same YUV-to-RGB compute shader as SimpleGPURenderer - const char* shaderSource = R"( -// YUV to RGB conversion compute shader -// Uses BT.709 color space conversion matrix - -// Input Y texture (luminance) -Texture2D g_yTexture : register(t0); - -// Input U texture (chroma) -Texture2D g_uTexture : register(t1); - -// Input V texture (chroma) -Texture2D g_vTexture : register(t2); - -// Output RGB texture -RWTexture2D g_rgbTexture : register(u0); - -[numthreads(8, 8, 1)] -void main(uint3 id : SV_DispatchThreadID) -{ - // Get texture dimensions - uint2 texSize; - g_yTexture.GetDimensions(texSize.x, texSize.y); - - // Bounds check - if (id.x >= texSize.x || id.y >= texSize.y) - return; - - // Sample Y, U, V values - float y = g_yTexture[id.xy].r; - - // UV coordinates are half resolution (4:2:0 format) - uint2 uvCoord = id.xy / 2; - float u = g_uTexture[uvCoord].r; - float v = g_vTexture[uvCoord].r; - - // Convert from [0,1] to YUV ranges - y = (y * 255.0f - 16.0f) / 219.0f; - u = (u * 255.0f - 128.0f) / 224.0f; - v = (v * 255.0f - 128.0f) / 224.0f; - - // BT.709 YUV to RGB conversion matrix - float3 rgb; - rgb.r = y + 1.5748f * v; - rgb.g = y - 0.1873f * u - 0.4681f * v; - rgb.b = y + 1.8556f * u; - - // Clamp to [0,1] range - rgb = saturate(rgb); - - // Write RGB result - g_rgbTexture[id.xy] = float4(rgb, 1.0f); -} -)"; - - // Test compilation - ComPtr shaderBlob; - ComPtr errorBlob; - - HRESULT hr = D3DCompile( - shaderSource, - strlen(shaderSource), - "YUVToRGB_Compute_Test", - nullptr, - nullptr, - "main", - "cs_5_0", - D3DCOMPILE_OPTIMIZATION_LEVEL3, - 0, - &shaderBlob, - &errorBlob - ); - - if (FAILED(hr)) - { - if (errorBlob) - { - std::cout << "[FAIL] Compute shader compilation failed: " - << (char*)errorBlob->GetBufferPointer() << std::endl; - } - return hr; - } - - std::cout << "[PASS] Compute shader compiled successfully (Size: " - << shaderBlob->GetBufferSize() << " bytes)" << std::endl; - - return S_OK; -} - -} // namespace Vav2Player \ No newline at end of file diff --git a/vav2/Vav2Player/Vav2Player/src/Rendering/SimpleGPURenderer_Headless.h b/vav2/Vav2Player/Vav2Player/src/Rendering/SimpleGPURenderer_Headless.h deleted file mode 100644 index 069c98e..0000000 --- a/vav2/Vav2Player/Vav2Player/src/Rendering/SimpleGPURenderer_Headless.h +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include "../Common/VideoTypes.h" - -using Microsoft::WRL::ComPtr; - -namespace Vav2Player { - -// Headless version of SimpleGPURenderer for testing -// Phase 3: GPU pipeline testing without GUI dependencies -class SimpleGPURenderer_Headless -{ -public: - SimpleGPURenderer_Headless(); - ~SimpleGPURenderer_Headless(); - - // Core lifecycle (headless - no SwapChainPanel) - HRESULT Initialize(uint32_t width, uint32_t height); - void Shutdown(); - bool IsInitialized() const { return m_initialized; } - - // Video rendering (headless - no actual display) - HRESULT RenderVideoFrame(const VideoFrame& frame); - HRESULT TestGPUPipeline(); // Test GPU functionality - - // Size management - uint32_t GetWidth() const { return m_width; } - uint32_t GetHeight() const { return m_height; } - -private: - // D3D12 core objects (minimal for headless) - ComPtr m_device; - ComPtr m_commandQueue; - ComPtr m_commandAllocator; - ComPtr m_commandList; - - // Synchronization - ComPtr m_fence; - UINT64 m_fenceValue; - HANDLE m_fenceEvent; - - // Video textures for testing - ComPtr m_yTexture; - ComPtr m_uTexture; - ComPtr m_vTexture; - - // State - bool m_initialized = false; - uint32_t m_width = 0; - uint32_t m_height = 0; - - // Helper methods - HRESULT CreateDevice(); - HRESULT CreateCommandQueue(); - HRESULT CreateSynchronizationObjects(); - HRESULT WaitForGPU(); - HRESULT TestComputeShaderCompilation(); -}; - -} // namespace Vav2Player \ No newline at end of file