|
|
|
|
@@ -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
|