chrono_batch_render/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
358 / 625
57.3%
Branches:
271 / 596
45.5%
Functions:
43 / 65
66.2%

device/device.cpp
Line Branch Exec Source
1 // SPDX-License-Identifier: BSD-3-Clause
2 #include "chrono_batch_render/device/device.h"
3
4 #include <algorithm>
5 #include <bit>
6 #include <cstring>
7 #include <map>
8 #include <memory>
9 #include <mutex>
10 #include <optional>
11 #include <set>
12 #include <tuple>
13 #include <utility>
14 #include <vector>
15
16 #include <fmt/format.h>
17
18 #include "chrono_batch_render/device/cuda/device.h"
19 #include "chrono_batch_render/device/hip/device.h"
20 #include "chrono_batch_render/device/kernels/shared.h"
21 #include "chrono_batch_render/device/queue.h"
22 #include "chrono_batch_render/device/slang/compiler.h"
23 #include "chrono_batch_render/device/slang/debug.h"
24 #include "chrono_batch_render/device/vulkan/device.h"
25 #include "chrono_batch_render/device/vulkan/native.h"
26 #include "chrono_batch_render/util/disk_cache.h"
27 #include "chrono_batch_render/util/environment.h"
28 #include "chrono_batch_render/util/error.h"
29 #include "chrono_batch_render/util/log.h"
30 #include "chrono_batch_render/util/span.h"
31 #include "chrono_batch_render/util/text.h"
32
33 namespace chrono::batch_render::device {
34 namespace {
35
36 constexpr uint64_t kKernelCacheBytes = uint64_t(1) << 30;
37
38 10 BufferRange ContiguousRange(const Tensor& tensor, const char* role) {
39
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (tensor.Empty()) {
40
0/1
✗ Branch 2 not taken.
✗ throw Error("Copying needs a {} tensor, but it's empty", role);
41 }
42 10 const Span<const int64_t> shape = tensor.Shape();
43 10 const Span<const int64_t> strides = tensor.Strides();
44 10 int64_t packed = 1;
45
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 10 times.
23 for (size_t axis = shape.size(); axis-- > 0;) {
46
3/6
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 13 times.
13 if (!strides.empty() && shape[axis] > 1 && strides[axis] != packed) {
47
0/1
✗ Branch 2 not taken.
✗ throw Error("Copying needs a contiguous {} tensor", role);
48 }
49 13 packed *= shape[axis];
50 }
51 10 return tensor.GetBuffer().Slice(tensor.Offset(),
52 10 uint64_t(tensor.Elements()) * ElementBytes(tensor.Type()));
53 }
54
55 ✗ std::optional<ExternalAddress> SharedAddress(Buffer& buffer, Device& reader) {
56 ✗ const std::optional<DLDevice> api = reader.SharedDevice();
57
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (!api) {
58 ✗ return std::nullopt;
59 }
60 ✗ try {
61
0/1
✗ Branch 1 not taken.
✗ const ExternalAddress external = buffer.External(api->device_type);
62
0/3
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ if (reader.ReadsDirectly(external.device)) {
63 ✗ return external;
64 }
65
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ } catch (const UnsupportedError&) {
66 ✗ }
67 ✗ return std::nullopt;
68 }
69
70 constexpr uint64_t kFullyBackedPageBytes = uint64_t(64) * 1024;
71
72 constexpr Choice<Traversal> kTraversals[] = {{"auto", Traversal::Auto},
73 {"hardware", Traversal::Hardware},
74 {"software", Traversal::Software}};
75
76 constexpr Choice<Sampling> kSamplings[] = {
77 {"auto", Sampling::Auto}, {"hardware", Sampling::Hardware}, {"software", Sampling::Software}};
78
79 struct Implementation {
80 std::string_view name;
81 Backend value;
82 const char* title;
83 std::vector<AdapterInfo> (*list)() = nullptr;
84 Ref<Device> (*create)(const AdapterInfo&, const DeviceDesc&, Traversal) = nullptr;
85 };
86
87 class BorrowedBuffer : public Buffer {
88 public:
89 ✗ BorrowedBuffer(Device& device, const BufferDesc& desc, uint64_t address)
90 ✗ : Buffer(device, desc) {
91 ✗ m_address = address;
92 }
93 };
94
95 template <class BackendDevice>
96 18 Ref<Device> Create(const AdapterInfo& adapter, const DeviceDesc& desc, Traversal traversal) {
97
1/3
chrono::batch_render::Ref<chrono::batch_render::device::Device> chrono::batch_render::device::(anonymous namespace)::Create<chrono::batch_render::device::cuda::CudaDevice>(chrono::batch_render::device::AdapterInfo const&, chrono::batch_render::device::DeviceDesc const&, chrono::batch_render::device::Traversal):
✗ Branch 2 not taken.
chrono::batch_render::Ref<chrono::batch_render::device::Device> chrono::batch_render::device::(anonymous namespace)::Create<chrono::batch_render::device::hip::HipDevice>(chrono::batch_render::device::AdapterInfo const&, chrono::batch_render::device::DeviceDesc const&, chrono::batch_render::device::Traversal):
✗ Branch 2 not taken.
chrono::batch_render::Ref<chrono::batch_render::device::Device> chrono::batch_render::device::(anonymous namespace)::Create<chrono::batch_render::device::vulkan::VulkanDevice>(chrono::batch_render::device::AdapterInfo const&, chrono::batch_render::device::DeviceDesc const&, chrono::batch_render::device::Traversal):
✓ Branch 2 taken 18 times.
18 return Ref<Device>(new BackendDevice(adapter, desc, traversal));
98 }
99
100 constexpr Implementation kBackends[] = {
101 {"vulkan", Backend::Vulkan, "Vulkan", vulkan::ListAdapters, Create<vulkan::VulkanDevice>},
102 {"cuda", Backend::Cuda, "CUDA", cuda::ListAdapters, Create<cuda::CudaDevice>},
103 {"hip", Backend::Hip, "HIP", hip::ListAdapters, Create<hip::HipDevice>},
104 {"metal", Backend::Metal, "Metal"},
105 {"cpu", Backend::Cpu, "CPU"},
106 };
107
108 23 DeviceDesc WithEnvironment(DeviceDesc desc) {
109 46 if (const std::optional<Backend> backend =
110
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 23 times.
23 EnvironmentChoice("CHRONO_BATCH_RENDER_BACKEND", kBackends)) {
111 ✗ Log(LogLevel::Info, "CHRONO_BATCH_RENDER_BACKEND chooses {}", BackendName(*backend));
112 ✗ desc.backend = backend;
113 }
114
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 23 times.
23 if (std::string adapter = Environment("CHRONO_BATCH_RENDER_ADAPTER"); !adapter.empty()) {
115
0/1
✗ Branch 1 not taken.
✗ Log(LogLevel::Info, "CHRONO_BATCH_RENDER_ADAPTER chooses \"{}\"", adapter);
116 ✗ desc.adapter = std::move(adapter);
117 ✗ }
118 46 if (const std::optional<Traversal> traversal =
119
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 23 times.
23 EnvironmentChoice("CHRONO_BATCH_RENDER_TRAVERSAL", kTraversals)) {
120
0/1
✗ Branch 1 not taken.
✗ Log(LogLevel::Info, "CHRONO_BATCH_RENDER_TRAVERSAL chooses {} traversal",
121 ✗ Environment("CHRONO_BATCH_RENDER_TRAVERSAL"));
122 ✗ desc.traversal = *traversal;
123 }
124 46 if (const std::optional<Sampling> sampling =
125
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 23 times.
23 EnvironmentChoice("CHRONO_BATCH_RENDER_SAMPLING", kSamplings)) {
126
0/1
✗ Branch 1 not taken.
✗ Log(LogLevel::Info, "CHRONO_BATCH_RENDER_SAMPLING chooses {} texture sampling",
127 ✗ Environment("CHRONO_BATCH_RENDER_SAMPLING"));
128 ✗ desc.sampling = *sampling;
129 }
130
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 23 times.
23 if (Environment("CHRONO_BATCH_RENDER_VALIDATION") == "1") {
131 ✗ Log(LogLevel::Info, "CHRONO_BATCH_RENDER_VALIDATION validates Vulkan calls");
132 ✗ desc.validation = true;
133 }
134
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 23 times.
23 if (Environment("CHRONO_BATCH_RENDER_DEVICE_SYNC") == "1") {
135 ✗ Log(LogLevel::Info,
136 "CHRONO_BATCH_RENDER_DEVICE_SYNC runs each command alone and waits for it");
137 ✗ desc.synchronous = true;
138 }
139
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 23 times.
23 if (Environment("CHRONO_BATCH_RENDER_KERNEL_DEBUG_INFO") == "1") {
140 ✗ Log(LogLevel::Info,
141 "CHRONO_BATCH_RENDER_KERNEL_DEBUG_INFO compiles kernels with line info");
142 ✗ desc.kernelDebugInfo = true;
143 }
144 23 return desc;
145 }
146
147 19 std::optional<Backend> AliasBackend(std::string_view adapter) {
148 19 const size_t colon = adapter.find(':');
149
2/2
✓ Branch 0 taken 51 times.
✓ Branch 1 taken 8 times.
59 for (const Implementation& implementation : kBackends) {
150
4/4
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 35 times.
✓ Branch 3 taken 11 times.
✓ Branch 4 taken 5 times.
51 if (colon != std::string_view::npos && adapter.substr(0, colon) == implementation.name) {
151 11 return implementation.value;
152 }
153 }
154 8 return std::nullopt;
155 }
156
157 32 const Implementation& Implement(Backend backend) {
158
1/2
✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
32 for (const Implementation& implementation : kBackends) {
159
1/2
✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
32 if (implementation.value == backend) {
160
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 32 times.
32 if (implementation.list == nullptr) {
161 ✗ throw UnsupportedError("Chrono Batch Render has no {} backend yet",
162
0/1
✗ Branch 2 not taken.
✗ implementation.title);
163 }
164 32 return implementation;
165 }
166 }
167
0/1
✗ Branch 2 not taken.
✗ throw UnsupportedError("Chrono Batch Render has no such backend");
168 }
169
170 98 void AddAdapters(std::vector<AdapterInfo>& adapters, const Implementation& implementation,
171 bool required) {
172 98 std::vector<AdapterInfo> found;
173 98 try {
174
1/1
✓ Branch 1 taken 42 times.
140 found = implementation.list();
175
1/3
✗ Branch 0 not taken.
✓ Branch 1 taken 56 times.
✗ Branch 2 not taken.
56 } catch (const UnsupportedError&) {
176
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 56 times.
56 if (required) {
177 ✗ throw;
178 }
179 56 return;
180
1/1
✓ Branch 1 taken 56 times.
56 } catch (const Error& error) {
181
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (required) {
182 ✗ throw;
183 }
184 ✗ Log(LogLevel::Warning, "{} adapters could not be listed: {}", implementation.title,
185
0/1
✗ Branch 2 not taken.
✗ error.what());
186 ✗ return;
187 ✗ }
188
189
2/2
✓ Branch 0 taken 42 times.
✓ Branch 1 taken 42 times.
84 for (AdapterInfo& adapter : found) {
190
1/1
✓ Branch 1 taken 42 times.
42 adapter.alias = fmt::format("{}:{}", implementation.name, adapter.ordinal);
191
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 42 times.
42 if (!adapter.unsupported.empty()) {
192 ✗ Log(LogLevel::Debug, "Chrono Batch Render can't run on {} ({}): {}", adapter.alias,
193
0/1
✗ Branch 1 not taken.
✗ adapter.name, adapter.unsupported);
194 }
195
1/1
✓ Branch 1 taken 42 times.
42 adapters.push_back(std::move(adapter));
196 }
197 98 }
198
199 21 bool Matches(const AdapterInfo& adapter, std::string_view wanted) {
200
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 13 times.
29 return adapter.alias == wanted ||
201
8/10
✓ Branch 1 taken 8 times.
✓ Branch 4 taken 8 times.
✓ Branch 6 taken 7 times.
✓ Branch 7 taken 1 time.
✓ Branch 8 taken 8 times.
✓ Branch 9 taken 13 times.
✓ Branch 10 taken 8 times.
✓ Branch 11 taken 13 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
52 Lowercase(adapter.name).find(Lowercase(wanted)) != std::string::npos;
202 }
203
204 ✗ std::tuple<int, int, int> Preference(const AdapterInfo& adapter) {
205 ✗ const int type = int(adapter.type);
206
0/6
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ switch (adapter.backend) {
207 ✗ case Backend::Vulkan:
208
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ if (adapter.features.hardwareTraversal && adapter.type == AdapterType::Discrete) {
209 ✗ return {0, 0, 0};
210 }
211
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ return {4, adapter.features.hardwareTraversal ? 0 : 1, type};
212 ✗ case Backend::Metal:
213 ✗ return {1, 0, type};
214 ✗ case Backend::Cuda:
215 ✗ return {2, 0, type};
216 ✗ case Backend::Hip:
217 ✗ return {3, 0, type};
218 ✗ case Backend::Cpu:
219 ✗ return {5, 0, type};
220 }
221 ✗ return {6, 0, type};
222 }
223
224 1 std::string Describe(const std::vector<AdapterInfo>& adapters) {
225
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if (adapters.empty()) {
226 ✗ return "Chrono Batch Render found no adapters";
227 }
228 1 std::string text = "Chrono Batch Render found:";
229
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 for (const AdapterInfo& adapter : adapters) {
230
1/1
✓ Branch 1 taken 1 time.
2 text += fmt::format("\n {}: {}", adapter.alias, adapter.name);
231
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if (!adapter.unsupported.empty()) {
232
0/1
✗ Branch 1 not taken.
✗ text += fmt::format(" (unsupported: {})", adapter.unsupported);
233 }
234 }
235 1 return text;
236 1 }
237
238 21 const AdapterInfo& ChooseAdapter(const std::vector<AdapterInfo>& adapters, const DeviceDesc& desc) {
239 21 std::vector<const AdapterInfo*> matching;
240
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 21 times.
42 for (const AdapterInfo& adapter : adapters) {
241
6/7
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 19 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 21 times.
✓ Branch 7 taken 20 times.
✓ Branch 8 taken 1 time.
21 if ((!desc.backend || adapter.backend == *desc.backend) && Matches(adapter, desc.adapter)) {
242
1/1
✓ Branch 1 taken 20 times.
20 matching.push_back(&adapter);
243 }
244 }
245
246
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 20 times.
21 if (matching.empty()) {
247
1/1
✓ Branch 1 taken 1 time.
1 std::string wanted = "adapter";
248
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 if (!desc.adapter.empty()) {
249
1/1
✓ Branch 1 taken 1 time.
1 wanted = fmt::format("adapter \"{}\"", desc.adapter);
250
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ } else if (desc.backend) {
251
0/2
✗ Branch 1 not taken.
✗ Branch 4 not taken.
1 wanted = fmt::format("{} adapter", BackendName(*desc.backend));
252 }
253
2/2
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
1 throw UnsupportedError("there is no {}. {}", wanted, Describe(adapters));
254 1 }
255
256 40 const auto reason = [&](const AdapterInfo& adapter) -> std::string {
257
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
20 if (!adapter.unsupported.empty()) {
258 ✗ return adapter.unsupported;
259 }
260
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
20 if (desc.presentation && !adapter.features.presentation) {
261 ✗ return "it can't present to windows";
262 }
263 20 return {};
264 20 };
265 20 std::vector<const AdapterInfo*> supported;
266
1/1
✓ Branch 1 taken 20 times.
20 std::copy_if(matching.begin(), matching.end(), std::back_inserter(supported),
267 20 [&](const AdapterInfo* adapter) { return reason(*adapter).empty(); });
268
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
20 if (supported.empty()) {
269 ✗ const AdapterInfo& first = *matching.front();
270 ✗ throw UnsupportedError("Chrono Batch Render can't run on {} ({}): {}", first.alias,
271
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ first.name, reason(first));
272 }
273
274 20 return **std::min_element(
275 supported.begin(), supported.end(),
276 20 [](const AdapterInfo* a, const AdapterInfo* b) { return Preference(*a) < Preference(*b); });
277 21 }
278
279 20 Traversal ResolveTraversal(const AdapterInfo& adapter, Traversal wanted) {
280
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
20 if (wanted == Traversal::Hardware && !adapter.features.hardwareTraversal) {
281 ✗ throw UnsupportedError("{} ({}) has no hardware ray traversal", adapter.alias,
282
0/1
✗ Branch 2 not taken.
✗ adapter.name);
283 }
284
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 if (wanted == Traversal::Auto) {
285
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
20 if (!adapter.features.hardwareTraversal) {
286 ✗ Log(LogLevel::Debug, "{} has no hardware ray traversal, so rays use software BVHs",
287 ✗ adapter.alias);
288 ✗ return Traversal::Software;
289 }
290
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 if (adapter.type == AdapterType::Cpu) {
291 20 Log(LogLevel::Debug, "{} emulates ray traversal on the CPU, so rays use software BVHs",
292 20 adapter.alias);
293 20 return Traversal::Software;
294 }
295 return Traversal::Hardware;
296 }
297 return wanted;
298 }
299
300 20 Sampling ResolveSampling(const AdapterInfo& adapter, Sampling wanted) {
301
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
20 if (wanted == Sampling::Hardware && !adapter.features.hardwareTextures) {
302 ✗ throw UnsupportedError("{} ({}) has no hardware texture sampling", adapter.alias,
303
0/1
✗ Branch 2 not taken.
✗ adapter.name);
304 }
305
2/2
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 1 time.
20 if (wanted == Sampling::Auto) {
306
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
19 if (!adapter.features.hardwareTextures) {
307 ✗ Log(LogLevel::Debug,
308 "{} has no hardware texture sampling, so textures are sampled in software",
309 ✗ adapter.alias);
310 ✗ return Sampling::Software;
311 }
312 return Sampling::Hardware;
313 }
314 return wanted;
315 }
316
317 22 std::vector<AdapterInfo> FindAdapters(const DeviceDesc& desc) {
318
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 19 times.
22 const std::optional<Backend> backend = desc.backend ? desc.backend : AliasBackend(desc.adapter);
319
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 14 times.
22 if (!backend) {
320 8 return EnumerateAdapters();
321 }
322 14 std::vector<AdapterInfo> adapters;
323
2/2
✓ Branch 1 taken 14 times.
✓ Branch 4 taken 14 times.
14 AddAdapters(adapters, Implement(*backend), true);
324 14 return adapters;
325 14 }
326
327 }
328
329 3414 std::string_view BackendName(Backend backend) {
330
1/2
✓ Branch 0 taken 3415 times.
✗ Branch 1 not taken.
3415 for (const Implementation& implementation : kBackends) {
331
2/2
✓ Branch 0 taken 3414 times.
✓ Branch 1 taken 1 time.
3415 if (implementation.value == backend) {
332 3414 return implementation.name;
333 }
334 }
335 ✗ return "unknown";
336 }
337
338 28 std::vector<AdapterInfo> EnumerateAdapters() {
339 28 std::vector<AdapterInfo> adapters;
340
2/2
✓ Branch 0 taken 140 times.
✓ Branch 1 taken 28 times.
168 for (const Implementation& implementation : kBackends) {
341
2/2
✓ Branch 0 taken 84 times.
✓ Branch 1 taken 56 times.
140 if (implementation.list != nullptr) {
342
1/1
✓ Branch 1 taken 84 times.
84 AddAdapters(adapters, implementation, false);
343 }
344 }
345 28 return adapters;
346 ✗ }
347
348 23 Ref<Device> Device::Create(const DeviceDesc& requested) {
349
1/1
✓ Branch 2 taken 23 times.
23 DeviceDesc desc = WithEnvironment(requested);
350
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 19 times.
23 if (desc.creator) {
351
3/4
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
4 if (desc.backend && *desc.backend != Backend::Vulkan) {
352 1 throw Error("a Vulkan creator needs a Vulkan device, not a {} one",
353
2/2
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
1 BackendName(*desc.backend));
354 }
355 3 desc.backend = Backend::Vulkan;
356 }
357
1/1
✓ Branch 1 taken 22 times.
22 const std::vector<AdapterInfo> adapters = FindAdapters(desc);
358 22 vulkan::OwnedInstance instance;
359
1/1
✓ Branch 1 taken 22 times.
22 DeviceDesc chosen = desc;
360
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 19 times.
22 if (desc.creator) {
361
1/1
✓ Branch 1 taken 2 times.
3 instance = vulkan::CreateInstance(desc.validation, *desc.creator);
362
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 if (desc.creator->pickAdapter) {
363
1/1
✓ Branch 1 taken 1 time.
1 const std::array<uint8_t, 16> uuid =
364
2/2
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
1 vulkan::DeviceUuid(desc.creator->pickAdapter(instance.Get()));
365 1 const auto picked = std::ranges::find_if(
366
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 adapters, [&](const AdapterInfo& adapter) { return adapter.uuid == uuid; });
367
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if (picked == adapters.end()) {
368 ✗ throw UnsupportedError(
369 "the Vulkan creator picked an adapter Chrono Batch Render doesn't list. {}",
370
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Describe(adapters));
371 }
372
1/5
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
1 if (!desc.adapter.empty() && !Matches(*picked, desc.adapter)) {
373 ✗ throw Error("the Vulkan creator picked {}, not the adapter \"{}\" asked for",
374
0/1
✗ Branch 2 not taken.
✗ picked->alias, desc.adapter);
375 }
376
1/1
✓ Branch 1 taken 1 time.
2 chosen.adapter = picked->alias;
377 }
378 }
379
1/1
✓ Branch 1 taken 20 times.
21 const AdapterInfo& adapter = ChooseAdapter(adapters, chosen);
380
1/1
✓ Branch 1 taken 20 times.
20 const Traversal traversal = ResolveTraversal(adapter, desc.traversal);
381
1/1
✓ Branch 1 taken 20 times.
20 DeviceDesc resolved = desc;
382
1/1
✓ Branch 1 taken 20 times.
20 resolved.sampling = ResolveSampling(adapter, desc.sampling);
383
384
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 18 times.
20 Ref<Device> device = desc.creator
385
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 18 times.
20 ? Ref<Device>(new vulkan::VulkanDevice(adapter, resolved, traversal,
386
2/4
✓ Branch 1 taken 2 times.
✓ Branch 5 taken 2 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
4 std::move(instance)))
387
2/4
✓ Branch 2 taken 18 times.
✓ Branch 5 taken 18 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
20 : Implement(adapter.backend).create(adapter, resolved, traversal);
388
1/1
✓ Branch 1 taken 20 times.
20 device->CreateGlobals();
389
390
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 Log(LogLevel::Info, "using {} ({}), with {} ray traversal and {} texture sampling",
391
1/1
✓ Branch 1 taken 20 times.
20 adapter.alias, adapter.name, traversal == Traversal::Hardware ? "hardware" : "software",
392
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 19 times.
20 resolved.sampling == Sampling::Hardware ? "hardware" : "software");
393 20 return device;
394 29 }
395
396 struct Device::Counters {
397 std::mutex mutex;
398 std::map<std::string, MemoryUse> uses;
399 MemoryStats totals;
400 TrafficStats traffic;
401 };
402
403 20 struct Device::AccelSizeCache {
404 std::mutex mutex;
405 std::map<std::tuple<uint32_t, uint32_t, uint32_t, uint32_t, bool, bool>, AccelSizes> sizes;
406 };
407
408 20 Device::Device(AdapterInfo adapter, const DeviceDesc& desc, Traversal traversal)
409 20 : m_adapter(std::move(adapter)), m_traversal(traversal), m_sampling(desc.sampling),
410
2/4
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 20 times.
✗ Branch 6 not taken.
20 m_cacheDirectory(desc.cacheDirectory.empty() ? UserCacheDirectory() : desc.cacheDirectory),
411
1/1
✓ Branch 1 taken 20 times.
20 m_kernelCacheDirectory(chrono::batch_render::KernelCacheDirectory(m_cacheDirectory)),
412 20 m_profiling(desc.profiling), m_synchronous(desc.synchronous),
413 20 m_kernelDebugInfo(desc.kernelDebugInfo), m_presents(desc.presentation),
414
4/4
✓ Branch 2 taken 20 times.
✓ Branch 5 taken 20 times.
✓ Branch 7 taken 11 times.
✓ Branch 8 taken 9 times.
40 m_counters(std::make_unique<Counters>()), m_accelSizes(std::make_unique<AccelSizeCache>()) {
415 20 static std::mutex trimming;
416
3/4
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 9 times.
✓ Branch 3 taken 11 times.
✗ Branch 4 not taken.
20 static std::set<std::filesystem::path> trimmed;
417
1/1
✓ Branch 1 taken 20 times.
20 const std::lock_guard lock(trimming);
418
3/3
✓ Branch 1 taken 20 times.
✓ Branch 3 taken 11 times.
✓ Branch 4 taken 9 times.
20 if (trimmed.insert(m_cacheDirectory).second) {
419
1/1
✓ Branch 1 taken 11 times.
11 TrimKernelCache(m_cacheDirectory, kKernelCacheBytes);
420 }
421 20 }
422
423
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 20 times.
60 Device::~Device() = default;
424
425 119983 Ref<Buffer> Device::CreateBuffer(const BufferDesc& requested) {
426 119983 const bool reserve = requested.memory == Memory::Virtual;
427
1/2
✓ Branch 0 taken 119983 times.
✗ Branch 1 not taken.
119983 const char* kind = reserve ? "virtual buffer" : "buffer";
428 239966 const uint64_t limit =
429
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 119983 times.
119983 reserve ? m_adapter.limits.maxReservedBytes : m_adapter.limits.maxBufferBytes;
430
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 119983 times.
119983 if (requested.bytes == 0) {
431
0/1
✗ Branch 2 not taken.
✗ throw Error("{} \"{}\" has no bytes", kind, requested.label);
432 }
433
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 119983 times.
119983 if (requested.bytes > limit) {
434 ✗ throw Error("{} \"{}\" has {} bytes; {} takes at most {}", kind, requested.label,
435
0/1
✗ Branch 2 not taken.
✗ requested.bytes, m_adapter.alias, limit);
436 }
437
438 119983 BufferDesc desc = requested;
439
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 119983 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
119983 if (desc.memory == Memory::Shared && !m_adapter.features.hostMappedMemory) {
440 ✗ desc.memory = Memory::Device;
441 }
442
443 119983 Ref<Buffer> buffer;
444
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 119983 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
119983 if (reserve && !m_adapter.features.virtualMemory) {
445 ✗ desc.memory = Memory::Device;
446
0/1
✗ Branch 1 not taken.
✗ desc.bytes = RoundUp(desc.bytes, kFullyBackedPageBytes);
447
0/3
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ buffer = NewBuffer(desc);
448 ✗ buffer->m_memory = Memory::Virtual;
449 ✗ buffer->m_pageBytes = kFullyBackedPageBytes;
450 } else {
451
2/5
✓ Branch 1 taken 119983 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 119983 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
119983 buffer = NewBuffer(desc);
452 }
453
454
1/1
✓ Branch 1 taken 119983 times.
119983 CountMemory(*buffer, 1);
455 119983 return buffer;
456 119983 }
457
458 ✗ Tensor Device::CreateSharedTensor(DType type, std::vector<int64_t> shape, std::string label) {
459 ✗ uint64_t bytes = ElementBytes(type);
460
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ for (const int64_t side : shape) {
461
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (side < 0) {
462
0/1
✗ Branch 2 not taken.
✗ throw Error("Tensor \"{}\" has a side of {}", label, side);
463 }
464 ✗ bytes *= uint64_t(side);
465 }
466 ✗ Ref<Buffer> buffer = CreateBuffer({.bytes = std::max<uint64_t>(bytes, 4),
467 .usage = BufferUsage::Interop,
468
0/1
✗ Branch 1 not taken.
✗ .label = std::move(label)});
469
0/5
✗ Branch 1 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ return Tensor(std::move(buffer), 0, type, std::move(shape));
470 ✗ }
471
472 987 Tensor Device::CreateTensor(DType type, std::vector<int64_t> shape, std::string label) {
473 987 uint64_t bytes = ElementBytes(type);
474
2/2
✓ Branch 0 taken 1145 times.
✓ Branch 1 taken 986 times.
2131 for (const int64_t side : shape) {
475
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1144 times.
1145 if (side < 0) {
476
1/1
✓ Branch 2 taken 1 time.
1 throw Error("Tensor \"{}\" has a side of {}", label, side);
477 }
478 1144 bytes *= uint64_t(side);
479 }
480
1/2
✓ Branch 1 taken 986 times.
✗ Branch 2 not taken.
986 const BufferUsage usage = SharedDevice() ? BufferUsage::Interop : BufferUsage::None;
481 986 Ref<Buffer> buffer = CreateBuffer(
482
1/1
✓ Branch 1 taken 986 times.
986 {.bytes = std::max<uint64_t>(bytes, 4), .usage = usage, .label = std::move(label)});
483
2/5
✓ Branch 1 taken 986 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 986 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
1972 return Tensor(std::move(buffer), 0, type, std::move(shape));
484 1972 }
485
486 ✗ Operation Device::Follow(Stream producer) {
487 ✗ m_queue->Follow(producer);
488 ✗ return m_queue->Mark();
489 }
490
491 69122 Tensor Device::Import(const DLTensor& source, Stream producer, std::string label) {
492 69122 const DLDataType type = source.dtype;
493 69122 const std::optional<DType> dtype =
494
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 69122 times.
69122 type.lanes == 0 ? std::nullopt
495
2/2
✓ Branch 0 taken 69106 times.
✓ Branch 1 taken 16 times.
69122 : type.code == kDLFloat && type.bits == 32 ? std::optional(DType::Float32)
496
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 10 times.
16 : type.code == kDLFloat && type.bits == 64 ? std::optional(DType::Float64)
497
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 6 times.
10 : type.code == kDLInt && type.bits == 32 ? std::optional(DType::Int32)
498
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 : type.code == kDLInt && type.bits == 64 ? std::optional(DType::Int64)
499
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 5 times.
6 : type.code == kDLUInt && type.bits == 8 ? std::optional(DType::UInt8)
500
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 4 times.
5 : type.code == kDLUInt && type.bits == 32 ? std::optional(DType::UInt32)
501
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
4 : type.code == kDLUInt && type.bits == 64 ? std::optional(DType::UInt64)
502 69122 : std::nullopt;
503
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 69121 times.
69122 if (!dtype) {
504 1 throw UnsupportedError("{} imports no tensors of {}-bit type code {} with {} lanes",
505
1/1
✓ Branch 2 taken 1 time.
1 m_adapter.alias, type.bits, type.code, type.lanes);
506 }
507 69121 std::vector<int64_t> shape(source.shape, source.shape + source.ndim);
508 69121 int64_t elements = 1;
509
2/2
✓ Branch 0 taken 138240 times.
✓ Branch 1 taken 69121 times.
207361 for (int axis = source.ndim; axis-- > 0;) {
510
4/4
✓ Branch 0 taken 203 times.
✓ Branch 1 taken 138037 times.
✓ Branch 2 taken 194 times.
✓ Branch 3 taken 9 times.
138240 if (source.strides != nullptr && shape[size_t(axis)] > 1 &&
511
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 194 times.
194 source.strides[axis] != elements) {
512
0/1
✗ Branch 2 not taken.
✗ throw UnsupportedError("\"{}\" is imported from contiguous tensors only", label);
513 }
514 138240 elements *= shape[size_t(axis)];
515 }
516
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 69121 times.
69121 if (type.lanes > 1) {
517
0/1
✗ Branch 1 not taken.
✗ shape.push_back(type.lanes);
518 ✗ elements *= type.lanes;
519 }
520
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 69121 times.
69121 const uint64_t bytes = uint64_t(elements) * ElementBytes(*dtype);
521 69121 const uint64_t address = uint64_t(source.data) + source.byte_offset;
522 69121 Ref<Buffer> buffer;
523 138242 const bool host =
524
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 69121 times.
69121 source.device.device_type == kDLCPU || source.device.device_type == kDLCUDAHost ||
525 source.device.device_type == kDLROCMHost || source.device.device_type == kDLCUDAManaged;
526 ✗ if (host) {
527
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 69121 times.
69121 if (producer.IsCompute()) {
528
0/1
✗ Branch 1 not taken.
✗ m_queue->Follow(producer);
529
0/2
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ m_queue->Mark().Wait();
530 }
531 69121 buffer =
532
2/2
✓ Branch 1 taken 69121 times.
✓ Branch 4 taken 69121 times.
69121 CreateBuffer({.label = label},
533 69121 Span<const std::byte>(reinterpret_cast<const std::byte*>(address), bytes));
534
0/5
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ } else if (bytes > 0 && ReadsDirectly(source.device)) {
535
0/2
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ buffer = Borrow(source.device, address, bytes, label);
536
0/1
✗ Branch 1 not taken.
✗ m_queue->Follow(producer);
537 } else {
538
0/1
✗ Branch 1 not taken.
✗ buffer = Stage(address, bytes, producer, label);
539 }
540
1/3
✓ Branch 1 taken 69121 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
138242 return Tensor(std::move(buffer), 0, *dtype, std::move(shape));
541 138242 }
542
543 5 Operation Device::Copy(const Tensor& src, const Tensor& dst, const Operation& after) {
544 5 const BufferRange from = ContiguousRange(src, "source");
545 5 const BufferRange to = ContiguousRange(dst, "destination");
546
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
5 if (&dst.GetDevice() != this) {
547 ✗ throw Error("{} copies only into its own tensors, but the destination is on {}",
548
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ m_adapter.alias, dst.GetDevice().Adapter().alias);
549 }
550
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 4 times.
5 if (from.Bytes() != to.Bytes()) {
551
1/1
✓ Branch 2 taken 1 time.
1 throw Error("Copying a tensor of {} bytes into one of {} bytes", from.Bytes(), to.Bytes());
552 }
553 4 Device& source = src.GetDevice();
554 8 const auto copy = [](Queue& queue, BufferRange read, BufferRange written) {
555
4/5
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 2 times.
4 static const ZoneId copying = ZoneId::Named("Copy");
556 4 CommandEncoder encoder = queue.CreateEncoder();
557
1/1
✓ Branch 1 taken 4 times.
4 encoder.BeginZone(copying);
558
1/1
✓ Branch 1 taken 4 times.
4 encoder.Copy(read, written);
559
1/1
✓ Branch 1 taken 4 times.
4 encoder.EndZone();
560
2/2
✓ Branch 1 taken 4 times.
✓ Branch 4 taken 4 times.
4 return queue.Submit(encoder.Finish());
561 4 };
562
563
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (&source == this) {
564 4 m_queue->Follow(after);
565 4 return copy(*m_queue, from, to);
566 }
567
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (const std::optional<ExternalAddress> shared = SharedAddress(src.GetBuffer(), *this)) {
568 ✗ const Ref<Buffer> borrowed =
569
0/1
✗ Branch 2 not taken.
✗ Borrow(shared->device, shared->address, src.GetBuffer().Bytes(), "copy source");
570
0/1
✗ Branch 1 not taken.
✗ m_queue->Follow(after);
571
0/2
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ const Operation copied = copy(*m_queue, borrowed->Slice(src.Offset(), from.Bytes()), to);
572
0/6
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ m_queue->Retire(std::make_shared<Tensor>(src), copied);
573
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ return copied;
574 ✗ }
575
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (const std::optional<ExternalAddress> shared = SharedAddress(dst.GetBuffer(), source)) {
576
0/1
✗ Branch 2 not taken.
✗ const Ref<Buffer> borrowed = source.Borrow(shared->device, shared->address,
577
0/1
✗ Branch 1 not taken.
✗ dst.GetBuffer().Bytes(), "copy destination");
578
0/1
✗ Branch 1 not taken.
✗ Queue& queue = source.GetQueue();
579
0/1
✗ Branch 1 not taken.
✗ queue.Follow(after);
580
0/2
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ const Operation copied = copy(queue, from, borrowed->Slice(dst.Offset(), to.Bytes()));
581
0/6
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ queue.Retire(std::make_shared<Tensor>(dst), copied);
582
0/1
✗ Branch 1 not taken.
✗ m_queue->Follow(copied);
583
0/1
✗ Branch 1 not taken.
✗ return m_queue->Mark();
584 ✗ }
585
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (const Ref<Buffer> view = source.Import(dst.GetBuffer())) {
586
0/1
✗ Branch 1 not taken.
✗ Queue& queue = source.GetQueue();
587
0/1
✗ Branch 1 not taken.
✗ queue.Follow(after);
588
0/2
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ const Operation copied = copy(queue, from, view->Slice(dst.Offset(), to.Bytes()));
589
0/6
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ queue.Retire(std::make_shared<Tensor>(dst), copied);
590
0/1
✗ Branch 1 not taken.
✗ m_queue->Follow(copied);
591
0/1
✗ Branch 1 not taken.
✗ return m_queue->Mark();
592 ✗ }
593 ✗ after.Wait();
594 ✗ const Download download = src.ToHostAsync();
595
0/1
✗ Branch 1 not taken.
✗ download.Wait();
596
0/2
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ m_queue->Write(to, download.Bytes().data());
597
0/1
✗ Branch 1 not taken.
✗ return m_queue->Mark();
598 ✗ }
599
600 ✗ Ref<Buffer> Device::Borrow(DLDevice source, uint64_t address, uint64_t bytes, std::string label) {
601
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (!ReadsDirectly(source)) {
602 ✗ throw UnsupportedError("{} can't read memory of device type {} directly", m_adapter.alias,
603
0/1
✗ Branch 2 not taken.
✗ int(source.device_type));
604 }
605
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ if (address == 0 || bytes == 0 || address % 4 != 0) {
606
0/1
✗ Branch 2 not taken.
✗ throw Error("Borrowed memory \"{}\" needs a word-aligned address and bytes", label);
607 }
608 ✗ return Ref<Buffer>(new BorrowedBuffer(
609 ✗ *this, {.bytes = bytes, .memory = Memory::Device, .label = std::move(label)}, address));
610 ✗ }
611
612 84932 Ref<Buffer> Device::CreateBuffer(BufferDesc desc, Span<const std::byte> data) {
613
2/2
✓ Branch 0 taken 84362 times.
✓ Branch 1 taken 570 times.
84932 if (desc.bytes == 0) {
614
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 84362 times.
84362 desc.bytes = std::max<uint64_t>(data.size(), 4);
615 }
616 84932 const Ref<Buffer> buffer = CreateBuffer(desc);
617
2/2
✓ Branch 0 taken 84386 times.
✓ Branch 1 taken 546 times.
84932 if (!data.empty()) {
618
2/2
✓ Branch 1 taken 84386 times.
✓ Branch 4 taken 84386 times.
84386 GetQueue().Write(buffer->Slice(0, data.size()), data.data());
619 }
620 84932 return buffer;
621 ✗ }
622
623 ✗ AccelSizes Device::AccelBuildSizes(const AccelInput& input) const {
624
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (m_traversal != Traversal::Hardware) {
625 ✗ throw UnsupportedError("AccelBuildSizes on {}, which traverses rays in software",
626
0/1
✗ Branch 2 not taken.
✗ m_adapter.alias);
627 }
628 ✗ const auto key = std::visit(
629 ✗ Overloaded{
630 ✗ [](const AccelTriangles& t) {
631 ✗ return std::tuple(0u, t.count, t.vertexCount, t.stride, t.indices.has_value(),
632 ✗ t.refittable);
633 },
634 ✗ [](const AccelBoxes& b) {
635 ✗ return std::tuple(1u, b.count, 0u, 0u, false, b.refittable);
636 },
637 ✗ [](const AccelInstances& i) { return std::tuple(2u, i.count, 0u, 0u, false, false); },
638 },
639 ✗ input);
640 ✗ const std::lock_guard lock(m_accelSizes->mutex);
641
0/1
✗ Branch 1 not taken.
✗ const auto [found, added] = m_accelSizes->sizes.try_emplace(key);
642
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (added) {
643
0/1
✗ Branch 1 not taken.
✗ found->second = HardwareBuildSizes(input);
644 }
645 ✗ return found->second;
646 ✗ }
647
648 ✗ Ref<Accel> Device::CreateAccel(const AccelInput& input, std::string label) {
649 ✗ const AccelSizes sizes = AccelBuildSizes(input);
650 ✗ Ref<Buffer> storage = CreateBuffer({.bytes = std::max<uint64_t>(sizes.storage, 256),
651 .usage = BufferUsage::AccelStorage,
652
0/1
✗ Branch 1 not taken.
✗ .label = label});
653
0/7
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ return NewAccel(std::move(storage), GetLevel(input), sizes, std::move(label));
654 ✗ }
655
656 ✗ AccelSizes Device::HardwareBuildSizes(const AccelInput&) const {
657
0/1
✗ Branch 2 not taken.
✗ throw UnsupportedError("{} builds no hardware acceleration structures", m_adapter.alias);
658 }
659
660 // NOLINTNEXTLINE(performance-unnecessary-value-param)
661 ✗ Ref<Accel> Device::NewAccel(Ref<Buffer>, AccelLevel, AccelSizes, std::string) {
662
0/1
✗ Branch 2 not taken.
✗ throw UnsupportedError("{} builds no hardware acceleration structures", m_adapter.alias);
663 }
664
665 28 Ref<Texture> Device::CreateTexture(const TextureDesc& desc) {
666
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 28 times.
28 if (m_sampling != Sampling::Hardware) {
667 ✗ throw UnsupportedError("CreateTexture on {}, which samples textures in software",
668
0/1
✗ Branch 2 not taken.
✗ m_adapter.alias);
669 }
670
2/4
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 28 times.
28 if (desc.width == 0 || desc.height == 0) {
671
0/1
✗ Branch 2 not taken.
✗ throw Error("texture \"{}\" is {}x{}", desc.label, desc.width, desc.height);
672 }
673
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 28 times.
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
28 const uint32_t levels = std::bit_width(std::max(desc.width, desc.height));
674
2/4
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 28 times.
28 if (desc.levels == 0 || desc.levels > levels) {
675 ✗ throw Error("texture \"{}\" has {} levels; {}x{} takes 1 to {}", desc.label, desc.levels,
676
0/1
✗ Branch 2 not taken.
✗ desc.width, desc.height, levels);
677 }
678
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
28 if (TexelBlockSide(desc.format) == 4 && !m_adapter.features.blockCompression) {
679 ✗ throw UnsupportedError("texture \"{}\" is block compressed, which {} can't sample",
680
0/1
✗ Branch 2 not taken.
✗ desc.label, m_adapter.alias);
681 }
682 28 Ref<Texture> texture = NewTexture(desc);
683
1/1
✓ Branch 1 taken 28 times.
28 CountMemory(*texture, 1);
684 28 return texture;
685 ✗ }
686
687 28 Texture::~Texture() {
688
1/2
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
28 if (m_counted) {
689 28 GetDevice().CountMemory(*this, -1);
690 }
691 28 }
692
693 ✗ Ref<Texture> Device::NewTexture(const TextureDesc& desc) {
694 ✗ throw UnsupportedError("{} creates no hardware textures, so \"{}\" can't be made",
695
0/1
✗ Branch 2 not taken.
✗ m_adapter.alias, desc.label);
696 }
697
698 ✗ Ref<Buffer> Device::Import(Buffer&) {
699 ✗ return {};
700 }
701
702 ✗ std::optional<DLDevice> Device::SharedDevice() {
703
0/3
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ switch (m_adapter.backend) {
704 ✗ case Backend::Cuda:
705 ✗ return DLDevice{kDLCUDA, int32_t(m_adapter.ordinal)};
706 ✗ case Backend::Hip:
707 ✗ return DLDevice{kDLROCM, int32_t(m_adapter.ordinal)};
708 ✗ default:
709 ✗ return std::nullopt;
710 }
711 }
712
713 9 bool Device::ReadsDirectly(DLDevice source) const {
714
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 9 times.
9 const bool api = (m_adapter.backend == Backend::Cuda && source.device_type == kDLCUDA) ||
715
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ (m_adapter.backend == Backend::Hip && source.device_type == kDLROCM);
716
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ return api && uint32_t(source.device_id) == m_adapter.ordinal;
717 }
718
719 ✗ void Device::CopyIn(BufferRange dst, uint64_t source, Stream producer) {
720
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (dst.Bytes() == 0) {
721 ✗ return;
722 }
723
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (!SharedDevice()) {
724
0/1
✗ Branch 2 not taken.
✗ throw UnsupportedError("{} shares no memory with CUDA or HIP", m_adapter.alias);
725 }
726
727
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ const Stream copier = producer.IsCompute() ? producer : CopyStream();
728 ✗ const Operation earlier = m_queue->Mark();
729
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (copier.IsCompute()) {
730 ✗ earlier.Wait(copier);
731 } else {
732 ✗ earlier.Wait();
733 }
734
735 ✗ ExternalCopy(dst, source, copier);
736 ✗ CountTraffic({.copyBytes = dst.Bytes()});
737 ✗ m_queue->Follow(copier);
738
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ if (producer.IsHost() && copier.IsCompute()) {
739 ✗ m_queue->Mark().Wait();
740 }
741 }
742
743 ✗ Ref<Buffer> Device::StageOnStream(uint64_t source, uint64_t bytes, Stream producer,
744 const std::string& label) {
745
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (!SharedDevice()) {
746
0/1
✗ Branch 2 not taken.
✗ throw UnsupportedError("{} shares no memory with CUDA or HIP", m_adapter.alias);
747 }
748
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (!producer.IsCompute()) {
749
0/1
✗ Branch 2 not taken.
✗ throw Error("Staging \"{}\" on its producer's stream needs a stream", label);
750 }
751 ✗ Ref<Buffer> staging = CreateBuffer(
752
0/1
✗ Branch 1 not taken.
✗ {.bytes = std::max<uint64_t>(bytes, 4), .usage = BufferUsage::Interop, .label = label});
753
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (bytes > 0) {
754
0/2
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ ExternalCopy(staging->Slice(0, bytes), source, producer);
755
0/3
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ CountTraffic({.copyBytes = bytes});
756 }
757 ✗ return staging;
758 ✗ }
759
760 ✗ Ref<Buffer> Device::Stage(uint64_t source, uint64_t bytes, Stream producer,
761 const std::string& label) {
762
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (!SharedDevice()) {
763
0/1
✗ Branch 2 not taken.
✗ throw UnsupportedError("{} shares no memory with CUDA or HIP", m_adapter.alias);
764 }
765 ✗ Ref<Buffer> staging =
766
0/1
✗ Branch 1 not taken.
✗ CreateBuffer({.bytes = bytes, .usage = BufferUsage::Interop, .label = label});
767
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (bytes == 0) {
768 return staging;
769 }
770
0/3
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ const Stream copier = producer.IsCompute() ? producer : CopyStream();
771
0/1
✗ Branch 1 not taken.
✗ ExternalCopy(*staging, source, copier);
772
0/1
✗ Branch 1 not taken.
✗ CountTraffic({.copyBytes = bytes});
773
0/1
✗ Branch 1 not taken.
✗ m_queue->Follow(copier);
774
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ if (producer.IsHost() && copier.IsCompute()) {
775
0/4
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ m_queue->Mark().Wait();
776 }
777 return staging;
778 ✗ }
779
780 ✗ void Device::ExternalCopy(BufferRange, uint64_t, Stream) {
781
0/1
✗ Branch 2 not taken.
✗ throw UnsupportedError("{} doesn't copy in foreign device memory", m_adapter.alias);
782 }
783
784 ✗ Stream Device::CopyStream() {
785 ✗ return Stream::None();
786 }
787
788 955274 void Device::Require(const Resource& resource, const char* what) const {
789
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 955274 times.
955274 if (&resource.GetDevice() != this) {
790
0/1
✗ Branch 2 not taken.
✗ throw Error("{} names \"{}\", which is on another device", what, resource.Label());
791 }
792 955274 }
793
794 ✗ std::vector<MemoryHeap> Device::Heaps() const {
795 ✗ return {};
796 }
797
798 10 MemoryStats Device::MemoryUsage() const {
799 10 std::vector<MemoryHeap> heaps = Heaps();
800
1/1
✓ Branch 1 taken 10 times.
10 std::lock_guard lock(m_counters->mutex);
801
1/1
✓ Branch 1 taken 10 times.
10 MemoryStats stats = m_counters->totals;
802 10 stats.heaps = std::move(heaps);
803
3/3
✓ Branch 1 taken 274 times.
✓ Branch 3 taken 274 times.
✓ Branch 4 taken 10 times.
284 for (const auto& [tag, use] : m_counters->uses) {
804
1/1
✓ Branch 1 taken 274 times.
274 stats.uses.push_back(use);
805 }
806 10 std::sort(stats.uses.begin(), stats.uses.end(), [](const MemoryUse& a, const MemoryUse& b) {
807
2/2
✓ Branch 0 taken 1602 times.
✓ Branch 1 taken 339 times.
1941 return a.bytes != b.bytes ? a.bytes > b.bytes : a.label < b.label;
808 });
809 20 return stats;
810 10 }
811
812 11 TrafficStats Device::Traffic() const {
813 11 const std::lock_guard lock(m_counters->mutex);
814 11 return m_counters->traffic;
815 11 }
816
817 344376 void Device::CountTraffic(const TrafficStats& traffic) {
818 344376 const std::lock_guard lock(m_counters->mutex);
819 344376 m_counters->traffic += traffic;
820 344376 }
821
822 1137 DeviceGlobals Device::Globals() const {
823 1137 return {.debug = {m_debug->Address()}, .debugCapacity = DebugRecords::kCapacity, .reserved = 0};
824 }
825
826 20 void Device::CreateGlobals() {
827 20 Ref<Buffer> debug = CreateBuffer(
828
1/1
✓ Branch 2 taken 20 times.
20 {.bytes = DebugRecords::Bytes(), .memory = Memory::Readback, .label = "debug"});
829 20 Adopt(*debug);
830
1/1
✓ Branch 1 taken 20 times.
20 m_debug = std::make_unique<DebugRecords>(std::move(debug));
831
832
1/1
✓ Branch 1 taken 20 times.
40 m_globals = CreateBuffer(
833
2/3
✓ Branch 1 taken 20 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 20 times.
40 {.bytes = sizeof(DeviceGlobals), .memory = Memory::Upload, .label = "globals"});
834 20 Adopt(*m_globals);
835
1/1
✓ Branch 1 taken 20 times.
20 const DeviceGlobals globals = Globals();
836
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
20 std::memcpy(m_globals->Mapped(), &globals, sizeof(globals));
837 100 }
838
839 ✗ void Device::CloseQueue(Queue& queue) noexcept {
840 ✗ queue.Close();
841 ✗ }
842
843 20 void Device::LastReleased() noexcept {
844
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 20 times.
20 m_programs = nullptr;
845 20 m_queue->Close();
846
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 m_debug.reset();
847
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 m_globals = nullptr;
848
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 m_queue.reset();
849 20 delete this;
850 20 }
851
852 120321 Resource::Resource(Device& device, std::string label)
853 120321 : m_device(&device), m_label(std::move(label)) {
854 120321 device.AddRef();
855 120321 }
856
857 120321 Resource::~Resource() {
858
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 120320 times.
120321 if (m_holdsDevice) {
859 1 m_device->Release();
860 }
861 120321 }
862
863 120320 void Resource::LastReleased() noexcept {
864 120320 m_device->Retire(*this);
865 120320 }
866
867 120320 void Device::Retire(Resource& resource) noexcept {
868 120320 const bool held = std::exchange(resource.m_holdsDevice, false);
869 120320 m_queue->Retire(resource);
870
2/2
✓ Branch 0 taken 119848 times.
✓ Branch 1 taken 472 times.
120320 if (held) {
871 119848 Release();
872 }
873 120320 }
874
875 472 void Device::Adopt(Resource& resource) {
876
1/2
✓ Branch 0 taken 472 times.
✗ Branch 1 not taken.
472 if (std::exchange(resource.m_holdsDevice, false)) {
877 472 resource.m_device->Release();
878 }
879 472 }
880
881 239966 void Device::CountMemory(Buffer& buffer, int sign) {
882
1/2
✓ Branch 1 taken 239966 times.
✗ Branch 2 not taken.
239966 const int64_t backed = sign * int64_t(buffer.FullyBacked() ? buffer.m_bytes : 0);
883
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 239966 times.
239966 const int64_t reserved = sign * int64_t(buffer.Virtual() ? buffer.m_bytes : 0);
884
885 239966 std::lock_guard lock(m_counters->mutex);
886
1/1
✓ Branch 1 taken 239966 times.
239966 MemoryUse& use = m_counters->uses[buffer.Label()];
887
1/1
✓ Branch 1 taken 239966 times.
239966 use.label = buffer.Label();
888 239966 use.buffers = uint32_t(int64_t(use.buffers) + sign);
889 239966 use.bytes = uint64_t(int64_t(use.bytes) + backed);
890
2/2
✓ Branch 0 taken 38750 times.
✓ Branch 1 taken 201216 times.
239966 if (use.buffers == 0 && use.textures == 0) {
891 38750 m_counters->uses.erase(buffer.Label());
892 }
893
894
2/2
✓ Branch 0 taken 182 times.
✓ Branch 1 taken 239784 times.
239966 MemoryStats& totals = m_counters->totals;
895
5/6
✓ Branch 0 taken 182 times.
✓ Branch 1 taken 239784 times.
✓ Branch 2 taken 168 times.
✓ Branch 3 taken 239616 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 239616 times.
239966 uint64_t& kind = buffer.m_memory == Memory::Upload ? totals.uploadBytes
896 : buffer.m_memory == Memory::Readback ? totals.readbackBytes
897 : buffer.m_memory == Memory::Shared ? totals.sharedBytes
898 : totals.deviceBytes;
899 239966 kind = uint64_t(int64_t(kind) + backed);
900 239966 totals.reservedBytes = uint64_t(int64_t(totals.reservedBytes) + reserved);
901 239966 buffer.m_counted = sign > 0;
902 239966 }
903
904 56 void Device::CountMemory(Texture& texture, int sign) {
905 56 uint64_t bytes = 0;
906
2/2
✓ Branch 0 taken 260 times.
✓ Branch 1 taken 56 times.
316 for (uint32_t level = 0; level < texture.Desc().levels; ++level) {
907 260 bytes += texture.LevelBytes(level);
908 }
909 56 const int64_t change = sign * int64_t(bytes);
910
911 56 std::lock_guard lock(m_counters->mutex);
912
1/1
✓ Branch 1 taken 56 times.
56 MemoryUse& use = m_counters->uses[texture.Label()];
913
1/1
✓ Branch 1 taken 56 times.
56 use.label = texture.Label();
914 56 use.textures = uint32_t(int64_t(use.textures) + sign);
915 56 use.bytes = uint64_t(int64_t(use.bytes) + change);
916
2/2
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 29 times.
56 if (use.buffers == 0 && use.textures == 0) {
917 27 m_counters->uses.erase(texture.Label());
918 }
919 56 MemoryStats& totals = m_counters->totals;
920 56 totals.deviceBytes = uint64_t(int64_t(totals.deviceBytes) + change);
921 56 totals.textureBytes = uint64_t(int64_t(totals.textureBytes) + change);
922 56 texture.m_counted = sign > 0;
923 56 }
924
925 }
926