From 47ccea3ad2c34fc592246b59fda5594847bc0518 Mon Sep 17 00:00:00 2001 From: ened Date: Tue, 23 Sep 2025 00:31:16 +0900 Subject: [PATCH] Simple GPU pipeline implementation --- .../Vav2Player/VideoPlayerControl.xaml | 4 +- .../Vav2Player/VideoPlayerControl.xaml.cpp | 70 +- .../src/Rendering/SimpleGPURenderer.cpp | 608 +++++++++++++----- .../src/Rendering/SimpleGPURenderer.h | 41 +- 4 files changed, 518 insertions(+), 205 deletions(-) diff --git a/vav2/Vav2Player/Vav2Player/VideoPlayerControl.xaml b/vav2/Vav2Player/Vav2Player/VideoPlayerControl.xaml index fe169e1..a715bb3 100644 --- a/vav2/Vav2Player/Vav2Player/VideoPlayerControl.xaml +++ b/vav2/Vav2Player/Vav2Player/VideoPlayerControl.xaml @@ -21,8 +21,8 @@ + HorizontalAlignment="Center" + VerticalAlignment="Center"/> 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(); + } + + // 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()); } diff --git a/vav2/Vav2Player/Vav2Player/src/Rendering/SimpleGPURenderer.cpp b/vav2/Vav2Player/Vav2Player/src/Rendering/SimpleGPURenderer.cpp index 82134cf..e31db96 100644 --- a/vav2/Vav2Player/Vav2Player/src/Rendering/SimpleGPURenderer.cpp +++ b/vav2/Vav2Player/Vav2Player/src/Rendering/SimpleGPURenderer.cpp @@ -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(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 \ No newline at end of file diff --git a/vav2/Vav2Player/Vav2Player/src/Rendering/SimpleGPURenderer.h b/vav2/Vav2Player/Vav2Player/src/Rendering/SimpleGPURenderer.h index 0605b24..c4c2754 100644 --- a/vav2/Vav2Player/Vav2Player/src/Rendering/SimpleGPURenderer.h +++ b/vav2/Vav2Player/Vav2Player/src/Rendering/SimpleGPURenderer.h @@ -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 m_swapChain; ComPtr m_rtvHeap; - // Command objects - static const UINT FrameCount = 2; + // Command objects - Triple buffering + static const UINT FrameCount = 3; ComPtr m_commandAllocators[FrameCount]; ComPtr m_commandList; ComPtr m_renderTargets[FrameCount]; UINT m_frameIndex; - // Synchronization + // Simple synchronization ComPtr 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 m_srvUavHeap; ComPtr m_computeShaderBlob; - // Video textures - ComPtr m_yTexture; - ComPtr m_uTexture; - ComPtr m_vTexture; - ComPtr m_rgbTexture; + // Video textures - Triple buffered for proper synchronization + ComPtr m_yTextures[FrameCount]; + ComPtr m_uTextures[FrameCount]; + ComPtr m_vTextures[FrameCount]; + ComPtr m_rgbTextures[FrameCount]; - // Upload resources for CPU->GPU transfer - ComPtr m_yUploadBuffer; - ComPtr m_uUploadBuffer; - ComPtr m_vUploadBuffer; + // Upload resources for CPU->GPU transfer - Triple buffered + ComPtr m_yUploadBuffers[FrameCount]; + ComPtr m_uUploadBuffers[FrameCount]; + ComPtr m_vUploadBuffers[FrameCount]; // Constant buffer for shader ComPtr 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();