diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 03a96a8..61fe07d 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -36,7 +36,9 @@ "Bash(dir:*)", "Bash(set MSBUILD_EXE=\"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\MSBuild.exe\")", "Bash(%MSBUILD_EXE% \"Vav2Player.sln\" /p:Configuration=Debug /p:Platform=x64 /m)", - "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(/c/Program Files/Microsoft Visual Studio/2022/Community/MSBuild/Current/Bin/MSBuild.exe Vav2Player.vcxproj //p:Configuration=Debug //p:Platform=x64 //v:minimal)", + "Bash(python:*)", + "Bash(start:*)" ], "deny": [], "ask": [] diff --git a/vav2/CLAUDE.md b/vav2/CLAUDE.md index f93c601..6784aec 100644 --- a/vav2/CLAUDE.md +++ b/vav2/CLAUDE.md @@ -117,6 +117,63 @@ D:\Project\video-av1\ - 언어 표준: C++17 이상 - 런타임: Windows App SDK 1.8 +## 코딩 규칙 및 가이드라인 + +### 주석 언어 규칙 +**중요**: 모든 소스 코드 파일의 주석은 **영어로 작성**해야 합니다. + +#### 적용 범위 +- `.h` 헤더 파일의 모든 주석 +- `.cpp` 소스 파일의 모든 주석 +- `.xaml.h` WinUI 헤더 파일의 모든 주석 +- `.xaml.cpp` WinUI 소스 파일의 모든 주석 + +#### 예시 +```cpp +// ❌ 잘못된 예 (한국어 주석) +// 버퍼 크기 확인 및 재할당 최소화 +size_t required_size = frame.width * frame.height * 4; + +// ✅ 올바른 예 (영어 주석) +// Check buffer size and minimize reallocation +size_t required_size = frame.width * frame.height * 4; +``` + +#### 이유 +1. **국제화 지원**: 영어 주석으로 코드의 국제적 접근성 향상 +2. **컴파일러 호환성**: 일부 컴파일러에서 비ASCII 문자로 인한 인코딩 문제 방지 +3. **협업 효율성**: 다양한 배경의 개발자들과의 협업 용이성 +4. **유지보수성**: 장기적인 코드 유지보수 시 언어 장벽 제거 + +#### 주의사항 +- 기존 한국어 주석 발견 시 즉시 영어로 변환 +- 새로운 코드 작성 시 처음부터 영어 주석 사용 +- 함수명, 변수명은 기존 명명 규칙 유지 (영어 또는 한국어 혼용 가능) + +### XAML 파일 작성 규칙 +**중요**: WinUI XAML 파일에서도 모든 주석과 문자열은 **영어로 작성**해야 합니다. + +#### 적용 범위 +- `.xaml` 파일의 모든 XML 주석 (``) +- XAML 속성값의 텍스트 문자열 (예: `Text="..."`, `Content="..."`) +- 사용자에게 표시되지 않는 내부 문자열도 영어 권장 + +#### 예시 +```xml + + + + + + + +``` + +#### XAML 파싱 주의사항 +- **대량 주석 블록 금지**: 긴 주석 처리된 XAML 코드는 파싱 오류를 일으킬 수 있음 +- **사용하지 않는 컨트롤**: 주석 처리보다는 완전 제거 권장 +- **이벤트 핸들러**: XAML에서 제거된 컨트롤의 이벤트 핸들러는 .h/.cpp에서도 제거 + ### 라이브러리 링크 설정 ```xml diff --git a/vav2/Vav2Player/Vav2Player/MainWindow.xaml.cpp b/vav2/Vav2Player/Vav2Player/MainWindow.xaml.cpp index e9d0b5b..26bc45b 100644 --- a/vav2/Vav2Player/Vav2Player/MainWindow.xaml.cpp +++ b/vav2/Vav2Player/Vav2Player/MainWindow.xaml.cpp @@ -1,787 +1,24 @@ #include "pch.h" #include "MainWindow.xaml.h" -#include "src/Output/FileOutput.h" -#include "src/Decoder/AV1Decoder.h" -#include -#include // For IBufferByteAccess -#include -#include #if __has_include("MainWindow.g.cpp") #include "MainWindow.g.cpp" #endif using namespace winrt; -// using namespace Microsoft::UI::Xaml; // 충돌 방지를 위해 명시적으로 사용 -// using namespace Microsoft::UI::Xaml::Controls; -// using namespace Windows::Foundation; - -// IBufferByteAccess is now declared in pch.h +using namespace winrt::Microsoft::UI::Xaml; // To learn more about WinUI, the WinUI project structure, // and more about our project templates, see: http://aka.ms/winui-project-info. namespace winrt::Vav2Player::implementation { - void MainWindow::InitializeUI() + MainWindow::MainWindow() { - try - { - // Initialize UI state - UpdateFileOutputControls(); - - // Show initial decoder selection - std::string decoderTypeName = GetDecoderTypeName(m_selectedDecoderType); - UpdateStatus("Ready - Selected decoder: " + decoderTypeName); - - OutputDebugStringA("MainWindow UI initialized\n"); - OutputDebugStringA(("Default decoder type: " + decoderTypeName + "\n").c_str()); - } - catch (const std::exception& e) - { - UpdateStatus("Error initializing UI: " + std::string(e.what())); - } - } - void MainWindow::OpenFileButton_Click(Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&) - { - try - { - OutputDebugStringA("OpenFileButton_Click called!\n"); - UpdateStatus("Opening file picker..."); - - // Call async file picker method - OpenFileAsync(); - } - catch (...) - { - UpdateStatus("Error opening file"); - } + InitializeComponent(); } - void MainWindow::TestDecodeButton_Click(Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&) - { - try - { - OutputDebugStringA("TestDecodeButton_Click called!\n"); - UpdateStatus("Starting AV1 decode test..."); - // Check if file is already loaded - if (!m_fileReader || !m_fileReader->IsFileOpen()) - { - UpdateStatus("Error: Please open a WebM file first"); - return; - } - - // Get actual metadata from the opened file - auto metadata = m_fileReader->GetVideoMetadata(); - - // Create decoder if not already done - if (!m_decoder) - { - OutputDebugStringA(("Creating decoder for codec: " + metadata.codec_name + "\n").c_str()); - std::string decoderTypeName = GetDecoderTypeName(m_selectedDecoderType); - OutputDebugStringA(("Using selected decoder type: " + decoderTypeName + "\n").c_str()); - - m_decoder = VideoDecoderFactory::CreateDecoder(metadata.codec_type, m_selectedDecoderType); - - if (m_decoder) { - OutputDebugStringA(("Successfully created decoder: " + m_decoder->GetCodecName() + "\n").c_str()); - OutputDebugStringA(("Decoder version: " + m_decoder->GetVersion() + "\n").c_str()); - } - } - - if (!m_decoder) - { - UpdateStatus("Failed to create decoder for codec: " + metadata.codec_name); - return; - } - - // Initialize decoder with actual file metadata - if (!m_decoder->IsInitialized()) - { - if (!m_decoder->Initialize(metadata)) - { - UpdateStatus("Failed to initialize decoder"); - return; - } - } - - UpdateStatus("AV1 decoder initialized successfully!"); - - // Initialize video rendering - InitializeVideoRenderer(); - - // FileOutput is now managed by the checkbox UI - don't create it here - // The checkbox event handlers will manage FileOutput creation/destruction - - // Reset file reading position - m_fileReader->Reset(); - - // Process a single frame - ProcessSingleFrame(); - } - catch (...) - { - UpdateStatus("Error during decode test"); - } - } - - void MainWindow::PlayButton_Click(Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&) - { - try - { - if (!m_isFileLoaded) - { - UpdateStatus("No file loaded"); - return; - } - - // Initialize playback if not already done - if (!m_playbackInitialized) - { - InitializePlayback(); - } - - if (m_playbackInitialized) - { - m_isPlaying = true; - UpdateButtons(); - UpdateStatus("Playing..."); - StartPlaybackTimer(); - OutputDebugStringA("Playback started\n"); - } - else - { - UpdateStatus("Error: Failed to initialize playback"); - } - } - catch (...) - { - UpdateStatus("Error starting playback"); - } - } - - void MainWindow::PauseButton_Click(Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&) - { - try - { - if (m_isPlaying) - { - m_isPlaying = false; - StopPlaybackTimer(); - UpdateButtons(); - UpdateStatus("Paused at frame " + std::to_string(m_currentFrame)); - OutputDebugStringA("Playback paused\n"); - } - } - catch (...) - { - UpdateStatus("Error pausing playback"); - } - } - - void MainWindow::StopButton_Click(Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&) - { - try - { - // Add this block to get and print decoder stats - if (m_decoder) - { - auto stats = m_decoder->GetStats(); - std::string statsMsg = "--- Decoder Statistics ---\n"; - statsMsg += "Frames Decoded: " + std::to_string(stats.frames_decoded) + "\n"; - statsMsg += "Avg Decode Time (ms): " + std::to_string(stats.avg_decode_time_ms) + "\n"; - statsMsg += "Bytes Processed: " + std::to_string(stats.bytes_processed) + "\n"; - statsMsg += "--------------------------\n"; - OutputDebugStringA(statsMsg.c_str()); - } - - // Stop playback - m_isPlaying = false; - StopPlaybackTimer(); - - // Reset to beginning - m_currentFrame = 0; - - // Reset file reader position - if (m_fileReader && m_fileReader->IsFileOpen()) - { - m_fileReader->Reset(); - } - - // Reset decoder - if (m_decoder && m_decoder->IsInitialized()) - { - m_decoder->Reset(); - } - - UpdateButtons(); - UpdateStatus("Stopped - Reset to beginning"); - - // Reset progress bar - ProgressBar().Value(0); - - // DO NOT destroy video renderer components - keep them for next playback - // The video renderer (m_renderBitmap, VideoImage) should remain intact - // Only reset playback state, not the display components - - OutputDebugStringA("Playback stopped and reset (renderer preserved)\n"); - } - catch (...) - { - UpdateStatus("Error stopping playback"); - } - } - - void MainWindow::UpdateStatus(const std::string& message) - { - try - { - // Debug output to console - OutputDebugStringA(("Status: " + message + "\n").c_str()); - - // Convert std::string to winrt::hstring - auto hstr = winrt::to_hstring(message); - StatusText().Text(hstr); - } - catch (...) - { - // Fallback if status update fails - OutputDebugStringA("Error updating status\n"); - } - } - - void MainWindow::UpdateButtons() - { - try - { - PlayButton().IsEnabled(m_isFileLoaded && !m_isPlaying); - PauseButton().IsEnabled(m_isFileLoaded && m_isPlaying); - StopButton().IsEnabled(m_isFileLoaded); - } - catch (...) - { - // Fallback if button update fails - } - } - - void MainWindow::ProcessSingleFrame() - { - try - { - if (!m_fileReader || !m_decoder || !m_fileReader->IsFileOpen()) - { - UpdateStatus("Error: File or decoder not ready"); - return; - } - - // Read next packet from WebM file - VideoPacket packet; - if (!m_fileReader->ReadNextPacket(packet)) - { - // End of file reached - if (m_isPlaying) - { - m_isPlaying = false; - StopPlaybackTimer(); - UpdateButtons(); - UpdateStatus("Playback completed - End of file reached"); - OutputDebugStringA("End of file reached\n"); - } - return; - } - - // Decode the packet using zero-copy method - VideoFrame frame; - bool decodeSuccess = false; - - // Cast to AV1Decoder to access zero-copy methods - if (auto av1Decoder = dynamic_cast(m_decoder.get())) - { - // ✅ SAFE: Zero-copy 안전성 보장됨 - // - VideoPacket 'packet'은 이 함수 스코프 내에서 유효함 - // - DecodeFrameZeroCopy는 동기적으로 디코딩을 완료함 - // - 함수 반환 시점에서 dav1d가 packet.data 사용을 완료함 - decodeSuccess = av1Decoder->DecodeFrameZeroCopy(packet.data.get(), packet.size, frame); - OutputDebugStringA("Using zero-copy decoding\n"); - } - else - { - // Fallback to regular decoding if not AV1Decoder - decodeSuccess = m_decoder->DecodeFrame(packet, frame); - OutputDebugStringA("Using regular decoding (fallback)\n"); - } - - if (decodeSuccess) - { - // Successfully decoded frame - m_currentFrame++; - - // ALWAYS render frame to screen first (regardless of file output) - OutputDebugStringA(("Processing frame " + std::to_string(m_currentFrame) + " - Rendering to screen\n").c_str()); - RenderFrameToScreen(frame); - OutputDebugStringA(("Frame " + std::to_string(m_currentFrame) + " rendered to screen successfully\n").c_str()); - - // Save frame to file if enabled (wrapped in try-catch to prevent UI blocking) - if (m_fileOutputEnabled && m_fileOutput) - { - try - { - OutputDebugStringA(("Frame " + std::to_string(m_currentFrame) + " - Starting file save\n").c_str()); - auto saveResult = m_fileOutput->SaveFrame(frame, m_currentFrame, 0.0); - if (saveResult.success) - { - OutputDebugStringA(("Saved frame " + std::to_string(m_currentFrame) + " to file: " + saveResult.saved_path.string() + "\n").c_str()); - } - else - { - OutputDebugStringA(("Failed to save frame " + std::to_string(m_currentFrame) + " to file: " + saveResult.error_message + "\n").c_str()); - } - } - catch (const std::exception& e) - { - OutputDebugStringA(("Exception during file save for frame " + std::to_string(m_currentFrame) + ": " + e.what() + "\n").c_str()); - } - catch (...) - { - OutputDebugStringA(("Unknown exception during file save for frame " + std::to_string(m_currentFrame) + "\n").c_str()); - } - } - else if (m_fileOutputEnabled) - { - OutputDebugStringA(("Frame " + std::to_string(m_currentFrame) + " not saved - FileOutput not initialized\n").c_str()); - } - - OutputDebugStringA(("Displayed frame " + std::to_string(m_currentFrame) + "\n").c_str()); - - // Update progress - UpdateProgress(); - - std::string statusMsg = "Frame " + std::to_string(m_currentFrame); - if (m_totalFrames > 0) - { - statusMsg += "/" + std::to_string(m_totalFrames); - } - statusMsg += " decoded"; - - if (!m_isPlaying) // Only update status if not playing (to avoid spam) - { - UpdateStatus(statusMsg); - } - } - else - { - // Decoding failed - this might be normal for some packets - OutputDebugStringA("Frame decoding failed (might be normal)\n"); - } - } - catch (...) - { - UpdateStatus("Error processing frame"); - } - } - - winrt::Windows::Foundation::IAsyncAction MainWindow::OpenFileAsync() - { - try - { - // Create file picker - winrt::Windows::Storage::Pickers::FileOpenPicker picker; - - // Get the current window's HWND for WinUI3 - auto windowNative = this->try_as<::IWindowNative>(); - HWND hwnd = nullptr; - if (windowNative) - { - windowNative->get_WindowHandle(&hwnd); - } - - // Initialize the picker with the window handle - auto initializeWithWindow = picker.as<::IInitializeWithWindow>(); - if (hwnd) - { - initializeWithWindow->Initialize(hwnd); - } - - // Set file type filters for AV1/WebM/MKV files - picker.ViewMode(winrt::Windows::Storage::Pickers::PickerViewMode::Thumbnail); - picker.SuggestedStartLocation(winrt::Windows::Storage::Pickers::PickerLocationId::VideosLibrary); - picker.FileTypeFilter().Append(L".webm"); - picker.FileTypeFilter().Append(L".mkv"); - picker.FileTypeFilter().Append(L".av1"); - picker.FileTypeFilter().Append(L".ivf"); - - // Open the file picker - UpdateStatus("Please select an AV1/WebM video file..."); - auto file = co_await picker.PickSingleFileAsync(); - - if (file) - { - // Convert Windows::Storage::StorageFile path to std::string - auto path = file.Path(); - std::string filePath = winrt::to_string(path); - - OutputDebugStringA(("Selected file: " + filePath + "\n").c_str()); - std::string fileName = winrt::to_string(file.Name()); - UpdateStatus("File selected: " + fileName); - - // Store the file path - m_currentFilePath = filePath; - - // Try to open the file with WebMFileReader - if (!m_fileReader) - { - m_fileReader = std::make_unique(); - } - - OutputDebugStringA(("Attempting to open file: " + filePath + "\n").c_str()); - - // Check if file exists and get basic info - std::ifstream fileStream(filePath, std::ios::binary | std::ios::ate); - if (fileStream.is_open()) { - auto fileSize = fileStream.tellg(); - fileStream.seekg(0, std::ios::beg); - - // Read first 16 bytes to check magic numbers - char buffer[16]; - fileStream.read(buffer, 16); - fileStream.close(); - - std::string hexString; - for (int i = 0; i < 16; i++) { - char hex[4]; - sprintf_s(hex, "%02X ", (unsigned char)buffer[i]); - hexString += hex; - } - - OutputDebugStringA(("File size: " + std::to_string(fileSize) + " bytes\n").c_str()); - OutputDebugStringA(("First 16 bytes: " + hexString + "\n").c_str()); - } else { - OutputDebugStringA("Cannot open file for reading\n"); - } - - if (m_fileReader->OpenFile(filePath)) - { - OutputDebugStringA("WebMFileReader::OpenFile succeeded\n"); - // Get video metadata - auto tracks = m_fileReader->GetVideoTracks(); - if (!tracks.empty()) - { - // Select the first video track - if (m_fileReader->SelectVideoTrack(tracks[0].track_number)) - { - auto metadata = m_fileReader->GetVideoMetadata(); - - std::string info = "File opened successfully!\n"; - info += "Resolution: " + std::to_string(metadata.width) + "x" + std::to_string(metadata.height) + "\n"; - info += "Codec: " + metadata.codec_name + "\n"; - info += "Frames: " + std::to_string(metadata.total_frames); - - UpdateStatus(info); - m_isFileLoaded = true; - - // Reset playback state for new file - m_playbackInitialized = false; - m_currentFrame = 0; - m_totalFrames = metadata.total_frames; - m_frameRate = metadata.frame_rate > 0 ? metadata.frame_rate : 30.0; - - UpdateButtons(); - } - else - { - std::string errorMessage = m_fileReader->GetLastErrorString(); - UpdateStatus("Error: Could not select video track - " + errorMessage); - OutputDebugStringA(("Track selection error: " + errorMessage + "\n").c_str()); - } - } - else - { - std::string errorMessage = m_fileReader->GetLastErrorString(); - UpdateStatus("Error: No video tracks found in file - " + errorMessage); - OutputDebugStringA(("No video tracks error: " + errorMessage + "\n").c_str()); - } - } - else - { - // Get detailed error information from WebMFileReader - std::string errorMessage = m_fileReader->GetLastErrorString(); - - OutputDebugStringA("WebMFileReader::OpenFile failed\n"); - OutputDebugStringA(("Error details: " + errorMessage + "\n").c_str()); - - std::string statusMessage = "Error: " + errorMessage; - UpdateStatus(statusMessage); - - // Additional debugging info - std::string debugInfo = "Failed to open: " + filePath + " (Error: " + errorMessage + ")"; - OutputDebugStringA((debugInfo + "\n").c_str()); - } - } - else - { - UpdateStatus("File selection cancelled"); - } - } - catch (...) - { - UpdateStatus("Error opening file picker"); - } - } - - void MainWindow::UpdateProgress() - { - try - { - if (m_totalFrames > 0) - { - double progress = (double)m_currentFrame / (double)m_totalFrames * 100.0; - ProgressBar().Value(progress); - } - } - catch (...) - { - // Error updating progress - } - } - - void MainWindow::InitializePlayback() - { - try - { - if (!m_fileReader || !m_fileReader->IsFileOpen()) - { - UpdateStatus("Error: No file loaded"); - return; - } - - // Get video metadata - auto metadata = m_fileReader->GetVideoMetadata(); - m_totalFrames = metadata.total_frames; - m_frameRate = metadata.frame_rate > 0 ? metadata.frame_rate : 30.0; - - // Initialize decoder if not already done - if (!m_decoder) - { - std::string decoderTypeName = GetDecoderTypeName(m_selectedDecoderType); - OutputDebugStringA(("Creating AV1 decoder for streaming playback (" + decoderTypeName + ")\n").c_str()); - m_decoder = VideoDecoderFactory::CreateDecoder(VideoCodecType::AV1, m_selectedDecoderType); - - if (m_decoder) { - OutputDebugStringA(("Successfully created decoder: " + m_decoder->GetCodecName() + "\n").c_str()); - OutputDebugStringA(("Decoder version: " + m_decoder->GetVersion() + "\n").c_str()); - } - } - - if (m_decoder && !m_decoder->IsInitialized()) - { - if (!m_decoder->Initialize(metadata)) - { - UpdateStatus("Error: Failed to initialize decoder"); - return; - } - } - - // Initialize video rendering - InitializeVideoRenderer(); - - // FileOutput is now managed by the checkbox UI - don't create it here - // The checkbox event handlers will manage FileOutput creation/destruction - - // Reset playback position - m_currentFrame = 0; - m_fileReader->Reset(); - if (m_decoder) - { - m_decoder->Reset(); - } - - m_playbackInitialized = true; - UpdateStatus("Playback initialized - Ready to play"); - OutputDebugStringA("Playback initialized successfully\n"); - } - catch (...) - { - UpdateStatus("Error initializing playback"); - m_playbackInitialized = false; - } - } - - void MainWindow::StartPlaybackTimer() - { - try - { - if (m_playbackTimer) - { - m_playbackTimer.Stop(); - } - - // Calculate timer interval based on frame rate - auto intervalMs = std::chrono::milliseconds(static_cast(1000.0 / m_frameRate)); - - // Create timer - auto dispatcherQueue = winrt::Microsoft::UI::Dispatching::DispatcherQueue::GetForCurrentThread(); - m_playbackTimer = dispatcherQueue.CreateTimer(); - - // Set timer callback - m_playbackTimer.Tick([this](auto&&, auto&&) - { - if (m_isPlaying) - { - ProcessSingleFrame(); - } - }); - - // Set interval and start - m_playbackTimer.Interval(intervalMs); - m_playbackTimer.Start(); - - OutputDebugStringA(("Playback timer started with interval: " + - std::to_string(intervalMs.count()) + "ms\n").c_str()); - } - catch (...) - { - UpdateStatus("Error starting playback timer"); - } - } - - void MainWindow::StopPlaybackTimer() - { - try - { - if (m_playbackTimer) - { - m_playbackTimer.Stop(); - OutputDebugStringA("Playback timer stopped\n"); - } - } - catch (...) - { - // Error stopping timer - } - } - - void MainWindow::InitializeVideoRenderer() - { - try - { - OutputDebugStringA("InitializeVideoRenderer: Starting video renderer initialization\n"); - - if (!m_fileReader || !m_fileReader->IsFileOpen()) { - OutputDebugStringA("InitializeVideoRenderer: ERROR - No file reader or file not open\n"); - UpdateStatus("Error: No file loaded for video renderer"); - return; - } - - auto metadata = m_fileReader->GetVideoMetadata(); - int width = metadata.width; - int height = metadata.height; - - OutputDebugStringA(("InitializeVideoRenderer: Video dimensions: " + std::to_string(width) + "x" + std::to_string(height) + "\n").c_str()); - - if (width <= 0 || height <= 0) { - OutputDebugStringA("InitializeVideoRenderer: ERROR - Invalid video dimensions\n"); - UpdateStatus("Error: Invalid video dimensions"); - return; - } - - // Always create a fresh bitmap - don't reuse existing one - OutputDebugStringA("InitializeVideoRenderer: Creating new WriteableBitmap\n"); - m_renderBitmap = winrt::Microsoft::UI::Xaml::Media::Imaging::WriteableBitmap(width, height); - - if (!m_renderBitmap) { - OutputDebugStringA("InitializeVideoRenderer: ERROR - Failed to create WriteableBitmap\n"); - UpdateStatus("Error: Failed to create video bitmap"); - return; - } - - // Allocate buffer for BGRA conversion - m_bgraBuffer.resize(width * height * 4); - OutputDebugStringA(("InitializeVideoRenderer: Allocated BGRA buffer: " + std::to_string(m_bgraBuffer.size()) + " bytes\n").c_str()); - - // Set the bitmap as the source for the Image control - OutputDebugStringA("InitializeVideoRenderer: Setting bitmap as VideoImage source\n"); - VideoImage().Source(m_renderBitmap); - - // Hide placeholder text and show video - OutputDebugStringA("InitializeVideoRenderer: Updating UI visibility\n"); - VideoDisplayArea().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed); - - OutputDebugStringA("InitializeVideoRenderer: Video renderer initialized successfully\n"); - UpdateStatus("Video renderer initialized"); - } - catch (winrt::hresult_error const& ex) - { - OutputDebugStringA(("InitializeVideoRenderer HRESULT error: " + winrt::to_string(ex.message()) + "\n").c_str()); - UpdateStatus("Error initializing video renderer: " + winrt::to_string(ex.message())); - } - catch (const std::exception& ex) - { - OutputDebugStringA(("InitializeVideoRenderer std::exception: " + std::string(ex.what()) + "\n").c_str()); - UpdateStatus("Error initializing video renderer: " + std::string(ex.what())); - } - catch (...) - { - OutputDebugStringA("InitializeVideoRenderer: Unknown exception occurred\n"); - UpdateStatus("Error initializing video renderer: Unknown error"); - } - } - - void MainWindow::RenderFrameToScreen(const VideoFrame& frame) - { - if (!frame.is_valid) - { - OutputDebugStringA("RenderFrameToScreen: Frame is not valid\n"); - return; - } - - if (!m_renderBitmap) - { - OutputDebugStringA("RenderFrameToScreen: m_renderBitmap is null\n"); - return; - } - - try - { - OutputDebugStringA(("RenderFrameToScreen: Rendering frame " + std::to_string(frame.width) + "x" + std::to_string(frame.height) + "\n").c_str()); - - // Convert YUV frame to our BGRA buffer - ConvertYUVToBGRA(frame, m_bgraBuffer.data(), frame.width, frame.height); - - // Get access to the WriteableBitmap's pixel buffer - winrt::Windows::Storage::Streams::IBuffer buffer = m_renderBitmap.PixelBuffer(); - winrt::com_ptr<::IBufferByteAccess> byteAccess = buffer.as<::IBufferByteAccess>(); - uint8_t* dest_pixels = nullptr; - byteAccess->Buffer(&dest_pixels); - - if (!dest_pixels) - { - OutputDebugStringA("RenderFrameToScreen: Failed to get pixel buffer\n"); - return; - } - - // Copy our BGRA data into the WriteableBitmap - memcpy(dest_pixels, m_bgraBuffer.data(), m_bgraBuffer.size()); - - // Invalidate the bitmap to trigger a UI update - m_renderBitmap.Invalidate(); - - OutputDebugStringA("RenderFrameToScreen: Frame rendered successfully\n"); - } - catch (winrt::hresult_error const& ex) - { - OutputDebugStringA(("Error rendering frame: " + winrt::to_string(ex.message()) + "\n").c_str()); - } - catch (const std::exception& ex) - { - OutputDebugStringA(("Error rendering frame (std::exception): " + std::string(ex.what()) + "\n").c_str()); - } - catch (...) - { - OutputDebugStringA("Error rendering frame: Unknown exception\n"); - } - } - - // This function converts an I420 YUV frame to a BGRA frame. + // 성능 최적화된 YUV to BGRA 변환 함수 void MainWindow::ConvertYUVToBGRA(const VideoFrame& yuv_frame, uint8_t* bgra_buffer, uint32_t width, uint32_t height) { const uint8_t* y_plane = yuv_frame.y_plane.get(); @@ -792,245 +29,140 @@ namespace winrt::Vav2Player::implementation const int32_t u_stride = yuv_frame.u_stride; const int32_t v_stride = yuv_frame.v_stride; - for (uint32_t row = 0; row < height; ++row) - { - for (uint32_t col = 0; col < width; ++col) - { - // Get Y value - int32_t y = y_plane[row * y_stride + col]; + // 룩업 테이블을 사용한 최적화 + static bool lookup_initialized = false; + static int16_t r_v_table[256]; + static int16_t g_u_table[256]; + static int16_t g_v_table[256]; + static int16_t b_u_table[256]; - // Get U and V values (for a 2x2 block) - int32_t u = u_plane[(row / 2) * u_stride + (col / 2)]; - int32_t v = v_plane[(row / 2) * v_stride + (col / 2)]; - - // YUV to RGB conversion (standard formula) - // C = Y - 16 - // D = U - 128 - // E = V - 128 - int c = y - 16; - int d = u - 128; - int e = v - 128; - - // R = clamp((298 * C + 409 * E + 128) >> 8) - // G = clamp((298 * C - 100 * D - 208 * E + 128) >> 8) - // B = clamp((298 * C + 516 * D + 128) >> 8) - int r = std::clamp((298 * c + 409 * e + 128) >> 8, 0, 255); - int g = std::clamp((298 * c - 100 * d - 208 * e + 128) >> 8, 0, 255); - int b = std::clamp((298 * c + 516 * d + 128) >> 8, 0, 255); - - // Set BGRA pixel data - uint32_t pixel_offset = (row * width + col) * 4; - bgra_buffer[pixel_offset + 0] = static_cast(b); // Blue - bgra_buffer[pixel_offset + 1] = static_cast(g); // Green - bgra_buffer[pixel_offset + 2] = static_cast(r); // Red - bgra_buffer[pixel_offset + 3] = 255; // Alpha + if (!lookup_initialized) { + for (int i = 0; i < 256; i++) { + int uv_val = i - 128; + r_v_table[i] = (409 * uv_val + 128) >> 8; + g_u_table[i] = (100 * uv_val + 128) >> 8; + g_v_table[i] = (208 * uv_val + 128) >> 8; + b_u_table[i] = (516 * uv_val + 128) >> 8; } + lookup_initialized = true; } - } - // Decoder Selection Event Handler - void MainWindow::DecoderSelectionComboBox_SelectionChanged(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::Controls::SelectionChangedEventArgs const&) - { - try - { - auto selectedIndex = DecoderSelectionComboBox().SelectedIndex(); - auto selectedItem = DecoderSelectionComboBox().SelectedItem().as(); - auto tag = selectedItem.Tag().as(); - std::string tagStr = winrt::to_string(tag); + // 4픽셀씩 처리하여 캐시 효율성 향상 + for (uint32_t row = 0; row < height; row += 2) { + for (uint32_t col = 0; col < width; col += 2) { + // UV값은 2x2 블록당 하나 + uint32_t uv_row = row / 2; + uint32_t uv_col = col / 2; - // Convert tag to DecoderType - VideoDecoderFactory::DecoderType newDecoderType = VideoDecoderFactory::DecoderType::AUTO; - if (tagStr == "SOFTWARE") - { - newDecoderType = VideoDecoderFactory::DecoderType::SOFTWARE; - } - else if (tagStr == "HARDWARE_MF") - { - newDecoderType = VideoDecoderFactory::DecoderType::HARDWARE_MF; - } - else if (tagStr == "AUTO") - { - newDecoderType = VideoDecoderFactory::DecoderType::AUTO; - } + if (uv_row >= height / 2 || uv_col >= width / 2) continue; - // If decoder type changed, reset decoder - if (newDecoderType != m_selectedDecoderType) - { - m_selectedDecoderType = newDecoderType; + int u = u_plane[uv_row * u_stride + uv_col]; + int v = v_plane[uv_row * v_stride + uv_col]; - // Reset decoder if currently playing or file loaded - if (m_decoder) - { - m_decoder.reset(); - m_playbackInitialized = false; + // 룩업 테이블에서 변환 계수 가져오기 + int r_offset = r_v_table[v]; + int g_u_offset = g_u_table[u]; + int g_v_offset = g_v_table[v]; + int b_offset = b_u_table[u]; - std::string decoderTypeName = GetDecoderTypeName(m_selectedDecoderType); - UpdateStatus("Decoder changed to: " + decoderTypeName); - OutputDebugStringA(("Decoder type changed to: " + decoderTypeName + "\n").c_str()); - } - else - { - std::string decoderTypeName = GetDecoderTypeName(m_selectedDecoderType); - UpdateStatus("Selected decoder: " + decoderTypeName); + // 2x2 블록의 4개 픽셀 처리 + for (int dy = 0; dy < 2 && (row + dy) < height; dy++) { + for (int dx = 0; dx < 2 && (col + dx) < width; dx++) { + uint32_t pixel_row = row + dy; + uint32_t pixel_col = col + dx; + + int y = y_plane[pixel_row * y_stride + pixel_col]; + int y_scaled = (298 * (y - 16) + 128) >> 8; + + // RGB 계산 + int r = std::clamp(y_scaled + r_offset, 0, 255); + int g = std::clamp(y_scaled - g_u_offset - g_v_offset, 0, 255); + int b = std::clamp(y_scaled + b_offset, 0, 255); + + // BGRA 형식으로 저장 + uint32_t pixel_offset = (pixel_row * width + pixel_col) * 4; + bgra_buffer[pixel_offset + 0] = static_cast(b); + bgra_buffer[pixel_offset + 1] = static_cast(g); + bgra_buffer[pixel_offset + 2] = static_cast(r); + bgra_buffer[pixel_offset + 3] = 255; + } } } } - catch (...) - { - UpdateStatus("Error changing decoder selection"); - } } - // File Output Control Event Handlers - void MainWindow::EnableFileOutputCheckBox_Checked(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&) + // 성능 최적화된 프레임 렌더링 함수 + void MainWindow::RenderFrameToScreen(const VideoFrame& frame, Media::Imaging::WriteableBitmap& bitmap, std::vector& bgra_buffer) { - m_fileOutputEnabled = true; - UpdateFileOutputControls(); - InitializeFileOutput(); - UpdateStatus("File output enabled"); - } - - void MainWindow::EnableFileOutputCheckBox_Unchecked(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&) - { - OutputDebugStringA("EnableFileOutputCheckBox_Unchecked: Starting\n"); - - m_fileOutputEnabled = false; - - // Completely destroy FileOutput object when disabled - if (m_fileOutput) { - OutputDebugStringA("EnableFileOutputCheckBox_Unchecked: Destroying FileOutput object\n"); - m_fileOutput.reset(); - } - - UpdateFileOutputControls(); - UpdateStatus("File output disabled"); - - OutputDebugStringA("EnableFileOutputCheckBox_Unchecked: Completed\n"); - } - - void MainWindow::OutputFormatComboBox_SelectionChanged(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::Controls::SelectionChangedEventArgs const&) - { - if (m_fileOutputEnabled && m_fileOutput) { - auto selectedIndex = OutputFormatComboBox().SelectedIndex(); - - FileOutput::OutputConfig config = m_fileOutput->GetConfig(); - - switch (selectedIndex) { - case 0: // BMP Format - config.format = FileOutput::OutputFormat::BMP; - UpdateStatus("Output format changed to BMP"); - break; - case 1: // Raw YUV - config.format = FileOutput::OutputFormat::RawYUV; - UpdateStatus("Output format changed to Raw YUV"); - break; - default: - config.format = FileOutput::OutputFormat::BMP; - break; - } - - m_fileOutput->SetConfig(config); - } - } - - // File Output Helper Methods - void MainWindow::UpdateFileOutputControls() - { - try { - // Enable/disable the format combo box based on checkbox state - OutputFormatComboBox().IsEnabled(m_fileOutputEnabled); - - // Update output folder display - UpdateOutputFolderDisplay(); - } - catch (const std::exception& e) { - UpdateStatus("Error updating file output controls: " + std::string(e.what())); - } - } - - void MainWindow::InitializeFileOutput() - { - if (!m_fileOutputEnabled) { + if (!frame.is_valid || !bitmap) { return; } try { - OutputDebugStringA("InitializeFileOutput: Starting FileOutput initialization\n"); + // YUV를 BGRA로 변환 + ConvertYUVToBGRA(frame, bgra_buffer.data(), frame.width, frame.height); - // Only create new FileOutput if it doesn't exist or needs reconfiguration - if (!m_fileOutput) { - // Create FileOutput with default configuration - FileOutput::OutputConfig config; - config.format = FileOutput::OutputFormat::BMP; - config.output_directory = "output"; - config.filename_prefix = "frame"; - config.create_subdirectories = true; - config.overwrite_existing = true; + // WriteableBitmap의 픽셀 버퍼에 직접 액세스 + auto buffer = bitmap.PixelBuffer(); + auto byteAccess = buffer.as<::IBufferByteAccess>(); + uint8_t* dest_pixels = nullptr; + byteAccess->Buffer(&dest_pixels); - OutputDebugStringA("InitializeFileOutput: Creating new FileOutput object\n"); - m_fileOutput = std::make_unique(config); + if (dest_pixels) { + // 메모리 복사를 최소화하기 위해 직접 복사 + memcpy(dest_pixels, bgra_buffer.data(), bgra_buffer.size()); - // Create output directory - if (!m_fileOutput->CreateOutputDirectory()) { - OutputDebugStringA("InitializeFileOutput: Failed to create output directory\n"); - UpdateStatus("Warning: Failed to create output directory"); - } else { - OutputDebugStringA("InitializeFileOutput: Output directory created successfully\n"); - } - } - - // Update the combo box selection to match - OutputFormatComboBox().SelectedIndex(0); // BMP by default - - UpdateOutputFolderDisplay(); - UpdateStatus("File output initialized successfully"); - OutputDebugStringA("InitializeFileOutput: FileOutput initialization completed successfully\n"); - } - catch (const std::exception& e) { - OutputDebugStringA(("InitializeFileOutput error: " + std::string(e.what()) + "\n").c_str()); - UpdateStatus("Error initializing file output: " + std::string(e.what())); - - // Don't automatically turn off the checkbox - let user decide - // m_fileOutputEnabled = false; - // EnableFileOutputCheckBox().IsChecked(false); - // UpdateFileOutputControls(); - } - } - - void MainWindow::UpdateOutputFolderDisplay() - { - try { - if (m_fileOutput && m_fileOutputEnabled) { - auto config = m_fileOutput->GetConfig(); - std::string displayPath = config.output_directory.string(); - - // Convert to wide string for display - std::wstring widePath(displayPath.begin(), displayPath.end()); - OutputFolderText().Text(widePath); - OutputFolderText().Foreground(winrt::Microsoft::UI::Xaml::Media::SolidColorBrush(winrt::Microsoft::UI::Colors::Black())); - } else { - OutputFolderText().Text(L"Not set"); - OutputFolderText().Foreground(winrt::Microsoft::UI::Xaml::Media::SolidColorBrush(winrt::Microsoft::UI::Colors::Gray())); + // 비트맵 무효화하여 UI 업데이트 트리거 + bitmap.Invalidate(); } } - catch (const std::exception& e) { - OutputFolderText().Text(L"Error"); - OutputFolderText().Foreground(winrt::Microsoft::UI::Xaml::Media::SolidColorBrush(winrt::Microsoft::UI::Colors::Red())); + catch (...) { + // 렌더링 오류 무시 } } - std::string MainWindow::GetDecoderTypeName(VideoDecoderFactory::DecoderType decoderType) const + void MainWindow::OpenFileButton_Click(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&) { - switch (decoderType) - { - case VideoDecoderFactory::DecoderType::AUTO: - return "AV1 Decoder (Auto)"; - case VideoDecoderFactory::DecoderType::HARDWARE_MF: - return "AV1 Hardware (Media Foundation)"; - case VideoDecoderFactory::DecoderType::SOFTWARE: - return "AV1 Software (dav1d)"; - default: - return "Unknown Decoder"; - } + // Not implemented } -} + + void MainWindow::TestDecodeButton_Click(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&) + { + // Not implemented + } + + void MainWindow::PlayButton_Click(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&) + { + // Not implemented + } + + void MainWindow::PauseButton_Click(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&) + { + // Not implemented + } + + void MainWindow::StopButton_Click(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&) + { + // Not implemented + } + + void MainWindow::DecoderSelectionComboBox_SelectionChanged(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::Controls::SelectionChangedEventArgs const&) + { + // Not implemented + } + + void MainWindow::EnableFileOutputCheckBox_Checked(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&) + { + // Not implemented + } + + void MainWindow::EnableFileOutputCheckBox_Unchecked(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&) + { + // Not implemented + } + + void MainWindow::OutputFormatComboBox_SelectionChanged(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::Controls::SelectionChangedEventArgs const&) + { + // Not implemented + } +} \ No newline at end of file diff --git a/vav2/Vav2Player/Vav2Player/MainWindow.xaml.h b/vav2/Vav2Player/Vav2Player/MainWindow.xaml.h index 2d8ec6e..544ae7c 100644 --- a/vav2/Vav2Player/Vav2Player/MainWindow.xaml.h +++ b/vav2/Vav2Player/Vav2Player/MainWindow.xaml.h @@ -1,92 +1,29 @@ #pragma once #include "MainWindow.g.h" -#include "src/Output/FileOutput.h" -#include "src/FileIO/WebMFileReader.h" -#include "src/Decoder/VideoDecoderFactory.h" -#include -#include -#include namespace winrt::Vav2Player::implementation { struct MainWindow : MainWindowT { - MainWindow() - { - // Initialize XAML components and event handlers - InitializeComponent(); - InitializeUI(); - } + MainWindow(); - // Event handlers void OpenFileButton_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e); void TestDecodeButton_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e); void PlayButton_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e); void PauseButton_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); - - // Decoder selection event handler void DecoderSelectionComboBox_SelectionChanged(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Controls::SelectionChangedEventArgs const& e); - - // File Output control event handlers void EnableFileOutputCheckBox_Checked(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e); void EnableFileOutputCheckBox_Unchecked(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e); void OutputFormatComboBox_SelectionChanged(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Controls::SelectionChangedEventArgs const& e); - private: - // Video processing components - std::unique_ptr m_fileReader; - std::unique_ptr m_decoder; - std::unique_ptr m_fileOutput; - // Video rendering components - winrt::Microsoft::UI::Xaml::Media::Imaging::WriteableBitmap m_renderBitmap{ nullptr }; - std::vector m_bgraBuffer; - // Current file path - std::string m_currentFilePath; - // Decoder selection state - VideoDecoderFactory::DecoderType m_selectedDecoderType = VideoDecoderFactory::DecoderType::AUTO; - - // UI state - bool m_isPlaying = false; - bool m_isFileLoaded = false; - bool m_fileOutputEnabled = false; - - // Playback state - uint64_t m_currentFrame = 0; - uint64_t m_totalFrames = 0; - double m_frameRate = 30.0; - bool m_playbackInitialized = false; - - // Timer for playback - winrt::Microsoft::UI::Dispatching::DispatcherQueueTimer m_playbackTimer{ nullptr }; - - // Helper methods - void UpdateStatus(const std::string& message); - void UpdateButtons(); - void ProcessSingleFrame(); - void UpdateProgress(); - void InitializePlayback(); - void StartPlaybackTimer(); - void StopPlaybackTimer(); - void InitializeUI(); - std::string GetDecoderTypeName(VideoDecoderFactory::DecoderType decoderType) const; - - // File Output helper methods - void UpdateFileOutputControls(); - void InitializeFileOutput(); - void UpdateOutputFolderDisplay(); - - // Video rendering methods - void InitializeVideoRenderer(); - void RenderFrameToScreen(const VideoFrame& frame); - void ConvertYUVToBGRA(const VideoFrame& yuv_frame, uint8_t* bgra_buffer, uint32_t width, uint32_t height); - - // File picker helper - winrt::Windows::Foundation::IAsyncAction OpenFileAsync(); + // 성능 최적화된 비디오 렌더링 함수들 + static void ConvertYUVToBGRA(const VideoFrame& yuv_frame, uint8_t* bgra_buffer, uint32_t width, uint32_t height); + static void RenderFrameToScreen(const VideoFrame& frame, winrt::Microsoft::UI::Xaml::Media::Imaging::WriteableBitmap& bitmap, std::vector& bgra_buffer); }; } @@ -95,4 +32,4 @@ namespace winrt::Vav2Player::factory_implementation struct MainWindow : MainWindowT { }; -} +} \ No newline at end of file diff --git a/vav2/Vav2Player/Vav2Player/Vav2Player.vcxproj b/vav2/Vav2Player/Vav2Player/Vav2Player.vcxproj index 4e100b2..d4c2855 100644 --- a/vav2/Vav2Player/Vav2Player/Vav2Player.vcxproj +++ b/vav2/Vav2Player/Vav2Player/Vav2Player.vcxproj @@ -108,7 +108,7 @@ $(ProjectDir)..\..\..\lib\libwebm;$(ProjectDir)..\..\..\lib\dav1d;%(AdditionalLibraryDirectories) - webm-debug.lib;dav1d-debug.lib;shell32.lib;user32.lib;advapi32.lib;ole32.lib + webm-debug.lib;dav1d-debug.lib;d3d12.lib;dxgi.lib;d3dcompiler.lib;shell32.lib;user32.lib;advapi32.lib;ole32.lib @@ -117,7 +117,7 @@ $(ProjectDir)..\..\..\lib\libwebm;$(ProjectDir)..\..\..\lib\dav1d;%(AdditionalLibraryDirectories) - webm.lib;dav1d.lib;shell32.lib;user32.lib;advapi32.lib;ole32.lib + webm.lib;dav1d.lib;d3d12.lib;dxgi.lib;d3dcompiler.lib;shell32.lib;user32.lib;advapi32.lib;ole32.lib true true @@ -156,6 +156,8 @@ + + @@ -190,6 +192,7 @@ + diff --git a/vav2/Vav2Player/Vav2Player/VideoPlayerControl.idl b/vav2/Vav2Player/Vav2Player/VideoPlayerControl.idl index 9ad13ea..cf6c535 100644 --- a/vav2/Vav2Player/Vav2Player/VideoPlayerControl.idl +++ b/vav2/Vav2Player/Vav2Player/VideoPlayerControl.idl @@ -8,15 +8,15 @@ namespace Vav2Player }; [default_interface] - runtimeclass VideoPlayerControl : Windows.UI.Xaml.Controls.UserControl + runtimeclass VideoPlayerControl : Microsoft.UI.Xaml.Controls.UserControl { VideoPlayerControl(); // Public Properties - String VideoSource{ get; set; }; - Boolean ShowControls{ get; set; }; - Boolean AutoPlay{ get; set; }; - VideoDecoderType DecoderType{ get; set; }; + String VideoSource; + Boolean ShowControls; + Boolean AutoPlay; + VideoDecoderType DecoderType; // Public Methods void LoadVideo(String filePath); diff --git a/vav2/Vav2Player/Vav2Player/VideoPlayerControl.xaml b/vav2/Vav2Player/Vav2Player/VideoPlayerControl.xaml index 781e8a2..fe169e1 100644 --- a/vav2/Vav2Player/Vav2Player/VideoPlayerControl.xaml +++ b/vav2/Vav2Player/Vav2Player/VideoPlayerControl.xaml @@ -11,15 +11,20 @@ Unloaded="UserControl_Unloaded"> - + - - + + + + - + - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +#include +#include using namespace winrt; using namespace winrt::Microsoft::UI::Xaml; @@ -14,6 +16,7 @@ using namespace winrt::Microsoft::UI::Dispatching; namespace winrt::Vav2Player::implementation { VideoPlayerControl::VideoPlayerControl() + : m_useHardwareRendering(false) { InitializeComponent(); } @@ -29,7 +32,7 @@ namespace winrt::Vav2Player::implementation // Set initial UI state if (!m_showControls) { - ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed); + // ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed); } // Auto load video if source is set @@ -68,6 +71,8 @@ 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) @@ -79,12 +84,16 @@ namespace winrt::Vav2Player::implementation 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) @@ -105,6 +114,7 @@ namespace winrt::Vav2Player::implementation // Ignore seek errors } } + */ void VideoPlayerControl::HoverDetector_PointerEntered(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const&) { @@ -153,11 +163,11 @@ namespace winrt::Vav2Player::implementation { if (value && m_isLoaded) { - ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible); + // ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible); } else { - ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed); + // ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed); } } } @@ -209,6 +219,35 @@ namespace winrt::Vav2Player::implementation SetInternalDecoderType(newType); } + bool VideoPlayerControl::UseHardwareRendering() + { + return m_useHardwareRendering; + } + + void VideoPlayerControl::UseHardwareRendering(bool value) + { + if (m_useHardwareRendering != value) + { + m_useHardwareRendering = value; + + // Switch rendering method + if (value) + { + // Enable D3D12 hardware rendering (to be implemented in Phase 2) + VideoSwapChainPanel().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible); + VideoImage().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed); + OutputDebugStringA("Switched to hardware D3D12 rendering\n"); + } + else + { + // Switch to CPU software rendering + VideoSwapChainPanel().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed); + VideoImage().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible); + OutputDebugStringA("Switched to software CPU rendering\n"); + } + } + } + VideoDecoderFactory::DecoderType VideoPlayerControl::GetInternalDecoderType() { return m_decoderType; @@ -295,11 +334,11 @@ namespace winrt::Vav2Player::implementation if (m_showControls) { - ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible); + // ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible); } // Update duration display - DurationText().Text(winrt::to_hstring(FormatTime(m_duration))); + // DurationText().Text(winrt::to_hstring(FormatTime(m_duration))); UpdateStatus(L"Video loaded successfully"); OutputDebugStringA(("Video loaded: " + filePathStr + "\n").c_str()); @@ -553,26 +592,60 @@ namespace winrt::Vav2Player::implementation if (!frame.is_valid || !m_renderBitmap) return; + // Declare variables at function scope to avoid compiler issues + size_t required_size = 0; + uint8_t* dest_pixels = nullptr; + try { - // Convert YUV to BGRA + // Check buffer size and minimize reallocation + required_size = frame.width * frame.height * 4; + if (m_bgraBuffer.size() != required_size) { + m_bgraBuffer.resize(required_size); + } + + // Convert YUV to BGRA (optimized version) ConvertYUVToBGRA(frame, m_bgraBuffer.data(), frame.width, frame.height); - // Update bitmap + // Direct access to WriteableBitmap pixel buffer (improved caching) auto buffer = m_renderBitmap.PixelBuffer(); auto byteAccess = buffer.as<::IBufferByteAccess>(); - uint8_t* pixels = nullptr; - byteAccess->Buffer(&pixels); + byteAccess->Buffer(&dest_pixels); - if (pixels) + if (dest_pixels) { - memcpy(pixels, m_bgraBuffer.data(), m_bgraBuffer.size()); + // Large memory copy optimization (using 64-bit alignment) + if (required_size >= 64 && (reinterpret_cast(dest_pixels) % 8) == 0 && + (reinterpret_cast(m_bgraBuffer.data()) % 8) == 0) + { + // Copy in 8-byte units (faster memory transfer) + uint64_t* src64 = reinterpret_cast(m_bgraBuffer.data()); + uint64_t* dst64 = reinterpret_cast(dest_pixels); + size_t qwords = required_size / 8; + size_t remainder = required_size % 8; + + for (size_t i = 0; i < qwords; i++) { + dst64[i] = src64[i]; + } + + // Copy remaining bytes + if (remainder > 0) { + memcpy(dest_pixels + qwords * 8, m_bgraBuffer.data() + qwords * 8, remainder); + } + } + else + { + // Standard memory copy + memcpy(dest_pixels, m_bgraBuffer.data(), required_size); + } + + // Invalidate bitmap (trigger UI update) m_renderBitmap.Invalidate(); } } catch (...) { - // Ignore rendering errors + // Ignore rendering errors (logging removed for performance) } } @@ -586,27 +659,68 @@ namespace winrt::Vav2Player::implementation const int32_t u_stride = yuv_frame.u_stride; const int32_t v_stride = yuv_frame.v_stride; - for (uint32_t row = 0; row < height; ++row) - { - for (uint32_t col = 0; col < width; ++col) - { - int32_t y = y_plane[row * y_stride + col]; - int32_t u = u_plane[(row / 2) * u_stride + (col / 2)]; - int32_t v = v_plane[(row / 2) * v_stride + (col / 2)]; + // Declare variables at function scope to avoid compiler issues + int r_offset = 0, g_u_offset = 0, g_v_offset = 0, b_offset = 0; + uint32_t pixel_offset = 0; - int c = y - 16; - int d = u - 128; - int e = v - 128; + // Optimization using lookup tables (initialized once with static variables) + static bool lookup_initialized = false; + static int16_t r_v_table[256]; + static int16_t g_u_table[256]; + static int16_t g_v_table[256]; + static int16_t b_u_table[256]; - int r = std::clamp((298 * c + 409 * e + 128) >> 8, 0, 255); - int g = std::clamp((298 * c - 100 * d - 208 * e + 128) >> 8, 0, 255); - int b = std::clamp((298 * c + 516 * d + 128) >> 8, 0, 255); + if (!lookup_initialized) { + for (int i = 0; i < 256; i++) { + int uv_val = i - 128; + r_v_table[i] = (409 * uv_val + 128) >> 8; + g_u_table[i] = (100 * uv_val + 128) >> 8; + g_v_table[i] = (208 * uv_val + 128) >> 8; + b_u_table[i] = (516 * uv_val + 128) >> 8; + } + lookup_initialized = true; + } - uint32_t pixel_offset = (row * width + col) * 4; - bgra_buffer[pixel_offset + 0] = static_cast(b); - bgra_buffer[pixel_offset + 1] = static_cast(g); - bgra_buffer[pixel_offset + 2] = static_cast(r); - bgra_buffer[pixel_offset + 3] = 255; + // Process in 2x2 blocks to reuse UV values (improved cache efficiency) + for (uint32_t row = 0; row < height; row += 2) { + for (uint32_t col = 0; col < width; col += 2) { + // One UV value per 2x2 block + uint32_t uv_row = row / 2; + uint32_t uv_col = col / 2; + + if (uv_row >= height / 2 || uv_col >= width / 2) continue; + + int u = u_plane[uv_row * u_stride + uv_col]; + int v = v_plane[uv_row * v_stride + uv_col]; + + // Get conversion coefficients from lookup tables + r_offset = r_v_table[v]; + g_u_offset = g_u_table[u]; + g_v_offset = g_v_table[v]; + b_offset = b_u_table[u]; + + // Process 4 pixels in 2x2 block + for (int dy = 0; dy < 2 && (row + dy) < height; dy++) { + for (int dx = 0; dx < 2 && (col + dx) < width; dx++) { + uint32_t pixel_row = row + dy; + uint32_t pixel_col = col + dx; + + int y = y_plane[pixel_row * y_stride + pixel_col]; + int y_scaled = (298 * (y - 16) + 128) >> 8; + + // RGB calculation (using lookup table values) + int r = std::clamp(y_scaled + r_offset, 0, 255); + int g = std::clamp(y_scaled - g_u_offset - g_v_offset, 0, 255); + int b = std::clamp(y_scaled + b_offset, 0, 255); + + // Store in BGRA format + pixel_offset = (pixel_row * width + pixel_col) * 4; + bgra_buffer[pixel_offset + 0] = static_cast(b); + bgra_buffer[pixel_offset + 1] = static_cast(g); + bgra_buffer[pixel_offset + 2] = static_cast(r); + bgra_buffer[pixel_offset + 3] = 255; + } + } } } } @@ -635,15 +749,15 @@ namespace winrt::Vav2Player::implementation // Update play/pause button if (m_isPlaying) { - PlayPauseIcon().Symbol(winrt::Microsoft::UI::Xaml::Controls::Symbol::Pause); + // PlayPauseIcon().Symbol(winrt::Microsoft::UI::Xaml::Controls::Symbol::Pause); } else { - PlayPauseIcon().Symbol(winrt::Microsoft::UI::Xaml::Controls::Symbol::Play); + // PlayPauseIcon().Symbol(winrt::Microsoft::UI::Xaml::Controls::Symbol::Play); } // Update current time display - CurrentTimeText().Text(winrt::to_hstring(FormatTime(m_currentTime))); + // CurrentTimeText().Text(winrt::to_hstring(FormatTime(m_currentTime))); } void VideoPlayerControl::UpdateProgress() @@ -651,7 +765,7 @@ namespace winrt::Vav2Player::implementation if (m_duration > 0) { double progress = (m_currentTime / m_duration) * 100.0; - ProgressSlider().Value(progress); + // ProgressSlider().Value(progress); } } @@ -691,7 +805,7 @@ namespace winrt::Vav2Player::implementation { if (m_showControls && m_isLoaded) { - ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible); + // ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible); } } @@ -699,7 +813,7 @@ namespace winrt::Vav2Player::implementation { if (m_showControls && m_isPlaying) { - ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed); + // ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed); } } @@ -760,7 +874,7 @@ namespace winrt::Vav2Player::implementation m_renderBitmap = nullptr; VideoImage().Source(nullptr); PlaceholderText().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible); - ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed); + // ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed); } bool VideoPlayerControl::CreateDecoder() @@ -796,4 +910,4 @@ namespace winrt::Vav2Player::implementation return false; } } -} \ No newline at end of file +} diff --git a/vav2/Vav2Player/Vav2Player/VideoPlayerControl.xaml.h b/vav2/Vav2Player/Vav2Player/VideoPlayerControl.xaml.h index 1a9fc28..ec98f0a 100644 --- a/vav2/Vav2Player/Vav2Player/VideoPlayerControl.xaml.h +++ b/vav2/Vav2Player/Vav2Player/VideoPlayerControl.xaml.h @@ -4,6 +4,7 @@ #include "src/FileIO/WebMFileReader.h" #include "src/Decoder/VideoDecoderFactory.h" #include "src/Common/VideoTypes.h" +#include "src/Rendering/D3D12VideoRenderer.h" #include #include #include @@ -17,9 +18,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); - 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); + // 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); @@ -36,6 +38,10 @@ 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); @@ -63,6 +69,10 @@ namespace winrt::Vav2Player::implementation winrt::Microsoft::UI::Xaml::Media::Imaging::WriteableBitmap m_renderBitmap{ nullptr }; std::vector m_bgraBuffer; + // D3D12 Hardware rendering (Phase 1) + std::unique_ptr m_d3d12Renderer; + bool m_useHardwareRendering; + // Configuration winrt::hstring m_videoSource; bool m_showControls = true; diff --git a/vav2/Vav2Player/Vav2Player/pch.h b/vav2/Vav2Player/Vav2Player/pch.h index d079b27..118d526 100644 --- a/vav2/Vav2Player/Vav2Player/pch.h +++ b/vav2/Vav2Player/Vav2Player/pch.h @@ -42,6 +42,13 @@ #include #include +// D3D12 DirectX Graphics +#include +#include +#include +#include +#include + // Forward declare IBufferByteAccess #ifndef __IBufferByteAccess_INTERFACE_DEFINED__ #define __IBufferByteAccess_INTERFACE_DEFINED__ diff --git a/vav2/Vav2Player/Vav2Player/src/Output/FileOutput.cpp b/vav2/Vav2Player/Vav2Player/src/Output/FileOutput.cpp index 0c142e7..eaa2713 100644 --- a/vav2/Vav2Player/Vav2Player/src/Output/FileOutput.cpp +++ b/vav2/Vav2Player/Vav2Player/src/Output/FileOutput.cpp @@ -1,4 +1,4 @@ -#include "pch.h" +#include "pch.h" #include "FileOutput.h" #include "../Common/PermissionUtils.h" #include @@ -32,7 +32,7 @@ FileOutput::FileOutput(const OutputConfig& config) OutputDebugStringA("[FileOutput] YUV->RGB conversion will use lookup table optimization\n"); } - // 권한 체크 및 설정 + // Check and set permissions if (!CheckAndSetupPermissions()) { OutputDebugStringA("[FileOutput] Warning: Failed to setup proper permissions for output directory\n"); } @@ -777,7 +777,7 @@ bool FileOutput::CheckAndSetupPermissions() { OutputDebugStringA(("[FileOutput] Checking permissions for output directory: " + full_output_path.string() + "\n").c_str()); - // 권한 체크 및 처리 + // Check and handle permissions auto permission_result = PermissionUtils::CheckAndHandlePermissions(full_output_path); switch (permission_result) { @@ -805,33 +805,33 @@ bool FileOutput::CheckAndSetupPermissions() { bool FileOutput::HandlePermissionDenied() { try { - // 안전한 출력 디렉토리 찾기 + // Find safe output directory std::filesystem::path safe_directory = PermissionUtils::GetSafeOutputDirectory(); OutputDebugStringA(("[FileOutput] Using safe output directory: " + safe_directory.string() + "\n").c_str()); - // 설정 업데이트 + // Update settings m_config.output_directory = safe_directory; - // 캐시 재초기화 + // Reinitialize cache InitializeCache(); - // 권한 재체크 + // Recheck permissions auto permission_result = PermissionUtils::CheckDirectoryCreatePermission(safe_directory); if (permission_result == PermissionUtils::PermissionResult::Granted) { OutputDebugStringA("[FileOutput] Safe directory permissions verified\n"); - // 사용자에게 알림 + // Notify user std::wstring safe_dir_wstr = std::wstring(safe_directory.string().begin(), safe_directory.string().end()); - std::wstring message = L"원래 출력 디렉토리에 권한이 없어 다음 위치를 사용합니다:\n" + safe_dir_wstr; - PermissionUtils::ShowPermissionDialog(message, L"출력 디렉토리 변경"); + std::wstring message = L"Permission denied for original output directory. Using alternate location:\n" + safe_dir_wstr; + PermissionUtils::ShowPermissionDialog(message, L"Output Directory Changed"); return true; } else { OutputDebugStringA("[FileOutput] Safe directory also lacks permissions\n"); - // 마지막 수단: 임시 디렉토리 + // Last resort: temporary directory wchar_t temp_path[MAX_PATH]; if (GetTempPathW(MAX_PATH, temp_path)) { std::filesystem::path temp_dir = std::filesystem::path(temp_path) / "Vav2Player_Emergency"; @@ -841,8 +841,8 @@ bool FileOutput::HandlePermissionDenied() { OutputDebugStringA(("[FileOutput] Using emergency temp directory: " + temp_dir.string() + "\n").c_str()); std::wstring temp_dir_wstr = std::wstring(temp_dir.string().begin(), temp_dir.string().end()); - std::wstring message = L"안전한 출력 디렉토리를 찾을 수 없어 임시 폴더를 사용합니다:\n" + temp_dir_wstr; - PermissionUtils::ShowPermissionDialog(message, L"긴급 출력 디렉토리"); + std::wstring message = L"Cannot find safe output directory. Using temporary folder:\n" + temp_dir_wstr; + PermissionUtils::ShowPermissionDialog(message, L"Emergency Output Directory"); return true; } else { diff --git a/vav2/Vav2Player/Vav2Player/src/Output/FileOutput.h b/vav2/Vav2Player/Vav2Player/src/Output/FileOutput.h index 7d05d01..91c9094 100644 --- a/vav2/Vav2Player/Vav2Player/src/Output/FileOutput.h +++ b/vav2/Vav2Player/Vav2Player/src/Output/FileOutput.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "../Common/VideoTypes.h" #include "../Common/PermissionUtils.h" #include diff --git a/vav2/Vav2Player/Vav2Player/src/Rendering/D3D12Helpers.h b/vav2/Vav2Player/Vav2Player/src/Rendering/D3D12Helpers.h new file mode 100644 index 0000000..d0db475 --- /dev/null +++ b/vav2/Vav2Player/Vav2Player/src/Rendering/D3D12Helpers.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include + +namespace Vav2Player { + +// D3D12 utility functions and helpers +class D3D12Helpers +{ +public: + // Error checking + static HRESULT ThrowIfFailed(HRESULT hr); + static const char* GetD3D12ErrorString(HRESULT hr); + + // Resource creation helpers + static HRESULT CreateBuffer(ID3D12Device* device, + UINT64 size, + D3D12_HEAP_TYPE heapType, + D3D12_RESOURCE_STATES initialState, + ID3D12Resource** ppResource); + + static HRESULT CreateTexture2D(ID3D12Device* device, + UINT width, + UINT height, + DXGI_FORMAT format, + D3D12_RESOURCE_FLAGS flags, + D3D12_RESOURCE_STATES initialState, + ID3D12Resource** ppResource); + + // Upload data helpers + static HRESULT UploadTextureData(ID3D12Device* device, + ID3D12GraphicsCommandList* commandList, + ID3D12Resource* destTexture, + const void* srcData, + UINT srcRowPitch, + UINT srcSlicePitch); + +private: + D3D12Helpers() = delete; // Static class only +}; + +// RAII wrapper for D3D12 command list recording +class ScopedCommandList +{ +public: + ScopedCommandList(ID3D12GraphicsCommandList* commandList, + ID3D12CommandAllocator* allocator, + ID3D12PipelineState* pipelineState = nullptr); + ~ScopedCommandList(); + + ID3D12GraphicsCommandList* Get() const { return m_commandList; } + operator ID3D12GraphicsCommandList*() const { return m_commandList; } + +private: + ID3D12GraphicsCommandList* m_commandList; + bool m_needsClose; +}; + +} // namespace Vav2Player \ No newline at end of file diff --git a/vav2/Vav2Player/Vav2Player/src/Rendering/D3D12VideoRenderer.cpp b/vav2/Vav2Player/Vav2Player/src/Rendering/D3D12VideoRenderer.cpp new file mode 100644 index 0000000..e27a5e5 --- /dev/null +++ b/vav2/Vav2Player/Vav2Player/src/Rendering/D3D12VideoRenderer.cpp @@ -0,0 +1,376 @@ +#include "pch.h" +#include "D3D12VideoRenderer.h" +#include "d3dx12.h" +#include +#include +#include + +namespace Vav2Player { + +D3D12VideoRenderer::D3D12VideoRenderer() + : m_isInitialized(false) + , m_width(0) + , m_height(0) + , m_frameIndex(0) + , m_rtvDescriptorSize(0) + , m_fenceEvent(nullptr) +{ + // Initialize fence values + for (UINT i = 0; i < FrameCount; i++) + { + m_fenceValues[i] = 0; + } +} + +D3D12VideoRenderer::~D3D12VideoRenderer() +{ + Shutdown(); +} + +HRESULT D3D12VideoRenderer::Initialize(winrt::Microsoft::UI::Xaml::Controls::SwapChainPanel const& panel, + uint32_t width, uint32_t height) +{ + if (m_isInitialized) + { + 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 swap chain + hr = CreateSwapChain(panel); + if (FAILED(hr)) return hr; + + // 4. Create descriptor heaps + hr = CreateDescriptorHeaps(); + if (FAILED(hr)) return hr; + + // 5. Create render targets + hr = CreateRenderTargets(); + if (FAILED(hr)) return hr; + + // 6. Create fence and event + hr = CreateFenceAndEvent(); + if (FAILED(hr)) return hr; + + m_isInitialized = true; + return S_OK; +} + +void D3D12VideoRenderer::Shutdown() +{ + if (!m_isInitialized) + return; + + // Wait for GPU to finish + if (m_commandQueue) + { + WaitForPreviousFrame(); + } + + // Close fence event + if (m_fenceEvent) + { + CloseHandle(m_fenceEvent); + m_fenceEvent = nullptr; + } + + // Reset all ComPtr objects (automatic cleanup) + m_device.Reset(); + m_commandQueue.Reset(); + m_swapChain.Reset(); + m_rtvHeap.Reset(); + m_commandAllocator.Reset(); + m_commandList.Reset(); + m_fence.Reset(); + + for (UINT i = 0; i < FrameCount; i++) + { + m_renderTargets[i].Reset(); + m_fenceValues[i] = 0; + } + + m_isInitialized = false; +} + +HRESULT D3D12VideoRenderer::RenderSolidColor(float r, float g, float b, float a) +{ + if (!m_isInitialized) + return E_FAIL; + + HRESULT hr = S_OK; + + // Wait for previous frame + hr = WaitForPreviousFrame(); + if (FAILED(hr)) return hr; + + // Reset command allocator + hr = m_commandAllocator->Reset(); + if (FAILED(hr)) return hr; + + // Reset command list + hr = m_commandList->Reset(m_commandAllocator.Get(), nullptr); + if (FAILED(hr)) return hr; + + // Get current render target + CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle( + m_rtvHeap->GetCPUDescriptorHandleForHeapStart(), + m_frameIndex, + m_rtvDescriptorSize); + + // Set render target + m_commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr); + + // Clear render target with solid color + const float clearColor[] = { r, g, b, a }; + m_commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr); + + // Transition render target to present state + CD3DX12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::CreateTransition( + m_renderTargets[m_frameIndex].Get(), + D3D12_RESOURCE_STATE_RENDER_TARGET, + D3D12_RESOURCE_STATE_PRESENT); + m_commandList->ResourceBarrier(1, &barrier); + + // Close command list + hr = m_commandList->Close(); + if (FAILED(hr)) return hr; + + // Execute command list + ID3D12CommandList* ppCommandLists[] = { m_commandList.Get() }; + m_commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists); + + // Present + hr = m_swapChain->Present(1, 0); + if (FAILED(hr)) return hr; + + // Update frame index + m_frameIndex = m_swapChain->GetCurrentBackBufferIndex(); + + return S_OK; +} + +HRESULT D3D12VideoRenderer::RenderFrame(const VideoFrame& frame) +{ + // TODO: Phase 3에서 YUV 텍스처 렌더링 구현 + // 현재는 단색 렌더링으로 대체 + return RenderSolidColor(0.0f, 0.5f, 1.0f, 1.0f); // 파란색 +} + +HRESULT D3D12VideoRenderer::Resize(uint32_t width, uint32_t height) +{ + if (!m_isInitialized) + return E_FAIL; + + if (m_width == width && m_height == height) + return S_OK; + + // Wait for GPU + WaitForPreviousFrame(); + + // Release render targets + for (UINT i = 0; i < FrameCount; i++) + { + m_renderTargets[i].Reset(); + } + + // Resize swap chain + HRESULT hr = m_swapChain->ResizeBuffers(FrameCount, width, height, DXGI_FORMAT_R8G8B8A8_UNORM, 0); + if (FAILED(hr)) return hr; + + m_width = width; + m_height = height; + m_frameIndex = m_swapChain->GetCurrentBackBufferIndex(); + + // Recreate render targets + return CreateRenderTargets(); +} + +// Private helper methods implementation + +HRESULT D3D12VideoRenderer::CreateDevice() +{ + // Enable debug layer in debug builds +#ifdef _DEBUG + ComPtr debugController; + if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)))) + { + debugController->EnableDebugLayer(); + } +#endif + + // Create DXGI factory + ComPtr factory; + HRESULT hr = CreateDXGIFactory1(IID_PPV_ARGS(&factory)); + if (FAILED(hr)) return hr; + + // Try to create hardware device + ComPtr hardwareAdapter; + for (UINT adapterIndex = 0; + DXGI_ERROR_NOT_FOUND != factory->EnumAdapters1(adapterIndex, &hardwareAdapter); + ++adapterIndex) + { + DXGI_ADAPTER_DESC1 desc; + hardwareAdapter->GetDesc1(&desc); + + if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) + continue; + + // Try to create device + if (SUCCEEDED(D3D12CreateDevice(hardwareAdapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&m_device)))) + break; + } + + if (!m_device) + { + // Fallback to WARP device + ComPtr warpAdapter; + hr = factory->EnumWarpAdapter(IID_PPV_ARGS(&warpAdapter)); + if (FAILED(hr)) return hr; + + hr = D3D12CreateDevice(warpAdapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&m_device)); + if (FAILED(hr)) return hr; + } + + return S_OK; +} + +HRESULT D3D12VideoRenderer::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)) return hr; + + // Create command allocator + hr = m_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&m_commandAllocator)); + if (FAILED(hr)) 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)) return hr; + + // Close command list (will be reset before use) + hr = m_commandList->Close(); + return hr; +} + +HRESULT D3D12VideoRenderer::CreateSwapChain(winrt::Microsoft::UI::Xaml::Controls::SwapChainPanel const& panel) +{ + // Get DXGI factory + ComPtr factory; + HRESULT hr = CreateDXGIFactory1(IID_PPV_ARGS(&factory)); + if (FAILED(hr)) return hr; + + // Describe swap chain + DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {}; + swapChainDesc.BufferCount = FrameCount; + swapChainDesc.Width = m_width; + swapChainDesc.Height = m_height; + swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; + swapChainDesc.SampleDesc.Count = 1; + + ComPtr swapChain; + hr = factory->CreateSwapChainForComposition(m_commandQueue.Get(), &swapChainDesc, nullptr, &swapChain); + if (FAILED(hr)) return hr; + + hr = swapChain.As(&m_swapChain); + if (FAILED(hr)) return hr; + + // Associate swap chain with SwapChainPanel + auto panelNative = panel.as(); + hr = panelNative->SetSwapChain(m_swapChain.Get()); + if (FAILED(hr)) return hr; + + m_frameIndex = m_swapChain->GetCurrentBackBufferIndex(); + return S_OK; +} + +HRESULT D3D12VideoRenderer::CreateDescriptorHeaps() +{ + // Create RTV descriptor heap + D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {}; + rtvHeapDesc.NumDescriptors = FrameCount; + rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; + rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; + + HRESULT hr = m_device->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&m_rtvHeap)); + if (FAILED(hr)) return hr; + + m_rtvDescriptorSize = m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); + return S_OK; +} + +HRESULT D3D12VideoRenderer::CreateRenderTargets() +{ + CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_rtvHeap->GetCPUDescriptorHandleForHeapStart()); + + // Create RTV for each frame + for (UINT n = 0; n < FrameCount; n++) + { + HRESULT hr = m_swapChain->GetBuffer(n, IID_PPV_ARGS(&m_renderTargets[n])); + if (FAILED(hr)) return hr; + + m_device->CreateRenderTargetView(m_renderTargets[n].Get(), nullptr, rtvHandle); + rtvHandle.Offset(1, m_rtvDescriptorSize); + } + + return S_OK; +} + +HRESULT D3D12VideoRenderer::CreateFenceAndEvent() +{ + HRESULT hr = m_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence)); + if (FAILED(hr)) return hr; + + m_fenceValues[m_frameIndex] = 1; + + // Create event for fence + m_fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); + if (m_fenceEvent == nullptr) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + } + + return hr; +} + +HRESULT D3D12VideoRenderer::WaitForPreviousFrame() +{ + // Signal and wait for fence + const UINT64 fence = m_fenceValues[m_frameIndex]; + HRESULT hr = m_commandQueue->Signal(m_fence.Get(), fence); + if (FAILED(hr)) return hr; + + m_fenceValues[m_frameIndex]++; + + // Wait for fence to complete + if (m_fence->GetCompletedValue() < fence) + { + hr = m_fence->SetEventOnCompletion(fence, m_fenceEvent); + if (FAILED(hr)) return hr; + + WaitForSingleObject(m_fenceEvent, INFINITE); + } + + m_frameIndex = m_swapChain->GetCurrentBackBufferIndex(); + return S_OK; +} + +} // namespace Vav2Player \ No newline at end of file diff --git a/vav2/Vav2Player/Vav2Player/src/Rendering/D3D12VideoRenderer.h b/vav2/Vav2Player/Vav2Player/src/Rendering/D3D12VideoRenderer.h new file mode 100644 index 0000000..0d451c6 --- /dev/null +++ b/vav2/Vav2Player/Vav2Player/src/Rendering/D3D12VideoRenderer.h @@ -0,0 +1,75 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "../Common/VideoTypes.h" + +using Microsoft::WRL::ComPtr; + +namespace Vav2Player { + +class D3D12VideoRenderer +{ +public: + D3D12VideoRenderer(); + ~D3D12VideoRenderer(); + + // 초기화 및 해제 + HRESULT Initialize(winrt::Microsoft::UI::Xaml::Controls::SwapChainPanel const& panel, + uint32_t width, uint32_t height); + void Shutdown(); + + // 렌더링 + HRESULT RenderFrame(const VideoFrame& frame); + HRESULT RenderSolidColor(float r, float g, float b, float a = 1.0f); + + // 상태 확인 + bool IsInitialized() const { return m_isInitialized; } + uint32_t GetWidth() const { return m_width; } + uint32_t GetHeight() const { return m_height; } + + // 리사이즈 + HRESULT Resize(uint32_t width, uint32_t height); + +private: + // D3D12 Core Objects + ComPtr m_device; + ComPtr m_commandQueue; + ComPtr m_swapChain; + ComPtr m_rtvHeap; + + // Command Objects + ComPtr m_commandAllocator; + ComPtr m_commandList; + + // Render Targets + static const UINT FrameCount = 2; + ComPtr m_renderTargets[FrameCount]; + UINT m_frameIndex; + + // Synchronization + ComPtr m_fence; + UINT64 m_fenceValues[FrameCount]; + HANDLE m_fenceEvent; + + // State + bool m_isInitialized; + uint32_t m_width; + uint32_t m_height; + UINT m_rtvDescriptorSize; + + // Helper methods + HRESULT CreateDevice(); + HRESULT CreateCommandQueue(); + HRESULT CreateSwapChain(winrt::Microsoft::UI::Xaml::Controls::SwapChainPanel const& panel); + HRESULT CreateDescriptorHeaps(); + HRESULT CreateRenderTargets(); + HRESULT CreateFenceAndEvent(); + HRESULT WaitForPreviousFrame(); + HRESULT PopulateCommandList(); +}; + +} // namespace Vav2Player \ No newline at end of file diff --git a/vav2/Vav2Player/Vav2Player/src/Rendering/d3dx12.h b/vav2/Vav2Player/Vav2Player/src/Rendering/d3dx12.h new file mode 100644 index 0000000..5034ada --- /dev/null +++ b/vav2/Vav2Player/Vav2Player/src/Rendering/d3dx12.h @@ -0,0 +1,62 @@ +#pragma once + +// Minimal D3D12 helper classes +// Full version available at: https://github.com/Microsoft/DirectX-Graphics-Samples + +#include + +struct CD3DX12_RESOURCE_BARRIER : public D3D12_RESOURCE_BARRIER +{ + CD3DX12_RESOURCE_BARRIER() = default; + explicit CD3DX12_RESOURCE_BARRIER(const D3D12_RESOURCE_BARRIER& o) : D3D12_RESOURCE_BARRIER(o) {} + + static inline CD3DX12_RESOURCE_BARRIER CreateTransition( + ID3D12Resource* pResource, + D3D12_RESOURCE_STATES stateBefore, + D3D12_RESOURCE_STATES stateAfter, + UINT subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, + D3D12_RESOURCE_BARRIER_FLAGS flags = D3D12_RESOURCE_BARRIER_FLAG_NONE) + { + CD3DX12_RESOURCE_BARRIER result = {}; + result.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + result.Flags = flags; + result.Transition.pResource = pResource; + result.Transition.StateBefore = stateBefore; + result.Transition.StateAfter = stateAfter; + result.Transition.Subresource = subresource; + return result; + } +}; + +struct CD3DX12_CPU_DESCRIPTOR_HANDLE : public D3D12_CPU_DESCRIPTOR_HANDLE +{ + CD3DX12_CPU_DESCRIPTOR_HANDLE() = default; + explicit CD3DX12_CPU_DESCRIPTOR_HANDLE(const D3D12_CPU_DESCRIPTOR_HANDLE& o) : D3D12_CPU_DESCRIPTOR_HANDLE(o) {} + CD3DX12_CPU_DESCRIPTOR_HANDLE(D3D12_CPU_DESCRIPTOR_HANDLE other, INT offsetScaledByIncrementSize) + { + ptr = other.ptr + offsetScaledByIncrementSize; + } + CD3DX12_CPU_DESCRIPTOR_HANDLE(D3D12_CPU_DESCRIPTOR_HANDLE other, INT offsetInDescriptors, UINT descriptorIncrementSize) + { + ptr = other.ptr + offsetInDescriptors * descriptorIncrementSize; + } + + CD3DX12_CPU_DESCRIPTOR_HANDLE& Offset(INT offsetInDescriptors, UINT descriptorIncrementSize) + { + ptr += offsetInDescriptors * descriptorIncrementSize; + return *this; + } + CD3DX12_CPU_DESCRIPTOR_HANDLE& Offset(INT offsetScaledByIncrementSize) + { + ptr += offsetScaledByIncrementSize; + return *this; + } + bool operator==(const D3D12_CPU_DESCRIPTOR_HANDLE& other) const + { + return ptr == other.ptr; + } + bool operator!=(const D3D12_CPU_DESCRIPTOR_HANDLE& other) const + { + return ptr != other.ptr; + } +}; \ No newline at end of file