From 96c3056cc9cfb0b5125d04cea1b81f78cc926712 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Mon, 17 Aug 2026 04:42:30 +1000 Subject: [PATCH] [idd] capture: add direct full-copy workaround --- doc/idd_configuration.rst | 8 ++++++ doc/words.txt | 1 + idd/LGIdd/capture/CHardwareFrameProcessor.cpp | 25 +++++++++++++----- idd/LGIdd/capture/CHardwareFrameProcessor.h | 1 + idd/LGIdd/config/CSettings.h | 5 ++++ idd/LGIddHelper/CConfigWindow.cpp | 26 ++++++++++++++++++- idd/LGIddHelper/CConfigWindow.h | 2 ++ idd/LGIddHelper/CRegistrySettings.cpp | 25 ++++++++++++++++++ idd/LGIddHelper/CRegistrySettings.h | 3 +++ 9 files changed, 88 insertions(+), 8 deletions(-) diff --git a/doc/idd_configuration.rst b/doc/idd_configuration.rst index c188bf79..ec78fa9d 100644 --- a/doc/idd_configuration.rst +++ b/doc/idd_configuration.rst @@ -63,6 +63,14 @@ Disable no GPU warning processing. It does not enable GPU acceleration or change the active adapter. +Force full direct frame copies + Works around GPU drivers that corrupt partial D3D12 copies into the shared + transport heap. When enabled, the GPU copies every direct frame in full; + readback/indirect transfers continue using partial damage copies. This can + substantially increase GPU and shared-memory bandwidth, so leave it + disabled unless you encounter corruption. Changing it requests a driver + reload. + Unlike mode-list edits, preference checkboxes are saved when clicked. Software processing diff --git a/doc/words.txt b/doc/words.txt index 6c1089ac..c9a472f7 100644 --- a/doc/words.txt +++ b/doc/words.txt @@ -82,6 +82,7 @@ quadraphonic Quadro queueing radeon +readback realtime renderer repo diff --git a/idd/LGIdd/capture/CHardwareFrameProcessor.cpp b/idd/LGIdd/capture/CHardwareFrameProcessor.cpp index 9ea16f49..aeda4d26 100644 --- a/idd/LGIdd/capture/CHardwareFrameProcessor.cpp +++ b/idd/LGIdd/capture/CHardwareFrameProcessor.cpp @@ -19,6 +19,8 @@ */ #include "capture/CHardwareFrameProcessor.h" + +#include "config/CSettings.h" #include "capture/CFrameProcessorUtil.h" #include "transport/IFrameTransport.h" #include "CSRWLock.h" @@ -76,11 +78,15 @@ CHardwareFrameProcessor::CHardwareFrameProcessor( CSRWLock * pipelineLock, HANDLE terminateEvent, bool useCadence) : CFrameProcessor(transport, std::move(dx12), postProcessors, pipelineLock, terminateEvent), - m_useCadence(useCadence) + m_useCadence(useCadence), + m_forceFullDirectCopy(CSettings::ShouldForceFullDirectCopy()) { m_candidateAvailableEvent.Attach( CreateEvent(nullptr, FALSE, FALSE, nullptr)); m_copySubmitEvent.Attach(CreateEvent(nullptr, TRUE, TRUE, nullptr)); + + if (m_forceFullDirectCopy) + DEBUG_INFO("Forcing full copies to direct transport memory"); } bool CHardwareFrameProcessor::IsValid() const @@ -668,12 +674,17 @@ bool CHardwareFrameProcessor::Publish( RECT copyDirtyRects[LG_MAX_DIRTY_RECTS * 2] = {}; unsigned nbCopyDirtyRects = 0; - const bool fullCopy = CFrameProcessorUtil::BuildCopyDamage( - postProcessor, prepared.targets[i].fullCopy, - previousDirtyRects, nbPreviousDirtyRects, - candidate.dirtyRects, candidate.nbDirtyRects, - candidate.dstFormat.width, candidate.dstFormat.height, - copyDirtyRects, &nbCopyDirtyRects); + // Some GPUs corrupt fragmented writes to the external transport heap. + // An unmapped target is a direct D3D12 transport resource; readback + // targets remain mapped and continue using normal damage copies. + const bool fullCopy = + (m_forceFullDirectCopy && !fbRes->GetMap()) || + CFrameProcessorUtil::BuildCopyDamage( + postProcessor, prepared.targets[i].fullCopy, + previousDirtyRects, nbPreviousDirtyRects, + candidate.dirtyRects, candidate.nbDirtyRects, + candidate.dstFormat.width, candidate.dstFormat.height, + copyDirtyRects, &nbCopyDirtyRects); fbRes->SetTiming( candidate.captureTime, candidate.postProcessStart, publishStart); fbRes->SetCandidateIndex(candidateIndex); diff --git a/idd/LGIdd/capture/CHardwareFrameProcessor.h b/idd/LGIdd/capture/CHardwareFrameProcessor.h index c9ac6f32..11ba296d 100644 --- a/idd/LGIdd/capture/CHardwareFrameProcessor.h +++ b/idd/LGIdd/capture/CHardwareFrameProcessor.h @@ -73,6 +73,7 @@ private: CSRWLock m_copySubmitLock; bool m_publishPending = false; bool m_useCadence = true; + bool m_forceFullDirectCopy = false; Wrappers::Event m_candidateAvailableEvent; Wrappers::Event m_copySubmitEvent; diff --git a/idd/LGIdd/config/CSettings.h b/idd/LGIdd/config/CSettings.h index 51c40209..b4b967e6 100644 --- a/idd/LGIdd/config/CSettings.h +++ b/idd/LGIdd/config/CSettings.h @@ -96,6 +96,11 @@ class CSettings return ReadBoolValue(L"LogStatistics"); } + static bool ShouldForceFullDirectCopy() + { + return ReadBoolValue(L"ForceFullDirectCopy"); + } + private: static const wchar_t * RegistryKey() { diff --git a/idd/LGIddHelper/CConfigWindow.cpp b/idd/LGIddHelper/CConfigWindow.cpp index d9a59463..c91ad183 100644 --- a/idd/LGIddHelper/CConfigWindow.cpp +++ b/idd/LGIddHelper/CConfigWindow.cpp @@ -54,6 +54,7 @@ CConfigWindow::CConfigWindow() : m_scale(1) m_defaultRefresh = m_settings.getDefaultRefresh(); m_noGPU = m_settings.getNoGPU(); m_exclusive = m_settings.getExclusiveMonitor(); + m_forceFullDirectCopy = m_settings.getForceFullDirectCopy(); } if (!CreateWindowEx(0, MAKEINTATOM(s_atom), L"Looking Glass IDD Configuration", @@ -94,6 +95,7 @@ void CConfigWindow::updateFont() *m_modeWidth, *m_modeHeight, *m_modeRefresh, *m_modeUpdate, *m_modeDelete, *m_modeReset, *m_defRefreshLabel, *m_defRefresh, *m_defRefreshHz, *m_modeSave, *m_modeRevert, *m_prefGroup, *m_prefNoGPU, *m_prefExclusive, + *m_prefForceFullDirectCopy, })) SendMessage(child, WM_SETFONT, (WPARAM)m_font.Get(), 1); } @@ -187,6 +189,8 @@ LRESULT CConfigWindow::onCreate() m_prefGroup.reset(new CGroupBox(L"Preferences", 0, m_hwnd)); m_prefNoGPU.reset(new CCheckbox(L"Disable no GPU warning", 0, m_hwnd)); m_prefExclusive.reset(new CCheckbox(L"Make LG the only monitor", 0, m_hwnd)); + m_prefForceFullDirectCopy.reset(new CCheckbox( + L"Force full direct frame copies", 0, m_hwnd)); if (m_noGPU) m_prefNoGPU->setChecked(*m_noGPU); @@ -194,6 +198,9 @@ LRESULT CConfigWindow::onCreate() if (m_exclusive) m_prefExclusive->setChecked(*m_exclusive); + if (m_forceFullDirectCopy) + m_prefForceFullDirectCopy->setChecked(*m_forceFullDirectCopy); + LONG width, height; getMinimumSize(width, height); SetWindowPos(m_hwnd, NULL, 0, 0, width, height, SWP_NOMOVE | SWP_NOZORDER); @@ -233,9 +240,10 @@ LRESULT CConfigWindow::onResize(DWORD width, DWORD height) pos.pinBottomLeft(*m_modeSave, 20, 20, 132, 24); pos.pinBottomLeft(*m_modeRevert, 154, 20, 50, 24); - pos.pinTopLeft(*m_prefGroup, 224, 40, 200, 72); + pos.pinTopLeft(*m_prefGroup, 224, 40, 200, 92); pos.pinTopLeft(*m_prefNoGPU, 236, 64, 176, 20); pos.pinTopLeft(*m_prefExclusive, 236, 84, 176, 20); + pos.pinTopLeft(*m_prefForceFullDirectCopy, 236, 104, 188, 20); return 0; } @@ -344,6 +352,22 @@ LRESULT CConfigWindow::onCommand(WORD id, WORD code, HWND hwnd) m_settings.setExclusiveMonitor(*m_exclusive); m_prefExclusive->setChecked(*m_exclusive); } + else if (m_prefForceFullDirectCopy && + hwnd == *m_prefForceFullDirectCopy && code == BN_CLICKED && + m_forceFullDirectCopy) + { + const bool value = !*m_forceFullDirectCopy; + const LSTATUS result = m_settings.setForceFullDirectCopy(value); + if (result != ERROR_SUCCESS) + { + DEBUG_ERROR_HR(result, "Failed to save ForceFullDirectCopy"); + return 0; + } + + *m_forceFullDirectCopy = value; + m_prefForceFullDirectCopy->setChecked(value); + sendSettingChange(); + } else if (m_modeSave && hwnd == *m_modeSave && code == BN_CLICKED) { bool updated = false; diff --git a/idd/LGIddHelper/CConfigWindow.h b/idd/LGIddHelper/CConfigWindow.h index 9d93627f..87d7cd57 100644 --- a/idd/LGIddHelper/CConfigWindow.h +++ b/idd/LGIddHelper/CConfigWindow.h @@ -64,6 +64,7 @@ class CConfigWindow : public CWindow std::unique_ptr m_prefGroup; std::unique_ptr m_prefNoGPU; std::unique_ptr m_prefExclusive; + std::unique_ptr m_prefForceFullDirectCopy; std::function m_onDestroy; std::function m_onSettingChange; @@ -75,6 +76,7 @@ class CConfigWindow : public CWindow std::optional m_defaultRefresh; std::optional m_noGPU; std::optional m_exclusive; + std::optional m_forceFullDirectCopy; void getMinimumSize(LONG &width, LONG &height); void updateFont(); diff --git a/idd/LGIddHelper/CRegistrySettings.cpp b/idd/LGIddHelper/CRegistrySettings.cpp index 3cc93ab6..016d2752 100644 --- a/idd/LGIddHelper/CRegistrySettings.cpp +++ b/idd/LGIddHelper/CRegistrySettings.cpp @@ -265,3 +265,28 @@ LSTATUS CRegistrySettings::setExclusiveMonitor(bool exclusive) DWORD dwValue = exclusive; return RegSetValueEx(hKey, L"ExclusiveMonitor", 0, REG_DWORD, (LPBYTE)&dwValue, sizeof(DWORD)); } + +std::optional CRegistrySettings::getForceFullDirectCopy() +{ + DWORD result, cbData = sizeof result; + + LSTATUS status = RegGetValue(hKey, nullptr, L"ForceFullDirectCopy", + RRF_RT_REG_DWORD, nullptr, &result, &cbData); + switch (status) + { + case ERROR_SUCCESS: + return !!result; + case ERROR_FILE_NOT_FOUND: + return false; + default: + DEBUG_ERROR_HR(status, "RegGetValue(ForceFullDirectCopy)"); + return {}; + } +} + +LSTATUS CRegistrySettings::setForceFullDirectCopy(bool forceFullCopy) +{ + DWORD dwValue = forceFullCopy; + return RegSetValueEx(hKey, L"ForceFullDirectCopy", 0, REG_DWORD, + (LPBYTE)&dwValue, sizeof(DWORD)); +} diff --git a/idd/LGIddHelper/CRegistrySettings.h b/idd/LGIddHelper/CRegistrySettings.h index 2b732c3a..e4d44f22 100644 --- a/idd/LGIddHelper/CRegistrySettings.h +++ b/idd/LGIddHelper/CRegistrySettings.h @@ -56,4 +56,7 @@ public: std::optional getExclusiveMonitor(); LSTATUS setExclusiveMonitor(bool exclusive); + + std::optional getForceFullDirectCopy(); + LSTATUS setForceFullDirectCopy(bool forceFullCopy); };