// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include "VideoBackends/D3D12/DescriptorHeapManager.h"
#include "Common/Assert.h"
#include "VideoBackends/D3D12/DXContext.h"
#include "VideoCommon/VideoConfig.h"

namespace DX12
{
DescriptorHeapManager::DescriptorHeapManager() = default;
DescriptorHeapManager::~DescriptorHeapManager() = default;

bool DescriptorHeapManager::Create(ID3D12Device* device, D3D12_DESCRIPTOR_HEAP_TYPE type,
                                   u32 num_descriptors)
{
  D3D12_DESCRIPTOR_HEAP_DESC desc = {type, static_cast<UINT>(num_descriptors),
                                     D3D12_DESCRIPTOR_HEAP_FLAG_NONE};

  HRESULT hr = device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&m_descriptor_heap));
  CHECK(SUCCEEDED(hr), "Create descriptor heap");
  if (FAILED(hr))
    return false;

  m_heap_base_cpu = m_descriptor_heap->GetCPUDescriptorHandleForHeapStart();
  m_heap_base_gpu = m_descriptor_heap->GetGPUDescriptorHandleForHeapStart();
  m_num_descriptors = num_descriptors;
  m_descriptor_increment_size = device->GetDescriptorHandleIncrementSize(type);

  // Set all slots to unallocated (1)
  const u32 bitset_count =
      num_descriptors / BITSET_SIZE + (((num_descriptors % BITSET_SIZE) != 0) ? 1 : 0);
  m_free_slots.resize(bitset_count);
  for (BitSetType& bs : m_free_slots)
    bs.flip();

  return true;
}

bool DescriptorHeapManager::Allocate(DescriptorHandle* handle)
{
  // Start past the temporary slots, no point in searching those.
  for (u32 group = 0; group < m_free_slots.size(); group++)
  {
    BitSetType& bs = m_free_slots[group];
    if (bs.none())
      continue;

    u32 bit = 0;
    for (; bit < BITSET_SIZE; bit++)
    {
      if (bs[bit])
        break;
    }

    u32 index = group * BITSET_SIZE + bit;
    bs[bit] = false;

    handle->index = index;
    handle->cpu_handle.ptr = m_heap_base_cpu.ptr + index * m_descriptor_increment_size;
    handle->gpu_handle.ptr = m_heap_base_gpu.ptr + index * m_descriptor_increment_size;
    return true;
  }

  PanicAlert("Out of fixed descriptors");
  return false;
}

void DescriptorHeapManager::Free(u32 index)
{
  ASSERT(index < m_num_descriptors);

  u32 group = index / BITSET_SIZE;
  u32 bit = index % BITSET_SIZE;
  m_free_slots[group][bit] = true;
}

void DescriptorHeapManager::Free(const DescriptorHandle& handle)
{
  Free(handle.index);
}

SamplerHeapManager::SamplerHeapManager() = default;
SamplerHeapManager::~SamplerHeapManager() = default;

static void GetD3DSamplerDesc(D3D12_SAMPLER_DESC* desc, const SamplerState& state)
{
  if (state.mipmap_filter == SamplerState::Filter::Linear)
  {
    if (state.min_filter == SamplerState::Filter::Linear)
    {
      desc->Filter = (state.mag_filter == SamplerState::Filter::Linear) ?
                         D3D12_FILTER_MIN_MAG_MIP_LINEAR :
                         D3D12_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR;
    }
    else
    {
      desc->Filter = (state.mag_filter == SamplerState::Filter::Linear) ?
                         D3D12_FILTER_MIN_POINT_MAG_MIP_LINEAR :
                         D3D12_FILTER_MIN_MAG_POINT_MIP_LINEAR;
    }
  }
  else
  {
    if (state.min_filter == SamplerState::Filter::Linear)
    {
      desc->Filter = (state.mag_filter == SamplerState::Filter::Linear) ?
                         D3D12_FILTER_MIN_MAG_LINEAR_MIP_POINT :
                         D3D12_FILTER_MIN_LINEAR_MAG_MIP_POINT;
    }
    else
    {
      desc->Filter = (state.mag_filter == SamplerState::Filter::Linear) ?
                         D3D12_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT :
                         D3D12_FILTER_MIN_MAG_MIP_POINT;
    }
  }

  static constexpr std::array<D3D12_TEXTURE_ADDRESS_MODE, 3> address_modes = {
      {D3D12_TEXTURE_ADDRESS_MODE_CLAMP, D3D12_TEXTURE_ADDRESS_MODE_WRAP,
       D3D12_TEXTURE_ADDRESS_MODE_MIRROR}};
  desc->AddressU = address_modes[static_cast<u32>(state.wrap_u.Value())];
  desc->AddressV = address_modes[static_cast<u32>(state.wrap_v.Value())];
  desc->AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
  desc->MaxLOD = state.max_lod / 16.f;
  desc->MinLOD = state.min_lod / 16.f;
  desc->MipLODBias = static_cast<s32>(state.lod_bias) / 256.f;
  desc->ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER;

  if (state.anisotropic_filtering)
  {
    desc->Filter = D3D12_FILTER_ANISOTROPIC;
    desc->MaxAnisotropy = 1u << g_ActiveConfig.iMaxAnisotropy;
  }
}

bool SamplerHeapManager::Lookup(const SamplerState& ss, D3D12_CPU_DESCRIPTOR_HANDLE* handle)
{
  const auto it = m_sampler_map.find(ss.hex);
  if (it != m_sampler_map.end())
  {
    *handle = it->second;
    return true;
  }

  if (m_current_offset == m_num_descriptors)
  {
    // We can clear at any time because the descriptors are copied prior to execution.
    // It's still not free, since we have to recreate all our samplers again.
    WARN_LOG(VIDEO, "Out of samplers, resetting CPU heap");
    Clear();
  }

  D3D12_SAMPLER_DESC desc = {};
  GetD3DSamplerDesc(&desc, ss);

  const D3D12_CPU_DESCRIPTOR_HANDLE new_handle = {m_heap_base_cpu.ptr +
                                                  m_current_offset * m_descriptor_increment_size};
  g_dx_context->GetDevice()->CreateSampler(&desc, new_handle);

  m_sampler_map.emplace(ss.hex, new_handle);
  m_current_offset++;
  *handle = new_handle;
  return true;
}

void SamplerHeapManager::Clear()
{
  m_sampler_map.clear();
  m_current_offset = 0;
}

bool SamplerHeapManager::Create(ID3D12Device* device, u32 num_descriptors)
{
  const D3D12_DESCRIPTOR_HEAP_DESC desc = {D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, num_descriptors};
  HRESULT hr = device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&m_descriptor_heap));
  CHECK(SUCCEEDED(hr), "Failed to create sampler descriptor heap");
  if (FAILED(hr))
    return false;

  m_num_descriptors = num_descriptors;
  m_descriptor_increment_size =
      device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER);
  m_heap_base_cpu = m_descriptor_heap->GetCPUDescriptorHandleForHeapStart();
  return true;
}
}  // namespace DX12