text: #include "dxgi_factory.h"
  #include "dxgi_output.h"
  #include "dxgi_swapchain.h"
  
  namespace dxvk {
    
    DxgiSwapChain::DxgiSwapChain(
            IDXGIFactory*               pFactory,
            IDXGIVkSwapChain*           pPresenter,
            HWND                        hWnd,
      const DXGI_SWAP_CHAIN_DESC1*      pDesc,
      const DXGI_SWAP_CHAIN_FULLSCREEN_DESC*  pFullscreenDesc)
    : m_factory   (pFactory),
      m_window    (hWnd),
      m_desc      (*pDesc),
      m_descFs    (*pFullscreenDesc),
      m_presenter (pPresenter),
      m_monitor   (nullptr) {
      // Initialize frame statistics
      m_stats.PresentCount         = 0;
      m_stats.PresentRefreshCount  = 0;
      m_stats.SyncRefreshCount     = 0;
      m_stats.SyncQPCTime.QuadPart = 0;
      m_stats.SyncGPUTime.QuadPart = 0;
      
      if (FAILED(m_presenter->GetAdapter(__uuidof(IDXGIAdapter), reinterpret_cast<void**>(&m_adapter))))
        throw DxvkError("DXGI: Failed to get adapter for present device");
      
      // Query monitor info form DXVK's DXGI factory, if available
      m_factory->QueryInterface(__uuidof(IDXGIVkMonitorInfo), reinterpret_cast<void**>(&m_monitorInfo));
      
      // Apply initial window mode and fullscreen state
      if (!m_descFs.Windowed && FAILED(EnterFullscreenMode(nullptr)))
        throw DxvkError("DXGI: Failed to set initial fullscreen state");
    }
    
    
    DxgiSwapChain::~DxgiSwapChain() {
      RestoreDisplayMode(m_monitor);
  
      // Decouple swap chain from monitor if necessary
      DXGI_VK_MONITOR_DATA* monitorInfo = nullptr;
      
      if (SUCCEEDED(AcquireMonitorData(m_monitor, &monitorInfo))) {
        if (monitorInfo->pSwapChain == this)
          monitorInfo->pSwapChain = nullptr;
        
        ReleaseMonitorData();
      }
    }
    
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::QueryInterface(REFIID riid, void** ppvObject) {
      if (ppvObject == nullptr)
        return E_POINTER;
  
      *ppvObject = nullptr;
      
      if (riid == __uuidof(IUnknown)
       || riid == __uuidof(IDXGIObject)
       || riid == __uuidof(IDXGIDeviceSubObject)
       || riid == __uuidof(IDXGISwapChain)
       || riid == __uuidof(IDXGISwapChain1)
       || riid == __uuidof(IDXGISwapChain2)
       || riid == __uuidof(IDXGISwapChain3)
       || riid == __uuidof(IDXGISwapChain4)) {
        *ppvObject = ref(this);
        return S_OK;
      }
      
      Logger::warn("DxgiSwapChain::QueryInterface: Unknown interface query");
      Logger::warn(str::format(riid));
      return E_NOINTERFACE;
    }
    
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::GetParent(REFIID riid, void** ppParent) {
      return m_factory->QueryInterface(riid, ppParent);
    }
    
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::GetDevice(REFIID riid, void** ppDevice) {
      return m_presenter->GetDevice(riid, ppDevice);
    }
    
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::GetBuffer(UINT Buffer, REFIID riid, void** ppSurface) {
      return m_presenter->GetImage(Buffer, riid, ppSurface);
    }
  
  
    UINT STDMETHODCALLTYPE DxgiSwapChain::GetCurrentBackBufferIndex() {
      return m_presenter->GetImageIndex();
    }
    
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::GetContainingOutput(IDXGIOutput** ppOutput) {
      InitReturnPtr(ppOutput);
      
      if (!IsWindow(m_window))
        return DXGI_ERROR_INVALID_CALL;
      
      if (m_target != nullptr) {
        *ppOutput = m_target.ref();
        return S_OK;
      }
  
      RECT windowRect = { 0, 0, 0, 0 };
      ::GetWindowRect(m_window, &windowRect);
      
      HMONITOR monitor = ::MonitorFromPoint(
        { (windowRect.left + windowRect.right) / 2,
          (windowRect.top + windowRect.bottom) / 2 },
        MONITOR_DEFAULTTOPRIMARY);
      
      return GetOutputFromMonitor(monitor, ppOutput);
    }
    
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::GetDesc(DXGI_SWAP_CHAIN_DESC* pDesc) {
      if (!pDesc)
        return E_INVALIDARG;
      
      pDesc->BufferDesc.Width     = m_desc.Width;
      pDesc->BufferDesc.Height    = m_desc.Height;
      pDesc->BufferDesc.RefreshRate = m_descFs.RefreshRate;
      pDesc->BufferDesc.Format    = m_desc.Format;
      pDesc->BufferDesc.ScanlineOrdering = m_descFs.ScanlineOrdering;
      pDesc->BufferDesc.Scaling   = m_descFs.Scaling;
      pDesc->SampleDesc           = m_desc.SampleDesc;
      pDesc->BufferUsage          = m_desc.BufferUsage;
      pDesc->BufferCount          = m_desc.BufferCount;
      pDesc->OutputWindow         = m_window;
      pDesc->Windowed             = m_descFs.Windowed;
      pDesc->SwapEffect           = m_desc.SwapEffect;
      pDesc->Flags                = m_desc.Flags;
      return S_OK;
    }
    
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::GetDesc1(DXGI_SWAP_CHAIN_DESC1* pDesc) {
      if (pDesc == nullptr)
        return E_INVALIDARG;
      
      *pDesc = m_desc;
      return S_OK;
    }
    
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::GetBackgroundColor(
            DXGI_RGBA*                pColor) {
      Logger::err("DxgiSwapChain::GetBackgroundColor: Not implemented");
      return E_NOTIMPL;
    }
    
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::GetRotation(
            DXGI_MODE_ROTATION*       pRotation) {
      Logger::err("DxgiSwapChain::GetRotation: Not implemented");
      return E_NOTIMPL;
    }
    
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::GetRestrictToOutput(
            IDXGIOutput**             ppRestrictToOutput) {
      InitReturnPtr(ppRestrictToOutput);
      
      Logger::err("DxgiSwapChain::GetRestrictToOutput: Not implemented");
      return E_NOTIMPL;
    }
    
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::GetFrameStatistics(DXGI_FRAME_STATISTICS* pStats) {
      if (pStats == nullptr)
        return E_INVALIDARG;
      
      *pStats = m_stats;
      return S_OK;
    }
    
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::GetFullscreenState(
            BOOL*         pFullscreen,
            IDXGIOutput** ppTarget) {
      if (!IsWindow(m_window))
        return DXGI_ERROR_INVALID_CALL;
      
      HRESULT hr = S_OK;
      
      if (pFullscreen != nullptr)
        *pFullscreen = !m_descFs.Windowed;
      
      if (ppTarget != nullptr)
        *ppTarget = m_target.ref();
  
      return hr;
    }
    
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::GetFullscreenDesc(
            DXGI_SWAP_CHAIN_FULLSCREEN_DESC* pDesc) {
      if (pDesc == nullptr)
        return E_INVALIDARG;
      
      *pDesc = m_descFs;
      return S_OK;
    }
    
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::GetHwnd(
            HWND*                     pHwnd) {
      if (pHwnd == nullptr)
        return E_INVALIDARG;
      
      *pHwnd = m_window;
      return S_OK;
    }
    
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::GetCoreWindow(
            REFIID                    refiid,
            void**                    ppUnk) {
      InitReturnPtr(ppUnk);
      
      Logger::err("DxgiSwapChain::GetCoreWindow: Not implemented");
      return E_NOTIMPL;
    }
    
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::GetLastPresentCount(UINT* pLastPresentCount) {
      if (pLastPresentCount == nullptr)
        return E_INVALIDARG;
      
      *pLastPresentCount = m_stats.PresentCount;
      return S_OK;
    }
    
    
    BOOL STDMETHODCALLTYPE DxgiSwapChain::IsTemporaryMonoSupported() {
      // This seems to be related to stereo 3D display
      // modes, which we don't support at the moment
      return FALSE;
    }
    
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::Present(UINT SyncInterval, UINT Flags) {
      return Present1(SyncInterval, Flags, nullptr);
    }
    
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::Present1(
            UINT                      SyncInterval,
            UINT                      PresentFlags,
      const DXGI_PRESENT_PARAMETERS*  pPresentParameters) {
      if (!IsWindow(m_window))
        return DXGI_ERROR_INVALID_CALL;
      
      if (SyncInterval > 4)
        return DXGI_ERROR_INVALID_CALL;
  
      if (PresentFlags & DXGI_PRESENT_TEST)
        return S_OK;
      
      std::lock_guard<std::recursive_mutex> lockWin(m_lockWindow);
      std::lock_guard<std::mutex> lockBuf(m_lockBuffer);
  
      try {
        return m_presenter->Present(SyncInterval, PresentFlags, nullptr);
      } catch (const DxvkError& err) {
        Logger::err(err.message());
        return DXGI_ERROR_DRIVER_INTERNAL_ERROR;
      }
    }
    
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::ResizeBuffers(
            UINT        BufferCount,
            UINT        Width,
            UINT        Height,
            DXGI_FORMAT NewFormat,
            UINT        SwapChainFlags) {
      if (!IsWindow(m_window))
        return DXGI_ERROR_INVALID_CALL;
  
      constexpr UINT PreserveFlags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
  
      if ((m_desc.Flags & PreserveFlags) != (SwapChainFlags & PreserveFlags))
        return DXGI_ERROR_INVALID_CALL;
      
      std::lock_guard<std::mutex> lock(m_lockBuffer);
      m_desc.Width  = Width;
      m_desc.Height = Height;
      
      GetWindowClientSize(m_window,
        m_desc.Width  ? nullptr : &m_desc.Width,
        m_desc.Height ? nullptr : &m_desc.Height);
      
      if (BufferCount != 0)
        m_desc.BufferCount = BufferCount;
      
      if (NewFormat != DXGI_FORMAT_UNKNOWN)
        m_desc.Format = NewFormat;
      
      return m_presenter->ChangeProperties(&m_desc);
    }
    
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::ResizeBuffers1(
            UINT                      BufferCount,
            UINT                      Width,
            UINT                      Height,
            DXGI_FORMAT               Format,
            UINT                      SwapChainFlags,
      const UINT*                     pCreationNodeMask,
            IUnknown* const*          ppPresentQueue) {
      static bool s_errorShown = false;
  
      if (!std::exchange(s_errorShown, true))
        Logger::warn("DxgiSwapChain::ResizeBuffers1: Stub");
  
      return ResizeBuffers(BufferCount,
        Width, Height, Format, SwapChainFlags);
    }
  
  
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::ResizeTarget(const DXGI_MODE_DESC* pNewTargetParameters) {
      std::lock_guard<std::recursive_mutex> lock(m_lockWindow);
  
      if (pNewTargetParameters == nullptr)
        return DXGI_ERROR_INVALID_CALL;
      
      if (!IsWindow(m_window))
        return DXGI_ERROR_INVALID_CALL;
  
      // Update the swap chain description
      if (pNewTargetParameters->RefreshRate.Numerator != 0)
        m_descFs.RefreshRate = pNewTargetParameters->RefreshRate;
      
      m_descFs.ScanlineOrdering = pNewTargetParameters->ScanlineOrdering;
      m_descFs.Scaling          = pNewTargetParameters->Scaling;
      
      if (m_descFs.Windowed) {
        // Adjust window position and size
        RECT newRect = { 0, 0, 0, 0 };
        RECT oldRect = { 0, 0, 0, 0 };
        
        ::GetWindowRect(m_window, &oldRect);
        ::SetRect(&newRect, 0, 0, pNewTargetParameters->Width, pNewTargetParameters->Height);
        ::AdjustWindowRectEx(&newRect,
          ::GetWindowLongW(m_window, GWL_STYLE), FALSE,
          ::GetWindowLongW(m_window, GWL_EXSTYLE));
        ::SetRect(&newRect, 0, 0, newRect.right - newRect.left, newRect.bottom - newRect.top);
        ::OffsetRect(&newRect, oldRect.left, oldRect.top);    
        ::MoveWindow(m_window, newRect.left, newRect.top,
            newRect.right - newRect.left, newRect.bottom - newRect.top, TRUE);
      } else {
        Com<IDXGIOutput> output;
        
        if (FAILED(GetOutputFromMonitor(m_monitor, &output))) {
          Logger::err("DXGI: ResizeTarget: Failed to query containing output");
          return E_FAIL;
        }
        
        // If the swap chain allows it, change the display mode
        if (m_desc.Flags & DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH)
          ChangeDisplayMode(output.ptr(), pNewTargetParameters);
        
        // Resize and reposition the window to 
        DXGI_OUTPUT_DESC desc;
        output->GetDesc(&desc);
        
        RECT newRect = desc.DesktopCoordinates;
        
        ::MoveWindow(m_window, newRect.left, newRect.top,
            newRect.right - newRect.left, newRect.bottom - newRect.top, TRUE);
      }
      
      return S_OK;
    }
    
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::SetFullscreenState(
            BOOL          Fullscreen,
            IDXGIOutput*  pTarget) {
      std::lock_guard<std::recursive_mutex> lock(m_lockWindow);
  
      if (!Fullscreen && pTarget)
        return DXGI_ERROR_INVALID_CALL;
  
      if (m_descFs.Windowed && Fullscreen)
        return this->EnterFullscreenMode(pTarget);
      else if (!m_descFs.Windowed && !Fullscreen)
        return this->LeaveFullscreenMode();
      
      return S_OK;
    }
    
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::SetBackgroundColor(
      const DXGI_RGBA*                pColor) {
      Logger::err("DxgiSwapChain::SetBackgroundColor: Not implemented");
      return E_NOTIMPL;
    }
    
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::SetRotation(
            DXGI_MODE_ROTATION        Rotation) {
      Logger::err("DxgiSwapChain::SetRotation: Not implemented");
      return E_NOTIMPL;
    }
    
    
    HANDLE STDMETHODCALLTYPE DxgiSwapChain::GetFrameLatencyWaitableObject() {
      if (!(m_desc.Flags & DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT))
        return nullptr;
  
      return m_presenter->GetFrameLatencyEvent();
    }
  
  
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::GetMatrixTransform(
            DXGI_MATRIX_3X2_F*        pMatrix) {
      // We don't support composition swap chains
      Logger::err("DxgiSwapChain::GetMatrixTransform: Not supported");
      return DXGI_ERROR_INVALID_CALL;
    }
  
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::GetMaximumFrameLatency(
            UINT*                     pMaxLatency) {
      if (!(m_desc.Flags & DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT))
        return DXGI_ERROR_INVALID_CALL;
  
      std::lock_guard<std::recursive_mutex> lock(m_lockWindow);
      *pMaxLatency = m_presenter->GetFrameLatency();
      return S_OK;
    }
  
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::GetSourceSize(
            UINT*                     pWidth,
            UINT*                     pHeight) {
      // TODO implement properly once supported
      if (pWidth)  *pWidth  = m_desc.Width;
      if (pHeight) *pHeight = m_desc.Height;
      return S_OK;
    }
  
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::SetMatrixTransform(
      const DXGI_MATRIX_3X2_F*        pMatrix) {
      // We don't support composition swap chains
      Logger::err("DxgiSwapChain::SetMatrixTransform: Not supported");
      return DXGI_ERROR_INVALID_CALL;
    }
  
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::SetMaximumFrameLatency(
            UINT                      MaxLatency) {
      if (!(m_desc.Flags & DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT))
        return DXGI_ERROR_INVALID_CALL;
  
      std::lock_guard<std::recursive_mutex> lock(m_lockWindow);
      return m_presenter->SetFrameLatency(MaxLatency);
    }
  
  
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::SetSourceSize(
            UINT                      Width,
            UINT                      Height) {
      if (Width  == 0 || Width  > m_desc.Width
       || Height == 0 || Height > m_desc.Height)
        return E_INVALIDARG;
  
      RECT region;
      region.left   = 0;
      region.top    = 0;
      region.right  = Width;
      region.bottom = Height;
      return m_presenter->SetPresentRegion(&region);
    }
    
  
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::CheckColorSpaceSupport(
            DXGI_COLOR_SPACE_TYPE           ColorSpace,
            UINT*                           pColorSpaceSupport) {
      if (!pColorSpaceSupport)
        return E_INVALIDARG;
      
      UINT supportFlags = 0;
  
      if (ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709)
        supportFlags |= DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT;
  
      *pColorSpaceSupport = supportFlags;
      return S_OK;
    }
  
  
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::SetColorSpace1(DXGI_COLOR_SPACE_TYPE ColorSpace) {
      UINT support = 0;
  
      HRESULT hr = CheckColorSpaceSupport(ColorSpace, &support);
  
      if (FAILED(hr))
        return hr;
  
      if (!support)
        return E_INVALIDARG;
  
      return S_OK;
    }
  
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::SetHDRMetaData(
            DXGI_HDR_METADATA_TYPE    Type,
            UINT                      Size,
            void*                     pMetaData) {
      if (Size && !pMetaData)
        return E_INVALIDARG;
  
      switch (Type) {
        case DXGI_HDR_METADATA_TYPE_NONE:
          return S_OK;
  
        case DXGI_HDR_METADATA_TYPE_HDR10:
          if (Size != sizeof(DXGI_HDR_METADATA_HDR10))
            return E_INVALIDARG;
  
          // For some reason this always seems to succeed on Windows
          Logger::warn("DXGI: HDR not supported");
          return S_OK;
  
        default:
          Logger::err(str::format("DXGI: Invalid HDR metadata type: ", Type));
          return E_INVALIDARG;
      }
    }
    
    
    HRESULT STDMETHODCALLTYPE DxgiSwapChain::SetGammaControl(
            UINT                      NumPoints,
      const DXGI_RGB*                 pGammaCurve) {
      std::lock_guard<std::mutex> lockBuf(m_lockBuffer);
      return m_presenter->SetGammaControl(NumPoints, pGammaCurve);
    }
  
  
    HRESULT DxgiSwapChain::EnterFullscreenMode(IDXGIOutput* pTarget) {
      Com<IDXGIOutput> output = pTarget;
  
      if (!IsWindow(m_window))
        return DXGI_ERROR_NOT_CURRENTLY_AVAILABLE;
      
      if (output == nullptr) {
        if (FAILED(GetContainingOutput(&output))) {
          Logger::err("DXGI: EnterFullscreenMode: Cannot query containing output");
          return E_FAIL;
        }
      }
      
      // Find a display mode that matches what we need
      ::GetWindowRect(m_window, &m_windowState.rect);
      
      if (m_desc.Flags & DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH) {
        DXGI_MODE_DESC displayMode;
        displayMode.Width            = m_desc.Width;
        displayMode.Height           = m_desc.Height;
        displayMode.RefreshRate      = m_descFs.RefreshRate;
        displayMode.Format           = m_desc.Format;
        // Ignore these two, games usually use them wrong and we don't
        // support any scaling modes except UNSPECIFIED anyway.
        displayMode.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
        displayMode.Scaling          = DXGI_MODE_SCALING_UNSPECIFIED;
        
        if (FAILED(ChangeDisplayMode(output.ptr(), &displayMode))) {
          Logger::err("DXGI: EnterFullscreenMode: Failed to change display mode");
          return DXGI_ERROR_NOT_CURRENTLY_AVAILABLE;
        }
      }
      
      // Update swap chain description
      m_descFs.Windowed = FALSE;
      
      // 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
      DXGI_OUTPUT_DESC desc;
      output->GetDesc(&desc);
      
      const RECT rect = desc.DesktopCoordinates;
      
      ::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 = desc.Monitor;
      m_target  = std::move(output);
  
      // Apply current gamma curve of the output
      DXGI_VK_MONITOR_DATA* monitorInfo = nullptr;
  
      if (SUCCEEDED(AcquireMonitorData(m_monitor, &monitorInfo))) {
        if (!monitorInfo->pSwapChain)
          monitorInfo->pSwapChain = this;
        
        SetGammaControl(DXGI_VK_GAMMA_CP_COUNT, monitorInfo->GammaCurve.GammaCurve);
        ReleaseMonitorData();
      }
  
      return S_OK;
    }
    
    
    HRESULT DxgiSwapChain::LeaveFullscreenMode() {
      if (FAILED(RestoreDisplayMode(m_monitor)))
        Logger::warn("DXGI: LeaveFullscreenMode: Failed to restore display mode");
      
      // Reset gamma control and decouple swap chain from monitor
      DXGI_VK_MONITOR_DATA* monitorInfo = nullptr;
  
      if (SUCCEEDED(AcquireMonitorData(m_monitor, &monitorInfo))) {
        if (monitorInfo->pSwapChain == this)
          monitorInfo->pSwapChain = nullptr;
        
        SetGammaControl(0, nullptr);
        ReleaseMonitorData();
      }
      
      // Restore internal state
      m_descFs.Windowed = TRUE;
      m_monitor = nullptr;
      m_target  = nullptr;
      
      if (!IsWindow(m_window))
        return S_OK;
      
      // Only restore the window style if the application hasn't
      // changed them. This is in line with what native DXGI 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 S_OK;
    }
    
    
    HRESULT DxgiSwapChain::ChangeDisplayMode(
            IDXGIOutput*            pOutput,
      const DXGI_MODE_DESC*         pDisplayMode) {
      if (!pOutput)
        return DXGI_ERROR_INVALID_CALL;
      
      // Find a mode that the output supports
      DXGI_OUTPUT_DESC outputDesc;
      pOutput->GetDesc(&outputDesc);
      
      DXGI_MODE_DESC preferredMode = *pDisplayMode;
      DXGI_MODE_DESC selectedMode;
  
      if (preferredMode.Format == DXGI_FORMAT_UNKNOWN)
        preferredMode.Format = m_desc.Format;
      
      HRESULT hr = pOutput->FindClosestMatchingMode(
        &preferredMode, &selectedMode, nullptr);
      
      if (FAILED(hr)) {
        Logger::err(str::format(
          "DXGI: Failed to query closest mode:",
          "\n  Format: ", preferredMode.Format,
          "\n  Mode:   ", preferredMode.Width, "x", preferredMode.Height,
            "@", preferredMode.RefreshRate.Numerator / preferredMode.RefreshRate.Denominator));
        return hr;
      }
      
      return SetMonitorDisplayMode(outputDesc.Monitor, &selectedMode);
    }
    
    
    HRESULT DxgiSwapChain::RestoreDisplayMode(HMONITOR hMonitor) {
      if (!hMonitor)
        return DXGI_ERROR_INVALID_CALL;
      
      // Restore registry settings
      DXGI_MODE_DESC mode;
      
      HRESULT hr = GetMonitorDisplayMode(
        hMonitor, ENUM_REGISTRY_SETTINGS, &mode);
      
      if (FAILED(hr))
        return hr;
      
      return SetMonitorDisplayMode(hMonitor, &mode);
    }
    
    
    HRESULT DxgiSwapChain::GetSampleCount(UINT Count, VkSampleCountFlagBits* pCount) const {
      switch (Count) {
        case  1: *pCount = VK_SAMPLE_COUNT_1_BIT;  return S_OK;
        case  2: *pCount = VK_SAMPLE_COUNT_2_BIT;  return S_OK;
        case  4: *pCount = VK_SAMPLE_COUNT_4_BIT;  return S_OK;
        case  8: *pCount = VK_SAMPLE_COUNT_8_BIT;  return S_OK;
        case 16: *pCount = VK_SAMPLE_COUNT_16_BIT; return S_OK;
      }
      
      return E_INVALIDARG;
    }
  
  
    HRESULT DxgiSwapChain::GetOutputFromMonitor(
            HMONITOR                  Monitor,
            IDXGIOutput**             ppOutput) {
      if (!ppOutput)
        return DXGI_ERROR_INVALID_CALL;
      
      for (uint32_t i = 0; SUCCEEDED(m_adapter->EnumOutputs(i, ppOutput)); i++) {
        DXGI_OUTPUT_DESC outputDesc;
        (*ppOutput)->GetDesc(&outputDesc);
        
        if (outputDesc.Monitor == Monitor)
          return S_OK;
        
        (*ppOutput)->Release();
        (*ppOutput) = nullptr;
      }
      
      return DXGI_ERROR_NOT_FOUND;
    }
  
  
    HRESULT DxgiSwapChain::AcquireMonitorData(
            HMONITOR                hMonitor,
            DXGI_VK_MONITOR_DATA**  ppData) {
      return m_monitorInfo != nullptr
        ? m_monitorInfo->AcquireMonitorData(hMonitor, ppData)
        : E_NOINTERFACE;
    }
  
    
    void DxgiSwapChain::ReleaseMonitorData() {
      if (m_monitorInfo != nullptr)
        m_monitorInfo->ReleaseMonitorData();
    }
    
  }
  