Пример #1
0
        /// <summary>
        ///     Reads EDID data of an output
        /// </summary>
        /// <param name="output">The GPU output to read EDID information for</param>
        /// <returns>A byte array containing EDID data</returns>
        public byte[] ReadEDIDData(GPUOutput output)
        {
            try
            {
                var data           = new byte[0];
                var identification = 0;
                var totalSize      = EDIDV3.MaxDataSize;
                for (var offset = 0; offset < totalSize; offset += EDIDV3.MaxDataSize)
                {
                    var edid = GPUApi.GetEDID(Handle, output.OutputId, offset, identification);
                    identification = edid.Identification;
                    totalSize      = edid.TotalSize;

                    var edidData = edid.Data;
                    Array.Resize(ref data, data.Length + edidData.Length);
                    Array.Copy(edidData, 0, data, data.Length - edidData.Length, edidData.Length);
                }
                return(data);
            }
            catch (NVIDIAApiException ex)
            {
                if (ex.Status == Status.IncompatibleStructureVersion)
                {
                    return(GPUApi.GetEDID(Handle, output.OutputId).Data);
                }
                throw;
            }
        }
        /// <summary>
        ///     Changes a cooler settings by modifying the policy and the current level
        /// </summary>
        /// <param name="coolerId">The cooler identification number (index) to change the settings.</param>
        /// <param name="policy">The new cooler policy.</param>
        /// <param name="newLevel">The new cooler level. Valid only if policy is set to manual.</param>
        // ReSharper disable once TooManyDeclarations
        public void SetCoolerSettings(int coolerId, CoolerPolicy policy, int newLevel)
        {
            if (Coolers.All(cooler => cooler.CoolerId != coolerId))
            {
                throw new ArgumentException("Invalid cooler identification number provided.", nameof(coolerId));
            }

            try
            {
                GPUApi.SetCoolerLevels(
                    PhysicalGPU.Handle,
                    (uint)coolerId,
                    new PrivateCoolerLevelsV1(new[]
                {
                    new PrivateCoolerLevelsV1.CoolerLevel(policy, (uint)newLevel)
                }
                                              ),
                    1
                    );

                return;
            }
            catch (NVIDIAApiException e)
            {
                if (e.Status != Status.NotSupported)
                {
                    throw;
                }
            }

            var currentControl = GPUApi.GetClientFanCoolersControl(PhysicalGPU.Handle);
            var newControl     = new PrivateFanCoolersControlV1(
                currentControl.FanCoolersControlEntries.Select(
                    entry => entry.CoolerId == coolerId
                            ? new PrivateFanCoolersControlV1.FanCoolersControlEntry(
                        entry.CoolerId,
                        policy == CoolerPolicy.Manual
                                    ? FanCoolersControlMode.Manual
                                    : FanCoolersControlMode.Auto,
                        policy == CoolerPolicy.Manual ? (uint)newLevel : 0u)
                            : entry
                    )
                .ToArray(),
                currentControl.UnknownUInt
                );

            GPUApi.SetClientFanCoolersControl(PhysicalGPU.Handle, newControl);
        }
Пример #3
0
 private void WriteEDIDData(uint displayOutputId, byte[] edidData)
 {
     try
     {
         for (var offset = 0; offset < edidData.Length; offset += EDIDV3.MaxDataSize)
         {
             var array = new byte[Math.Min(EDIDV3.MaxDataSize, edidData.Length - offset)];
             Array.Copy(edidData, offset, array, 0, array.Length);
             var instance = EDIDV3.CreateWithData(0, (uint)offset, array, edidData.Length);
             GPUApi.SetEDID(Handle, displayOutputId, instance);
         }
         return;
     }
     catch (NVIDIAApiException ex)
     {
         if (ex.Status != Status.IncompatibleStructureVersion)
         {
             throw;
         }
     }
     catch (NVIDIANotSupportedException)
     {
         // ignore
     }
     try
     {
         for (var offset = 0; offset < edidData.Length; offset += EDIDV2.MaxDataSize)
         {
             var array = new byte[Math.Min(EDIDV2.MaxDataSize, edidData.Length - offset)];
             Array.Copy(edidData, offset, array, 0, array.Length);
             GPUApi.SetEDID(Handle, displayOutputId, EDIDV2.CreateWithData(array, edidData.Length));
         }
         return;
     }
     catch (NVIDIAApiException ex)
     {
         if (ex.Status != Status.IncompatibleStructureVersion)
         {
             throw;
         }
     }
     catch (NVIDIANotSupportedException)
     {
         // ignore
     }
     GPUApi.SetEDID(Handle, displayOutputId, EDIDV1.CreateWithData(edidData));
 }
        /// <summary>
        ///     Resets one or more cooler settings to default.
        /// </summary>
        /// <param name="coolerIds">The cooler identification numbers (indexes) to reset their settings to default.</param>
        public void RestoreCoolerSettingsToDefault(params int[] coolerIds)
        {
            var availableCoolerIds = Coolers.Select(cooler => cooler.CoolerId).ToArray();

            if (coolerIds.Any(i => !availableCoolerIds.Contains(i)))
            {
                throw new ArgumentException("Invalid cooler identification number provided.", nameof(coolerIds));
            }

            try
            {
                GPUApi.RestoreCoolerSettings(PhysicalGPU.Handle, coolerIds.Select(i => (uint)i).ToArray());

                return;
            }
            catch (NVIDIAApiException e)
            {
                if (e.Status != Status.NotSupported)
                {
                    throw;
                }
            }

            var currentControl = GPUApi.GetClientFanCoolersControl(PhysicalGPU.Handle);
            var newControl     = new PrivateFanCoolersControlV1(
                currentControl.FanCoolersControlEntries.Select(
                    entry => coolerIds.Contains((int)entry.CoolerId)
                            ? new PrivateFanCoolersControlV1.FanCoolersControlEntry(
                        entry.CoolerId,
                        FanCoolersControlMode.Auto
                        )
                            : entry
                    )
                .ToArray(),
                currentControl.UnknownUInt
                );

            GPUApi.SetClientFanCoolersControl(PhysicalGPU.Handle, newControl);
        }
Пример #5
0
        public override void Update()
        {
            NvGPUThermalSettings settings = GetThermalSettings();

            foreach (Sensor sensor in temperatures)
            {
                sensor.Value = settings.Sensor[sensor.Index].CurrentTemp;
            }

            bool tachReadingOk = false;

            if (NVAPI.NvAPI_GPU_GetTachReading != null &&
                NVAPI.NvAPI_GPU_GetTachReading(handle, out int fanValue) == NvStatus.OK)
            {
                fan.Value = fanValue;
                ActivateSensor(fan);
                tachReadingOk = true;
            }

            uint[] values = GetClocks();
            if (values != null)
            {
                clocks[1].Value = 0.001f * values[8];
                if (values[30] != 0)
                {
                    clocks[0].Value = 0.0005f * values[30];
                    clocks[2].Value = 0.001f * values[30];
                }
                else
                {
                    clocks[0].Value = 0.001f * values[0];
                    clocks[2].Value = 0.001f * values[14];
                }
            }

            // set extra sensors from external NvAPI wrapper
            if (physicalGPU != null)
            {
                try
                {
                    voltage.Value = GPUApi.GetCurrentVoltage(physicalGPU.Handle).ValueInMicroVolt / 1E06f;
                    ActivateSensor(voltage);

                    var currentActiveLimit = physicalGPU.PerformanceControl.CurrentActiveLimit;
                    powerLimit.Value = ((currentActiveLimit & PerformanceLimit.PowerLimit)
                                        == PerformanceLimit.PowerLimit) ? 1 : 0;
                    ActivateSensor(powerLimit);
                    temperatureLimit.Value = ((currentActiveLimit & PerformanceLimit.TemperatureLimit)
                                              == PerformanceLimit.TemperatureLimit) ? 1 : 0;
                    ActivateSensor(temperatureLimit);
                    voltageLimit.Value = ((currentActiveLimit & PerformanceLimit.VoltageLimit)
                                          == PerformanceLimit.VoltageLimit) ? 1 : 0;
                    ActivateSensor(voltageLimit);
                }
                catch
                {
                    voltage.Value          = float.NaN;
                    powerLimit.Value       = float.NaN;
                    temperatureLimit.Value = float.NaN;
                    voltageLimit.Value     = float.NaN;
                }
            }

            var infoEx = new NvDynamicPstatesInfoEx();

            infoEx.Version            = NVAPI.GPU_DYNAMIC_PSTATES_INFO_EX_VER;
            infoEx.UtilizationDomains =
                new NvUtilizationDomainEx[NVAPI.NVAPI_MAX_GPU_UTILIZATIONS];
            if (NVAPI.NvAPI_GPU_GetDynamicPstatesInfoEx != null &&
                NVAPI.NvAPI_GPU_GetDynamicPstatesInfoEx(handle, ref infoEx) == NvStatus.OK)
            {
                for (int i = 0; i < loads.Length; i++)
                {
                    if (infoEx.UtilizationDomains[i].Present)
                    {
                        loads[i].Value = infoEx.UtilizationDomains[i].Percentage;
                        ActivateSensor(loads[i]);
                    }
                }
            }
            else
            {
                var info = new NvDynamicPstatesInfo
                {
                    Version            = NVAPI.GPU_DYNAMIC_PSTATES_INFO_VER,
                    UtilizationDomains =
                        new NvUtilizationDomain[NVAPI.NVAPI_MAX_GPU_UTILIZATIONS]
                };

                if (NVAPI.NvAPI_GPU_GetDynamicPstatesInfo != null &&
                    NVAPI.NvAPI_GPU_GetDynamicPstatesInfo(handle, ref info) == NvStatus.OK)
                {
                    for (int i = 0; i < loads.Length; i++)
                    {
                        if (info.UtilizationDomains[i].Present)
                        {
                            loads[i].Value = info.UtilizationDomains[i].Percentage;
                            ActivateSensor(loads[i]);
                        }
                    }
                }
            }

            var coolerSettings   = GetCoolerSettings();
            var coolerSettingsOk = false;

            if (coolerSettings.Count > 0)
            {
                control.Value = coolerSettings.Cooler[0].CurrentLevel;
                ActivateSensor(control);
                coolerSettingsOk = true;
            }

            if (!tachReadingOk || !coolerSettingsOk)
            {
                var coolersStatus = GetFanCoolersStatus();
                if (coolersStatus.Count > 0)
                {
                    if (!coolerSettingsOk)
                    {
                        control.Value = coolersStatus.Items[0].CurrentLevel;
                        ActivateSensor(control);
                        coolerSettingsOk = true;
                    }
                    if (!tachReadingOk)
                    {
                        fan.Value = coolersStatus.Items[0].CurrentRpm;
                        ActivateSensor(fan);
                        tachReadingOk = true;
                    }
                }
            }

            NvDisplayDriverMemoryInfo memoryInfo = new NvDisplayDriverMemoryInfo
            {
                Version = NVAPI.DISPLAY_DRIVER_MEMORY_INFO_VER,
                Values  = new uint[NVAPI.MAX_MEMORY_VALUES_PER_GPU]
            };

            if (NVAPI.NvAPI_GetDisplayDriverMemoryInfo != null && displayHandle.HasValue &&
                NVAPI.NvAPI_GetDisplayDriverMemoryInfo(displayHandle.Value, ref memoryInfo) ==
                NvStatus.OK)
            {
                uint  totalMemory = memoryInfo.Values[0];
                uint  freeMemory  = memoryInfo.Values[4];
                float usedMemory  = Math.Max(totalMemory - freeMemory, 0);
                memoryFree.Value  = (float)freeMemory / 1024;
                memoryAvail.Value = (float)totalMemory / 1024;
                memoryUsed.Value  = usedMemory / 1024;
                memoryLoad.Value  = 100f * usedMemory / totalMemory;
                ActivateSensor(memoryAvail);
                ActivateSensor(memoryUsed);
                ActivateSensor(memoryFree);
                ActivateSensor(memoryLoad);
            }

            if (power != null)
            {
                if (NVML.NvmlDeviceGetPowerUsage(device.Value, out int powerValue)
                    == NVML.NvmlReturn.Success)
                {
                    power.Value = powerValue * 0.001f;
                    ActivateSensor(power);
                }
            }

            if (pcieThroughputRx != null)
            {
                if (NVML.NvmlDeviceGetPcieThroughput(device.Value,
                                                     NVML.NvmlPcieUtilCounter.RxBytes, out uint value)
                    == NVML.NvmlReturn.Success)
                {
                    pcieThroughputRx.Value = value * (1.0f / 0x400);
                    ActivateSensor(pcieThroughputRx);
                }
            }

            if (pcieThroughputTx != null)
            {
                if (NVML.NvmlDeviceGetPcieThroughput(device.Value,
                                                     NVML.NvmlPcieUtilCounter.TxBytes, out uint value)
                    == NVML.NvmlReturn.Success)
                {
                    pcieThroughputTx.Value = value * (1.0f / 0x400);
                    ActivateSensor(pcieThroughputTx);
                }
            }
        }
Пример #6
0
        /// <summary>
        ///     Validates a set of GPU outputs to check if they can be active simultaneously
        /// </summary>
        /// <param name="outputs">GPU outputs to check</param>
        /// <returns>true if all specified outputs can be active simultaneously, otherwise false</returns>
        public bool ValidateOutputCombination(GPUOutput[] outputs)
        {
            var gpuOutpudIds = outputs.Aggregate(OutputId.Invalid, (current, gpuOutput) => current | gpuOutput.OutputId);

            return(GPUApi.ValidateOutputCombination(Handle, gpuOutpudIds));
        }
Пример #7
0
 /// <summary>
 ///     Get a list of all display devices on any possible output
 /// </summary>
 /// <returns>An array of display devices</returns>
 public DisplayDevice[] GetDisplayDevices()
 {
     return(GPUApi.GetAllDisplayIds(Handle).Select(display => new DisplayDevice(display)).ToArray());
 }
Пример #8
0
 /// <summary>
 ///     Get the display device connected to a specific GPU output
 /// </summary>
 /// <param name="output">The GPU output to get connected display device for</param>
 /// <returns>DisplayDevice connected to the specified GPU output</returns>
 public DisplayDevice GetDisplayDeviceByOutput(GPUOutput output)
 {
     return(new DisplayDevice(GPUApi.GetDisplayIdFromGPUAndOutputId(Handle, output.OutputId)));
 }
Пример #9
0
 /// <summary>
 ///     Get a list of all connected display devices on this GPU
 /// </summary>
 /// <param name="flags">ConnectedIdsFlag flag</param>
 /// <returns>An array of display devices</returns>
 public DisplayDevice[] GetConnectedDisplayDevices(ConnectedIdsFlag flags)
 {
     return(GPUApi.GetConnectedDisplayIds(Handle, flags).Select(display => new DisplayDevice(display)).ToArray());
 }
Пример #10
0
 /// <summary>
 ///     Gets all physical GPUs in TCC state
 /// </summary>
 /// <returns>An array of physical GPUs</returns>
 public static PhysicalGPU[] GetTCCPhysicalGPUs()
 {
     return(GPUApi.EnumTCCPhysicalGPUs().Select(handle => new PhysicalGPU(handle)).ToArray());
 }
Пример #11
0
 private int GetCurrentPCIeLanes()
 {
     return(GPUApi.GetCurrentPCIEDownStreamWidth(PhysicalGPU.GetPhysicalGPUs()[0].Handle));
 }
Пример #12
0
 internal GPUOutput(OutputId outputId, PhysicalGPUHandle gpuHandle)
 {
     OutputId    = outputId;
     OutputType  = !gpuHandle.IsNull ? GPUApi.GetOutputType(gpuHandle, outputId) : OutputType.Unknown;
     PhysicalGPU = new PhysicalGPU(gpuHandle);
 }
Пример #13
0
 /// <summary>
 ///     Gets all logical GPUs
 /// </summary>
 /// <returns>An array of logical GPUs</returns>
 public static LogicalGPU[] GetLogicalGPUs()
 {
     return(GPUApi.EnumLogicalGPUs().Select(handle => new LogicalGPU(handle)).ToArray());
 }
Пример #14
0
        public override void Update()
        {
            NvGPUThermalSettings settings = GetThermalSettings();

            foreach (Sensor sensor in temperatures)
            {
                sensor.Value = settings.Sensor[sensor.Index].CurrentTemp;
            }

            bool tachReadingOk = false;

            if (NVAPI.NvAPI_GPU_GetTachReading != null &&
                NVAPI.NvAPI_GPU_GetTachReading(handle, out int fanValue) == NvStatus.OK)
            {
                fan.Value = fanValue;
                ActivateSensor(fan);
                tachReadingOk = true;
            }

            uint[] values = GetClocks();
            if (values != null)
            {
                clocks[1].Value = 0.001f * values[8];
                if (values[30] != 0)
                {
                    clocks[0].Value = 0.0005f * values[30];
                    clocks[2].Value = 0.001f * values[30];
                }
                else
                {
                    clocks[0].Value = 0.001f * values[0];
                    clocks[2].Value = 0.001f * values[14];
                }
            }

            // set extra sensors from external NvAPI wrapper
            if (physicalGPU != null)
            {
                try
                {
                    voltage.Value = GPUApi.GetCurrentVoltage(physicalGPU.Handle).ValueInMicroVolt / 1E06f;
                    ActivateSensor(voltage);

                    var currentActiveLimit = physicalGPU.PerformanceControl.CurrentActiveLimit;
                    powerLimit.Value = ((currentActiveLimit & PerformanceLimit.PowerLimit)
                                        == PerformanceLimit.PowerLimit) ? 1 : 0;
                    ActivateSensor(powerLimit);
                    temperatureLimit.Value = ((currentActiveLimit & PerformanceLimit.TemperatureLimit)
                                              == PerformanceLimit.TemperatureLimit) ? 1 : 0;
                    ActivateSensor(temperatureLimit);
                    voltageLimit.Value = ((currentActiveLimit & PerformanceLimit.VoltageLimit)
                                          == PerformanceLimit.VoltageLimit) ? 1 : 0;
                    ActivateSensor(voltageLimit);
                }
                catch
                {
                    voltage.Value          = float.NaN;
                    powerLimit.Value       = float.NaN;
                    temperatureLimit.Value = float.NaN;
                    voltageLimit.Value     = float.NaN;
                }
            }

            var infoEx = new NvDynamicPstatesInfoEx();

            infoEx.Version            = NVAPI.GPU_DYNAMIC_PSTATES_INFO_EX_VER;
            infoEx.UtilizationDomains =
                new NvUtilizationDomainEx[NVAPI.NVAPI_MAX_GPU_UTILIZATIONS];
            if (NVAPI.NvAPI_GPU_GetDynamicPstatesInfoEx != null &&
                NVAPI.NvAPI_GPU_GetDynamicPstatesInfoEx(handle, ref infoEx) == NvStatus.OK)
            {
                for (int i = 0; i < loads.Length; i++)
                {
                    if (infoEx.UtilizationDomains[i].Present)
                    {
                        loads[i].Value = infoEx.UtilizationDomains[i].Percentage;
                        ActivateSensor(loads[i]);
                    }
                }
            }
            else
            {
                var info = new NvDynamicPstatesInfo
                {
                    Version            = NVAPI.GPU_DYNAMIC_PSTATES_INFO_VER,
                    UtilizationDomains =
                        new NvUtilizationDomain[NVAPI.NVAPI_MAX_GPU_UTILIZATIONS]
                };

                if (NVAPI.NvAPI_GPU_GetDynamicPstatesInfo != null &&
                    NVAPI.NvAPI_GPU_GetDynamicPstatesInfo(handle, ref info) == NvStatus.OK)
                {
                    for (int i = 0; i < loads.Length; i++)
                    {
                        if (info.UtilizationDomains[i].Present)
                        {
                            loads[i].Value = info.UtilizationDomains[i].Percentage;
                            ActivateSensor(loads[i]);
                        }
                    }
                }
            }

            var coolerSettings   = GetCoolerSettings();
            var coolerSettingsOk = false;

            if (coolerSettings.Count > 0)
            {
                control.Value = coolerSettings.Cooler[0].CurrentLevel;
                ActivateSensor(control);
                coolerSettingsOk = true;
            }

            if (!tachReadingOk || !coolerSettingsOk)
            {
                var coolersStatus = GetFanCoolersStatus();
                if (coolersStatus.Count > 0)
                {
                    if (!coolerSettingsOk)
                    {
                        control.Value = coolersStatus.Items[0].CurrentLevel;
                        ActivateSensor(control);
                        coolerSettingsOk = true;
                    }
                    if (!tachReadingOk)
                    {
                        fan.Value = coolersStatus.Items[0].CurrentRpm;
                        ActivateSensor(fan);
                        tachReadingOk = true;
                    }
                }
            }

            NvDisplayDriverMemoryInfo memoryInfo = new NvDisplayDriverMemoryInfo
            {
                Version = NVAPI.DISPLAY_DRIVER_MEMORY_INFO_VER,
                Values  = new uint[NVAPI.MAX_MEMORY_VALUES_PER_GPU]
            };

            if (NVAPI.NvAPI_GetDisplayDriverMemoryInfo != null && displayHandle.HasValue &&
                NVAPI.NvAPI_GetDisplayDriverMemoryInfo(displayHandle.Value, ref memoryInfo) ==
                NvStatus.OK)
            {
                uint  totalMemory = memoryInfo.Values[0];
                uint  freeMemory  = memoryInfo.Values[4];
                float usedMemory  = Math.Max(totalMemory - freeMemory, 0);
                memoryFree.Value  = (float)freeMemory / 1024;
                memoryAvail.Value = (float)totalMemory / 1024;
                memoryUsed.Value  = usedMemory / 1024;
                memoryLoad.Value  = 100f * usedMemory / totalMemory;
                ActivateSensor(memoryAvail);
                ActivateSensor(memoryUsed);
                ActivateSensor(memoryFree);
                ActivateSensor(memoryLoad);
            }

            if (power != null)
            {
                var channels = new NvGpuPowerMonitorPowerChannelStatus[NVAPI.POWER_STATUS_CHANNEL_COUNT];
                for (int i = 0; i < channels.Length; i++)
                {
                    channels[i].Rsvd = new byte[NVAPI.POWER_STATUS_RSVD_SIZE];
                }

                var powerStatus = new NvGpuPowerStatus
                {
                    Version  = NVAPI.GPU_POWER_MONITOR_STATUS_VER,
                    Rsvd     = new byte[NVAPI.POWER_STATUS_RSVD_SIZE],
                    Channels = channels
                };

                if (NVAPI.NvAPI_GPU_PowerMonitorGetStatus != null &&
                    NVAPI.NvAPI_GPU_PowerMonitorGetStatus(handle, ref powerStatus) == NvStatus.OK)
                {
                    power.Value = powerStatus.TotalGpuPowermW * 1E-03f;
                    ActivateSensor(power);

                    //// Grap all other sensors/channels
                    //var powerSensors = powerStatus.Channels.Where(ch => ch.PwrAvgmW != 0).Select(ch => ch.PwrAvgmW * 1E-03).ToArray();

                    //for (int i = 0; i < powerSensors.Length; i++)
                    //{
                    //    Console.WriteLine($"Sensor {i}: {powerSensors[i]}W");
                    //}
                    //Console.WriteLine("------------------------------------------");
                }
            }

            // update VRAM usage
            if (dedicatedVramUsagePerformCounter != null)
            {
                try
                {
                    memoryUsageDedicated.Value = dedicatedVramUsagePerformCounter.NextValue() / 1024f / 1024f;
                    ActivateSensor(memoryUsageDedicated);
                }
                catch { }
            }

            if (sharedVramUsagePerformCounter != null)
            {
                try
                {
                    memoryUsageShared.Value = (float)sharedVramUsagePerformCounter.NextValue() / 1024f / 1024f;
                    ActivateSensor(memoryUsageShared);
                }
                catch { }
            }

            if (pcieThroughputRx != null)
            {
                if (NVML.NvmlDeviceGetPcieThroughput(device.Value,
                                                     NVML.NvmlPcieUtilCounter.RxBytes, out uint value)
                    == NVML.NvmlReturn.Success)
                {
                    pcieThroughputRx.Value = value / 1024f;
                    ActivateSensor(pcieThroughputRx);
                }
            }

            if (pcieThroughputTx != null)
            {
                if (NVML.NvmlDeviceGetPcieThroughput(device.Value,
                                                     NVML.NvmlPcieUtilCounter.TxBytes, out uint value)
                    == NVML.NvmlReturn.Success)
                {
                    pcieThroughputTx.Value = value / 1024f;
                    ActivateSensor(pcieThroughputTx);
                }
            }
        }
Пример #15
0
        public NvidiaGroup(ISettings settings)
        {
            if (!NVAPI.IsAvailable)
            {
                return;
            }

            report.AppendLine("NVAPI");
            report.AppendLine();

            if (NVAPI.NvAPI_GetInterfaceVersionString(out string version) == NvStatus.OK)
            {
                report.Append(" Version: ");
                report.AppendLine(version);
            }

            NvPhysicalGpuHandle[] handles =
                new NvPhysicalGpuHandle[NVAPI.MAX_PHYSICAL_GPUS];
            int count;

            if (NVAPI.NvAPI_EnumPhysicalGPUs == null)
            {
                report.AppendLine(" Error: NvAPI_EnumPhysicalGPUs not available");
                report.AppendLine();
                return;
            }
            else
            {
                NvStatus status = NVAPI.NvAPI_EnumPhysicalGPUs(handles, out count);
                if (status != NvStatus.OK)
                {
                    report.AppendLine(" Status: " + status);
                    report.AppendLine();
                    return;
                }
            }

            var result = NVML.NvmlInit();

            report.AppendLine();
            report.AppendLine("NVML");
            report.AppendLine();
            report.AppendLine(" Status: " + result);
            report.AppendLine();

            IDictionary <NvPhysicalGpuHandle, NvDisplayHandle> displayHandles =
                new Dictionary <NvPhysicalGpuHandle, NvDisplayHandle>();

            if (NVAPI.NvAPI_EnumNvidiaDisplayHandle != null &&
                NVAPI.NvAPI_GetPhysicalGPUsFromDisplay != null)
            {
                NvStatus status = NvStatus.OK;
                int      i      = 0;
                while (status == NvStatus.OK)
                {
                    NvDisplayHandle displayHandle = new NvDisplayHandle();
                    status = NVAPI.NvAPI_EnumNvidiaDisplayHandle(i, ref displayHandle);
                    i++;

                    if (status == NvStatus.OK)
                    {
                        NvPhysicalGpuHandle[] handlesFromDisplay =
                            new NvPhysicalGpuHandle[NVAPI.MAX_PHYSICAL_GPUS];
                        uint countFromDisplay;
                        if (NVAPI.NvAPI_GetPhysicalGPUsFromDisplay(displayHandle,
                                                                   handlesFromDisplay, out countFromDisplay) == NvStatus.OK)
                        {
                            for (int j = 0; j < countFromDisplay; j++)
                            {
                                if (!displayHandles.ContainsKey(handlesFromDisplay[j]))
                                {
                                    displayHandles.Add(handlesFromDisplay[j], displayHandle);
                                }
                            }
                        }
                    }
                }
            }

            report.Append("Number of GPUs: ");
            report.AppendLine(count.ToString(CultureInfo.InvariantCulture));

            // API wrapper calls
            PhysicalGPUHandle[] physicalGPUHandles = null;
            try
            {
                physicalGPUHandles = GPUApi.EnumPhysicalGPUs();
            }
            catch { }

            for (int i = 0; i < count; i++)
            {
                displayHandles.TryGetValue(handles[i], out NvDisplayHandle displayHandle);
                if (physicalGPUHandles == null || !physicalGPUHandles.Any())
                {
                    hardware.Add(new NvidiaGPU(i, handles[i], displayHandle, settings));
                }
                else
                {
                    hardware.Add(new NvidiaGPU(i, handles[i], displayHandle, settings,
                                               new NvAPIWrapper.GPU.PhysicalGPU(physicalGPUHandles[i])));
                }
            }

            report.AppendLine();
        }