|
@@ -997,6 +997,180 @@ static bool GetMonitorTarget(const MONITORINFOEX &info,
|
|
|
return found;
|
|
|
}
|
|
|
|
|
|
+static DXGI_COLOR_SPACE_TYPE GetColorSpace(IDXGIOutput *const output)
|
|
|
+{
|
|
|
+ DXGI_COLOR_SPACE_TYPE space = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
|
|
|
+
|
|
|
+ ComPtr<IDXGIOutput6> output6;
|
|
|
+ HRESULT hr = output->QueryInterface(IID_PPV_ARGS(output6.Assign()));
|
|
|
+ if (SUCCEEDED(hr)) {
|
|
|
+ DXGI_OUTPUT_DESC1 desc1;
|
|
|
+ hr = output6->GetDesc1(&desc1);
|
|
|
+ if (SUCCEEDED(hr)) {
|
|
|
+ space = desc1.ColorSpace;
|
|
|
+ } else {
|
|
|
+ blog(LOG_WARNING,
|
|
|
+ "IDXGIOutput6::GetDesc1 failed: 0x%08lX", hr);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return space;
|
|
|
+}
|
|
|
+
|
|
|
+// Returns true if this is an integrated display panel e.g. the screen attached to tablets or laptops.
|
|
|
+static bool IsInternalVideoOutput(
|
|
|
+ const DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY VideoOutputTechnologyType)
|
|
|
+{
|
|
|
+ switch (VideoOutputTechnologyType) {
|
|
|
+ case DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INTERNAL:
|
|
|
+ case DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EMBEDDED:
|
|
|
+ case DISPLAYCONFIG_OUTPUT_TECHNOLOGY_UDI_EMBEDDED:
|
|
|
+ return TRUE;
|
|
|
+
|
|
|
+ default:
|
|
|
+ return FALSE;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Note: Since an hmon can represent multiple monitors while in clone, this function as written will return
|
|
|
+// the value for the internal monitor if one exists, and otherwise the highest clone-path priority.
|
|
|
+static HRESULT GetPathInfo(_In_ PCWSTR pszDeviceName,
|
|
|
+ _Out_ DISPLAYCONFIG_PATH_INFO *pPathInfo)
|
|
|
+{
|
|
|
+ HRESULT hr = S_OK;
|
|
|
+ UINT32 NumPathArrayElements = 0;
|
|
|
+ UINT32 NumModeInfoArrayElements = 0;
|
|
|
+ DISPLAYCONFIG_PATH_INFO *PathInfoArray = nullptr;
|
|
|
+ DISPLAYCONFIG_MODE_INFO *ModeInfoArray = nullptr;
|
|
|
+
|
|
|
+ do {
|
|
|
+ // In case this isn't the first time through the loop, delete the buffers allocated
|
|
|
+ delete[] PathInfoArray;
|
|
|
+ PathInfoArray = nullptr;
|
|
|
+
|
|
|
+ delete[] ModeInfoArray;
|
|
|
+ ModeInfoArray = nullptr;
|
|
|
+
|
|
|
+ hr = HRESULT_FROM_WIN32(GetDisplayConfigBufferSizes(
|
|
|
+ QDC_ONLY_ACTIVE_PATHS, &NumPathArrayElements,
|
|
|
+ &NumModeInfoArrayElements));
|
|
|
+ if (FAILED(hr)) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ PathInfoArray = new (std::nothrow)
|
|
|
+ DISPLAYCONFIG_PATH_INFO[NumPathArrayElements];
|
|
|
+ if (PathInfoArray == nullptr) {
|
|
|
+ hr = E_OUTOFMEMORY;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ ModeInfoArray = new (std::nothrow)
|
|
|
+ DISPLAYCONFIG_MODE_INFO[NumModeInfoArrayElements];
|
|
|
+ if (ModeInfoArray == nullptr) {
|
|
|
+ hr = E_OUTOFMEMORY;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ hr = HRESULT_FROM_WIN32(QueryDisplayConfig(
|
|
|
+ QDC_ONLY_ACTIVE_PATHS, &NumPathArrayElements,
|
|
|
+ PathInfoArray, &NumModeInfoArrayElements, ModeInfoArray,
|
|
|
+ nullptr));
|
|
|
+ } while (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER));
|
|
|
+
|
|
|
+ INT DesiredPathIdx = -1;
|
|
|
+
|
|
|
+ if (SUCCEEDED(hr)) {
|
|
|
+ // Loop through all sources until the one which matches the 'monitor' is found.
|
|
|
+ for (UINT PathIdx = 0; PathIdx < NumPathArrayElements;
|
|
|
+ ++PathIdx) {
|
|
|
+ DISPLAYCONFIG_SOURCE_DEVICE_NAME SourceName = {};
|
|
|
+ SourceName.header.type =
|
|
|
+ DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
|
|
|
+ SourceName.header.size = sizeof(SourceName);
|
|
|
+ SourceName.header.adapterId =
|
|
|
+ PathInfoArray[PathIdx].sourceInfo.adapterId;
|
|
|
+ SourceName.header.id =
|
|
|
+ PathInfoArray[PathIdx].sourceInfo.id;
|
|
|
+
|
|
|
+ hr = HRESULT_FROM_WIN32(
|
|
|
+ DisplayConfigGetDeviceInfo(&SourceName.header));
|
|
|
+ if (SUCCEEDED(hr)) {
|
|
|
+ if (wcscmp(pszDeviceName,
|
|
|
+ SourceName.viewGdiDeviceName) == 0) {
|
|
|
+ // Found the source which matches this hmonitor. The paths are given in path-priority order
|
|
|
+ // so the first found is the most desired, unless we later find an internal.
|
|
|
+ if (DesiredPathIdx == -1 ||
|
|
|
+ IsInternalVideoOutput(
|
|
|
+ PathInfoArray[PathIdx]
|
|
|
+ .targetInfo
|
|
|
+ .outputTechnology)) {
|
|
|
+ DesiredPathIdx = PathIdx;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (DesiredPathIdx != -1) {
|
|
|
+ *pPathInfo = PathInfoArray[DesiredPathIdx];
|
|
|
+ } else {
|
|
|
+ hr = E_INVALIDARG;
|
|
|
+ }
|
|
|
+
|
|
|
+ delete[] PathInfoArray;
|
|
|
+ PathInfoArray = nullptr;
|
|
|
+
|
|
|
+ delete[] ModeInfoArray;
|
|
|
+ ModeInfoArray = nullptr;
|
|
|
+
|
|
|
+ return hr;
|
|
|
+}
|
|
|
+
|
|
|
+// Overloaded function accepts an HMONITOR and converts to DeviceName
|
|
|
+static HRESULT GetPathInfo(HMONITOR hMonitor,
|
|
|
+ _Out_ DISPLAYCONFIG_PATH_INFO *pPathInfo)
|
|
|
+{
|
|
|
+ HRESULT hr = S_OK;
|
|
|
+
|
|
|
+ // Get the name of the 'monitor' being requested
|
|
|
+ MONITORINFOEXW ViewInfo;
|
|
|
+ RtlZeroMemory(&ViewInfo, sizeof(ViewInfo));
|
|
|
+ ViewInfo.cbSize = sizeof(ViewInfo);
|
|
|
+ if (!GetMonitorInfoW(hMonitor, &ViewInfo)) {
|
|
|
+ // Error condition, likely invalid monitor handle, could log error
|
|
|
+ hr = HRESULT_FROM_WIN32(GetLastError());
|
|
|
+ }
|
|
|
+
|
|
|
+ if (SUCCEEDED(hr)) {
|
|
|
+ hr = GetPathInfo(ViewInfo.szDevice, pPathInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ return hr;
|
|
|
+}
|
|
|
+
|
|
|
+static ULONG GetSdrWhiteNits(HMONITOR monitor)
|
|
|
+{
|
|
|
+ ULONG nits = 0;
|
|
|
+
|
|
|
+ DISPLAYCONFIG_PATH_INFO info;
|
|
|
+ if (SUCCEEDED(GetPathInfo(monitor, &info))) {
|
|
|
+ const DISPLAYCONFIG_PATH_TARGET_INFO &targetInfo =
|
|
|
+ info.targetInfo;
|
|
|
+
|
|
|
+ DISPLAYCONFIG_SDR_WHITE_LEVEL level;
|
|
|
+ DISPLAYCONFIG_DEVICE_INFO_HEADER &header = level.header;
|
|
|
+ header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL;
|
|
|
+ header.size = sizeof(level);
|
|
|
+ header.adapterId = targetInfo.adapterId;
|
|
|
+ header.id = targetInfo.id;
|
|
|
+ if (DisplayConfigGetDeviceInfo(&header) == ERROR_SUCCESS)
|
|
|
+ nits = (level.SDRWhiteLevel * 80) / 1000;
|
|
|
+ }
|
|
|
+
|
|
|
+ return nits;
|
|
|
+}
|
|
|
+
|
|
|
static inline void LogAdapterMonitors(IDXGIAdapter1 *adapter)
|
|
|
{
|
|
|
UINT i;
|
|
@@ -1030,18 +1204,36 @@ static inline void LogAdapterMonitors(IDXGIAdapter1 *adapter)
|
|
|
target.monitorFriendlyDeviceName[0] = 0;
|
|
|
}
|
|
|
|
|
|
+ const char *space = "Unknown";
|
|
|
+ const DXGI_COLOR_SPACE_TYPE type = GetColorSpace(output);
|
|
|
+ switch (type) {
|
|
|
+ case DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709:
|
|
|
+ space = "RGB_FULL_G22_NONE_P709";
|
|
|
+ break;
|
|
|
+ case DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020:
|
|
|
+ space = "RGB_FULL_G2084_NONE_P2020";
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ blog(LOG_WARNING,
|
|
|
+ "Unexpected DXGI_COLOR_SPACE_TYPE: %u",
|
|
|
+ (unsigned)type);
|
|
|
+ }
|
|
|
+
|
|
|
const RECT &rect = desc.DesktopCoordinates;
|
|
|
+ const ULONG nits = GetSdrWhiteNits(desc.Monitor);
|
|
|
blog(LOG_INFO,
|
|
|
"\t output %u: "
|
|
|
"pos={%d, %d}, "
|
|
|
"size={%d, %d}, "
|
|
|
"attached=%s, "
|
|
|
+ "space=%s, "
|
|
|
+ "sdr_white_nits=%lu, "
|
|
|
"refresh=%u, "
|
|
|
"name=%ls",
|
|
|
i, rect.left, rect.top, rect.right - rect.left,
|
|
|
rect.bottom - rect.top,
|
|
|
- desc.AttachedToDesktop ? "true" : "false", refresh,
|
|
|
- target.monitorFriendlyDeviceName);
|
|
|
+ desc.AttachedToDesktop ? "true" : "false", space, nits,
|
|
|
+ refresh, target.monitorFriendlyDeviceName);
|
|
|
}
|
|
|
}
|
|
|
|