Simple CPU pipeline implementation
This commit is contained in:
@@ -67,7 +67,9 @@
|
||||
"Bash(\"/c/Program Files/Microsoft Visual Studio/2022/Community/MSBuild/Current/Bin/MSBuild.exe\" Vav2Player.vcxproj \"//p:Configuration=Debug\" \"//p:Platform=x64\" \"//v:minimal\")",
|
||||
"Bash(python3:*)",
|
||||
"Bash(find:*)",
|
||||
"Bash(\"/c/Program Files/Microsoft Visual Studio/2022/Community/MSBuild/Current/Bin/MSBuild.exe\" Vav2PlayerHeadless.vcxproj \"/p:Configuration=Debug\" \"/p:Platform=x64\" \"/v:minimal\")"
|
||||
"Bash(\"/c/Program Files/Microsoft Visual Studio/2022/Community/MSBuild/Current/Bin/MSBuild.exe\" Vav2PlayerHeadless.vcxproj \"/p:Configuration=Debug\" \"/p:Platform=x64\" \"/v:minimal\")",
|
||||
"Bash(cmd:*)",
|
||||
"Bash(\"C:/Program Files/Microsoft Visual Studio/2022/Community/MSBuild/Current/Bin/MSBuild.exe\" Vav2Player.vcxproj /p:Configuration=Debug /p:Platform=x64 /v:minimal)",
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
<DependentUpon>MultiVideoTestWindow.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\Common\VideoTypes.h" />
|
||||
<ClInclude Include="src\Common\PermissionUtils.h" />
|
||||
|
||||
<ClInclude Include="src\Decoder\IVideoDecoder.h" />
|
||||
<ClInclude Include="src\Decoder\VideoDecoderFactory.h" />
|
||||
<!-- <ClInclude Include="src\Decoder\AV1Decoder.h" /> -->
|
||||
@@ -179,7 +179,7 @@
|
||||
<DependentUpon>MultiVideoTestWindow.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\Decoder\VideoDecoderFactory.cpp" />
|
||||
<ClCompile Include="src\Common\PermissionUtils.cpp" />
|
||||
|
||||
<!-- <ClCompile Include="src\Decoder\AV1Decoder.cpp" /> -->
|
||||
<ClCompile Include="headless\AV1Decoder_Headless.cpp" />
|
||||
<ClCompile Include="src\Decoder\MediaFoundationAV1Decoder.cpp" />
|
||||
|
||||
@@ -97,7 +97,6 @@
|
||||
|
||||
<!-- Core Infrastructure -->
|
||||
<!-- Note: FramePool.cpp and PacketPool.cpp removed during Phase 1 simplification -->
|
||||
<ClCompile Include="src\Common\PermissionUtils.cpp" />
|
||||
|
||||
<!-- Video Processing Components -->
|
||||
<ClCompile Include="src\FileIO\WebMFileReader.cpp" />
|
||||
@@ -106,7 +105,6 @@
|
||||
<!-- <ClCompile Include="src\Decoder\VideoDecoderFactory.cpp" /> -->
|
||||
|
||||
<!-- Phase 3: Simple GPU Renderer (Headless Version) -->
|
||||
<ClCompile Include="src\Rendering\SimpleGPURenderer_Headless.cpp" />
|
||||
|
||||
<!-- Additional components can be added here as needed -->
|
||||
</ItemGroup>
|
||||
@@ -122,7 +120,6 @@
|
||||
<ClInclude Include="src\Common\StdComPtr.h" />
|
||||
<ClInclude Include="src\Common\FramePool.h" />
|
||||
<ClInclude Include="src\Common\PacketPool.h" />
|
||||
<ClInclude Include="src\Common\PermissionUtils.h" />
|
||||
|
||||
<!-- Video Processing Headers -->
|
||||
<ClInclude Include="src\FileIO\WebMFileReader.h" />
|
||||
@@ -131,7 +128,6 @@
|
||||
<ClInclude Include="src\Decoder\VideoDecoderFactory.h" />
|
||||
|
||||
<!-- GPU Rendering Headers -->
|
||||
<ClInclude Include="src\Rendering\SimpleGPURenderer_Headless.h" />
|
||||
|
||||
<!-- Precompiled Header Creation -->
|
||||
<ClCompile Include="headless\pch.cpp">
|
||||
|
||||
@@ -5,11 +5,17 @@
|
||||
#endif
|
||||
|
||||
#include <winrt/Microsoft.UI.Dispatching.h>
|
||||
#include <winrt/Windows.Storage.Streams.h>
|
||||
#include <windows.storage.streams.h>
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
#include "headless/AV1Decoder_Headless.h"
|
||||
#include "src/Rendering/SimpleGPURenderer.h"
|
||||
#include "src/Common/VideoTypes.h"
|
||||
#include "src/FileIO/WebMFileReader.h"
|
||||
#include "src/Decoder/VideoDecoderFactory.h"
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::Microsoft::UI::Xaml;
|
||||
@@ -19,7 +25,7 @@ using namespace winrt::Microsoft::UI::Dispatching;
|
||||
namespace winrt::Vav2Player::implementation
|
||||
{
|
||||
VideoPlayerControl::VideoPlayerControl()
|
||||
: m_useHardwareRendering(true)
|
||||
: m_useHardwareRendering(false) // CPU mode for Phase 1
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
@@ -37,12 +43,6 @@ namespace winrt::Vav2Player::implementation
|
||||
m_isInitialized = true;
|
||||
UpdateStatus(L"Ready");
|
||||
|
||||
// Set initial UI state
|
||||
if (!m_showControls)
|
||||
{
|
||||
// ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed);
|
||||
}
|
||||
|
||||
// Auto load video if source is set
|
||||
if (!m_videoSource.empty())
|
||||
{
|
||||
@@ -58,6 +58,21 @@ namespace winrt::Vav2Player::implementation
|
||||
});
|
||||
|
||||
OutputDebugStringA("VideoPlayerControl loaded successfully\n");
|
||||
|
||||
// Show purple outline placeholder while waiting
|
||||
ShowPurpleOutlinePlaceholder();
|
||||
|
||||
// After 3 seconds, try to load actual video
|
||||
auto delayTimer = winrt::Microsoft::UI::Xaml::DispatcherTimer();
|
||||
delayTimer.Interval(std::chrono::seconds(3));
|
||||
delayTimer.Tick([this, delayTimer](auto&&, auto&&) mutable {
|
||||
delayTimer.Stop();
|
||||
// Try to load test video file
|
||||
auto testVideoPath = L"D:/Project/video-av1/sample/simple_test.webm";
|
||||
OutputDebugStringA("[DEBUG] Attempting to load test video after 3 second delay\n");
|
||||
LoadVideo(testVideoPath);
|
||||
});
|
||||
delayTimer.Start();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
@@ -65,15 +80,86 @@ namespace winrt::Vav2Player::implementation
|
||||
}
|
||||
}
|
||||
|
||||
void VideoPlayerControl::ShowPurpleOutlinePlaceholder()
|
||||
{
|
||||
try
|
||||
{
|
||||
OutputDebugStringA("Showing purple outline placeholder...\n");
|
||||
|
||||
// Get container size for full screen placeholder
|
||||
auto container = VideoDisplayArea();
|
||||
if (!container) return;
|
||||
|
||||
int width = static_cast<int>(container.ActualWidth());
|
||||
int height = static_cast<int>(container.ActualHeight());
|
||||
|
||||
// Use minimum size if container not ready
|
||||
if (width <= 0 || height <= 0) {
|
||||
width = 800;
|
||||
height = 600;
|
||||
}
|
||||
|
||||
m_renderBitmap = winrt::Microsoft::UI::Xaml::Media::Imaging::WriteableBitmap(width, height);
|
||||
VideoImage().Source(m_renderBitmap);
|
||||
VideoImage().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible);
|
||||
VideoImage().Width(width);
|
||||
VideoImage().Height(height);
|
||||
|
||||
auto buffer = m_renderBitmap.PixelBuffer();
|
||||
auto bufferByteAccess = buffer.as<::Windows::Storage::Streams::IBufferByteAccess>();
|
||||
uint8_t* bufferData = nullptr;
|
||||
winrt::check_hresult(bufferByteAccess->Buffer(&bufferData));
|
||||
|
||||
// Fill with transparent background
|
||||
for (int i = 0; i < width * height; i++)
|
||||
{
|
||||
bufferData[i * 4 + 0] = 0; // Blue
|
||||
bufferData[i * 4 + 1] = 0; // Green
|
||||
bufferData[i * 4 + 2] = 0; // Red
|
||||
bufferData[i * 4 + 3] = 0; // Alpha (transparent)
|
||||
}
|
||||
|
||||
// Draw purple outline (border thickness = 4 pixels)
|
||||
int borderThickness = 4;
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
bool isOutline = (x < borderThickness || x >= width - borderThickness ||
|
||||
y < borderThickness || y >= height - borderThickness);
|
||||
|
||||
if (isOutline)
|
||||
{
|
||||
int index = (y * width + x) * 4;
|
||||
bufferData[index + 0] = 128; // Blue
|
||||
bufferData[index + 1] = 0; // Green
|
||||
bufferData[index + 2] = 128; // Red (Purple = Red + Blue)
|
||||
bufferData[index + 3] = 255; // Alpha (opaque)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buffer.Length(width * height * 4);
|
||||
m_renderBitmap.Invalidate();
|
||||
OutputDebugStringA("Purple outline placeholder completed\n");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
OutputDebugStringA("Purple outline placeholder failed\n");
|
||||
}
|
||||
}
|
||||
|
||||
void VideoPlayerControl::UserControl_Unloaded(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&)
|
||||
{
|
||||
try
|
||||
{
|
||||
Stop();
|
||||
StopPlaybackTimer();
|
||||
if (m_playbackTimer)
|
||||
{
|
||||
m_playbackTimer.Stop();
|
||||
}
|
||||
StopControlsHideTimer();
|
||||
|
||||
// Cleanup resources
|
||||
if (m_gpuRenderer)
|
||||
{
|
||||
m_gpuRenderer->Shutdown();
|
||||
@@ -92,51 +178,6 @@ namespace winrt::Vav2Player::implementation
|
||||
}
|
||||
}
|
||||
|
||||
// Control event handlers (commented out as controls are disabled)
|
||||
/*
|
||||
void VideoPlayerControl::PlayPauseButton_Click(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&)
|
||||
{
|
||||
if (m_isPlaying)
|
||||
{
|
||||
Pause();
|
||||
}
|
||||
else
|
||||
{
|
||||
Play();
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
void VideoPlayerControl::StopButton_Click(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
void VideoPlayerControl::ProgressSlider_ValueChanged(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::Controls::Primitives::RangeBaseValueChangedEventArgs const& e)
|
||||
{
|
||||
if (!m_isLoaded || !m_fileReader)
|
||||
return;
|
||||
|
||||
// Avoid feedback loop during programmatic updates
|
||||
if (e.OldValue() == e.NewValue())
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
double percentage = e.NewValue();
|
||||
double targetTime = (percentage / 100.0) * m_duration;
|
||||
Seek(targetTime);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Ignore seek errors
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
void VideoPlayerControl::HoverDetector_PointerEntered(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const&)
|
||||
{
|
||||
if (m_showControls && m_isLoaded)
|
||||
@@ -182,14 +223,7 @@ namespace winrt::Vav2Player::implementation
|
||||
m_showControls = value;
|
||||
if (m_isInitialized)
|
||||
{
|
||||
if (value && m_isLoaded)
|
||||
{
|
||||
// ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible);
|
||||
}
|
||||
else
|
||||
{
|
||||
// ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed);
|
||||
}
|
||||
// Update controls visibility based on value and loaded state
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,10 +332,12 @@ namespace winrt::Vav2Player::implementation
|
||||
// Public Methods
|
||||
void VideoPlayerControl::LoadVideo(winrt::hstring const& filePath)
|
||||
{
|
||||
OutputDebugStringA("[VideoPlayerControl] LoadVideo called\n");
|
||||
OutputDebugStringA("[DEBUG] LoadVideo called\n");
|
||||
try
|
||||
{
|
||||
std::string filePathStr = winrt::to_string(filePath);
|
||||
OutputDebugStringA(("[DEBUG] Loading file: " + filePathStr + "\n").c_str());
|
||||
|
||||
UpdateStatus(L"Loading video...");
|
||||
LoadingRing().IsActive(true);
|
||||
|
||||
@@ -311,80 +347,98 @@ namespace winrt::Vav2Player::implementation
|
||||
// Create file reader
|
||||
if (!m_fileReader)
|
||||
{
|
||||
OutputDebugStringA("[DEBUG] Creating WebMFileReader\n");
|
||||
m_fileReader = std::make_unique<WebMFileReader>();
|
||||
}
|
||||
|
||||
// Open file
|
||||
OutputDebugStringA("[DEBUG] Opening file...\n");
|
||||
if (!m_fileReader->OpenFile(filePathStr))
|
||||
{
|
||||
OutputDebugStringA("[DEBUG] Failed to open file\n");
|
||||
UpdateStatus(L"Failed to open video file");
|
||||
LoadingRing().IsActive(false);
|
||||
return;
|
||||
}
|
||||
OutputDebugStringA("[DEBUG] File opened successfully\n");
|
||||
|
||||
// Get video tracks
|
||||
OutputDebugStringA("[DEBUG] Getting video tracks...\n");
|
||||
auto tracks = m_fileReader->GetVideoTracks();
|
||||
if (tracks.empty())
|
||||
{
|
||||
OutputDebugStringA("[DEBUG] No video tracks found\n");
|
||||
UpdateStatus(L"No video tracks found");
|
||||
LoadingRing().IsActive(false);
|
||||
return;
|
||||
}
|
||||
OutputDebugStringA(("[DEBUG] Found " + std::to_string(tracks.size()) + " video tracks\n").c_str());
|
||||
|
||||
// Select first video track
|
||||
OutputDebugStringA("[DEBUG] Selecting video track...\n");
|
||||
if (!m_fileReader->SelectVideoTrack(tracks[0].track_number))
|
||||
{
|
||||
OutputDebugStringA("[DEBUG] Failed to select video track\n");
|
||||
UpdateStatus(L"Failed to select video track");
|
||||
LoadingRing().IsActive(false);
|
||||
return;
|
||||
}
|
||||
OutputDebugStringA("[DEBUG] Video track selected successfully\n");
|
||||
|
||||
// Get metadata
|
||||
OutputDebugStringA("[DEBUG] Getting video metadata...\n");
|
||||
auto metadata = m_fileReader->GetVideoMetadata();
|
||||
m_totalFrames = metadata.total_frames;
|
||||
m_frameRate = metadata.frame_rate > 0 ? metadata.frame_rate : 30.0;
|
||||
m_duration = m_totalFrames / m_frameRate;
|
||||
|
||||
// Enable hardware rendering for optimal performance
|
||||
UseHardwareRendering(true);
|
||||
OutputDebugStringA(("[DEBUG] Video metadata: " + std::to_string(metadata.width) + "x" + std::to_string(metadata.height) +
|
||||
", " + std::to_string(m_totalFrames) + " frames, " + std::to_string(m_frameRate) + " fps\n").c_str());
|
||||
|
||||
// Enable CPU rendering for Phase 1 (MAJOR_REFACTORING_GUIDE)
|
||||
UseHardwareRendering(false);
|
||||
|
||||
// Create and initialize decoder
|
||||
if (!CreateDecoder() || !InitializeDecoder())
|
||||
OutputDebugStringA("[DEBUG] Creating decoder...\n");
|
||||
if (!CreateDecoder())
|
||||
{
|
||||
UpdateStatus(L"Failed to initialize decoder");
|
||||
OutputDebugStringA("[DEBUG] Failed to create decoder\n");
|
||||
UpdateStatus(L"Failed to create decoder");
|
||||
LoadingRing().IsActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
OutputDebugStringA("[DEBUG] Initializing decoder...\n");
|
||||
if (!InitializeDecoder())
|
||||
{
|
||||
OutputDebugStringA("[DEBUG] Failed to initialize decoder\n");
|
||||
UpdateStatus(L"Failed to initialize decoder");
|
||||
LoadingRing().IsActive(false);
|
||||
return;
|
||||
}
|
||||
OutputDebugStringA("[DEBUG] Decoder initialized successfully\n");
|
||||
|
||||
// Initialize video renderer
|
||||
InitializeVideoRenderer();
|
||||
|
||||
// Simplified initialization - removed complex pipelines
|
||||
|
||||
// Update UI state
|
||||
m_isLoaded = true;
|
||||
LoadingRing().IsActive(false);
|
||||
PlaceholderText().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed);
|
||||
|
||||
if (m_showControls)
|
||||
{
|
||||
// ControlsGrid().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible);
|
||||
}
|
||||
|
||||
// Update duration display
|
||||
// DurationText().Text(winrt::to_hstring(FormatTime(m_duration)));
|
||||
|
||||
UpdateStatus(L"Video loaded successfully");
|
||||
OutputDebugStringA(("Video loaded: " + filePathStr + "\n").c_str());
|
||||
OutputDebugStringA(("[DEBUG] Video loaded successfully: " + filePathStr + "\n").c_str());
|
||||
|
||||
// Auto play if enabled
|
||||
if (m_autoPlay)
|
||||
{
|
||||
OutputDebugStringA("[DEBUG] Auto play enabled - starting playback\n");
|
||||
Play();
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
OutputDebugStringA("[DEBUG] Exception in LoadVideo\n");
|
||||
UpdateStatus(L"Error loading video");
|
||||
LoadingRing().IsActive(false);
|
||||
}
|
||||
@@ -392,38 +446,429 @@ namespace winrt::Vav2Player::implementation
|
||||
|
||||
void VideoPlayerControl::Play()
|
||||
{
|
||||
OutputDebugStringA("[DEBUG] Play() called\n");
|
||||
if (!m_isLoaded || m_isPlaying)
|
||||
{
|
||||
OutputDebugStringA("[DEBUG] Play() - not ready or already playing\n");
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
m_isPlaying = true;
|
||||
UpdateStatus(L"Playing");
|
||||
|
||||
// Setup playback timer for continuous frame processing
|
||||
if (!m_playbackTimer)
|
||||
{
|
||||
OutputDebugStringA("[DEBUG] Creating playback timer\n");
|
||||
m_playbackTimer = winrt::Microsoft::UI::Xaml::DispatcherTimer();
|
||||
|
||||
// Store weak reference to avoid circular dependency
|
||||
auto weakThis = get_weak();
|
||||
m_playbackTimer.Tick([weakThis](auto&&, auto&&) {
|
||||
if (auto strongThis = weakThis.get())
|
||||
{
|
||||
OutputDebugStringA("[DEBUG] Timer tick - checking conditions\n");
|
||||
OutputDebugStringA(("[DEBUG] m_isPlaying: " + std::string(strongThis->m_isPlaying ? "true" : "false") +
|
||||
", m_isLoaded: " + std::string(strongThis->m_isLoaded ? "true" : "false") + "\n").c_str());
|
||||
|
||||
if (strongThis->m_isPlaying && strongThis->m_isLoaded)
|
||||
{
|
||||
try {
|
||||
strongThis->ProcessSingleFrame();
|
||||
}
|
||||
catch (...) {
|
||||
OutputDebugStringA("[DEBUG] Exception in timer ProcessSingleFrame\n");
|
||||
strongThis->m_isPlaying = false;
|
||||
strongThis->m_playbackTimer.Stop();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
OutputDebugStringA("[DEBUG] Timer tick - conditions not met, skipping frame processing\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
OutputDebugStringA("[DEBUG] Timer tick - object destroyed, stopping timer\n");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Set timer interval based on frame rate (default 30fps = 33.33ms)
|
||||
auto interval = std::chrono::milliseconds(static_cast<int>(1000.0 / m_frameRate));
|
||||
m_playbackTimer.Interval(interval);
|
||||
|
||||
OutputDebugStringA(("[DEBUG] Timer interval set to: " + std::to_string(interval.count()) + "ms\n").c_str());
|
||||
OutputDebugStringA(("[DEBUG] Timer object valid: " + std::string(m_playbackTimer ? "true" : "false") + "\n").c_str());
|
||||
|
||||
m_playbackTimer.Start();
|
||||
OutputDebugStringA("[DEBUG] Timer.Start() called\n");
|
||||
|
||||
// Immediate test: try to process one frame manually to verify the pipeline works
|
||||
OutputDebugStringA("[DEBUG] Testing immediate frame processing...\n");
|
||||
ProcessSingleFrame();
|
||||
|
||||
OutputDebugStringA(("[DEBUG] Started playback timer at " + std::to_string(m_frameRate) + " fps\n").c_str());
|
||||
|
||||
// Backup approach: Create a manual timer that restarts itself
|
||||
OutputDebugStringA("[DEBUG] Setting up backup manual timer approach\n");
|
||||
auto manualTimer = winrt::Microsoft::UI::Xaml::DispatcherTimer();
|
||||
manualTimer.Interval(interval);
|
||||
|
||||
// Manual timer approach with restart
|
||||
manualTimer.Tick([this, manualTimer](auto&&, auto&&) mutable {
|
||||
OutputDebugStringA("[DEBUG] Manual timer tick\n");
|
||||
if (m_isPlaying && m_isLoaded) {
|
||||
ProcessSingleFrame();
|
||||
// Schedule next frame
|
||||
manualTimer.Start();
|
||||
}
|
||||
});
|
||||
|
||||
// Start both timers
|
||||
manualTimer.Start();
|
||||
OutputDebugStringA("[DEBUG] Manual timer also started\n");
|
||||
}
|
||||
|
||||
// MAJOR_REFACTORING_GUIDE.md: Simplified method stubs for compilation
|
||||
void VideoPlayerControl::Pause() { m_isPlaying = false; }
|
||||
void VideoPlayerControl::Stop() { m_isPlaying = false; }
|
||||
void VideoPlayerControl::ProcessSingleFrame() { /* Simplified */ }
|
||||
void VideoPlayerControl::ProcessSingleFrameLegacy() { /* Basic */ }
|
||||
void VideoPlayerControl::RenderFrameToScreen(const VideoFrame& frame) { /* Software */ }
|
||||
void VideoPlayerControl::RenderFrameSoftware(const VideoFrame& frame) { /* YUV to RGB */ }
|
||||
void VideoPlayerControl::UpdateStatus(winrt::hstring const& message) { m_status = message; }
|
||||
void VideoPlayerControl::StartPlaybackTimer() { /* Timer */ }
|
||||
void VideoPlayerControl::UpdateProgress() { /* Progress */ }
|
||||
void VideoPlayerControl::StopPlaybackTimer() { /* Stop Timer */ }
|
||||
void VideoPlayerControl::StopControlsHideTimer() { /* Stop Controls Timer */ }
|
||||
void VideoPlayerControl::ShowControlsInternal() { /* Show Controls */ }
|
||||
void VideoPlayerControl::InitializeVideoRenderer() { /* Init Renderer */ }
|
||||
void VideoPlayerControl::ResetVideoState() { /* Reset State */ }
|
||||
bool VideoPlayerControl::CreateDecoder() { return true; }
|
||||
bool VideoPlayerControl::InitializeDecoder() { return true; }
|
||||
void VideoPlayerControl::Pause()
|
||||
{
|
||||
OutputDebugStringA("[DEBUG] Pause() called\n");
|
||||
m_isPlaying = false;
|
||||
if (m_playbackTimer)
|
||||
{
|
||||
m_playbackTimer.Stop();
|
||||
}
|
||||
UpdateStatus(L"Paused");
|
||||
}
|
||||
|
||||
void VideoPlayerControl::Stop()
|
||||
{
|
||||
OutputDebugStringA("[DEBUG] Stop() called\n");
|
||||
m_isPlaying = false;
|
||||
if (m_playbackTimer)
|
||||
{
|
||||
m_playbackTimer.Stop();
|
||||
}
|
||||
|
||||
// Reset position to beginning
|
||||
m_currentFrame = 0;
|
||||
m_currentTime = 0.0;
|
||||
|
||||
// Reset file reader to beginning for next play
|
||||
if (m_fileReader && m_fileReader->IsFileOpen())
|
||||
{
|
||||
OutputDebugStringA("[DEBUG] Resetting file reader to beginning\n");
|
||||
m_fileReader->Reset();
|
||||
}
|
||||
|
||||
// Reset decoder state
|
||||
if (m_decoder)
|
||||
{
|
||||
OutputDebugStringA("[DEBUG] Resetting decoder state\n");
|
||||
m_decoder->Reset();
|
||||
}
|
||||
|
||||
UpdateStatus(L"Stopped");
|
||||
}
|
||||
|
||||
void VideoPlayerControl::ProcessSingleFrame()
|
||||
{
|
||||
OutputDebugStringA("[DEBUG] ProcessSingleFrame() called\n");
|
||||
if (!m_isLoaded || !m_fileReader || !m_decoder)
|
||||
{
|
||||
OutputDebugStringA("[DEBUG] ProcessSingleFrame() - not ready\n");
|
||||
return;
|
||||
}
|
||||
|
||||
VideoPacket packet;
|
||||
OutputDebugStringA("[DEBUG] Reading next packet...\n");
|
||||
if (!m_fileReader->ReadNextPacket(packet))
|
||||
{
|
||||
OutputDebugStringA("[DEBUG] No more packets - playback completed\n");
|
||||
m_isPlaying = false;
|
||||
if (m_playbackTimer) {
|
||||
m_playbackTimer.Stop();
|
||||
}
|
||||
UpdateStatus(L"Playback completed");
|
||||
return;
|
||||
}
|
||||
OutputDebugStringA(("[DEBUG] Packet read - size: " + std::to_string(packet.size) + "\n").c_str());
|
||||
|
||||
VideoFrame frame;
|
||||
OutputDebugStringA("[DEBUG] Decoding frame...\n");
|
||||
if (!m_decoder->DecodeFrame(packet, frame))
|
||||
{
|
||||
OutputDebugStringA("[DEBUG] Frame decode failed - skipping\n");
|
||||
return;
|
||||
}
|
||||
OutputDebugStringA(("[DEBUG] Frame decoded - size: " + std::to_string(frame.width) + "x" + std::to_string(frame.height) + "\n").c_str());
|
||||
|
||||
RenderFrameToScreen(frame);
|
||||
|
||||
m_currentFrame++;
|
||||
m_currentTime = m_currentFrame / m_frameRate;
|
||||
OutputDebugStringA(("[DEBUG] Frame " + std::to_string(m_currentFrame) + " processed\n").c_str());
|
||||
}
|
||||
|
||||
void VideoPlayerControl::ProcessSingleFrameLegacy()
|
||||
{
|
||||
// Legacy method - calls ProcessSingleFrame for compatibility
|
||||
ProcessSingleFrame();
|
||||
}
|
||||
|
||||
void VideoPlayerControl::RenderFrameToScreen(const VideoFrame& frame)
|
||||
{
|
||||
OutputDebugStringA("[DEBUG] RenderFrameToScreen() called\n");
|
||||
RenderFrameSoftware(frame);
|
||||
}
|
||||
|
||||
void VideoPlayerControl::RenderFrameSoftware(const VideoFrame& frame)
|
||||
{
|
||||
OutputDebugStringA(("[DEBUG] RenderFrameSoftware() called - " + std::to_string(frame.width) + "x" + std::to_string(frame.height) + "\n").c_str());
|
||||
OutputDebugStringA(("[DEBUG] y_plane: " + std::string(frame.y_plane ? "valid" : "null") + "\n").c_str());
|
||||
|
||||
if (!frame.y_plane || frame.width == 0 || frame.height == 0)
|
||||
{
|
||||
OutputDebugStringA("[DEBUG] Invalid frame data - returning\n");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Create or reuse WriteableBitmap for the frame
|
||||
if (!m_renderBitmap ||
|
||||
static_cast<uint32_t>(m_renderBitmap.PixelWidth()) != frame.width ||
|
||||
static_cast<uint32_t>(m_renderBitmap.PixelHeight()) != frame.height) {
|
||||
|
||||
OutputDebugStringA("[DEBUG] Creating new WriteableBitmap\n");
|
||||
m_renderBitmap = winrt::Microsoft::UI::Xaml::Media::Imaging::WriteableBitmap(
|
||||
frame.width, frame.height);
|
||||
VideoImage().Source(m_renderBitmap);
|
||||
UpdateVideoImageAspectFit(frame.width, frame.height);
|
||||
|
||||
OutputDebugStringA(("Created WriteableBitmap: " + std::to_string(frame.width) + "x" + std::to_string(frame.height) + "\n").c_str());
|
||||
}
|
||||
|
||||
// Convert YUV to BGRA and render to bitmap
|
||||
auto buffer = m_renderBitmap.PixelBuffer();
|
||||
uint32_t capacity = buffer.Capacity();
|
||||
OutputDebugStringA(("[DEBUG] Buffer capacity: " + std::to_string(capacity) + "\n").c_str());
|
||||
|
||||
if (capacity >= frame.width * frame.height * 4) {
|
||||
OutputDebugStringA("[DEBUG] Converting YUV to BGRA...\n");
|
||||
// Simple approach: create BGRA data and copy to buffer
|
||||
std::vector<uint8_t> bgra_data(frame.width * frame.height * 4);
|
||||
ConvertYUVToBGRA(frame, bgra_data.data(), frame.width, frame.height);
|
||||
|
||||
OutputDebugStringA("[DEBUG] Copying to bitmap buffer...\n");
|
||||
// Copy BGRA data directly to bitmap buffer
|
||||
auto bufferByteAccess = buffer.as<::Windows::Storage::Streams::IBufferByteAccess>();
|
||||
uint8_t* bufferData = nullptr;
|
||||
winrt::check_hresult(bufferByteAccess->Buffer(&bufferData));
|
||||
std::memcpy(bufferData, bgra_data.data(), frame.width * frame.height * 4);
|
||||
buffer.Length(frame.width * frame.height * 4);
|
||||
|
||||
// Trigger UI update
|
||||
m_renderBitmap.Invalidate();
|
||||
|
||||
OutputDebugStringA(("[DEBUG] Frame rendered successfully: " + std::to_string(frame.width) + "x" + std::to_string(frame.height) + "\n").c_str());
|
||||
} else {
|
||||
OutputDebugStringA("[DEBUG] Buffer capacity too small\n");
|
||||
}
|
||||
|
||||
} catch (...) {
|
||||
OutputDebugStringA("[DEBUG] Software rendering failed\n");
|
||||
}
|
||||
}
|
||||
|
||||
void VideoPlayerControl::ConvertYUVToBGRA(const VideoFrame& yuv_frame, uint8_t* bgra_buffer, uint32_t width, uint32_t height)
|
||||
{
|
||||
OutputDebugStringA("[DEBUG] ConvertYUVToBGRA() called\n");
|
||||
// YUV420P to BGRA conversion using BT.709 color space
|
||||
const uint8_t* y_plane = yuv_frame.y_plane.get();
|
||||
const uint8_t* u_plane = yuv_frame.u_plane.get();
|
||||
const uint8_t* v_plane = yuv_frame.v_plane.get();
|
||||
|
||||
if (!y_plane || !u_plane || !v_plane) {
|
||||
OutputDebugStringA("[DEBUG] ConvertYUVToBGRA: Invalid plane data\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t y_stride = yuv_frame.y_stride;
|
||||
const uint32_t u_stride = yuv_frame.u_stride;
|
||||
const uint32_t v_stride = yuv_frame.v_stride;
|
||||
|
||||
OutputDebugStringA(("[DEBUG] YUV strides: Y=" + std::to_string(y_stride) + " U=" + std::to_string(u_stride) + " V=" + std::to_string(v_stride) + "\n").c_str());
|
||||
|
||||
for (uint32_t y = 0; y < height; y++) {
|
||||
const uint8_t* y_row = y_plane + y * y_stride;
|
||||
const uint8_t* u_row = u_plane + (y / 2) * u_stride;
|
||||
const uint8_t* v_row = v_plane + (y / 2) * v_stride;
|
||||
|
||||
uint8_t* bgra_row = bgra_buffer + y * width * 4;
|
||||
|
||||
for (uint32_t x = 0; x < width; x++) {
|
||||
const uint8_t Y = y_row[x];
|
||||
const uint8_t U = u_row[x / 2];
|
||||
const uint8_t V = v_row[x / 2];
|
||||
|
||||
// BT.709 YUV to RGB conversion
|
||||
const int C = Y - 16;
|
||||
const int D = U - 128;
|
||||
const int E = V - 128;
|
||||
|
||||
int R = (298 * C + 409 * E + 128) >> 8;
|
||||
int G = (298 * C - 100 * D - 208 * E + 128) >> 8;
|
||||
int B = (298 * C + 516 * D + 128) >> 8;
|
||||
|
||||
// Clamp to [0, 255]
|
||||
R = std::max(0, std::min(255, R));
|
||||
G = std::max(0, std::min(255, G));
|
||||
B = std::max(0, std::min(255, B));
|
||||
|
||||
// Store as BGRA
|
||||
bgra_row[x * 4 + 0] = static_cast<uint8_t>(B); // Blue
|
||||
bgra_row[x * 4 + 1] = static_cast<uint8_t>(G); // Green
|
||||
bgra_row[x * 4 + 2] = static_cast<uint8_t>(R); // Red
|
||||
bgra_row[x * 4 + 3] = 255; // Alpha
|
||||
}
|
||||
}
|
||||
OutputDebugStringA("[DEBUG] YUV to BGRA conversion completed\n");
|
||||
}
|
||||
|
||||
void VideoPlayerControl::UpdateStatus(winrt::hstring const& message)
|
||||
{
|
||||
m_status = message;
|
||||
OutputDebugStringA(("[DEBUG] Status: " + winrt::to_string(message) + "\n").c_str());
|
||||
}
|
||||
|
||||
void VideoPlayerControl::ShowControlsInternal()
|
||||
{
|
||||
// Show video controls - simplified implementation
|
||||
}
|
||||
|
||||
void VideoPlayerControl::InitializeVideoRenderer()
|
||||
{
|
||||
OutputDebugStringA("[DEBUG] InitializeVideoRenderer() called\n");
|
||||
// Initialize CPU rendering mode (Phase 1)
|
||||
if (!m_useHardwareRendering)
|
||||
{
|
||||
// Ensure software rendering UI is visible
|
||||
VideoSwapChainPanel().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed);
|
||||
VideoImage().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible);
|
||||
OutputDebugStringA("[DEBUG] Initialized CPU rendering mode\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hardware rendering setup (Phase 2 - future)
|
||||
VideoSwapChainPanel().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible);
|
||||
VideoImage().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed);
|
||||
OutputDebugStringA("[DEBUG] Hardware rendering not implemented yet\n");
|
||||
}
|
||||
}
|
||||
|
||||
void VideoPlayerControl::ResetVideoState()
|
||||
{
|
||||
OutputDebugStringA("[DEBUG] ResetVideoState() called\n");
|
||||
m_currentFrame = 0;
|
||||
m_currentTime = 0.0;
|
||||
m_isLoaded = false;
|
||||
m_isPlaying = false;
|
||||
|
||||
// Stop and reset playback timer
|
||||
if (m_playbackTimer)
|
||||
{
|
||||
m_playbackTimer.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
bool VideoPlayerControl::CreateDecoder()
|
||||
{
|
||||
OutputDebugStringA("[DEBUG] CreateDecoder() called\n");
|
||||
m_decoder = VideoDecoderFactory::CreateDecoder(VideoCodecType::AV1, m_decoderType);
|
||||
bool success = m_decoder != nullptr;
|
||||
OutputDebugStringA(("[DEBUG] Decoder created: " + std::string(success ? "success" : "failed") + "\n").c_str());
|
||||
return success;
|
||||
}
|
||||
|
||||
bool VideoPlayerControl::InitializeDecoder()
|
||||
{
|
||||
OutputDebugStringA("[DEBUG] InitializeDecoder() called\n");
|
||||
if (!m_decoder) {
|
||||
OutputDebugStringA("[DEBUG] No decoder available\n");
|
||||
return false;
|
||||
}
|
||||
auto metadata = m_fileReader->GetVideoMetadata();
|
||||
bool success = m_decoder->Initialize(metadata);
|
||||
OutputDebugStringA(("[DEBUG] Decoder initialized: " + std::string(success ? "success" : "failed") + "\n").c_str());
|
||||
return success;
|
||||
}
|
||||
|
||||
void VideoPlayerControl::UpdateVideoImageAspectFit(int videoWidth, int videoHeight)
|
||||
{
|
||||
OutputDebugStringA(("[DEBUG] UpdateVideoImageAspectFit: " + std::to_string(videoWidth) + "x" + std::to_string(videoHeight) + "\n").c_str());
|
||||
// AspectFit calculation for proper video scaling
|
||||
auto container = VideoDisplayArea();
|
||||
if (!container) return;
|
||||
|
||||
double containerWidth = container.ActualWidth();
|
||||
double containerHeight = container.ActualHeight();
|
||||
if (containerWidth <= 0 || containerHeight <= 0) return;
|
||||
|
||||
double videoAspectRatio = static_cast<double>(videoWidth) / videoHeight;
|
||||
double containerAspectRatio = containerWidth / containerHeight;
|
||||
|
||||
double displayWidth, displayHeight;
|
||||
if (videoAspectRatio > containerAspectRatio) {
|
||||
// Video is wider - fit to container width
|
||||
displayWidth = containerWidth;
|
||||
displayHeight = containerWidth / videoAspectRatio;
|
||||
} else {
|
||||
// Video is taller - fit to container height
|
||||
displayHeight = containerHeight;
|
||||
displayWidth = containerHeight * videoAspectRatio;
|
||||
}
|
||||
|
||||
VideoImage().Width(displayWidth);
|
||||
VideoImage().Height(displayHeight);
|
||||
OutputDebugStringA(("[DEBUG] Video size set to: " + std::to_string(displayWidth) + "x" + std::to_string(displayHeight) + "\n").c_str());
|
||||
}
|
||||
|
||||
void VideoPlayerControl::Seek(double timeSeconds)
|
||||
{
|
||||
OutputDebugStringA(("[DEBUG] Seek to: " + std::to_string(timeSeconds) + "s\n").c_str());
|
||||
if (!m_isLoaded || !m_fileReader) return;
|
||||
|
||||
// Stop playback during seek
|
||||
bool wasPlaying = m_isPlaying;
|
||||
if (m_isPlaying) {
|
||||
Pause();
|
||||
}
|
||||
|
||||
// Seek to the specified time
|
||||
if (m_fileReader->SeekToTime(timeSeconds)) {
|
||||
m_currentTime = timeSeconds;
|
||||
m_currentFrame = static_cast<uint64_t>(timeSeconds * m_frameRate);
|
||||
|
||||
// Process one frame to update display
|
||||
ProcessSingleFrame();
|
||||
|
||||
// Resume playback if it was playing before seek
|
||||
if (wasPlaying) {
|
||||
Play();
|
||||
}
|
||||
|
||||
UpdateStatus(L"Seeked");
|
||||
} else {
|
||||
OutputDebugStringA("[DEBUG] Seek operation failed\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Additional required methods for linker
|
||||
void VideoPlayerControl::UpdateVideoImageAspectFit(int videoWidth, int videoHeight) { /* AspectFit */ }
|
||||
void VideoPlayerControl::Seek(double timeSeconds) { /* Seek */ }
|
||||
bool VideoPlayerControl::IsVideoPlaying() { return m_isPlaying; }
|
||||
bool VideoPlayerControl::IsVideoLoaded() { return m_isLoaded; }
|
||||
double VideoPlayerControl::CurrentTime() { return m_currentTime; }
|
||||
double VideoPlayerControl::Duration() { return m_duration; }
|
||||
winrt::hstring VideoPlayerControl::Status() { return m_status; }
|
||||
void VideoPlayerControl::StartControlsHideTimer() { /* Start Hide Timer */ }
|
||||
}
|
||||
|
||||
void VideoPlayerControl::StartControlsHideTimer() { /* Simplified implementation */ }
|
||||
void VideoPlayerControl::StopControlsHideTimer() { /* Simplified implementation */ }
|
||||
}
|
||||
@@ -4,18 +4,10 @@
|
||||
#include "src/FileIO/WebMFileReader.h"
|
||||
#include "src/Decoder/VideoDecoderFactory.h"
|
||||
#include "src/Common/VideoTypes.h"
|
||||
// Simplified architecture - removed FramePool
|
||||
#include "src/Rendering/D3D12VideoRenderer.h"
|
||||
#include "src/Rendering/SimpleGPURenderer.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <atomic>
|
||||
#include <future>
|
||||
|
||||
// Forward declarations
|
||||
namespace Vav2Player {
|
||||
// Only essential components remain
|
||||
}
|
||||
|
||||
namespace winrt::Vav2Player::implementation
|
||||
{
|
||||
@@ -27,14 +19,10 @@ namespace winrt::Vav2Player::implementation
|
||||
// Events
|
||||
void UserControl_Loaded(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e);
|
||||
void UserControl_Unloaded(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e);
|
||||
// Control event handlers (commented out as controls are disabled)
|
||||
// void PlayPauseButton_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e);
|
||||
// void StopButton_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e);
|
||||
// void ProgressSlider_ValueChanged(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Controls::Primitives::RangeBaseValueChangedEventArgs const& e);
|
||||
void HoverDetector_PointerEntered(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& e);
|
||||
void HoverDetector_PointerExited(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& e);
|
||||
|
||||
// Public Properties (Dependency Properties will be added later)
|
||||
// Public Properties
|
||||
winrt::hstring VideoSource();
|
||||
void VideoSource(winrt::hstring const& value);
|
||||
|
||||
@@ -47,11 +35,9 @@ namespace winrt::Vav2Player::implementation
|
||||
Vav2Player::VideoDecoderType DecoderType();
|
||||
void DecoderType(Vav2Player::VideoDecoderType value);
|
||||
|
||||
// Hardware rendering control
|
||||
bool UseHardwareRendering();
|
||||
void UseHardwareRendering(bool value);
|
||||
|
||||
// Internal decoder type management
|
||||
VideoDecoderFactory::DecoderType GetInternalDecoderType();
|
||||
void SetInternalDecoderType(VideoDecoderFactory::DecoderType value);
|
||||
|
||||
@@ -74,17 +60,13 @@ namespace winrt::Vav2Player::implementation
|
||||
std::unique_ptr<WebMFileReader> m_fileReader;
|
||||
std::unique_ptr<IVideoDecoder> m_decoder;
|
||||
|
||||
// Simplified processing - no complex pipelines
|
||||
|
||||
// Video rendering components
|
||||
winrt::Microsoft::UI::Xaml::Media::Imaging::WriteableBitmap m_renderBitmap{ nullptr };
|
||||
std::vector<uint8_t> m_bgraBuffer;
|
||||
|
||||
// D3D12 Hardware rendering (Phase 1)
|
||||
std::unique_ptr<D3D12VideoRenderer> m_d3d12Renderer;
|
||||
std::unique_ptr<SimpleGPURenderer> m_gpuRenderer;
|
||||
bool m_useHardwareRendering = false;
|
||||
bool m_hardwareEnvironmentCriticalError = false; // Critical system-level hardware issue
|
||||
// Playback timer for continuous frame processing
|
||||
winrt::Microsoft::UI::Xaml::DispatcherTimer m_playbackTimer;
|
||||
|
||||
// Video dimensions
|
||||
uint32_t m_videoWidth = 0;
|
||||
@@ -96,10 +78,6 @@ namespace winrt::Vav2Player::implementation
|
||||
bool m_autoPlay = false;
|
||||
VideoDecoderFactory::DecoderType m_decoderType = VideoDecoderFactory::DecoderType::AUTO;
|
||||
|
||||
// Decoder reuse optimization to prevent excessive dav1d worker thread creation
|
||||
VideoCodecType m_lastCodecType = VideoCodecType::AV1;
|
||||
VideoDecoderFactory::DecoderType m_lastDecoderType = VideoDecoderFactory::DecoderType::AUTO;
|
||||
|
||||
// Playback state
|
||||
std::atomic<bool> m_isPlaying{ false };
|
||||
std::atomic<bool> m_isLoaded{ false };
|
||||
@@ -111,41 +89,22 @@ namespace winrt::Vav2Player::implementation
|
||||
double m_duration = 0.0;
|
||||
winrt::hstring m_status = L"Ready";
|
||||
|
||||
// Timer for playback
|
||||
winrt::Microsoft::UI::Dispatching::DispatcherQueueTimer m_playbackTimer{ nullptr };
|
||||
winrt::Microsoft::UI::Dispatching::DispatcherQueueTimer m_controlsHideTimer{ nullptr };
|
||||
|
||||
// Helper methods
|
||||
void InitializeVideoRenderer();
|
||||
void InitializeHardwareRenderer(int width, int height);
|
||||
void InitializeSoftwareRenderer(int width, int height);
|
||||
void ProcessSingleFrame();
|
||||
void RenderFrameToScreen(const VideoFrame& frame);
|
||||
void RenderFrameHardware(const VideoFrame& frame);
|
||||
void RenderFrameSoftware(const VideoFrame& frame);
|
||||
void ProcessSingleFrameSimple(); // Single linear processing path
|
||||
void ProcessSingleFrameZeroCopy(); // Zero-copy processing path
|
||||
void ProcessSingleFrameRingBuffer(); // Ring buffer processing path
|
||||
void ProcessSingleFrameLegacy(); // Legacy CPU processing path
|
||||
void ProcessSingleFrameLegacy();
|
||||
void ConvertYUVToBGRA(const VideoFrame& yuv_frame, uint8_t* bgra_buffer, uint32_t width, uint32_t height);
|
||||
|
||||
// Simplified processing helpers
|
||||
void UpdateVideoImageAspectFit(int videoWidth, int videoHeight);
|
||||
void UpdateStatus(winrt::hstring const& message);
|
||||
void UpdatePlaybackUI();
|
||||
void UpdateProgress();
|
||||
void StartPlaybackTimer();
|
||||
void StopPlaybackTimer();
|
||||
void ShowControlsInternal();
|
||||
void HideControls();
|
||||
void StartControlsHideTimer();
|
||||
void StopControlsHideTimer();
|
||||
std::string FormatTime(double seconds);
|
||||
|
||||
// Internal state management
|
||||
void ResetVideoState();
|
||||
bool CreateDecoder();
|
||||
bool InitializeDecoder();
|
||||
void ShowPurpleOutlinePlaceholder();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,121 +1,87 @@
|
||||
#include "pch.h"
|
||||
#include "../src/Rendering/SimpleGPURenderer_Headless.h"
|
||||
#include "../src/Decoder/VideoDecoderFactory.h"
|
||||
#include "../src/FileIO/WebMFileReader.h"
|
||||
|
||||
// Forward declarations
|
||||
int TestSimpleGPUIntegration();
|
||||
int TestAV1GPUIntegration(const std::string& videoFile);
|
||||
|
||||
// Phase 3: Test SimpleGPURenderer basic functionality
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
SetConsoleCP(CP_UTF8);
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
|
||||
std::cout << "=== Vav2Player Phase 3 Headless Test ===" << std::endl;
|
||||
std::cout << "Testing SimpleGPURenderer and basic dependencies" << std::endl;
|
||||
std::cout << "=== MAJOR_REFACTORING_GUIDE Phase 3: Basic Video Test ===" << std::endl;
|
||||
|
||||
if (argc < 2) {
|
||||
std::cout << "Usage: Vav2PlayerHeadless.exe <video_file.webm>" << std::endl;
|
||||
std::cout << "Usage: " << argv[0] << " <video_file.webm>" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string videoFile = argv[1];
|
||||
std::cout << "Would test with video file: " << videoFile << std::endl;
|
||||
|
||||
// Test 1: Basic library dependencies
|
||||
std::cout << "[TEST] Testing basic library dependencies..." << std::endl;
|
||||
|
||||
Dav1dSettings settings;
|
||||
dav1d_default_settings(&settings);
|
||||
std::cout << "[PASS] dav1d library loaded successfully" << std::endl;
|
||||
std::string filePath = argv[1];
|
||||
std::cout << "Testing video file: " << filePath << std::endl;
|
||||
|
||||
try {
|
||||
Dav1dContext* ctx = nullptr;
|
||||
int result = dav1d_open(&ctx, &settings);
|
||||
|
||||
if (result == 0 && ctx != nullptr) {
|
||||
std::cout << "[PASS] dav1d_open successful" << std::endl;
|
||||
dav1d_close(&ctx);
|
||||
} else {
|
||||
std::cout << "[FAIL] dav1d_open failed with error: " << result << std::endl;
|
||||
}
|
||||
} catch (...) {
|
||||
std::cout << "[FAIL] dav1d_open crashed" << std::endl;
|
||||
}
|
||||
|
||||
// Test 2: SimpleGPURenderer creation and destruction
|
||||
std::cout << "[TEST] Testing SimpleGPURenderer basic functionality..." << std::endl;
|
||||
|
||||
try {
|
||||
std::unique_ptr<Vav2Player::SimpleGPURenderer_Headless> renderer =
|
||||
std::make_unique<Vav2Player::SimpleGPURenderer_Headless>();
|
||||
|
||||
if (renderer) {
|
||||
std::cout << "[PASS] SimpleGPURenderer created successfully" << std::endl;
|
||||
|
||||
// Test basic properties
|
||||
std::cout << "[INFO] Initial state - Width: " << renderer->GetWidth()
|
||||
<< ", Height: " << renderer->GetHeight()
|
||||
<< ", Initialized: " << (renderer->IsInitialized() ? "Yes" : "No") << std::endl;
|
||||
|
||||
// Test GPU pipeline initialization
|
||||
std::cout << "[TEST] Testing GPU pipeline initialization..." << std::endl;
|
||||
HRESULT hr = renderer->Initialize(1920, 1080);
|
||||
if (SUCCEEDED(hr)) {
|
||||
std::cout << "[PASS] GPU pipeline initialized successfully" << std::endl;
|
||||
|
||||
// Test GPU pipeline functionality
|
||||
hr = renderer->TestGPUPipeline();
|
||||
if (SUCCEEDED(hr)) {
|
||||
std::cout << "[PASS] GPU pipeline test completed successfully" << std::endl;
|
||||
} else {
|
||||
std::cout << "[FAIL] GPU pipeline test failed: 0x" << std::hex << hr << std::endl;
|
||||
}
|
||||
} else {
|
||||
std::cout << "[FAIL] GPU pipeline initialization failed: 0x" << std::hex << hr << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "[INFO] Testing SimpleGPURenderer destruction..." << std::endl;
|
||||
renderer.reset(); // Test destruction
|
||||
std::cout << "[PASS] SimpleGPURenderer destroyed successfully" << std::endl;
|
||||
} else {
|
||||
std::cout << "[FAIL] SimpleGPURenderer creation failed" << std::endl;
|
||||
// Test WebMFileReader
|
||||
auto fileReader = std::make_unique<WebMFileReader>();
|
||||
if (!fileReader->OpenFile(filePath)) {
|
||||
std::cout << "Failed to open video file" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto tracks = fileReader->GetVideoTracks();
|
||||
if (tracks.empty()) {
|
||||
std::cout << "No video tracks found" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Found " << tracks.size() << " video track(s)" << std::endl;
|
||||
|
||||
// Select first track
|
||||
if (!fileReader->SelectVideoTrack(tracks[0].track_number)) {
|
||||
std::cout << "Failed to select video track" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto metadata = fileReader->GetVideoMetadata();
|
||||
std::cout << "Video: " << metadata.width << "x" << metadata.height
|
||||
<< " @ " << metadata.frame_rate << " fps" << std::endl;
|
||||
|
||||
// Test decoder creation
|
||||
auto decoder = VideoDecoderFactory::CreateDecoder(VideoCodecType::AV1, VideoDecoderFactory::DecoderType::AUTO);
|
||||
if (!decoder) {
|
||||
std::cout << "Failed to create AV1 decoder" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!decoder->Initialize(metadata)) {
|
||||
std::cout << "Failed to initialize decoder" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Decoder initialized successfully" << std::endl;
|
||||
|
||||
// Test a few frames
|
||||
for (int i = 0; i < 5; i++) {
|
||||
VideoPacket packet;
|
||||
if (!fileReader->ReadNextPacket(packet)) {
|
||||
std::cout << "End of file or read error at frame " << i << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
VideoFrame frame;
|
||||
if (decoder->DecodeFrame(packet, frame)) {
|
||||
std::cout << "Frame " << i << ": " << frame.width << "x" << frame.height << std::endl;
|
||||
} else {
|
||||
std::cout << "Frame " << i << ": decode failed" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "=== MAJOR_REFACTORING_GUIDE Phase 3: Test completed successfully ===" << std::endl;
|
||||
std::cout << "Basic video decoding pipeline verified!" << std::endl;
|
||||
return 0;
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
std::cout << "[FAIL] Exception during SimpleGPURenderer test: " << e.what() << std::endl;
|
||||
std::cout << "Exception: " << e.what() << std::endl;
|
||||
return 1;
|
||||
} catch (...) {
|
||||
std::cout << "[FAIL] Unknown exception during SimpleGPURenderer test" << std::endl;
|
||||
std::cout << "Unknown exception occurred" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "[SUCCESS] Phase 3 headless test completed successfully!" << std::endl;
|
||||
std::cout << "SimpleGPURenderer infrastructure verified" << std::endl;
|
||||
|
||||
// Run simple GPU integration test with mock data
|
||||
std::cout << "\n=== Running Simple GPU Integration Test ===" << std::endl;
|
||||
int result = TestSimpleGPUIntegration();
|
||||
if (result != 0) {
|
||||
std::cout << "\n[FAIL] Simple GPU integration test failed" << std::endl;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Run AV1 + GPU integration test with real video file
|
||||
if (argc >= 2) {
|
||||
std::cout << "\n=== Running AV1 + GPU Integration Test ===" << std::endl;
|
||||
result = TestAV1GPUIntegration(videoFile);
|
||||
if (result != 0) {
|
||||
std::cout << "\n[FAIL] AV1 + GPU integration test failed" << std::endl;
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
std::cout << "\n[INFO] Skipping AV1 + GPU integration test (no video file provided)" << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "\nGPU pipeline foundation is ready for full implementation" << std::endl;
|
||||
std::cout << "Press Enter to exit...";
|
||||
std::cin.get();
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace Vav2Player {
|
||||
|
||||
// Simple RAII wrapper for COM objects
|
||||
template<typename T>
|
||||
class COMWrapper {
|
||||
public:
|
||||
COMWrapper() : ptr_(nullptr) {}
|
||||
|
||||
explicit COMWrapper(T* ptr) : ptr_(ptr) {
|
||||
if (ptr_) ptr_->AddRef();
|
||||
}
|
||||
|
||||
COMWrapper(const COMWrapper& other) : ptr_(other.ptr_) {
|
||||
if (ptr_) ptr_->AddRef();
|
||||
}
|
||||
|
||||
COMWrapper(COMWrapper&& other) noexcept : ptr_(other.ptr_) {
|
||||
other.ptr_ = nullptr;
|
||||
}
|
||||
|
||||
~COMWrapper() {
|
||||
if (ptr_) ptr_->Release();
|
||||
}
|
||||
|
||||
COMWrapper& operator=(const COMWrapper& other) {
|
||||
if (this != &other) {
|
||||
if (ptr_) ptr_->Release();
|
||||
ptr_ = other.ptr_;
|
||||
if (ptr_) ptr_->AddRef();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
COMWrapper& operator=(COMWrapper&& other) noexcept {
|
||||
if (this != &other) {
|
||||
if (ptr_) ptr_->Release();
|
||||
ptr_ = other.ptr_;
|
||||
other.ptr_ = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// ComPtr-like interface
|
||||
T* Get() const { return ptr_; }
|
||||
T** GetAddressOf() { Reset(); return &ptr_; }
|
||||
T* operator->() const { return ptr_; }
|
||||
T& operator*() const { return *ptr_; }
|
||||
|
||||
void Reset() {
|
||||
if (ptr_) {
|
||||
ptr_->Release();
|
||||
ptr_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Attach(T* ptr) {
|
||||
if (ptr_) ptr_->Release();
|
||||
ptr_ = ptr;
|
||||
}
|
||||
|
||||
T* Detach() {
|
||||
T* temp = ptr_;
|
||||
ptr_ = nullptr;
|
||||
return temp;
|
||||
}
|
||||
|
||||
explicit operator bool() const { return ptr_ != nullptr; }
|
||||
|
||||
// QueryInterface helper
|
||||
template<typename U>
|
||||
HRESULT As(COMWrapper<U>& result) const {
|
||||
return ptr_ ? ptr_->QueryInterface(__uuidof(U), reinterpret_cast<void**>(result.GetAddressOf())) : E_POINTER;
|
||||
}
|
||||
|
||||
private:
|
||||
T* ptr_;
|
||||
};
|
||||
|
||||
// Type alias
|
||||
template<typename T>
|
||||
using ComPtr = COMWrapper<T>;
|
||||
|
||||
} // namespace Vav2Player
|
||||
@@ -1,54 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
namespace Vav2Player {
|
||||
|
||||
// COM object deleter for std::shared_ptr
|
||||
struct COMDeleter {
|
||||
template<typename T>
|
||||
void operator()(T* ptr) const {
|
||||
if (ptr) {
|
||||
ptr->Release();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Type alias for COM objects with std::shared_ptr
|
||||
template<typename T>
|
||||
using com_ptr = std::shared_ptr<T>;
|
||||
|
||||
// Helper function to create COM shared_ptr
|
||||
template<typename T>
|
||||
com_ptr<T> make_com_ptr(T* ptr) {
|
||||
return com_ptr<T>(ptr, COMDeleter{});
|
||||
}
|
||||
|
||||
// Helper function for QueryInterface
|
||||
template<typename T, typename U>
|
||||
com_ptr<T> com_cast(const com_ptr<U>& ptr) {
|
||||
if (!ptr) return nullptr;
|
||||
|
||||
T* result = nullptr;
|
||||
HRESULT hr = ptr->QueryInterface(__uuidof(T), reinterpret_cast<void**>(&result));
|
||||
if (SUCCEEDED(hr) && result) {
|
||||
return make_com_ptr(result);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Helper for .Get() equivalent
|
||||
template<typename T>
|
||||
T* get_raw_ptr(const com_ptr<T>& ptr) {
|
||||
return ptr.get();
|
||||
}
|
||||
|
||||
// Helper for .GetAddressOf() equivalent
|
||||
template<typename T>
|
||||
T** get_address_of(com_ptr<T>& ptr) {
|
||||
ptr.reset(); // Release current object
|
||||
return reinterpret_cast<T**>(&ptr); // This is unsafe but needed for COM APIs
|
||||
}
|
||||
|
||||
} // namespace Vav2Player
|
||||
@@ -1,253 +0,0 @@
|
||||
#include "pch.h"
|
||||
#include "PermissionUtils.h"
|
||||
#include <windows.h>
|
||||
#include <shlobj.h>
|
||||
#include <shellapi.h>
|
||||
#include <sddl.h>
|
||||
#include <aclapi.h>
|
||||
#include <fstream>
|
||||
#include <random>
|
||||
|
||||
namespace Vav2Player {
|
||||
|
||||
PermissionUtils::PermissionResult PermissionUtils::CheckDirectoryCreatePermission(const std::filesystem::path& directory_path) {
|
||||
try {
|
||||
// 1. If directory already exists, check write permission
|
||||
if (std::filesystem::exists(directory_path)) {
|
||||
if (TestDirectoryWrite(directory_path)) {
|
||||
OutputDebugStringA(("[PermissionUtils] Directory exists and writable: " + directory_path.string() + "\n").c_str());
|
||||
return PermissionResult::Granted;
|
||||
} else {
|
||||
OutputDebugStringA(("[PermissionUtils] Directory exists but not writable: " + directory_path.string() + "\n").c_str());
|
||||
return PermissionResult::Denied;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Check creation permission in parent directory
|
||||
std::filesystem::path parent_path = directory_path.parent_path();
|
||||
if (!std::filesystem::exists(parent_path)) {
|
||||
// Recursively check if parent directory doesn't exist
|
||||
auto parent_result = CheckDirectoryCreatePermission(parent_path);
|
||||
if (parent_result != PermissionResult::Granted) {
|
||||
return parent_result;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Actually test temporary directory creation
|
||||
std::filesystem::path test_dir = parent_path / ("test_permission_" + std::to_string(GetTickCount64()));
|
||||
|
||||
std::error_code ec;
|
||||
bool created = std::filesystem::create_directory(test_dir, ec);
|
||||
|
||||
if (created && !ec) {
|
||||
// Creation successful, cleanup
|
||||
std::filesystem::remove(test_dir, ec);
|
||||
OutputDebugStringA(("[PermissionUtils] Directory creation test successful: " + directory_path.string() + "\n").c_str());
|
||||
return PermissionResult::Granted;
|
||||
} else {
|
||||
OutputDebugStringA(("[PermissionUtils] Directory creation test failed: " + directory_path.string() + ", Error: " + ec.message() + "\n").c_str());
|
||||
return PermissionResult::Denied;
|
||||
}
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
OutputDebugStringA(("[PermissionUtils] Exception in CheckDirectoryCreatePermission: " + std::string(e.what()) + "\n").c_str());
|
||||
return PermissionResult::Error;
|
||||
}
|
||||
}
|
||||
|
||||
PermissionUtils::PermissionResult PermissionUtils::CheckFileWritePermission(const std::filesystem::path& file_path) {
|
||||
try {
|
||||
// 1. Check directory existence
|
||||
std::filesystem::path parent_dir = file_path.parent_path();
|
||||
if (!std::filesystem::exists(parent_dir)) {
|
||||
auto dir_result = CheckDirectoryCreatePermission(parent_dir);
|
||||
if (dir_result != PermissionResult::Granted) {
|
||||
return dir_result;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Test temporary file creation
|
||||
std::filesystem::path test_file = parent_dir / ("test_write_" + std::to_string(GetTickCount64()) + ".tmp");
|
||||
|
||||
std::ofstream test_stream(test_file, std::ios::binary);
|
||||
if (test_stream.is_open()) {
|
||||
test_stream << "permission test";
|
||||
test_stream.close();
|
||||
|
||||
// Cleanup
|
||||
std::error_code ec;
|
||||
std::filesystem::remove(test_file, ec);
|
||||
|
||||
OutputDebugStringA(("[PermissionUtils] File write test successful: " + file_path.string() + "\n").c_str());
|
||||
return PermissionResult::Granted;
|
||||
} else {
|
||||
OutputDebugStringA(("[PermissionUtils] File write test failed: " + file_path.string() + "\n").c_str());
|
||||
return PermissionResult::Denied;
|
||||
}
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
OutputDebugStringA(("[PermissionUtils] Exception in CheckFileWritePermission: " + std::string(e.what()) + "\n").c_str());
|
||||
return PermissionResult::Error;
|
||||
}
|
||||
}
|
||||
|
||||
bool PermissionUtils::IsRunningAsAdmin() {
|
||||
BOOL is_admin = FALSE;
|
||||
PSID admin_group = nullptr;
|
||||
SID_IDENTIFIER_AUTHORITY nt_authority = SECURITY_NT_AUTHORITY;
|
||||
|
||||
if (AllocateAndInitializeSid(&nt_authority, 2,
|
||||
SECURITY_BUILTIN_DOMAIN_RID,
|
||||
DOMAIN_ALIAS_RID_ADMINS,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
&admin_group)) {
|
||||
|
||||
if (!::CheckTokenMembership(nullptr, admin_group, &is_admin)) {
|
||||
is_admin = FALSE;
|
||||
}
|
||||
FreeSid(admin_group);
|
||||
}
|
||||
|
||||
OutputDebugStringA(is_admin ? "[PermissionUtils] Running as administrator\n" : "[PermissionUtils] Not running as administrator\n");
|
||||
return is_admin == TRUE;
|
||||
}
|
||||
|
||||
bool PermissionUtils::RequestAdminRestart() {
|
||||
wchar_t exe_path[MAX_PATH];
|
||||
DWORD path_length = GetModuleFileNameW(nullptr, exe_path, MAX_PATH);
|
||||
|
||||
if (path_length == 0 || path_length == MAX_PATH) {
|
||||
OutputDebugStringA("[PermissionUtils] Failed to get executable path for restart\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Request administrator permission with ShellExecute
|
||||
HINSTANCE result = ShellExecuteW(nullptr, L"runas", exe_path, nullptr, nullptr, SW_SHOWNORMAL);
|
||||
|
||||
bool success = reinterpret_cast<uintptr_t>(result) > 32;
|
||||
if (success) {
|
||||
OutputDebugStringA("[PermissionUtils] Requested admin restart\n");
|
||||
// Terminate current process
|
||||
PostQuitMessage(0);
|
||||
} else {
|
||||
OutputDebugStringA("[PermissionUtils] Failed to request admin restart\n");
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool PermissionUtils::ShowPermissionDialog(const std::wstring& message, const std::wstring& title) {
|
||||
int result = MessageBoxW(nullptr, message.c_str(), title.c_str(),
|
||||
MB_YESNO | MB_ICONWARNING | MB_TOPMOST);
|
||||
|
||||
return result == IDYES;
|
||||
}
|
||||
|
||||
std::filesystem::path PermissionUtils::GetSafeOutputDirectory() {
|
||||
// Try safe directories in priority order
|
||||
std::vector<std::filesystem::path> safe_directories;
|
||||
|
||||
// 1. User documents folder
|
||||
wchar_t* documents_path = nullptr;
|
||||
if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Documents, 0, nullptr, &documents_path))) {
|
||||
safe_directories.push_back(std::filesystem::path(documents_path) / "Vav2Player");
|
||||
CoTaskMemFree(documents_path);
|
||||
}
|
||||
|
||||
// 2. User temporary folder
|
||||
wchar_t temp_path[MAX_PATH];
|
||||
if (GetTempPathW(MAX_PATH, temp_path)) {
|
||||
safe_directories.push_back(std::filesystem::path(temp_path) / "Vav2Player");
|
||||
}
|
||||
|
||||
// 3. Current user profile folder
|
||||
wchar_t* profile_path = nullptr;
|
||||
if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Profile, 0, nullptr, &profile_path))) {
|
||||
safe_directories.push_back(std::filesystem::path(profile_path) / "Vav2Player");
|
||||
CoTaskMemFree(profile_path);
|
||||
}
|
||||
|
||||
// 4. Same directory as executable
|
||||
wchar_t exe_path[MAX_PATH];
|
||||
if (GetModuleFileNameW(nullptr, exe_path, MAX_PATH)) {
|
||||
std::filesystem::path exe_dir = std::filesystem::path(exe_path).parent_path();
|
||||
safe_directories.push_back(exe_dir / "output");
|
||||
}
|
||||
|
||||
// Check permissions for each directory
|
||||
for (const auto& dir : safe_directories) {
|
||||
if (CheckDirectoryCreatePermission(dir) == PermissionResult::Granted) {
|
||||
OutputDebugStringA(("[PermissionUtils] Found safe directory: " + dir.string() + "\n").c_str());
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
|
||||
// Default value when all attempts fail
|
||||
OutputDebugStringA("[PermissionUtils] No safe directory found, using fallback\n");
|
||||
return std::filesystem::path("C:\\temp\\Vav2Player");
|
||||
}
|
||||
|
||||
PermissionUtils::PermissionResult PermissionUtils::CheckAndHandlePermissions(const std::filesystem::path& output_directory) {
|
||||
OutputDebugStringA(("[PermissionUtils] Checking permissions for: " + output_directory.string() + "\n").c_str());
|
||||
|
||||
// 1. Basic permission check
|
||||
auto result = CheckDirectoryCreatePermission(output_directory);
|
||||
|
||||
if (result == PermissionResult::Granted) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// 2. Handle case when permission is denied
|
||||
std::wstring message;
|
||||
|
||||
if (result == PermissionResult::Denied) {
|
||||
std::wstring dir_path = std::wstring(output_directory.string().begin(), output_directory.string().end());
|
||||
message = L"No write permission to output directory:\n" + dir_path +
|
||||
L"\n\nWould you like to restart with administrator privileges?\n(Selecting No will use a safe location)";
|
||||
} else {
|
||||
std::wstring dir_path = std::wstring(output_directory.string().begin(), output_directory.string().end());
|
||||
message = L"An error occurred while checking directory permissions:\n" + dir_path +
|
||||
L"\n\nWould you like to restart with administrator privileges?";
|
||||
}
|
||||
|
||||
// 3. Provide choice to user
|
||||
if (ShowPermissionDialog(message, L"Directory Permission Required")) {
|
||||
// Request restart with administrator privileges
|
||||
if (RequestAdminRestart()) {
|
||||
// Restart request successful (app will terminate)
|
||||
return PermissionResult::Granted;
|
||||
} else {
|
||||
// Restart failed - use safe directory
|
||||
return PermissionResult::Denied;
|
||||
}
|
||||
} else {
|
||||
// User denied administrator privileges - use safe directory
|
||||
return PermissionResult::Denied;
|
||||
}
|
||||
}
|
||||
|
||||
bool PermissionUtils::TestDirectoryWrite(const std::filesystem::path& directory_path) {
|
||||
try {
|
||||
std::filesystem::path test_file = directory_path / ("write_test_" + std::to_string(GetTickCount64()) + ".tmp");
|
||||
|
||||
std::ofstream test_stream(test_file, std::ios::binary);
|
||||
if (!test_stream.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
test_stream << "write permission test";
|
||||
test_stream.close();
|
||||
|
||||
// Cleanup
|
||||
std::error_code ec;
|
||||
std::filesystem::remove(test_file, ec);
|
||||
|
||||
return true;
|
||||
|
||||
} catch (const std::exception&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace Vav2Player
|
||||
@@ -1,44 +0,0 @@
|
||||
#pragma once
|
||||
#include <windows.h>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
namespace Vav2Player {
|
||||
|
||||
class PermissionUtils {
|
||||
public:
|
||||
// 권한 체크 결과
|
||||
enum class PermissionResult {
|
||||
Granted, // 권한 있음
|
||||
Denied, // 권한 없음
|
||||
Error // 체크 중 오류 발생
|
||||
};
|
||||
|
||||
// 디렉토리 생성 권한 체크
|
||||
static PermissionResult CheckDirectoryCreatePermission(const std::filesystem::path& directory_path);
|
||||
|
||||
// 파일 쓰기 권한 체크
|
||||
static PermissionResult CheckFileWritePermission(const std::filesystem::path& file_path);
|
||||
|
||||
// 관리자 권한으로 실행 중인지 확인
|
||||
static bool IsRunningAsAdmin();
|
||||
|
||||
// 관리자 권한으로 재시작 요청
|
||||
static bool RequestAdminRestart();
|
||||
|
||||
// 사용자에게 권한 관련 메시지 표시
|
||||
static bool ShowPermissionDialog(const std::wstring& message, const std::wstring& title = L"권한 필요");
|
||||
|
||||
// 안전한 출력 디렉토리 제안
|
||||
static std::filesystem::path GetSafeOutputDirectory();
|
||||
|
||||
// 권한 체크 및 처리 (통합 함수)
|
||||
static PermissionResult CheckAndHandlePermissions(const std::filesystem::path& output_directory);
|
||||
|
||||
private:
|
||||
// 디렉토리에 대한 실제 쓰기 테스트
|
||||
static bool TestDirectoryWrite(const std::filesystem::path& directory_path);
|
||||
|
||||
};
|
||||
|
||||
} // namespace Vav2Player
|
||||
@@ -1,161 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <windows.h>
|
||||
|
||||
namespace Vav2Player {
|
||||
|
||||
// Lightweight std-based ComPtr replacement
|
||||
// Maintains Microsoft::WRL::ComPtr compatibility while using std libraries
|
||||
template<typename T>
|
||||
class StdComPtr {
|
||||
private:
|
||||
T* ptr_;
|
||||
|
||||
public:
|
||||
StdComPtr() : ptr_(nullptr) {}
|
||||
|
||||
explicit StdComPtr(T* ptr) : ptr_(ptr) {}
|
||||
|
||||
StdComPtr(const StdComPtr& other) : ptr_(other.ptr_) {
|
||||
if (ptr_) ptr_->AddRef();
|
||||
}
|
||||
|
||||
StdComPtr(StdComPtr&& other) noexcept : ptr_(other.ptr_) {
|
||||
other.ptr_ = nullptr;
|
||||
}
|
||||
|
||||
~StdComPtr() {
|
||||
if (ptr_) ptr_->Release();
|
||||
}
|
||||
|
||||
StdComPtr& operator=(const StdComPtr& other) {
|
||||
if (this != &other) {
|
||||
if (ptr_) ptr_->Release();
|
||||
ptr_ = other.ptr_;
|
||||
if (ptr_) ptr_->AddRef();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
StdComPtr& operator=(StdComPtr&& other) noexcept {
|
||||
if (this != &other) {
|
||||
if (ptr_) ptr_->Release();
|
||||
ptr_ = other.ptr_;
|
||||
other.ptr_ = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
StdComPtr& operator=(T* ptr) {
|
||||
if (ptr_) ptr_->Release();
|
||||
ptr_ = ptr;
|
||||
if (ptr_) ptr_->AddRef();
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Microsoft::WRL::ComPtr compatible interface
|
||||
T* Get() const { return ptr_; }
|
||||
T** GetAddressOf() {
|
||||
Reset();
|
||||
return &ptr_;
|
||||
}
|
||||
T* const* GetAddressOf() const {
|
||||
return &ptr_;
|
||||
}
|
||||
T** ReleaseAndGetAddressOf() {
|
||||
Reset();
|
||||
return &ptr_;
|
||||
}
|
||||
T* operator->() const { return ptr_; }
|
||||
T& operator*() const { return *ptr_; }
|
||||
explicit operator bool() const { return ptr_ != nullptr; }
|
||||
|
||||
// Address-of operator for COM API compatibility
|
||||
T** operator&() {
|
||||
Reset();
|
||||
return &ptr_;
|
||||
}
|
||||
|
||||
// Comparison operators
|
||||
bool operator==(std::nullptr_t) const { return ptr_ == nullptr; }
|
||||
bool operator!=(std::nullptr_t) const { return ptr_ != nullptr; }
|
||||
bool operator==(const StdComPtr& other) const { return ptr_ == other.ptr_; }
|
||||
bool operator!=(const StdComPtr& other) const { return ptr_ != other.ptr_; }
|
||||
bool operator<(const StdComPtr& other) const { return ptr_ < other.ptr_; }
|
||||
|
||||
void Reset() {
|
||||
if (ptr_) {
|
||||
ptr_->Release();
|
||||
ptr_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Attach(T* ptr) {
|
||||
if (ptr_) ptr_->Release();
|
||||
ptr_ = ptr;
|
||||
}
|
||||
|
||||
T* Detach() {
|
||||
T* temp = ptr_;
|
||||
ptr_ = nullptr;
|
||||
return temp;
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
HRESULT As(StdComPtr<U>* result) const {
|
||||
return ptr_ ? ptr_->QueryInterface(__uuidof(U), reinterpret_cast<void**>(result->GetAddressOf())) : E_POINTER;
|
||||
}
|
||||
|
||||
// Handle COM QueryInterface with raw pointer-to-pointer
|
||||
template<typename U>
|
||||
HRESULT As(U** result) const {
|
||||
return ptr_ ? ptr_->QueryInterface(__uuidof(U), reinterpret_cast<void**>(result)) : E_POINTER;
|
||||
}
|
||||
|
||||
// Direct QueryInterface support
|
||||
HRESULT QueryInterface(const IID& riid, void** ppvObject) const {
|
||||
return ptr_ ? ptr_->QueryInterface(riid, ppvObject) : E_POINTER;
|
||||
}
|
||||
|
||||
// Support for IID_PPV_ARGS macro compatibility
|
||||
template<typename U>
|
||||
HRESULT As(const IID& riid, void** ppvObject) const {
|
||||
return ptr_ ? ptr_->QueryInterface(riid, ppvObject) : E_POINTER;
|
||||
}
|
||||
};
|
||||
|
||||
// Type alias for easy migration
|
||||
template<typename T>
|
||||
using ComPtr = StdComPtr<T>;
|
||||
|
||||
// Helper function to make IID_PPV_ARGS work with StdComPtr
|
||||
template<typename T>
|
||||
void** IID_PPV_ARGS_Helper(StdComPtr<T>* pp) {
|
||||
return reinterpret_cast<void**>(pp->GetAddressOf());
|
||||
}
|
||||
|
||||
// Global comparison operators for symmetry
|
||||
template<typename T>
|
||||
bool operator==(std::nullptr_t, const StdComPtr<T>& ptr) { return ptr == nullptr; }
|
||||
|
||||
template<typename T>
|
||||
bool operator!=(std::nullptr_t, const StdComPtr<T>& ptr) { return ptr != nullptr; }
|
||||
|
||||
} // namespace Vav2Player
|
||||
|
||||
// Allow IID_PPV_ARGS to work with Vav2Player::StdComPtr
|
||||
namespace {
|
||||
template<typename T>
|
||||
void** IID_PPV_ARGS_Helper(Vav2Player::StdComPtr<T>* pp) {
|
||||
return reinterpret_cast<void**>(pp->GetAddressOf());
|
||||
}
|
||||
}
|
||||
|
||||
// Macro to easily switch between Microsoft WRL and std version
|
||||
#ifdef USE_STD_COMPTR
|
||||
#define COMPTR_NAMESPACE Vav2Player
|
||||
#else
|
||||
#include <wrl/client.h>
|
||||
#define COMPTR_NAMESPACE Microsoft::WRL
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,156 +0,0 @@
|
||||
#pragma once
|
||||
#include "IVideoDecoder.h"
|
||||
// Simplified architecture - removed complex pools
|
||||
#include <dav1d.h>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
|
||||
namespace Vav2Player {
|
||||
|
||||
// AV1 video decoder implementation class
|
||||
// Decodes AV1 video streams to YUV frames using the dav1d library
|
||||
class AV1Decoder : public IVideoDecoder {
|
||||
public:
|
||||
AV1Decoder();
|
||||
~AV1Decoder() override;
|
||||
|
||||
// Prevent copying
|
||||
AV1Decoder(const AV1Decoder&) = delete;
|
||||
AV1Decoder& operator=(const AV1Decoder&) = delete;
|
||||
|
||||
// IVideoDecoder interface implementation
|
||||
bool Initialize(const VideoMetadata& metadata) override;
|
||||
void Cleanup() override;
|
||||
bool IsInitialized() const override;
|
||||
|
||||
bool DecodeFrame(const VideoPacket& input_packet, VideoFrame& output_frame) override;
|
||||
bool DecodeFrame(const uint8_t* packet_data, size_t packet_size, VideoFrame& output_frame) override;
|
||||
|
||||
// Note: Pool-based methods removed during Phase 1 simplification
|
||||
|
||||
// Zero-copy decoding methods (decode without memory copying)
|
||||
// Warning: packet_data lifetime must be maintained until decoding completion!
|
||||
// dav1d may queue packets internally, so packet data can be referenced
|
||||
// asynchronously even after the call returns immediately
|
||||
bool DecodeFrameZeroCopy(const uint8_t* packet_data, size_t packet_size, VideoFrame& output_frame);
|
||||
// Note: DecodeFramePooledZeroCopy removed during Phase 1 simplification
|
||||
|
||||
// Note: Enhanced zero-copy and packet pool methods removed during Phase 1 simplification
|
||||
|
||||
// Direct GPU decoding method (direct output to D3D12 mapped buffers)
|
||||
bool DecodeFrameToGPU(const uint8_t* packet_data, size_t packet_size,
|
||||
uint8_t* yMappedBuffer, uint8_t* uMappedBuffer, uint8_t* vMappedBuffer,
|
||||
uint32_t yRowPitch, uint32_t uRowPitch, uint32_t vRowPitch,
|
||||
uint32_t videoWidth, uint32_t videoHeight);
|
||||
|
||||
// Ring Buffer supported GPU decoding method
|
||||
bool DecodeFrameToRingBuffer(const uint8_t* packet_data, size_t packet_size,
|
||||
uint32_t bufferIndex,
|
||||
uint8_t* yMappedBuffer, uint8_t* uMappedBuffer, uint8_t* vMappedBuffer,
|
||||
uint32_t yRowPitch, uint32_t uRowPitch, uint32_t vRowPitch,
|
||||
uint32_t videoWidth, uint32_t videoHeight);
|
||||
|
||||
// Compute Shader based GPU copy optimization method
|
||||
bool DecodeFrameWithGPUCopy(const uint8_t* packet_data, size_t packet_size,
|
||||
class D3D12VideoRenderer* renderer, uint32_t bufferIndex,
|
||||
uint32_t videoWidth, uint32_t videoHeight);
|
||||
|
||||
// Note: Direct texture mapping methods removed during Phase 1 simplification
|
||||
|
||||
bool Reset() override;
|
||||
bool Flush() override;
|
||||
|
||||
// Check EAGAIN state (when AV1 characteristics require more packets)
|
||||
bool IsWaitingForMoreData() const { return m_lastDecodeResult == DAV1D_ERR(EAGAIN); }
|
||||
|
||||
std::string GetCodecName() const override;
|
||||
VideoCodecType GetCodecType() const override;
|
||||
std::string GetVersion() const override;
|
||||
|
||||
DecoderStats GetStats() const override;
|
||||
void ResetStats() override;
|
||||
|
||||
// AV1 specific options
|
||||
bool SetOption(const std::string& key, const std::string& value) override;
|
||||
std::string GetOption(const std::string& key) const override;
|
||||
|
||||
// AV1 dedicated methods
|
||||
struct AV1Settings {
|
||||
int max_frame_delay = 1; // Maximum frame delay (lower means less delay)
|
||||
int num_threads = 0; // Number of decoding threads (0 = auto)
|
||||
bool apply_grain = true; // Apply film grain synthesis
|
||||
bool all_layers = false; // Decode all spatial/temporal layers
|
||||
|
||||
// Advanced dav1d optimization settings
|
||||
bool strict_std_compliance = true; // Standard compliance (false = performance optimization)
|
||||
bool operating_point = false; // Operating point processing
|
||||
bool enable_inloop_filters = true; // Enable in-loop filters (false = performance priority)
|
||||
};
|
||||
|
||||
void SetAV1Settings(const AV1Settings& settings);
|
||||
AV1Settings GetAV1Settings() const;
|
||||
|
||||
// Resolution-based optimized settings
|
||||
static AV1Settings GetOptimalSettingsForResolution(uint32_t width, uint32_t height);
|
||||
void ApplyOptimalSettingsForResolution(uint32_t width, uint32_t height);
|
||||
|
||||
private:
|
||||
// dav1d related members
|
||||
Dav1dContext* m_dav1d_context;
|
||||
Dav1dSettings m_dav1d_settings;
|
||||
AV1Settings m_av1_settings;
|
||||
|
||||
// Note: Direct texture allocator removed during Phase 1 simplification
|
||||
|
||||
// Initialization state
|
||||
bool m_initialized;
|
||||
VideoMetadata m_metadata;
|
||||
|
||||
// Track decoding results (for EAGAIN distinction)
|
||||
int m_lastDecodeResult = 0;
|
||||
|
||||
// Members for performance measurement
|
||||
std::chrono::high_resolution_clock::time_point m_decode_start_time;
|
||||
double m_total_decode_time_ms = 0.0;
|
||||
|
||||
// Internal helper methods
|
||||
bool InitializeDav1d();
|
||||
void CleanupDav1d();
|
||||
bool SetupDav1dSettings();
|
||||
|
||||
bool ConvertDav1dPicture(const Dav1dPicture& dav1d_pic, VideoFrame& output_frame);
|
||||
void UpdateDecodingStats(double decode_time_ms, size_t input_bytes);
|
||||
|
||||
// Convert dav1d pixel format to VideoTypes format
|
||||
ColorSpace ConvertDav1dPixelFormat(const Dav1dPicture& pic);
|
||||
|
||||
// Error handling
|
||||
std::string GetDav1dErrorString(int error_code);
|
||||
void LogError(const std::string& message, int error_code = 0);
|
||||
|
||||
// Zero-copy decoding support
|
||||
static void DummyFreeCallback(const uint8_t* data, void* cookie);
|
||||
};
|
||||
|
||||
// AV1 related utility functions
|
||||
namespace AV1Utils {
|
||||
// AV1 OBU (Open Bitstream Unit) type analysis
|
||||
enum class OBUType {
|
||||
SEQUENCE_HEADER = 1,
|
||||
TEMPORAL_DELIMITER = 2,
|
||||
FRAME_HEADER = 3,
|
||||
FRAME = 6,
|
||||
TILE_GROUP = 4,
|
||||
METADATA = 5,
|
||||
UNKNOWN = -1
|
||||
};
|
||||
|
||||
OBUType GetOBUType(const uint8_t* data, size_t size);
|
||||
bool IsKeyFrame(const uint8_t* data, size_t size);
|
||||
|
||||
// dav1d version information
|
||||
std::string GetDav1dVersion();
|
||||
std::string GetDav1dCopyright();
|
||||
}
|
||||
|
||||
} // namespace Vav2Player
|
||||
@@ -1,336 +0,0 @@
|
||||
#include "pch.h"
|
||||
#include "SimpleGPURenderer_Headless.h"
|
||||
#include <iostream>
|
||||
|
||||
#pragma comment(lib, "d3d12.lib")
|
||||
#pragma comment(lib, "dxgi.lib")
|
||||
|
||||
namespace Vav2Player {
|
||||
|
||||
SimpleGPURenderer_Headless::SimpleGPURenderer_Headless()
|
||||
: m_fenceValue(1)
|
||||
{
|
||||
m_fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
|
||||
}
|
||||
|
||||
SimpleGPURenderer_Headless::~SimpleGPURenderer_Headless()
|
||||
{
|
||||
Shutdown();
|
||||
if (m_fenceEvent)
|
||||
{
|
||||
CloseHandle(m_fenceEvent);
|
||||
m_fenceEvent = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT SimpleGPURenderer_Headless::Initialize(uint32_t width, uint32_t height)
|
||||
{
|
||||
if (m_initialized)
|
||||
return S_OK;
|
||||
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
// 1. Create D3D12 device
|
||||
hr = CreateDevice();
|
||||
if (FAILED(hr)) return hr;
|
||||
|
||||
// 2. Create command queue
|
||||
hr = CreateCommandQueue();
|
||||
if (FAILED(hr)) return hr;
|
||||
|
||||
// 3. Create synchronization objects
|
||||
hr = CreateSynchronizationObjects();
|
||||
if (FAILED(hr)) return hr;
|
||||
|
||||
m_initialized = true;
|
||||
std::cout << "[SimpleGPURenderer_Headless] Initialized successfully (" << width << "x" << height << ")" << std::endl;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void SimpleGPURenderer_Headless::Shutdown()
|
||||
{
|
||||
if (!m_initialized)
|
||||
return;
|
||||
|
||||
// Wait for GPU to finish
|
||||
WaitForGPU();
|
||||
|
||||
// Reset COM objects
|
||||
m_yTexture.Reset();
|
||||
m_uTexture.Reset();
|
||||
m_vTexture.Reset();
|
||||
m_commandList.Reset();
|
||||
m_commandAllocator.Reset();
|
||||
m_fence.Reset();
|
||||
m_commandQueue.Reset();
|
||||
m_device.Reset();
|
||||
|
||||
m_initialized = false;
|
||||
std::cout << "[SimpleGPURenderer_Headless] Shutdown completed" << std::endl;
|
||||
}
|
||||
|
||||
HRESULT SimpleGPURenderer_Headless::RenderVideoFrame(const VideoFrame& frame)
|
||||
{
|
||||
if (!m_initialized)
|
||||
return E_FAIL;
|
||||
|
||||
// Basic frame validation
|
||||
if (frame.width == 0 || frame.height == 0)
|
||||
return E_INVALIDARG;
|
||||
|
||||
std::cout << "[SimpleGPURenderer_Headless] Would render frame ("
|
||||
<< frame.width << "x" << frame.height << ")" << std::endl;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT SimpleGPURenderer_Headless::TestGPUPipeline()
|
||||
{
|
||||
if (!m_initialized)
|
||||
return E_FAIL;
|
||||
|
||||
std::cout << "[SimpleGPURenderer_Headless] Testing GPU pipeline..." << std::endl;
|
||||
|
||||
// Test command list recording
|
||||
HRESULT hr = m_commandAllocator->Reset();
|
||||
if (FAILED(hr))
|
||||
{
|
||||
std::cout << "[FAIL] Command allocator reset failed: 0x" << std::hex << hr << std::endl;
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = m_commandList->Reset(m_commandAllocator.Get(), nullptr);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
std::cout << "[FAIL] Command list reset failed: 0x" << std::hex << hr << std::endl;
|
||||
return hr;
|
||||
}
|
||||
|
||||
// Close command list
|
||||
hr = m_commandList->Close();
|
||||
if (FAILED(hr))
|
||||
{
|
||||
std::cout << "[FAIL] Command list close failed: 0x" << std::hex << hr << std::endl;
|
||||
return hr;
|
||||
}
|
||||
|
||||
// Execute empty command list (just to test pipeline)
|
||||
ID3D12CommandList* commandLists[] = { m_commandList.Get() };
|
||||
m_commandQueue->ExecuteCommandLists(1, commandLists);
|
||||
|
||||
// Wait for completion
|
||||
hr = WaitForGPU();
|
||||
if (FAILED(hr))
|
||||
{
|
||||
std::cout << "[FAIL] GPU wait failed: 0x" << std::hex << hr << std::endl;
|
||||
return hr;
|
||||
}
|
||||
|
||||
std::cout << "[PASS] GPU pipeline test completed successfully" << std::endl;
|
||||
|
||||
// Test compute shader compilation
|
||||
hr = TestComputeShaderCompilation();
|
||||
if (FAILED(hr))
|
||||
{
|
||||
std::cout << "[FAIL] Compute shader compilation test failed" << std::endl;
|
||||
return hr;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT SimpleGPURenderer_Headless::CreateDevice()
|
||||
{
|
||||
// Enable debug layer in debug builds
|
||||
#ifdef _DEBUG
|
||||
ComPtr<ID3D12Debug> debugController;
|
||||
if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController))))
|
||||
{
|
||||
debugController->EnableDebugLayer();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Create device
|
||||
HRESULT hr = D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&m_device));
|
||||
if (FAILED(hr))
|
||||
{
|
||||
std::cout << "[SimpleGPURenderer_Headless] Failed to create D3D12 device: 0x" << std::hex << hr << std::endl;
|
||||
return hr;
|
||||
}
|
||||
|
||||
std::cout << "[SimpleGPURenderer_Headless] D3D12 device created successfully" << std::endl;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT SimpleGPURenderer_Headless::CreateCommandQueue()
|
||||
{
|
||||
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
|
||||
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
|
||||
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
|
||||
|
||||
HRESULT hr = m_device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&m_commandQueue));
|
||||
if (FAILED(hr))
|
||||
{
|
||||
std::cout << "[SimpleGPURenderer_Headless] Failed to create command queue: 0x" << std::hex << hr << std::endl;
|
||||
return hr;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT SimpleGPURenderer_Headless::CreateSynchronizationObjects()
|
||||
{
|
||||
// Create fence
|
||||
HRESULT hr = m_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence));
|
||||
if (FAILED(hr))
|
||||
{
|
||||
std::cout << "[SimpleGPURenderer_Headless] Failed to create fence: 0x" << std::hex << hr << std::endl;
|
||||
return hr;
|
||||
}
|
||||
|
||||
// Create command allocator
|
||||
hr = m_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&m_commandAllocator));
|
||||
if (FAILED(hr))
|
||||
{
|
||||
std::cout << "[SimpleGPURenderer_Headless] Failed to create command allocator: 0x" << std::hex << hr << std::endl;
|
||||
return hr;
|
||||
}
|
||||
|
||||
// Create command list
|
||||
hr = m_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_commandAllocator.Get(), nullptr, IID_PPV_ARGS(&m_commandList));
|
||||
if (FAILED(hr))
|
||||
{
|
||||
std::cout << "[SimpleGPURenderer_Headless] Failed to create command list: 0x" << std::hex << hr << std::endl;
|
||||
return hr;
|
||||
}
|
||||
|
||||
// Close the command list initially
|
||||
hr = m_commandList->Close();
|
||||
if (FAILED(hr))
|
||||
{
|
||||
std::cout << "[SimpleGPURenderer_Headless] Failed to close initial command list: 0x" << std::hex << hr << std::endl;
|
||||
return hr;
|
||||
}
|
||||
|
||||
std::cout << "[SimpleGPURenderer_Headless] Synchronization objects created successfully" << std::endl;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT SimpleGPURenderer_Headless::WaitForGPU()
|
||||
{
|
||||
if (!m_commandQueue || !m_fence || !m_fenceEvent)
|
||||
return E_FAIL;
|
||||
|
||||
// Signal the fence
|
||||
HRESULT hr = m_commandQueue->Signal(m_fence.Get(), m_fenceValue);
|
||||
if (FAILED(hr)) return hr;
|
||||
|
||||
// Wait for fence completion
|
||||
if (m_fence->GetCompletedValue() < m_fenceValue)
|
||||
{
|
||||
hr = m_fence->SetEventOnCompletion(m_fenceValue, m_fenceEvent);
|
||||
if (FAILED(hr)) return hr;
|
||||
|
||||
WaitForSingleObject(m_fenceEvent, INFINITE);
|
||||
}
|
||||
|
||||
m_fenceValue++;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT SimpleGPURenderer_Headless::TestComputeShaderCompilation()
|
||||
{
|
||||
std::cout << "[SimpleGPURenderer_Headless] Testing compute shader compilation..." << std::endl;
|
||||
|
||||
// Define the same YUV-to-RGB compute shader as SimpleGPURenderer
|
||||
const char* shaderSource = R"(
|
||||
// YUV to RGB conversion compute shader
|
||||
// Uses BT.709 color space conversion matrix
|
||||
|
||||
// Input Y texture (luminance)
|
||||
Texture2D<float> g_yTexture : register(t0);
|
||||
|
||||
// Input U texture (chroma)
|
||||
Texture2D<float> g_uTexture : register(t1);
|
||||
|
||||
// Input V texture (chroma)
|
||||
Texture2D<float> g_vTexture : register(t2);
|
||||
|
||||
// Output RGB texture
|
||||
RWTexture2D<float4> g_rgbTexture : register(u0);
|
||||
|
||||
[numthreads(8, 8, 1)]
|
||||
void main(uint3 id : SV_DispatchThreadID)
|
||||
{
|
||||
// Get texture dimensions
|
||||
uint2 texSize;
|
||||
g_yTexture.GetDimensions(texSize.x, texSize.y);
|
||||
|
||||
// Bounds check
|
||||
if (id.x >= texSize.x || id.y >= texSize.y)
|
||||
return;
|
||||
|
||||
// Sample Y, U, V values
|
||||
float y = g_yTexture[id.xy].r;
|
||||
|
||||
// UV coordinates are half resolution (4:2:0 format)
|
||||
uint2 uvCoord = id.xy / 2;
|
||||
float u = g_uTexture[uvCoord].r;
|
||||
float v = g_vTexture[uvCoord].r;
|
||||
|
||||
// Convert from [0,1] to YUV ranges
|
||||
y = (y * 255.0f - 16.0f) / 219.0f;
|
||||
u = (u * 255.0f - 128.0f) / 224.0f;
|
||||
v = (v * 255.0f - 128.0f) / 224.0f;
|
||||
|
||||
// BT.709 YUV to RGB conversion matrix
|
||||
float3 rgb;
|
||||
rgb.r = y + 1.5748f * v;
|
||||
rgb.g = y - 0.1873f * u - 0.4681f * v;
|
||||
rgb.b = y + 1.8556f * u;
|
||||
|
||||
// Clamp to [0,1] range
|
||||
rgb = saturate(rgb);
|
||||
|
||||
// Write RGB result
|
||||
g_rgbTexture[id.xy] = float4(rgb, 1.0f);
|
||||
}
|
||||
)";
|
||||
|
||||
// Test compilation
|
||||
ComPtr<ID3DBlob> shaderBlob;
|
||||
ComPtr<ID3DBlob> errorBlob;
|
||||
|
||||
HRESULT hr = D3DCompile(
|
||||
shaderSource,
|
||||
strlen(shaderSource),
|
||||
"YUVToRGB_Compute_Test",
|
||||
nullptr,
|
||||
nullptr,
|
||||
"main",
|
||||
"cs_5_0",
|
||||
D3DCOMPILE_OPTIMIZATION_LEVEL3,
|
||||
0,
|
||||
&shaderBlob,
|
||||
&errorBlob
|
||||
);
|
||||
|
||||
if (FAILED(hr))
|
||||
{
|
||||
if (errorBlob)
|
||||
{
|
||||
std::cout << "[FAIL] Compute shader compilation failed: "
|
||||
<< (char*)errorBlob->GetBufferPointer() << std::endl;
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
|
||||
std::cout << "[PASS] Compute shader compiled successfully (Size: "
|
||||
<< shaderBlob->GetBufferSize() << " bytes)" << std::endl;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
} // namespace Vav2Player
|
||||
@@ -1,64 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <d3d12.h>
|
||||
#include <dxgi1_6.h>
|
||||
#include <d3dcompiler.h>
|
||||
#include <wrl/client.h>
|
||||
#include "../Common/VideoTypes.h"
|
||||
|
||||
using Microsoft::WRL::ComPtr;
|
||||
|
||||
namespace Vav2Player {
|
||||
|
||||
// Headless version of SimpleGPURenderer for testing
|
||||
// Phase 3: GPU pipeline testing without GUI dependencies
|
||||
class SimpleGPURenderer_Headless
|
||||
{
|
||||
public:
|
||||
SimpleGPURenderer_Headless();
|
||||
~SimpleGPURenderer_Headless();
|
||||
|
||||
// Core lifecycle (headless - no SwapChainPanel)
|
||||
HRESULT Initialize(uint32_t width, uint32_t height);
|
||||
void Shutdown();
|
||||
bool IsInitialized() const { return m_initialized; }
|
||||
|
||||
// Video rendering (headless - no actual display)
|
||||
HRESULT RenderVideoFrame(const VideoFrame& frame);
|
||||
HRESULT TestGPUPipeline(); // Test GPU functionality
|
||||
|
||||
// Size management
|
||||
uint32_t GetWidth() const { return m_width; }
|
||||
uint32_t GetHeight() const { return m_height; }
|
||||
|
||||
private:
|
||||
// D3D12 core objects (minimal for headless)
|
||||
ComPtr<ID3D12Device> m_device;
|
||||
ComPtr<ID3D12CommandQueue> m_commandQueue;
|
||||
ComPtr<ID3D12CommandAllocator> m_commandAllocator;
|
||||
ComPtr<ID3D12GraphicsCommandList> m_commandList;
|
||||
|
||||
// Synchronization
|
||||
ComPtr<ID3D12Fence> m_fence;
|
||||
UINT64 m_fenceValue;
|
||||
HANDLE m_fenceEvent;
|
||||
|
||||
// Video textures for testing
|
||||
ComPtr<ID3D12Resource> m_yTexture;
|
||||
ComPtr<ID3D12Resource> m_uTexture;
|
||||
ComPtr<ID3D12Resource> m_vTexture;
|
||||
|
||||
// State
|
||||
bool m_initialized = false;
|
||||
uint32_t m_width = 0;
|
||||
uint32_t m_height = 0;
|
||||
|
||||
// Helper methods
|
||||
HRESULT CreateDevice();
|
||||
HRESULT CreateCommandQueue();
|
||||
HRESULT CreateSynchronizationObjects();
|
||||
HRESULT WaitForGPU();
|
||||
HRESULT TestComputeShaderCompilation();
|
||||
};
|
||||
|
||||
} // namespace Vav2Player
|
||||
Reference in New Issue
Block a user