//----------------------------------------------------------------------------------
// File:        CDx12SwapChain.cpp
// SDK Version: 1.0.2
//
// SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: LicenseRef-NvidiaProprietary
//
// NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
// property and proprietary rights in and to this material, related
// documentation and any modifications thereto. Any use, reproduction,
// disclosure or distribution of this material and related documentation
// without an express license agreement from NVIDIA CORPORATION or
// its affiliates is strictly prohibited.
//
//----------------------------------------------------------------------------------

///////////////////////////////////////////
// CDx12SwapChain.cpp
//
// Implementation of DX12 SwapChain wrapping a DXGI SwapChain
//

#include <d3d12.h>

#include "CDX1x_Common.h"
#include "CDx12Sync.h"
#include "CDx12SwapChain.h"
#include "d3dx12.h"


//////////////////////////////////////////////////////////////////////////////////////
CDx12SwapChain::CDx12SwapChain()
    : m_NumBackBuffers(0)
    , m_CurBackBufferIndex(0)
    , m_BackBufferSyncObj(nullptr)
    , m_AllocatorSyncObj(nullptr)
    , m_StagingSyncObj(nullptr)
    , m_gpuIndex(0)                 // swapchain is always on primary
    , m_gpuMask(1)                  // 1<<0
{
    LARGE_INTEGER freq;
    QueryPerformanceFrequency(&freq);
    m_freq = freq.QuadPart;
    m_prevtime.QuadPart = 0;

    InitializeCriticalSection(&m_csStagingBuffer);      // with -LDA -pip, prevent staging buffer deleted while in use on <alt><enter>
}

//////////////////////////////////////////////////////////////////////////////////////
CDx12SwapChain::~CDx12SwapChain()
{
    if (m_pSwapChain)
    {
        m_pSwapChain->SetFullscreenState(false, NULL);
    }

    // need to do cpu wait last fence on staging buffer use before destroying the staging buffer.
    if (m_StagingSyncObj)
    {
        m_StagingSyncObj->WaitForCPUFence();
    }

    SafeDelete(m_BackBufferSyncObj);
    SafeDelete(m_StagingSyncObj);
    SafeDelete(m_AllocatorSyncObj);

    SafeRelease(m_StagingBuffer);
    SafeRelease(m_commandAllocator);
    SafeRelease(m_commandList);
    SafeRelease(m_commandQueue);
    SafeRelease(m_pSwapChain);
    SafeRelease(m_pD3D12Device);

    DeleteCriticalSection(&m_csStagingBuffer);
}

//////////////////////////////////////////////////////////////////////////////////////
HRESULT CDx12SwapChain::Init(ID3D12Device*pD3D12Device, HWND hwnd, UINT WidthFS, UINT HeightFS,
                                DXGI_FORMAT dispFormat, UINT backBufferCount,
                                UINT gpuStagingMask, UINT gpuVisibleMask)
{
    HRESULT hr = S_OK;
    m_uWidthFS = WidthFS;
    m_uHeightFS = HeightFS;
    SetRect(&m_rcFullScreen, 0, 0, m_uWidthFS, m_uHeightFS);
    m_DisplayFormat = dispFormat;
    m_hWndDisplay = hwnd;
    m_NumBackBuffers = backBufferCount;

    m_gpuStagingMask = gpuStagingMask;
    m_gpuVisibleMask = gpuVisibleMask;

    // Describe and create the command queue.
    D3D12_COMMAND_QUEUE_DESC queueDesc = { D3D12_COMMAND_LIST_TYPE_DIRECT, 0, D3D12_COMMAND_QUEUE_FLAG_NONE, m_gpuMask};
    hr = pD3D12Device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&m_commandQueue));
    if (FAILED(hr)) return hr;

    // SwapChain back buffer can only be written to by its 3d commandQueue
    m_pD3D12Device = pD3D12Device;
    m_pD3D12Device->AddRef();
    hr = pD3D12Device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&m_commandAllocator));
    if (FAILED(hr)) return hr;
    hr = pD3D12Device->CreateCommandList(m_gpuMask, D3D12_COMMAND_LIST_TYPE_DIRECT, m_commandAllocator, nullptr, IID_PPV_ARGS(&m_commandList));
    if (FAILED(hr)) return hr;
    // Close the command list and execute it
    hr = m_commandList->Close();
    if (FAILED(hr)) return hr;

    // Create allocator sync obj
    m_AllocatorSyncObj = new CDx12SyncObject(pD3D12Device);

    // Describe and create the swap chain.
    DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
    swapChainDesc.BufferCount = m_NumBackBuffers;
    swapChainDesc.Width = m_uWidthFS;
    swapChainDesc.Height = m_uHeightFS;
    swapChainDesc.Format = m_DisplayFormat;
    swapChainDesc.SampleDesc.Count = 1;
    swapChainDesc.BufferUsage = DXGI_USAGE_BACK_BUFFER | DXGI_USAGE_RENDER_TARGET_OUTPUT;
    swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;

    if (hwnd)
    {
        IDXGIFactory5* factory = nullptr;
        hr = CreateDXGIFactory2(0, IID_PPV_ARGS(&factory));
        if (FAILED(hr)) return hr;
        IDXGISwapChain1* pSwapChain = nullptr;
        hr = factory->CreateSwapChainForHwnd(m_commandQueue, hwnd, &swapChainDesc, nullptr, nullptr, &pSwapChain);
        if (FAILED(hr)) return hr;

        hr = pSwapChain->QueryInterface(IID_PPV_ARGS(&m_pSwapChain));
        if (FAILED(hr)) return hr;
        SafeRelease(pSwapChain);
        SafeRelease(factory);
    }


    // get a sync obj for each of the back buffers
    m_BackBufferSyncObj = new CDx12SyncObjectArray(pD3D12Device, m_NumBackBuffers);

    // Create stagingBuffer sync obj
    m_StagingSyncObj = new CDx12SyncObject(pD3D12Device);

    {
        D3D12_HEAP_FLAGS     heapFlags = D3D12_HEAP_FLAG_NONE;
        D3D12_RESOURCE_DESC textureDesc = CD3DX12_RESOURCE_DESC::Tex2D(m_DisplayFormat, m_uWidthFS, m_uHeightFS, 1, 1, 1, 0, D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS);
        CD3DX12_HEAP_PROPERTIES heapProps = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT, m_gpuStagingMask, m_gpuVisibleMask);
        if (m_gpuStagingMask != m_gpuMask)      // create in sys mem
        {
            heapProps = CD3DX12_HEAP_PROPERTIES(D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE, D3D12_MEMORY_POOL_L0, m_gpuStagingMask, m_gpuVisibleMask);
        }
        hr = m_pD3D12Device->CreateCommittedResource(
            &heapProps,
            heapFlags,
            &textureDesc,
            D3D12_RESOURCE_STATE_COMMON,
            nullptr,
            IID_PPV_ARGS(&m_StagingBuffer));
        if (FAILED(hr)) return hr;
    }

    return S_OK;
}


//////////////////////////////////////////////////////////////////////////////////////
HRESULT CDx12SwapChain::GetBuffer(ID3D12Resource** ppBuffer, CDx12SyncObject** ppSyncObj)
{
    CAutoCS autoCS(m_csStagingBuffer);
    *ppSyncObj = m_StagingSyncObj;

    *ppBuffer = m_StagingBuffer;
    return S_OK;
}

//////////////////////////////////////////////////////////////////////////////////////
// SwapChain back buffer can only be written to by its 3d commandQueue
HRESULT CDx12SwapChain::UpdateBackBufferFromStaging()
{
    CAutoCS autoCS(m_csStagingBuffer);
    HRESULT hr = S_OK;

    if (m_pSwapChain)
    {
        // get current back buffer
        m_CurBackBufferIndex = m_pSwapChain->GetCurrentBackBufferIndex();
        ID3D12Resource* pBackBuffer;
        hr = m_pSwapChain->GetBuffer(m_CurBackBufferIndex, IID_PPV_ARGS(&pBackBuffer));
        if (FAILED(hr)) return hr;

        // make sure the source is ready
        m_StagingSyncObj->WaitForFence(m_commandQueue);
        // make sure the dest is ready
        m_BackBufferSyncObj->WaitForFenceIndexed(m_commandQueue, m_CurBackBufferIndex);

        // make sure the allocator is ready
        m_AllocatorSyncObj->SignalFence(m_commandQueue);
        m_AllocatorSyncObj->WaitForCPUFence();
        // reset list
        hr = m_commandAllocator->Reset();
        if (FAILED(hr)) return hr;
        hr = m_commandList->Reset(m_commandAllocator, nullptr);
        if (FAILED(hr)) return hr;

        CD3DX12_RESOURCE_BARRIER transition[2];
        // transistion to copy
        transition[0] = CD3DX12_RESOURCE_BARRIER::Transition(m_StagingBuffer, D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_SOURCE, 0);
        transition[1] = CD3DX12_RESOURCE_BARRIER::Transition(pBackBuffer, D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_COPY_DEST, 0);
        m_commandList->ResourceBarrier(2, transition);

        // do copy
        m_commandList->CopyResource(pBackBuffer, m_StagingBuffer);
        // transition from copy
        transition[0] = CD3DX12_RESOURCE_BARRIER::Transition(m_StagingBuffer, D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_COMMON, 0);
        transition[1] = CD3DX12_RESOURCE_BARRIER::Transition(pBackBuffer, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PRESENT, 0);
        m_commandList->ResourceBarrier(2, transition);

        // Close the command list
        hr = m_commandList->Close();
        if (FAILED(hr)) return hr;

        // Execute the command list.
        ID3D12CommandList* ppCommandLists[] = { m_commandList };
        m_commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);

        // signal for both source and dest that execution is done
        m_StagingSyncObj->SignalFence(m_commandQueue);
        m_BackBufferSyncObj->SignalFenceIndexed(m_commandQueue, m_CurBackBufferIndex);
        SafeRelease(pBackBuffer);
    }

    return hr;
}

//////////////////////////////////////////////////////////////////////////////////////
DXGI_COLOR_SPACE_TYPE CDx12SwapChain::UpdateSwapChainColorSpace(CDx1xCommon* pCDx1xCommon, bool bSupportHDR)
{
    return pCDx1xCommon->UpdateSwapChainColorSpace(m_hWndDisplay, m_pSwapChain, m_DisplayFormat, bSupportHDR);
}


//////////////////////////////////////////////////////////////////////////////////////
HRESULT CDx12SwapChain::Present()
{
    HRESULT hr = S_OK;
    if (m_pSwapChain)
    {
        hr = UpdateBackBufferFromStaging();
        if (SUCCEEDED(hr))
        {
            hr = m_pSwapChain->Present(1, 0);
        }
        if (SUCCEEDED(hr))
        {
            m_BackBufferSyncObj->SignalFenceIndexed(m_commandQueue, m_CurBackBufferIndex);
        }
    }
    return hr;
}


//////////////////////////////////////////////////////////////////////////////////////
HRESULT GetAdapter(IDXGIFactory5* pFactory, IDXGIAdapter3** ppAdapter)
{
    IDXGIAdapter1* adapter;
    *ppAdapter = nullptr;

    for (UINT adapterIndex = 0; DXGI_ERROR_NOT_FOUND != pFactory->EnumAdapters1(adapterIndex, &adapter); ++adapterIndex)
    {
        DXGI_ADAPTER_DESC1 desc;
        adapter->GetDesc1(&desc);

        if ((desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) || (desc.VendorId != 0x10de))
        {
            adapter->Release();
            continue;
        }

        // Check to see if the adapter supports Direct3D 12, but don't create the
        // actual device yet.
        if (SUCCEEDED(D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr)))
        {
            break;
        }
    }
    if (!adapter)
        return E_NOINTERFACE;
    HRESULT hr = adapter->QueryInterface(IID_PPV_ARGS(ppAdapter));
    return hr;
}


