diff --git a/DisplayMagicianShared/NVIDIA/NVAPI.cs b/DisplayMagicianShared/NVIDIA/NVAPI.cs index a3502a7..edd4bb4 100644 --- a/DisplayMagicianShared/NVIDIA/NVAPI.cs +++ b/DisplayMagicianShared/NVIDIA/NVAPI.cs @@ -1025,6 +1025,27 @@ namespace DisplayMagicianShared.NVIDIA public static bool operator !=(NV_DISPLAYCONFIG_PATH_TARGET_INFO_V2 lhs, NV_DISPLAYCONFIG_PATH_TARGET_INFO_V2 rhs) => !(lhs == rhs); } + [StructLayout(LayoutKind.Sequential, Pack = 8)] + public struct NV_DISPLAYCONFIG_PATH_TARGET_INFO_V1 : IEquatable + { + public UInt32 DisplayId; //!< Display ID + NV_DISPLAYCONFIG_PATH_ADVANCED_TARGET_INFO Details; //!< May be NULL if no advanced settings are required + + public override bool Equals(object obj) => obj is NV_DISPLAYCONFIG_PATH_TARGET_INFO_V1 other && this.Equals(other); + + public bool Equals(NV_DISPLAYCONFIG_PATH_TARGET_INFO_V1 other) + => DisplayId == other.DisplayId && + Details.Equals(other.Details); + + public override Int32 GetHashCode() + { + return (DisplayId, Details).GetHashCode(); + } + public static bool operator ==(NV_DISPLAYCONFIG_PATH_TARGET_INFO_V1 lhs, NV_DISPLAYCONFIG_PATH_TARGET_INFO_V1 rhs) => lhs.Equals(rhs); + + public static bool operator !=(NV_DISPLAYCONFIG_PATH_TARGET_INFO_V1 lhs, NV_DISPLAYCONFIG_PATH_TARGET_INFO_V1 rhs) => !(lhs == rhs); + } + [StructLayout(LayoutKind.Sequential)] public struct NV_DISPLAYCONFIG_PATH_INFO_V2 : IEquatable // Version is 2 { @@ -1032,12 +1053,15 @@ namespace DisplayMagicianShared.NVIDIA public UInt32 SourceId; //!< Identifies sourceId used by Windows CCD. This can be optionally set. public UInt32 TargetInfoCount; //!< Number of elements in targetInfo array - public NV_DISPLAYCONFIG_PATH_TARGET_INFO_V2 TargetInfo; - public NV_DISPLAYCONFIG_SOURCE_MODE_INFO_V1 sourceModeInfo; //!< May be NULL if mode info is not important + //[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + public NV_DISPLAYCONFIG_PATH_TARGET_INFO_V2[] TargetInfo; + public NV_DISPLAYCONFIG_SOURCE_MODE_INFO_V1 SourceModeInfo; //!< May be NULL if mode info is not important //public UInt32 IsNonNVIDIAAdapter : 1; //!< True for non-NVIDIA adapter. //public UInt32 reserved : 31; //!< Must be 0 //public LUID pOSAdapterID; //!< Used by Non-NVIDIA adapter for poInt32er to OS Adapter of LUID //!< type, type casted to void *. + public UInt32 Reserved; + public IntPtr OSAdapterID; public override bool Equals(object obj) => obj is NV_DISPLAYCONFIG_PATH_INFO_V2 other && this.Equals(other); @@ -1046,17 +1070,53 @@ namespace DisplayMagicianShared.NVIDIA SourceId == other.SourceId && TargetInfoCount == other.TargetInfoCount && TargetInfo.Equals(other.TargetInfo) && - sourceModeInfo.Equals(other.sourceModeInfo); + SourceModeInfo.Equals(other.SourceModeInfo) && + Reserved == other.Reserved && + OSAdapterID == other.OSAdapterID; public override Int32 GetHashCode() { - return (Version, SourceId, TargetInfoCount, TargetInfo, sourceModeInfo).GetHashCode(); + return (Version, SourceId, TargetInfoCount, TargetInfo, SourceModeInfo).GetHashCode(); } public static bool operator ==(NV_DISPLAYCONFIG_PATH_INFO_V2 lhs, NV_DISPLAYCONFIG_PATH_INFO_V2 rhs) => lhs.Equals(rhs); public static bool operator !=(NV_DISPLAYCONFIG_PATH_INFO_V2 lhs, NV_DISPLAYCONFIG_PATH_INFO_V2 rhs) => !(lhs == rhs); } + [StructLayout(LayoutKind.Sequential)] + public struct NV_DISPLAYCONFIG_PATH_INFO_V1 : IEquatable // Version is 1 + { + public UInt32 Version; + public UInt32 SourceId; //!< Identifies sourceId used by Windows CCD. This can be optionally set. + + public UInt32 TargetInfoCount; //!< Number of elements in targetInfo array + //[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + public NV_DISPLAYCONFIG_PATH_TARGET_INFO_V2[] TargetInfo; + public NV_DISPLAYCONFIG_SOURCE_MODE_INFO_V1 SourceModeInfo; //!< May be NULL if mode info is not important + //public UInt32 IsNonNVIDIAAdapter : 1; //!< True for non-NVIDIA adapter. + //public UInt32 reserved : 31; //!< Must be 0 + //public LUID pOSAdapterID; //!< Used by Non-NVIDIA adapter for poInt32er to OS Adapter of LUID + //!< type, type casted to void *. + + public override bool Equals(object obj) => obj is NV_DISPLAYCONFIG_PATH_INFO_V1 other && this.Equals(other); + + public bool Equals(NV_DISPLAYCONFIG_PATH_INFO_V1 other) + => Version == other.Version && + SourceId == other.SourceId && + TargetInfoCount == other.TargetInfoCount && + TargetInfo.Equals(other.TargetInfo) && + SourceModeInfo.Equals(other.SourceModeInfo); + + public override Int32 GetHashCode() + { + return (Version, SourceId, TargetInfoCount, TargetInfo, SourceModeInfo).GetHashCode(); + } + public static bool operator ==(NV_DISPLAYCONFIG_PATH_INFO_V1 lhs, NV_DISPLAYCONFIG_PATH_INFO_V1 rhs) => lhs.Equals(rhs); + + public static bool operator !=(NV_DISPLAYCONFIG_PATH_INFO_V1 lhs, NV_DISPLAYCONFIG_PATH_INFO_V1 rhs) => !(lhs == rhs); + } + + [StructLayout(LayoutKind.Sequential, Pack = 8)] public struct NV_DISPLAYCONFIG_SOURCE_MODE_INFO_V1 : IEquatable { @@ -1880,6 +1940,8 @@ namespace DisplayMagicianShared.NVIDIA public static UInt32 NV_GPU_DISPLAYIDS_V2_VER = MAKE_NVAPI_VERSION(3); // NOTE: There is a bug in R470 that sets the NV_GPU_DISPLAYIDS_V2 version to 3! public static UInt32 NV_BOARD_INFO_V1_VER = MAKE_NVAPI_VERSION(1); public static UInt32 NV_EDID_V3_VER = MAKE_NVAPI_VERSION(3); + public static UInt32 NV_DISPLAYCONFIG_PATH_INFO_V1_VER = MAKE_NVAPI_VERSION(1); + public static UInt32 NV_DISPLAYCONFIG_PATH_INFO_V2_VER = MAKE_NVAPI_VERSION(2); #region Internal Constant @@ -1901,6 +1963,10 @@ namespace DisplayMagicianShared.NVIDIA { return (UInt32)((Marshal.SizeOf(typeof(T))) | (Int32)(version << 16)); } + private static UInt32 MAKE_NVAPI_VERSION(Int32 size, Int32 version) + { + return (UInt32)((Int32)size | (Int32)(version << 16)); + } private static void GetDelegate(UInt32 apiId, out T newDelegate) where T : class { @@ -2008,7 +2074,9 @@ namespace DisplayMagicianShared.NVIDIA GetDelegate(NvId_DISP_GetGDIPrimaryDisplayId, out DISP_GetGDIPrimaryDisplayIdInternal); GetDelegate(NvId_Disp_GetHdrCapabilities, out Disp_GetHdrCapabilitiesInternal); GetDelegate(NvId_Disp_HdrColorControl, out Disp_HdrColorControlInternal); - + GetDelegate(NvId_DISP_GetDisplayConfig, out DISP_GetDisplayConfigInternal); + GetDelegate(NvId_DISP_GetDisplayConfig, out DISP_GetDisplayConfigInternalNull); // null version of the submission + GetDelegate(NvId_DISP_GetDisplayIdByDisplayName, out DISP_GetDisplayIdByDisplayNameInternal); // GPUs GetDelegate(NvId_EnumPhysicalGPUs, out EnumPhysicalGPUsInternal); @@ -3060,6 +3128,189 @@ namespace DisplayMagicianShared.NVIDIA } #endregion + + // ******** IMPORTANT! This code has an error when attempting to perform the third pass as required by NVAPI documentation ********* + // ******** FOr this reason I have disabled the code as I don't actually need to get it going. ******** + /* // NVAPI_INTERFACE NvAPI_DISP_GetDisplayConfig(__inout NvU32 *pathInfoCount, __out_ecount_full_opt(*pathInfoCount) NV_DISPLAYCONFIG_PATH_INFO *pathInfo); + private delegate NVAPI_STATUS DISP_GetDisplayConfigDelegate( + [In][Out] ref UInt32 pathInfoCount, + [In][Out] IntPtr pathInfoBuffer); + private static readonly DISP_GetDisplayConfigDelegate DISP_GetDisplayConfigInternal; + + /// + /// DESCRIPTION: This API lets caller retrieve the current global display configuration. + /// USAGE: The caller might have to call this three times to fetch all the required configuration details as follows: + /// First Pass: Caller should Call NvAPI_DISP_GetDisplayConfig() with pathInfo set to NULL to fetch pathInfoCount. + /// Second Pass: Allocate memory for pathInfo with respect to the number of pathInfoCount(from First Pass) to fetch + /// targetInfoCount. If sourceModeInfo is needed allocate memory or it can be initialized to NULL. + /// Third Pass(Optional, only required if target information is required): Allocate memory for targetInfo with respect + // to number of targetInfoCount(from Second Pass). + /// SUPPORTED OS: Windows 7 and higher + /// + /// + /// + /// + public static NVAPI_STATUS NvAPI_DISP_GetDisplayConfig(ref UInt32 PathInfoCount, ref NV_DISPLAYCONFIG_PATH_INFO_V1[] PathInfos, bool partFilledIn = false) + { + NVAPI_STATUS status; + IntPtr pathInfoBuffer = IntPtr.Zero; + IntPtr currentPathInfoBuffer = IntPtr.Zero; + if (partFilledIn) + { + // Copy the supplied object for the third pass (when we have the pathInfoCount and the targetInfoCount for each pathInfo, but we want the details) + //NV_DISPLAYCONFIG_PATH_INFO_V1[] passedPathInfo = PathInfos; + //PathInfos = new NV_DISPLAYCONFIG_PATH_INFO_V1[PathInfoCount]; + // Go through the array and create the structure + int overallTargetCount = 0; + for (Int32 x = 0; x < (Int32)PathInfoCount; x++) + { + // Copy the information passed in, into the buffer we want to pass + //PathInfos[x].Version = MAKE_NVAPI_VERSION(Marshal.SizeOf(passedPathInfo[x]),1); + *//*PathInfos[x].SourceId = passedPathInfo[x].SourceId; + PathInfos[x].TargetInfoCount = passedPathInfo[x].TargetInfoCount; + PathInfos[x].TargetInfo = passedPathInfo[x].TargetInfo; + PathInfos[x].SourceModeInfo = = passedPathInfo[x].SourceModeInfo;*//* + overallTargetCount += (int)PathInfos[x].TargetInfoCount; + } + // Initialize unmanged memory to hold the unmanaged array of structs + int memorySizeRequired = Marshal.SizeOf(typeof(NV_DISPLAYCONFIG_PATH_INFO_V1)) * (int)PathInfoCount; + pathInfoBuffer = Marshal.AllocCoTaskMem(memorySizeRequired); + // Also set another memory pointer to the same place so that we can do the memory copying item by item + // as we have to do it ourselves (there isn't an easy to use Marshal equivalent) + currentPathInfoBuffer = pathInfoBuffer; + // Go through the array and copy things from managed code to unmanaged code + for (Int32 x = 0; x < (Int32)PathInfoCount; x++) + { + // Marshal a single gridtopology into unmanaged code ready for sending to the unmanaged NVAPI function + Marshal.StructureToPtr(PathInfos[x], currentPathInfoBuffer, false); + // advance the buffer forwards to the next object + currentPathInfoBuffer = (IntPtr)((long)currentPathInfoBuffer + Marshal.SizeOf(PathInfos[x])); + } + + } + else + { + // Build a new blank object for the second pass (when we have the pathInfoCount, but want the targetInfoCount for each pathInfo) + // Build a managed structure for us to use as a data source for another object that the unmanaged NVAPI C library can use + PathInfos = new NV_DISPLAYCONFIG_PATH_INFO_V1[PathInfoCount]; + // Initialize unmanged memory to hold the unmanaged array of structs + pathInfoBuffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(NV_DISPLAYCONFIG_PATH_INFO_V1)) * (int)PathInfoCount); + // Also set another memory pointer to the same place so that we can do the memory copying item by item + // as we have to do it ourselves (there isn't an easy to use Marshal equivalent) + currentPathInfoBuffer = pathInfoBuffer; + // Go through the array and copy things from managed code to unmanaged code + for (Int32 x = 0; x < (Int32)PathInfoCount; x++) + { + PathInfos[x].Version = MAKE_NVAPI_VERSION(Marshal.SizeOf(PathInfos[x]), 1); + PathInfos[x].SourceModeInfo = new NV_DISPLAYCONFIG_SOURCE_MODE_INFO_V1(); + // Marshal a single gridtopology into unmanaged code ready for sending to the unmanaged NVAPI function + Marshal.StructureToPtr(PathInfos[x], currentPathInfoBuffer, false); + // advance the buffer forwards to the next object + currentPathInfoBuffer = (IntPtr)((long)currentPathInfoBuffer + Marshal.SizeOf(PathInfos[x])); + } + } + + + if (DISP_GetDisplayConfigInternal != null) + { + // Use the unmanaged buffer in the unmanaged C call + status = DISP_GetDisplayConfigInternal(ref PathInfoCount, pathInfoBuffer); + + if (status == NVAPI_STATUS.NVAPI_OK) + { + // If everything worked, then copy the data back from the unmanaged array into the managed array + // So that we can use it in C# land + // Reset the memory pointer we're using for tracking where we are back to the start of the unmanaged memory buffer + currentPathInfoBuffer = pathInfoBuffer; + // Create a managed array to store the received information within + PathInfos = new NV_DISPLAYCONFIG_PATH_INFO_V1[PathInfoCount]; + // Go through the memory buffer item by item and copy the items into the managed array + for (int i = 0; i < PathInfoCount; i++) + { + // build a structure in the array slot + PathInfos[i] = new NV_DISPLAYCONFIG_PATH_INFO_V1(); + // fill the array slot structure with the data from the buffer + PathInfos[i] = (NV_DISPLAYCONFIG_PATH_INFO_V1)Marshal.PtrToStructure(currentPathInfoBuffer, typeof(NV_DISPLAYCONFIG_PATH_INFO_V1)); + // destroy the bit of memory we no longer need + Marshal.DestroyStructure(currentPathInfoBuffer, typeof(NV_DISPLAYCONFIG_PATH_INFO_V1)); + // advance the buffer forwards to the next object + currentPathInfoBuffer = (IntPtr)((long)currentPathInfoBuffer + Marshal.SizeOf(PathInfos[i])); + } + } + } + else + { + status = NVAPI_STATUS.NVAPI_FUNCTION_NOT_FOUND; + } + + Marshal.FreeCoTaskMem(pathInfoBuffer); + + return status; + } + + // NVAPI_INTERFACE NvAPI_DISP_GetDisplayConfig(__inout NvU32 *pathInfoCount, __out_ecount_full_opt(*pathInfoCount) NV_DISPLAYCONFIG_PATH_INFO *pathInfo); + // NvAPIMosaic_EnumDisplayGrids + private delegate NVAPI_STATUS DISP_GetDisplayConfigDelegateNull( + [In][Out] ref UInt32 pathInfoCount, + [In][Out] IntPtr pathInfoBuffer); + private static readonly DISP_GetDisplayConfigDelegateNull DISP_GetDisplayConfigInternalNull; + + /// + /// DESCRIPTION: This API lets caller retrieve the current global display configuration. + /// USAGE: The caller might have to call this three times to fetch all the required configuration details as follows: + /// First Pass: Caller should Call NvAPI_DISP_GetDisplayConfig() with pathInfo set to NULL to fetch pathInfoCount. + /// Second Pass: Allocate memory for pathInfo with respect to the number of pathInfoCount(from First Pass) to fetch + /// targetInfoCount. If sourceModeInfo is needed allocate memory or it can be initialized to NULL. + /// Third Pass(Optional, only required if target information is required): Allocate memory for targetInfo with respect + // to number of targetInfoCount(from Second Pass). + /// SUPPORTED OS: Windows 7 and higher + /// + /// + /// + public static NVAPI_STATUS NvAPI_DISP_GetDisplayConfig(ref UInt32 PathInfoCount) + { + NVAPI_STATUS status; + IntPtr pathInfos = IntPtr.Zero; + + if (DISP_GetDisplayConfigInternalNull != null) { status = DISP_GetDisplayConfigInternalNull(ref PathInfoCount, pathInfos); } + else { status = NVAPI_STATUS.NVAPI_FUNCTION_NOT_FOUND; } + + return status; + }*/ + + + // NVAPI_INTERFACE NvAPI_DISP_GetDisplayIdByDisplayName(const char *displayName, NvU32* displayId); + private delegate NVAPI_STATUS DISP_GetDisplayIdByDisplayNameDelegate( + [In] string displayName, + [Out] out UInt32 displayId); + private static readonly DISP_GetDisplayIdByDisplayNameDelegate DISP_GetDisplayIdByDisplayNameInternal; + + /// + /// + /// DESCRIPTION: This API retrieves the Display Id of a given display by + /// display name. The display must be active to retrieve the + /// displayId. In the case of clone mode or Surround gaming, + /// the primary or top-left display will be returned. + /// + /// \param [in] displayName Name of display (Eg: "\\DISPLAY1" to + /// retrieve the displayId for. + /// \param [out] displayId Display ID of the requested display. + /// + /// + /// + /// + public static NVAPI_STATUS NvAPI_DISP_GetDisplayIdByDisplayName(string displayName, out UInt32 displayId) + { + NVAPI_STATUS status; + displayId = 0; + + if (DISP_GetDisplayIdByDisplayNameInternal != null) { status = DISP_GetDisplayIdByDisplayNameInternal(displayName, out displayId); } + else { status = NVAPI_STATUS.NVAPI_FUNCTION_NOT_FOUND; } + + return status; + } + + // EnumPhysicalGPUs private delegate NVAPI_STATUS EnumPhysicalGPUsDelegate( [In][Out][MarshalAs(UnmanagedType.LPArray, SizeConst = (int)NV_MAX_PHYSICAL_GPUS)] PhysicalGpuHandle[] gpuHandles, diff --git a/DisplayMagicianShared/NVIDIA/NVIDIALibrary.cs b/DisplayMagicianShared/NVIDIA/NVIDIALibrary.cs index 4e45082..79c1e86 100644 --- a/DisplayMagicianShared/NVIDIA/NVIDIALibrary.cs +++ b/DisplayMagicianShared/NVIDIA/NVIDIALibrary.cs @@ -70,22 +70,43 @@ namespace DisplayMagicianShared.NVIDIA public static bool operator !=(NVIDIA_HDR_CONFIG lhs, NVIDIA_HDR_CONFIG rhs) => !(lhs == rhs); } + /*[StructLayout(LayoutKind.Sequential)] + public struct NVIDIA_WINDOWS_DISPLAY_CONFIG : IEquatable + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = (Int32)NVImport.NV_MAX_DISPLAYS)] + public NV_DISPLAYCONFIG_PATH_INFO_V2[] WindowsPaths; + + public override bool Equals(object obj) => obj is NVIDIA_WINDOWS_DISPLAY_CONFIG other && this.Equals(other); + public bool Equals(NVIDIA_WINDOWS_DISPLAY_CONFIG other) + => WindowsPaths.SequenceEqual(other.WindowsPaths); + + public override int GetHashCode() + { + return (WindowsPaths).GetHashCode(); + } + public static bool operator ==(NVIDIA_WINDOWS_DISPLAY_CONFIG lhs, NVIDIA_WINDOWS_DISPLAY_CONFIG rhs) => lhs.Equals(rhs); + + public static bool operator !=(NVIDIA_WINDOWS_DISPLAY_CONFIG lhs, NVIDIA_WINDOWS_DISPLAY_CONFIG rhs) => !(lhs == rhs); + }*/ + [StructLayout(LayoutKind.Sequential)] public struct NVIDIA_DISPLAY_CONFIG : IEquatable { public NVIDIA_MOSAIC_CONFIG MosaicConfig; public NVIDIA_HDR_CONFIG HdrConfig; + public Dictionary DisplayNames; public List DisplayIdentifiers; public override bool Equals(object obj) => obj is NVIDIA_DISPLAY_CONFIG other && this.Equals(other); public bool Equals(NVIDIA_DISPLAY_CONFIG other) => MosaicConfig.Equals(other.MosaicConfig) && HdrConfig.Equals(other.HdrConfig) && - DisplayIdentifiers.SequenceEqual(other.DisplayIdentifiers); + DisplayIdentifiers.SequenceEqual(other.DisplayIdentifiers) && + DisplayNames.Equals(other.DisplayNames); public override int GetHashCode() { - return (MosaicConfig, HdrConfig, DisplayIdentifiers).GetHashCode(); + return (MosaicConfig, HdrConfig, DisplayIdentifiers, DisplayNames).GetHashCode(); } public static bool operator ==(NVIDIA_DISPLAY_CONFIG lhs, NVIDIA_DISPLAY_CONFIG rhs) => lhs.Equals(rhs); @@ -768,6 +789,129 @@ namespace DisplayMagicianShared.NVIDIA } } + + /*// Get the NVIDIA Windows Display Config too + // Figure out how many pathInfo objects there are + uint pathInfoCount = 0; + NVStatus = NVImport.NvAPI_DISP_GetDisplayConfig(ref pathInfoCount); + if (NVStatus == NVAPI_STATUS.NVAPI_OK) + { + SharedLogger.logger.Trace($"NVIDIALibrary/GetNVIDIADisplayConfig: NvAPI_DISP_GetDisplayConfig returned OK. We know we have {pathInfoCount} pathInfo objects to get"); + } + + // Now get the number of targetInfoCount for each returned pathInfoCount object + NV_DISPLAYCONFIG_PATH_INFO_V1[] pathInfos = new NV_DISPLAYCONFIG_PATH_INFO_V1[pathInfoCount]; + NVStatus = NVImport.NvAPI_DISP_GetDisplayConfig(ref pathInfoCount, ref pathInfos); + if (NVStatus == NVAPI_STATUS.NVAPI_OK) + { + SharedLogger.logger.Trace($"NVIDIALibrary/GetNVIDIADisplayConfig: NvAPI_DISP_GetDisplayConfig returned OK."); + } + else if (NVStatus == NVAPI_STATUS.NVAPI_NOT_SUPPORTED) + { + SharedLogger.logger.Warn($"NVIDIALibrary/GetNVIDIADisplayConfig: Mosaic is not supported with the existing hardware. NvAPI_DISP_GetDisplayConfig() returned error code {NVStatus}"); + } + else if (NVStatus == NVAPI_STATUS.NVAPI_INVALID_ARGUMENT) + { + SharedLogger.logger.Warn($"NVIDIALibrary/GetNVIDIADisplayConfig: One or more argumentss passed in are invalid. NvAPI_DISP_GetDisplayConfig() returned error code {NVStatus}"); + } + else if (NVStatus == NVAPI_STATUS.NVAPI_API_NOT_INITIALIZED) + { + SharedLogger.logger.Warn($"NVIDIALibrary/GetNVIDIADisplayConfig: The NvAPI API needs to be initialized first. NvAPI_DISP_GetDisplayConfig() returned error code {NVStatus}"); + } + else if (NVStatus == NVAPI_STATUS.NVAPI_NO_IMPLEMENTATION) + { + SharedLogger.logger.Warn($"NVIDIALibrary/GetNVIDIADisplayConfig: This entry point not available in this NVIDIA Driver. NvAPI_DISP_GetDisplayConfig() returned error code {NVStatus}"); + } + else if (NVStatus == NVAPI_STATUS.NVAPI_DEVICE_BUSY) + { + SharedLogger.logger.Warn($"NVIDIALibrary/GetNVIDIADisplayConfig: ModeSet has not yet completed. Please wait and call it again. NvAPI_DISP_GetDisplayConfig() returned error code {NVStatus}"); + } + else if (NVStatus == NVAPI_STATUS.NVAPI_ERROR) + { + SharedLogger.logger.Warn($"NVIDIALibrary/GetNVIDIADisplayConfig: A miscellaneous error occurred. NvAPI_DISP_GetDisplayConfig() returned error code {NVStatus}"); + } + else + { + SharedLogger.logger.Trace($"NVIDIALibrary/GetNVIDIADisplayConfig: Some non standard error occurred while getting Mosaic Topology! NvAPI_DISP_GetDisplayConfig() returned error code {NVStatus}"); + } + + // Now we send the same partially filled object back in a third time to get the target information + NVStatus = NVImport.NvAPI_DISP_GetDisplayConfig(ref pathInfoCount, ref pathInfos, true); + if (NVStatus == NVAPI_STATUS.NVAPI_OK) + { + SharedLogger.logger.Trace($"NVIDIALibrary/GetNVIDIADisplayConfig: NvAPI_DISP_GetDisplayConfig returned OK."); + } + else if (NVStatus == NVAPI_STATUS.NVAPI_NOT_SUPPORTED) + { + SharedLogger.logger.Warn($"NVIDIALibrary/GetNVIDIADisplayConfig: Mosaic is not supported with the existing hardware. NvAPI_DISP_GetDisplayConfig() returned error code {NVStatus}"); + } + else if (NVStatus == NVAPI_STATUS.NVAPI_INVALID_ARGUMENT) + { + SharedLogger.logger.Warn($"NVIDIALibrary/GetNVIDIADisplayConfig: One or more argumentss passed in are invalid. NvAPI_DISP_GetDisplayConfig() returned error code {NVStatus}"); + } + else if (NVStatus == NVAPI_STATUS.NVAPI_API_NOT_INITIALIZED) + { + SharedLogger.logger.Warn($"NVIDIALibrary/GetNVIDIADisplayConfig: The NvAPI API needs to be initialized first. NvAPI_DISP_GetDisplayConfig() returned error code {NVStatus}"); + } + else if (NVStatus == NVAPI_STATUS.NVAPI_NO_IMPLEMENTATION) + { + SharedLogger.logger.Warn($"NVIDIALibrary/GetNVIDIADisplayConfig: This entry point not available in this NVIDIA Driver. NvAPI_DISP_GetDisplayConfig() returned error code {NVStatus}"); + } + else if (NVStatus == NVAPI_STATUS.NVAPI_DEVICE_BUSY) + { + SharedLogger.logger.Warn($"NVIDIALibrary/GetNVIDIADisplayConfig: ModeSet has not yet completed. Please wait and call it again. NvAPI_DISP_GetDisplayConfig() returned error code {NVStatus}"); + } + else if (NVStatus == NVAPI_STATUS.NVAPI_ERROR) + { + SharedLogger.logger.Warn($"NVIDIALibrary/GetNVIDIADisplayConfig: A miscellaneous error occurred. NvAPI_DISP_GetDisplayConfig() returned error code {NVStatus}"); + } + else + { + SharedLogger.logger.Trace($"NVIDIALibrary/GetNVIDIADisplayConfig: Some non standard error occurred while getting Mosaic Topology! NvAPI_DISP_GetDisplayConfig() returned error code {NVStatus}"); + }*/ + + + // Now we need to loop through each of the windows paths so we can record the Windows DisplayName to DisplayID mapping + // This is needed for us to piece together the Screen layout for when we draw the NVIDIA screens! + myDisplayConfig.DisplayNames = new Dictionary(); + foreach (KeyValuePair displaySource in WinLibrary.GetDisplaySourceNames()) + { + // Now we try to get the information about the displayIDs and map them to windows \\DISPLAY names e.g. \\DISPLAY1 + string displayName = displaySource.Value; + UInt32 displayId = 0; + NVStatus = NVImport.NvAPI_DISP_GetDisplayIdByDisplayName(displayName, out displayId); + if (NVStatus == NVAPI_STATUS.NVAPI_OK) + { + SharedLogger.logger.Trace($"NVIDIALibrary/GetNVIDIADisplayConfig: NvAPI_DISP_GetDisplayIdByDisplayName returned OK. The display {displayName} has NVIDIA DisplayID {displayId}"); + myDisplayConfig.DisplayNames.Add(displayName, displayId); + } + else if (NVStatus == NVAPI_STATUS.NVAPI_NVIDIA_DEVICE_NOT_FOUND) + { + SharedLogger.logger.Warn($"NVIDIALibrary/GetNVIDIADisplayConfig: GDI Primary not on an NVIDIA GPU. NvAPI_DISP_GetDisplayIdByDisplayName() returned error code {NVStatus}"); + } + else if (NVStatus == NVAPI_STATUS.NVAPI_INVALID_ARGUMENT) + { + SharedLogger.logger.Warn($"NVIDIALibrary/GetNVIDIADisplayConfig: One or more args passed in are invalid. NvAPI_DISP_GetDisplayIdByDisplayName() returned error code {NVStatus}"); + } + else if (NVStatus == NVAPI_STATUS.NVAPI_API_NOT_INITIALIZED) + { + SharedLogger.logger.Warn($"NVIDIALibrary/GetNVIDIADisplayConfig: The NvAPI API needs to be initialized first. NvAPI_DISP_GetDisplayIdByDisplayName() returned error code {NVStatus}"); + } + else if (NVStatus == NVAPI_STATUS.NVAPI_NO_IMPLEMENTATION) + { + SharedLogger.logger.Warn($"NVIDIALibrary/GetNVIDIADisplayConfig: This entry point not available in this NVIDIA Driver. NvAPI_DISP_GetDisplayIdByDisplayName() returned error code {NVStatus}"); + } + else if (NVStatus == NVAPI_STATUS.NVAPI_ERROR) + { + SharedLogger.logger.Warn($"NVIDIALibrary/GetNVIDIADisplayConfig: A miscellaneous error occurred. NvAPI_DISP_GetDisplayIdByDisplayName() returned error code {NVStatus}"); + } + else + { + SharedLogger.logger.Trace($"NVIDIALibrary/GetNVIDIADisplayConfig: Some non standard error occurred while getting Mosaic Topology! NvAPI_DISP_GetDisplayIdByDisplayName() returned error code {NVStatus}"); + } + } + + // Get the display identifiers myDisplayConfig.DisplayIdentifiers = GetCurrentDisplayIdentifiers(); } else diff --git a/DisplayMagicianShared/Windows/WinLibrary.cs b/DisplayMagicianShared/Windows/WinLibrary.cs index 74dded0..173a744 100644 --- a/DisplayMagicianShared/Windows/WinLibrary.cs +++ b/DisplayMagicianShared/Windows/WinLibrary.cs @@ -41,6 +41,7 @@ namespace DisplayMagicianShared.Windows public DISPLAYCONFIG_PATH_INFO[] DisplayConfigPaths; public DISPLAYCONFIG_MODE_INFO[] DisplayConfigModes; public ADVANCED_HDR_INFO_PER_PATH[] DisplayHDRStates; + public Dictionary DisplaySources; public List DisplayIdentifiers; public override bool Equals(object obj) => obj is WINDOWS_DISPLAY_CONFIG other && this.Equals(other); @@ -278,6 +279,7 @@ namespace DisplayMagicianShared.Windows WINDOWS_DISPLAY_CONFIG windowsDisplayConfig = new WINDOWS_DISPLAY_CONFIG(); windowsDisplayConfig.DisplayAdapters = new Dictionary(); windowsDisplayConfig.DisplayHDRStates = new ADVANCED_HDR_INFO_PER_PATH[pathCount]; + windowsDisplayConfig.DisplaySources = new Dictionary(); // Now cycle through the paths and grab the HDR state information // and map the adapter name to adapter id @@ -285,6 +287,24 @@ namespace DisplayMagicianShared.Windows int hdrInfoCount = 0; foreach (var path in paths) { + // get display source name + var sourceInfo = new DISPLAYCONFIG_SOURCE_DEVICE_NAME(); + sourceInfo.Header.Type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME; + sourceInfo.Header.Size = (uint)Marshal.SizeOf(); + sourceInfo.Header.AdapterId = path.SourceInfo.AdapterId; + sourceInfo.Header.Id = path.SourceInfo.Id; + err = CCDImport.DisplayConfigGetDeviceInfo(ref sourceInfo); + if (err == WIN32STATUS.ERROR_SUCCESS) + { + // Store it for later + windowsDisplayConfig.DisplaySources.Add(path.SourceInfo.Id, sourceInfo.ViewGdiDeviceName); + SharedLogger.logger.Trace($"WinLibrary/GetWindowsDisplayConfig: Found Display Source {sourceInfo.ViewGdiDeviceName} for source {path.SourceInfo.Id}."); + } + else + { + SharedLogger.logger.Warn($"WinLibrary/PrintActiveConfig: WARNING - DisplayConfigGetDeviceInfo returned WIN32STATUS {err} when trying to get the source info for source adapter #{path.SourceInfo.AdapterId}"); + } + // Get adapter ID for later SharedLogger.logger.Trace($"WinLibrary/GetWindowsDisplayConfig: Attempting to get adapter name for adapter {path.TargetInfo.AdapterId.Value}."); if (!windowsDisplayConfig.DisplayAdapters.ContainsKey(path.TargetInfo.AdapterId.Value)) @@ -374,6 +394,88 @@ namespace DisplayMagicianShared.Windows return windowsDisplayConfig; } + public static Dictionary GetDisplaySourceNames() + { + // Get the size of the largest Active Paths and Modes arrays + SharedLogger.logger.Trace($"WinLibrary/GetWindowsDisplayConfig: Getting the size of the largest Active Paths and Modes arrays"); + int pathCount = 0; + int modeCount = 0; + WIN32STATUS err = CCDImport.GetDisplayConfigBufferSizes(QDC.QDC_ONLY_ACTIVE_PATHS, out pathCount, out modeCount); + if (err != WIN32STATUS.ERROR_SUCCESS) + { + SharedLogger.logger.Error($"WinLibrary/GetWindowsDisplayConfig: ERROR - GetDisplayConfigBufferSizes returned WIN32STATUS {err} when trying to get the maximum path and mode sizes"); + throw new WinLibraryException($"GetDisplayConfigBufferSizes returned WIN32STATUS {err} when trying to get the maximum path and mode sizes"); + } + + SharedLogger.logger.Trace($"WinLibrary/GetWindowsDisplayConfig: Getting the current Display Config path and mode arrays"); + var paths = new DISPLAYCONFIG_PATH_INFO[pathCount]; + var modes = new DISPLAYCONFIG_MODE_INFO[modeCount]; + err = CCDImport.QueryDisplayConfig(QDC.QDC_ONLY_ACTIVE_PATHS, ref pathCount, paths, ref modeCount, modes, IntPtr.Zero); + if (err == WIN32STATUS.ERROR_INSUFFICIENT_BUFFER) + { + SharedLogger.logger.Warn($"WinLibrary/GetWindowsDisplayConfig: The displays were modified between GetDisplayConfigBufferSizes and QueryDisplayConfig so we need to get the buffer sizes again."); + SharedLogger.logger.Trace($"WinLibrary/GetWindowsDisplayConfig: Getting the size of the largest Active Paths and Modes arrays"); + // Screen changed in between GetDisplayConfigBufferSizes and QueryDisplayConfig, so we need to get buffer sizes again + // as per https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-querydisplayconfig + err = CCDImport.GetDisplayConfigBufferSizes(QDC.QDC_ONLY_ACTIVE_PATHS, out pathCount, out modeCount); + if (err != WIN32STATUS.ERROR_SUCCESS) + { + SharedLogger.logger.Error($"WinLibrary/GetWindowsDisplayConfig: ERROR - GetDisplayConfigBufferSizes returned WIN32STATUS {err} when trying to get the maximum path and mode sizes again"); + throw new WinLibraryException($"GetDisplayConfigBufferSizes returned WIN32STATUS {err} when trying to get the maximum path and mode sizes again"); + } + SharedLogger.logger.Trace($"WinLibrary/GetWindowsDisplayConfig: Getting the current Display Config path and mode arrays"); + paths = new DISPLAYCONFIG_PATH_INFO[pathCount]; + modes = new DISPLAYCONFIG_MODE_INFO[modeCount]; + err = CCDImport.QueryDisplayConfig(QDC.QDC_ONLY_ACTIVE_PATHS, ref pathCount, paths, ref modeCount, modes, IntPtr.Zero); + if (err == WIN32STATUS.ERROR_INSUFFICIENT_BUFFER) + { + SharedLogger.logger.Error($"WinLibrary/GetWindowsDisplayConfig: ERROR - The displays were still modified between GetDisplayConfigBufferSizes and QueryDisplayConfig, even though we tried twice. Something is wrong."); + throw new WinLibraryException($"The displays were still modified between GetDisplayConfigBufferSizes and QueryDisplayConfig, even though we tried twice. Something is wrong."); + } + else if (err != WIN32STATUS.ERROR_SUCCESS) + { + SharedLogger.logger.Error($"WinLibrary/GetWindowsDisplayConfig: ERROR - QueryDisplayConfig returned WIN32STATUS {err} when trying to query all available displays again"); + throw new WinLibraryException($"QueryDisplayConfig returned WIN32STATUS {err} when trying to query all available displays again."); + } + } + else if (err != WIN32STATUS.ERROR_SUCCESS) + { + SharedLogger.logger.Error($"WinLibrary/GetWindowsDisplayConfig: ERROR - QueryDisplayConfig returned WIN32STATUS {err} when trying to query all available displays"); + throw new WinLibraryException($"QueryDisplayConfig returned WIN32STATUS {err} when trying to query all available displays."); + } + + // Prepare the empty DisplaySources dictionary + Dictionary DisplaySources = new Dictionary(); + + // Now cycle through the paths and grab the HDR state information + // and map the adapter name to adapter id + var hdrInfos = new ADVANCED_HDR_INFO_PER_PATH[pathCount]; + int hdrInfoCount = 0; + foreach (var path in paths) + { + // get display source name + var sourceInfo = new DISPLAYCONFIG_SOURCE_DEVICE_NAME(); + sourceInfo.Header.Type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME; + sourceInfo.Header.Size = (uint)Marshal.SizeOf(); + sourceInfo.Header.AdapterId = path.SourceInfo.AdapterId; + sourceInfo.Header.Id = path.SourceInfo.Id; + err = CCDImport.DisplayConfigGetDeviceInfo(ref sourceInfo); + if (err == WIN32STATUS.ERROR_SUCCESS) + { + // Store it for later + DisplaySources.Add(path.SourceInfo.Id, sourceInfo.ViewGdiDeviceName); + SharedLogger.logger.Trace($"WinLibrary/GetWindowsDisplayConfig: Found Display Source {sourceInfo.ViewGdiDeviceName} for source {path.SourceInfo.Id}."); + } + else + { + SharedLogger.logger.Warn($"WinLibrary/PrintActiveConfig: WARNING - DisplayConfigGetDeviceInfo returned WIN32STATUS {err} when trying to get the source info for source adapter #{path.SourceInfo.AdapterId}"); + } + + } + + return DisplaySources; + } + private LUID AdapterValueToLUID(ulong adapterValue) {