Simple GPU pipeline implementation

This commit is contained in:
2025-09-23 00:31:16 +09:00
parent 5961a3408e
commit 47ccea3ad2
4 changed files with 518 additions and 205 deletions

View File

@@ -21,8 +21,8 @@
<!-- Hardware D3D12 rendering (Phase 1) -->
<SwapChainPanel x:Name="VideoSwapChainPanel"
Visibility="Collapsed"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<!-- Software CPU rendering (fallback) -->
<Image x:Name="VideoImage"

View File

@@ -25,7 +25,7 @@ using namespace winrt::Microsoft::UI::Dispatching;
namespace winrt::Vav2Player::implementation
{
VideoPlayerControl::VideoPlayerControl()
: m_useHardwareRendering(false) // CPU mode for Phase 1
: m_useHardwareRendering(true) // TEST: Enable GPU rendering to test the upload buffer fix
{
InitializeComponent();
}
@@ -395,8 +395,8 @@ namespace winrt::Vav2Player::implementation
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);
// Try GPU rendering first, fallback to CPU if needed
UseHardwareRendering(true);
// Create and initialize decoder
OutputDebugStringA("[DEBUG] Creating decoder...\n");
@@ -573,41 +573,25 @@ namespace winrt::Vav2Player::implementation
void VideoPlayerControl::ProcessSingleFrame()
{
OutputDebugStringA("[DEBUG] ProcessSingleFrame() called\n");
if (!m_isLoaded || !m_fileReader || !m_decoder)
{
OutputDebugStringA("[DEBUG] ProcessSingleFrame() - not ready\n");
return;
}
if (!m_isLoaded || !m_fileReader || !m_decoder) return;
VideoPacket packet;
OutputDebugStringA("[DEBUG] Reading next packet...\n");
if (!m_fileReader->ReadNextPacket(packet))
{
OutputDebugStringA("[DEBUG] No more packets - playback completed\n");
// End of video - stop playback
m_isPlaying = false;
if (m_playbackTimer) {
m_playbackTimer.Stop();
}
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());
if (!m_decoder->DecodeFrame(packet, frame)) return; // Skip failed frames
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()
@@ -618,7 +602,16 @@ namespace winrt::Vav2Player::implementation
void VideoPlayerControl::RenderFrameToScreen(const VideoFrame& frame)
{
OutputDebugStringA("[DEBUG] RenderFrameToScreen() called\n");
// GPU rendering attempt
if (m_useHardwareRendering && m_gpuRenderer && m_gpuRenderer->IsInitialized()) {
if (m_gpuRenderer->TryRenderFrame(frame)) {
// GPU rendering successful - apply AspectFit to SwapChainPanel
UpdateVideoImageAspectFit(frame.width, frame.height);
return; // Success - done
}
}
// CPU rendering fallback (always works)
RenderFrameSoftware(frame);
}
@@ -759,10 +752,29 @@ namespace winrt::Vav2Player::implementation
}
else
{
// Hardware rendering setup (Phase 2 - future)
// Hardware rendering setup
VideoSwapChainPanel().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible);
VideoImage().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed);
OutputDebugStringA("[DEBUG] Hardware rendering not implemented yet\n");
if (!m_gpuRenderer)
{
m_gpuRenderer = std::make_unique<SimpleGPURenderer>();
}
// Initialize GPU renderer with SwapChainPanel
HRESULT hr = m_gpuRenderer->Initialize(VideoSwapChainPanel(), 1920, 1080);
if (SUCCEEDED(hr))
{
OutputDebugStringA("[DEBUG] GPU rendering initialized successfully\n");
}
else
{
OutputDebugStringA("[DEBUG] GPU rendering initialization failed, falling back to CPU\n");
m_useHardwareRendering = false;
m_gpuRenderer.reset();
VideoSwapChainPanel().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed);
VideoImage().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible);
}
}
}
@@ -828,8 +840,14 @@ namespace winrt::Vav2Player::implementation
displayWidth = containerHeight * videoAspectRatio;
}
// Apply AspectFit to both CPU and GPU rendering controls
VideoImage().Width(displayWidth);
VideoImage().Height(displayHeight);
// Also apply to GPU rendering SwapChainPanel
VideoSwapChainPanel().Width(displayWidth);
VideoSwapChainPanel().Height(displayHeight);
OutputDebugStringA(("[DEBUG] Video size set to: " + std::to_string(displayWidth) + "x" + std::to_string(displayHeight) + "\n").c_str());
}

View File

@@ -10,8 +10,15 @@
namespace Vav2Player {
SimpleGPURenderer::SimpleGPURenderer()
: m_frameIndex(0) // Always use frame index 0 for simplicity
, m_fenceValue(0)
{
m_fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
// Initialize frame completion tracking
for (UINT i = 0; i < FrameCount; ++i)
{
m_frameCompletionValues[i] = 0;
}
}
SimpleGPURenderer::~SimpleGPURenderer()
@@ -80,13 +87,19 @@ void SimpleGPURenderer::Shutdown()
m_computePipelineState.Reset();
m_computeRootSignature.Reset();
m_srvUavHeap.Reset();
m_rgbTexture.Reset();
m_yTexture.Reset();
m_uTexture.Reset();
m_vTexture.Reset();
m_yUploadBuffer.Reset();
m_uUploadBuffer.Reset();
m_vUploadBuffer.Reset();
for (UINT i = 0; i < FrameCount; ++i)
{
m_rgbTextures[i].Reset();
m_yTextures[i].Reset();
m_uTextures[i].Reset();
m_vTextures[i].Reset();
}
for (UINT i = 0; i < FrameCount; ++i)
{
m_yUploadBuffers[i].Reset();
m_uUploadBuffers[i].Reset();
m_vUploadBuffers[i].Reset();
}
m_constantBuffer.Reset();
for (UINT i = 0; i < FrameCount; i++)
@@ -117,29 +130,53 @@ HRESULT SimpleGPURenderer::RenderVideoFrame(const VideoFrame& frame)
HRESULT hr = S_OK;
// 1. Create/update video textures if needed
// 1. TRIPLE BUFFERING DEBUG: Check frame completion status
std::cout << "[SimpleGPURenderer] Frame " << m_frameIndex << " - Current fence: " << m_fence->GetCompletedValue()
<< ", Target: " << m_frameCompletionValues[m_frameIndex] << std::endl;
WaitForFrameCompletion(m_frameIndex);
// 2. Create/update video textures if needed
if (frame.width != m_videoWidth || frame.height != m_videoHeight)
{
hr = CreateVideoTextures(frame.width, frame.height);
if (FAILED(hr)) return hr;
}
// 2. Upload YUV data to GPU
hr = UpdateVideoTextures(frame);
// 3. Execute complete GPU pipeline in single command recording session
hr = ExecuteGPUPipeline(frame);
if (FAILED(hr)) return hr;
// 3. Execute YUV-to-RGB compute shader
hr = ExecuteComputeShader();
// 4. Signal completion for current frame and advance to next frame
const UINT64 currentFrameFenceValue = ++m_fenceValue;
m_frameCompletionValues[m_frameIndex] = currentFrameFenceValue;
hr = m_commandQueue->Signal(m_fence.Get(), currentFrameFenceValue);
if (FAILED(hr)) return hr;
// 4. Copy RGB result to back buffer
hr = CopyToBackBuffer();
if (FAILED(hr)) return hr;
// 5. Advance to next frame (triple buffering rotation)
m_frameIndex = (m_frameIndex + 1) % FrameCount;
std::cout << "[SimpleGPURenderer] Frame rendered (" << frame.width << "x" << frame.height << ")" << std::endl;
std::cout << "[SimpleGPURenderer] Frame rendered successfully (" << frame.width << "x" << frame.height << "), frame index: " << m_frameIndex << std::endl;
return S_OK;
}
bool SimpleGPURenderer::TryRenderFrame(const VideoFrame& frame)
{
try {
HRESULT hr = RenderVideoFrame(frame);
if (SUCCEEDED(hr))
{
hr = Present();
return SUCCEEDED(hr);
}
return false;
}
catch (...) {
std::cout << "[SimpleGPURenderer] Exception caught in TryRenderFrame - falling back to CPU" << std::endl;
return false;
}
}
HRESULT SimpleGPURenderer::Present()
{
if (!m_initialized || !m_swapChain)
@@ -250,9 +287,9 @@ HRESULT SimpleGPURenderer::CreateDescriptorHeaps()
m_rtvDescriptorSize = m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
// Create SRV/UAV descriptor heap for compute shader
// Create SRV/UAV descriptor heap for compute shader (triple buffering)
D3D12_DESCRIPTOR_HEAP_DESC srvUavHeapDesc = {};
srvUavHeapDesc.NumDescriptors = 10; // Y,U,V textures + RGB output + spare
srvUavHeapDesc.NumDescriptors = FrameCount * 4; // 3 frames * (Y,U,V,RGB) = 12 descriptors
srvUavHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
srvUavHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
@@ -301,11 +338,9 @@ HRESULT SimpleGPURenderer::CreateSynchronizationObjects()
return hr;
}
// Initialize fence values
for (UINT i = 0; i < FrameCount; i++)
{
m_fenceValues[i] = 1;
}
// Initialize fence values (already done in constructor)
// m_fenceValue = 0;
// m_frameCompletionValues already initialized to 0
// Create command allocators
for (UINT i = 0; i < FrameCount; i++)
@@ -405,47 +440,56 @@ HRESULT SimpleGPURenderer::CreateVideoTextures(uint32_t videoWidth, uint32_t vid
D3D12_HEAP_PROPERTIES heapProps = {};
heapProps.Type = D3D12_HEAP_TYPE_DEFAULT;
hr = m_device->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &textureDesc,
D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&m_yTexture));
if (FAILED(hr))
// Create video textures for all frames (triple buffering)
for (UINT i = 0; i < FrameCount; ++i)
{
std::cout << "[SimpleGPURenderer] Failed to create Y texture: 0x" << std::hex << hr << std::endl;
return hr;
// Y texture (full resolution)
textureDesc.Width = videoWidth;
textureDesc.Height = videoHeight;
hr = m_device->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &textureDesc,
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, nullptr, IID_PPV_ARGS(&m_yTextures[i]));
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to create Y texture " << i << ": 0x" << std::hex << hr << std::endl;
return hr;
}
// U texture (half resolution for 4:2:0)
textureDesc.Width = videoWidth / 2;
textureDesc.Height = videoHeight / 2;
hr = m_device->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &textureDesc,
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, nullptr, IID_PPV_ARGS(&m_uTextures[i]));
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to create U texture " << i << ": 0x" << std::hex << hr << std::endl;
return hr;
}
// V texture (half resolution for 4:2:0)
hr = m_device->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &textureDesc,
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, nullptr, IID_PPV_ARGS(&m_vTextures[i]));
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to create V texture " << i << ": 0x" << std::hex << hr << std::endl;
return hr;
}
}
// Create U texture (half resolution for 4:2:0)
textureDesc.Width = videoWidth / 2;
textureDesc.Height = videoHeight / 2;
hr = m_device->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &textureDesc,
D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&m_uTexture));
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to create U texture: 0x" << std::hex << hr << std::endl;
return hr;
}
// Create V texture (half resolution for 4:2:0)
hr = m_device->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &textureDesc,
D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&m_vTexture));
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to create V texture: 0x" << std::hex << hr << std::endl;
return hr;
}
// Create RGB output texture (for compute shader output)
// Create RGB output textures (for compute shader output) - triple buffered
textureDesc.Width = m_width;
textureDesc.Height = m_height;
textureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
textureDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
hr = m_device->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &textureDesc,
D3D12_RESOURCE_STATE_UNORDERED_ACCESS, nullptr, IID_PPV_ARGS(&m_rgbTexture));
if (FAILED(hr))
for (UINT i = 0; i < FrameCount; ++i)
{
std::cout << "[SimpleGPURenderer] Failed to create RGB texture: 0x" << std::hex << hr << std::endl;
return hr;
hr = m_device->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &textureDesc,
D3D12_RESOURCE_STATE_UNORDERED_ACCESS, nullptr, IID_PPV_ARGS(&m_rgbTextures[i]));
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to create RGB texture " << i << ": 0x" << std::hex << hr << std::endl;
return hr;
}
}
// Create upload buffers for CPU->GPU transfer
@@ -461,63 +505,221 @@ HRESULT SimpleGPURenderer::CreateVideoTextures(uint32_t videoWidth, uint32_t vid
uploadDesc.SampleDesc.Count = 1;
uploadDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
// Y upload buffer
uploadDesc.Width = videoWidth * videoHeight;
hr = m_device->CreateCommittedResource(&uploadHeapProps, D3D12_HEAP_FLAG_NONE, &uploadDesc,
D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&m_yUploadBuffer));
if (FAILED(hr))
// Create upload buffers for all frames (triple buffering)
for (UINT i = 0; i < FrameCount; ++i)
{
std::cout << "[SimpleGPURenderer] Failed to create Y upload buffer: 0x" << std::hex << hr << std::endl;
return hr;
// Y upload buffer
uploadDesc.Width = videoWidth * videoHeight;
hr = m_device->CreateCommittedResource(&uploadHeapProps, D3D12_HEAP_FLAG_NONE, &uploadDesc,
D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&m_yUploadBuffers[i]));
if (FAILED(hr))
{
std::cout << "[SimpleGPURenderer] Failed to create Y upload buffer " << i << ": 0x" << std::hex << hr << std::endl;
return hr;
}
// U and V upload buffers
uploadDesc.Width = (videoWidth / 2) * (videoHeight / 2);
hr = m_device->CreateCommittedResource(&uploadHeapProps, D3D12_HEAP_FLAG_NONE, &uploadDesc,
D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&m_uUploadBuffers[i]));
if (FAILED(hr)) return hr;
hr = m_device->CreateCommittedResource(&uploadHeapProps, D3D12_HEAP_FLAG_NONE, &uploadDesc,
D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&m_vUploadBuffers[i]));
if (FAILED(hr)) return hr;
}
// U and V upload buffers
uploadDesc.Width = (videoWidth / 2) * (videoHeight / 2);
hr = m_device->CreateCommittedResource(&uploadHeapProps, D3D12_HEAP_FLAG_NONE, &uploadDesc,
D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&m_uUploadBuffer));
if (FAILED(hr)) return hr;
hr = m_device->CreateCommittedResource(&uploadHeapProps, D3D12_HEAP_FLAG_NONE, &uploadDesc,
D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&m_vUploadBuffer));
if (FAILED(hr)) return hr;
// Create descriptor views (SRV for Y,U,V textures and UAV for RGB texture) - All frames for triple buffering
if (m_srvUavHeap)
{
CD3DX12_CPU_DESCRIPTOR_HANDLE srvHandle(m_srvUavHeap->GetCPUDescriptorHandleForHeapStart());
UINT descriptorSize = m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Format = DXGI_FORMAT_R8_UNORM;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MipLevels = 1;
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {};
uavDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D;
uavDesc.Texture2D.MipSlice = 0;
// Create descriptors for all frames (triple buffering)
for (UINT frameIdx = 0; frameIdx < FrameCount; ++frameIdx)
{
// Y texture SRV (frame 0: descriptor 0, frame 1: descriptor 4, frame 2: descriptor 8)
CD3DX12_CPU_DESCRIPTOR_HANDLE frameHandle = srvHandle;
frameHandle.Offset(frameIdx * 4, descriptorSize);
m_device->CreateShaderResourceView(m_yTextures[frameIdx].Get(), &srvDesc, frameHandle);
// U texture SRV (frame 0: descriptor 1, frame 1: descriptor 5, frame 2: descriptor 9)
frameHandle.Offset(descriptorSize);
m_device->CreateShaderResourceView(m_uTextures[frameIdx].Get(), &srvDesc, frameHandle);
// V texture SRV (frame 0: descriptor 2, frame 1: descriptor 6, frame 2: descriptor 10)
frameHandle.Offset(descriptorSize);
m_device->CreateShaderResourceView(m_vTextures[frameIdx].Get(), &srvDesc, frameHandle);
// RGB texture UAV (frame 0: descriptor 3, frame 1: descriptor 7, frame 2: descriptor 11)
frameHandle.Offset(descriptorSize);
m_device->CreateUnorderedAccessView(m_rgbTextures[frameIdx].Get(), nullptr, &uavDesc, frameHandle);
}
std::cout << "[SimpleGPURenderer] Descriptor views created for " << FrameCount << " frame sets (triple buffering)" << std::endl;
}
else
{
std::cout << "[SimpleGPURenderer] Warning: SRV/UAV heap not available for descriptor creation" << std::endl;
}
std::cout << "[SimpleGPURenderer] Video textures created (" << videoWidth << "x" << videoHeight << ")" << std::endl;
return S_OK;
}
HRESULT SimpleGPURenderer::UpdateVideoTextures(const VideoFrame& frame)
HRESULT SimpleGPURenderer::ExecuteGPUPipeline(const VideoFrame& frame)
{
if (!m_yTexture || !m_uTexture || !m_vTexture)
if (!m_commandAllocators[m_frameIndex] || !m_commandList)
return E_FAIL;
HRESULT hr = S_OK;
// Reset command allocator and command list
// Step 1: Reset command allocator and list ONCE per frame
hr = m_commandAllocators[m_frameIndex]->Reset();
if (FAILED(hr)) return hr;
if (FAILED(hr)) {
std::cout << "[SimpleGPURenderer] Failed to reset command allocator: 0x" << std::hex << hr << std::endl;
return hr;
}
hr = m_commandList->Reset(m_commandAllocators[m_frameIndex].Get(), nullptr);
if (FAILED(hr)) {
std::cout << "[SimpleGPURenderer] Failed to reset command list: 0x" << std::hex << hr << std::endl;
return hr;
}
std::cout << "[SimpleGPURenderer] Starting GPU pipeline for frame " << m_frameIndex << std::endl;
// Step 2: Update video textures (upload YUV data)
hr = UpdateVideoTexturesInternal(frame);
if (FAILED(hr)) return hr;
// Step 3: Execute compute shader (YUV->RGB conversion)
hr = ExecuteComputeShaderInternal();
if (FAILED(hr)) return hr;
// Step 4: Copy RGB to back buffer
hr = CopyToBackBufferInternal();
if (FAILED(hr)) return hr;
// Step 5: Close and execute command list ONCE
hr = m_commandList->Close();
if (FAILED(hr)) {
std::cout << "[SimpleGPURenderer] Failed to close command list: 0x" << std::hex << hr << std::endl;
return hr;
}
ID3D12CommandList* commandLists[] = { m_commandList.Get() };
m_commandQueue->ExecuteCommandLists(1, commandLists);
std::cout << "[SimpleGPURenderer] GPU pipeline executed successfully" << std::endl;
return S_OK;
}
HRESULT SimpleGPURenderer::UpdateVideoTexturesInternal(const VideoFrame& frame)
{
// Use current frame's video textures
auto& yTexture = m_yTextures[m_frameIndex];
auto& uTexture = m_uTextures[m_frameIndex];
auto& vTexture = m_vTextures[m_frameIndex];
auto& rgbTexture = m_rgbTextures[m_frameIndex];
if (!yTexture || !uTexture || !vTexture || !rgbTexture)
return E_FAIL;
// TRIPLE BUFFERING: Use current frame's upload buffers (already created during initialization)
if (!m_yUploadBuffers[m_frameIndex] || !m_uUploadBuffers[m_frameIndex] || !m_vUploadBuffers[m_frameIndex])
return E_FAIL;
std::cout << "[SimpleGPURenderer] Using upload buffers for frame " << m_frameIndex << std::endl;
HRESULT hr = S_OK;
if (!m_commandAllocators[m_frameIndex] || !m_commandList)
return E_FAIL;
// NOTE: Command allocator and list are reset in ExecuteGPUPipeline() - do not reset here
// 1. Map and copy Y data
std::cout << "[SimpleGPURenderer] Starting Y data mapping..." << std::endl;
// Validate device and buffer state
if (!m_device) {
std::cout << "[SimpleGPURenderer] D3D12 device is null" << std::endl;
return E_FAIL;
}
// Use current frame's upload buffers
auto& yUploadBuffer = m_yUploadBuffers[m_frameIndex];
auto& uUploadBuffer = m_uUploadBuffers[m_frameIndex];
auto& vUploadBuffer = m_vUploadBuffers[m_frameIndex];
// Check upload buffer description
D3D12_RESOURCE_DESC bufferDesc = yUploadBuffer->GetDesc();
std::cout << "[SimpleGPURenderer] Y buffer size: " << bufferDesc.Width << " bytes" << std::endl;
void* yMappedData = nullptr;
hr = m_yUploadBuffer->Map(0, nullptr, &yMappedData);
D3D12_RANGE readRange = { 0, 0 }; // We don't read from this resource on the CPU
// Additional safety: Check if upload buffer is valid before mapping
if (!yUploadBuffer)
{
std::cout << "[SimpleGPURenderer] Y upload buffer is null!" << std::endl;
return E_FAIL;
}
// Frame-specific synchronization has already been handled in RenderVideoFrame()
// No additional wait needed here - current frame allocator should be free
std::cout << "[SimpleGPURenderer] About to call Map() on Y upload buffer..." << std::endl;
hr = yUploadBuffer->Map(0, &readRange, &yMappedData);
if (FAILED(hr)) {
std::cout << "[SimpleGPURenderer] Failed to map Y upload buffer: 0x" << std::hex << hr << std::endl;
return hr;
}
if (!yMappedData)
{
std::cout << "[SimpleGPURenderer] Y upload buffer mapping returned null pointer!" << std::endl;
yUploadBuffer->Unmap(0, nullptr);
return E_FAIL;
}
// Validate frame data
if (!frame.y_plane.get() || frame.width == 0 || frame.height == 0) {
std::cout << "[SimpleGPURenderer] Invalid frame data" << std::endl;
yUploadBuffer->Unmap(0, nullptr);
return E_FAIL;
}
if (SUCCEEDED(hr))
{
const uint8_t* srcY = frame.y_plane.get();
uint8_t* dstY = static_cast<uint8_t*>(yMappedData);
std::cout << "[SimpleGPURenderer] Copying Y data: " << frame.width << "x" << frame.height << std::endl;
for (uint32_t row = 0; row < frame.height; ++row)
{
memcpy(dstY + row * frame.width, srcY + row * frame.y_stride, frame.width);
}
m_yUploadBuffer->Unmap(0, nullptr);
yUploadBuffer->Unmap(0, nullptr);
}
// 2. Map and copy U data
void* uMappedData = nullptr;
hr = m_uUploadBuffer->Map(0, nullptr, &uMappedData);
hr = uUploadBuffer->Map(0, nullptr, &uMappedData);
if (SUCCEEDED(hr))
{
const uint8_t* srcU = frame.u_plane.get();
@@ -530,12 +732,12 @@ HRESULT SimpleGPURenderer::UpdateVideoTextures(const VideoFrame& frame)
memcpy(dstU + row * uvWidth, srcU + row * frame.u_stride, uvWidth);
}
m_uUploadBuffer->Unmap(0, nullptr);
uUploadBuffer->Unmap(0, nullptr);
}
// 3. Map and copy V data
void* vMappedData = nullptr;
hr = m_vUploadBuffer->Map(0, nullptr, &vMappedData);
hr = vUploadBuffer->Map(0, nullptr, &vMappedData);
if (SUCCEEDED(hr))
{
const uint8_t* srcV = frame.v_plane.get();
@@ -548,12 +750,12 @@ HRESULT SimpleGPURenderer::UpdateVideoTextures(const VideoFrame& frame)
memcpy(dstV + row * uvWidth, srcV + row * frame.v_stride, uvWidth);
}
m_vUploadBuffer->Unmap(0, nullptr);
vUploadBuffer->Unmap(0, nullptr);
}
// 4. Copy upload buffers to textures
D3D12_TEXTURE_COPY_LOCATION srcY = {};
srcY.pResource = m_yUploadBuffer.Get();
srcY.pResource = yUploadBuffer.Get();
srcY.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
srcY.PlacedFootprint.Offset = 0;
srcY.PlacedFootprint.Footprint.Format = DXGI_FORMAT_R8_UNORM;
@@ -563,67 +765,71 @@ HRESULT SimpleGPURenderer::UpdateVideoTextures(const VideoFrame& frame)
srcY.PlacedFootprint.Footprint.RowPitch = frame.width;
D3D12_TEXTURE_COPY_LOCATION dstY = {};
dstY.pResource = m_yTexture.Get();
dstY.pResource = yTexture.Get();
dstY.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
dstY.SubresourceIndex = 0;
m_commandList->CopyTextureRegion(&dstY, 0, 0, 0, &srcY, nullptr);
// Copy U texture
// Prepare U texture copy location
uint32_t uvWidth = frame.width / 2;
uint32_t uvHeight = frame.height / 2;
D3D12_TEXTURE_COPY_LOCATION srcU = srcY;
srcU.pResource = m_uUploadBuffer.Get();
srcU.pResource = uUploadBuffer.Get();
srcU.PlacedFootprint.Footprint.Width = uvWidth;
srcU.PlacedFootprint.Footprint.Height = uvHeight;
srcU.PlacedFootprint.Footprint.RowPitch = uvWidth;
D3D12_TEXTURE_COPY_LOCATION dstU = dstY;
dstU.pResource = m_uTexture.Get();
dstU.pResource = uTexture.Get();
m_commandList->CopyTextureRegion(&dstU, 0, 0, 0, &srcU, nullptr);
// Copy V texture
// Prepare V texture copy location
D3D12_TEXTURE_COPY_LOCATION srcV = srcU;
srcV.pResource = m_vUploadBuffer.Get();
srcV.pResource = vUploadBuffer.Get();
D3D12_TEXTURE_COPY_LOCATION dstV = dstU;
dstV.pResource = m_vTexture.Get();
dstV.pResource = vTexture.Get();
// 4.1. First transition textures to COPY_DEST state for writing
D3D12_RESOURCE_BARRIER beforeCopyBarriers[3] = {};
beforeCopyBarriers[0].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
beforeCopyBarriers[0].Transition.pResource = yTexture.Get();
beforeCopyBarriers[0].Transition.StateBefore = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
beforeCopyBarriers[0].Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST;
beforeCopyBarriers[1] = beforeCopyBarriers[0];
beforeCopyBarriers[1].Transition.pResource = uTexture.Get();
beforeCopyBarriers[2] = beforeCopyBarriers[0];
beforeCopyBarriers[2].Transition.pResource = vTexture.Get();
m_commandList->ResourceBarrier(3, beforeCopyBarriers);
// 4.2. Now perform all copy operations
m_commandList->CopyTextureRegion(&dstY, 0, 0, 0, &srcY, nullptr);
m_commandList->CopyTextureRegion(&dstU, 0, 0, 0, &srcU, nullptr);
m_commandList->CopyTextureRegion(&dstV, 0, 0, 0, &srcV, nullptr);
// 5. Transition textures to shader resource state
D3D12_RESOURCE_BARRIER barriers[3] = {};
barriers[0].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barriers[0].Transition.pResource = m_yTexture.Get();
barriers[0].Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
barriers[0].Transition.StateAfter = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
// 5. Transition textures back to shader resource state for reading
D3D12_RESOURCE_BARRIER afterCopyBarriers[3] = {};
afterCopyBarriers[0].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
afterCopyBarriers[0].Transition.pResource = yTexture.Get();
afterCopyBarriers[0].Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
afterCopyBarriers[0].Transition.StateAfter = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
barriers[1] = barriers[0];
barriers[1].Transition.pResource = m_uTexture.Get();
afterCopyBarriers[1] = afterCopyBarriers[0];
afterCopyBarriers[1].Transition.pResource = uTexture.Get();
barriers[2] = barriers[0];
barriers[2].Transition.pResource = m_vTexture.Get();
afterCopyBarriers[2] = afterCopyBarriers[0];
afterCopyBarriers[2].Transition.pResource = vTexture.Get();
m_commandList->ResourceBarrier(3, barriers);
m_commandList->ResourceBarrier(3, afterCopyBarriers);
// Execute commands
hr = m_commandList->Close();
if (FAILED(hr)) return hr;
ID3D12CommandList* commandLists[] = { m_commandList.Get() };
m_commandQueue->ExecuteCommandLists(1, commandLists);
// Wait for completion
hr = WaitForGPU();
if (FAILED(hr)) return hr;
std::cout << "[SimpleGPURenderer] YUV textures updated (" << frame.width << "x" << frame.height << ")" << std::endl;
return S_OK;
}
HRESULT SimpleGPURenderer::ExecuteComputeShader()
HRESULT SimpleGPURenderer::ExecuteComputeShaderInternal()
{
if (!m_computePipelineState || !m_computeRootSignature)
{
@@ -633,12 +839,7 @@ HRESULT SimpleGPURenderer::ExecuteComputeShader()
HRESULT hr = S_OK;
// Reset command allocator and list
hr = m_commandAllocators[m_frameIndex]->Reset();
if (FAILED(hr)) return hr;
hr = m_commandList->Reset(m_commandAllocators[m_frameIndex].Get(), nullptr);
if (FAILED(hr)) return hr;
// NOTE: Command allocator and list are reset in ExecuteGPUPipeline() - do not reset here
// Set compute pipeline state
m_commandList->SetComputeRootSignature(m_computeRootSignature.Get());
@@ -648,13 +849,16 @@ HRESULT SimpleGPURenderer::ExecuteComputeShader()
ID3D12DescriptorHeap* heaps[] = { m_srvUavHeap.Get() };
m_commandList->SetDescriptorHeaps(1, heaps);
// Bind Y, U, V textures as SRVs (Shader Resource Views)
// Bind current frame's Y, U, V textures as SRVs (Shader Resource Views)
CD3DX12_GPU_DESCRIPTOR_HANDLE srvHandle(m_srvUavHeap->GetGPUDescriptorHandleForHeapStart());
UINT descriptorSize = m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
// Offset to current frame's descriptors (frame 0: offset 0, frame 1: offset 4, frame 2: offset 8)
srvHandle.Offset(m_frameIndex * 4, descriptorSize);
m_commandList->SetComputeRootDescriptorTable(0, srvHandle); // Root parameter 0: SRV table
// Bind RGB texture as UAV (Unordered Access View)
// Bind current frame's RGB texture as UAV (Unordered Access View)
CD3DX12_GPU_DESCRIPTOR_HANDLE uavHandle = srvHandle;
uavHandle.Offset(3, m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV));
uavHandle.Offset(3, descriptorSize); // UAV is at offset +3 from frame's SRV base
m_commandList->SetComputeRootDescriptorTable(1, uavHandle); // Root parameter 1: UAV table
// Dispatch compute shader
@@ -666,65 +870,105 @@ HRESULT SimpleGPURenderer::ExecuteComputeShader()
m_commandList->Dispatch(dispatchX, dispatchY, dispatchZ);
// Add UAV barrier to ensure compute shader completes before next operation
CD3DX12_RESOURCE_BARRIER uavBarrier = CD3DX12_RESOURCE_BARRIER::UAV(m_rgbTexture.Get());
CD3DX12_RESOURCE_BARRIER uavBarrier = CD3DX12_RESOURCE_BARRIER::UAV(m_rgbTextures[m_frameIndex].Get());
m_commandList->ResourceBarrier(1, &uavBarrier);
// Close and execute command list
hr = m_commandList->Close();
if (FAILED(hr)) return hr;
ID3D12CommandList* commandLists[] = { m_commandList.Get() };
m_commandQueue->ExecuteCommandLists(1, commandLists);
std::cout << "[SimpleGPURenderer] Compute shader executed successfully ("
std::cout << "[SimpleGPURenderer] Compute shader commands recorded successfully ("
<< dispatchX << "x" << dispatchY << " thread groups)" << std::endl;
return S_OK;
}
HRESULT SimpleGPURenderer::CopyToBackBuffer()
HRESULT SimpleGPURenderer::CopyToBackBufferInternal()
{
if (!m_renderTargets[m_frameIndex])
// Get current back buffer index from swap chain (critical for proper synchronization)
UINT currentBackBufferIndex = m_swapChain->GetCurrentBackBufferIndex();
if (!m_renderTargets[currentBackBufferIndex])
return E_FAIL;
std::cout << "[SimpleGPURenderer] CopyToBackBuffer: frameIndex=" << m_frameIndex
<< ", backBufferIndex=" << currentBackBufferIndex << std::endl;
// For Phase 3 testing, we'll just clear the back buffer to a test color
// TODO: Implement actual RGB texture to back buffer copy
HRESULT hr = m_commandAllocators[m_frameIndex]->Reset();
if (FAILED(hr)) return hr;
// CRITICAL: Do NOT reset command allocator again - it was already reset in UpdateVideoTextures
// Just continue using the existing command list
HRESULT hr = S_OK;
hr = m_commandList->Reset(m_commandAllocators[m_frameIndex].Get(), nullptr);
if (FAILED(hr)) return hr;
// Transition back buffer to render target state
// Transition back buffer to render target state for clearing
D3D12_RESOURCE_BARRIER barrier = {};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Transition.pResource = m_renderTargets[m_frameIndex].Get();
barrier.Transition.pResource = m_renderTargets[currentBackBufferIndex].Get();
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
m_commandList->ResourceBarrier(1, &barrier);
// Clear back buffer to test color (green indicates GPU pipeline is working)
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = m_rtvHeap->GetCPUDescriptorHandleForHeapStart();
rtvHandle.ptr += m_frameIndex * m_rtvDescriptorSize;
// Clear back buffer to black background
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_rtvHeap->GetCPUDescriptorHandleForHeapStart(),
currentBackBufferIndex, m_rtvDescriptorSize);
const float clearColor[] = { 0.0f, 0.0f, 0.0f, 1.0f }; // Black background
m_commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
const float testColor[4] = { 0.0f, 0.5f, 0.0f, 1.0f }; // Green test color
m_commandList->ClearRenderTargetView(rtvHandle, testColor, 0, nullptr);
// Transition back buffer to copy dest state for texture copy
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST;
m_commandList->ResourceBarrier(1, &barrier);
// Copy RGB texture to back buffer using simple texture copy (no rendering pipeline needed)
// Simple approach: transition RGB texture to copy source and use CopyTextureRegion
// Transition current frame's RGB texture to copy source
D3D12_RESOURCE_BARRIER rgbBarrier = {};
rgbBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
rgbBarrier.Transition.pResource = m_rgbTextures[m_frameIndex].Get();
rgbBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
rgbBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE;
rgbBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
m_commandList->ResourceBarrier(1, &rgbBarrier);
// Copy RGB texture region to back buffer (handles format differences)
if (m_rgbTextures[m_frameIndex] && m_renderTargets[currentBackBufferIndex])
{
D3D12_TEXTURE_COPY_LOCATION src = {};
src.pResource = m_rgbTextures[m_frameIndex].Get();
src.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
src.SubresourceIndex = 0;
D3D12_TEXTURE_COPY_LOCATION dst = {};
dst.pResource = m_renderTargets[currentBackBufferIndex].Get();
dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
dst.SubresourceIndex = 0;
// Copy the minimum of the two texture sizes to prevent overrun
UINT copyWidth = (m_videoWidth < m_width) ? m_videoWidth : m_width;
UINT copyHeight = (m_videoHeight < m_height) ? m_videoHeight : m_height;
D3D12_BOX srcBox = {};
srcBox.left = 0;
srcBox.top = 0;
srcBox.right = copyWidth;
srcBox.bottom = copyHeight;
srcBox.front = 0;
srcBox.back = 1;
m_commandList->CopyTextureRegion(&dst, 0, 0, 0, &src, &srcBox);
}
// Transition RGB texture back to UAV for next frame
rgbBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_SOURCE;
rgbBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
m_commandList->ResourceBarrier(1, &rgbBarrier);
// Transition back to present state
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
m_commandList->ResourceBarrier(1, &barrier);
hr = m_commandList->Close();
if (FAILED(hr)) return hr;
// Execute commands
ID3D12CommandList* commandLists[] = { m_commandList.Get() };
m_commandQueue->ExecuteCommandLists(1, commandLists);
std::cout << "[SimpleGPURenderer] Back buffer cleared with test color" << std::endl;
std::cout << "[SimpleGPURenderer] RGB texture copy commands recorded ("
<< m_videoWidth << "x" << m_videoHeight << " -> " << m_width << "x" << m_height << ")" << std::endl;
return S_OK;
}
@@ -733,15 +977,12 @@ HRESULT SimpleGPURenderer::WaitForGPU()
if (!m_commandQueue || !m_fence || !m_fenceEvent)
return E_FAIL;
// Signal the fence with current value
const UINT64 fenceValue = m_fenceValues[m_frameIndex];
// Simple: signal and wait for current fence value
const UINT64 fenceValue = ++m_fenceValue;
HRESULT hr = m_commandQueue->Signal(m_fence.Get(), fenceValue);
if (FAILED(hr)) return hr;
// Increment fence value for next frame
m_fenceValues[m_frameIndex]++;
// Wait for fence completion
// Wait for completion
if (m_fence->GetCompletedValue() < fenceValue)
{
hr = m_fence->SetEventOnCompletion(fenceValue, m_fenceEvent);
@@ -753,6 +994,32 @@ HRESULT SimpleGPURenderer::WaitForGPU()
return S_OK;
}
void SimpleGPURenderer::WaitForFrameCompletion(UINT frameIndex)
{
if (!m_fence || !m_fenceEvent)
return;
UINT64 targetValue = m_frameCompletionValues[frameIndex];
std::cout << "[SimpleGPURenderer] WaitForFrameCompletion frame " << frameIndex
<< ": target=" << targetValue << ", current=" << m_fence->GetCompletedValue() << std::endl;
if (targetValue > 0)
{
// Always wait with timeout to prevent infinite hangs
while (m_fence->GetCompletedValue() < targetValue)
{
m_fence->SetEventOnCompletion(targetValue, m_fenceEvent);
DWORD result = WaitForSingleObject(m_fenceEvent, 1000); // 1 second timeout
if (result == WAIT_TIMEOUT)
{
std::cout << "[SimpleGPURenderer] Frame completion timeout for frame " << frameIndex
<< ", target: " << targetValue << ", current: " << m_fence->GetCompletedValue() << std::endl;
break; // Continue anyway to prevent infinite hang
}
}
}
}
HRESULT SimpleGPURenderer::CreateComputeRootSignature()
{
// Root signature for compute shader: 3 SRVs (Y,U,V) + 1 UAV (RGB) + 1 CBV (constants)
@@ -945,4 +1212,23 @@ HRESULT SimpleGPURenderer::CreateComputePipelineState()
return S_OK;
}
// Legacy public interface methods (for backward compatibility)
HRESULT SimpleGPURenderer::UpdateVideoTextures(const VideoFrame& frame)
{
std::cout << "[SimpleGPURenderer] Warning: Using legacy UpdateVideoTextures - use ExecuteGPUPipeline instead" << std::endl;
return UpdateVideoTexturesInternal(frame);
}
HRESULT SimpleGPURenderer::ExecuteComputeShader()
{
std::cout << "[SimpleGPURenderer] Warning: Using legacy ExecuteComputeShader - use ExecuteGPUPipeline instead" << std::endl;
return ExecuteComputeShaderInternal();
}
HRESULT SimpleGPURenderer::CopyToBackBuffer()
{
std::cout << "[SimpleGPURenderer] Warning: Using legacy CopyToBackBuffer - use ExecuteGPUPipeline instead" << std::endl;
return CopyToBackBufferInternal();
}
} // namespace Vav2Player

View File

@@ -29,6 +29,7 @@ public:
// Video rendering
HRESULT RenderVideoFrame(const VideoFrame& frame);
bool TryRenderFrame(const VideoFrame& frame); // Returns true if successful
HRESULT Present();
// Size management
@@ -43,16 +44,17 @@ private:
ComPtr<IDXGISwapChain3> m_swapChain;
ComPtr<ID3D12DescriptorHeap> m_rtvHeap;
// Command objects
static const UINT FrameCount = 2;
// Command objects - Triple buffering
static const UINT FrameCount = 3;
ComPtr<ID3D12CommandAllocator> m_commandAllocators[FrameCount];
ComPtr<ID3D12GraphicsCommandList> m_commandList;
ComPtr<ID3D12Resource> m_renderTargets[FrameCount];
UINT m_frameIndex;
// Synchronization
// Simple synchronization
ComPtr<ID3D12Fence> m_fence;
UINT64 m_fenceValues[FrameCount];
UINT64 m_fenceValue; // Single incrementing counter
UINT64 m_frameCompletionValues[FrameCount]; // Per-frame completion tracking
HANDLE m_fenceEvent;
// YUV-to-RGB compute shader resources
@@ -61,16 +63,16 @@ private:
ComPtr<ID3D12DescriptorHeap> m_srvUavHeap;
ComPtr<ID3DBlob> m_computeShaderBlob;
// Video textures
ComPtr<ID3D12Resource> m_yTexture;
ComPtr<ID3D12Resource> m_uTexture;
ComPtr<ID3D12Resource> m_vTexture;
ComPtr<ID3D12Resource> m_rgbTexture;
// Video textures - Triple buffered for proper synchronization
ComPtr<ID3D12Resource> m_yTextures[FrameCount];
ComPtr<ID3D12Resource> m_uTextures[FrameCount];
ComPtr<ID3D12Resource> m_vTextures[FrameCount];
ComPtr<ID3D12Resource> m_rgbTextures[FrameCount];
// Upload resources for CPU->GPU transfer
ComPtr<ID3D12Resource> m_yUploadBuffer;
ComPtr<ID3D12Resource> m_uUploadBuffer;
ComPtr<ID3D12Resource> m_vUploadBuffer;
// Upload resources for CPU->GPU transfer - Triple buffered
ComPtr<ID3D12Resource> m_yUploadBuffers[FrameCount];
ComPtr<ID3D12Resource> m_uUploadBuffers[FrameCount];
ComPtr<ID3D12Resource> m_vUploadBuffers[FrameCount];
// Constant buffer for shader
ComPtr<ID3D12Resource> m_constantBuffer;
@@ -93,10 +95,17 @@ private:
HRESULT CreateSynchronizationObjects();
HRESULT CreateComputeShaderResources();
HRESULT CreateVideoTextures(uint32_t videoWidth, uint32_t videoHeight);
HRESULT UpdateVideoTextures(const VideoFrame& frame);
HRESULT ExecuteComputeShader();
HRESULT CopyToBackBuffer();
HRESULT ExecuteGPUPipeline(const VideoFrame& frame); // Consolidated GPU pipeline
HRESULT UpdateVideoTextures(const VideoFrame& frame); // Legacy public interface
HRESULT ExecuteComputeShader(); // Legacy public interface
HRESULT CopyToBackBuffer(); // Legacy public interface
// Internal methods (no command list management)
HRESULT UpdateVideoTexturesInternal(const VideoFrame& frame);
HRESULT ExecuteComputeShaderInternal();
HRESULT CopyToBackBufferInternal();
HRESULT WaitForGPU();
void WaitForFrameCompletion(UINT frameIndex); // Simple frame-specific wait
// Shader compilation
HRESULT CompileComputeShader();