Bicameral Engine
GPU-resident engine for a magic-research open-world game
Loading...
Searching...
No Matches
caps.cpp
Go to the documentation of this file.
1// caps.cpp — GPU の対応状況を調べてログに出す(T-0001 の到達点。T-0007 で printf からログへ)。
2// Work Graphs / Mesh Shader / DXR / SM6.8 がこの PC で使えるかを、エンジン開発の最初に確定させる。
3// T-0013: シミュが頼る 64bit 整数演算・64bit atomic と、WARP(ソフトウェアの D3D12。CI で GPU テストを回せるか)も表示する。
4//
5// 注意: OS 標準の D3D12 ランタイムで問い合わせている。古い OS では Agility SDK(1.613 以降)を
6// 読み込まないと Work Graphs が NOT_SUPPORTED になる。Windows 11 build 26200 + RTX 3070 Ti では
7// OS 標準のランタイムで WorkGraphsTier 1.0 を確認済み(2026-09-29)。
8#include "platform/caps.h"
9
10#include "core/aliases.h"
11#include "core/hresult.h"
12#include "core/log.h"
13#include "core/unicode.h"
14#include "gpu/com_ptr.h"
15
16namespace bicameral {
17 namespace {
18
19 const char* WorkGraphsTierName(D3D12_WORK_GRAPHS_TIER tier) {
20 switch (tier) {
21 case D3D12_WORK_GRAPHS_TIER_NOT_SUPPORTED: return "NOT_SUPPORTED";
22 case D3D12_WORK_GRAPHS_TIER_1_0: return "1.0";
23 default: return "(newer)";
24 }
25 }
26
27 // D3D_SHADER_MODEL_6_8 = 0x68 → "6.8"
28 std::string ShaderModelName(D3D_SHADER_MODEL shaderModel) {
29 const int value = static_cast<int>(shaderModel);
30 return std::format("{}.{}", (value >> 4) & 0xF, value & 0xF);
31 }
32
33 // RaytracingTier・MeshShaderTier は 10 倍表記(TIER_1_1 = 11)
34 std::string TierName(int tier) {
35 return std::format("{}.{}", tier / 10, tier % 10);
36 }
37
38 // どの D3D12Core.dll が読み込まれたかを表示する。
39 // exe 横の D3D12\ から読まれていれば Agility SDK、System32 からなら OS 標準のランタイム。
40 // D3D12Core.dll はデバイスを作った後でないと読み込まれていないので、その後に呼ぶ。
42 HMODULE core = GetModuleHandleW(L"D3D12Core.dll");
43 if (core == nullptr) {
44 Log(Channel::Platform, Level::Warning, "D3D12Core : 読み込まれていない");
45 return;
46 }
47
48 wchar_t path[MAX_PATH] = {};
49 GetModuleFileNameW(core, path, MAX_PATH);
50 Log(Channel::Platform, Level::Info, "D3D12Core : {} (D3D12SDKVersion {})", ToUtf8(path),
51 D3D12_SDK_VERSION);
52 }
53
54 const char* YesNo(BOOL value) {
55 return value ? "yes" : "no";
56 }
57
58 // シミュは 64bit の整数で計算し(04 §2)、保存量の足し合わせに 64bit atomic を使う(R4)。
59 // Int64ShaderOps と、64bit atomic のうち任意の機能(typed・groupshared・ヒープの記述子経由)を表示する。
60 // raw / structured バッファへの 64bit atomic は SM 6.6 以上で必須なので問い合わせる項目が無い(実際の動作は gpu_work_graph_test で確かめる)
61 void ReportIntegerFeatures(ID3D12Device* device) {
62 D3D12_FEATURE_DATA_D3D12_OPTIONS1 options1{};
63 if (SUCCEEDED(device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS1, &options1, sizeof(options1))))
64 Log(Channel::Platform, Level::Info, " Int64ShaderOps : {}", YesNo(options1.Int64ShaderOps));
65
66 D3D12_FEATURE_DATA_D3D12_OPTIONS9 options9{};
67 if (SUCCEEDED(device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS9, &options9, sizeof(options9)))) {
68 Log(Channel::Platform, Level::Info, " Atomic64 typed : {} groupshared: {}",
69 YesNo(options9.AtomicInt64OnTypedResourceSupported),
70 YesNo(options9.AtomicInt64OnGroupSharedSupported));
71 }
72
73 D3D12_FEATURE_DATA_D3D12_OPTIONS11 options11{};
74 if (SUCCEEDED(device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS11, &options11, sizeof(options11)))) {
75 Log(Channel::Platform, Level::Info, " Atomic64 heap : {}",
76 YesNo(options11.AtomicInt64OnDescriptorHeapResourceSupported));
77 }
78 }
79
80 void ReportDevice(ID3D12Device* device) {
81 D3D12_FEATURE_DATA_SHADER_MODEL shaderModel{D3D_SHADER_MODEL_6_8};
82 if (FAILED(device->CheckFeatureSupport(D3D12_FEATURE_SHADER_MODEL, &shaderModel, sizeof(shaderModel))))
83 shaderModel.HighestShaderModel = D3D_SHADER_MODEL_6_0;
84
85 Log(Channel::Platform, Level::Info, " HighestShaderModel : {}",
86 ShaderModelName(shaderModel.HighestShaderModel));
88
89 D3D12_FEATURE_DATA_D3D12_OPTIONS5 options5{};
90 if (SUCCEEDED(device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS5, &options5, sizeof(options5)))) {
91 Log(Channel::Platform, Level::Info, " RaytracingTier : {}",
92 TierName(static_cast<int>(options5.RaytracingTier)));
93 }
94
95 D3D12_FEATURE_DATA_D3D12_OPTIONS7 options7{};
96 if (SUCCEEDED(device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS7, &options7, sizeof(options7)))) {
97 Log(Channel::Platform, Level::Info, " MeshShaderTier : {}",
98 TierName(static_cast<int>(options7.MeshShaderTier)));
99 }
100
101 // 古いランタイムでは OPTIONS21 の問い合わせ自体が失敗する。バグではなく環境の問題なので Warning
102 // 0 で埋めて渡し、CheckFeatureSupport が書き込む(Tier の enum に 0 の値が無いのは SDK の定義による)
103 // NOLINTNEXTLINE(bugprone-invalid-enum-default-initialization)
104 D3D12_FEATURE_DATA_D3D12_OPTIONS21 options21{};
105 const HRESULT result = device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS21, &options21,
106 sizeof(options21));
107
108 if (FAILED(result)) {
110 " WorkGraphsTier : 問い合わせが失敗(ランタイムが古い → Agility SDK が必要): {}",
111 DescribeHresult(result));
112 return;
113 }
114
115 Log(Channel::Platform, Level::Info, " WorkGraphsTier : {}",
116 WorkGraphsTierName(options21.WorkGraphsTier));
117 }
118
119 bool SameLuid(const LUID& a, const LUID& b) {
120 return a.LowPart == b.LowPart && a.HighPart == b.HighPart;
121 }
122
123 // WARP(ソフトウェアの D3D12)。CI の GPU の無い機械で GPU テストを回せるかを知るために表示する(16 §4)。
124 // WARP は FL 12_2 に届かない版があるので、作れる一番高い FL で作ってから機能を問い合わせる
125 void ReportWarp(IDXGIFactory6* factory) {
126 ComPtr<IDXGIAdapter1> adapter;
127 if (FAILED(factory->EnumWarpAdapter(IID_PPV_ARGS(&adapter)))) {
128 Log(Channel::Platform, Level::Warning, "WARP: アダプタを取れない");
129 return;
130 }
131
132 constexpr D3D_FEATURE_LEVEL LEVELS[] = {D3D_FEATURE_LEVEL_12_2, D3D_FEATURE_LEVEL_12_1,
133 D3D_FEATURE_LEVEL_12_0, D3D_FEATURE_LEVEL_11_0};
134 for (const D3D_FEATURE_LEVEL level : LEVELS) {
135 ComPtr<ID3D12Device> device;
136 if (FAILED(D3D12CreateDevice(adapter.Get(), level, IID_PPV_ARGS(&device))))
137 continue;
138
139 Log(Channel::Platform, Level::Info, "WARP: FL {}_{} でデバイスを作れた", (level >> 12) & 0xF,
140 (level >> 8) & 0xF);
141 ReportDevice(device.Get());
142
143 return;
144 }
145
146 Log(Channel::Platform, Level::Warning, "WARP: デバイスを作れない");
147 }
148
149 // 同じ GPU が別の LUID で 2 回列挙される件(T-0004)を調べるための表示: PCI の ID と、つながっている画面
150 void ReportAdapterIdentity(IDXGIAdapter1* adapter, const DXGI_ADAPTER_DESC1& desc) {
151 Log(Channel::Platform, Level::Info, " PCI vendor {:04x} device {:04x} subsys {:08x} rev {:x}",
152 desc.VendorId, desc.DeviceId, desc.SubSysId, desc.Revision);
153 UINT outputCount = 0;
154
155 for (UINT outputIndex = 0;; ++outputIndex) {
156 ComPtr<IDXGIOutput> output;
157 if (adapter->EnumOutputs(outputIndex, &output) == DXGI_ERROR_NOT_FOUND)
158 break;
159
160 DXGI_OUTPUT_DESC outputDesc{};
161 if (FAILED(output->GetDesc(&outputDesc)))
162 continue;
163
164 const RECT& area = outputDesc.DesktopCoordinates;
165 Log(Channel::Platform, Level::Info, " 画面 {}: {} ({},{})-({},{})", outputIndex,
166 ToUtf8(outputDesc.DeviceName), area.left, area.top, area.right, area.bottom);
167 ++outputCount;
168 }
169
170 if (outputCount == 0)
171 Log(Channel::Platform, Level::Info, " 画面: なし");
172 }
173 } // namespace
174
176 ComPtr<IDXGIFactory6> factory;
177 if (!BICAMERAL_CHECK_HR(Channel::Platform, CreateDXGIFactory2(0, IID_PPV_ARGS(&factory))))
178 return 1;
179
180 int found = 0;
181 std::vector<LUID> seen; // 同じ GPU が複数回列挙されることがある(仮想ディスプレイ等)ので LUID で除く
182 for (UINT index = 0;; ++index) {
183 ComPtr<IDXGIAdapter1> adapter;
184 if (factory->EnumAdapterByGpuPreference(index, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE,
185 IID_PPV_ARGS(&adapter)) == DXGI_ERROR_NOT_FOUND) {
186 break;
187 }
188
189 DXGI_ADAPTER_DESC1 desc{};
190 if (!BICAMERAL_CHECK_HR(Channel::Platform, adapter->GetDesc1(&desc)))
191 continue;
192
193 if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
194 continue;
195
196 if (rng::any_of(seen, [&](const LUID& luid) { return SameLuid(luid, desc.AdapterLuid); }))
197 continue;
198 seen.push_back(desc.AdapterLuid);
199
200 Log(Channel::Platform, Level::Info, "Adapter {}: {} VRAM {} MB LUID {:08x}:{:08x} flags 0x{:x}", index,
201 ToUtf8(desc.Description), desc.DedicatedVideoMemory >> 20,
202 static_cast<unsigned long>(desc.AdapterLuid.HighPart), desc.AdapterLuid.LowPart, desc.Flags);
203
204 ReportAdapterIdentity(adapter.Get(), desc);
205
206 // FL 12_2 に届かない GPU は対象外なだけ(エラーではない)
207 ComPtr<ID3D12Device> device;
208 const HRESULT result = D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_12_2, IID_PPV_ARGS(&device));
209
210 if (FAILED(result)) {
211 Log(Channel::Platform, Level::Warning, " FL 12_2 のデバイスを作れない(DX12 Ultimate 非対応): {}",
212 DescribeHresult(result));
213 continue;
214 }
215
216 if (found == 0)
217 ReportRuntime();
218
219 ReportDevice(device.Get());
220 ++found;
221 }
222
223 // WARP の前に、どのランタイムかを出しておく
224 if (found == 0)
225 ReportRuntime();
226
227 ReportWarp(factory.Get());
228 if (found == 0) {
229 Log(Channel::Platform, Level::Error, "DX12 Ultimate 対応の GPU が見つからない");
230 return 1;
231 }
232
233 return 0;
234 }
235
236} // namespace bicameral
#define BICAMERAL_CHECK_HR(channel, expression)
Definition hresult.h:32
std::string ShaderModelName(D3D_SHADER_MODEL shaderModel)
Definition caps.cpp:28
bool SameLuid(const LUID &a, const LUID &b)
Definition caps.cpp:119
void ReportAdapterIdentity(IDXGIAdapter1 *adapter, const DXGI_ADAPTER_DESC1 &desc)
Definition caps.cpp:150
std::string TierName(int tier)
Definition caps.cpp:34
void ReportDevice(ID3D12Device *device)
Definition caps.cpp:80
void ReportWarp(IDXGIFactory6 *factory)
Definition caps.cpp:125
const char * YesNo(BOOL value)
Definition caps.cpp:54
void ReportIntegerFeatures(ID3D12Device *device)
Definition caps.cpp:61
const char * WorkGraphsTierName(D3D12_WORK_GRAPHS_TIER tier)
Definition caps.cpp:19
int RunCapsProbe()
Definition caps.cpp:175
std::string DescribeHresult(HResult result)
Definition hresult.cpp:88
void Log(Channel channel, Level level, FormatWithLocation< std::type_identity_t< Args >... > pattern, Args &&... args)
Definition log.h:140
std::string ToUtf8(std::wstring_view text)
Definition unicode.cpp:10