//----------------------------------------------------------------------------------
// File:        CDX12TrueHDRDemo.cpp
// SDK Version: 1.0.2
//
// SPDX-FileCopyrightText: Copyright (c) 2023-2024 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.
//
//----------------------------------------------------------------------------------

///////////////////////////////////////////
// CDX12TrueHDRDemo.cpp
//
// This is an example of a DX12 class using the NGX wrapper class for TrueHDR
//
//
// This is an example of a DX12 class using the NGX wrapper class for TrueHDR.
// It initializes DX12 video devices and sync objects, creates video processors, and swap chains.
// The swap chains are created in the HDR format and in a standard ARGB format.
// It then reads uncompressed YUV (typically NV12) from a file.
// For TrueHDR it does a VpBlt from YUV to ARGB, 
//    then uses TrueHDR to go from ARGB to the HDR swap chain back buffer.
// For the comparison window, it does a VpBlt from YUV to the ARGB swap chain back buffer.
//


#include <iostream>
#include "d3dx12.h"

#include "CDX12TrueHDRDemo.h"
#include "CVideoFile.h"
#include "CDX1x_Common.h"

#include "CDx12NGXTrueHDR.h"


CDX12TrueHDRDemo::CDX12TrueHDRDemo()
{
}

CDX12TrueHDRDemo::~CDX12TrueHDRDemo()
{
    Shutdown();
}

void CDX12TrueHDRDemo::Shutdown()
{
    if (m_pDstSyncObj && m_commandQueue)
    {
        m_pDstSyncObj->SignalFence(m_commandQueue);
        m_pDstSyncObj->WaitForCPUFence();
    }
    SafeDelete(m_pDx12SwapChain);
    SafeDelete(m_pDx12SwapChainAlt);
    if (m_CNGXTrueHDR)
    {
        m_CNGXTrueHDR->ReleaseFeature();
        SafeDelete(m_CNGXTrueHDR);
    }
    SafeDelete(m_pNGXInSyncObj);
    SafeRelease(m_commandQueue);
    SafeDelete(m_AllocatorSyncObj);
    SafeDelete(m_pDstSyncObj);
    SafeRelease(m_pDstTexture);
    SafeRelease(m_commandList);
    SafeRelease(m_commandAllocator);
    SafeRelease(m_pD3D12VideoProcessor);
    SafeRelease(m_pD3D12VideoProcessorSwapBuffer);
    SafeDelete(m_pCDx12Api);

    CStreamData12* pStreamData = &m_StreamData;
    {
        SafeRelease(pStreamData->pTexture);
        SafeRelease(pStreamData->pBuffer);
        SafeDelete(pStreamData->pSyncObject);
        SafeDelete(pStreamData->pVideoFile);
    }
}

HRESULT CDX12TrueHDRDemo::Init(HWND hWndDisplay, HWND hWndDisplayAlt, Configuration_s* pConfiguration)
{
    HRESULT hr = S_OK;
    if (pConfiguration->bMonitorIsNotNV)
    {
        // swap chain creation is an issue as it needs items created by the dx12 device
        fwprintf(stdout, L"-dx12 on this app only supports display on NVIDIA adapter. Exiting\n");
        return E_FAIL;
    }
    m_CDx1xCommon.SetWindowOnNonNV(pConfiguration->bMonitorIsNotNV);

    m_pCDx12Api = new CDx12Api;
    if (!m_pCDx12Api) return E_OUTOFMEMORY;

    hr = m_pCDx12Api->Init(&m_CDx1xCommon, 0, hWndDisplay);
    if (FAILED(hr)) return hr;

    if (!m_CDx1xCommon.IsMonitorHDR())
    {
        fwprintf(stdout, L"Monitor is not in HDR mode. Exiting\n");
        return E_NOT_VALID_STATE;
    }

    m_Configuration = *pConfiguration;

    D3D12_VIDEO_PROCESS_INPUT_STREAM_DESC VPInputDesc = {};
    hr = SetupStreamData(&VPInputDesc, pConfiguration->pSzInputFile);
    if (FAILED(hr)) return hr;

    pConfiguration->WidthInUse = m_OutWidth;
    pConfiguration->HeightInUse = m_OutHeight;

    ID3D12Device* pD3D12Device = m_pCDx12Api->GetD3D12Device();
    {
        // Describe and create the command queue.
        D3D12_COMMAND_QUEUE_DESC queueDesc = {};
        queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
        queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;

        hr = pD3D12Device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&m_commandQueue));
        if (FAILED(hr)) return hr;
    }

    hr = pD3D12Device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&m_commandAllocator));
    if (FAILED(hr)) return hr;

    hr = pD3D12Device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_commandAllocator, nullptr, IID_PPV_ARGS(&m_commandList));
    if (FAILED(hr)) return hr;

    hr = m_commandList->Close();
    if (FAILED(hr)) return hr;

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

    // make sure the allocator is ready
    m_AllocatorSyncObj->SignalFence(m_commandQueue);
    m_AllocatorSyncObj->WaitForCPUFence();

    // update the nextFrame, which will use the command list to copy from upload buffer to texture
    hr = m_commandAllocator->Reset();
    if (FAILED(hr)) return hr;
    hr = m_commandList->Reset(m_commandAllocator, nullptr);
    if (FAILED(hr)) return hr;

    do
    {
        // Setup the input stream surfaces
        CStreamData12* pStreamData = &m_StreamData;
        D3D12_VIDEO_PROCESS_INPUT_STREAM_ARGUMENTS1* pVPInputStream = &m_VPInputStream;
        {
            hr = m_pCDx12Api->CreateVideoTexture(pStreamData->Width,
                pStreamData->Height,
                pStreamData->StreamFormat,
                true,
                false,
                true,
                &pStreamData->nBufferSize,
                &pStreamData->pTexture,
                1, 1,
                false,
                &pStreamData->StreamState);
            if (FAILED(hr)) break;

            if (pStreamData->bSplitChroma)
            {
                pStreamData->nLumaBufferSize = ROUNDUP(pStreamData->nBufferSize, 512);
                pStreamData->nChromaBufferSize = ROUNDUP((pStreamData->nLumaBufferSize / 2), 512);
                pStreamData->nBufferSize = pStreamData->nLumaBufferSize + pStreamData->nChromaBufferSize;
            }
            hr = m_pCDx12Api->CreateVideoTexture(pStreamData->nBufferSize,
                1,
                DXGI_FORMAT_UNKNOWN,
                false,
                true,
                false,
                nullptr,
                &pStreamData->pBuffer);
            if (FAILED(hr)) return hr;

            pVPInputStream->InputStream[0].pTexture2D = pStreamData->pTexture;
            pVPInputStream->InputStream[0].Subresource = 0;

            pStreamData->pSyncObject = new CDx12SyncObject(m_pCDx12Api->GetD3D12Device());
        }
        if (FAILED(hr)) break;

        // setup the vp output surfaces
        hr = m_pCDx12Api->CreateVideoTexture(m_DstWidth,
            m_DstHeight,
            m_DstFormat,
            true,
            false,
            false,
            nullptr,
            &m_pDstTexture,
            1, 1,
            false,
            &m_DstState);
        if (FAILED(hr)) break;

        m_VPOutputStream.OutputStream[0].pTexture2D = m_pDstTexture;
        m_VPOutputStream.OutputStream[0].Subresource = 0;
        SetRect(&m_VPOutputStream.TargetRectangle, 0, 0, m_DstWidth, m_DstHeight);

    } while (0);
    if (FAILED(hr)) return hr;

    {
        CD3DX12_RESOURCE_BARRIER barrier =
            CD3DX12_RESOURCE_BARRIER::Transition(m_VPOutputStream.OutputStream[0].pTexture2D,
                m_DstState,
                D3D12_RESOURCE_STATE_COMMON,
                m_VPOutputStream.OutputStream[0].Subresource);
        m_commandList->ResourceBarrier(1, &barrier);
    }
    m_DstState = D3D12_RESOURCE_STATE_COMMON;

    // Close the command list and execute it to begin the initial GPU setup.
    hr = m_commandList->Close();
    if (FAILED(hr)) return hr;
    ID3D12CommandList* ppCommandLists[] = { m_commandList };
    m_commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);

    hr = m_pCDx12Api->VpInit(m_StreamCount, &VPInputDesc,
                                m_DstWidth, m_DstHeight, m_DstFormat, m_DstColorSpace,
                                m_BackgroundColor, &m_pD3D12VideoProcessor);
    if (FAILED(hr)) return hr;

    hr = m_pCDx12Api->VpInit(m_StreamCount, &VPInputDesc,
                                m_OutWidth, m_OutHeight, m_OutFormat, m_OutColorSpace,
                                m_BackgroundColor, &m_pD3D12VideoProcessorSwapBuffer);
    if (FAILED(hr)) return hr;

    m_pDx12SwapChain = new CDx12SwapChain;
    hr = m_pDx12SwapChain->Init(pD3D12Device, hWndDisplay, m_OutWidth, m_OutHeight, m_OutFormat, 16, 1, 1);
    if (FAILED(hr)) return hr;
    m_pDx12SwapChain->UpdateSwapChainColorSpace(&m_CDx1xCommon, true);
    if (hWndDisplayAlt)
    {
        m_pDx12SwapChainAlt = new CDx12SwapChain;
        hr = m_pDx12SwapChainAlt->Init(pD3D12Device, hWndDisplayAlt, m_OutWidth, m_OutHeight, m_DstFormat, 16, 1, 1);
        if (FAILED(hr)) return hr;
        m_pDx12SwapChainAlt->UpdateSwapChainColorSpace(&m_CDx1xCommon, false);
    }

    m_NGXDstRect = { 0, 0, (LONG)m_OutWidth, (LONG)m_OutHeight };
    m_NGXSrcRect = { 0, 0, (LONG)m_DstWidth, (LONG)m_DstHeight };

    m_CNGXTrueHDR = new CDx12NGXTrueHDR;
    hr = m_CNGXTrueHDR->CreateFeature(pD3D12Device, 1, 1);
    if (FAILED(hr))
    {
        SafeDelete(m_CNGXTrueHDR);
        return hr;
    }

    return hr;
}


HRESULT CDX12TrueHDRDemo::SetupStreamData(D3D12_VIDEO_PROCESS_INPUT_STREAM_DESC* pVPInputStreamDesc, char* szFile)
{
    CStreamData12* pStreamData = &m_StreamData;
    D3D12_VIDEO_PROCESS_INPUT_STREAM_ARGUMENTS1* pVPInputStream = &m_VPInputStream;
    D3D12_VIDEO_PROCESS_INPUT_STREAM_DESC* pVPInDesc = pVPInputStreamDesc;

    pStreamData->FileBased = TRUE;
    pStreamData->Enable = TRUE;

    // Open the file for reading.
    pStreamData->pVideoFile = new CVideoFile(true);

    strcpy_s(pStreamData->FileName, szFile);
    HRESULT hr = pStreamData->pVideoFile->Open(pStreamData->FileName);
    if (FAILED(hr))
    {
        delete pStreamData->pVideoFile;
        pStreamData->pVideoFile = NULL;
        return false;
    }
    pStreamData->Width = pStreamData->pVideoFile->Width();
    pStreamData->Height = pStreamData->pVideoFile->Height();
    pStreamData->Pitch = pStreamData->pVideoFile->Pitch();
    pStreamData->MaxFrameCount = pStreamData->pVideoFile->NumFrames();

    m_DstWidth = m_Configuration.DisplayWidth;
    m_DstHeight = m_Configuration.DisplayHeight;
    m_OutWidth = m_Configuration.DisplayWidth;
    m_OutHeight = m_Configuration.DisplayHeight;

    // CONFIG
    m_DstFormat = DXGI_FORMAT_B8G8R8A8_UNORM;
    m_DstColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
    m_OutFormat = DXGI_FORMAT_B8G8R8A8_UNORM;
    m_OutColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;

    if (m_Configuration.bUseNativeSize)
    {
        m_DstWidth = m_StreamData.Width;
        m_DstHeight = m_StreamData.Height;
        m_OutWidth = m_StreamData.Width;
        m_OutHeight = m_StreamData.Height;
    }

    if (m_Configuration.bFP16)
    {
        m_OutFormat = DXGI_FORMAT_R16G16B16A16_FLOAT;
        m_OutColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709;
    }
    else
    {
        m_OutFormat = DXGI_FORMAT_R10G10B10A2_UNORM;
    }

    m_BackgroundColor[0] = 0.0f;     //R
    m_BackgroundColor[1] = 0.0f;     //G
    m_BackgroundColor[2] = 0.0f;     //B
    m_BackgroundColor[3] = 1.0f;     //A

    m_StreamCount = 1;

    SetRect(&pVPInputStream->Transform.SourceRectangle, 0, 0, pStreamData->pVideoFile->Width(), pStreamData->pVideoFile->Height());
    SetRect(&pVPInputStream->Transform.DestinationRectangle, 0, 0, m_DstWidth, m_DstHeight);
    pVPInDesc->ColorSpace = DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709;

    switch (pStreamData->pVideoFile->FourCC())
    {
    case FOURCC_NV12:
        pStreamData->StreamFormat = DXGI_FORMAT_NV12;
        pStreamData->bSplitChroma = true;
        break;
    case FOURCC_YUY2:
        pStreamData->StreamFormat = DXGI_FORMAT_YUY2;
        break;
    case FOURCC_ARGB:
        pVPInDesc->ColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
        pStreamData->StreamFormat = DXGI_FORMAT_B8G8R8A8_UNORM;
        break;
    default:
        break;
    }

    pVPInDesc->Format = pStreamData->StreamFormat;
    pVPInDesc->SourceAspectRatio.Numerator = pStreamData->Width;
    pVPInDesc->SourceAspectRatio.Denominator = pStreamData->Height;
    pVPInDesc->DestinationAspectRatio.Numerator = m_DstWidth;
    pVPInDesc->DestinationAspectRatio.Denominator = m_DstHeight;
    pVPInDesc->FrameRate.Numerator = 30;
    pVPInDesc->FrameRate.Denominator = 1;

    pVPInDesc->SourceSizeRange.MinWidth = 64;
    pVPInDesc->SourceSizeRange.MinHeight = 64;
    pVPInDesc->SourceSizeRange.MaxWidth = pStreamData->Width;
    pVPInDesc->SourceSizeRange.MaxHeight = pStreamData->Height;

    pVPInDesc->DestinationSizeRange.MinWidth = 64;
    pVPInDesc->DestinationSizeRange.MinHeight = 64;
    pVPInDesc->DestinationSizeRange.MaxWidth = m_OutWidth;
    pVPInDesc->DestinationSizeRange.MaxHeight = m_OutHeight;

    pVPInDesc->EnableOrientation = FALSE;
    pVPInDesc->FilterFlags = D3D12_VIDEO_PROCESS_FILTER_FLAG_NONE;
    pVPInDesc->EnableAutoProcessing = TRUE;
    pVPInDesc->StereoFormat = D3D12_VIDEO_FRAME_STEREO_FORMAT_NONE;
    pVPInDesc->FieldType = D3D12_VIDEO_FIELD_TYPE_NONE;
    pVPInDesc->DeinterlaceMode = D3D12_VIDEO_PROCESS_DEINTERLACE_FLAG_NONE;

    pVPInDesc->EnableAlphaBlending = FALSE;
    pVPInDesc->LumaKey.Enable = FALSE;
    pVPInDesc->LumaKey.Lower = 0.0f;
    pVPInDesc->LumaKey.Upper = 1.0f;
    pVPInDesc->NumPastFrames = 0;
    pVPInDesc->NumFutureFrames = 0;

    pVPInputStream->Flags = D3D12_VIDEO_PROCESS_INPUT_STREAM_FLAG_NONE;
    pVPInputStream->RateInfo.OutputIndex = 0;
    pVPInputStream->RateInfo.InputFrameOrField = 0;
    pVPInputStream->AlphaBlending.Enable = pVPInDesc->EnableAlphaBlending;
    pVPInputStream->AlphaBlending.Alpha = 1.0;
    pVPInputStream->Transform.Orientation = D3D12_VIDEO_PROCESS_ORIENTATION_DEFAULT;

    return true;
}


//-----------------------------------------------------------------------------------------------
// copy upload buffer to texture
void CDX12TrueHDRDemo::CopyBufferToSurface(CStreamData12* pStreamData)
{
    if (pStreamData->bSplitChroma)
    {
        // copy luma - dx handles it as mip level 0
        CD3DX12_TEXTURE_COPY_LOCATION Dst(pStreamData->pTexture, 0);
        CD3DX12_TEXTURE_COPY_LOCATION Src(pStreamData->pBuffer, { 0, CD3DX12_SUBRESOURCE_FOOTPRINT(pStreamData->StreamFormat == DXGI_FORMAT_P010 ? DXGI_FORMAT_R16_UNORM : DXGI_FORMAT_R8_UNORM, pStreamData->Width, pStreamData->Height, 1, pStreamData->Pitch) });
        m_commandList->CopyTextureRegion(&Dst, 0, 0, 0, &Src, nullptr);

        // copy chroma - dx handles it as mip level 1
        CD3DX12_TEXTURE_COPY_LOCATION DstCh(pStreamData->pTexture, 1);
        CD3DX12_TEXTURE_COPY_LOCATION SrcCh(pStreamData->pBuffer, { pStreamData->nLumaBufferSize, CD3DX12_SUBRESOURCE_FOOTPRINT(pStreamData->StreamFormat == DXGI_FORMAT_P010 ? DXGI_FORMAT_R16G16_UNORM : DXGI_FORMAT_R8G8_UNORM, pStreamData->Width / 2, pStreamData->Height / 2, 1, pStreamData->Pitch) });
        m_commandList->CopyTextureRegion(&DstCh, 0, 0, 0, &SrcCh, nullptr);
    }
    else
    {
        CD3DX12_RESOURCE_BARRIER dx12CopyBarrier = CD3DX12_RESOURCE_BARRIER::Transition(pStreamData->pTexture, pStreamData->StreamState, D3D12_RESOURCE_STATE_COPY_DEST);
        m_commandList->ResourceBarrier(1, &dx12CopyBarrier);

        CD3DX12_TEXTURE_COPY_LOCATION Dst(pStreamData->pTexture, 0);
        CD3DX12_TEXTURE_COPY_LOCATION Src(pStreamData->pBuffer, { 0, CD3DX12_SUBRESOURCE_FOOTPRINT(pStreamData->StreamFormat, pStreamData->Width, pStreamData->Height, 1, pStreamData->Pitch) });
        m_commandList->CopyTextureRegion(&Dst, 0, 0, 0, &Src, nullptr);

        CD3DX12_RESOURCE_BARRIER dx12CommonBarrier = CD3DX12_RESOURCE_BARRIER::Transition(pStreamData->pTexture, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COMMON);
        m_commandList->ResourceBarrier(1, &dx12CommonBarrier);
        pStreamData->StreamState = D3D12_RESOURCE_STATE_COMMON;
    }
}


//-----------------------------------------------------------------------------------------------
// Prepares the source surface for presentation by fill it with the video data.
HRESULT CDX12TrueHDRDemo::FillFrameFromFile(CStreamData12* pStreamData, D3D12_VIDEO_PROCESS_INPUT_STREAM_ARGUMENTS1* pVPInputStream, bool bBackward)
{
    pVPInputStream->RateInfo.InputFrameOrField++;

    // Map the surface to fill with data from file
    char* pData = NULL;
    CD3DX12_RANGE readRange(0, 0);
    HRESULT hr = pStreamData->pBuffer->Map(0, &readRange, (void**)&pData);
    if (FAILED(hr)) return hr;

    if (m_uCurFrameCount)       // skip first frame
    {
        if (bBackward)
            pStreamData->CurFrameCount = pStreamData->pVideoFile->DecrementFrameCyclic();
        else
            pStreamData->CurFrameCount = pStreamData->pVideoFile->IncrementFrameCyclic();
    }
    // Fill the source buffer with the video data. This surface may be a different
    // format than the video file (ex. NV24 file to NV12 surface).
    hr = pStreamData->pVideoFile->FillBuffer(pData, pStreamData->pVideoFile->FourCC(), pStreamData->pVideoFile->Pitch());
    if (SUCCEEDED(hr))
    {
        {
            // handle the difference on DX12 where it needs a round up pitch of 512
            char* pSrcBuf = pData + pStreamData->pVideoFile->Pitch() * pStreamData->pVideoFile->Height();
            char* pDstBuf = pData + pStreamData->nLumaBufferSize;
            memmove(pDstBuf, pSrcBuf, (size_t)pStreamData->nChromaBufferSize);
        }
        pVPInputStream->RateInfo.OutputIndex = 0;
    }

    // unmap the buffer
    pStreamData->pBuffer->Unmap(0, nullptr);
    // copy from buffer to surface
    CopyBufferToSurface(pStreamData);

    return hr;
}


//-----------------------------------------------------------------------------------------------
HRESULT CDX12TrueHDRDemo::NextFrame(bool bBackward)
{
    HRESULT hr = S_OK;

    // make sure the allocator is ready
    m_AllocatorSyncObj->SignalFence(m_commandQueue);
    m_AllocatorSyncObj->WaitForCPUFence();

    // update the nextFrame, which will use the command list to copy from upload buffer to texture
    hr = m_commandAllocator->Reset();
    if (FAILED(hr)) return hr;
    hr = m_commandList->Reset(m_commandAllocator, nullptr);
    if (FAILED(hr)) return hr;

    CStreamData12* pStreamData = &m_StreamData;
    D3D12_VIDEO_PROCESS_INPUT_STREAM_ARGUMENTS1* pVPInputStream = &m_VPInputStream;
    {
        pStreamData->pSyncObject->WaitForFence(m_commandQueue);
        if (pStreamData->FileBased)
        {
            hr = FillFrameFromFile(pStreamData, pVPInputStream, bBackward);
            if (FAILED(hr)) return hr;
        }
        pStreamData->pSyncObject->SignalFence(m_commandQueue);
    }
    if (FAILED(hr)) return hr;

    m_pDx12SwapChain->UpdateSwapChainColorSpace(&m_CDx1xCommon, true);


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

    {
        ID3D12Resource*     pSrcTexture     = pStreamData->pTexture;
        CDx12SyncObject*    pSrcSyncObj     = pStreamData->pSyncObject;
        RECT                srcRect         = { 0, 0, pStreamData->Width, pStreamData->Height };
 
        ID3D12Resource*     pDstTexture     = nullptr;
        CDx12SyncObject*    pDstSyncObj     = nullptr;
        hr = m_pDx12SwapChain->GetBuffer(&pDstTexture, &pDstSyncObj);
        if (FAILED(hr)) return hr;

        if (m_CDx1xCommon.IsMonitorHDR())
        {
            if (pStreamData->StreamFormat == DXGI_FORMAT_NV12 || pStreamData->StreamFormat == DXGI_FORMAT_YUY2)
            {
                m_VPOutputStream.OutputStream[0].pTexture2D = m_pDstTexture;
                pStreamData->pSyncObject->WaitForFence(m_pCDx12Api->GetCommandQueue());

                // VpBlt to NGX input buffer
                hr = m_pCDx12Api->VpBltHd(&m_VPOutputStream, m_pDstSyncObj, &m_VPInputStream, m_StreamCount, m_pD3D12VideoProcessor);
                if (FAILED(hr)) return hr;

                pStreamData->pSyncObject->SignalFence(m_pCDx12Api->GetCommandQueue());
                pSrcTexture = m_pDstTexture;
                pSrcSyncObj = m_pDstSyncObj;
                srcRect     = m_NGXSrcRect;
            }

            UINT Contrast       = 100;
            UINT Saturation     = 100;
            UINT MiddleGray     = 50;
            UINT MaxLuminance   = m_CDx1xCommon.GetMonitorMaxLuminance();
            hr = m_CNGXTrueHDR->EvaluateFeature(pDstTexture, pDstSyncObj, m_NGXDstRect,
                                                pSrcTexture, pSrcSyncObj, srcRect,
                                                Contrast, Saturation, MiddleGray, MaxLuminance);
        }
        else
        {
            m_VPOutputStream.OutputStream[0].pTexture2D = pDstTexture;
            pSrcSyncObj->WaitForFence(m_pCDx12Api->GetCommandQueue());

            hr = m_pCDx12Api->VpBltHd(&m_VPOutputStream, pDstSyncObj, &m_VPInputStream, m_StreamCount, m_pD3D12VideoProcessorSwapBuffer);
            if (FAILED(hr)) return hr;

            pStreamData->pSyncObject->SignalFence(m_pCDx12Api->GetCommandQueue());
        }

        if (FAILED(hr)) return hr;
        hr = m_pDx12SwapChain->Present();
        if (FAILED(hr)) return hr;

        if (m_pDx12SwapChainAlt)
        {
            m_pDx12SwapChainAlt->GetBuffer(&pDstTexture, &pDstSyncObj);
            m_VPOutputStream.OutputStream[0].pTexture2D = pDstTexture;

            hr = m_pCDx12Api->VpBltHd(&m_VPOutputStream, pDstSyncObj, &m_VPInputStream, m_StreamCount, m_pD3D12VideoProcessor);
            if (FAILED(hr)) return hr;
            hr = m_pDx12SwapChainAlt->Present();
            if (FAILED(hr)) return hr;
        }
    }

    m_uCurFrameCount++;
    return hr;
}

