/**
 * Looking Glass
 * Copyright © 2017-2026 The Looking Glass Authors
 * https://looking-glass.io
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include "d3d/CD3D12Device.h"
#include "config/CSettings.h"
#include "CDebug.h"

bool CD3D12Device::s_directHeapFailed = false;

CD3D12Device::CD3D12Device(LUID adapterLuid) :
  m_adapterLuid(adapterLuid),
  m_debug(false)
{
  if (m_debug)
  {
    HRESULT hr = D3D12GetDebugInterface(IID_PPV_ARGS(&m_dxDebug));
    if (FAILED(hr))
    {
      DEBUG_ERROR_HR(hr, "Failed to get the debug interface");
      return;
    }

    m_dxDebug->EnableDebugLayer();
    m_dxDebug->SetEnableGPUBasedValidation(TRUE);
    m_dxDebug->SetEnableSynchronizedCommandQueueValidation(TRUE);
    m_dxDebug->SetForceLegacyBarrierValidation(TRUE);
  }
}

static void CALLBACK _D3D12DebugCallback(
  D3D12_MESSAGE_CATEGORY category,
  D3D12_MESSAGE_SEVERITY severity,
  D3D12_MESSAGE_ID       id,
  LPCSTR                 description,
  void                  *context
)
{
  (void)context;

  DEBUG_INFO("category:%d severity:%d id:%d desc:%s",
    category,
    severity,
    id,
    description);
}

CD3D12Device::InitResult CD3D12Device::Init(
  const DirectFrameBufferMemory& directMemory,
  UINT64& alignSize, bool enableCompute)
{
  HRESULT hr;

  const bool forceIndirectCopy = CSettings::ShouldForceIndirectCopy();
  m_computeEnabled = enableCompute;
  m_indirectCopy   = !directMemory || s_directHeapFailed || forceIndirectCopy;

  if (forceIndirectCopy)
    DEBUG_INFO("Forcing indirect transport copies");

  hr = CreateDXGIFactory2(m_debug ? DXGI_CREATE_FACTORY_DEBUG : 0, IID_PPV_ARGS(&m_factory));  
  if (FAILED(hr))
  {
    DEBUG_ERROR_HR(hr, "Failed to create the DXGI factory");
    return InitResult::FAILURE;
  }

  hr = m_factory->EnumAdapterByLuid(m_adapterLuid, IID_PPV_ARGS(&m_adapter));
  if (FAILED(hr))
  {
    DEBUG_ERROR_HR(hr, "Failed to enumerate the adapter");
    return InitResult::FAILURE;
  }

  hr = D3D12CreateDevice(m_adapter.Get(), D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&m_device));
  if (FAILED(hr))
  {
    DEBUG_ERROR_HR(hr, "Failed to create the DirectX12 device");
    return InitResult::FAILURE;
  }

  if (m_debug)
  {
    hr = m_device.As(&m_infoQueue);
    if (FAILED(hr))
    {
      DEBUG_WARN_HR(hr, "Failed to get the ID3D12InfoQueue1 interface");
      //non-fatal do not exit
    }
    else
    {
      m_infoQueue->RegisterMessageCallback(
        _D3D12DebugCallback, D3D12_MESSAGE_CALLBACK_FLAG_NONE, NULL, &m_callbackCookie);
    }
  }

  if (!m_indirectCopy)
  {
    hr = m_device->OpenExistingHeapFromAddress(
      directMemory.address, IID_PPV_ARGS(&m_transportHeap));
    if (FAILED(hr))
    {
      DEBUG_ERROR_HR(hr,
        "Failed to open transport memory as a D3D12 heap");
      s_directHeapFailed = true;
      return InitResult::RETRY;
    }
    m_transportHeap->SetName(L"Transport Memory");
  
    D3D12_HEAP_DESC heapDesc = m_transportHeap->GetDesc();
    alignSize = heapDesc.Alignment;

    // test that the heap is usable
    if (!HeapTest())
    {
      DEBUG_WARN("Unable to create resources in transport memory, "
        "falling back to indirect copy");

      // failure often results in the device being removed and we need to completely reinit when this occurs
      s_directHeapFailed = true;
      return InitResult::RETRY;
    }

    DEBUG_INFO("Using transport memory as a D3D12 heap");
  }

  if (!m_copyQueue.Init(m_device.Get(), D3D12_COMMAND_LIST_TYPE_COPY,
      L"Copy", m_indirectCopy ? CD3D12CommandSlot::NORMAL :
                               CD3D12CommandSlot::FAST, 2, true, true))
    return InitResult::FAILURE;

  if (m_computeEnabled &&
      !m_computeQueue.Init(m_device.Get(), D3D12_COMMAND_LIST_TYPE_COMPUTE,
        L"Compute", CD3D12CommandSlot::FAST, 2, false, true))
    return InitResult::FAILURE;

  DEBUG_INFO("Created CD3D12Device");
  return InitResult::SUCCESS;
}

void CD3D12Device::DeInit()
{
  if (m_debug && m_infoQueue)
    m_infoQueue->UnregisterMessageCallback(m_callbackCookie);
  m_infoQueue.Reset();
}

void CD3D12Device::WaitForIdle()
{
  m_copyQueue.WaitForIdle();
  if (m_computeEnabled)
    m_computeQueue.WaitForIdle();
}

bool CD3D12Device::HeapTest()
{
  D3D12_RESOURCE_DESC desc = {};
  desc.Dimension          = D3D12_RESOURCE_DIMENSION_BUFFER;
  desc.Alignment          = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT;
  desc.Width              = 1048576;
  desc.Height             = 1;
  desc.DepthOrArraySize   = 1;
  desc.MipLevels          = 1;
  desc.Format             = DXGI_FORMAT_UNKNOWN;
  desc.SampleDesc.Count   = 1;
  desc.SampleDesc.Quality = 0;
  desc.Layout             = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
  desc.Flags              = D3D12_RESOURCE_FLAG_ALLOW_CROSS_ADAPTER;

  HRESULT hr;

  ComPtr<ID3D12Resource> resource;
  hr = m_device->CreatePlacedResource(
    m_transportHeap.Get(),
    0,
    &desc,
    D3D12_RESOURCE_STATE_COPY_DEST,
    NULL,
    IID_PPV_ARGS(&resource));
  if (FAILED(hr))
  {
    DEBUG_ERROR_HR(hr,
      "Failed to create the transport-memory ID3D12Resource");
    return false;
  }
  resource->SetName(L"HeapTest");

  /* the above may succeed even if there was a fault, as such we also need to check
   * check if the device was removed */
  hr = m_device->GetDeviceRemovedReason();
  if (hr != S_OK)
  {
    DEBUG_ERROR_HR(hr, "Device Removed");
    return false;
  }

  return true;
}

CD3D12CommandSlot * CD3D12Device::GetCopySlot(unsigned frameIndex)
{
  return m_copyQueue.Acquire(frameIndex);
}

CD3D12CommandSlot * CD3D12Device::GetCopySlot()
{
  return m_copyQueue.Acquire();
}

CD3D12CommandSlot * CD3D12Device::GetComputeSlot(unsigned frameIndex)
{
  if (!m_computeEnabled)
  {
    DEBUG_ERROR("Compute queue requested while compute processing is disabled");
    return nullptr;
  }

  return m_computeQueue.Acquire(frameIndex);
}
