From f615594f22d40e58092d1d0e9056663b5811a66a Mon Sep 17 00:00:00 2001
From: Philip Rebohle <philip.rebohle@tu-dortmund.de>
Date: Mon, 10 Feb 2025 14:03:40 +0100
Subject: [PATCH] [dxgi] Create dummy window for composition swap chains

Obviously not a valid implementation, but allows composition swap chains
to exist with some basic functionality.
---
 src/dxgi/dxgi_factory.cpp | 152 ++++++++++++++++++++------------------
 src/dxgi/dxgi_factory.h   |   2 +-
 src/dxgi/dxgi_surface.cpp |  54 ++++++++++++--
 src/dxgi/dxgi_surface.h   |  11 ++-
 4 files changed, 137 insertions(+), 82 deletions(-)

diff --git a/src/dxgi/dxgi_factory.cpp b/src/dxgi/dxgi_factory.cpp
index fb9c6d359..af7321210 100644
--- a/src/dxgi/dxgi_factory.cpp
+++ b/src/dxgi/dxgi_factory.cpp
@@ -207,9 +207,9 @@ namespace dxvk {
           IUnknown*             pDevice,
           DXGI_SWAP_CHAIN_DESC* pDesc,
           IDXGISwapChain**      ppSwapChain) {
-    if (ppSwapChain == nullptr || pDesc == nullptr || pDevice == nullptr)
+    if (!ppSwapChain || !pDesc || !pDesc->OutputWindow || !pDevice)
       return DXGI_ERROR_INVALID_CALL;
-    
+
     DXGI_SWAP_CHAIN_DESC1 desc;
     desc.Width              = pDesc->BufferDesc.Width;
     desc.Height             = pDesc->BufferDesc.Height;
@@ -222,7 +222,7 @@ namespace dxvk {
     desc.SwapEffect         = pDesc->SwapEffect;
     desc.AlphaMode          = DXGI_ALPHA_MODE_IGNORE;
     desc.Flags              = pDesc->Flags;
-    
+
     DXGI_SWAP_CHAIN_FULLSCREEN_DESC descFs;
     descFs.RefreshRate      = pDesc->BufferDesc.RefreshRate;
     descFs.ScanlineOrdering = pDesc->BufferDesc.ScanlineOrdering;
@@ -230,11 +230,10 @@ namespace dxvk {
     descFs.Windowed         = pDesc->Windowed;
     
     IDXGISwapChain1* swapChain = nullptr;
-    HRESULT hr = CreateSwapChainForHwndBase(
-      pDevice, pDesc->OutputWindow,
-      &desc, &descFs, nullptr,
-      &swapChain);
-    
+
+    HRESULT hr = CreateSwapChainBase(pDevice,
+      pDesc->OutputWindow, &desc, &descFs, nullptr, &swapChain);
+
     *ppSwapChain = swapChain;
     return hr;
   }
@@ -247,73 +246,17 @@ namespace dxvk {
     const DXGI_SWAP_CHAIN_FULLSCREEN_DESC* pFullscreenDesc,
           IDXGIOutput*          pRestrictToOutput,
           IDXGISwapChain1**     ppSwapChain) {
-    return CreateSwapChainForHwndBase(
-      pDevice, hWnd,
+    InitReturnPtr(ppSwapChain);
+
+    if (!ppSwapChain || !pDesc || !hWnd || !pDevice)
+      return DXGI_ERROR_INVALID_CALL;
+
+    return CreateSwapChainBase(pDevice, hWnd,
       pDesc, pFullscreenDesc, pRestrictToOutput,
       ppSwapChain);
   }
 
-  HRESULT STDMETHODCALLTYPE DxgiFactory::CreateSwapChainForHwndBase(
-          IUnknown*             pDevice,
-          HWND                  hWnd,
-    const DXGI_SWAP_CHAIN_DESC1* pDesc,
-    const DXGI_SWAP_CHAIN_FULLSCREEN_DESC* pFullscreenDesc,
-          IDXGIOutput*          pRestrictToOutput,
-          IDXGISwapChain1**     ppSwapChain) {
-    InitReturnPtr(ppSwapChain);
-    
-    if (!ppSwapChain || !pDesc || !hWnd || !pDevice)
-      return DXGI_ERROR_INVALID_CALL;
-    
-    // Make sure the back buffer size is not zero
-    DXGI_SWAP_CHAIN_DESC1 desc = *pDesc;
 
-    wsi::getWindowSize(hWnd,
-      desc.Width  ? nullptr : &desc.Width,
-      desc.Height ? nullptr : &desc.Height);
-
-    // If necessary, set up a default set of
-    // fullscreen parameters for the swap chain
-    DXGI_SWAP_CHAIN_FULLSCREEN_DESC fsDesc;
-
-    if (pFullscreenDesc) {
-      fsDesc = *pFullscreenDesc;
-    } else {
-      fsDesc.RefreshRate      = { 0, 0 };
-      fsDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
-      fsDesc.Scaling          = DXGI_MODE_SCALING_UNSPECIFIED;
-      fsDesc.Windowed         = TRUE;
-    }
-
-    // Probe various modes to create the swap chain object
-    Com<IDXGISwapChain4> frontendSwapChain;
-
-    Com<IDXGIVkSwapChainFactory> dxvkFactory;
-
-    if (SUCCEEDED(pDevice->QueryInterface(IID_PPV_ARGS(&dxvkFactory)))) {
-      Com<IDXGIVkSurfaceFactory> surfaceFactory = new DxgiSurfaceFactory(
-        m_instance->vki()->getLoaderProc(), hWnd);
-
-      Com<IDXGIVkSwapChain> presenter;
-      HRESULT hr = dxvkFactory->CreateSwapChain(surfaceFactory.ptr(), &desc, &presenter);
-
-      if (FAILED(hr)) {
-        Logger::err(str::format("DXGI: CreateSwapChainForHwnd: Failed to create swap chain, hr ", hr));
-        return hr;
-      }
-
-      frontendSwapChain = new DxgiSwapChain(this, presenter.ptr(), hWnd, &desc, &fsDesc, pDevice);
-    } else {
-      Logger::err("DXGI: CreateSwapChainForHwnd: Unsupported device type");
-      return DXGI_ERROR_UNSUPPORTED;
-    }
-    
-    // Wrap object in swap chain dispatcher
-    *ppSwapChain = new DxgiSwapChainDispatcher(frontendSwapChain.ref(), pDevice);
-    return S_OK;
-  }
-  
-  
   HRESULT STDMETHODCALLTYPE DxgiFactory::CreateSwapChainForCoreWindow(
           IUnknown*             pDevice,
           IUnknown*             pWindow,
@@ -333,9 +276,16 @@ namespace dxvk {
           IDXGIOutput*          pRestrictToOutput,
           IDXGISwapChain1**     ppSwapChain) {
     InitReturnPtr(ppSwapChain);
-    
-    Logger::err("DxgiFactory::CreateSwapChainForComposition: Not implemented");
-    return E_NOTIMPL;
+
+    if (!m_options.enableDummyCompositionSwapchain) {
+      Logger::err("DxgiFactory::CreateSwapChainForComposition: Not implemented");
+      return E_NOTIMPL;
+    }
+
+    Logger::warn("DxgiFactory::CreateSwapChainForComposition: Creating dummy swap chain");
+
+    return CreateSwapChainBase(pDevice,
+      nullptr, pDesc, nullptr, pRestrictToOutput, ppSwapChain);
   }
   
   
@@ -562,6 +512,62 @@ namespace dxvk {
   }
 
 
+  HRESULT STDMETHODCALLTYPE DxgiFactory::CreateSwapChainBase(
+          IUnknown*             pDevice,
+          HWND                  hWnd,
+    const DXGI_SWAP_CHAIN_DESC1* pDesc,
+    const DXGI_SWAP_CHAIN_FULLSCREEN_DESC* pFullscreenDesc,
+          IDXGIOutput*          pRestrictToOutput,
+          IDXGISwapChain1**     ppSwapChain) {
+    // Make sure the back buffer size is not zero
+    DXGI_SWAP_CHAIN_DESC1 desc = *pDesc;
+
+    wsi::getWindowSize(hWnd,
+      desc.Width  ? nullptr : &desc.Width,
+      desc.Height ? nullptr : &desc.Height);
+
+    // If necessary, set up a default set of
+    // fullscreen parameters for the swap chain
+    DXGI_SWAP_CHAIN_FULLSCREEN_DESC fsDesc;
+
+    if (pFullscreenDesc) {
+      fsDesc = *pFullscreenDesc;
+    } else {
+      fsDesc.RefreshRate      = { 0, 0 };
+      fsDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
+      fsDesc.Scaling          = DXGI_MODE_SCALING_UNSPECIFIED;
+      fsDesc.Windowed         = TRUE;
+    }
+
+    // Probe various modes to create the swap chain object
+    Com<IDXGISwapChain4> frontendSwapChain;
+
+    Com<IDXGIVkSwapChainFactory> dxvkFactory;
+
+    if (SUCCEEDED(pDevice->QueryInterface(IID_PPV_ARGS(&dxvkFactory)))) {
+      Com<IDXGIVkSurfaceFactory> surfaceFactory = new DxgiSurfaceFactory(
+        m_instance->vki()->getLoaderProc(), hWnd);
+
+      Com<IDXGIVkSwapChain> presenter;
+      HRESULT hr = dxvkFactory->CreateSwapChain(surfaceFactory.ptr(), &desc, &presenter);
+
+      if (FAILED(hr)) {
+        Logger::err(str::format("DXGI: CreateSwapChainForHwnd: Failed to create swap chain, hr ", hr));
+        return hr;
+      }
+
+      frontendSwapChain = new DxgiSwapChain(this, presenter.ptr(), hWnd, &desc, &fsDesc, pDevice);
+    } else {
+      Logger::err("DXGI: CreateSwapChainForHwnd: Unsupported device type");
+      return DXGI_ERROR_UNSUPPORTED;
+    }
+
+    // Wrap object in swap chain dispatcher
+    *ppSwapChain = new DxgiSwapChainDispatcher(frontendSwapChain.ref(), pDevice);
+    return S_OK;
+  }
+
+
   DXVK_VK_GLOBAL_HDR_STATE DxgiFactory::GlobalHDRState() {
     std::unique_lock lock(s_globalHDRStateMutex);
     return s_globalHDRState;
diff --git a/src/dxgi/dxgi_factory.h b/src/dxgi/dxgi_factory.h
index 280b0a84a..623e133d7 100644
--- a/src/dxgi/dxgi_factory.h
+++ b/src/dxgi/dxgi_factory.h
@@ -201,7 +201,7 @@ namespace dxvk {
     BOOL             m_monitorFallback;
       
 
-    HRESULT STDMETHODCALLTYPE CreateSwapChainForHwndBase(
+    HRESULT STDMETHODCALLTYPE CreateSwapChainBase(
             IUnknown*             pDevice,
             HWND                  hWnd,
       const DXGI_SWAP_CHAIN_DESC1* pDesc,
diff --git a/src/dxgi/dxgi_surface.cpp b/src/dxgi/dxgi_surface.cpp
index b2cc03935..0c11b0c28 100644
--- a/src/dxgi/dxgi_surface.cpp
+++ b/src/dxgi/dxgi_surface.cpp
@@ -5,13 +5,15 @@
 namespace dxvk {
 
   DxgiSurfaceFactory::DxgiSurfaceFactory(PFN_vkGetInstanceProcAddr vulkanLoaderProc, HWND hWnd)
-  : m_vkGetInstanceProcAddr(vulkanLoaderProc), m_window(hWnd) {
-
+  : m_vkGetInstanceProcAddr(vulkanLoaderProc), m_window(hWnd), m_ownsWindow(!hWnd) {
+    if (!m_window)
+      m_window = CreateDummyWindow();
   }
 
 
   DxgiSurfaceFactory::~DxgiSurfaceFactory() {
-
+    if (m_ownsWindow)
+      DestroyDummyWindow();
   }
 
 
@@ -44,5 +46,47 @@ namespace dxvk {
           VkSurfaceKHR*             pSurface) {
     return wsi::createSurface(m_window, m_vkGetInstanceProcAddr, Instance, pSurface);
   }
-  
-}
\ No newline at end of file
+
+
+  HWND DxgiSurfaceFactory::CreateDummyWindow() {
+#ifdef _WIN32
+    static std::atomic<bool> s_wndClassRegistered = { false };
+
+    HINSTANCE hInstance = ::GetModuleHandle(nullptr);
+
+    if (!s_wndClassRegistered.load(std::memory_order_acquire)) {
+      WNDCLASSEXW wndClass = { };
+      wndClass.cbSize = sizeof(wndClass);
+      wndClass.style = CS_HREDRAW | CS_VREDRAW;
+      wndClass.lpfnWndProc = &::DefWindowProcW;
+      wndClass.hInstance = hInstance;
+      wndClass.lpszClassName = L"DXVKDUMMYWNDCLASS";
+
+      ATOM atom = ::RegisterClassExW(&wndClass);
+
+      if (!atom)
+        Logger::warn("DxgiSurfaceFactory: Failed to register dummy window class");
+
+      s_wndClassRegistered.store(!!atom, std::memory_order_release);
+    }
+
+    HWND hWnd = ::CreateWindowW(L"DXVKDUMMYWNDCLASS", L"DXVKDUMMYWINDOW",
+      WS_OVERLAPPEDWINDOW, 0, 0, 320, 240, nullptr, nullptr, hInstance, nullptr);
+
+    if (!hWnd)
+      Logger::err("DxgiSurfaceFactory: Failed to create dummy window");
+
+    return hWnd;
+#else
+    return nullptr;
+#endif
+  }
+
+
+  void DxgiSurfaceFactory::DestroyDummyWindow() {
+#ifdef _WIN32
+    DestroyWindow(m_window);
+#endif
+  }
+
+}
diff --git a/src/dxgi/dxgi_surface.h b/src/dxgi/dxgi_surface.h
index 25a845fdd..4a5c36f6d 100644
--- a/src/dxgi/dxgi_surface.h
+++ b/src/dxgi/dxgi_surface.h
@@ -35,9 +35,14 @@ namespace dxvk {
 
   private:
 
-    PFN_vkGetInstanceProcAddr m_vkGetInstanceProcAddr;
-    HWND                      m_window;
+    PFN_vkGetInstanceProcAddr m_vkGetInstanceProcAddr = nullptr;
+    HWND                      m_window = nullptr;
+    bool                      m_ownsWindow = false;
+
+    HWND CreateDummyWindow();
+
+    void DestroyDummyWindow();
 
   };
   
-}
\ No newline at end of file
+}