Compare commits

...

5 Commits

Author SHA1 Message Date
Geoffrey McRae
b97a0b79f1 [idd] don't rebuild the monitor from inside the old monitor's context
Some checks are pending
build / client (Debug, map[cc:clang cxx:clang++], libdecor) (push) Waiting to run
build / client (Debug, map[cc:clang cxx:clang++], xdg-shell) (push) Waiting to run
build / client (Debug, map[cc:gcc cxx:g++], libdecor) (push) Waiting to run
build / client (Debug, map[cc:gcc cxx:g++], xdg-shell) (push) Waiting to run
build / client (Release, map[cc:clang cxx:clang++], libdecor) (push) Waiting to run
build / client (Release, map[cc:clang cxx:clang++], xdg-shell) (push) Waiting to run
build / client (Release, map[cc:gcc cxx:g++], libdecor) (push) Waiting to run
build / client (Release, map[cc:gcc cxx:g++], xdg-shell) (push) Waiting to run
build / module (push) Waiting to run
build / host-linux (push) Waiting to run
build / host-windows-cross (push) Waiting to run
build / host-windows-native (push) Waiting to run
build / idd (push) Waiting to run
build / obs (clang) (push) Waiting to run
build / obs (gcc) (push) Waiting to run
build / docs (push) Waiting to run
2026-07-16 16:13:24 +10:00
Geoffrey McRae
fbf564f2e1 [idd] prevent races from corrupting the mode list and edid 2026-07-16 15:26:21 +10:00
Geoffrey McRae
0140a3f6fb [client/idd] hdr: fix incorrect tone mapping knee bug
Some checks failed
build / client (Debug, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / module (push) Has been cancelled
build / host-linux (push) Has been cancelled
build / host-windows-cross (push) Has been cancelled
build / host-windows-native (push) Has been cancelled
build / idd (push) Has been cancelled
build / obs (clang) (push) Has been cancelled
build / obs (gcc) (push) Has been cancelled
build / docs (push) Has been cancelled
2026-07-16 10:48:10 +10:00
Geoffrey McRae
e502af7d9d Revert "[idd/client] correct HDR luminance when PQ conversion is in use"
This reverts commit 05d8bc5311.
2026-07-16 09:58:44 +10:00
Geoffrey McRae
05d8bc5311 [idd/client] correct HDR luminance when PQ conversion is in use 2026-07-16 09:34:02 +10:00
7 changed files with 317 additions and 82 deletions

View File

@@ -280,20 +280,20 @@ void waylandSetHDRImageDescription(const uint16_t displayPrimary[3][2],
hdrPQ ? WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ hdrPQ ? WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ
: WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR); : WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR);
// Set luminance range from metadata. // Set the primary colour volume luminances.
// min_lum is in 0.0001 cd/m² units (multiplied by 10000 as per protocol). // min_lum : 0.0001 cd/m² (source already scaled) -> pass through
// max_lum and reference_lum are in unscaled cd/m² units. // max_lum : cd/m² (used only for scRGB; ignored for PQ, where the
// compositor forces it to min_lum + 10000 cd/m²)
// reference_lum : cd/m² -> BT.2408 diffuse white (203 PQ / 80 scRGB)
if (wlWm.cmHasLuminances) if (wlWm.cmHasLuminances)
wp_image_description_creator_params_v1_set_luminances( wp_image_description_creator_params_v1_set_luminances(
wlWm.hdrImageCreator, wlWm.hdrImageCreator,
minDisplayLuminance > 0 ? minDisplayLuminance / 10000 : 50, minDisplayLuminance > 0 ? minDisplayLuminance : 50,
maxDisplayLuminance > 0 ? maxDisplayLuminance / 10000 : 1000, maxDisplayLuminance > 0 ? maxDisplayLuminance : 1000,
hdrPQ ? 203 : 80); hdrPQ ? 203 : 80);
// Set mastering display primaries from frame HDR metadata. // Advertise the content primaries (BT.2020 for PQ). These describe the real
// Always set when the compositor supports it, falling back to the // colour gamut of the signal and are safe to forward.
// primaries from the metadata (even if zero, in which case the
// compositor uses its own defaults).
if (wlWm.cmHasMasteringPrimaries) if (wlWm.cmHasMasteringPrimaries)
wp_image_description_creator_params_v1_set_mastering_display_primaries( wp_image_description_creator_params_v1_set_mastering_display_primaries(
wlWm.hdrImageCreator, wlWm.hdrImageCreator,
@@ -302,10 +302,16 @@ void waylandSetHDRImageDescription(const uint16_t displayPrimary[3][2],
displayPrimary[2][0], displayPrimary[2][1], displayPrimary[2][0], displayPrimary[2][1],
whitePoint[0], whitePoint[1]); whitePoint[0], whitePoint[1]);
if (maxCLL > 0) // NOTE: we deliberately do NOT forward the host's mastering luminance range,
wp_image_description_creator_params_v1_set_max_cll(wlWm.hdrImageCreator, maxCLL); // MaxCLL or MaxFALL. Those values describe the *virtual display* the IDD
if (maxFALL > 0) // advertises to Windows (~1000 cd/m² from our EDID), not the content. LG
wp_image_description_creator_params_v1_set_max_fall(wlWm.hdrImageCreator, maxFALL); // captures the full-range scRGB desktop composition and passes it through to
// PQ without tone mapping, so the signal spans the entire PQ range (scRGB
// 125.0 == 10000 cd/m²). Advertising the virtual display's peak as the
// content peak makes the compositor place its tone-mapping knee there and
// hard-clip everything brighter, crushing highlights. Leaving these unset
// lets the content be treated as standard full-range PQ so the compositor
// tone-maps it to the actual physical display.
wlWm.hdrImageDesc = wlWm.hdrImageDesc =
wp_image_description_creator_params_v1_create(wlWm.hdrImageCreator); wp_image_description_creator_params_v1_create(wlWm.hdrImageCreator);
@@ -332,8 +338,10 @@ void waylandSetHDRImageDescription(const uint16_t displayPrimary[3][2],
WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL); WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL);
wlWm.hdrActive = true; wlWm.hdrActive = true;
DEBUG_INFO("HDR image description set on surface (%s, %s, %u nits)", DEBUG_INFO("HDR image description set on surface (%s, %s, "
hdrPQ ? "PQ" : "scRGB", hdrPQ ? "BT.2020" : "sRGB", maxDisplayLuminance); "maxLum:%u cd/m² minLum:%u (0.0001 cd/m²) maxCLL:%u maxFALL:%u)",
hdrPQ ? "PQ" : "scRGB", hdrPQ ? "BT.2020" : "sRGB",
maxDisplayLuminance, minDisplayLuminance, maxCLL, maxFALL);
} }
bool waylandRequestHDR(const uint16_t displayPrimary[3][2], bool waylandRequestHDR(const uint16_t displayPrimary[3][2],

View File

@@ -167,7 +167,11 @@ typedef struct KVMFRFrame
// Display color primaries in 0.00002 units (SMPTE ST 2086 format) // Display color primaries in 0.00002 units (SMPTE ST 2086 format)
uint16_t hdrDisplayPrimary[3][2]; // Rx,Ry, Gx,Gy, Bx,By uint16_t hdrDisplayPrimary[3][2]; // Rx,Ry, Gx,Gy, Bx,By
uint16_t hdrWhitePoint[2]; // Wx, Wy uint16_t hdrWhitePoint[2]; // Wx, Wy
uint32_t hdrMaxDisplayLuminance; // Max mastering display luminance (0.0001 cd/m²) // Mastering display luminances follow SMPTE ST 2086 units: the maximum is
// in whole cd/m², the minimum in 0.0001 cd/m². (Note: the DXGI docs
// describe MaxMasteringLuminance as 0.0001 cd/m², but IddCx/ST 2086 provide
// it in whole cd/m².)
uint32_t hdrMaxDisplayLuminance; // Max mastering display luminance (cd/m²)
uint32_t hdrMinDisplayLuminance; // Min mastering display luminance (0.0001 cd/m²) uint32_t hdrMinDisplayLuminance; // Min mastering display luminance (0.0001 cd/m²)
uint32_t hdrMaxContentLightLevel; // MaxCLL (cd/m²) uint32_t hdrMaxContentLightLevel; // MaxCLL (cd/m²)
uint32_t hdrMaxFrameAverageLightLevel; // MaxFALL (cd/m²) uint32_t hdrMaxFrameAverageLightLevel; // MaxFALL (cd/m²)

View File

@@ -43,6 +43,16 @@ CIVSHMEM::~CIVSHMEM()
bool CIVSHMEM::Init() bool CIVSHMEM::Init()
{ {
// Init may be called more than once (the adapter init is retried at boot
// until IVSHMEM enumerates). Release any handle from a prior attempt so we
// do not leak it when re-enumerating.
if (m_handle != INVALID_HANDLE_VALUE)
{
Close();
CloseHandle(m_handle);
m_handle = INVALID_HANDLE_VALUE;
}
HDEVINFO devInfoSet; HDEVINFO devInfoSet;
SP_DEVINFO_DATA devInfoData; SP_DEVINFO_DATA devInfoData;
SP_DEVICE_INTERFACE_DATA devInterfaceData; SP_DEVICE_INTERFACE_DATA devInterfaceData;

View File

@@ -99,19 +99,89 @@ void CIndirectDeviceContext::PopulateDefaultModes()
{ {
g_settings.LoadModes(); g_settings.LoadModes();
m_displayModes.clear(); // Build the new mode list and EDID into locals first so we only hold the
m_displayModes.reserve(g_settings.GetDisplayModes().size()); // lock for the swap. IddCx readers may be iterating the live containers on
// another thread; a clear()/push_back() under them would reallocate the
// backing store and crash. std::move makes the publish a pointer swap.
CSettings::DisplayModes newModes;
newModes.reserve(g_settings.GetDisplayModes().size());
for (auto& dm : g_settings.GetDisplayModes()) for (auto& dm : g_settings.GetDisplayModes())
m_displayModes.push_back(dm); newModes.push_back(dm);
m_edid.Build(m_displayModes); CEdid newEdid;
newEdid.Build(newModes);
AcquireSRWLockExclusive(&m_modeLock);
m_displayModes = std::move(newModes);
m_edid = std::move(newEdid);
ReleaseSRWLockExclusive(&m_modeLock);
}
void CIndirectDeviceContext::ScheduleInitRetry()
{
// Create the retry timer once; if it already exists it is either running or
// will be (re)started below.
if (!m_initTimer)
{
WDF_TIMER_CONFIG config;
WDF_TIMER_CONFIG_INIT_PERIODIC(&config,
[](WDFTIMER timer) -> void
{
WDFOBJECT parent = WdfTimerGetParentObject(timer);
auto wrapper = WdfObjectGet_CIndirectDeviceContextWrapper(parent);
wrapper->context->InitAdapter();
},
500);
config.AutomaticSerialization = FALSE;
WDF_OBJECT_ATTRIBUTES attribs;
WDF_OBJECT_ATTRIBUTES_INIT(&attribs);
attribs.ParentObject = m_wdfDevice;
attribs.ExecutionLevel = WdfExecutionLevelDispatch;
NTSTATUS status = WdfTimerCreate(&config, &attribs, &m_initTimer);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR_HR(status, "Init retry timer creation failed");
m_initTimer = nullptr;
return;
}
}
WdfTimerStart(m_initTimer, WDF_REL_TIMEOUT_IN_MS(500));
}
void CIndirectDeviceContext::StopInitRetry()
{
if (m_initTimer)
WdfTimerStop(m_initTimer, FALSE);
} }
void CIndirectDeviceContext::InitAdapter() void CIndirectDeviceContext::InitAdapter()
{ {
if (!m_ivshmem.Init() || !m_ivshmem.Open()) // The adapter only needs to be created once. D0Entry and the retry timer can
// both land here, so guard against re-entrancy and repeated creation.
if (m_adapter)
return; return;
if (InterlockedCompareExchange(&m_initInProgress, 1, 0) != 0)
return;
// At boot the IVSHMEM PCI device may not have enumerated yet. Rather than
// silently abandoning the adapter (leaving the device loaded but with no
// monitor), retry from a timer until the shared memory becomes available.
if (!m_ivshmemOpened)
{
if (!m_ivshmem.Init() || !m_ivshmem.Open())
{
DEBUG_WARN("IVSHMEM not available yet, scheduling init retry");
ScheduleInitRetry();
InterlockedExchange(&m_initInProgress, 0);
return;
}
m_ivshmemOpened = true;
}
QueryIddCxCapabilities(); QueryIddCxCapabilities();
PopulateDefaultModes(); PopulateDefaultModes();
@@ -170,6 +240,7 @@ void CIndirectDeviceContext::InitAdapter()
if (!NT_SUCCESS(status)) if (!NT_SUCCESS(status))
{ {
DEBUG_ERROR_HR(status, "IddCxAdapterInitAsync Failed"); DEBUG_ERROR_HR(status, "IddCxAdapterInitAsync Failed");
InterlockedExchange(&m_initInProgress, 0);
return; return;
} }
@@ -199,6 +270,10 @@ void CIndirectDeviceContext::InitAdapter()
auto * wrapper = WdfObjectGet_CIndirectDeviceContextWrapper(m_adapter); auto * wrapper = WdfObjectGet_CIndirectDeviceContextWrapper(m_adapter);
wrapper->context = this; wrapper->context = this;
// Adapter is up; no need to keep retrying.
StopInitRetry();
InterlockedExchange(&m_initInProgress, 0);
} }
void CIndirectDeviceContext::FinishInit(UINT connectorIndex) void CIndirectDeviceContext::FinishInit(UINT connectorIndex)
@@ -206,6 +281,15 @@ void CIndirectDeviceContext::FinishInit(UINT connectorIndex)
WDF_OBJECT_ATTRIBUTES attr; WDF_OBJECT_ATTRIBUTES attr;
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attr, CIndirectMonitorContextWrapper); WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attr, CIndirectMonitorContextWrapper);
// Take a private copy of the EDID so a concurrent PopulateDefaultModes on
// the timer thread cannot reallocate the buffer out from under
// IddCxMonitorCreate. The copy lives for the duration of the synchronous
// create call below.
std::vector<BYTE> edid;
AcquireSRWLockShared(&m_modeLock);
edid.assign(m_edid.Data(), m_edid.Data() + m_edid.Size());
ReleaseSRWLockShared(&m_modeLock);
IDDCX_MONITOR_INFO info = {}; IDDCX_MONITOR_INFO info = {};
info.Size = sizeof(info); info.Size = sizeof(info);
info.MonitorType = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI; info.MonitorType = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI;
@@ -213,8 +297,8 @@ void CIndirectDeviceContext::FinishInit(UINT connectorIndex)
info.MonitorDescription.Size = sizeof(info.MonitorDescription); info.MonitorDescription.Size = sizeof(info.MonitorDescription);
info.MonitorDescription.Type = IDDCX_MONITOR_DESCRIPTION_TYPE_EDID; info.MonitorDescription.Type = IDDCX_MONITOR_DESCRIPTION_TYPE_EDID;
info.MonitorDescription.DataSize = m_edid.Size(); info.MonitorDescription.DataSize = (UINT)edid.size();
info.MonitorDescription.pData = const_cast<BYTE*>(m_edid.Data()); info.MonitorDescription.pData = edid.empty() ? nullptr : edid.data();
CoCreateGuid(&info.MonitorContainerId); CoCreateGuid(&info.MonitorContainerId);
@@ -230,7 +314,10 @@ void CIndirectDeviceContext::FinishInit(UINT connectorIndex)
return; return;
} }
AcquireSRWLockExclusive(&m_stateLock);
m_monitor = createOut.MonitorObject; m_monitor = createOut.MonitorObject;
ReleaseSRWLockExclusive(&m_stateLock);
auto * wrapper = WdfObjectGet_CIndirectMonitorContextWrapper(m_monitor); auto * wrapper = WdfObjectGet_CIndirectMonitorContextWrapper(m_monitor);
wrapper->context = new CIndirectMonitorContext(m_monitor, this); wrapper->context = new CIndirectMonitorContext(m_monitor, this);
@@ -245,21 +332,35 @@ void CIndirectDeviceContext::FinishInit(UINT connectorIndex)
void CIndirectDeviceContext::ReplugMonitor() void CIndirectDeviceContext::ReplugMonitor()
{ {
if (m_monitor == WDF_NO_HANDLE) // Sample and mutate the replug state atomically. The monitor handle is
// published by FinishInit on an IddCx thread while this can run from the
// timer thread, so a plain check-then-act would race.
AcquireSRWLockExclusive(&m_stateLock);
IDDCX_MONITOR monitor = m_monitor;
if (monitor == WDF_NO_HANDLE)
{ {
ReleaseSRWLockExclusive(&m_stateLock);
FinishInit(0); FinishInit(0);
return; return;
} }
if (m_replugMonitor) if (m_replugMonitor)
{
ReleaseSRWLockExclusive(&m_stateLock);
return; return;
}
m_replugMonitor = true;
ReleaseSRWLockExclusive(&m_stateLock);
DEBUG_TRACE("ReplugMonitor"); DEBUG_TRACE("ReplugMonitor");
m_replugMonitor = true; NTSTATUS status = IddCxMonitorDeparture(monitor);
NTSTATUS status = IddCxMonitorDeparture(m_monitor);
if (!NT_SUCCESS(status)) if (!NT_SUCCESS(status))
{ {
AcquireSRWLockExclusive(&m_stateLock);
m_replugMonitor = false; m_replugMonitor = false;
ReleaseSRWLockExclusive(&m_stateLock);
DEBUG_ERROR("IddCxMonitorDeparture Failed (0x%08x)", status); DEBUG_ERROR("IddCxMonitorDeparture Failed (0x%08x)", status);
return; return;
} }
@@ -269,11 +370,14 @@ void CIndirectDeviceContext::OnAssignSwapChain()
{ {
InterlockedExchange(&m_recoverModeUpdateSwapChain, 0); InterlockedExchange(&m_recoverModeUpdateSwapChain, 0);
if (m_doSetMode) AcquireSRWLockExclusive(&m_stateLock);
{ bool doSetMode = m_doSetMode;
m_doSetMode = false; CSettings::DisplayMode mode = m_setMode;
g_pipe.SetDisplayMode(m_setMode.width, m_setMode.height, m_setMode.refresh); m_doSetMode = false;
} ReleaseSRWLockExclusive(&m_stateLock);
if (doSetMode)
g_pipe.SetDisplayMode(mode.width, mode.height, mode.refresh);
} }
void CIndirectDeviceContext::OnUnassignedSwapChain() void CIndirectDeviceContext::OnUnassignedSwapChain()
@@ -281,11 +385,18 @@ void CIndirectDeviceContext::OnUnassignedSwapChain()
InterlockedExchange(&m_replugMonitorQueued, 0); InterlockedExchange(&m_replugMonitorQueued, 0);
InterlockedExchange(&m_recoverModeUpdateSwapChain, 0); InterlockedExchange(&m_recoverModeUpdateSwapChain, 0);
if (m_replugMonitor) AcquireSRWLockExclusive(&m_stateLock);
{ bool replug = m_replugMonitor;
m_replugMonitor = false; m_replugMonitor = false;
FinishInit(0); ReleaseSRWLockExclusive(&m_stateLock);
}
// Do NOT rebuild the monitor from inside this callback. Creating and arriving
// a new monitor here re-enters IddCx while the old monitor's swap-chain
// teardown is still unwinding on this thread, which leaves the new swap
// chain's surfaces lost/abandoned (DXGI_ERROR_ACCESS_LOST). Defer to the LGMP
// timer, matching how the departure is scheduled.
if (replug)
InterlockedExchange(&m_finishInitQueued, 1);
} }
void CIndirectDeviceContext::OnSwapChainLost() void CIndirectDeviceContext::OnSwapChainLost()
@@ -324,13 +435,18 @@ NTSTATUS CIndirectDeviceContext::ParseMonitorDescription(
const IDARG_IN_PARSEMONITORDESCRIPTION* inArgs, const IDARG_IN_PARSEMONITORDESCRIPTION* inArgs,
IDARG_OUT_PARSEMONITORDESCRIPTION* outArgs) IDARG_OUT_PARSEMONITORDESCRIPTION* outArgs)
{ {
outArgs->MonitorModeBufferOutputCount = (UINT)m_displayModes.size(); CSettings::DisplayModes modes;
AcquireSRWLockShared(&m_modeLock);
modes = m_displayModes;
ReleaseSRWLockShared(&m_modeLock);
outArgs->MonitorModeBufferOutputCount = (UINT)modes.size();
outArgs->PreferredMonitorModeIdx = 0; outArgs->PreferredMonitorModeIdx = 0;
if (inArgs->MonitorModeBufferInputCount < (UINT)m_displayModes.size()) if (inArgs->MonitorModeBufferInputCount < (UINT)modes.size())
return (inArgs->MonitorModeBufferInputCount > 0) ? STATUS_BUFFER_TOO_SMALL : STATUS_SUCCESS; return (inArgs->MonitorModeBufferInputCount > 0) ? STATUS_BUFFER_TOO_SMALL : STATUS_SUCCESS;
auto * mode = inArgs->pMonitorModes; auto * mode = inArgs->pMonitorModes;
for (auto it = m_displayModes.cbegin(); it != m_displayModes.cend(); ++it, ++mode) for (auto it = modes.cbegin(); it != modes.cend(); ++it, ++mode)
{ {
mode->Size = sizeof(IDDCX_MONITOR_MODE); mode->Size = sizeof(IDDCX_MONITOR_MODE);
mode->Origin = IDDCX_MONITOR_MODE_ORIGIN_MONITORDESCRIPTOR; mode->Origin = IDDCX_MONITOR_MODE_ORIGIN_MONITORDESCRIPTOR;
@@ -338,7 +454,7 @@ NTSTATUS CIndirectDeviceContext::ParseMonitorDescription(
if (it->preferred) if (it->preferred)
outArgs->PreferredMonitorModeIdx = outArgs->PreferredMonitorModeIdx =
(UINT)std::distance(m_displayModes.cbegin(), it); (UINT)std::distance(modes.cbegin(), it);
} }
return STATUS_SUCCESS; return STATUS_SUCCESS;
@@ -348,13 +464,18 @@ NTSTATUS CIndirectDeviceContext::MonitorGetDefaultModes(
const IDARG_IN_GETDEFAULTDESCRIPTIONMODES* inArgs, const IDARG_IN_GETDEFAULTDESCRIPTIONMODES* inArgs,
IDARG_OUT_GETDEFAULTDESCRIPTIONMODES* outArgs) IDARG_OUT_GETDEFAULTDESCRIPTIONMODES* outArgs)
{ {
outArgs->DefaultMonitorModeBufferOutputCount = (UINT)m_displayModes.size(); CSettings::DisplayModes modes;
AcquireSRWLockShared(&m_modeLock);
modes = m_displayModes;
ReleaseSRWLockShared(&m_modeLock);
outArgs->DefaultMonitorModeBufferOutputCount = (UINT)modes.size();
outArgs->PreferredMonitorModeIdx = 0; outArgs->PreferredMonitorModeIdx = 0;
if (inArgs->DefaultMonitorModeBufferInputCount < (UINT)m_displayModes.size()) if (inArgs->DefaultMonitorModeBufferInputCount < (UINT)modes.size())
return (inArgs->DefaultMonitorModeBufferInputCount > 0) ? STATUS_BUFFER_TOO_SMALL : STATUS_SUCCESS; return (inArgs->DefaultMonitorModeBufferInputCount > 0) ? STATUS_BUFFER_TOO_SMALL : STATUS_SUCCESS;
auto* mode = inArgs->pDefaultMonitorModes; auto* mode = inArgs->pDefaultMonitorModes;
for (auto it = m_displayModes.cbegin(); it != m_displayModes.cend(); ++it, ++mode) for (auto it = modes.cbegin(); it != modes.cend(); ++it, ++mode)
{ {
mode->Size = sizeof(IDDCX_MONITOR_MODE); mode->Size = sizeof(IDDCX_MONITOR_MODE);
mode->Origin = IDDCX_MONITOR_MODE_ORIGIN_DRIVER; mode->Origin = IDDCX_MONITOR_MODE_ORIGIN_DRIVER;
@@ -362,7 +483,7 @@ NTSTATUS CIndirectDeviceContext::MonitorGetDefaultModes(
if (it->preferred) if (it->preferred)
outArgs->PreferredMonitorModeIdx = outArgs->PreferredMonitorModeIdx =
(UINT)std::distance(m_displayModes.cbegin(), it); (UINT)std::distance(modes.cbegin(), it);
} }
return STATUS_SUCCESS; return STATUS_SUCCESS;
@@ -372,12 +493,17 @@ NTSTATUS CIndirectDeviceContext::MonitorQueryTargetModes(
const IDARG_IN_QUERYTARGETMODES* inArgs, const IDARG_IN_QUERYTARGETMODES* inArgs,
IDARG_OUT_QUERYTARGETMODES* outArgs) IDARG_OUT_QUERYTARGETMODES* outArgs)
{ {
outArgs->TargetModeBufferOutputCount = (UINT)m_displayModes.size(); CSettings::DisplayModes modes;
if (inArgs->TargetModeBufferInputCount < (UINT)m_displayModes.size()) AcquireSRWLockShared(&m_modeLock);
modes = m_displayModes;
ReleaseSRWLockShared(&m_modeLock);
outArgs->TargetModeBufferOutputCount = (UINT)modes.size();
if (inArgs->TargetModeBufferInputCount < (UINT)modes.size())
return (inArgs->TargetModeBufferInputCount > 0) ? STATUS_BUFFER_TOO_SMALL : STATUS_SUCCESS; return (inArgs->TargetModeBufferInputCount > 0) ? STATUS_BUFFER_TOO_SMALL : STATUS_SUCCESS;
auto* mode = inArgs->pTargetModes; auto* mode = inArgs->pTargetModes;
for (auto it = m_displayModes.cbegin(); it != m_displayModes.cend(); ++it, ++mode) for (auto it = modes.cbegin(); it != modes.cend(); ++it, ++mode)
{ {
mode->Size = sizeof(IDDCX_TARGET_MODE); mode->Size = sizeof(IDDCX_TARGET_MODE);
FillSignalInfo(mode->TargetVideoSignalInfo.targetVideoSignalInfo, it->width, it->height, it->refresh, false); FillSignalInfo(mode->TargetVideoSignalInfo.targetVideoSignalInfo, it->width, it->height, it->refresh, false);
@@ -392,13 +518,18 @@ NTSTATUS CIndirectDeviceContext::ParseMonitorDescription2(
const IDARG_IN_PARSEMONITORDESCRIPTION2* inArgs, const IDARG_IN_PARSEMONITORDESCRIPTION2* inArgs,
IDARG_OUT_PARSEMONITORDESCRIPTION* outArgs) IDARG_OUT_PARSEMONITORDESCRIPTION* outArgs)
{ {
outArgs->MonitorModeBufferOutputCount = (UINT)m_displayModes.size(); CSettings::DisplayModes modes;
AcquireSRWLockShared(&m_modeLock);
modes = m_displayModes;
ReleaseSRWLockShared(&m_modeLock);
outArgs->MonitorModeBufferOutputCount = (UINT)modes.size();
outArgs->PreferredMonitorModeIdx = 0; outArgs->PreferredMonitorModeIdx = 0;
if (inArgs->MonitorModeBufferInputCount < (UINT)m_displayModes.size()) if (inArgs->MonitorModeBufferInputCount < (UINT)modes.size())
return (inArgs->MonitorModeBufferInputCount > 0) ? STATUS_BUFFER_TOO_SMALL : STATUS_SUCCESS; return (inArgs->MonitorModeBufferInputCount > 0) ? STATUS_BUFFER_TOO_SMALL : STATUS_SUCCESS;
auto * mode = inArgs->pMonitorModes; auto * mode = inArgs->pMonitorModes;
for (auto it = m_displayModes.cbegin(); it != m_displayModes.cend(); ++it, ++mode) for (auto it = modes.cbegin(); it != modes.cend(); ++it, ++mode)
{ {
ZeroMemory(mode, sizeof(*mode)); ZeroMemory(mode, sizeof(*mode));
mode->Size = sizeof(IDDCX_MONITOR_MODE2); mode->Size = sizeof(IDDCX_MONITOR_MODE2);
@@ -408,7 +539,7 @@ NTSTATUS CIndirectDeviceContext::ParseMonitorDescription2(
if (it->preferred) if (it->preferred)
outArgs->PreferredMonitorModeIdx = outArgs->PreferredMonitorModeIdx =
(UINT)std::distance(m_displayModes.cbegin(), it); (UINT)std::distance(modes.cbegin(), it);
} }
return STATUS_SUCCESS; return STATUS_SUCCESS;
@@ -418,15 +549,20 @@ NTSTATUS CIndirectDeviceContext::MonitorQueryTargetModes2(
const IDARG_IN_QUERYTARGETMODES2* inArgs, const IDARG_IN_QUERYTARGETMODES2* inArgs,
IDARG_OUT_QUERYTARGETMODES* outArgs) IDARG_OUT_QUERYTARGETMODES* outArgs)
{ {
outArgs->TargetModeBufferOutputCount = (UINT)m_displayModes.size(); CSettings::DisplayModes modes;
if (inArgs->TargetModeBufferInputCount < (UINT)m_displayModes.size()) AcquireSRWLockShared(&m_modeLock);
modes = m_displayModes;
ReleaseSRWLockShared(&m_modeLock);
outArgs->TargetModeBufferOutputCount = (UINT)modes.size();
if (inArgs->TargetModeBufferInputCount < (UINT)modes.size())
return STATUS_SUCCESS; return STATUS_SUCCESS;
if (!inArgs->pTargetModes) if (!inArgs->pTargetModes)
return STATUS_INVALID_PARAMETER; return STATUS_INVALID_PARAMETER;
auto* mode = inArgs->pTargetModes; auto* mode = inArgs->pTargetModes;
for (auto it = m_displayModes.cbegin(); it != m_displayModes.cend(); ++it, ++mode) for (auto it = modes.cbegin(); it != modes.cend(); ++it, ++mode)
{ {
ZeroMemory(mode, sizeof(*mode)); ZeroMemory(mode, sizeof(*mode));
mode->Size = sizeof(IDDCX_TARGET_MODE2); mode->Size = sizeof(IDDCX_TARGET_MODE2);
@@ -440,24 +576,36 @@ NTSTATUS CIndirectDeviceContext::MonitorQueryTargetModes2(
bool CIndirectDeviceContext::UpdateMonitorModes() bool CIndirectDeviceContext::UpdateMonitorModes()
{ {
if (!m_monitor) AcquireSRWLockExclusive(&m_stateLock);
IDDCX_MONITOR monitor = m_monitor;
ReleaseSRWLockExclusive(&m_stateLock);
if (!monitor)
return false; return false;
// Snapshot the mode list so we do not hold m_modeLock across the IddCx call
// below - IddCxMonitorUpdateModes can synchronously re-enter our mode
// enumeration callbacks (which also take m_modeLock).
CSettings::DisplayModes displayModes;
AcquireSRWLockShared(&m_modeLock);
displayModes = m_displayModes;
ReleaseSRWLockShared(&m_modeLock);
#ifdef HAS_IDDCX_110 #ifdef HAS_IDDCX_110
if (CanUseIddCx110DDIs()) if (CanUseIddCx110DDIs())
{ {
IDDCX_TARGET_MODE2* modes = (IDDCX_TARGET_MODE2*)_malloca( IDDCX_TARGET_MODE2* modes = (IDDCX_TARGET_MODE2*)_malloca(
m_displayModes.size() * sizeof(IDDCX_TARGET_MODE2)); displayModes.size() * sizeof(IDDCX_TARGET_MODE2));
if (!modes) if (!modes)
{ {
DEBUG_WARN("Failed to allocate memory for the mode list"); DEBUG_WARN("Failed to allocate memory for the mode list");
return false; return false;
} }
ZeroMemory(modes, m_displayModes.size() * sizeof(IDDCX_TARGET_MODE2)); ZeroMemory(modes, displayModes.size() * sizeof(IDDCX_TARGET_MODE2));
auto* mode = modes; auto* mode = modes;
for (auto it = m_displayModes.cbegin(); it != m_displayModes.cend(); ++it, ++mode) for (auto it = displayModes.cbegin(); it != displayModes.cend(); ++it, ++mode)
{ {
mode->Size = sizeof(IDDCX_TARGET_MODE2); mode->Size = sizeof(IDDCX_TARGET_MODE2);
mode->RequiredBandwidth = (UINT64)it->width * it->height * it->refresh * 32; mode->RequiredBandwidth = (UINT64)it->width * it->height * it->refresh * 32;
@@ -467,10 +615,10 @@ bool CIndirectDeviceContext::UpdateMonitorModes()
IDARG_IN_UPDATEMODES2 updateModes = {}; IDARG_IN_UPDATEMODES2 updateModes = {};
updateModes.Reason = IDDCX_UPDATE_REASON_OTHER; updateModes.Reason = IDDCX_UPDATE_REASON_OTHER;
updateModes.TargetModeCount = (UINT)m_displayModes.size(); updateModes.TargetModeCount = (UINT)displayModes.size();
updateModes.pTargetModes = modes; updateModes.pTargetModes = modes;
NTSTATUS status = IddCxMonitorUpdateModes2(m_monitor, &updateModes); NTSTATUS status = IddCxMonitorUpdateModes2(monitor, &updateModes);
_freea(modes); _freea(modes);
if (!NT_SUCCESS(status)) if (!NT_SUCCESS(status))
{ {
@@ -483,17 +631,17 @@ bool CIndirectDeviceContext::UpdateMonitorModes()
#endif #endif
IDDCX_TARGET_MODE* modes = (IDDCX_TARGET_MODE*)_malloca( IDDCX_TARGET_MODE* modes = (IDDCX_TARGET_MODE*)_malloca(
m_displayModes.size() * sizeof(IDDCX_TARGET_MODE)); displayModes.size() * sizeof(IDDCX_TARGET_MODE));
if (!modes) if (!modes)
{ {
DEBUG_WARN("Failed to allocate memory for the mode list"); DEBUG_WARN("Failed to allocate memory for the mode list");
return false; return false;
} }
ZeroMemory(modes, m_displayModes.size() * sizeof(IDDCX_TARGET_MODE)); ZeroMemory(modes, displayModes.size() * sizeof(IDDCX_TARGET_MODE));
auto* mode = modes; auto* mode = modes;
for (auto it = m_displayModes.cbegin(); it != m_displayModes.cend(); ++it, ++mode) for (auto it = displayModes.cbegin(); it != displayModes.cend(); ++it, ++mode)
{ {
mode->Size = sizeof(IDDCX_TARGET_MODE); mode->Size = sizeof(IDDCX_TARGET_MODE);
mode->RequiredBandwidth = (UINT64)it->width * it->height * it->refresh * 32; mode->RequiredBandwidth = (UINT64)it->width * it->height * it->refresh * 32;
@@ -502,10 +650,10 @@ bool CIndirectDeviceContext::UpdateMonitorModes()
IDARG_IN_UPDATEMODES updateModes = {}; IDARG_IN_UPDATEMODES updateModes = {};
updateModes.Reason = IDDCX_UPDATE_REASON_OTHER; updateModes.Reason = IDDCX_UPDATE_REASON_OTHER;
updateModes.TargetModeCount = (UINT)m_displayModes.size(); updateModes.TargetModeCount = (UINT)displayModes.size();
updateModes.pTargetModes = modes; updateModes.pTargetModes = modes;
NTSTATUS status = IddCxMonitorUpdateModes(m_monitor, &updateModes); NTSTATUS status = IddCxMonitorUpdateModes(monitor, &updateModes);
_freea(modes); _freea(modes);
if (!NT_SUCCESS(status)) if (!NT_SUCCESS(status))
{ {
@@ -518,25 +666,35 @@ bool CIndirectDeviceContext::UpdateMonitorModes()
void CIndirectDeviceContext::SetResolution(int width, int height) void CIndirectDeviceContext::SetResolution(int width, int height)
{ {
m_setMode.width = width; CSettings::DisplayMode mode = {};
m_setMode.height = height; mode.width = width;
m_setMode.refresh = g_settings.GetDefaultRefresh(); mode.height = height;
m_setMode.preferred = true; mode.refresh = g_settings.GetDefaultRefresh();
g_settings.SetExtraMode(m_setMode); mode.preferred = true;
AcquireSRWLockExclusive(&m_stateLock);
m_setMode = mode;
ReleaseSRWLockExclusive(&m_stateLock);
g_settings.SetExtraMode(mode);
PopulateDefaultModes(); PopulateDefaultModes();
if (UpdateMonitorModes()) if (UpdateMonitorModes())
{ {
DEBUG_TRACE("Updated monitor modes without replugging"); DEBUG_TRACE("Updated monitor modes without replugging");
AcquireSRWLockExclusive(&m_stateLock);
m_doSetMode = false; m_doSetMode = false;
ReleaseSRWLockExclusive(&m_stateLock);
InterlockedExchange(&m_recoverModeUpdateSwapChain, 1); InterlockedExchange(&m_recoverModeUpdateSwapChain, 1);
g_pipe.SetDisplayMode(m_setMode.width, m_setMode.height, m_setMode.refresh); g_pipe.SetDisplayMode(mode.width, mode.height, mode.refresh);
return; return;
} }
DEBUG_TRACE("Falling back to monitor replug for mode update"); DEBUG_TRACE("Falling back to monitor replug for mode update");
AcquireSRWLockExclusive(&m_stateLock);
m_doSetMode = true; m_doSetMode = true;
ReleaseSRWLockExclusive(&m_stateLock);
InterlockedExchange(&m_recoverModeUpdateSwapChain, 0); InterlockedExchange(&m_recoverModeUpdateSwapChain, 0);
ReplugMonitor(); ReplugMonitor();
} }
@@ -722,6 +880,15 @@ void CIndirectDeviceContext::DeInitLGMP()
{ {
m_hasFrame = false; m_hasFrame = false;
// The retry timer callback dereferences this context, so make sure it is
// stopped and drained before we tear anything down. Wait for any in-flight
// callback to complete.
if (m_initTimer)
{
WdfTimerStop(m_initTimer, TRUE);
m_initTimer = nullptr;
}
if (m_lgmp == nullptr) if (m_lgmp == nullptr)
return; return;
@@ -744,11 +911,21 @@ void CIndirectDeviceContext::LGMPTimer()
{ {
if (InterlockedExchange(&m_replugMonitorQueued, 0)) if (InterlockedExchange(&m_replugMonitorQueued, 0))
{ {
AcquireSRWLockExclusive(&m_stateLock);
m_doSetMode = true; m_doSetMode = true;
ReleaseSRWLockExclusive(&m_stateLock);
ReplugMonitor(); ReplugMonitor();
return; return;
} }
// Rebuild the monitor deferred from the IddCx unassign callback, off that
// callback's thread and after its swap-chain teardown has fully unwound.
if (InterlockedExchange(&m_finishInitQueued, 0))
{
FinishInit(0);
return;
}
LGMP_STATUS status; LGMP_STATUS status;
if ((status = lgmpHostProcess(m_lgmp)) != LGMP_OK) if ((status = lgmpHostProcess(m_lgmp)) != LGMP_OK)
{ {
@@ -807,9 +984,16 @@ CIndirectDeviceContext::PreparedFrameBuffer CIndirectDeviceContext::PrepareFrame
if (!m_lgmp || !m_frameQueue) if (!m_lgmp || !m_frameQueue)
return result; return result;
if (InterlockedCompareExchange(&m_recoverModeUpdateSwapChain, 0, 0) && if (InterlockedCompareExchange(&m_recoverModeUpdateSwapChain, 0, 0))
srcFormat.width == m_setMode.width && srcFormat.height == m_setMode.height) {
InterlockedExchange(&m_recoverModeUpdateSwapChain, 0); AcquireSRWLockShared(&m_stateLock);
const bool sizeMatches =
srcFormat.width == m_setMode.width && srcFormat.height == m_setMode.height;
ReleaseSRWLockShared(&m_stateLock);
if (sizeMatches)
InterlockedExchange(&m_recoverModeUpdateSwapChain, 0);
}
if (m_width != dstFormat.desc.Width || if (m_width != dstFormat.desc.Width ||
m_height != dstFormat.desc.Height || m_height != dstFormat.desc.Height ||

View File

@@ -65,6 +65,17 @@ private:
IDDCX_MONITOR m_monitor = nullptr; IDDCX_MONITOR m_monitor = nullptr;
bool m_replugMonitor = false; bool m_replugMonitor = false;
// Guards the adapter/monitor init handshake and the replug state machine
// (m_monitor, m_replugMonitor, m_doSetMode, m_setMode). These are touched
// from the IddCx callback threads and the LGMP timer thread.
SRWLOCK m_stateLock = SRWLOCK_INIT;
// Retry state for InitAdapter. At boot the IVSHMEM device may not have
// enumerated yet; if so we re-attempt from a timer instead of giving up.
WDFTIMER m_initTimer = nullptr;
bool m_ivshmemOpened = false;
volatile LONG m_initInProgress = 0;
CIVSHMEM m_ivshmem; CIVSHMEM m_ivshmem;
PLGMPHost m_lgmp = nullptr; PLGMPHost m_lgmp = nullptr;
@@ -123,11 +134,20 @@ private:
void QueryIddCxCapabilities(); void QueryIddCxCapabilities();
bool CanUseIddCx110DDIs() const { return m_canProcessFP16; } bool CanUseIddCx110DDIs() const { return m_canProcessFP16; }
void ScheduleInitRetry();
void StopInitRetry();
void DeInitLGMP(); void DeInitLGMP();
void LGMPTimer(); void LGMPTimer();
void ResendCursor() const; void ResendCursor() const;
bool UpdateMonitorModes(); bool UpdateMonitorModes();
// Guards m_displayModes and m_edid. The mode list is rebuilt on the LGMP
// timer thread (SetResolution) while IddCx concurrently enumerates it on its
// own callback threads (ParseMonitorDescription / MonitorQueryTargetModes /
// FinishInit). Never held across an IddCx API call - snapshot then call.
mutable SRWLOCK m_modeLock = SRWLOCK_INIT;
CSettings::DisplayModes m_displayModes; CSettings::DisplayModes m_displayModes;
CEdid m_edid; CEdid m_edid;
@@ -135,6 +155,12 @@ private:
bool m_doSetMode = false; bool m_doSetMode = false;
volatile LONG m_replugMonitorQueued = 0; volatile LONG m_replugMonitorQueued = 0;
volatile LONG m_recoverModeUpdateSwapChain = 0; volatile LONG m_recoverModeUpdateSwapChain = 0;
// Set from the IddCx unassign callback to defer the monitor rebuild
// (FinishInit) onto the LGMP timer. Creating/arriving a new monitor from
// inside the old monitor's unassign callback re-enters IddCx while its
// swap-chain teardown is still unwinding, which leaves the new swap-chain
// surfaces in a lost/abandoned state.
volatile LONG m_finishInitQueued = 0;
public: public:
CIndirectDeviceContext(_In_ WDFDEVICE wdfDevice) : CIndirectDeviceContext(_In_ WDFDEVICE wdfDevice) :

View File

@@ -396,8 +396,9 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
// D65 white point (in 0.00002 units): // D65 white point (in 0.00002 units):
srcFormat.whitePoint[0] = 15635; srcFormat.whitePoint[0] = 15635;
srcFormat.whitePoint[1] = 16450; srcFormat.whitePoint[1] = 16450;
// 80 cd/m² display, 0.005 cd/m² black (in 0.0001 cd/m² units): // Mastering luminances follow SMPTE ST 2086 units: max in whole cd/m²,
srcFormat.maxDisplayLuminance = 800000; // min in 0.0001 cd/m². 80 cd/m² display, 0.005 cd/m² black:
srcFormat.maxDisplayLuminance = 80;
srcFormat.minDisplayLuminance = 50; srcFormat.minDisplayLuminance = 50;
// Content light levels unknown: // Content light levels unknown:
srcFormat.maxContentLightLevel = 0; srcFormat.maxContentLightLevel = 0;

View File

@@ -267,14 +267,16 @@ NTSTATUS LGIddCreateDevice(_Inout_ PWDFDEVICE_INIT deviceInit)
return status; return status;
/* /*
* Keep the WDF device cached so callbacks that do not provide an adapter or * Construct the device context and cache the WDF device BEFORE calling
* monitor context can still reach the device context on down-level IddCx. * IddCxDeviceInitialize. IddCxDeviceInitialize arms the IddCx callbacks, and
* callbacks that resolve the context via l_wdfDevice (down-level IddCx that
* provides no adapter/monitor context) must never observe a null context.
*/ */
auto wrapper = WdfObjectGet_CIndirectDeviceContextWrapper(device);
wrapper->context = new CIndirectDeviceContext(device);
l_wdfDevice = device; l_wdfDevice = device;
status = IddCxDeviceInitialize(device); status = IddCxDeviceInitialize(device);
auto wrapper = WdfObjectGet_CIndirectDeviceContextWrapper(device);
wrapper->context = new CIndirectDeviceContext(device);
return status; return status;
} }