From 7afb9b93ebb7089ad30e59a694b958ab2b4f145b Mon Sep 17 00:00:00 2001
From: Geoffrey McRae <geoff@hostfission.com>
Date: Sat, 29 Mar 2025 22:37:33 +0000
Subject: [PATCH] [idd] driver: improve display mode support and resolution
 switch

---
 idd/LGCommon/PipeMsg.h                |  10 +-
 idd/LGIdd/CIndirectDeviceContext.cpp  | 170 +++++++++++++++++++++-----
 idd/LGIdd/CIndirectDeviceContext.h    |   9 +-
 idd/LGIdd/CIndirectMonitorContext.cpp |   3 +-
 idd/LGIdd/CPipeServer.cpp             |  13 ++
 idd/LGIdd/CPipeServer.h               |   1 +
 idd/LGIdd/Device.cpp                  |   3 +-
 idd/LGIdd/Device.h                    |  27 ----
 idd/LGIddHelper/CPipeClient.cpp       |  17 +++
 idd/LGIddHelper/CPipeClient.h         |   1 +
 10 files changed, 194 insertions(+), 60 deletions(-)

diff --git a/idd/LGCommon/PipeMsg.h b/idd/LGCommon/PipeMsg.h
index 043aa2ad..f650fe93 100644
--- a/idd/LGCommon/PipeMsg.h
+++ b/idd/LGCommon/PipeMsg.h
@@ -28,7 +28,8 @@ struct LGPipeMsg
   unsigned size;
   enum
   {
-    SETCURSORPOS
+    SETCURSORPOS,
+    SETDISPLAYMODE
   }
   type;
   union
@@ -39,5 +40,12 @@ struct LGPipeMsg
       uint32_t y;
     }
     curorPos;
+
+    struct
+    {
+      uint32_t width;
+      uint32_t height;
+    }
+    displayMode;
   };
 };
\ No newline at end of file
diff --git a/idd/LGIdd/CIndirectDeviceContext.cpp b/idd/LGIdd/CIndirectDeviceContext.cpp
index 90b574cf..5a8a4e32 100644
--- a/idd/LGIdd/CIndirectDeviceContext.cpp
+++ b/idd/LGIdd/CIndirectDeviceContext.cpp
@@ -42,11 +42,58 @@ static const struct LGMPQueueConfig POINTER_QUEUE_CONFIG =
   1000                //subTimeout
 };
 
+const DWORD DefaultDisplayModes[][3] =
+{
+  {7680, 4800, 120}, {7680, 4320, 120}, {6016, 3384, 120}, {5760, 3600, 120},
+  {5760, 3240, 120}, {5120, 2800, 120}, {4096, 2560, 120}, {4096, 2304, 120},
+  {3840, 2400, 120}, {3840, 2160, 120}, {3200, 2400, 120}, {3200, 1800, 120},
+  {3008, 1692, 120}, {2880, 1800, 120}, {2880, 1620, 120}, {2560, 1600, 120},
+  {2560, 1440, 120}, {1920, 1440, 120}, {1920, 1200, 120}, {1920, 1080, 120},
+  {1600, 1200, 120}, {1600, 1024, 120}, {1600, 1050, 120}, {1600, 900 , 120},
+  {1440, 900 , 120}, {1400, 1050, 120}, {1366, 768 , 120}, {1360, 768 , 120},
+  {1280, 1024, 120}, {1280, 960 , 120}, {1280, 800 , 120}, {1280, 768 , 120},
+  {1280, 720 , 120}, {1280, 600 , 120}, {1152, 864 , 120}, {1024, 768 , 120},
+  {800 , 600 , 120}, {640 , 480 , 120}
+};
+
+static const BYTE EDID[] =
+{
+  0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x30,0xE8,0x34,0x12,0xC9,0x07,0xCC,0x00,
+  0x01,0x21,0x01,0x04,0xA5,0x3C,0x22,0x78,0xFB,0x6C,0xE5,0xA5,0x55,0x50,0xA0,0x23,
+  0x0B,0x50,0x54,0x00,0x02,0x00,0xD1,0xC0,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x58,0xE3,0x00,0xA0,0xA0,0xA0,0x29,0x50,0x30,0x20,
+  0x35,0x00,0x55,0x50,0x21,0x00,0x00,0x1A,0x00,0x00,0x00,0xFF,0x00,0x4C,0x6F,0x6F,
+  0x6B,0x69,0x6E,0x67,0x47,0x6C,0x61,0x73,0x73,0x0A,0x00,0x00,0x00,0xFC,0x00,0x4C,
+  0x6F,0x6F,0x6B,0x69,0x6E,0x67,0x20,0x47,0x6C,0x61,0x73,0x73,0x00,0x00,0x00,0xFD,
+  0x00,0x28,0x9B,0xFA,0xFA,0x40,0x01,0x0A,0x20,0x20,0x20,0x20,0x20,0x20,0x00,0x4A
+};
+
+const DWORD DefaultPreferredDisplayMode = 19;
+
+void CIndirectDeviceContext::PopulateDefaultModes(bool setDefaultMode)
+{
+  m_displayModes.reserve(m_displayModes.size() +
+    ARRAYSIZE(DefaultDisplayModes));
+
+  for (int i = 0; i < ARRAYSIZE(DefaultDisplayModes); ++i)
+  {
+    DisplayMode m;
+    m.width     = DefaultDisplayModes[i][0];
+    m.height    = DefaultDisplayModes[i][1];
+    m.refresh   = DefaultDisplayModes[i][2];
+    m.preferred = setDefaultMode && (i == DefaultPreferredDisplayMode);
+    m_displayModes.push_back(m);
+  }
+}
+
 void CIndirectDeviceContext::InitAdapter()
 {
   if (!m_ivshmem.Init() || !m_ivshmem.Open())
     return;
 
+  m_displayModes.clear();
+  PopulateDefaultModes(true);
+
   IDDCX_ADAPTER_CAPS caps = {};
   caps.Size = sizeof(caps);
 
@@ -114,30 +161,10 @@ void CIndirectDeviceContext::InitAdapter()
   }
   factory->Release();
 
-  // setup some default display modes
-  DisplayMode m;
-  m.refresh = 120;
-
-  m.width = 800 ; m.height = 600 ; m.preferred = false; m_displayModes.push_back(m);
-  m.width = 1024; m.height = 768 ; m.preferred = false; m_displayModes.push_back(m);
-  m.width = 1920; m.height = 1200; m.preferred = true ; m_displayModes.push_back(m);
-
   auto * wrapper = WdfObjectGet_CIndirectDeviceContextWrapper(m_adapter);
   wrapper->context = this;  
 }
 
-static const BYTE EDID[] =
-{
-  0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x30,0xE8,0x34,0x12,0xC9,0x07,0xCC,0x00,
-  0x01,0x21,0x01,0x04,0xA5,0x3C,0x22,0x78,0xFB,0x6C,0xE5,0xA5,0x55,0x50,0xA0,0x23,
-  0x0B,0x50,0x54,0x00,0x02,0x00,0xD1,0xC0,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
-  0x01,0x01,0x01,0x01,0x01,0x01,0x58,0xE3,0x00,0xA0,0xA0,0xA0,0x29,0x50,0x30,0x20,
-  0x35,0x00,0x55,0x50,0x21,0x00,0x00,0x1A,0x00,0x00,0x00,0xFF,0x00,0x4C,0x6F,0x6F,
-  0x6B,0x69,0x6E,0x67,0x47,0x6C,0x61,0x73,0x73,0x0A,0x00,0x00,0x00,0xFC,0x00,0x4C,
-  0x6F,0x6F,0x6B,0x69,0x6E,0x67,0x20,0x47,0x6C,0x61,0x73,0x73,0x00,0x00,0x00,0xFD,
-  0x00,0x28,0x9B,0xFA,0xFA,0x40,0x01,0x0A,0x20,0x20,0x20,0x20,0x20,0x20,0x00,0x4A
-};
-
 void CIndirectDeviceContext::FinishInit(UINT connectorIndex)
 {
   WDF_OBJECT_ATTRIBUTES attr;
@@ -210,7 +237,16 @@ void CIndirectDeviceContext::ReplugMonitor()
   }
 }
 
-void CIndirectDeviceContext::UnassignSwapChain()
+void CIndirectDeviceContext::OnAssignSwapChain()
+{
+  if (m_setCustomMode)
+  {
+    m_setCustomMode = false;
+    g_pipe.SetDisplayMode(m_customMode.width, m_customMode.height);
+  }
+}
+
+void CIndirectDeviceContext::OnUnassignedSwapChain()
 {
   if (m_replugMonitor)
   {
@@ -300,6 +336,89 @@ NTSTATUS CIndirectDeviceContext::MonitorQueryTargetModes(
   return STATUS_SUCCESS;
 }
 
+void CIndirectDeviceContext::SetResolution(int width, int height)
+{
+  m_displayModes.clear();
+  m_customMode.width     = width;
+  m_customMode.height    = height;
+  m_customMode.refresh   = 120;
+  m_customMode.preferred = true;
+  m_displayModes.push_back(m_customMode);
+  PopulateDefaultModes(false);
+
+  m_setCustomMode = true;
+
+#if 1
+  ReplugMonitor();
+#else
+
+  if (IDD_IS_FUNCTION_AVAILABLE(IddCxMonitorUpdateModes2))
+  {
+    IDDCX_TARGET_MODE2* modes = (IDDCX_TARGET_MODE2*)_malloca(
+      m_displayModes.size() * sizeof(IDDCX_TARGET_MODE2));
+
+    if (!modes)
+    {
+      DEBUG_ERROR("Failed to allocate memory for the mode list");
+      return;
+    }
+
+    ZeroMemory(modes, m_displayModes.size() * sizeof(IDDCX_TARGET_MODE2));
+
+    IDARG_IN_UPDATEMODES2 um = {};
+    um.Reason          = IDDCX_UPDATE_REASON_OTHER;
+    um.TargetModeCount = (UINT)m_displayModes.size();
+    um.pTargetModes    = modes;
+    auto* mode = modes;
+    for (auto it = m_displayModes.cbegin(); it != m_displayModes.cend(); ++it, ++mode)
+    {      
+      mode->Size                 = sizeof(IDDCX_TARGET_MODE2);
+      mode->RequiredBandwidth    = (UINT64)(it->width * it->height * it->refresh * 32);
+      mode->BitsPerComponent.Rgb = IDDCX_BITS_PER_COMPONENT_8;
+
+      FillSignalInfo(mode->TargetVideoSignalInfo.targetVideoSignalInfo, it->width, it->height, it->refresh, true);
+    }
+
+    NTSTATUS status = IddCxMonitorUpdateModes2(m_monitor, &um);
+    if (!NT_SUCCESS(status))
+      DEBUG_ERROR("IddCxMonitorUpdateModes2 Failed (0x%08x)", status);
+
+    _freea(modes);
+  }
+  else
+  {
+    IDDCX_TARGET_MODE* modes = (IDDCX_TARGET_MODE*)_malloca(
+      m_displayModes.size() * sizeof(IDDCX_TARGET_MODE));
+
+    if (!modes)
+    {
+      DEBUG_ERROR("Failed to allocate memory for the mode list");
+      return;
+    }
+
+    IDARG_IN_UPDATEMODES um = {};
+    um.Reason          = IDDCX_UPDATE_REASON_OTHER;
+    um.TargetModeCount = (UINT)m_displayModes.size();
+    um.pTargetModes    = modes;
+  
+    auto* mode = modes;
+    for (auto it = m_displayModes.cbegin(); it != m_displayModes.cend(); ++it, ++mode)
+    {
+      mode->Size              = sizeof(IDDCX_TARGET_MODE);
+      mode->RequiredBandwidth = (UINT64)(it->width * it->height * it->refresh * 32);
+
+      FillSignalInfo(mode->TargetVideoSignalInfo.targetVideoSignalInfo, it->width, it->height, it->refresh, true);
+    }
+
+    NTSTATUS status = IddCxMonitorUpdateModes(m_monitor, &um);
+    if (!NT_SUCCESS(status))
+      DEBUG_ERROR("IddCxMonitorUpdateModes Failed (0x%08x)", status);
+
+    _freea(modes);
+  }
+#endif
+}
+
 bool CIndirectDeviceContext::SetupLGMP(size_t alignSize)
 {
   // this may get called multiple times as we need to delay calling it until
@@ -522,14 +641,7 @@ void CIndirectDeviceContext::LGMPTimer()
       case KVMFR_MESSAGE_WINDOWSIZE:
       {
         KVMFRWindowSize* ws = (KVMFRWindowSize*)msg;
-        m_displayModes.clear();
-        DisplayMode m;
-        m.width     = ws->w;
-        m.height    = ws->h;
-        m.refresh   = 120;
-        m.preferred = true;
-        m_displayModes.push_back(m);
-        ReplugMonitor();
+        SetResolution(ws->w, ws->h);
       }
     }
 
diff --git a/idd/LGIdd/CIndirectDeviceContext.h b/idd/LGIdd/CIndirectDeviceContext.h
index de14bdf8..16638513 100644
--- a/idd/LGIdd/CIndirectDeviceContext.h
+++ b/idd/LGIdd/CIndirectDeviceContext.h
@@ -83,6 +83,8 @@ private:
     bool     preferred;
   };
   std::vector<DisplayMode> m_displayModes;
+  DisplayMode m_customMode    = {};
+  bool        m_setCustomMode = false;
 
 public:
   CIndirectDeviceContext(_In_ WDFDEVICE wdfDevice) :
@@ -92,10 +94,13 @@ public:
 
   bool SetupLGMP(size_t alignSize);
 
+  void PopulateDefaultModes(bool setDefaultMode);
   void InitAdapter();
   void FinishInit(UINT connectorIndex);
   void ReplugMonitor();
-  void UnassignSwapChain();
+
+  void OnAssignSwapChain();
+  void OnUnassignedSwapChain();
 
   NTSTATUS ParseMonitorDescription(
     const IDARG_IN_PARSEMONITORDESCRIPTION* inArgs, IDARG_OUT_PARSEMONITORDESCRIPTION* outArgs);
@@ -104,6 +109,8 @@ public:
   NTSTATUS MonitorQueryTargetModes(
     const IDARG_IN_QUERYTARGETMODES* inArgs, IDARG_OUT_QUERYTARGETMODES* outArgs);
 
+  void SetResolution(int width, int height);
+
   size_t GetAlignSize()    { return m_alignSize;    }
   size_t GetMaxFrameSize() { return m_maxFrameSize; }
 
diff --git a/idd/LGIdd/CIndirectMonitorContext.cpp b/idd/LGIdd/CIndirectMonitorContext.cpp
index b264ce73..68e48541 100644
--- a/idd/LGIdd/CIndirectMonitorContext.cpp
+++ b/idd/LGIdd/CIndirectMonitorContext.cpp
@@ -40,7 +40,8 @@ CIndirectMonitorContext::~CIndirectMonitorContext()
 void CIndirectMonitorContext::AssignSwapChain(IDDCX_SWAPCHAIN swapChain, LUID renderAdapter, HANDLE newFrameEvent)
 {
 reInit:
-  m_swapChain.reset();
+  UnassignSwapChain();
+
   m_dx11Device = std::make_shared<CD3D11Device>(renderAdapter);
   if (FAILED(m_dx11Device->Init()))
   {
diff --git a/idd/LGIdd/CPipeServer.cpp b/idd/LGIdd/CPipeServer.cpp
index 05d62022..4f5b072f 100644
--- a/idd/LGIdd/CPipeServer.cpp
+++ b/idd/LGIdd/CPipeServer.cpp
@@ -160,4 +160,17 @@ void CPipeServer::SetCursorPos(uint32_t x, uint32_t y)
   msg.curorPos.x = x;
   msg.curorPos.y = y;
   WriteMsg(msg);
+}
+
+void CPipeServer::SetDisplayMode(uint32_t width, uint32_t height)
+{
+  if (!m_connected)
+    return;
+
+  LGPipeMsg msg;
+  msg.size               = sizeof(msg);
+  msg.type               = LGPipeMsg::SETDISPLAYMODE;
+  msg.displayMode.width  = width;
+  msg.displayMode.height = height;
+  WriteMsg(msg);
 }
\ No newline at end of file
diff --git a/idd/LGIdd/CPipeServer.h b/idd/LGIdd/CPipeServer.h
index 67a18d1a..6b8b87b9 100644
--- a/idd/LGIdd/CPipeServer.h
+++ b/idd/LGIdd/CPipeServer.h
@@ -54,6 +54,7 @@ class CPipeServer
     void DeInit();
 
     void SetCursorPos(uint32_t x, uint32_t y);
+    void SetDisplayMode(uint32_t width, uint32_t height);
 };
 
 extern CPipeServer g_pipe;
\ No newline at end of file
diff --git a/idd/LGIdd/Device.cpp b/idd/LGIdd/Device.cpp
index c56b80b4..50808bfc 100644
--- a/idd/LGIdd/Device.cpp
+++ b/idd/LGIdd/Device.cpp
@@ -108,6 +108,7 @@ NTSTATUS LGIddMonitorAssignSwapChain(IDDCX_MONITOR monitor, const IDARG_IN_SETSW
   auto * wrapper = WdfObjectGet_CIndirectMonitorContextWrapper(monitor);
   wrapper->context->AssignSwapChain(
     inArgs->hSwapChain, inArgs->RenderAdapterLuid, inArgs->hNextSurfaceAvailable);
+  wrapper->context->GetDeviceContext()->OnAssignSwapChain();
   return STATUS_SUCCESS;
 }
 
@@ -115,7 +116,7 @@ NTSTATUS LGIddMonitorUnassignSwapChain(IDDCX_MONITOR monitor)
 {
   auto* wrapper = WdfObjectGet_CIndirectMonitorContextWrapper(monitor);
   wrapper->context->UnassignSwapChain();
-  wrapper->context->GetDeviceContext()->UnassignSwapChain();
+  wrapper->context->GetDeviceContext()->OnUnassignedSwapChain();
   return STATUS_SUCCESS;
 }
 
diff --git a/idd/LGIdd/Device.h b/idd/LGIdd/Device.h
index 04ee464e..8f15783c 100644
--- a/idd/LGIdd/Device.h
+++ b/idd/LGIdd/Device.h
@@ -24,33 +24,6 @@
 
 #include <Windows.h>
 
-#if 1
-const DWORD DisplayModes[][3] =
-{
-  {7680, 4800, 120}, {7680, 4320, 120}, {6016, 3384, 120}, {5760, 3600, 120},
-  {5760, 3240, 120}, {5120, 2800, 120}, {4096, 2560, 120}, {4096, 2304, 120},
-  {3840, 2400, 120}, {3840, 2160, 120}, {3200, 2400, 120}, {3200, 1800, 120},
-  {3008, 1692, 120}, {2880, 1800, 120}, {2880, 1620, 120}, {2560, 1600, 120},
-  {2560, 1440, 120}, {1920, 1440, 120}, {1920, 1200, 120}, {1920, 1080, 120},
-  {1600, 1200, 120}, {1600, 1024, 120}, {1600, 1050, 120}, {1600, 900 , 120},
-  {1440, 900 , 120}, {1400, 1050, 120}, {1366, 768 , 120}, {1360, 768 , 120},
-  {1280, 1024, 120}, {1280, 960 , 120}, {1280, 800 , 120}, {1280, 768 , 120},
-  {1280, 720 , 120}, {1280, 600 , 120}, {1152, 864 , 120}, {1024, 768 , 120},
-  {800 , 600 , 120}, {640 , 480 , 120}
-};
-
-const DWORD PreferredDisplayMode = 19;
-#else
-const DWORD DisplayModes[][3] =
-{
-  { 2560, 1440, 144 },
-  { 1920, 1080,  60 },
-  { 1024,  768,  60 },
-};
-
-const DWORD PreferredDisplayMode = 0;
-#endif
-
 typedef struct _DEVICE_CONTEXT
 {
   ULONG PrivateDeviceData;  // just a placeholder
diff --git a/idd/LGIddHelper/CPipeClient.cpp b/idd/LGIddHelper/CPipeClient.cpp
index 84a1427f..58d8ec53 100644
--- a/idd/LGIddHelper/CPipeClient.cpp
+++ b/idd/LGIddHelper/CPipeClient.cpp
@@ -211,6 +211,10 @@ void CPipeClient::Thread()
           HandleSetCursorPos(msg);
           break;
 
+        case LGPipeMsg::SETDISPLAYMODE:
+          HandleSetDisplayMode(msg);
+          break;
+
         default:
           DEBUG_ERROR("Unknown message type %d", msg.type);
           break;
@@ -228,4 +232,17 @@ void CPipeClient::HandleSetCursorPos(const LGPipeMsg& msg)
 {
   SetActiveDesktop();
   SetCursorPos(msg.curorPos.x, msg.curorPos.y);
+}
+
+void CPipeClient::HandleSetDisplayMode(const LGPipeMsg& msg)
+{
+  DEVMODE dm = {};
+  dm.dmSize       = sizeof(dm);
+  dm.dmPelsWidth  = msg.displayMode.width;
+  dm.dmPelsHeight = msg.displayMode.height;
+  dm.dmFields     = DM_PELSWIDTH | DM_PELSHEIGHT;
+
+  LONG result = ChangeDisplaySettingsEx(NULL, &dm, NULL, CDS_UPDATEREGISTRY, NULL);
+  if (result != DISP_CHANGE_SUCCESSFUL)
+    DEBUG_ERROR("ChangeDisplaySettingsEx Failed (0x%08x)", result);
 }
\ No newline at end of file
diff --git a/idd/LGIddHelper/CPipeClient.h b/idd/LGIddHelper/CPipeClient.h
index 8dbe9959..53140304 100644
--- a/idd/LGIddHelper/CPipeClient.h
+++ b/idd/LGIddHelper/CPipeClient.h
@@ -46,6 +46,7 @@ private:
   void SetActiveDesktop();
   
   void HandleSetCursorPos(const LGPipeMsg& msg);
+  void HandleSetDisplayMode(const LGPipeMsg& msg);
 
 public:
   ~CPipeClient() { DeInit(); }