// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.

#if GRAPHICS_API_DIRECTX12

#include "QueryHeapDX12.h"
#include "GPUDeviceDX12.h"
#include "GPUContextDX12.h"
#include "../RenderToolsDX.h"

QueryHeapDX12::QueryHeapDX12(GPUDeviceDX12* device, const D3D12_QUERY_HEAP_TYPE& queryHeapType, int32 queryHeapCount)
    : _device(device)
    , _queryHeap(nullptr)
    , _resultBuffer(nullptr)
    , _queryHeapType(queryHeapType)
    , _currentIndex(0)
    , _queryHeapCount(queryHeapCount)
{
    if (queryHeapType == D3D12_QUERY_HEAP_TYPE_OCCLUSION)
    {
        _resultSize = sizeof(uint64);
        _queryType = D3D12_QUERY_TYPE_OCCLUSION;
    }
    else if (queryHeapType == D3D12_QUERY_HEAP_TYPE_TIMESTAMP)
    {
        _resultSize = sizeof(uint64);
        _queryType = D3D12_QUERY_TYPE_TIMESTAMP;
    }
    else
    {
        MISSING_CODE("Not support D3D12 query heap type.");
    }
}

bool QueryHeapDX12::Init()
{
    _resultData.Resize(_resultSize * _queryHeapCount);

    // Create the query heap
    D3D12_QUERY_HEAP_DESC heapDesc;
    heapDesc.Type = _queryHeapType;
    heapDesc.Count = _queryHeapCount;
    heapDesc.NodeMask = 0;
    HRESULT result = _device->GetDevice()->CreateQueryHeap(&heapDesc, IID_PPV_ARGS(&_queryHeap));
    LOG_DIRECTX_RESULT_WITH_RETURN(result, true);
    DX_SET_DEBUG_NAME(_queryHeap, "Query Heap");

    // Create the result buffer
    D3D12_HEAP_PROPERTIES heapProperties;
    heapProperties.Type = D3D12_HEAP_TYPE_READBACK;
    heapProperties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
    heapProperties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
    heapProperties.CreationNodeMask = 1;
    heapProperties.VisibleNodeMask = 1;
    D3D12_RESOURCE_DESC resourceDesc;
    resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
    resourceDesc.Alignment = 0;
    resourceDesc.Width = _resultData.Count();
    resourceDesc.Height = 1;
    resourceDesc.DepthOrArraySize = 1;
    resourceDesc.MipLevels = 1;
    resourceDesc.Format = DXGI_FORMAT_UNKNOWN;
    resourceDesc.SampleDesc.Count = 1;
    resourceDesc.SampleDesc.Quality = 0;
    resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
    resourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
    result = _device->GetDevice()->CreateCommittedResource(&heapProperties, D3D12_HEAP_FLAG_NONE, &resourceDesc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&_resultBuffer));
    LOG_DIRECTX_RESULT_WITH_RETURN(result, true);
    DX_SET_DEBUG_NAME(_resultBuffer, "Query Heap Result Buffer");

    // Start out with an open query batch
    _currentBatch.Open = false;
    StartQueryBatch();

    return false;
}

void QueryHeapDX12::Destroy()
{
    SAFE_RELEASE(_resultBuffer);
    SAFE_RELEASE(_queryHeap);
    _currentBatch.Clear();
    _resultData.SetCapacity(0);
}

void QueryHeapDX12::EndQueryBatchAndResolveQueryData(GPUContextDX12* context)
{
    ASSERT(_currentBatch.Open);
    if (_currentBatch.Count == 0)
        return;

    // Close the current batch
    _currentBatch.Open = false;

    // Resolve the batch
    const int32 offset = _currentBatch.Start * _resultSize;
    context->GetCommandList()->ResolveQueryData(_queryHeap, _queryType, _currentBatch.Start, _currentBatch.Count, _resultBuffer, offset);
    _currentBatch.Sync = _device->GetCommandQueue()->GetSyncPoint();

    // Begin a new query batch
    _batches.Add(_currentBatch);
    StartQueryBatch();
}

void QueryHeapDX12::AllocQuery(GPUContextDX12* context, ElementHandle& handle)
{
    ASSERT(_currentBatch.Open);

    // Check if need to start from the buffer head
    if (_currentIndex >= GetQueryHeapCount())
    {
        // We're in the middle of a batch, but we're at the end of the heap so split the batch in two
        EndQueryBatchAndResolveQueryData(context);
    }

    // Allocate element into the current batch
    handle = _currentIndex++;
    _currentBatch.Count++;
}

void QueryHeapDX12::BeginQuery(GPUContextDX12* context, ElementHandle& handle)
{
    AllocQuery(context, handle);

    context->GetCommandList()->BeginQuery(_queryHeap, _queryType, handle);
}

void QueryHeapDX12::EndQuery(GPUContextDX12* context, ElementHandle& handle)
{
    AllocQuery(context, handle);

    context->GetCommandList()->EndQuery(_queryHeap, _queryType, handle);
}

bool QueryHeapDX12::IsReady(ElementHandle& handle)
{
    // Current batch is not ready (not ended)
    if (_currentBatch.ContainsElement(handle))
        return false;

    for (int32 i = 0; i < _batches.Count(); i++)
    {
        auto& batch = _batches[i];
        if (batch.ContainsElement(handle))
        {
            ASSERT(batch.Sync.IsValid());
            return batch.Sync.IsComplete();
        }
    }

    return true;
}

void* QueryHeapDX12::ResolveQuery(ElementHandle& handle)
{
    // Prevent queries from the current batch
    ASSERT(!_currentBatch.ContainsElement(handle));

    // Find the batch that contains this element to resolve it
    for (int32 i = 0; i < _batches.Count(); i++)
    {
        auto& batch = _batches[i];
        if (batch.ContainsElement(handle))
        {
            ASSERT(batch.Sync.IsValid());

            // Ensure that end point has been already executed
            if (!batch.Sync.IsComplete())
            {
                if (batch.Sync.IsOpen())
                {
                    // The query is on a command list that hasn't been submitted yet
                    LOG(Warning, "Stalling the rendering and flushing GPU commands to wait for a query that hasn't been submitted to the GPU yet.");
                    _device->WaitForGPU();
                }

                batch.Sync.WaitForCompletion();
            }

            // Map the query values readback buffer
            D3D12_RANGE range;
            range.Begin = batch.Start * _resultSize;
            range.End = range.Begin + batch.Count * _resultSize;
            void* mapped = nullptr;
            VALIDATE_DIRECTX_CALL(_resultBuffer->Map(0, &range, &mapped));

            // Copy the results data
            Platform::MemoryCopy(_resultData.Get() + range.Begin, (byte*)mapped + range.Begin, batch.Count * _resultSize);

            // Unmap with an empty range to indicate nothing was written by the CPU
            _resultBuffer->Unmap(0, nullptr);

            // All elements got its results so we can remove this batch
            _batches.RemoveAt(i);

            break;
        }
    }

    return _resultData.Get() + handle * _resultSize;
}

void QueryHeapDX12::StartQueryBatch()
{
    ASSERT(!_currentBatch.Open);

    // Clear the current batch
    _currentBatch.Clear();

    // Loop active index on overflow
    if (_currentIndex >= GetQueryHeapCount())
    {
        _currentIndex = 0;
    }

    // Start a new batch
    _currentBatch.Start = _currentIndex;
    _currentBatch.Open = true;
}

#endif
