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