code:
#include "d3d9_swapchain.h"
#include "d3d9_surface.h"
#include "d3d9_monitor.h"

#include "d3d9_hud.h"

namespace dxvk {

  struct D3D9WindowData {
    bool unicode;
    bool filter;
    bool activateProcessed;
    WNDPROC proc;
    D3D9SwapChainEx* swapchain;
  };

  static dxvk::recursive_mutex g_windowProcMapMutex;
  static std::unordered_map<HWND, D3D9WindowData> g_windowProcMap;

  static void SetActivateProcessed(HWND window, bool processed)
  {
      std::lock_guard lock(g_windowProcMapMutex);
      auto it = g_windowProcMap.find(window);
      if (it != g_windowProcMap.end())
        it->second.activateProcessed = processed;
  }

  template <typename T, typename J, typename ... Args>
  auto CallCharsetFunction(T unicode, J ascii, bool isUnicode, Args... args) {
    return isUnicode
      ? unicode(args...)
      : ascii  (args...);
  }

  class D3D9WindowMessageFilter {

  public:

    D3D9WindowMessageFilter(HWND window, bool filter = true)
      : m_window(window) {
      std::lock_guard lock(g_windowProcMapMutex);
      auto it = g_windowProcMap.find(m_window);
      m_filter = std::exchange(it->second.filter, filter);
    }

    ~D3D9WindowMessageFilter() {
      std::lock_guard lock(g_windowProcMapMutex);
      auto it = g_windowProcMap.find(m_window);
      it->second.filter = m_filter;
    }

    D3D9WindowMessageFilter             (const D3D9WindowMessageFilter&) = delete;
    D3D9WindowMessageFilter& operator = (const D3D9WindowMessageFilter&) = delete;

  private:

    HWND m_window;
    bool m_filter;

  };

  LRESULT CALLBACK D3D9WindowProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam);

  void ResetWindowProc(HWND window) {
    std::lock_guard lock(g_windowProcMapMutex);

    auto it = g_windowProcMap.find(window);
    if (it == g_windowProcMap.end())
      return;

    auto proc = reinterpret_cast<WNDPROC>(
      CallCharsetFunction(
      GetWindowLongPtrW, GetWindowLongPtrA, it->second.unicode,
        window, GWLP_WNDPROC));

    if (proc == D3D9WindowProc)
      CallCharsetFunction(
        SetWindowLongPtrW, SetWindowLongPtrA, it->second.unicode,
          window, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(it->second.proc));

    g_windowProcMap.erase(window);
  }

  void HookWindowProc(HWND window, D3D9SwapChainEx* swapchain) {
    std::lock_guard lock(g_windowProcMapMutex);

    ResetWindowProc(window);

    D3D9WindowData windowData;
    windowData.unicode = IsWindowUnicode(window);
    windowData.filter  = false;
    windowData.activateProcessed = false;
    windowData.proc = reinterpret_cast<WNDPROC>(
      CallCharsetFunction(
      SetWindowLongPtrW, SetWindowLongPtrA, windowData.unicode,
        window, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(D3D9WindowProc)));
    windowData.swapchain = swapchain;

    g_windowProcMap[window] = std::move(windowData);
  }

  LRESULT CALLBACK D3D9WindowProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) {
    if (message == WM_NCCALCSIZE && wparam == TRUE)
      return 0;

    D3D9WindowData windowData = {};

    {
      std::lock_guard lock(g_windowProcMapMutex);

      auto it = g_windowProcMap.find(window);
      if (it != g_windowProcMap.end())
        windowData = it->second;
    }

    bool unicode = windowData.proc
      ? windowData.unicode
      : IsWindowUnicode(window);

    if (!windowData.proc || windowData.filter)
      return CallCharsetFunction(
        DefWindowProcW, DefWindowProcA, unicode,
          window, message, wparam, lparam);

    if (message == WM_DESTROY)
      ResetWindowProc(window);
    else if (message == WM_ACTIVATEAPP) {
      D3DDEVICE_CREATION_PARAMETERS create_parms;
      windowData.swapchain->GetDevice()->GetCreationParameters(&create_parms);

      if (!(create_parms.BehaviorFlags & D3DCREATE_NOWINDOWCHANGES)) {
        D3D9WindowMessageFilter filter(window);
        if (wparam && !windowData.activateProcessed) {
          // Heroes of Might and Magic V needs this to resume drawing after a focus loss
          D3DPRESENT_PARAMETERS params;
          RECT rect;

          GetMonitorRect(GetDefaultMonitor(), &rect);
          windowData.swapchain->GetPresentParameters(&params);
          SetWindowPos(window, nullptr, rect.left, rect.top, params.BackBufferWidth, params.BackBufferHeight,
                       SWP_NOACTIVATE | SWP_NOZORDER);
          SetActivateProcessed(window, true);
        }
        else if (!wparam) {
          if (IsWindowVisible(window))
            ShowWindow(window, SW_MINIMIZE);
          SetActivateProcessed(window, false);
        }
      }
    }
    else if (message == WM_SIZE)
    {
      D3DDEVICE_CREATION_PARAMETERS create_parms;
      windowData.swapchain->GetDevice()->GetCreationParameters(&create_parms);

      if (!(create_parms.BehaviorFlags & D3DCREATE_NOWINDOWCHANGES) && !IsIconic(window))
        PostMessageW(window, WM_ACTIVATEAPP, 1, GetCurrentThreadId());
    }

    return CallCharsetFunction(
      CallWindowProcW, CallWindowProcA, unicode,
        windowData.proc, window, message, wparam, lparam);
  }

  static uint16_t MapGammaControlPoint(float x) {
    if (x < 0.0f) x = 0.0f;
    if (x > 1.0f) x = 1.0f;
    return uint16_t(65535.0f * x);
  }

  struct D3D9PresentInfo {
    float scale[2];
    float offset[2];
  };

  D3D9SwapChainEx::D3D9SwapChainEx(
          D3D9DeviceEx*          pDevice,
          D3DPRESENT_PARAMETERS* pPresentParams,
    const D3DDISPLAYMODEEX*      pFullscreenDisplayMode)
    : D3D9SwapChainExBase(pDevice)
    , m_device           (pDevice->GetDXVKDevice())
    , m_context          (m_device->createContext())
    , m_frameLatencyCap  (pDevice->GetOptions()->maxFrameLatency)
    , m_frameLatencySignal(new sync::Fence(m_frameId))
    , m_dialog           (pDevice->GetOptions()->enableDialogMode) {
    this->NormalizePresentParameters(pPresentParams);
    m_presentParams = *pPresentParams;
    m_window = m_presentParams.hDeviceWindow;

    UpdatePresentRegion(nullptr, nullptr);
    if (m_window && !pDevice->GetOptions()->deferSurfaceCreation)
      CreatePresenter();

    CreateBackBuffers(m_presentParams.BackBufferCount);
    CreateBlitter();
    CreateHud();

    InitRamp();

    // Apply initial window mode and fullscreen state
    if (!m_presentParams.Windowed && FAILED(EnterFullscreenMode(pPresentParams, pFullscreenDisplayMode)))
      throw DxvkError("D3D9: Failed to set initial fullscreen state");
  }

  D3D9SwapChainEx::~D3D9SwapChainEx() {
    // Avoids hanging when in this state, see comment
    // in DxvkDevice::~DxvkDevice.
    if (this_thread::isInModuleDetachment())
      return;

    DestroyBackBuffers();

    ResetWindowProc(m_window);
    RestoreDisplayMode(m_monitor);

    m_device->waitForSubmission(&m_presentStatus);
    m_device->waitForIdle();
  }

  HRESULT STDMETHODCALLTYPE D3D9SwapChainEx::QueryInterface(REFIID riid, void** ppvObject) {
    if (ppvObject == nullptr)
      return E_POINTER;

    *ppvObject = nullptr;

    if (riid == __uuidof(IUnknown)
     || riid == __uuidof(IDirect3DSwapChain9)
     || (GetParent()->IsExtended() && riid == __uuidof(IDirect3DSwapChain9Ex))) {
      *ppvObject = ref(this);
      return S_OK;
    }

    Logger::warn("D3D9SwapChainEx::QueryInterface: Unknown interface query");
    Logger::warn(str::format(riid));
    return E_NOINTERFACE;
  }

  HRESULT STDMETHODCALLTYPE D3D9SwapChainEx::Present(
    const RECT*    pSourceRect,
    const RECT*    pDestRect,
          HWND     hDestWindowOverride,
    const RGNDATA* pDirtyRegion,
          DWORD    dwFlags) {
    D3D9DeviceLock lock = m_parent->LockDevice();

    uint32_t presentInterval = m_presentParams.PresentationInterval;

    // This is not true directly in d3d9 to to timing differences that don't matter for us.
    // For our purposes...
    // D3DPRESENT_INTERVAL_DEFAULT (0) == D3DPRESENT_INTERVAL_ONE (1) which means VSYNC.
    presentInterval = std::max(presentInterval, 1u);

    if (presentInterval == D3DPRESENT_INTERVAL_IMMEDIATE || (dwFlags & D3DPRESENT_FORCEIMMEDIATE))
      presentInterval = 0;

    auto options = m_parent->GetOptions();

    if (options->presentInterval >= 0)
      presentInterval = options->presentInterval;

    bool vsync  = presentInterval != 0;

    HWND window = m_presentParams.hDeviceWindow;
    if (hDestWindowOverride != nullptr)
      window    = hDestWindowOverride;

    bool recreate = false;
    recreate   |= m_presenter == nullptr;
    recreate   |= window != m_window;
    recreate   |= m_dialog != m_lastDialog;

    m_window    = window;

    m_dirty    |= vsync != m_vsync;
    m_dirty    |= UpdatePresentRegion(pSourceRect, pDestRect);
    m_dirty    |= recreate;
    m_dirty    |= m_presenter != nullptr &&
                 !m_presenter->hasSwapChain();

    m_vsync     = vsync;

    m_lastDialog = m_dialog;

    try {
      if (recreate)
        CreatePresenter();

      if (std::exchange(m_dirty, false))
        RecreateSwapChain(vsync);

      // We aren't going to device loss simply because
      // 99% of D3D9 games don't handle this properly and
      // just end up crashing (like with alt-tab loss)
      if (!m_presenter->hasSwapChain())
        return D3D_OK;

      PresentImage(presentInterval);
      return D3D_OK;
    } catch (const DxvkError& e) {
      Logger::err(e.message());
      return D3DERR_DEVICEREMOVED;
    }
  }

  HRESULT STDMETHODCALLTYPE D3D9SwapChainEx::GetFrontBufferData(IDirect3DSurface9* pDestSurface) {
    D3D9DeviceLock lock = m_parent->LockDevice();

    // This function can do absolutely everything!
    // Copies the front buffer between formats with an implicit resolve.
    // Oh, and the dest is systemmem...
    // This is a slow function anyway, it waits for the copy to finish.
    // so there's no reason to not just make and throwaway temp images.

    // If extent of dst > src, then we blit to a subrect of the size
    // of src onto a temp image of dst's extents,
    // then copy buffer back to dst (given dst is subresource)

    D3D9Surface* dst = static_cast<D3D9Surface*>(pDestSurface);

    if (unlikely(dst == nullptr))
      return D3DERR_INVALIDCALL;

    D3D9CommonTexture* dstTexInfo = dst->GetCommonTexture();
    D3D9CommonTexture* srcTexInfo = m_backBuffers.back()->GetCommonTexture();

    if (unlikely(dstTexInfo->Desc()->Pool != D3DPOOL_SYSTEMMEM && dstTexInfo->Desc()->Pool != D3DPOOL_SCRATCH))
      return D3DERR_INVALIDCALL;

    Rc<DxvkBuffer> dstBuffer = dstTexInfo->GetBuffer(dst->GetSubresource());
    Rc<DxvkImage>  srcImage  = srcTexInfo->GetImage();

    if (srcImage->info().sampleCount != VK_SAMPLE_COUNT_1_BIT) {
      DxvkImageCreateInfo resolveInfo;
      resolveInfo.type          = VK_IMAGE_TYPE_2D;
      resolveInfo.format        = srcImage->info().format;
      resolveInfo.flags         = 0;
      resolveInfo.sampleCount   = VK_SAMPLE_COUNT_1_BIT;
      resolveInfo.extent        = srcImage->info().extent;
      resolveInfo.numLayers     = 1;
      resolveInfo.mipLevels     = 1;
      resolveInfo.usage         = VK_IMAGE_USAGE_SAMPLED_BIT
                                | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
                                | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
      resolveInfo.stages        = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
                                | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
                                | VK_PIPELINE_STAGE_TRANSFER_BIT;
      resolveInfo.access        = VK_ACCESS_SHADER_READ_BIT
                                | VK_ACCESS_TRANSFER_WRITE_BIT
                                | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT
                                | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
      resolveInfo.tiling        = VK_IMAGE_TILING_OPTIMAL;
      resolveInfo.layout        = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;

      Rc<DxvkImage> resolvedSrc = m_device->createImage(
        resolveInfo, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);

      m_parent->EmitCs([
        cDstImage = resolvedSrc,
        cSrcImage = srcImage
      ] (DxvkContext* ctx) {
        VkImageSubresourceLayers resolveSubresource;
        resolveSubresource.aspectMask      = VK_IMAGE_ASPECT_COLOR_BIT;
        resolveSubresource.mipLevel        = 0;
        resolveSubresource.baseArrayLayer  = 0;
        resolveSubresource.layerCount      = 1;

        VkImageResolve resolveRegion;
        resolveRegion.srcSubresource = resolveSubresource;
        resolveRegion.srcOffset      = VkOffset3D { 0, 0, 0 };
        resolveRegion.dstSubresource = resolveSubresource;
        resolveRegion.dstOffset      = VkOffset3D { 0, 0, 0 };
        resolveRegion.extent         = cSrcImage->info().extent;

        ctx->resolveImage(
          cDstImage, cSrcImage,
          resolveRegion, VK_FORMAT_UNDEFINED);
      });

      srcImage = std::move(resolvedSrc);
    }

    D3D9Format srcFormat = srcTexInfo->Desc()->Format;
    D3D9Format dstFormat = dstTexInfo->Desc()->Format;

    bool similar = AreFormatsSimilar(srcFormat, dstFormat);

    if (!similar || srcImage->info().extent != dstTexInfo->GetExtent()) {
      DxvkImageCreateInfo blitCreateInfo;
      blitCreateInfo.type          = VK_IMAGE_TYPE_2D;
      blitCreateInfo.format        = dstTexInfo->GetFormatMapping().FormatColor;
      blitCreateInfo.flags         = 0;
      blitCreateInfo.sampleCount   = VK_SAMPLE_COUNT_1_BIT;
      blitCreateInfo.extent        = dstTexInfo->GetExtent();
      blitCreateInfo.numLayers     = 1;
      blitCreateInfo.mipLevels     = 1;
      blitCreateInfo.usage         = VK_IMAGE_USAGE_SAMPLED_BIT
                                   | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
                                   | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
      blitCreateInfo.stages        = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
                                   | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
                                   | VK_PIPELINE_STAGE_TRANSFER_BIT;
      blitCreateInfo.access        = VK_ACCESS_SHADER_READ_BIT
                                   | VK_ACCESS_TRANSFER_WRITE_BIT
                                   | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT
                                   | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
      blitCreateInfo.tiling        = VK_IMAGE_TILING_OPTIMAL;
      blitCreateInfo.layout        = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;

      Rc<DxvkImage> blittedSrc = m_device->createImage(
        blitCreateInfo, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);

      const DxvkFormatInfo* dstFormatInfo = imageFormatInfo(blittedSrc->info().format);
      const DxvkFormatInfo* srcFormatInfo = imageFormatInfo(srcImage->info().format);

      const VkImageSubresource dstSubresource = dstTexInfo->GetSubresourceFromIndex(dstFormatInfo->aspectMask, 0);
      const VkImageSubresource srcSubresource = srcTexInfo->GetSubresourceFromIndex(srcFormatInfo->aspectMask, 0);

      VkImageSubresourceLayers dstSubresourceLayers = {
        dstSubresource.aspectMask,
        dstSubresource.mipLevel,
        dstSubresource.arrayLayer, 1 };

      VkImageSubresourceLayers srcSubresourceLayers = {
        srcSubresource.aspectMask,
        srcSubresource.mipLevel,
        srcSubresource.arrayLayer, 1 };

      VkExtent3D srcExtent = srcImage->mipLevelExtent(srcSubresource.mipLevel);

      // Blit to a subrect of the src extents
      VkImageBlit blitInfo;
      blitInfo.dstSubresource = dstSubresourceLayers;
      blitInfo.srcSubresource = srcSubresourceLayers;
      blitInfo.dstOffsets[0] = VkOffset3D{ 0, 0, 0 };
      blitInfo.dstOffsets[1] = VkOffset3D{ int32_t(srcExtent.width),  int32_t(srcExtent.height),  1 };
      blitInfo.srcOffsets[0] = VkOffset3D{ 0, 0, 0 };
      blitInfo.srcOffsets[1] = VkOffset3D{ int32_t(srcExtent.width),  int32_t(srcExtent.height),  1 };

      m_parent->EmitCs([
        cDstImage = blittedSrc,
        cDstMap   = dstTexInfo->GetMapping().Swizzle,
        cSrcImage = srcImage,
        cSrcMap   = srcTexInfo->GetMapping().Swizzle,
        cBlitInfo = blitInfo
      ] (DxvkContext* ctx) {
        ctx->blitImage(
          cDstImage, cDstMap,
          cSrcImage, cSrcMap,
          cBlitInfo, VK_FILTER_NEAREST);
      });

      srcImage = std::move(blittedSrc);
    }

    const DxvkFormatInfo* srcFormatInfo = imageFormatInfo(srcImage->info().format);
    const VkImageSubresource srcSubresource = srcTexInfo->GetSubresourceFromIndex(srcFormatInfo->aspectMask, 0);
    VkImageSubresourceLayers srcSubresourceLayers = {
      srcSubresource.aspectMask,
      srcSubresource.mipLevel,
      srcSubresource.arrayLayer, 1 };
    VkExtent3D srcExtent = srcImage->mipLevelExtent(srcSubresource.mipLevel);

    m_parent->EmitCs([
      cBuffer       = dstBuffer,
      cImage        = srcImage,
      cSubresources = srcSubresourceLayers,
      cLevelExtent  = srcExtent
    ] (DxvkContext* ctx) {
      ctx->copyImageToBuffer(cBuffer, 0, 4, 0,
        cImage, cSubresources, VkOffset3D { 0, 0, 0 },
        cLevelExtent);
    });

    dstTexInfo->SetNeedsReadback(dst->GetSubresource(), true);
    m_parent->TrackTextureMappingBufferSequenceNumber(dstTexInfo, dst->GetSubresource());

    return D3D_OK;
  }

  HRESULT STDMETHODCALLTYPE D3D9SwapChainEx::GetBackBuffer(
          UINT                iBackBuffer,
          D3DBACKBUFFER_TYPE  Type,
          IDirect3DSurface9** ppBackBuffer) {
    // Could be doing a device reset...
    D3D9DeviceLock lock = m_parent->LockDevice();

    if (unlikely(ppBackBuffer == nullptr))
      return D3DERR_INVALIDCALL;

    if (unlikely(iBackBuffer >= m_presentParams.BackBufferCount)) {
      Logger::err(str::format("D3D9: GetBackBuffer: Invalid back buffer index: ", iBackBuffer));
      return D3DERR_INVALIDCALL;
    }

    *ppBackBuffer = ref(m_backBuffers[iBackBuffer].ptr());
    return D3D_OK;
  }

  HRESULT STDMETHODCALLTYPE D3D9SwapChainEx::GetRasterStatus(D3DRASTER_STATUS* pRasterStatus) {
    // We could use D3DKMTGetScanLine but Wine doesn't implement that.
    // So... we lie here and make some stuff up
    // enough that it makes games work.

    // Assume there's 20 lines in a vBlank.
    constexpr uint32_t vBlankLineCount = 20;

    if (pRasterStatus == nullptr)
      return D3DERR_INVALIDCALL;

    D3DDISPLAYMODEEX mode;
    mode.Size = sizeof(mode);
    if (FAILED(this->GetDisplayModeEx(&mode, nullptr)))
      return D3DERR_INVALIDCALL;

    uint32_t scanLineCount = mode.Height + vBlankLineCount;

    auto nowUs = std::chrono::time_point_cast<std::chrono::microseconds>(
      dxvk::high_resolution_clock::now())
      .time_since_epoch();

    auto frametimeUs = std::chrono::microseconds(1000000u / mode.RefreshRate);
    auto scanLineUs  = frametimeUs / scanLineCount;

    pRasterStatus->ScanLine = (nowUs % frametimeUs) / scanLineUs;
    pRasterStatus->InVBlank = pRasterStatus->ScanLine >= mode.Height;

    if (pRasterStatus->InVBlank)
      pRasterStatus->ScanLine = 0;

    return D3D_OK;
  }

  HRESULT STDMETHODCALLTYPE D3D9SwapChainEx::GetDisplayMode(D3DDISPLAYMODE* pMode) {
    if (pMode == nullptr)
      return D3DERR_INVALIDCALL;

    *pMode = D3DDISPLAYMODE();

    D3DDISPLAYMODEEX mode;
    mode.Size = sizeof(mode);
    HRESULT hr = this->GetDisplayModeEx(&mode, nullptr);

    if (FAILED(hr))
      return hr;

    pMode->Width       = mode.Width;
    pMode->Height      = mode.Height;
    pMode->Format      = mode.Format;
    pMode->RefreshRate = mode.RefreshRate;

    return D3D_OK;
  }

  HRESULT STDMETHODCALLTYPE D3D9SwapChainEx::GetPresentParameters(D3DPRESENT_PARAMETERS* pPresentationParameters) {
    if (pPresentationParameters == nullptr)
      return D3DERR_INVALIDCALL;

    *pPresentationParameters = m_presentParams;

    return D3D_OK;
  }

  HRESULT STDMETHODCALLTYPE D3D9SwapChainEx::GetLastPresentCount(UINT* pLastPresentCount) {
    Logger::warn("D3D9SwapChainEx::GetLastPresentCount: Stub");
    return D3D_OK;
  }

  HRESULT STDMETHODCALLTYPE D3D9SwapChainEx::GetPresentStats(D3DPRESENTSTATS* pPresentationStatistics) {
    Logger::warn("D3D9SwapChainEx::GetPresentStats: Stub");
    return D3D_OK;
  }

  HRESULT STDMETHODCALLTYPE D3D9SwapChainEx::GetDisplayModeEx(D3DDISPLAYMODEEX* pMode, D3DDISPLAYROTATION* pRotation) {
    if (pMode == nullptr && pRotation == nullptr)
      return D3DERR_INVALIDCALL;

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

    if (pMode != nullptr) {
      DEVMODEW devMode = DEVMODEW();
      devMode.dmSize = sizeof(devMode);

      if (!GetMonitorDisplayMode(GetDefaultMonitor(), ENUM_CURRENT_SETTINGS, &devMode)) {
        Logger::err("D3D9SwapChainEx::GetDisplayModeEx: 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 D3D9SwapChainEx::Reset(
          D3DPRESENT_PARAMETERS* pPresentParams,
          D3DDISPLAYMODEEX*      pFullscreenDisplayMode) {
    D3D9DeviceLock lock = m_parent->LockDevice();

    this->SynchronizePresent();
    this->NormalizePresentParameters(pPresentParams);

    m_dirty    |= m_presentParams.BackBufferFormat   != pPresentParams->BackBufferFormat
               || m_presentParams.BackBufferCount    != pPresentParams->BackBufferCount;

    bool changeFullscreen = m_presentParams.Windowed != pPresentParams->Windowed;

    if (pPresentParams->Windowed) {
      if (changeFullscreen)
        this->LeaveFullscreenMode();
    }
    else {
      if (changeFullscreen) {
        if (FAILED(this->EnterFullscreenMode(pPresentParams, pFullscreenDisplayMode)))
          return D3DERR_INVALIDCALL;
      }

      D3D9WindowMessageFilter filter(m_window);

      if (!changeFullscreen) {
        if (FAILED(ChangeDisplayMode(pPresentParams, pFullscreenDisplayMode)))
          return D3DERR_INVALIDCALL;
      }

      // Move the window so that it covers the entire output
      RECT rect;
      GetMonitorRect(GetDefaultMonitor(), &rect);

      ::SetWindowPos(m_window, HWND_TOPMOST,
        rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
        SWP_FRAMECHANGED | SWP_SHOWWINDOW | SWP_NOACTIVATE);
    }

    m_presentParams = *pPresentParams;

    if (changeFullscreen)
      SetGammaRamp(0, &m_ramp);

    CreateBackBuffers(m_presentParams.BackBufferCount);

    return D3D_OK;
  }

  HRESULT D3D9SwapChainEx::WaitForVBlank() {
    static bool s_errorShown = false;

    if (!std::exchange(s_errorShown, true))
      Logger::warn("D3D9SwapChainEx::WaitForVBlank: Stub");

    return D3D_OK;
  }

  static bool validateGammaRamp(const WORD (&ramp)[256]) {
    if (ramp[0] >= ramp[std::size(ramp) - 1]) {
      Logger::err("validateGammaRamp: ramp inverted or flat");
      return false;
    }

    for (size_t i = 1; i < std::size(ramp); i++) {
      if (ramp[i] < ramp[i - 1]) {
        Logger::err("validateGammaRamp: ramp not monotonically increasing");
        return false;
      }
      if (ramp[i] - ramp[i - 1] >= UINT16_MAX / 2) {
        Logger::err("validateGammaRamp: huuuge jump");
        return false;
      }
    }

    return true;
  }

  void    D3D9SwapChainEx::SetGammaRamp(
            DWORD         Flags,
      const D3DGAMMARAMP* pRamp) {
    D3D9DeviceLock lock = m_parent->LockDevice();

    if (unlikely(pRamp == nullptr))
      return;

    if (unlikely(!validateGammaRamp(pRamp->red)
              && !validateGammaRamp(pRamp->blue)
              && !validateGammaRamp(pRamp->green)))
      return;

    m_ramp = *pRamp;

    bool isIdentity = true;

    std::array<DxvkGammaCp, NumControlPoints> cp;

    for (uint32_t i = 0; i < NumControlPoints; i++) {
      uint16_t identity = MapGammaControlPoint(float(i) / float(NumControlPoints - 1));

      cp[i].r = pRamp->red[i];
      cp[i].g = pRamp->green[i];
      cp[i].b = pRamp->blue[i];
      cp[i].a = 0;

      isIdentity &= cp[i].r == identity
                 && cp[i].g == identity
                 && cp[i].b == identity;
    }

    if (!isIdentity && !m_presentParams.Windowed)
      m_blitter->setGammaRamp(NumControlPoints, cp.data());
    else
      m_blitter->setGammaRamp(0, nullptr);
  }

  void    D3D9SwapChainEx::GetGammaRamp(D3DGAMMARAMP* pRamp) {
    D3D9DeviceLock lock = m_parent->LockDevice();

    if (likely(pRamp != nullptr))
      *pRamp = m_ramp;
  }

  void    D3D9SwapChainEx::Invalidate(HWND hWindow) {
    if (hWindow == nullptr)
      hWindow = m_parent->GetWindow();

    if (m_presentParams.hDeviceWindow == hWindow) {
      m_presenter = nullptr;

      m_device->waitForSubmission(&m_presentStatus);
      m_device->waitForIdle();
    }
  }

  HRESULT D3D9SwapChainEx::SetDialogBoxMode(bool bEnableDialogs) {
    D3D9DeviceLock lock = m_parent->LockDevice();

    // https://docs.microsoft.com/en-us/windows/win32/api/d3d9/nf-d3d9-idirect3ddevice9-setdialogboxmode
    // The MSDN documentation says this will error out under many weird conditions.
    // However it doesn't appear to error at all in any of my tests of these
    // cases described in the documentation.

    m_dialog = bEnableDialogs;

    return D3D_OK;
  }

  D3D9Surface* D3D9SwapChainEx::GetBackBuffer(UINT iBackBuffer) {
    if (iBackBuffer >= m_presentParams.BackBufferCount)
      return nullptr;

    return m_backBuffers[iBackBuffer].ptr();
  }

  void D3D9SwapChainEx::NormalizePresentParameters(D3DPRESENT_PARAMETERS* pPresentParams) {
    if (pPresentParams->hDeviceWindow == nullptr)
      pPresentParams->hDeviceWindow    = m_parent->GetWindow();

    pPresentParams->BackBufferCount    = std::max(pPresentParams->BackBufferCount, 1u);

    const int32_t forcedMSAA = m_parent->GetOptions()->forceSwapchainMSAA;
    if (forcedMSAA != -1) {
      pPresentParams->MultiSampleType    = D3DMULTISAMPLE_TYPE(forcedMSAA);
      pPresentParams->MultiSampleQuality = 0;
    }

    if (pPresentParams->Windowed) {
      GetWindowClientSize(pPresentParams->hDeviceWindow,
        pPresentParams->BackBufferWidth  ? nullptr : &pPresentParams->BackBufferWidth,
        pPresentParams->BackBufferHeight ? nullptr : &pPresentParams->BackBufferHeight);
    }
    else {
      GetMonitorClientSize(GetDefaultMonitor(),
        pPresentParams->BackBufferWidth  ? nullptr : &pPresentParams->BackBufferWidth,
        pPresentParams->BackBufferHeight ? nullptr : &pPresentParams->BackBufferHeight);
    }

    if (pPresentParams->BackBufferFormat == D3DFMT_UNKNOWN)
      pPresentParams->BackBufferFormat = D3DFMT_X8R8G8B8;

    if (env::getEnvVar("DXVK_FORCE_WINDOWED") == "1")
      pPresentParams->Windowed         = TRUE;
  }

  void D3D9SwapChainEx::PresentImage(UINT SyncInterval) {
    m_parent->Flush();

    // Retrieve the image and image view to present
    auto swapImage = m_backBuffers[0]->GetCommonTexture()->GetImage();
    auto swapImageView = m_backBuffers[0]->GetImageView(false);

    // Bump our frame id.
    ++m_frameId;

    for (uint32_t i = 0; i < SyncInterval || i < 1; i++) {
      SynchronizePresent();

      // Presentation semaphores and WSI swap chain image
      vk::PresenterInfo info = m_presenter->info();
      vk::PresenterSync sync;

      uint32_t imageIndex = 0;

      VkResult status = m_presenter->acquireNextImage(sync, imageIndex);

      while (status != VK_SUCCESS && status != VK_SUBOPTIMAL_KHR) {
        RecreateSwapChain(m_vsync);

        info = m_presenter->info();
        status = m_presenter->acquireNextImage(sync, imageIndex);
      }

      m_context->beginRecording(
        m_device->createCommandList());

      VkRect2D srcRect = {
        {  int32_t(m_srcRect.left),                    int32_t(m_srcRect.top)                    },
        { uint32_t(m_srcRect.right - m_srcRect.left), uint32_t(m_srcRect.bottom - m_srcRect.top) } };

      VkRect2D dstRect = {
        {  int32_t(m_dstRect.left),                    int32_t(m_dstRect.top)                    },
        { uint32_t(m_dstRect.right - m_dstRect.left), uint32_t(m_dstRect.bottom - m_dstRect.top) } };

      m_blitter->presentImage(m_context.ptr(),
        m_imageViews.at(imageIndex), dstRect,
        swapImageView, srcRect);

      if (m_hud != nullptr)
        m_hud->render(m_context, info.format, info.imageExtent);

      if (i + 1 >= SyncInterval)
        m_context->signal(m_frameLatencySignal, m_frameId);

      SubmitPresent(sync, i);
    }

    SyncFrameLatency();

    // Rotate swap chain buffers so that the back
    // buffer at index 0 becomes the front buffer.
    for (uint32_t i = 1; i < m_backBuffers.size(); i++)
      m_backBuffers[i]->Swap(m_backBuffers[i - 1].ptr());

    m_parent->m_flags.set(D3D9DeviceFlag::DirtyFramebuffer);
  }

  void D3D9SwapChainEx::SubmitPresent(const vk::PresenterSync& Sync, uint32_t FrameId) {
    // Present from CS thread so that we don't
    // have to synchronize with it first.
    m_presentStatus.result = VK_NOT_READY;

    m_parent->EmitCs([this,
      cFrameId     = FrameId,
      cSync        = Sync,
      cHud         = m_hud,
      cCommandList = m_context->endRecording()
    ] (DxvkContext* ctx) {
      m_device->submitCommandList(cCommandList,
        cSync.acquire, cSync.present);

      if (cHud != nullptr && !cFrameId)
        cHud->update();

      m_device->presentImage(m_presenter, &m_presentStatus);
    });

    m_parent->FlushCsChunk();
  }

  void D3D9SwapChainEx::SynchronizePresent() {
    // Recreate swap chain if the previous present call failed
    VkResult status = m_device->waitForSubmission(&m_presentStatus);

    if (status != VK_SUCCESS)
      RecreateSwapChain(m_vsync);
  }

  void D3D9SwapChainEx::RecreateSwapChain(BOOL Vsync) {
    // Ensure that we can safely destroy the swap chain
    m_device->waitForSubmission(&m_presentStatus);
    m_device->waitForIdle();

    m_presentStatus.result = VK_SUCCESS;

    vk::PresenterDesc presenterDesc;
    presenterDesc.imageExtent     = GetPresentExtent();
    presenterDesc.imageCount      = PickImageCount(m_presentParams.BackBufferCount + 1);
    presenterDesc.numFormats      = PickFormats(EnumerateFormat(m_presentParams.BackBufferFormat), presenterDesc.formats);
    presenterDesc.numPresentModes = PickPresentModes(Vsync, presenterDesc.presentModes);
    presenterDesc.fullScreenExclusive = PickFullscreenMode();

    if (m_presenter->recreateSwapChain(presenterDesc) != VK_SUCCESS)
      throw DxvkError("D3D9SwapChainEx: Failed to recreate swap chain");

    CreateRenderTargetViews();
  }

  void D3D9SwapChainEx::CreatePresenter() {
    // Ensure that we can safely destroy the swap chain
    m_device->waitForSubmission(&m_presentStatus);
    m_device->waitForIdle();

    m_presentStatus.result = VK_SUCCESS;

    DxvkDeviceQueue graphicsQueue = m_device->queues().graphics;

    vk::PresenterDevice presenterDevice;
    presenterDevice.queueFamily   = graphicsQueue.queueFamily;
    presenterDevice.queue         = graphicsQueue.queueHandle;
    presenterDevice.adapter       = m_device->adapter()->handle();

    vk::PresenterDesc presenterDesc;
    presenterDesc.imageExtent     = GetPresentExtent();
    presenterDesc.imageCount      = PickImageCount(m_presentParams.BackBufferCount + 1);
    presenterDesc.numFormats      = PickFormats(EnumerateFormat(m_presentParams.BackBufferFormat), presenterDesc.formats);
    presenterDesc.numPresentModes = PickPresentModes(false, presenterDesc.presentModes);
    presenterDesc.fullScreenExclusive = PickFullscreenMode();

    m_presenter = new vk::Presenter(m_window,
      m_device->adapter()->vki(),
      m_device->vkd(),
      presenterDevice,
      presenterDesc);

    m_presenter->setFrameRateLimit(m_parent->GetOptions()->maxFrameRate);

    CreateRenderTargetViews();
  }

  void D3D9SwapChainEx::CreateRenderTargetViews() {
    vk::PresenterInfo info = m_presenter->info();

    m_imageViews.clear();
    m_imageViews.resize(info.imageCount);

    DxvkImageCreateInfo imageInfo;
    imageInfo.type        = VK_IMAGE_TYPE_2D;
    imageInfo.format      = info.format.format;
    imageInfo.flags       = 0;
    imageInfo.sampleCount = VK_SAMPLE_COUNT_1_BIT;
    imageInfo.extent      = { info.imageExtent.width, info.imageExtent.height, 1 };
    imageInfo.numLayers   = 1;
    imageInfo.mipLevels   = 1;
    imageInfo.usage       = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
    imageInfo.stages      = 0;
    imageInfo.access      = 0;
    imageInfo.tiling      = VK_IMAGE_TILING_OPTIMAL;
    imageInfo.layout      = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
    imageInfo.shared      = VK_TRUE;

    DxvkImageViewCreateInfo viewInfo;
    viewInfo.type         = VK_IMAGE_VIEW_TYPE_2D;
    viewInfo.format       = info.format.format;
    viewInfo.usage        = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
    viewInfo.aspect       = VK_IMAGE_ASPECT_COLOR_BIT;
    viewInfo.minLevel     = 0;
    viewInfo.numLevels    = 1;
    viewInfo.minLayer     = 0;
    viewInfo.numLayers    = 1;

    for (uint32_t i = 0; i < info.imageCount; i++) {
      VkImage imageHandle = m_presenter->getImage(i).image;

      Rc<DxvkImage> image = new DxvkImage(
        m_device.ptr(), imageInfo, imageHandle);

      m_imageViews[i] = new DxvkImageView(
        m_device->vkd(), image, viewInfo);
    }
  }

  void D3D9SwapChainEx::DestroyBackBuffers() {
    for (auto& backBuffer : m_backBuffers)
      backBuffer->ClearContainer();

    m_backBuffers.clear();
  }

  void D3D9SwapChainEx::CreateBackBuffers(uint32_t NumBackBuffers) {
    // Explicitly destroy current swap image before
    // creating a new one to free up resources
    DestroyBackBuffers();

    int NumFrontBuffer = m_parent->GetOptions()->noExplicitFrontBuffer ? 0 : 1;
    m_backBuffers.resize(NumBackBuffers + NumFrontBuffer);

    // Create new back buffer
    D3D9_COMMON_TEXTURE_DESC desc;
    desc.Width              = std::max(m_presentParams.BackBufferWidth,  1u);
    desc.Height             = std::max(m_presentParams.BackBufferHeight, 1u);
    desc.Depth              = 1;
    desc.MipLevels          = 1;
    desc.ArraySize          = 1;
    desc.Format             = EnumerateFormat(m_presentParams.BackBufferFormat);
    desc.MultiSample        = m_presentParams.MultiSampleType;
    desc.MultisampleQuality = m_presentParams.MultiSampleQuality;
    desc.Pool               = D3DPOOL_DEFAULT;
    desc.Usage              = D3DUSAGE_RENDERTARGET;
    desc.Discard            = FALSE;
    desc.IsBackBuffer       = TRUE;
    desc.IsAttachmentOnly   = FALSE;

    for (uint32_t i = 0; i < m_backBuffers.size(); i++)
      m_backBuffers[i] = new D3D9Surface(m_parent, &desc, this, nullptr);

    auto swapImage = m_backBuffers[0]->GetCommonTexture()->GetImage();

    // Initialize the image so that we can use it. Clearing
    // to black prevents garbled output for the first frame.
    VkImageSubresourceRange subresources;
    subresources.aspectMask     = VK_IMAGE_ASPECT_COLOR_BIT;
    subresources.baseMipLevel   = 0;
    subresources.levelCount     = 1;
    subresources.baseArrayLayer = 0;
    subresources.layerCount     = 1;

    m_context->beginRecording(
      m_device->createCommandList());

    for (uint32_t i = 0; i < m_backBuffers.size(); i++) {
      m_context->initImage(
        m_backBuffers[i]->GetCommonTexture()->GetImage(),
        subresources, VK_IMAGE_LAYOUT_UNDEFINED);
    }

    m_device->submitCommandList(
      m_context->endRecording(),
      VK_NULL_HANDLE,
      VK_NULL_HANDLE);
  }

  void D3D9SwapChainEx::CreateBlitter() {
    m_blitter = new DxvkSwapchainBlitter(m_device);
  }

  void D3D9SwapChainEx::CreateHud() {
    m_hud = hud::Hud::createHud(m_device);

    if (m_hud != nullptr) {
      m_hud->addItem<hud::HudClientApiItem>("api", 1, GetApiName());
      m_hud->addItem<hud::HudSamplerCount>("samplers", -1, m_parent);
    }
  }

  void D3D9SwapChainEx::InitRamp() {
    for (uint32_t i = 0; i < NumControlPoints; i++) {
      DWORD identity = DWORD(MapGammaControlPoint(float(i) / float(NumControlPoints - 1)));

      m_ramp.red[i]   = identity;
      m_ramp.green[i] = identity;
      m_ramp.blue[i]  = identity;
    }
  }

  void D3D9SwapChainEx::SyncFrameLatency() {
    // Wait for the sync event so that we respect the maximum frame latency
    m_frameLatencySignal->wait(m_frameId - GetActualFrameLatency());
  }

  uint32_t D3D9SwapChainEx::GetActualFrameLatency() {
    uint32_t maxFrameLatency = m_parent->GetFrameLatency();

    if (m_frameLatencyCap)
      maxFrameLatency = std::min(maxFrameLatency, m_frameLatencyCap);

    maxFrameLatency = std::min(maxFrameLatency, m_presentParams.BackBufferCount + 1);
    return maxFrameLatency;
  }

  uint32_t D3D9SwapChainEx::PickFormats(
          D3D9Format                Format,
          VkSurfaceFormatKHR*       pDstFormats) {
    uint32_t n = 0;

    switch (Format) {
      default:
        Logger::warn(str::format("D3D9SwapChainEx: Unexpected format: ", Format));
     [[fallthrough]];

      case D3D9Format::A8R8G8B8:
      case D3D9Format::X8R8G8B8:
      case D3D9Format::A8B8G8R8:
      case D3D9Format::X8B8G8R8: {
        pDstFormats[n++] = { VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
        pDstFormats[n++] = { VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
      } break;

      case D3D9Format::A2R10G10B10:
      case D3D9Format::A2B10G10R10: {
        pDstFormats[n++] = { VK_FORMAT_A2B10G10R10_UNORM_PACK32, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
        pDstFormats[n++] = { VK_FORMAT_A2R10G10B10_UNORM_PACK32, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
      } break;

      case D3D9Format::X1R5G5B5:
      case D3D9Format::A1R5G5B5: {
        pDstFormats[n++] = { VK_FORMAT_B5G5R5A1_UNORM_PACK16, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
        pDstFormats[n++] = { VK_FORMAT_R5G5B5A1_UNORM_PACK16, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
        pDstFormats[n++] = { VK_FORMAT_A1R5G5B5_UNORM_PACK16, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
      } break;

      case D3D9Format::R5G6B5: {
        pDstFormats[n++] = { VK_FORMAT_B5G6R5_UNORM_PACK16, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
        pDstFormats[n++] = { VK_FORMAT_R5G6B5_UNORM_PACK16, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
      } break;
    }

    return n;
  }

  uint32_t D3D9SwapChainEx::PickPresentModes(
          BOOL                      Vsync,
          VkPresentModeKHR*         pDstModes) {
    uint32_t n = 0;

    if (Vsync) {
      if (m_parent->GetOptions()->tearFree == Tristate::False)
        pDstModes[n++] = VK_PRESENT_MODE_FIFO_RELAXED_KHR;
      pDstModes[n++] = VK_PRESENT_MODE_FIFO_KHR;
    } else {
      if (m_parent->GetOptions()->tearFree != Tristate::True)
        pDstModes[n++] = VK_PRESENT_MODE_IMMEDIATE_KHR;
      pDstModes[n++] = VK_PRESENT_MODE_MAILBOX_KHR;
    }

    return n;
  }

  uint32_t D3D9SwapChainEx::PickImageCount(
          UINT                      Preferred) {
    int32_t option = m_parent->GetOptions()->numBackBuffers;
    return option > 0 ? uint32_t(option) : uint32_t(Preferred);
  }

  void D3D9SwapChainEx::NotifyDisplayRefreshRate(
          double                  RefreshRate) {
    m_displayRefreshRate = RefreshRate;
  }

  HRESULT D3D9SwapChainEx::EnterFullscreenMode(
          D3DPRESENT_PARAMETERS* pPresentParams,
    const D3DDISPLAYMODEEX*      pFullscreenDisplayMode) {
    // Find a display mode that matches what we need
    ::GetWindowRect(m_window, &m_windowState.rect);

    if (FAILED(ChangeDisplayMode(pPresentParams, pFullscreenDisplayMode))) {
      Logger::err("D3D9: EnterFullscreenMode: Failed to change display mode");
      return D3DERR_INVALIDCALL;
    }

    // Testing shows we shouldn't hook WM_NCCALCSIZE but we shouldn't change
    // windows style either.
    //
    // Some games restore window styles after we have changed it, so hooking is
    // also required. Doing it will allow us to create fullscreen windows
    // regardless of their style and it also appears to work on Windows.
    HookWindowProc(m_window, this);

    D3D9WindowMessageFilter filter(m_window);

    // Change the window flags to remove the decoration etc.
    LONG style   = ::GetWindowLongW(m_window, GWL_STYLE);
    LONG exstyle = ::GetWindowLongW(m_window, GWL_EXSTYLE);

    m_windowState.style = style;
    m_windowState.exstyle = exstyle;

    style   &= ~WS_OVERLAPPEDWINDOW;
    exstyle &= ~WS_EX_OVERLAPPEDWINDOW;

    ::SetWindowLongW(m_window, GWL_STYLE, style);
    ::SetWindowLongW(m_window, GWL_EXSTYLE, exstyle);

    // Move the window so that it covers the entire output
    RECT rect;
    GetMonitorRect(GetDefaultMonitor(), &rect);

    ::SetWindowPos(m_window, HWND_TOPMOST,
      rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
      SWP_FRAMECHANGED | SWP_SHOWWINDOW | SWP_NOACTIVATE);

    m_monitor = GetDefaultMonitor();
    return D3D_OK;
  }

  HRESULT D3D9SwapChainEx::LeaveFullscreenMode() {
    if (!IsWindow(m_window))
      return D3DERR_INVALIDCALL;

    if (FAILED(RestoreDisplayMode(m_monitor)))
      Logger::warn("D3D9: LeaveFullscreenMode: Failed to restore display mode");

    m_monitor = nullptr;

    ResetWindowProc(m_window);

    // Only restore the window style if the application hasn't
    // changed them. This is in line with what native D3D9 does.
    LONG curStyle   = ::GetWindowLongW(m_window, GWL_STYLE) & ~WS_VISIBLE;
    LONG curExstyle = ::GetWindowLongW(m_window, GWL_EXSTYLE) & ~WS_EX_TOPMOST;

    if (curStyle == (m_windowState.style & ~(WS_VISIBLE | WS_OVERLAPPEDWINDOW))
     && curExstyle == (m_windowState.exstyle & ~(WS_EX_TOPMOST | WS_EX_OVERLAPPEDWINDOW))) {
      ::SetWindowLongW(m_window, GWL_STYLE,   m_windowState.style);
      ::SetWindowLongW(m_window, GWL_EXSTYLE, m_windowState.exstyle);
    }

    // Restore window position and apply the style
    const RECT rect = m_windowState.rect;

    ::SetWindowPos(m_window, 0,
      rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
      SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOACTIVATE);

    return D3D_OK;
  }

  HRESULT D3D9SwapChainEx::ChangeDisplayMode(
          D3DPRESENT_PARAMETERS* pPresentParams,
    const D3DDISPLAYMODEEX*      pFullscreenDisplayMode) {
    D3DDISPLAYMODEEX mode;

    if (pFullscreenDisplayMode) {
      mode = *pFullscreenDisplayMode;
    } else {
      mode.Width            = pPresentParams->BackBufferWidth;
      mode.Height           = pPresentParams->BackBufferHeight;
      mode.Format           = pPresentParams->BackBufferFormat;
      mode.RefreshRate      = pPresentParams->FullScreen_RefreshRateInHz;
      mode.ScanLineOrdering = D3DSCANLINEORDERING_PROGRESSIVE;
      mode.Size             = sizeof(D3DDISPLAYMODEEX);
    }

    DEVMODEW devMode = { };
    devMode.dmSize       = sizeof(devMode);
    devMode.dmFields     = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;
    devMode.dmPelsWidth  = mode.Width;
    devMode.dmPelsHeight = mode.Height;
    devMode.dmBitsPerPel = GetMonitorFormatBpp(EnumerateFormat(mode.Format));

    if (mode.RefreshRate != 0)  {
      devMode.dmFields |= DM_DISPLAYFREQUENCY;
      devMode.dmDisplayFrequency = mode.RefreshRate;
    }

    HMONITOR monitor = GetDefaultMonitor();

    if (!SetMonitorDisplayMode(monitor, &devMode))
      return D3DERR_NOTAVAILABLE;

    devMode.dmFields = DM_DISPLAYFREQUENCY;

    if (GetMonitorDisplayMode(monitor, ENUM_CURRENT_SETTINGS, &devMode))
      NotifyDisplayRefreshRate(double(devMode.dmDisplayFrequency));
    else
      NotifyDisplayRefreshRate(0.0);

    return D3D_OK;
  }

  HRESULT D3D9SwapChainEx::RestoreDisplayMode(HMONITOR hMonitor) {
    if (hMonitor == nullptr)
      return D3DERR_INVALIDCALL;

    if (!RestoreMonitorDisplayMode())
      return D3DERR_NOTAVAILABLE;

    NotifyDisplayRefreshRate(0.0);
    return D3D_OK;
  }

  bool    D3D9SwapChainEx::UpdatePresentRegion(const RECT* pSourceRect, const RECT* pDestRect) {
    if (pSourceRect == nullptr) {
      m_srcRect.top    = 0;
      m_srcRect.left   = 0;
      m_srcRect.right  = m_presentParams.BackBufferWidth;
      m_srcRect.bottom = m_presentParams.BackBufferHeight;
    }
    else
      m_srcRect = *pSourceRect;

    RECT dstRect;
    if (pDestRect == nullptr) {
      // TODO: Should we hook WM_SIZE message for this?
      UINT width, height;
      GetWindowClientSize(m_window, &width, &height);

      dstRect.top    = 0;
      dstRect.left   = 0;
      dstRect.right  = LONG(width);
      dstRect.bottom = LONG(height);
    }
    else
      dstRect = *pDestRect;

    bool recreate =
       m_dstRect.left   != dstRect.left
    || m_dstRect.top    != dstRect.top
    || m_dstRect.right  != dstRect.right
    || m_dstRect.bottom != dstRect.bottom;

    m_dstRect = dstRect;

    return recreate;
  }

  VkExtent2D D3D9SwapChainEx::GetPresentExtent() {
    return VkExtent2D {
      std::max<uint32_t>(m_dstRect.right  - m_dstRect.left, 1u),
      std::max<uint32_t>(m_dstRect.bottom - m_dstRect.top,  1u) };
  }

  VkFullScreenExclusiveEXT D3D9SwapChainEx::PickFullscreenMode() {
    return m_dialog
      ? VK_FULL_SCREEN_EXCLUSIVE_DISALLOWED_EXT
      : VK_FULL_SCREEN_EXCLUSIVE_DEFAULT_EXT;
  }

  std::string D3D9SwapChainEx::GetApiName() {
    return this->GetParent()->IsExtended() ? "D3D9Ex" : "D3D9";
  }

}


code:
#pragma once

#include "d3d9_device_child.h"
#include "d3d9_device.h"
#include "d3d9_format.h"

#include "../dxvk/hud/dxvk_hud.h"

#include "../dxvk/dxvk_swapchain_blitter.h"

#include "../util/sync/sync_signal.h"

#include <vector>

namespace dxvk {

  class D3D9Surface;

  using D3D9SwapChainExBase = D3D9DeviceChild<IDirect3DSwapChain9Ex>;
  class D3D9SwapChainEx final : public D3D9SwapChainExBase {
    static constexpr uint32_t NumControlPoints = 256;
  public:

    D3D9SwapChainEx(
            D3D9DeviceEx*          pDevice,
            D3DPRESENT_PARAMETERS* pPresentParams,
      const D3DDISPLAYMODEEX*      pFullscreenDisplayMode);

    ~D3D9SwapChainEx();

    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject);

    HRESULT STDMETHODCALLTYPE Present(
      const RECT*    pSourceRect,
      const RECT*    pDestRect,
            HWND     hDestWindowOverride,
      const RGNDATA* pDirtyRegion,
            DWORD    dwFlags);

    HRESULT STDMETHODCALLTYPE GetFrontBufferData(IDirect3DSurface9* pDestSurface);

    HRESULT STDMETHODCALLTYPE GetBackBuffer(
            UINT                iBackBuffer,
            D3DBACKBUFFER_TYPE  Type,
            IDirect3DSurface9** ppBackBuffer);

    HRESULT STDMETHODCALLTYPE GetRasterStatus(D3DRASTER_STATUS* pRasterStatus);

    HRESULT STDMETHODCALLTYPE GetDisplayMode(D3DDISPLAYMODE* pMode);

    HRESULT STDMETHODCALLTYPE GetPresentParameters(D3DPRESENT_PARAMETERS* pPresentationParameters);

    HRESULT STDMETHODCALLTYPE GetLastPresentCount(UINT* pLastPresentCount);

    HRESULT STDMETHODCALLTYPE GetPresentStats(D3DPRESENTSTATS* pPresentationStatistics);

    HRESULT STDMETHODCALLTYPE GetDisplayModeEx(D3DDISPLAYMODEEX* pMode, D3DDISPLAYROTATION* pRotation);

    HRESULT Reset(
            D3DPRESENT_PARAMETERS* pPresentParams,
            D3DDISPLAYMODEEX*      pFullscreenDisplayMode);

    HRESULT WaitForVBlank();

    void    SetGammaRamp(
            DWORD         Flags,
      const D3DGAMMARAMP* pRamp);

    void    GetGammaRamp(D3DGAMMARAMP* pRamp);

    void    Invalidate(HWND hWindow);

    HRESULT SetDialogBoxMode(bool bEnableDialogs);

    D3D9Surface* GetBackBuffer(UINT iBackBuffer);

    const D3DPRESENT_PARAMETERS* GetPresentParams() const { return &m_presentParams; }

    void SyncFrameLatency();

  private:

    enum BindingIds : uint32_t {
      Image = 0,
      Gamma = 1,
    };

    struct WindowState {
      LONG style   = 0;
      LONG exstyle = 0;
      RECT rect    = { 0, 0, 0, 0 };
    };

    D3DPRESENT_PARAMETERS     m_presentParams;
    D3DGAMMARAMP              m_ramp;

    Rc<DxvkDevice>            m_device;
    Rc<DxvkContext>           m_context;
    Rc<DxvkSwapchainBlitter>  m_blitter;

    Rc<vk::Presenter>         m_presenter;

    Rc<hud::Hud>              m_hud;

    std::vector<Com<D3D9Surface, false>> m_backBuffers;

    RECT                      m_srcRect;
    RECT                      m_dstRect;

    DxvkSubmitStatus          m_presentStatus;

    std::vector<Rc<DxvkImageView>> m_imageViews;

    uint64_t                  m_frameId           = D3D9DeviceEx::MaxFrameLatency;
    uint32_t                  m_frameLatencyCap   = 0;
    Rc<sync::Fence>           m_frameLatencySignal;

    bool                      m_dirty    = true;
    bool                      m_vsync    = true;

    bool                      m_dialog;
    bool                      m_lastDialog = false;

    HWND                      m_window   = nullptr;
    HMONITOR                  m_monitor  = nullptr;

    WindowState               m_windowState;

    double                    m_displayRefreshRate = 0.0;

    void PresentImage(UINT PresentInterval);

    void SubmitPresent(const vk::PresenterSync& Sync, uint32_t FrameId);

    void SynchronizePresent();

    void RecreateSwapChain(
        BOOL                      Vsync);

    void CreatePresenter();

    void CreateRenderTargetViews();

    void DestroyBackBuffers();

    void CreateBackBuffers(
            uint32_t            NumBackBuffers);

    void CreateBlitter();

    void CreateHud();

    void InitRamp();

    uint32_t GetActualFrameLatency();

    uint32_t PickFormats(
            D3D9Format                Format,
            VkSurfaceFormatKHR*       pDstFormats);

    uint32_t PickPresentModes(
            BOOL                      Vsync,
            VkPresentModeKHR*         pDstModes);

    uint32_t PickImageCount(
            UINT                      Preferred);

    void NormalizePresentParameters(D3DPRESENT_PARAMETERS* pPresentParams);

    void NotifyDisplayRefreshRate(
            double                  RefreshRate);

    HRESULT EnterFullscreenMode(
            D3DPRESENT_PARAMETERS*  pPresentParams,
      const D3DDISPLAYMODEEX*       pFullscreenDisplayMode);

    HRESULT LeaveFullscreenMode();

    HRESULT ChangeDisplayMode(
            D3DPRESENT_PARAMETERS*  pPresentParams,
      const D3DDISPLAYMODEEX*       pFullscreenDisplayMode);

    HRESULT RestoreDisplayMode(HMONITOR hMonitor);

    bool    UpdatePresentRegion(const RECT* pSourceRect, const RECT* pDestRect);

    VkExtent2D GetPresentExtent();

    VkFullScreenExclusiveEXT PickFullscreenMode();

    std::string GetApiName();

  };

}

repo_name: orochi/dxvk


code:
#include "d3d9_swvp_emu.h"

#include "d3d9_device.h"
#include "d3d9_vertex_declaration.h"

#include "../spirv/spirv_module.h"

namespace dxvk {

  // Doesn't compare everything, only what we use in SWVP.

  size_t D3D9VertexDeclHash::operator () (const D3D9VertexElements& key) const {
    DxvkHashState hash;

    std::hash<BYTE> bytehash;
    std::hash<WORD> wordhash;

    for (auto& element : key) {
      hash.add(wordhash(element.Stream));
      hash.add(wordhash(element.Offset));
      hash.add(bytehash(element.Type));
      hash.add(bytehash(element.Method));
      hash.add(bytehash(element.Usage));
      hash.add(bytehash(element.UsageIndex));
    }

    return hash;
  }

  bool D3D9VertexDeclEq::operator () (const D3D9VertexElements& a, const D3D9VertexElements& b) const {
    if (a.size() != b.size())
      return false;

    bool equal = true;

    for (uint32_t i = 0; i < a.size(); i++)
      equal &= std::memcmp(&a[i], &b[i], sizeof(a[0])) == 0;

    return equal;
  }

  enum class DecltypeClass {
    Float, Byte, Short, Dec, Half
  };

  enum DecltypeFlags {
    Signed     = 1,
    Normalize  = 2,
    ReverseRGB = 4
  };

  struct Decltype {
    DecltypeClass Class;
    uint32_t      VectorCount;
    uint32_t      Flags;
  };

  Decltype ClassifyDecltype(D3DDECLTYPE Type) {
    switch (Type) {
      case D3DDECLTYPE_FLOAT1:    return { DecltypeClass::Float, 1, DecltypeFlags::Signed };
      case D3DDECLTYPE_FLOAT2:    return { DecltypeClass::Float, 2, DecltypeFlags::Signed };
      case D3DDECLTYPE_FLOAT3:    return { DecltypeClass::Float, 3, DecltypeFlags::Signed };
      case D3DDECLTYPE_FLOAT4:    return { DecltypeClass::Float, 4, DecltypeFlags::Signed };
      case D3DDECLTYPE_D3DCOLOR:  return { DecltypeClass::Byte,  4, DecltypeFlags::Normalize | DecltypeFlags::ReverseRGB };
      case D3DDECLTYPE_UBYTE4:    return { DecltypeClass::Byte,  4, 0 };
      case D3DDECLTYPE_SHORT2:    return { DecltypeClass::Short, 2, DecltypeFlags::Signed };
      case D3DDECLTYPE_SHORT4:    return { DecltypeClass::Short, 4, DecltypeFlags::Signed };
      case D3DDECLTYPE_UBYTE4N:   return { DecltypeClass::Byte,  4, DecltypeFlags::Normalize };
      case D3DDECLTYPE_SHORT2N:   return { DecltypeClass::Short, 2, DecltypeFlags::Signed | DecltypeFlags::Normalize };
      case D3DDECLTYPE_SHORT4N:   return { DecltypeClass::Short, 4, DecltypeFlags::Signed | DecltypeFlags::Normalize };
      case D3DDECLTYPE_USHORT2N:  return { DecltypeClass::Short, 2, DecltypeFlags::Normalize };
      case D3DDECLTYPE_USHORT4N:  return { DecltypeClass::Short, 4, DecltypeFlags::Normalize };
      case D3DDECLTYPE_UDEC3:     return { DecltypeClass::Dec,   3, 0 };
      case D3DDECLTYPE_DEC3N:     return { DecltypeClass::Dec,   3, DecltypeFlags::Signed | DecltypeFlags::Normalize };
      case D3DDECLTYPE_FLOAT16_2: return { DecltypeClass::Half,  2, DecltypeFlags::Signed };
      case D3DDECLTYPE_FLOAT16_4: return { DecltypeClass::Half,  4, DecltypeFlags::Signed };
      default:                    return { DecltypeClass::Float, 4, DecltypeFlags::Signed };
    }
  }

  class D3D9SWVPEmulatorGenerator {

  public:

    D3D9SWVPEmulatorGenerator(const std::string& name)
    : m_module(spvVersion(1, 3)) {
      m_entryPointId = m_module.allocateId();

      m_module.setDebugSource(
        spv::SourceLanguageUnknown, 0,
        m_module.addDebugString(name.c_str()),
        nullptr);

      m_module.setMemoryModel(
        spv::AddressingModelLogical,
        spv::MemoryModelGLSL450);

      m_module.enableCapability(spv::CapabilityGeometry);

      m_module.setExecutionMode(m_entryPointId, spv::ExecutionModeInputPoints);
      m_module.setExecutionMode(m_entryPointId, spv::ExecutionModeOutputPoints);
      // This has to be > 0 for some reason even though
      // we will never emit a vertex
      m_module.setOutputVertices(m_entryPointId, 1);
      m_module.setInvocations(m_entryPointId, 1);

      m_module.functionBegin(m_module.defVoidType(), m_entryPointId, m_module.defFunctionType(
        m_module.defVoidType(), 0, nullptr), spv::FunctionControlMaskNone);
      m_module.opLabel(m_module.allocateId());
    }

    void compile(const D3D9VertexDecl* pDecl) {
      uint32_t uint_t     = m_module.defIntType(32, false);
      uint32_t float_t    = m_module.defFloatType(32);
      uint32_t vec4_t     = m_module.defVectorType(float_t, 4);

      uint32_t vec4_singular_array_t = m_module.defArrayType(vec4_t, m_module.constu32(1));

      // Setup the buffer
      uint32_t bufferSlot = getSWVPBufferSlot();

      uint32_t arrayType    = m_module.defRuntimeArrayTypeUnique(uint_t);
      m_module.decorateArrayStride(arrayType, sizeof(uint32_t));

      uint32_t buffer_t     = m_module.defStructTypeUnique(1, &arrayType);
      m_module.memberDecorateOffset(buffer_t, 0, 0);
      m_module.decorate(buffer_t, spv::DecorationBufferBlock);

      uint32_t buffer       = m_module.newVar(m_module.defPointerType(buffer_t, spv::StorageClassUniform), spv::StorageClassUniform);
      m_module.decorateDescriptorSet(buffer, 0);
      m_module.decorateBinding(buffer, bufferSlot);

      m_bufferResource.slot   = bufferSlot;
      m_bufferResource.type   = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
      m_bufferResource.view   = VK_IMAGE_VIEW_TYPE_MAX_ENUM;
      m_bufferResource.access = VK_ACCESS_SHADER_WRITE_BIT;

      // Load our builtins
      uint32_t primitiveIdPtr = m_module.newVar(m_module.defPointerType(uint_t, spv::StorageClassInput), spv::StorageClassInput);
      m_module.decorateBuiltIn(primitiveIdPtr, spv::BuiltInPrimitiveId);
      m_entryPointInterfaces.push_back(primitiveIdPtr);

      uint32_t primitiveId = m_module.opLoad(uint_t, primitiveIdPtr);

      // The size of any given vertex
      uint32_t vertexSize       = m_module.constu32(pDecl->GetSize() / sizeof(uint32_t));

      //The offset of this vertex from the beginning of the buffer
      uint32_t thisVertexOffset = m_module.opIMul(uint_t, vertexSize, primitiveId);

      for (auto& element : pDecl->GetElements()) {
        // Load the slot associated with this element
        DxsoSemantic semantic = { DxsoUsage(element.Usage), element.UsageIndex };

        uint32_t elementPtr;
        uint32_t elementVar;

        elementPtr = m_module.newVar(m_module.defPointerType(vec4_singular_array_t, spv::StorageClassInput), spv::StorageClassInput);
        if ((semantic.usage == DxsoUsage::Position || semantic.usage == DxsoUsage::PositionT) && element.UsageIndex == 0) {
          // Load from builtin
          m_module.decorateBuiltIn(elementPtr, spv::BuiltInPosition);
        }
        else {
          // Load from slot
          uint32_t slotIdx      = RegisterLinkerSlot(semantic);

          m_module.decorateLocation(elementPtr, slotIdx);
          m_inputMask |= 1u << slotIdx;
        }

        uint32_t zero = m_module.constu32(0);
        elementVar = m_module.opAccessChain(m_module.defPointerType(vec4_t, spv::StorageClassInput), elementPtr, 1, &zero);
        elementVar = m_module.opLoad(vec4_t, elementVar);

        m_entryPointInterfaces.push_back(elementPtr);

        // The offset of this element from the beginning of any given vertex
        uint32_t perVertexElementOffset = m_module.constu32(element.Offset / sizeof(uint32_t));

        // The offset of this element from the beginning of the buffer for **THIS** vertex
        uint32_t elementOffset          = m_module.opIAdd(uint_t, thisVertexOffset, perVertexElementOffset);

        // Write to the buffer at the element offset for each part of the vector.
        Decltype elementInfo = ClassifyDecltype(D3DDECLTYPE(element.Type));

        if (elementInfo.Class == DecltypeClass::Dec) {
          // TODO!
          Logger::warn("Encountered DEC3/UDEC3N class, ignoring...");
          continue;
        }

        uint32_t vecn_t = m_module.defVectorType(float_t, elementInfo.VectorCount);
        uint32_t componentSet;

        // Modifiers...
        if (elementInfo.Flags & DecltypeFlags::ReverseRGB) {
          std::array<uint32_t, 4> indices = { 2, 1, 0, 3 };
          componentSet = m_module.opVectorShuffle(vecn_t, elementVar, elementVar, elementInfo.VectorCount, indices.data());
        }
        else {
          std::array<uint32_t, 4> indices = { 0, 1, 2, 3 };
          componentSet = m_module.opVectorShuffle(vecn_t, elementVar, elementVar, elementInfo.VectorCount, indices.data());
        }

        if (elementInfo.Flags & DecltypeFlags::Normalize)
          componentSet = m_module.opVectorTimesScalar(vecn_t, componentSet, m_module.constf32(255.0f));

        bool isSigned = elementInfo.Flags & DecltypeFlags::Signed;

        // Convert the component to the correct type/value.
        switch (elementInfo.Class) {
          case DecltypeClass::Float: break; // Do nothing!
          case DecltypeClass::Byte: {
            m_module.enableCapability(spv::CapabilityInt8);

            uint32_t type = m_module.defIntType(8, isSigned);
                     type = m_module.defVectorType(type, elementInfo.VectorCount);

            componentSet = isSigned
              ? m_module.opConvertFtoS(type, componentSet)
              : m_module.opConvertFtoU(type, componentSet);

            break;
          }
          case DecltypeClass::Short: {
            m_module.enableCapability(spv::CapabilityInt16);

            uint32_t type = m_module.defIntType(16, isSigned);
                     type = m_module.defVectorType(type, elementInfo.VectorCount);

            componentSet = isSigned
              ? m_module.opConvertFtoS(type, componentSet)
              : m_module.opConvertFtoU(type, componentSet);

            break;
          }
          case DecltypeClass::Half: {
            m_module.enableCapability(spv::CapabilityFloat16);

            uint32_t type = m_module.defFloatType(16);
                     type = m_module.defVectorType(type, elementInfo.VectorCount);
            componentSet = m_module.opFConvert(type, componentSet);

            break;
          }
          case DecltypeClass::Dec: {
            // TODO!
            break;
          }
        }

        // Bitcast to dwords before we write.
        uint32_t dwordCount  = GetDecltypeSize(D3DDECLTYPE(element.Type)) / sizeof(uint32_t);
        uint32_t dwordVector = m_module.opBitcast(
          m_module.defVectorType(uint_t, dwordCount),
          componentSet);

        // Finally write each dword to the buffer!
        for (uint32_t i = 0; i < dwordCount; i++) {
          std::array<uint32_t, 2> bufferIndices = { m_module.constu32(0), elementOffset };

          uint32_t writeDest = m_module.opAccessChain(m_module.defPointerType(uint_t, spv::StorageClassUniform), buffer, bufferIndices.size(), bufferIndices.data());
          uint32_t currentDword = m_module.opCompositeExtract(uint_t, dwordVector, 1, &i);

          m_module.opStore(writeDest, currentDword);

          elementOffset = m_module.opIAdd(uint_t, elementOffset, m_module.constu32(1));
        }
      }
    }

    Rc<DxvkShader> finalize() {
      m_module.opReturn();
      m_module.functionEnd();

      m_module.addEntryPoint(m_entryPointId,
        spv::ExecutionModelGeometry, "main",
        m_entryPointInterfaces.size(),
        m_entryPointInterfaces.data());
      m_module.setDebugName(m_entryPointId, "main");

      DxvkShaderCreateInfo info;
      info.stage = VK_SHADER_STAGE_GEOMETRY_BIT;
      info.resourceSlotCount = 1;
      info.resourceSlots = &m_bufferResource;
      info.inputMask = m_inputMask;

      return new DxvkShader(info, m_module.compile());
    }

  private:

    SpirvModule m_module;

    std::vector<uint32_t> m_entryPointInterfaces;
    uint32_t              m_entryPointId = 0;
    uint32_t              m_inputMask = 0u;
    DxvkResourceSlot      m_bufferResource;

  };

  Rc<DxvkShader> D3D9SWVPEmulator::GetShaderModule(D3D9DeviceEx* pDevice, const D3D9VertexDecl* pDecl) {
    auto& elements = pDecl->GetElements();

    // Use the shader's unique key for the lookup
    { std::unique_lock<dxvk::mutex> lock(m_mutex);

      auto entry = m_modules.find(elements);
      if (entry != m_modules.end())
        return entry->second;
    }

    Sha1Hash hash = Sha1Hash::compute(
      elements.data(), elements.size() * sizeof(elements[0]));

    DxvkShaderKey key = { VK_SHADER_STAGE_GEOMETRY_BIT , hash };
    std::string name = str::format("SWVP_", key.toString());

    // This shader has not been compiled yet, so we have to create a
    // new module. This takes a while, so we won't lock the structure.
    D3D9SWVPEmulatorGenerator generator(name);
    generator.compile(pDecl);
    Rc<DxvkShader> shader = generator.finalize();

    shader->setShaderKey(key);
    pDevice->GetDXVKDevice()->registerShader(shader);

    const std::string dumpPath = env::getEnvVar("DXVK_SHADER_DUMP_PATH");

    if (dumpPath.size() != 0) {
      std::ofstream dumpStream(
        str::format(dumpPath, "/", name, ".spv"),
        std::ios_base::binary | std::ios_base::trunc);

      shader->dump(dumpStream);
    }

    // Insert the new module into the lookup table. If another thread
    // has compiled the same shader in the meantime, we should return
    // that object instead and discard the newly created module.
    { std::unique_lock<dxvk::mutex> lock(m_mutex);

      auto status = m_modules.insert({ elements, shader });
      if (!status.second)
        return status.first->second;
    }

    return shader;
  }

}


code:
#pragma once

#include <unordered_map>

#include "d3d9_include.h"

#include "../dxvk/dxvk_shader.h"

namespace dxvk {

  class D3D9VertexDecl;
  class D3D9DeviceEx;

  struct D3D9VertexDeclHash {
    size_t operator () (const D3D9VertexElements& key) const;
  };

  struct D3D9VertexDeclEq {
    bool operator () (const D3D9VertexElements& a, const D3D9VertexElements& b) const;
  };

  class D3D9SWVPEmulator {

  public:

    Rc<DxvkShader> GetShaderModule(D3D9DeviceEx* pDevice, const D3D9VertexDecl* pDecl);

  private:

    dxvk::mutex                               m_mutex;

    std::unordered_map<
      D3D9VertexElements, Rc<DxvkShader>,
      D3D9VertexDeclHash, D3D9VertexDeclEq>   m_modules;

  };

}


code:
#include "d3d9_texture.h"

#include "d3d9_util.h"

namespace dxvk {

  // Direct3DTexture9

  D3D9Texture2D::D3D9Texture2D(
          D3D9DeviceEx*             pDevice,
    const D3D9_COMMON_TEXTURE_DESC* pDesc,
          HANDLE*                   pSharedHandle)
    : D3D9Texture2DBase( pDevice, pDesc, D3DRTYPE_TEXTURE, pSharedHandle ) { }

  HRESULT STDMETHODCALLTYPE D3D9Texture2D::QueryInterface(REFIID riid, void** ppvObject) {
    if (ppvObject == nullptr)
      return E_POINTER;

    *ppvObject = nullptr;

    if (riid == __uuidof(IUnknown)
     || riid == __uuidof(IDirect3DResource9)
     || riid == __uuidof(IDirect3DBaseTexture9)
     || riid == __uuidof(IDirect3DTexture9)) {
      *ppvObject = ref(this);
      return S_OK;
    }

    Logger::warn("D3D9Texture2D::QueryInterface: Unknown interface query");
    Logger::warn(str::format(riid));
    return E_NOINTERFACE;
  }

  D3DRESOURCETYPE STDMETHODCALLTYPE D3D9Texture2D::GetType() {
    return D3DRTYPE_TEXTURE;
  }

  HRESULT STDMETHODCALLTYPE D3D9Texture2D::GetLevelDesc(UINT Level, D3DSURFACE_DESC *pDesc) {
    if (unlikely(Level >= m_texture.ExposedMipLevels()))
      return D3DERR_INVALIDCALL;

    return GetSubresource(Level)->GetDesc(pDesc);
  }

  HRESULT STDMETHODCALLTYPE D3D9Texture2D::GetSurfaceLevel(UINT Level, IDirect3DSurface9** ppSurfaceLevel) {
    InitReturnPtr(ppSurfaceLevel);

    if (unlikely(Level >= m_texture.ExposedMipLevels()))
      return D3DERR_INVALIDCALL;

    if (unlikely(ppSurfaceLevel == nullptr))
      return D3DERR_INVALIDCALL;

    *ppSurfaceLevel = ref(GetSubresource(Level));
    return D3D_OK;
  }

  HRESULT STDMETHODCALLTYPE D3D9Texture2D::LockRect(UINT Level, D3DLOCKED_RECT* pLockedRect, CONST RECT* pRect, DWORD Flags) {
    if (unlikely(Level >= m_texture.ExposedMipLevels()))
      return D3DERR_INVALIDCALL;

    return GetSubresource(Level)->LockRect(pLockedRect, pRect, Flags);
  }

  HRESULT STDMETHODCALLTYPE D3D9Texture2D::UnlockRect(UINT Level) {
    if (unlikely(Level >= m_texture.ExposedMipLevels()))
      return D3DERR_INVALIDCALL;

    return GetSubresource(Level)->UnlockRect();
  }

  HRESULT STDMETHODCALLTYPE D3D9Texture2D::AddDirtyRect(CONST RECT* pDirtyRect) {
    if (pDirtyRect) {
      D3DBOX box = { UINT(pDirtyRect->left), UINT(pDirtyRect->top), UINT(pDirtyRect->right), UINT(pDirtyRect->bottom), 0, 1 };
      m_texture.AddDirtyBox(&box, 0);
    } else {
      m_texture.AddDirtyBox(nullptr, 0);
    }

    // Some games keep using the pointer returned in LockRect() after calling Unlock()
    // and purely rely on AddDirtyRect to notify D3D9 that contents have changed.
    // We have no way of knowing which mip levels were actually changed.
    m_texture.SetAllNeedUpload();
    return D3D_OK;
  }

  // Direct3DVolumeTexture9

  D3D9Texture3D::D3D9Texture3D(
          D3D9DeviceEx*             pDevice,
    const D3D9_COMMON_TEXTURE_DESC* pDesc)
    : D3D9Texture3DBase( pDevice, pDesc, D3DRTYPE_VOLUMETEXTURE, nullptr ) { }

  HRESULT STDMETHODCALLTYPE D3D9Texture3D::QueryInterface(REFIID riid, void** ppvObject) {
    if (ppvObject == nullptr)
      return E_POINTER;

    *ppvObject = nullptr;

    if (riid == __uuidof(IUnknown)
     || riid == __uuidof(IDirect3DResource9)
     || riid == __uuidof(IDirect3DBaseTexture9)
     || riid == __uuidof(IDirect3DVolumeTexture9)) {
      *ppvObject = ref(this);
      return S_OK;
    }

    Logger::warn("D3D9Texture3D::QueryInterface: Unknown interface query");
    Logger::warn(str::format(riid));
    return E_NOINTERFACE;
  }

  D3DRESOURCETYPE STDMETHODCALLTYPE D3D9Texture3D::GetType() {
    return D3DRTYPE_VOLUMETEXTURE;
  }

  HRESULT STDMETHODCALLTYPE D3D9Texture3D::GetLevelDesc(UINT Level, D3DVOLUME_DESC *pDesc) {
    if (unlikely(Level >= m_texture.ExposedMipLevels()))
      return D3DERR_INVALIDCALL;

    return GetSubresource(Level)->GetDesc(pDesc);
  }

  HRESULT STDMETHODCALLTYPE D3D9Texture3D::GetVolumeLevel(UINT Level, IDirect3DVolume9** ppVolumeLevel) {
    InitReturnPtr(ppVolumeLevel);

    if (unlikely(Level >= m_texture.ExposedMipLevels()))
      return D3DERR_INVALIDCALL;

    if (unlikely(ppVolumeLevel == nullptr))
      return D3DERR_INVALIDCALL;

    *ppVolumeLevel = ref(GetSubresource(Level));
    return D3D_OK;
  }

  HRESULT STDMETHODCALLTYPE D3D9Texture3D::LockBox(UINT Level, D3DLOCKED_BOX* pLockedBox, CONST D3DBOX* pBox, DWORD Flags) {
    if (unlikely(Level >= m_texture.ExposedMipLevels()))
      return D3DERR_INVALIDCALL;

    return GetSubresource(Level)->LockBox(pLockedBox, pBox, Flags);
  }

  HRESULT STDMETHODCALLTYPE D3D9Texture3D::UnlockBox(UINT Level) {
    if (unlikely(Level >= m_texture.ExposedMipLevels()))
      return D3DERR_INVALIDCALL;

    return GetSubresource(Level)->UnlockBox();
  }

  HRESULT STDMETHODCALLTYPE D3D9Texture3D::AddDirtyBox(CONST D3DBOX* pDirtyBox) {
    m_texture.AddDirtyBox(pDirtyBox, 0);

    // Some games keep using the pointer returned in LockBox() after calling Unlock()
    // and purely rely on AddDirtyBox to notify D3D9 that contents have changed.
    // We have no way of knowing which mip levels were actually changed.
    m_texture.SetAllNeedUpload();
    return D3D_OK;
  }

  // Direct3DCubeTexture9

  D3D9TextureCube::D3D9TextureCube(
          D3D9DeviceEx*             pDevice,
    const D3D9_COMMON_TEXTURE_DESC* pDesc)
    : D3D9TextureCubeBase( pDevice, pDesc, D3DRTYPE_CUBETEXTURE, nullptr ) { }

  HRESULT STDMETHODCALLTYPE D3D9TextureCube::QueryInterface(REFIID riid, void** ppvObject) {
    if (ppvObject == nullptr)
      return E_POINTER;

    *ppvObject = nullptr;

    if (riid == __uuidof(IUnknown)
     || riid == __uuidof(IDirect3DResource9)
     || riid == __uuidof(IDirect3DBaseTexture9)
     || riid == __uuidof(IDirect3DCubeTexture9)) {
      *ppvObject = ref(this);
      return S_OK;
    }

    Logger::warn("D3D9TextureCube::QueryInterface: Unknown interface query");
    Logger::warn(str::format(riid));
    return E_NOINTERFACE;
  }

  D3DRESOURCETYPE STDMETHODCALLTYPE D3D9TextureCube::GetType() {
    return D3DRTYPE_CUBETEXTURE;
  }

  HRESULT STDMETHODCALLTYPE D3D9TextureCube::GetLevelDesc(UINT Level, D3DSURFACE_DESC *pDesc) {
    if (unlikely(Level >= m_texture.ExposedMipLevels()))
      return D3DERR_INVALIDCALL;

    return GetSubresource(Level)->GetDesc(pDesc);
  }

  HRESULT STDMETHODCALLTYPE D3D9TextureCube::GetCubeMapSurface(D3DCUBEMAP_FACES Face, UINT Level, IDirect3DSurface9** ppSurfaceLevel) {
    InitReturnPtr(ppSurfaceLevel);

    if (unlikely(Level >= m_texture.ExposedMipLevels() || Face >= D3DCUBEMAP_FACES(6)))
      return D3DERR_INVALIDCALL;

    if (unlikely(ppSurfaceLevel == nullptr))
      return D3DERR_INVALIDCALL;

    *ppSurfaceLevel = ref(GetSubresource(m_texture.CalcSubresource(UINT(Face), Level)));
    return D3D_OK;
  }

  HRESULT STDMETHODCALLTYPE D3D9TextureCube::LockRect(D3DCUBEMAP_FACES Face, UINT Level, D3DLOCKED_RECT* pLockedRect, CONST RECT* pRect, DWORD Flags) {
    if (unlikely(Face > D3DCUBEMAP_FACE_NEGATIVE_Z || Level >= m_texture.ExposedMipLevels()))
      return D3DERR_INVALIDCALL;

    return GetSubresource(m_texture.CalcSubresource(UINT(Face), Level))->LockRect(pLockedRect, pRect, Flags);
  }

  HRESULT STDMETHODCALLTYPE D3D9TextureCube::UnlockRect(D3DCUBEMAP_FACES Face, UINT Level) {
    if (unlikely(Face > D3DCUBEMAP_FACE_NEGATIVE_Z || Level >= m_texture.ExposedMipLevels()))
      return D3DERR_INVALIDCALL;

    return GetSubresource(m_texture.CalcSubresource(UINT(Face), Level))->UnlockRect();
  }

  HRESULT STDMETHODCALLTYPE D3D9TextureCube::AddDirtyRect(D3DCUBEMAP_FACES Face, CONST RECT* pDirtyRect) {
    if (pDirtyRect) {
      D3DBOX box = { UINT(pDirtyRect->left), UINT(pDirtyRect->top), UINT(pDirtyRect->right), UINT(pDirtyRect->bottom), 0, 1 };
      m_texture.AddDirtyBox(&box, Face);
    } else {
      m_texture.AddDirtyBox(nullptr, Face);
    }

    // Some games keep using the pointer returned in LockRect() after calling Unlock()
    // and purely rely on AddDirtyRect to notify D3D9 that contents have changed.
    // We have no way of knowing which mip levels were actually changed.
    for (uint32_t m = 0; m < m_texture.Desc()->MipLevels; m++) {
      m_texture.SetNeedsUpload(m_texture.CalcSubresource(Face, m), true);
    }
    return D3D_OK;
  }

}


code:
#pragma once

#include "d3d9_device.h"
#include "d3d9_surface.h"
#include "d3d9_volume.h"
#include "d3d9_util.h"

#include <vector>
#include <list>
#include <mutex>
#include <new>
#include <type_traits>

namespace dxvk {

  template <typename SubresourceType, typename... Base>
  class D3D9BaseTexture : public D3D9Resource<Base...> {

  public:

    using SubresourceData = std::aligned_storage_t<sizeof(SubresourceType), alignof(SubresourceType)>;

    D3D9BaseTexture(
            D3D9DeviceEx*             pDevice,
      const D3D9_COMMON_TEXTURE_DESC* pDesc,
            D3DRESOURCETYPE           ResourceType,
            HANDLE*                   pSharedHandle)
      : D3D9Resource<Base...> ( pDevice )
      , m_texture             ( pDevice, pDesc, ResourceType, pSharedHandle )
      , m_lod                 ( 0 ) {
      const uint32_t arraySlices = m_texture.Desc()->ArraySize;
      const uint32_t mipLevels   = m_texture.Desc()->MipLevels;

      m_subresources.resize(arraySlices * mipLevels);

      for (uint32_t i = 0; i < arraySlices; i++) {
        for (uint32_t j = 0; j < mipLevels; j++) {
          const uint32_t subresource = m_texture.CalcSubresource(i, j);

          SubresourceType* subObj = this->GetSubresource(subresource);

          new (subObj) SubresourceType(
            pDevice,
            &m_texture,
            i, j,
            this);
        }
      }
    }

    ~D3D9BaseTexture() {
      for (uint32_t i = 0; i < m_subresources.size(); i++) {
        SubresourceType* subObj = this->GetSubresource(i);
        subObj->~SubresourceType();
      }
    }

    DWORD STDMETHODCALLTYPE SetLOD(DWORD LODNew) final {
      DWORD oldLod = m_lod;
      m_lod = std::min<DWORD>(LODNew, m_texture.Desc()->MipLevels - 1);

      if (m_lod != oldLod) {
        m_texture.CreateSampleView(m_lod);
        if (this->GetPrivateRefCount() > 0)
          this->m_parent->MarkTextureBindingDirty(this);
      }

      return oldLod;
    }

    DWORD STDMETHODCALLTYPE GetLOD() final {
      return m_lod;
    }

    DWORD STDMETHODCALLTYPE GetLevelCount() final {
      return m_texture.ExposedMipLevels();
    }

    HRESULT STDMETHODCALLTYPE SetAutoGenFilterType(D3DTEXTUREFILTERTYPE FilterType) final {
      if (unlikely(FilterType == D3DTEXF_NONE))
        return D3DERR_INVALIDCALL;

      auto lock = this->m_parent->LockDevice();

      m_texture.SetMipFilter(FilterType);
      if (m_texture.IsAutomaticMip())
        this->m_parent->MarkTextureMipsDirty(&m_texture);
      return D3D_OK;
    }

    D3DTEXTUREFILTERTYPE STDMETHODCALLTYPE GetAutoGenFilterType() final {
      return m_texture.GetMipFilter();
    }

    void STDMETHODCALLTYPE GenerateMipSubLevels() final {
      if (!m_texture.NeedsMipGen())
        return;

      auto lock = this->m_parent->LockDevice();

      this->m_parent->MarkTextureMipsUnDirty(&m_texture);
      this->m_parent->EmitGenerateMips(&m_texture);
    }

    void STDMETHODCALLTYPE PreLoad() final {
      m_texture.PreLoadAll();
    }

    D3D9CommonTexture* GetCommonTexture() {
      return &m_texture;
    }

    SubresourceType* GetSubresource(UINT Subresource) {
      return reinterpret_cast<SubresourceType*>(&m_subresources[Subresource]);
    }

  protected:

    D3D9CommonTexture m_texture;

    std::vector<SubresourceData> m_subresources;

    DWORD m_lod;

  };

  using D3D9Texture2DBase = D3D9BaseTexture<D3D9Surface, IDirect3DTexture9>;
  class D3D9Texture2D final : public D3D9Texture2DBase {

  public:

    D3D9Texture2D(
            D3D9DeviceEx*             pDevice,
      const D3D9_COMMON_TEXTURE_DESC* pDesc,
            HANDLE*                   pSharedHandle);

    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject);

    D3DRESOURCETYPE STDMETHODCALLTYPE GetType();

    HRESULT STDMETHODCALLTYPE GetLevelDesc(UINT Level, D3DSURFACE_DESC *pDesc);

    HRESULT STDMETHODCALLTYPE GetSurfaceLevel(UINT Level, IDirect3DSurface9** ppSurfaceLevel);

    HRESULT STDMETHODCALLTYPE LockRect(UINT Level, D3DLOCKED_RECT* pLockedRect, CONST RECT* pRect, DWORD Flags);

    HRESULT STDMETHODCALLTYPE UnlockRect(UINT Level);

    HRESULT STDMETHODCALLTYPE AddDirtyRect(CONST RECT* pDirtyRect);

  };

  using D3D9Texture3DBase = D3D9BaseTexture<D3D9Volume, IDirect3DVolumeTexture9>;
  class D3D9Texture3D final : public D3D9Texture3DBase {

  public:

    D3D9Texture3D(
            D3D9DeviceEx*             pDevice,
      const D3D9_COMMON_TEXTURE_DESC* pDesc);

    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject);

    D3DRESOURCETYPE STDMETHODCALLTYPE GetType();

    HRESULT STDMETHODCALLTYPE GetLevelDesc(UINT Level, D3DVOLUME_DESC *pDesc);

    HRESULT STDMETHODCALLTYPE GetVolumeLevel(UINT Level, IDirect3DVolume9** ppSurfaceLevel);

    HRESULT STDMETHODCALLTYPE LockBox(UINT Level, D3DLOCKED_BOX* pLockedBox, CONST D3DBOX* pBox, DWORD Flags);

    HRESULT STDMETHODCALLTYPE UnlockBox(UINT Level);

    HRESULT STDMETHODCALLTYPE AddDirtyBox(CONST D3DBOX* pDirtyBox);

  };

  using D3D9TextureCubeBase = D3D9BaseTexture<D3D9Surface, IDirect3DCubeTexture9>;
  class D3D9TextureCube final : public D3D9TextureCubeBase {

  public:

    D3D9TextureCube(
            D3D9DeviceEx*             pDevice,
      const D3D9_COMMON_TEXTURE_DESC* pDesc);

    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject);

    D3DRESOURCETYPE STDMETHODCALLTYPE GetType();

    HRESULT STDMETHODCALLTYPE GetLevelDesc(UINT Level, D3DSURFACE_DESC *pDesc);

    HRESULT STDMETHODCALLTYPE GetCubeMapSurface(D3DCUBEMAP_FACES Face, UINT Level, IDirect3DSurface9** ppSurfaceLevel);

    HRESULT STDMETHODCALLTYPE LockRect(D3DCUBEMAP_FACES Face, UINT Level, D3DLOCKED_RECT* pLockedRect, CONST RECT* pRect, DWORD Flags);

    HRESULT STDMETHODCALLTYPE UnlockRect(D3DCUBEMAP_FACES Face, UINT Level);

    HRESULT STDMETHODCALLTYPE AddDirtyRect(D3DCUBEMAP_FACES Face, CONST RECT* pDirtyRect);

  };

  static_assert(sizeof(D3D9Texture2D) == sizeof(D3D9Texture3D) &&
                sizeof(D3D9Texture2D) == sizeof(D3D9TextureCube));

  inline D3D9CommonTexture* GetCommonTexture(IDirect3DBaseTexture9* ptr) {
    if (ptr == nullptr)
      return nullptr;

    // We can avoid needing to get the type as m_texture has the same offset
    // no matter the texture type.
    // The compiler is not smart enough to eliminate the call to GetType as it is
    // not marked const.
    return static_cast<D3D9Texture2D*>(ptr)->GetCommonTexture();
  }

  inline D3D9CommonTexture* GetCommonTexture(D3D9Surface* ptr) {
    if (ptr == nullptr)
      return nullptr;

    return ptr->GetCommonTexture();
  }

  inline D3D9CommonTexture* GetCommonTexture(IDirect3DSurface9* ptr) {
    return GetCommonTexture(static_cast<D3D9Surface*>(ptr));
  }

  inline void TextureRefPrivate(IDirect3DBaseTexture9* tex, bool AddRef) {
    if (tex == nullptr)
      return;

    // We can avoid needing to get the type as m_refCount has the same offset
    // no matter the texture type.
    // The compiler is not smart enough to eliminate the call to GetType as it is
    // not marked const.
    return CastRefPrivate<D3D9Texture2D>(tex, AddRef);
  }

  inline void TextureChangePrivate(IDirect3DBaseTexture9*& dst, IDirect3DBaseTexture9* src) {
    TextureRefPrivate(dst, false);
    TextureRefPrivate(src, true);
    dst = src;
  }

}


code:
#include "d3d9_util.h"

namespace dxvk {

  typedef HRESULT (STDMETHODCALLTYPE *D3DXDisassembleShader) (
    const void*      pShader,
          BOOL       EnableColorCode,
          char*      pComments,
          ID3DBlob** ppDisassembly); // ppDisassembly is actually a D3DXBUFFER, but it has the exact same vtable as a ID3DBlob at the start.

  D3DXDisassembleShader g_pfnDisassembleShader = nullptr;

  HRESULT DisassembleShader(
    const void*      pShader,
          BOOL       EnableColorCode,
          char*      pComments,
          ID3DBlob** ppDisassembly) {
    if (g_pfnDisassembleShader == nullptr) {
      HMODULE d3d9x = LoadLibraryA("d3dx9.dll");

      if (d3d9x == nullptr)
        d3d9x = LoadLibraryA("d3dx9_43.dll");

      g_pfnDisassembleShader =
        reinterpret_cast<D3DXDisassembleShader>(GetProcAddress(d3d9x, "D3DXDisassembleShader"));
    }

    if (g_pfnDisassembleShader == nullptr)
      return D3DERR_INVALIDCALL;

    return g_pfnDisassembleShader(
      pShader,
      EnableColorCode,
      pComments,
      ppDisassembly);
  }

  HRESULT DecodeMultiSampleType(
    const Rc<DxvkDevice>&           pDevice,
          D3DMULTISAMPLE_TYPE       MultiSample,
          DWORD                     MultisampleQuality,
          VkSampleCountFlagBits*    pSampleCount) {
    uint32_t sampleCount = std::max<uint32_t>(MultiSample, 1u);

    // Check if this is a power of two...
    if (sampleCount & (sampleCount - 1))
      return D3DERR_INVALIDCALL;

    if (MultiSample == D3DMULTISAMPLE_NONMASKABLE)
      sampleCount = 1u << MultisampleQuality;

    const auto& limits = pDevice->properties().core.properties.limits;
    VkSampleCountFlags supportedSampleCounts = limits.framebufferColorSampleCounts & limits.framebufferDepthSampleCounts;

    while (sampleCount > supportedSampleCounts)
      sampleCount >>= 1;

    if (pSampleCount)
      *pSampleCount = VkSampleCountFlagBits(sampleCount);

    return D3D_OK;
  }

  VkFormat GetPackedDepthStencilFormat(D3D9Format Format) {
    switch (Format) {
    case D3D9Format::D15S1:
      return VK_FORMAT_D16_UNORM_S8_UINT; // This should never happen!

    case D3D9Format::D16:
    case D3D9Format::D16_LOCKABLE:
    case D3D9Format::DF16:
      return VK_FORMAT_D16_UNORM;

    case D3D9Format::D24X8:
    case D3D9Format::DF24:
      return VK_FORMAT_X8_D24_UNORM_PACK32;

    case D3D9Format::D24X4S4:
    case D3D9Format::D24FS8:
    case D3D9Format::D24S8:
    case D3D9Format::INTZ:
      return VK_FORMAT_D24_UNORM_S8_UINT;

    case D3D9Format::D32:
    case D3D9Format::D32_LOCKABLE:
    case D3D9Format::D32F_LOCKABLE:
      return VK_FORMAT_D32_SFLOAT;

    case D3D9Format::S8_LOCKABLE:
      return VK_FORMAT_S8_UINT;

    default:
      return VK_FORMAT_UNDEFINED;
    }
  }

  VkFormatFeatureFlags GetImageFormatFeatures(DWORD Usage) {
    VkFormatFeatureFlags features = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT;

    if (Usage & D3DUSAGE_DEPTHSTENCIL)
      features |= VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT;

    if (Usage & D3DUSAGE_RENDERTARGET)
      features |= VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT;

    return features;
  }

  VkImageUsageFlags GetImageUsageFlags(DWORD Usage) {
    VkImageUsageFlags usage = VK_IMAGE_USAGE_SAMPLED_BIT;

    if (Usage & D3DUSAGE_DEPTHSTENCIL)
      usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;

    if (Usage & D3DUSAGE_RENDERTARGET)
      usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;

    return usage;
  }

  uint32_t GetVertexCount(D3DPRIMITIVETYPE type, UINT count) {
    switch (type) {
      default:
      case D3DPT_TRIANGLELIST:  return count * 3;
      case D3DPT_POINTLIST:     return count;
      case D3DPT_LINELIST:      return count * 2;
      case D3DPT_LINESTRIP:     return count + 1;
      case D3DPT_TRIANGLESTRIP: return count + 2;
      case D3DPT_TRIANGLEFAN:   return count + 2;
    }
  }

  DxvkInputAssemblyState DecodeInputAssemblyState(D3DPRIMITIVETYPE type) {
    switch (type) {
      default:
      case D3DPT_TRIANGLELIST:
        return { VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,  VK_FALSE, 0 };

      case D3DPT_POINTLIST:
        return { VK_PRIMITIVE_TOPOLOGY_POINT_LIST,     VK_FALSE, 0 };

      case D3DPT_LINELIST:
        return { VK_PRIMITIVE_TOPOLOGY_LINE_LIST,      VK_FALSE, 0 };

      case D3DPT_LINESTRIP:
        return { VK_PRIMITIVE_TOPOLOGY_LINE_STRIP,     VK_FALSE, 0 };

      case D3DPT_TRIANGLESTRIP:
        return { VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, VK_FALSE, 0 };

      case D3DPT_TRIANGLEFAN:
        return { VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN,   VK_FALSE, 0 };
    }
  }

  VkBlendFactor DecodeBlendFactor(D3DBLEND BlendFactor, bool IsAlpha) {
    switch (BlendFactor) {
      default:
      case D3DBLEND_ZERO:            return VK_BLEND_FACTOR_ZERO;
      case D3DBLEND_ONE:             return VK_BLEND_FACTOR_ONE;
      case D3DBLEND_SRCCOLOR:        return VK_BLEND_FACTOR_SRC_COLOR;
      case D3DBLEND_INVSRCCOLOR:     return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR;
      case D3DBLEND_SRCALPHA:        return VK_BLEND_FACTOR_SRC_ALPHA;
      case D3DBLEND_INVSRCALPHA:     return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
      case D3DBLEND_DESTALPHA:       return VK_BLEND_FACTOR_DST_ALPHA;
      case D3DBLEND_INVDESTALPHA:    return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA;
      case D3DBLEND_DESTCOLOR:       return VK_BLEND_FACTOR_DST_COLOR;
      case D3DBLEND_INVDESTCOLOR:    return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR;
      case D3DBLEND_SRCALPHASAT:     return VK_BLEND_FACTOR_SRC_ALPHA_SATURATE;
      case D3DBLEND_BOTHSRCALPHA:    return VK_BLEND_FACTOR_SRC_ALPHA;
      case D3DBLEND_BOTHINVSRCALPHA: return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
      case D3DBLEND_BLENDFACTOR:     return IsAlpha ? VK_BLEND_FACTOR_CONSTANT_ALPHA : VK_BLEND_FACTOR_CONSTANT_COLOR;
      case D3DBLEND_INVBLENDFACTOR:  return IsAlpha ? VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA : VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR;
      case D3DBLEND_SRCCOLOR2:       return VK_BLEND_FACTOR_SRC1_COLOR;
      case D3DBLEND_INVSRCCOLOR2:    return VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR;
    }
  }

  VkBlendOp DecodeBlendOp(D3DBLENDOP BlendOp) {
    switch (BlendOp) {
      default:
      case D3DBLENDOP_ADD:          return VK_BLEND_OP_ADD;
      case D3DBLENDOP_SUBTRACT:     return VK_BLEND_OP_SUBTRACT;
      case D3DBLENDOP_REVSUBTRACT:  return VK_BLEND_OP_REVERSE_SUBTRACT;
      case D3DBLENDOP_MIN:          return VK_BLEND_OP_MIN;
      case D3DBLENDOP_MAX:          return VK_BLEND_OP_MAX;
    }
  }

  VkFilter DecodeFilter(D3DTEXTUREFILTERTYPE Filter) {
    switch (Filter) {
    case D3DTEXF_NONE:
    case D3DTEXF_POINT:
      return VK_FILTER_NEAREST;
    default:
      return VK_FILTER_LINEAR;
    }
  }

  D3D9MipFilter DecodeMipFilter(D3DTEXTUREFILTERTYPE Filter) {
    D3D9MipFilter filter;
    filter.MipsEnabled = Filter != D3DTEXF_NONE;

    switch (Filter) {
    case D3DTEXF_POINT:
    case D3DTEXF_NONE:
      filter.MipFilter = VK_SAMPLER_MIPMAP_MODE_NEAREST; break;
    default:
      filter.MipFilter = VK_SAMPLER_MIPMAP_MODE_LINEAR; break;
    }

    return filter;
  }

  bool IsAnisotropic(D3DTEXTUREFILTERTYPE Filter) {
    return Filter == D3DTEXF_ANISOTROPIC;
  }

  VkSamplerAddressMode DecodeAddressMode(D3DTEXTUREADDRESS Mode) {
    switch (Mode) {
      default:
      case D3DTADDRESS_WRAP:
        return VK_SAMPLER_ADDRESS_MODE_REPEAT;
      case D3DTADDRESS_MIRROR:
        return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
      case D3DTADDRESS_CLAMP:
        return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
      case D3DTADDRESS_BORDER:
        return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
      case D3DTADDRESS_MIRRORONCE:
        return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE;
    }
  }

  VkCompareOp DecodeCompareOp(D3DCMPFUNC Func) {
    switch (Func) {
      default:
      case D3DCMP_NEVER:        return VK_COMPARE_OP_NEVER;
      case D3DCMP_LESS:         return VK_COMPARE_OP_LESS;
      case D3DCMP_EQUAL:        return VK_COMPARE_OP_EQUAL;
      case D3DCMP_LESSEQUAL:    return VK_COMPARE_OP_LESS_OR_EQUAL;
      case D3DCMP_GREATER:      return VK_COMPARE_OP_GREATER;
      case D3DCMP_NOTEQUAL:     return VK_COMPARE_OP_NOT_EQUAL;
      case D3DCMP_GREATEREQUAL: return VK_COMPARE_OP_GREATER_OR_EQUAL;
      case D3DCMP_ALWAYS:       return VK_COMPARE_OP_ALWAYS;
    }
  }

  VkStencilOp DecodeStencilOp(D3DSTENCILOP Op) {
    switch (Op) {
      default:
      case D3DSTENCILOP_KEEP:    return VK_STENCIL_OP_KEEP;
      case D3DSTENCILOP_ZERO:    return VK_STENCIL_OP_ZERO;
      case D3DSTENCILOP_REPLACE: return VK_STENCIL_OP_REPLACE;
      case D3DSTENCILOP_INCRSAT: return VK_STENCIL_OP_INCREMENT_AND_CLAMP;
      case D3DSTENCILOP_DECRSAT: return VK_STENCIL_OP_DECREMENT_AND_CLAMP;
      case D3DSTENCILOP_INVERT:  return VK_STENCIL_OP_INVERT;
      case D3DSTENCILOP_INCR:    return VK_STENCIL_OP_INCREMENT_AND_WRAP;
      case D3DSTENCILOP_DECR:    return VK_STENCIL_OP_DECREMENT_AND_WRAP;
    }
  }

  VkCullModeFlags DecodeCullMode(D3DCULL Mode) {
    switch (Mode) {
      case D3DCULL_CW:   return VK_CULL_MODE_FRONT_BIT;
      case D3DCULL_CCW:  return VK_CULL_MODE_BACK_BIT;
      default:
      case D3DCULL_NONE: return VK_CULL_MODE_NONE;
    }
  }

  VkPolygonMode DecodeFillMode(D3DFILLMODE Mode) {
    switch (Mode) {
      case D3DFILL_POINT:     return VK_POLYGON_MODE_POINT;
      case D3DFILL_WIREFRAME: return VK_POLYGON_MODE_LINE;
      default:
      case D3DFILL_SOLID:     return VK_POLYGON_MODE_FILL;
    }
  }

  VkIndexType DecodeIndexType(D3D9Format Format) {
    return Format == D3D9Format::INDEX16
                   ? VK_INDEX_TYPE_UINT16
                   : VK_INDEX_TYPE_UINT32;
  }

  VkFormat DecodeDecltype(D3DDECLTYPE Type) {
    switch (Type) {
      case D3DDECLTYPE_FLOAT1:    return VK_FORMAT_R32_SFLOAT;
      case D3DDECLTYPE_FLOAT2:    return VK_FORMAT_R32G32_SFLOAT;
      case D3DDECLTYPE_FLOAT3:    return VK_FORMAT_R32G32B32_SFLOAT;
      case D3DDECLTYPE_FLOAT4:    return VK_FORMAT_R32G32B32A32_SFLOAT;
      case D3DDECLTYPE_D3DCOLOR:  return VK_FORMAT_B8G8R8A8_UNORM;
      case D3DDECLTYPE_UBYTE4:    return VK_FORMAT_R8G8B8A8_USCALED;
      case D3DDECLTYPE_SHORT2:    return VK_FORMAT_R16G16_SSCALED;
      case D3DDECLTYPE_SHORT4:    return VK_FORMAT_R16G16B16A16_SSCALED;
      case D3DDECLTYPE_UBYTE4N:   return VK_FORMAT_R8G8B8A8_UNORM;
      case D3DDECLTYPE_SHORT2N:   return VK_FORMAT_R16G16_SNORM;
      case D3DDECLTYPE_SHORT4N:   return VK_FORMAT_R16G16B16A16_SNORM;
      case D3DDECLTYPE_USHORT2N:  return VK_FORMAT_R16G16_UNORM;
      case D3DDECLTYPE_USHORT4N:  return VK_FORMAT_R16G16B16A16_UNORM;
      case D3DDECLTYPE_UDEC3:     return VK_FORMAT_A2B10G10R10_USCALED_PACK32;
      case D3DDECLTYPE_FLOAT16_2: return VK_FORMAT_R16G16_SFLOAT;
      case D3DDECLTYPE_FLOAT16_4: return VK_FORMAT_R16G16B16A16_SFLOAT;
      case D3DDECLTYPE_DEC3N:     return VK_FORMAT_A2B10G10R10_SNORM_PACK32;
      case D3DDECLTYPE_UNUSED:
      default:                    return VK_FORMAT_UNDEFINED;
    }
  }

  void ConvertBox(D3DBOX box, VkOffset3D& offset, VkExtent3D& extent) {
    offset.x = box.Left;
    offset.y = box.Top;
    offset.z = box.Front;

    extent.width  = box.Right  - box.Left;
    extent.height = box.Bottom - box.Top;
    extent.depth  = box.Back   - box.Front;
  }

  void ConvertRect(RECT rect, VkOffset3D& offset, VkExtent3D& extent) {
    offset.x = rect.left;
    offset.y = rect.top;
    offset.z = 0;

    extent.width  = rect.right  - rect.left;
    extent.height = rect.bottom - rect.top;
    extent.depth  = 1;
  }

  void ConvertRect(RECT rect, VkOffset2D& offset, VkExtent2D& extent) {
    offset.x = rect.left;
    offset.y = rect.top;

    extent.width  = rect.right  - rect.left;
    extent.height = rect.bottom - rect.top;
  }

  uint32_t GetDecltypeSize(D3DDECLTYPE Type) {
    switch (Type) {
      case D3DDECLTYPE_FLOAT1:    return 1 * sizeof(float);
      case D3DDECLTYPE_FLOAT2:    return 2 * sizeof(float);
      case D3DDECLTYPE_FLOAT3:    return 3 * sizeof(float);
      case D3DDECLTYPE_FLOAT4:    return 4 * sizeof(float);
      case D3DDECLTYPE_D3DCOLOR:  return 1 * sizeof(DWORD);
      case D3DDECLTYPE_UBYTE4:    return 4 * sizeof(BYTE);
      case D3DDECLTYPE_SHORT2:    return 2 * sizeof(short);
      case D3DDECLTYPE_SHORT4:    return 4 * sizeof(short);
      case D3DDECLTYPE_UBYTE4N:   return 4 * sizeof(BYTE);
      case D3DDECLTYPE_SHORT2N:   return 2 * sizeof(short);
      case D3DDECLTYPE_SHORT4N:   return 4 * sizeof(short);
      case D3DDECLTYPE_USHORT2N:  return 2 * sizeof(short);
      case D3DDECLTYPE_USHORT4N:  return 4 * sizeof(short);
      case D3DDECLTYPE_UDEC3:     return 4;
      case D3DDECLTYPE_DEC3N:     return 4;
      case D3DDECLTYPE_FLOAT16_2: return 2 * 2;
      case D3DDECLTYPE_FLOAT16_4: return 4 * 2;
      default:                    return 0;
    }
  }

  uint32_t GetDecltypeCount(D3DDECLTYPE Type) {
    switch (Type) {
      case D3DDECLTYPE_FLOAT1:    return 1;
      case D3DDECLTYPE_FLOAT2:    return 2;
      case D3DDECLTYPE_FLOAT3:    return 3;
      case D3DDECLTYPE_FLOAT4:    return 4;
      case D3DDECLTYPE_D3DCOLOR:  return 4;
      case D3DDECLTYPE_UBYTE4:    return 4;
      case D3DDECLTYPE_SHORT2:    return 2;
      case D3DDECLTYPE_SHORT4:    return 4;
      case D3DDECLTYPE_UBYTE4N:   return 4;
      case D3DDECLTYPE_SHORT2N:   return 2;
      case D3DDECLTYPE_SHORT4N:   return 4;
      case D3DDECLTYPE_USHORT2N:  return 2;
      case D3DDECLTYPE_USHORT4N:  return 4;
      case D3DDECLTYPE_UDEC3:     return 3;
      case D3DDECLTYPE_DEC3N:     return 3;
      case D3DDECLTYPE_FLOAT16_2: return 2;
      case D3DDECLTYPE_FLOAT16_4: return 4;
      default:                    return 0;
    }
  }

  bool IsDepthFormat(D3D9Format Format) {
    return Format == D3D9Format::D16_LOCKABLE
        || Format == D3D9Format::D32
        || Format == D3D9Format::D15S1
        || Format == D3D9Format::D24S8
        || Format == D3D9Format::D24X8
        || Format == D3D9Format::D24X4S4
        || Format == D3D9Format::D16
        || Format == D3D9Format::D32F_LOCKABLE
        || Format == D3D9Format::D24FS8
        || Format == D3D9Format::D32_LOCKABLE
        || Format == D3D9Format::DF16
        || Format == D3D9Format::DF24
        || Format == D3D9Format::INTZ;
  }

}