//----------------------------------------------------------------------------------
// File:        CDx11NGXTrueHDR.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.
//
//----------------------------------------------------------------------------------

///////////////////////////////////////////
// CDx11NGXTrueHDR.cpp
// This is a wrapper class to simplify adding NGX TrueHDR to a DX11 app
// see .h for info


#include <d3d11_4.h>

///////////////////////////////////
#define NGX_CLASS_USE
#include "CDx11NGXTrueHDR.h"

HRESULT CDx11NGXTrueHDR::CreateFeature(ID3D11Device* pD3DDevice)
{
    HRESULT hr = S_OK;
    // default to false until creation is done
    m_bNGXInitialized   = false;

    // init NGX SDK
    NVSDK_NGX_Result Status = NVSDK_NGX_D3D11_Init(APP_ID, APP_PATH, pD3DDevice);
    if (NVSDK_NGX_FAILED(Status)) return E_FAIL;

    // Get NGX parameters interface (managed and released by NGX)
    Status = NVSDK_NGX_D3D11_GetCapabilityParameters(&m_ngxParameters);
    if (NVSDK_NGX_FAILED(Status)) return E_FAIL;

    // Now check if TrueHDR is available on the system
    int TrueHDRAvailable = 0;
    NVSDK_NGX_Result ResultTrueHDR = m_ngxParameters->Get(NVSDK_NGX_Parameter_TrueHDR_Available, &TrueHDRAvailable);
    if (!TrueHDRAvailable) return E_FAIL;

    m_pD3D11Device = pD3DDevice;
    m_pD3D11Device->AddRef();

    m_pD3D11Device->GetImmediateContext(&m_pD3D11DeviceContext);

    hr = m_pD3D11DeviceContext->QueryInterface(__uuidof(ID3D10Multithread), (void**)&m_pMultiThread);
    if (SUCCEEDED(hr))
    {
        m_pMultiThread->SetMultithreadProtected(TRUE);
        m_pMultiThread->Enter();
    }
#if 0
    ID3D11DeviceContext* pDeferredContext = nullptr;
    hr = m_pD3D11Device->CreateDeferredContext(0, &pDeferredContext);
    if (FAILED(hr)) return hr;
#endif

    // Create the TrueHDR feature instance 
    NVSDK_NGX_Feature_Create_Params TrueHDRCreateParams = {};
    ResultTrueHDR = NGX_D3D11_CREATE_TRUEHDR_EXT(m_pD3D11DeviceContext, &m_TrueHDRFeature, m_ngxParameters, &TrueHDRCreateParams);

    if (NVSDK_NGX_FAILED(ResultTrueHDR))
    {
        return E_FAIL;
    }
#if 0
    ID3D11CommandList* pD3D11CommandList = nullptr;
    // record the command list
    hr = pDeferredContext->FinishCommandList(FALSE, &pD3D11CommandList);
    // execute the command list
    m_pD3D11DeviceContext->ExecuteCommandList(pD3D11CommandList, FALSE);
#endif

    if (m_pMultiThread)
    {
        m_pMultiThread->Leave();
    }

    m_bNGXInitialized = true;
    return S_OK;
}

// call to apply TrueHDR from source to dest
// input must be DXGI_FORMAT_R8G8B8A8_UNORM or DXGI_FORMAT_B8G8R8A8_UNORM

HRESULT CDx11NGXTrueHDR::EvaluateFeature(ID3D11Texture2D* Output, RECT RectOutput,
                                            ID3D11Texture2D* Input,  RECT RectInput,
                                            UINT Contrast,      // 0 to 200 for HDR Contrast   (default 100)
                                            UINT Saturation,    // 0 to 200 for HDR Saturation (default 100)
                                            UINT MiddleGray,    // 10 to 100 for HDR MiddleGray (default  50)
                                            UINT MaxLuminance)  // 400 to 2000 for Monitor MaxLuminance (default 1000)
{
    if (!m_bNGXInitialized)
    {
        return E_FAIL;
    }
    HRESULT hr = S_OK;
    // check formats
    {
        D3D11_TEXTURE2D_DESC desc = {};
        // check input is DXGI_FORMAT_R8G8B8A8_UNORM or DXGI_FORMAT_B8G8R8A8_UNORM
        Input->GetDesc(&desc);
        if (desc.Format != DXGI_FORMAT_R8G8B8A8_UNORM && desc.Format != DXGI_FORMAT_B8G8R8A8_UNORM)
        {
            return E_INVALIDARG;
        }
        // verify input rect is within range
        if (   RectInput.left < 0 || RectInput.left >= RectInput.right  || RectInput.right  > (LONG)desc.Width
            || RectInput.top  < 0 || RectInput.top  >= RectInput.bottom || RectInput.bottom > (LONG)desc.Height)
        {
            return E_INVALIDARG;
        }
        // check output is HDR format DXGI_FORMAT_R10G10B10A2_UNORM or DXGI_FORMAT_R16G16B16A16_FLOAT
        Output->GetDesc(&desc);
        if (desc.Format != DXGI_FORMAT_R10G10B10A2_UNORM && desc.Format != DXGI_FORMAT_R16G16B16A16_FLOAT)
        {
            return E_INVALIDARG;
        }
        // verify output rect is within range
        if (   RectOutput.left < 0 || RectOutput.left >= RectOutput.right  || RectOutput.right  > (LONG)desc.Width
            || RectOutput.top  < 0 || RectOutput.top  >= RectOutput.bottom || RectOutput.bottom > (LONG)desc.Height)
        {
            return E_INVALIDARG;
        }

        // The NGX dst surface must be created with BIND_UNORDERED_ACCESS, which swap buffers are not.
        // check for UNORDERED_ACCESS
        m_bNGXInitializedDstTmp = !(desc.BindFlags & D3D11_BIND_UNORDERED_ACCESS);
 
        // verify DstTmp matches dest surface so copyRegion works
        if (m_bNGXInitializedDstTmp && (!m_pDstTmpNGX || desc.Width != m_uDstTmpWidth || desc.Height != m_uDstTmpHeight))
        {
            SafeRelease(m_pDstTmpNGX);
            m_uDstTmpWidth                          = desc.Width;
            m_uDstTmpHeight                         = desc.Height;
            D3D11_TEXTURE2D_DESC texture2d_desc     = { 0 };
            texture2d_desc.Width                    = m_uDstTmpWidth;
            texture2d_desc.Height                   = m_uDstTmpHeight;
            texture2d_desc.MipLevels                = 1;
            texture2d_desc.ArraySize                = 1;
            texture2d_desc.SampleDesc.Count         = 1;
            texture2d_desc.MiscFlags                = 0;
            texture2d_desc.Format                   = desc.Format;
            texture2d_desc.Usage                    = D3D11_USAGE_DEFAULT;
            texture2d_desc.BindFlags                = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS;

            hr = m_pD3D11Device->CreateTexture2D(&texture2d_desc, NULL, &m_pDstTmpNGX);
            if (FAILED(hr)) return hr;
        }
    }

    // NGX output rect, typically 0,0,swapWidth,swapHeight
    m_NGXDstRect = RectOutput;
    // NGX input rect,  typically 0,0,decodeWidth,decodeHeight
    m_NGXSrcRect = RectInput;

    // setup TrueHDR params
    NVSDK_NGX_D3D11_TRUEHDR_Eval_Params D3D11TrueHDREvalParams = {};

    D3D11TrueHDREvalParams.pInput                   = Input;
    D3D11TrueHDREvalParams.pOutput                  = m_bNGXInitializedDstTmp ? m_pDstTmpNGX : Output;
    D3D11TrueHDREvalParams.InputSubrectTL.X         = m_NGXSrcRect.left;
    D3D11TrueHDREvalParams.InputSubrectTL.Y         = m_NGXSrcRect.top;
    D3D11TrueHDREvalParams.InputSubrectBR.Width     = m_NGXSrcRect.right;
    D3D11TrueHDREvalParams.InputSubrectBR.Height    = m_NGXSrcRect.bottom;
    D3D11TrueHDREvalParams.OutputSubrectTL.X        = m_NGXDstRect.left;
    D3D11TrueHDREvalParams.OutputSubrectTL.Y        = m_NGXDstRect.top;
    D3D11TrueHDREvalParams.OutputSubrectBR.Width    = m_NGXDstRect.right;
    D3D11TrueHDREvalParams.OutputSubrectBR.Height   = m_NGXDstRect.bottom;
    D3D11TrueHDREvalParams.Contrast                 = Contrast;
    D3D11TrueHDREvalParams.Saturation               = Saturation;
    D3D11TrueHDREvalParams.MiddleGray               = MiddleGray;
    D3D11TrueHDREvalParams.MaxLuminance             = MaxLuminance;

    if (m_pMultiThread)
    {
        m_pMultiThread->Enter();
    }

    NVSDK_NGX_Result ResultTrueHDR = NGX_D3D11_EVALUATE_TRUEHDR_EXT(m_pD3D11DeviceContext, m_TrueHDRFeature, m_ngxParameters, &D3D11TrueHDREvalParams);
    if (NVSDK_NGX_FAILED(ResultTrueHDR))
    {
        return E_FAIL;
    }
    if (m_bNGXInitializedDstTmp)
    {
        m_pD3D11DeviceContext->CopySubresourceRegion(Output, 0, 0, 0, 0, m_pDstTmpNGX, 0, NULL);
    }

    if (m_pMultiThread)
    {
        m_pMultiThread->Leave();
    }

    return hr;
}

// ReleaseFeature needs to be called before the class destructor 
// as the runtime destructor will delete a critical section used in NVSDK_NGX_D3D11_Shutdown1
void CDx11NGXTrueHDR::ReleaseFeature()
{
    if (m_bNGXInitialized)
    {
        NVSDK_NGX_D3D11_ReleaseFeature(m_TrueHDRFeature);
        m_TrueHDRFeature = nullptr;
        NVSDK_NGX_D3D11_Shutdown1(m_pD3D11Device);
        NVSDK_NGX_D3D11_DestroyParameters(m_ngxParameters);
        m_bNGXInitialized = false;
    }
    SafeRelease(m_pDstTmpNGX);
    SafeRelease(m_pMultiThread);
    SafeRelease(m_pD3D11DeviceContext);
    SafeRelease(m_pD3D11Device);
}

