|
3 | 3 | #include <wrl/client.h> // For ComPtr |
4 | 4 | #include <dxgi.h> // For IDXGIAdapter, IDXGIFactory1 |
5 | 5 | #include <algorithm> // For sort |
| 6 | +#include <setupapi.h> |
| 7 | +#include <devguid.h> |
| 8 | +#include <devpkey.h> |
| 9 | +#include <cstdint> |
| 10 | +#include <optional> |
| 11 | +#include <string> |
| 12 | +#include <vector> |
| 13 | +#include <fstream> |
6 | 14 |
|
7 | 15 | using namespace std; |
8 | 16 | using namespace Microsoft::WRL; |
@@ -49,6 +57,68 @@ vector<GPUInfo> getAvailableGPUs() { |
49 | 57 | return gpus; |
50 | 58 | } |
51 | 59 |
|
| 60 | +// Resolve an adapter LUID from a PCI bus number by enumerating display devices (SetupAPI). |
| 61 | +// Returns nullopt if no match is found or if the system doesn't expose the LUID property. |
| 62 | +inline std::optional<LUID> ResolveAdapterLuidFromPciBus(uint32_t targetBusIndex) { |
| 63 | + HDEVINFO devInfo = SetupDiGetClassDevsW(&GUID_DEVCLASS_DISPLAY, nullptr, nullptr, DIGCF_PRESENT); |
| 64 | + if (devInfo == INVALID_HANDLE_VALUE) { |
| 65 | + return std::nullopt; |
| 66 | + } |
| 67 | + |
| 68 | + SP_DEVINFO_DATA devData = {}; |
| 69 | + devData.cbSize = sizeof(devData); |
| 70 | + |
| 71 | + std::optional<LUID> result = std::nullopt; |
| 72 | + |
| 73 | + for (DWORD i = 0; SetupDiEnumDeviceInfo(devInfo, i, &devData); ++i) { |
| 74 | + DWORD currentBus = 0; |
| 75 | + if (!SetupDiGetDeviceRegistryPropertyW( |
| 76 | + devInfo, |
| 77 | + &devData, |
| 78 | + SPDRP_BUSNUMBER, |
| 79 | + nullptr, |
| 80 | + reinterpret_cast<PBYTE>(¤tBus), |
| 81 | + sizeof(currentBus), |
| 82 | + nullptr)) { |
| 83 | + continue; |
| 84 | + } |
| 85 | + |
| 86 | + if (static_cast<uint32_t>(currentBus) != targetBusIndex) { |
| 87 | + continue; |
| 88 | + } |
| 89 | + |
| 90 | + // DEVPKEY_Device_Luid is exposed as a UINT64 on Windows; convert into LUID. |
| 91 | + DEVPROPTYPE propType = 0; |
| 92 | + ULONG propSize = 0; |
| 93 | + ULONGLONG luid64 = 0; |
| 94 | + |
| 95 | + if (!SetupDiGetDevicePropertyW( |
| 96 | + devInfo, |
| 97 | + &devData, |
| 98 | + &DEVPKEY_Device_Luid, |
| 99 | + &propType, |
| 100 | + reinterpret_cast<PBYTE>(&luid64), |
| 101 | + sizeof(luid64), |
| 102 | + &propSize, |
| 103 | + 0)) { |
| 104 | + continue; |
| 105 | + } |
| 106 | + |
| 107 | + if (propType != DEVPROP_TYPE_UINT64 || propSize != sizeof(luid64)) { |
| 108 | + continue; |
| 109 | + } |
| 110 | + |
| 111 | + LUID luid{}; |
| 112 | + luid.LowPart = static_cast<DWORD>(luid64 & 0xFFFFFFFFull); |
| 113 | + luid.HighPart = static_cast<LONG>((luid64 >> 32) & 0xFFFFFFFFull); |
| 114 | + result = luid; |
| 115 | + break; |
| 116 | + } |
| 117 | + |
| 118 | + SetupDiDestroyDeviceInfoList(devInfo); |
| 119 | + return result; |
| 120 | +} |
| 121 | + |
52 | 122 | class AdapterOption { |
53 | 123 | public: |
54 | 124 | bool hasTargetAdapter{}; // Indicates if a target adapter is selected |
@@ -110,8 +180,32 @@ class AdapterOption { |
110 | 180 | } |
111 | 181 |
|
112 | 182 | private: |
113 | | - // Find and set the adapter by its name |
114 | | - bool findAndSetAdapter(const wstring& adapterName) { |
| 183 | + // Find and set the adapter by name, optionally using "name,bus" where bus is the PCI bus number. |
| 184 | + bool findAndSetAdapter(const wstring& adapterSpec) { |
| 185 | + // If user provides "name,bus", use bus to resolve LUID (more deterministic on multi-GPU setups). |
| 186 | + const size_t comma = adapterSpec.find(L','); |
| 187 | + if (comma != wstring::npos) { |
| 188 | + const wstring namePart = adapterSpec.substr(0, comma); |
| 189 | + wstring busPart = adapterSpec.substr(comma + 1); |
| 190 | + // Trim whitespace in bus part |
| 191 | + busPart.erase(remove_if(busPart.begin(), busPart.end(), iswspace), busPart.end()); |
| 192 | + |
| 193 | + wchar_t* end = nullptr; |
| 194 | + const unsigned long busUl = wcstoul(busPart.c_str(), &end, 10); |
| 195 | + const bool parsedOk = (end != nullptr) && (*end == L'\0') && (end != busPart.c_str()); |
| 196 | + if (parsedOk && busUl <= 0xFFFFFFFFul) { |
| 197 | + if (auto luidOpt = ResolveAdapterLuidFromPciBus(static_cast<uint32_t>(busUl)); luidOpt.has_value()) { |
| 198 | + adapterLuid = luidOpt.value(); |
| 199 | + hasTargetAdapter = true; |
| 200 | + return true; |
| 201 | + } |
| 202 | + } |
| 203 | + |
| 204 | + // Fall through to name matching using the name portion. |
| 205 | + return findAndSetAdapter(namePart); |
| 206 | + } |
| 207 | + |
| 208 | + const wstring& adapterName = adapterSpec; |
115 | 209 | auto gpus = getAvailableGPUs(); |
116 | 210 |
|
117 | 211 | // Iterate through all available GPUs |
|
0 commit comments