#include "d3d9_adapter.h"

#include "d3d9_interface.h"
#include "d3d9_monitor.h"
#include "d3d9_caps.h"
#include "d3d9_util.h"

#include "../util/util_bit.h"
#include "../util/util_luid.h"
#include "../util/util_ratio.h"

#include <cfloat>

namespace dxvk {

  const char* GetDriverDLL(DxvkGpuVendor vendor) {
    switch (vendor) {
      default:
      case DxvkGpuVendor::Nvidia: return "nvd3dum.dll";

#if defined(__x86_64__) || defined(_M_X64)
      case DxvkGpuVendor::Amd:    return "aticfx64.dll";
      case DxvkGpuVendor::Intel:  return "igdumd64.dll";
#else
      case DxvkGpuVendor::Amd:    return "aticfx32.dll";
      case DxvkGpuVendor::Intel:  return "igdumd32.dll";
#endif
    }
  }


  D3D9Adapter::D3D9Adapter(
          D3D9InterfaceEx* pParent,
          Rc<DxvkAdapter>  Adapter,
          UINT             Ordinal,
          UINT             DisplayIndex)
  : m_parent          (pParent),
    m_adapter         (Adapter),
    m_ordinal         (Ordinal),
    m_displayIndex    (DisplayIndex),
    m_modeCacheFormat (D3D9Format::Unknown),
    m_d3d9Formats     (Adapter, m_parent->GetOptions()) {
    m_adapter->logAdapterInfo();
  }

  template <size_t N>
  static void copyToStringArray(char (&dst)[N], const char* src) {
    std::strncpy(dst, src, N);
    dst[N - 1] = '\0';
  }


  HRESULT D3D9Adapter::GetAdapterIdentifier(
          DWORD                   Flags,
          D3DADAPTER_IDENTIFIER9* pIdentifier) {
    if (unlikely(pIdentifier == nullptr))
      return D3DERR_INVALIDCALL;

    auto& options = m_parent->GetOptions();
    
    const auto& props = m_adapter->deviceProperties();

    DISPLAY_DEVICEA device = { };
    device.cb = sizeof(device);

    if (!::EnumDisplayDevicesA(nullptr, m_displayIndex, &device, 0)) {
      Logger::err("D3D9Adapter::GetAdapterIdentifier: Failed to query display info");
      return D3DERR_INVALIDCALL;
    }

    GUID guid          = bit::cast<GUID>(m_adapter->devicePropertiesExt().coreDeviceId.deviceUUID);

    uint32_t vendorId  = options.customVendorId == -1     ? props.vendorID   : uint32_t(options.customVendorId);
    uint32_t deviceId  = options.customDeviceId == -1     ? props.deviceID   : uint32_t(options.customDeviceId);
    const char*  desc  = options.customDeviceDesc.empty() ? props.deviceName : options.customDeviceDesc.c_str();
    const char* driver = GetDriverDLL(DxvkGpuVendor(vendorId));

    copyToStringArray(pIdentifier->Description, desc);
    copyToStringArray(pIdentifier->DeviceName,  device.DeviceName); // The GDI device name. Not the actual device name.
    copyToStringArray(pIdentifier->Driver,      driver);            // This is the driver's dll.

    pIdentifier->DeviceIdentifier       = guid;
    pIdentifier->DeviceId               = deviceId;
    pIdentifier->VendorId               = vendorId;
    pIdentifier->Revision               = 0;
    pIdentifier->SubSysId               = 0;
    pIdentifier->WHQLLevel              = m_parent->IsExtended() ? 1 : 0; // This doesn't check with the driver on Direct3D9Ex and is always 1.
    pIdentifier->DriverVersion.QuadPart = INT64_MAX;

    return D3D_OK;
  }


  HRESULT D3D9Adapter::CheckDeviceType(
          D3DDEVTYPE DevType,
          D3D9Format AdapterFormat,
          D3D9Format BackBufferFormat,
          BOOL       bWindowed) {
    if (!IsSupportedAdapterFormat(AdapterFormat))
      return D3DERR_NOTAVAILABLE;

    if (!IsSupportedBackBufferFormat(AdapterFormat, BackBufferFormat, bWindowed))
      return D3DERR_NOTAVAILABLE;

    return D3D_OK;
  }


  HRESULT D3D9Adapter::CheckDeviceFormat(
          D3DDEVTYPE      DeviceType,
          D3D9Format      AdapterFormat,
          DWORD           Usage,
          D3DRESOURCETYPE RType,
          D3D9Format      CheckFormat) {
    if (!IsSupportedAdapterFormat(AdapterFormat))
      return D3DERR_NOTAVAILABLE;

    const bool dmap = Usage & D3DUSAGE_DMAP;
    const bool rt   = Usage & D3DUSAGE_RENDERTARGET;
    const bool ds   = Usage & D3DUSAGE_DEPTHSTENCIL;

    const bool surface = RType == D3DRTYPE_SURFACE;
    const bool texture = RType == D3DRTYPE_TEXTURE;

    const bool twoDimensional = surface || texture;

    const bool srgb = (Usage & (D3DUSAGE_QUERY_SRGBREAD | D3DUSAGE_QUERY_SRGBWRITE)) != 0;

    if (CheckFormat == D3D9Format::INST)
      return D3D_OK;

    if (rt && CheckFormat == D3D9Format::A8 && m_parent->GetOptions().disableA8RT)
      return D3DERR_NOTAVAILABLE;

    if (ds && !IsDepthFormat(CheckFormat))
      return D3DERR_NOTAVAILABLE;

    if (rt && CheckFormat == D3D9Format::NULL_FORMAT && twoDimensional)
      return D3D_OK;

    if (rt && CheckFormat == D3D9Format::RESZ && surface)
      return D3D_OK;

    if (CheckFormat == D3D9Format::ATOC && surface)
      return D3D_OK;

    if (CheckFormat == D3D9Format::NVDB && surface)
      return m_adapter->features().core.features.depthBounds
        ? D3D_OK
        : D3DERR_NOTAVAILABLE;

    // I really don't want to support this...
    if (dmap)
      return D3DERR_NOTAVAILABLE;

    auto mapping = m_d3d9Formats.GetFormatMapping(CheckFormat);
    if (mapping.FormatColor == VK_FORMAT_UNDEFINED)
      return D3DERR_NOTAVAILABLE;

    if (mapping.FormatSrgb  == VK_FORMAT_UNDEFINED && srgb)
      return D3DERR_NOTAVAILABLE;

    if (RType == D3DRTYPE_VERTEXBUFFER || RType == D3DRTYPE_INDEXBUFFER)
      return D3D_OK;

    // Let's actually ask Vulkan now that we got some quirks out the way!
    return CheckDeviceVkFormat(mapping.FormatColor, Usage, RType);
  }


  HRESULT D3D9Adapter::CheckDeviceMultiSampleType(
        D3DDEVTYPE          DeviceType,
        D3D9Format          SurfaceFormat,
        BOOL                Windowed,
        D3DMULTISAMPLE_TYPE MultiSampleType,
        DWORD*              pQualityLevels) {
    if (pQualityLevels != nullptr)
      *pQualityLevels = 1;

    auto dst = ConvertFormatUnfixed(SurfaceFormat);
    if (dst.FormatColor == VK_FORMAT_UNDEFINED)
      return D3DERR_NOTAVAILABLE;

    if (MultiSampleType != D3DMULTISAMPLE_NONE
     && (SurfaceFormat == D3D9Format::D32_LOCKABLE
      || SurfaceFormat == D3D9Format::D32F_LOCKABLE
      || SurfaceFormat == D3D9Format::D16_LOCKABLE))
      return D3DERR_NOTAVAILABLE;

    uint32_t sampleCount = std::max<uint32_t>(MultiSampleType, 1u);

    // Check if this is a power of two...
    if (sampleCount & (sampleCount - 1))
      return D3DERR_NOTAVAILABLE;
    
    // Therefore...
    VkSampleCountFlags sampleFlags = VkSampleCountFlags(sampleCount);

    auto availableFlags = !IsDepthFormat(SurfaceFormat)
      ? m_adapter->deviceProperties().limits.framebufferColorSampleCounts
      : m_adapter->deviceProperties().limits.framebufferDepthSampleCounts;

    if (!(availableFlags & sampleFlags))
      return D3DERR_NOTAVAILABLE;

    if (pQualityLevels != nullptr) {
      if (MultiSampleType == D3DMULTISAMPLE_NONMASKABLE)
        *pQualityLevels = 32 - bit::lzcnt(availableFlags);
      else
        *pQualityLevels = 1;
    }

    return D3D_OK;
  }


  HRESULT D3D9Adapter::CheckDepthStencilMatch(
          D3DDEVTYPE DeviceType,
          D3D9Format AdapterFormat,
          D3D9Format RenderTargetFormat,
          D3D9Format DepthStencilFormat) {
    if (!IsDepthFormat(DepthStencilFormat))
      return D3DERR_NOTAVAILABLE;

    if (RenderTargetFormat == dxvk::D3D9Format::NULL_FORMAT)
      return D3D_OK;

    auto mapping = ConvertFormatUnfixed(RenderTargetFormat);
    if (mapping.FormatColor == VK_FORMAT_UNDEFINED)
      return D3DERR_NOTAVAILABLE;

    return D3D_OK;
  }


  HRESULT D3D9Adapter::CheckDeviceFormatConversion(
          D3DDEVTYPE DeviceType,
          D3D9Format SourceFormat,
          D3D9Format TargetFormat) {
    bool sourceSupported = IsSupportedBackBufferFormat(D3D9Format::Unknown, SourceFormat, TRUE);
    bool targetSupported = TargetFormat == D3D9Format::X1R5G5B5
                        || TargetFormat == D3D9Format::A1R5G5B5
                        || TargetFormat == D3D9Format::R5G6B5
                     // || TargetFormat == D3D9Format::R8G8B8 <-- We don't support R8G8B8
                        || TargetFormat == D3D9Format::X8R8G8B8
                        || TargetFormat == D3D9Format::A8R8G8B8
                        || TargetFormat == D3D9Format::A2R10G10B10
                        || TargetFormat == D3D9Format::A16B16G16R16
                        || TargetFormat == D3D9Format::A2B10G10R10
                        || TargetFormat == D3D9Format::A8B8G8R8
                        || TargetFormat == D3D9Format::X8B8G8R8
                        || TargetFormat == D3D9Format::A16B16G16R16F
                        || TargetFormat == D3D9Format::A32B32G32R32F;

    return (sourceSupported && targetSupported)
      ? D3D_OK
      : D3DERR_NOTAVAILABLE;
  }


  HRESULT D3D9Adapter::GetDeviceCaps(
          D3DDEVTYPE DeviceType,
          D3DCAPS9*  pCaps) {
    using namespace dxvk::caps;

    if (pCaps == nullptr)
      return D3DERR_INVALIDCALL;

    auto& options = m_parent->GetOptions();

    // TODO: Actually care about what the adapter supports here.
    // ^ For Intel and older cards most likely here.

    // Device Type
    pCaps->DeviceType               = DeviceType;
    // Adapter Id
    pCaps->AdapterOrdinal           = m_ordinal;
    // Caps 1
    pCaps->Caps                     = D3DCAPS_READ_SCANLINE;
    // Caps 2
    pCaps->Caps2                    = D3DCAPS2_FULLSCREENGAMMA
                                 /* | D3DCAPS2_CANCALIBRATEGAMMA */
                                 /* | D3DCAPS2_RESERVED */
                                 /* | D3DCAPS2_CANMANAGERESOURCE */
                                    | D3DCAPS2_DYNAMICTEXTURES
                                    | D3DCAPS2_CANAUTOGENMIPMAP
                                 /* | D3DCAPS2_CANSHARERESOURCE */;
    // Caps 3
    pCaps->Caps3                    = D3DCAPS3_ALPHA_FULLSCREEN_FLIP_OR_DISCARD
                                    | D3DCAPS3_LINEAR_TO_SRGB_PRESENTATION
                                    | D3DCAPS3_COPY_TO_VIDMEM
                                    | D3DCAPS3_COPY_TO_SYSTEMMEM
                                 /* | D3DCAPS3_DXVAHD */
                                 /* | D3DCAPS3_DXVAHD_LIMITED */;
    // Presentation Intervals
    pCaps->PresentationIntervals    = D3DPRESENT_INTERVAL_DEFAULT
                                    | D3DPRESENT_INTERVAL_ONE
                                    | D3DPRESENT_INTERVAL_TWO
                                    | D3DPRESENT_INTERVAL_THREE
                                    | D3DPRESENT_INTERVAL_FOUR
                                    | D3DPRESENT_INTERVAL_IMMEDIATE;
    // Cursor
    pCaps->CursorCaps               = D3DCURSORCAPS_COLOR; // I do not support Cursor yet, but I don't want to say I don't support it for compatibility reasons.
    // Dev Caps
    pCaps->DevCaps                  = D3DDEVCAPS_EXECUTESYSTEMMEMORY
                                    | D3DDEVCAPS_EXECUTEVIDEOMEMORY
                                    | D3DDEVCAPS_TLVERTEXSYSTEMMEMORY
                                    | D3DDEVCAPS_TLVERTEXVIDEOMEMORY
                                 /* | D3DDEVCAPS_TEXTURESYSTEMMEMORY */
                                    | D3DDEVCAPS_TEXTUREVIDEOMEMORY
                                    | D3DDEVCAPS_DRAWPRIMTLVERTEX
                                    | D3DDEVCAPS_CANRENDERAFTERFLIP
                                    | D3DDEVCAPS_TEXTURENONLOCALVIDMEM
                                    | D3DDEVCAPS_DRAWPRIMITIVES2
                                 /* | D3DDEVCAPS_SEPARATETEXTUREMEMORIES */
                                    | D3DDEVCAPS_DRAWPRIMITIVES2EX
                                    | D3DDEVCAPS_HWTRANSFORMANDLIGHT
                                    | D3DDEVCAPS_CANBLTSYSTONONLOCAL
                                    | D3DDEVCAPS_HWRASTERIZATION
                                    | D3DDEVCAPS_PUREDEVICE
                                 /* | D3DDEVCAPS_QUINTICRTPATCHES */
                                 /* | D3DDEVCAPS_RTPATCHES */
                                 /* | D3DDEVCAPS_RTPATCHHANDLEZERO */
                                 /* | D3DDEVCAPS_NPATCHES */;
    // Primitive Misc. Caps
    pCaps->PrimitiveMiscCaps        = D3DPMISCCAPS_MASKZ
                                    | D3DPMISCCAPS_CULLNONE
                                    | D3DPMISCCAPS_CULLCW
                                    | D3DPMISCCAPS_CULLCCW
                                    | D3DPMISCCAPS_COLORWRITEENABLE
                                    | D3DPMISCCAPS_CLIPPLANESCALEDPOINTS
                                    | D3DPMISCCAPS_CLIPTLVERTS
                                    | D3DPMISCCAPS_TSSARGTEMP
                                    | D3DPMISCCAPS_BLENDOP
                                 /* | D3DPMISCCAPS_NULLREFERENCE */
                                    | D3DPMISCCAPS_INDEPENDENTWRITEMASKS
                                    | D3DPMISCCAPS_PERSTAGECONSTANT
                                    | D3DPMISCCAPS_FOGANDSPECULARALPHA
                                    | D3DPMISCCAPS_SEPARATEALPHABLEND
                                    | D3DPMISCCAPS_MRTINDEPENDENTBITDEPTHS
                                    | D3DPMISCCAPS_MRTPOSTPIXELSHADERBLENDING
                                    | D3DPMISCCAPS_FOGVERTEXCLAMPED
                                    | D3DPMISCCAPS_POSTBLENDSRGBCONVERT;
    // Raster Caps
    pCaps->RasterCaps               = D3DPRASTERCAPS_DITHER
                                    | D3DPRASTERCAPS_ZTEST
                                    | D3DPRASTERCAPS_FOGVERTEX
                                    | D3DPRASTERCAPS_FOGTABLE
                                    | D3DPRASTERCAPS_MIPMAPLODBIAS
                                 /* | D3DPRASTERCAPS_ZBUFFERLESSHSR */
                                    | D3DPRASTERCAPS_FOGRANGE
                                    | D3DPRASTERCAPS_ANISOTROPY
                                 /* | D3DPRASTERCAPS_WBUFFER */
                                    | D3DPRASTERCAPS_WFOG
                                    | D3DPRASTERCAPS_ZFOG
                                    | D3DPRASTERCAPS_COLORPERSPECTIVE
                                    | D3DPRASTERCAPS_SCISSORTEST
                                    | D3DPRASTERCAPS_SLOPESCALEDEPTHBIAS
                                    | D3DPRASTERCAPS_DEPTHBIAS
                                    | D3DPRASTERCAPS_MULTISAMPLE_TOGGLE; // <-- TODO! (but difficult in Vk)
    // Z Comparison Caps
    pCaps->ZCmpCaps                 = D3DPCMPCAPS_NEVER
                                    | D3DPCMPCAPS_LESS
                                    | D3DPCMPCAPS_EQUAL
                                    | D3DPCMPCAPS_LESSEQUAL
                                    | D3DPCMPCAPS_GREATER
                                    | D3DPCMPCAPS_NOTEQUAL
                                    | D3DPCMPCAPS_GREATEREQUAL
                                    | D3DPCMPCAPS_ALWAYS;
    // Source Blend Caps
    pCaps->SrcBlendCaps             = D3DPBLENDCAPS_ZERO
                                    | D3DPBLENDCAPS_ONE
                                    | D3DPBLENDCAPS_SRCCOLOR
                                    | D3DPBLENDCAPS_INVSRCCOLOR
                                    | D3DPBLENDCAPS_SRCALPHA
                                    | D3DPBLENDCAPS_INVSRCALPHA
                                    | D3DPBLENDCAPS_DESTALPHA
                                    | D3DPBLENDCAPS_INVDESTALPHA
                                    | D3DPBLENDCAPS_DESTCOLOR
                                    | D3DPBLENDCAPS_INVDESTCOLOR
                                    | D3DPBLENDCAPS_SRCALPHASAT
                                    | D3DPBLENDCAPS_BOTHSRCALPHA
                                    | D3DPBLENDCAPS_BOTHINVSRCALPHA
                                    | D3DPBLENDCAPS_BLENDFACTOR
                                    | D3DPBLENDCAPS_INVSRCCOLOR2
                                    | D3DPBLENDCAPS_SRCCOLOR2;
    // Destination Blend Caps
    pCaps->DestBlendCaps            = pCaps->SrcBlendCaps;
    // Alpha Comparison Caps
    pCaps->AlphaCmpCaps             = pCaps->ZCmpCaps;
    // Shade Caps
    pCaps->ShadeCaps                = D3DPSHADECAPS_COLORGOURAUDRGB
                                    | D3DPSHADECAPS_SPECULARGOURAUDRGB
                                    | D3DPSHADECAPS_ALPHAGOURAUDBLEND
                                    | D3DPSHADECAPS_FOGGOURAUD;
    // Texture Caps
    pCaps->TextureCaps              = D3DPTEXTURECAPS_PERSPECTIVE
                                 /* | D3DPTEXTURECAPS_POW2 */
                                    | D3DPTEXTURECAPS_ALPHA
                                 /* | D3DPTEXTURECAPS_SQUAREONLY */
                                    | D3DPTEXTURECAPS_TEXREPEATNOTSCALEDBYSIZE
                                    | D3DPTEXTURECAPS_ALPHAPALETTE
                                 /* | D3DPTEXTURECAPS_NONPOW2CONDITIONAL */
                                    | D3DPTEXTURECAPS_PROJECTED
                                    | D3DPTEXTURECAPS_CUBEMAP
                                    | D3DPTEXTURECAPS_VOLUMEMAP
                                    | D3DPTEXTURECAPS_MIPMAP
                                    | D3DPTEXTURECAPS_MIPVOLUMEMAP
                                    | D3DPTEXTURECAPS_MIPCUBEMAP
                                 /* | D3DPTEXTURECAPS_CUBEMAP_POW2 */
                                 /* | D3DPTEXTURECAPS_VOLUMEMAP_POW2 */
                                 /* | D3DPTEXTURECAPS_NOPROJECTEDBUMPENV */;
    // Texture Filter Caps
    pCaps->TextureFilterCaps        = D3DPTFILTERCAPS_MINFPOINT
                                    | D3DPTFILTERCAPS_MINFLINEAR
                                    | D3DPTFILTERCAPS_MINFANISOTROPIC
                                 /* | D3DPTFILTERCAPS_MINFPYRAMIDALQUAD */
                                 /* | D3DPTFILTERCAPS_MINFGAUSSIANQUAD */
                                    | D3DPTFILTERCAPS_MIPFPOINT
                                    | D3DPTFILTERCAPS_MIPFLINEAR
                                 /* | D3DPTFILTERCAPS_CONVOLUTIONMONO */
                                    | D3DPTFILTERCAPS_MAGFPOINT
                                    | D3DPTFILTERCAPS_MAGFLINEAR
                                    | D3DPTFILTERCAPS_MAGFANISOTROPIC
                                 /* | D3DPTFILTERCAPS_MAGFPYRAMIDALQUAD */
                                 /* | D3DPTFILTERCAPS_MAGFGAUSSIANQUAD */;
    // Cube Texture Filter Caps
    pCaps->CubeTextureFilterCaps    = pCaps->TextureFilterCaps;
    // Volume Texture Filter Caps
    pCaps->VolumeTextureFilterCaps  = pCaps->TextureFilterCaps;
    // Texture Address Caps
    pCaps->TextureAddressCaps       = D3DPTADDRESSCAPS_WRAP
                                    | D3DPTADDRESSCAPS_MIRROR
                                    | D3DPTADDRESSCAPS_CLAMP
                                    | D3DPTADDRESSCAPS_BORDER
                                    | D3DPTADDRESSCAPS_INDEPENDENTUV
                                    | D3DPTADDRESSCAPS_MIRRORONCE;
    // Volume Texture Address Caps
    pCaps->VolumeTextureAddressCaps = pCaps->TextureAddressCaps;
    // Line Caps
    pCaps->LineCaps                 = D3DLINECAPS_TEXTURE
                                    | D3DLINECAPS_ZTEST
                                    | D3DLINECAPS_BLEND
                                    | D3DLINECAPS_ALPHACMP
                                    | D3DLINECAPS_FOG
                                    | D3DLINECAPS_ANTIALIAS; //<-- Lying about doing AA lines here, we don't *fully* support that.
    // Max Texture Width
    pCaps->MaxTextureWidth          = MaxTextureDimension;
    // Max Texture Height
    pCaps->MaxTextureHeight         = MaxTextureDimension;
    // Max Volume Extent
    pCaps->MaxVolumeExtent          = 8192;
    // Max Texture Repeat
    pCaps->MaxTextureRepeat         = 8192;
    // Max Texture Aspect Ratio
    pCaps->MaxTextureAspectRatio    = 8192;
    // Max Anisotropy
    pCaps->MaxAnisotropy            = 16;
    // Max Vertex W
    pCaps->MaxVertexW               = 1e10f;
    // Guard Bands
    pCaps->GuardBandLeft            = -32768.0f;
    pCaps->GuardBandTop             = -32768.0f;
    pCaps->GuardBandRight           =  32768.0f;
    pCaps->GuardBandBottom          =  32768.0f;
    // Extents Adjust
    pCaps->ExtentsAdjust            = 0.0f;
    // Stencil Caps
    pCaps->StencilCaps              = D3DSTENCILCAPS_KEEP
                                    | D3DSTENCILCAPS_ZERO
                                    | D3DSTENCILCAPS_REPLACE
                                    | D3DSTENCILCAPS_INCRSAT
                                    | D3DSTENCILCAPS_DECRSAT
                                    | D3DSTENCILCAPS_INVERT
                                    | D3DSTENCILCAPS_INCR
                                    | D3DSTENCILCAPS_DECR
                                    | D3DSTENCILCAPS_TWOSIDED;
    // FVF Caps
    pCaps->FVFCaps                  = (MaxSimultaneousTextures & D3DFVFCAPS_TEXCOORDCOUNTMASK)
                                 /* | D3DFVFCAPS_DONOTSTRIPELEMENTS */
                                    | D3DFVFCAPS_PSIZE;
    // Texture Op Caps
    pCaps->TextureOpCaps            = D3DTEXOPCAPS_DISABLE
                                    | D3DTEXOPCAPS_SELECTARG1
                                    | D3DTEXOPCAPS_SELECTARG2
                                    | D3DTEXOPCAPS_MODULATE
                                    | D3DTEXOPCAPS_MODULATE2X
                                    | D3DTEXOPCAPS_MODULATE4X
                                    | D3DTEXOPCAPS_ADD
                                    | D3DTEXOPCAPS_ADDSIGNED
                                    | D3DTEXOPCAPS_ADDSIGNED2X
                                    | D3DTEXOPCAPS_SUBTRACT
                                    | D3DTEXOPCAPS_ADDSMOOTH
                                    | D3DTEXOPCAPS_BLENDDIFFUSEALPHA
                                    | D3DTEXOPCAPS_BLENDTEXTUREALPHA
                                    | D3DTEXOPCAPS_BLENDFACTORALPHA
                                    | D3DTEXOPCAPS_BLENDTEXTUREALPHAPM
                                    | D3DTEXOPCAPS_BLENDCURRENTALPHA
                                    | D3DTEXOPCAPS_PREMODULATE
                                    | D3DTEXOPCAPS_MODULATEALPHA_ADDCOLOR
                                    | D3DTEXOPCAPS_MODULATECOLOR_ADDALPHA
                                    | D3DTEXOPCAPS_MODULATEINVALPHA_ADDCOLOR
                                    | D3DTEXOPCAPS_MODULATEINVCOLOR_ADDALPHA
                                    | D3DTEXOPCAPS_BUMPENVMAP
                                    | D3DTEXOPCAPS_BUMPENVMAPLUMINANCE
                                    | D3DTEXOPCAPS_DOTPRODUCT3
                                    | D3DTEXOPCAPS_MULTIPLYADD
                                    | D3DTEXOPCAPS_LERP;
    // Max Texture Blend Stages
    pCaps->MaxTextureBlendStages    = MaxTextureBlendStages;
    // Max Simultaneous Textures
    pCaps->MaxSimultaneousTextures  = MaxSimultaneousTextures;
    // Vertex Processing Caps
    pCaps->VertexProcessingCaps      = D3DVTXPCAPS_TEXGEN
                                     | D3DVTXPCAPS_MATERIALSOURCE7
                                     | D3DVTXPCAPS_DIRECTIONALLIGHTS
                                     | D3DVTXPCAPS_POSITIONALLIGHTS
                                     | D3DVTXPCAPS_LOCALVIEWER
                                     | D3DVTXPCAPS_TWEENING
                                     | D3DVTXPCAPS_TEXGEN_SPHEREMAP
                                  /* | D3DVTXPCAPS_NO_TEXGEN_NONLOCALVIEWER*/;
    // Max Active Lights
    pCaps->MaxActiveLights           = caps::MaxEnabledLights;
    // Max User Clip Planes
    pCaps->MaxUserClipPlanes         = MaxClipPlanes;
    // Max Vertex Blend Matrices
    pCaps->MaxVertexBlendMatrices    = 4;
    // Max Vertex Blend Matrix Index
    pCaps->MaxVertexBlendMatrixIndex = 8;
    // Max Point Size
    pCaps->MaxPointSize              = 256.0f;
    // Max Primitive Count
    pCaps->MaxPrimitiveCount         = 0x00555555;
    // Max Vertex Index
    pCaps->MaxVertexIndex            = 0x00ffffff;
    // Max Streams
    pCaps->MaxStreams                = MaxStreams;
    // Max Stream Stride
    pCaps->MaxStreamStride           = 508; // bytes

    const uint32_t majorVersion = options.shaderModel;
    const uint32_t minorVersion = options.shaderModel != 1 ? 0 : 4;

    // Shader Versions
    pCaps->VertexShaderVersion = D3DVS_VERSION(majorVersion, minorVersion);
    pCaps->PixelShaderVersion  = D3DPS_VERSION(majorVersion, minorVersion);

    // Max Vertex Shader Const
    pCaps->MaxVertexShaderConst       = MaxFloatConstantsVS;
    // Max PS1 Value
    pCaps->PixelShader1xMaxValue      = FLT_MAX;
    // Dev Caps 2
    pCaps->DevCaps2                   = D3DDEVCAPS2_STREAMOFFSET
                                   /* | D3DDEVCAPS2_DMAPNPATCH */
                                   /* | D3DDEVCAPS2_ADAPTIVETESSRTPATCH */
                                   /* | D3DDEVCAPS2_ADAPTIVETESSNPATCH */
                                      | D3DDEVCAPS2_CAN_STRETCHRECT_FROM_TEXTURES
                                   /* | D3DDEVCAPS2_PRESAMPLEDDMAPNPATCH */
                                      | D3DDEVCAPS2_VERTEXELEMENTSCANSHARESTREAMOFFSET;
    // Max N Patch Tesselation Level
    pCaps->MaxNpatchTessellationLevel = 0.0f;
    // Reserved for... something
    pCaps->Reserved5                  = 0;
    // Master adapter for us is adapter 0, atm... 
    pCaps->MasterAdapterOrdinal       = 0;
    // The group of adapters this one is in
    pCaps->AdapterOrdinalInGroup      = 0;
    // Number of adapters in current group
    pCaps->NumberOfAdaptersInGroup    = 1;
    // Decl Type Caps
    pCaps->DeclTypes                  = D3DDTCAPS_UBYTE4
                                      | D3DDTCAPS_UBYTE4N
                                      | D3DDTCAPS_SHORT2N
                                      | D3DDTCAPS_SHORT4N
                                      | D3DDTCAPS_USHORT2N
                                      | D3DDTCAPS_USHORT4N
                                      | D3DDTCAPS_UDEC3
                                      | D3DDTCAPS_DEC3N
                                      | D3DDTCAPS_FLOAT16_2
                                      | D3DDTCAPS_FLOAT16_4;
    // Number of simultaneous RTs
    pCaps->NumSimultaneousRTs         = MaxSimultaneousRenderTargets;
    // Possible StretchRect filters
    pCaps->StretchRectFilterCaps      = D3DPTFILTERCAPS_MINFPOINT
                                      | D3DPTFILTERCAPS_MINFLINEAR
                                   /* | D3DPTFILTERCAPS_MINFANISOTROPIC */
                                   /* | D3DPTFILTERCAPS_MINFPYRAMIDALQUAD */
                                   /* | D3DPTFILTERCAPS_MINFGAUSSIANQUAD */
                                   /* | D3DPTFILTERCAPS_MIPFPOINT */
                                   /* | D3DPTFILTERCAPS_MIPFLINEAR */
                                   /* | D3DPTFILTERCAPS_CONVOLUTIONMONO */
                                      | D3DPTFILTERCAPS_MAGFPOINT
                                      | D3DPTFILTERCAPS_MAGFLINEAR
                                   /* | D3DPTFILTERCAPS_MAGFANISOTROPIC */
                                   /* | D3DPTFILTERCAPS_MAGFPYRAMIDALQUAD */
                                   /* | D3DPTFILTERCAPS_MAGFGAUSSIANQUAD */;

    // Not too bothered about doing these longhand
    // We should match whatever my AMD hardware reports here
    // methinks for the best chance of stuff working.
    pCaps->VS20Caps.Caps                     = 1;
    pCaps->VS20Caps.DynamicFlowControlDepth  = 24;
    pCaps->VS20Caps.NumTemps                 = 32;
    pCaps->VS20Caps.StaticFlowControlDepth   = 4;

    pCaps->PS20Caps.Caps                     = 31;
    pCaps->PS20Caps.DynamicFlowControlDepth  = 24;
    pCaps->PS20Caps.NumTemps                 = 32;
    pCaps->PS20Caps.StaticFlowControlDepth   = 4;

    pCaps->PS20Caps.NumInstructionSlots      = options.shaderModel >= 2 ? 512 : 256;

    pCaps->VertexTextureFilterCaps           = 50332416;
    pCaps->MaxVShaderInstructionsExecuted    = 4294967295;
    pCaps->MaxPShaderInstructionsExecuted    = 4294967295;

    pCaps->MaxVertexShader30InstructionSlots = options.shaderModel == 3 ? 32768 : 0;
    pCaps->MaxPixelShader30InstructionSlots  = options.shaderModel == 3 ? 32768 : 0;

    return D3D_OK;
  }


  HMONITOR D3D9Adapter::GetMonitor() {
    return GetDefaultMonitor();
  }


  UINT D3D9Adapter::GetAdapterModeCountEx(const D3DDISPLAYMODEFILTER* pFilter) {
    if (pFilter == nullptr)
      return 0;

    // We don't offer any interlaced formats here so early out and avoid destroying mode cache.
    if (pFilter->ScanLineOrdering == D3DSCANLINEORDERING_INTERLACED)
      return 0;

    CacheModes(EnumerateFormat(pFilter->Format));
    return m_modes.size();
  }


  HRESULT D3D9Adapter::EnumAdapterModesEx(
    const D3DDISPLAYMODEFILTER* pFilter,
          UINT                  Mode,
          D3DDISPLAYMODEEX*     pMode) {
    if (pMode == nullptr || pFilter == nullptr)
      return D3DERR_INVALIDCALL;

    const D3D9Format format =
      EnumerateFormat(pFilter->Format);

    if (FAILED(CheckDeviceFormat(
      D3DDEVTYPE_HAL, EnumerateFormat(pFilter->Format),
      D3DUSAGE_RENDERTARGET, D3DRTYPE_SURFACE,
      EnumerateFormat(pFilter->Format))))
      return D3DERR_INVALIDCALL;

    CacheModes(format);

    // We don't return any scanline orderings that aren't progressive,
    // The format filtering is already handled for us by cache modes
    // So we can early out here and then just index.
    if (pFilter->ScanLineOrdering == D3DSCANLINEORDERING_INTERLACED)
      return D3DERR_INVALIDCALL;

    if (Mode >= m_modes.size())
      return D3DERR_INVALIDCALL;

    *pMode = m_modes[Mode];

    return D3D_OK;
  }


  HRESULT D3D9Adapter::GetAdapterDisplayModeEx(
          D3DDISPLAYMODEEX*   pMode,
          D3DDISPLAYROTATION* pRotation) {
    if (pMode == nullptr)
      return D3DERR_INVALIDCALL;

    if (pRotation != nullptr)
      *pRotation = D3DDISPLAYROTATION_IDENTITY;

    DEVMODEW devMode = DEVMODEW();
    devMode.dmSize = sizeof(devMode);

    if (!GetMonitorDisplayMode(GetDefaultMonitor(), ENUM_CURRENT_SETTINGS, &devMode)) {
      Logger::err("D3D9Adapter::GetAdapterDisplayModeEx: Failed to enum display settings");
      return D3DERR_INVALIDCALL;
    }

    pMode->Size             = sizeof(D3DDISPLAYMODEEX);
    pMode->Width            = devMode.dmPelsWidth;
    pMode->Height           = devMode.dmPelsHeight;
    pMode->RefreshRate      = devMode.dmDisplayFrequency;
    pMode->Format           = D3DFMT_X8R8G8B8;
    pMode->ScanLineOrdering = D3DSCANLINEORDERING_PROGRESSIVE;
    return D3D_OK;
  }


  HRESULT D3D9Adapter::GetAdapterLUID(LUID* pLUID) {
    if (pLUID == nullptr)
      return D3DERR_INVALIDCALL;

    auto& deviceId = m_adapter->devicePropertiesExt().coreDeviceId;

    if (deviceId.deviceLUIDValid)
      *pLUID = bit::cast<LUID>(deviceId.deviceLUID);
    else
      *pLUID = dxvk::GetAdapterLUID(m_ordinal);

    return D3D_OK;
  }


  HRESULT D3D9Adapter::CheckDeviceVkFormat(
          VkFormat        Format,
          DWORD           Usage,
          D3DRESOURCETYPE RType) {
    VkFormatFeatureFlags checkFlags = 0;

    if (RType != D3DRTYPE_SURFACE)
      checkFlags |= VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT;

    if (Usage & D3DUSAGE_RENDERTARGET) {
      checkFlags |= VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT;

      if (Usage & D3DUSAGE_QUERY_POSTPIXELSHADER_BLENDING)
        checkFlags |= VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT;
    }

    if (Usage & D3DUSAGE_DEPTHSTENCIL)
      checkFlags |= VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT;
    else
      checkFlags |= VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT;

    VkFormatFeatureFlags checkFlagsMipGen = checkFlags;

    if (Usage & D3DUSAGE_AUTOGENMIPMAP) {
      checkFlagsMipGen |= VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT;
      checkFlagsMipGen |= VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT;
    }

    VkFormatProperties   fmtSupport  = m_adapter->formatProperties(Format);
    VkFormatFeatureFlags imgFeatures = fmtSupport.optimalTilingFeatures | fmtSupport.linearTilingFeatures;

    if ((imgFeatures & checkFlags) != checkFlags)
      return D3DERR_NOTAVAILABLE;

    return ((imgFeatures & checkFlagsMipGen) != checkFlagsMipGen)
      ? D3DOK_NOAUTOGEN
      : D3D_OK;
  }


  void D3D9Adapter::CacheModes(D3D9Format Format) {
    if (!m_modes.empty() && m_modeCacheFormat == Format)
      return; // We already cached the modes for this format. No need to do it again.

    m_modes.clear();
    m_modeCacheFormat = Format;

    // Skip unsupported formats
    if (!IsSupportedAdapterFormat(Format))
      return;

    auto& options = m_parent->GetOptions();

    // Walk over all modes that the display supports and
    // return those that match the requested format etc.
    DEVMODEW devMode = { };
    devMode.dmSize = sizeof(DEVMODEW);

    uint32_t modeIndex = 0;

    const auto forcedRatio = Ratio<DWORD>(options.forceAspectRatio);

    while (GetMonitorDisplayMode(GetDefaultMonitor(), modeIndex++, &devMode)) {
      // Skip interlaced modes altogether
      if (devMode.dmDisplayFlags & DM_INTERLACED)
        continue;

      // Skip modes with incompatible formats
      if (devMode.dmBitsPerPel != GetMonitorFormatBpp(Format))
        continue;

      if (!forcedRatio.undefined() && Ratio<DWORD>(devMode.dmPelsWidth, devMode.dmPelsHeight) != forcedRatio)
        continue;

      D3DDISPLAYMODEEX mode;
      mode.Size             = sizeof(D3DDISPLAYMODEEX);
      mode.Width            = devMode.dmPelsWidth;
      mode.Height           = devMode.dmPelsHeight;
      mode.RefreshRate      = devMode.dmDisplayFrequency;
      mode.Format           = static_cast<D3DFORMAT>(Format);
      mode.ScanLineOrdering = D3DSCANLINEORDERING_PROGRESSIVE;

      m_modes.push_back(mode);
    }

    // Sort display modes by width, height and refresh rate,
    // in that order. Some games rely on correct ordering.
    std::sort(m_modes.begin(), m_modes.end(),
      [](const D3DDISPLAYMODEEX& a, const D3DDISPLAYMODEEX& b) {
        if (a.Width < b.Width)   return true;
        if (a.Width > b.Width)   return false;
        
        if (a.Height < b.Height) return true;
        if (a.Height > b.Height) return false;
        
        return a.RefreshRate < b.RefreshRate;
    });
  }

}
