chrono_batch_render/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
1048 / 1626
64.5%
Branches:
772 / 1633
47.3%
Functions:
96 / 123
78.0%

overlay/overlay.cpp
Line Branch Exec Source
1 // SPDX-License-Identifier: BSD-3-Clause
2 #include <algorithm>
3 #include <bit>
4 #include <cmath>
5 #include <cstring>
6 #include <initializer_list>
7 #include <limits>
8 #include <string>
9 #include <string_view>
10 #include <utility>
11
12 #include "chrono_batch_render/device/device.h"
13 #include "chrono_batch_render/device/queue.h"
14 #include "chrono_batch_render/device/slang/dispatch.h"
15 #include "chrono_batch_render/overlay/kernels.h"
16 #include "chrono_batch_render/overlay/overlay_impl.h"
17 #include "chrono_batch_render/util/error.h"
18 #include "chrono_batch_render/util/log.h"
19 #include "chrono_batch_render/util/profiler.h"
20 #include "chrono_batch_render/util/span.h"
21
22 namespace chrono::batch_render {
23
24 namespace {
25
26 using Batch = OverlayData::Batch;
27
28 constexpr uint64_t kLeastPairs = 1 << 16;
29
30 constexpr device::DType kFloats[] = {device::DType::Float32};
31 constexpr device::DType kWords[] = {device::DType::UInt32, device::DType::Int32};
32 constexpr device::DType kColors[] = {device::DType::Float32, device::DType::UInt8};
33
34 enum HostBuffer : size_t {
35 kHostPoints,
36 kHostEnds,
37 kHostColors,
38 kHostSizes,
39 kHostWorlds,
40 kHostRotations,
41 kHostScalars,
42 kHostExtras,
43 kHostCodes,
44 kHostPrims,
45 kHostGroups,
46 kHostIds,
47 kHostBuffers,
48 };
49
50 struct Layout {
51 int64_t worlds = 0;
52 int64_t rows = 0;
53 int64_t samples = 0;
54 };
55
56 struct HostRow {
57 Float3 point;
58 Float3 end;
59 Float4 rotation = {0, 0, 0, 1};
60 float scalar = 0;
61 uint32_t extra = 0;
62 };
63
64 204 uint32_t PackColor(Float4 color) {
65 204 return PackOverlayColor(color.x, color.y, color.z, color.w);
66 }
67
68 3 Float4 Rotation(Quatd q) {
69 3 return {float(q.x), float(q.y), float(q.z), float(q.w)};
70 }
71
72 34 std::string_view KindName(OverlayKind kind) {
73 34 constexpr std::string_view kNames[] = {
74 "lines", "markers", "boxes", "spheres", "circles", "frames", "arrows", "capsules",
75 "trails", "labels", "texts", "numbers", "triangles", "images", "prims", "outlines"};
76 34 return kNames[uint32_t(kind)];
77 }
78
79 1 uint32_t PrimPieces(OverlayPrimShape shape) {
80
1/6
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 time.
1 switch (shape) {
81 case OverlayPrimShape::Frames:
82 return 3;
83 ✗ case OverlayPrimShape::Bounds:
84 ✗ return 12;
85 ✗ case OverlayPrimShape::Frustums:
86 ✗ return kOverlayFrustumLines;
87 ✗ case OverlayPrimShape::Lights:
88 ✗ return kOverlayLightLines;
89 ✗ case OverlayPrimShape::CameraImages:
90 ✗ return 2;
91 }
92 ✗ return 1;
93 }
94
95 48 uint32_t PiecesPerRow(OverlayKind kind, uint32_t samples) {
96
7/9
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 28 times.
✗ Branch 8 not taken.
48 switch (kind) {
97 case OverlayKind::Images:
98 return 2;
99 3 case OverlayKind::Boxes:
100 3 return 12;
101 3 case OverlayKind::Spheres:
102 3 return 3 * kOverlaySphereSegments;
103 3 case OverlayKind::Circles:
104 3 return kOverlayCircleSegments;
105 3 case OverlayKind::Frames:
106 3 return 3;
107 5 case OverlayKind::Arrows:
108 5 return 1 + kOverlayArrowHeadLines;
109 3 case OverlayKind::Capsules:
110 3 return 2 * kOverlayCapsuleSegments + 4 + 4 * kOverlayCapsuleArcSegments;
111 ✗ case OverlayKind::Trails:
112 ✗ return samples - 1;
113 28 default:
114 28 return 1;
115 }
116 }
117
118 192 bool Textual(OverlayKind kind) {
119 192 return kind == OverlayKind::Labels || kind == OverlayKind::Texts ||
120 192 kind == OverlayKind::Numbers;
121 }
122
123 149 bool Hosted(const Batch& batch) {
124
5/8
✓ Branch 0 taken 143 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 143 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 143 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 143 times.
149 return batch.points.Empty() && batch.chars.Empty() && batch.scalars.Empty() &&
125
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 143 times.
143 batch.prims.Empty();
126 }
127
128 20 bool SameKey(const Batch& batch, OverlayKind kind, const OverlayStyle& style) {
129 20 const OverlayStyle& key = batch.style;
130
3/4
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
20 return Hosted(batch) && batch.kind == kind && kind != OverlayKind::Images &&
131
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 kind != OverlayKind::Prims && kind != OverlayKind::Outlines &&
132
3/6
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
6 key.depth == style.depth && key.anchor == style.anchor && key.dash == style.dash &&
133
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 key.gap == style.gap && key.units == style.units &&
134
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 key.screenSized == style.screenSized && key.facing == style.facing &&
135
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
6 key.pickable == style.pickable && key.screen == style.screen &&
136
1/4
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
20 (kind != OverlayKind::Markers || key.marker == style.marker) &&
137
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
6 (!Textual(kind) || (key.background == style.background && key.halo == style.halo &&
138
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ key.align == style.align && key.width == style.width));
139 }
140
141 96 uint32_t PackPattern(float pixels) {
142
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 96 times.
96 return uint32_t(std::clamp(std::lround(pixels), 0l, long(kOverlayPatternMask)));
143 }
144
145 struct ImageLayout {
146 uint32_t width = 0;
147 uint32_t height = 0;
148 uint32_t rowStride = 0;
149 uint32_t layerStride = 0;
150 bool layered = false;
151 };
152
153 ✗ void PackAffix(std::string_view text, uint32_t (&words)[4], uint32_t& length,
154 std::string_view what) {
155 ✗ const std::vector<uint8_t> codes = OverlayCodes(text);
156
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (codes.size() > kOverlayAffixChars) {
157 ✗ throw Error("An overlay number's {} \"{}\" has {} characters; at most {} fit", what, text,
158
0/1
✗ Branch 2 not taken.
✗ codes.size(), kOverlayAffixChars);
159 }
160
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ for (size_t at = 0; at < codes.size(); ++at) {
161 ✗ words[at / 4] |= uint32_t(codes[at]) << (8 * (at % 4));
162 }
163 ✗ length = uint32_t(codes.size());
164 ✗ }
165
166 37 void CheckScreen(OverlayKind kind, const OverlayStyle& style, bool placed) {
167
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 30 times.
37 if (!style.screen) {
168 return;
169 }
170
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 4 times.
7 if (kind == OverlayKind::Prims || kind == OverlayKind::Outlines || kind == OverlayKind::Texts) {
171 3 throw Error("Overlay {} have no screen anchor; only shapes, labels, numbers and images "
172 "take one",
173
1/1
✓ Branch 2 taken 3 times.
3 KindName(kind));
174 }
175
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (placed) {
176 ✗ throw Error("Overlay {} take a screen placement or a screen anchor, not both",
177
0/1
✗ Branch 2 not taken.
✗ KindName(kind));
178 }
179
4/6
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
4 if (style.anchor != kNoPrim || style.units == OverlayUnits::Meters || style.screenSized ||
180
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 style.facing) {
181 1 throw Error("Overlay {} on the screen are in pixels, so they take no anchor prim, meters, "
182 "screen sizing or facing",
183
1/1
✓ Branch 2 taken 1 time.
1 KindName(kind));
184 }
185 }
186
187 24 Batch& HostBatch(OverlayData& contents, OverlayKind kind, const OverlayStyle& style) {
188 24 CheckScreen(kind, style, false);
189
4/4
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 14 times.
✓ Branch 3 taken 6 times.
22 if (contents.batches.empty() || !SameKey(contents.batches.back(), kind, style)) {
190 16 contents.batches.push_back({.kind = kind, .style = style});
191 }
192 22 return contents.batches.back();
193 16 }
194
195 52 void AppendRow(Batch& batch, const HostRow& row, uint32_t color, float size, uint32_t world,
196 uint32_t id) {
197 52 batch.hostIds.push_back(id);
198 52 batch.hostPoints.push_back(row.point);
199 52 batch.hostEnds.push_back(row.end);
200 52 batch.hostColors.push_back(color);
201 52 batch.hostSizes.push_back(size);
202 52 batch.hostWorlds.push_back(world);
203 52 batch.hostRotations.push_back(row.rotation);
204 52 batch.hostScalars.push_back(row.scalar);
205 52 batch.hostExtras.push_back(row.extra);
206 52 }
207
208 42 void AppendRow(Batch& batch, const HostRow& row, const OverlayStyle& style) {
209 42 AppendRow(batch, row, PackColor(style.color), style.width, style.world, style.id);
210 42 }
211
212 2 void AppendText(Batch& batch, std::string_view text, const HostRow& row,
213 const OverlayStyle& style) {
214 2 std::vector<uint8_t> codes = OverlayCodes(text);
215
2/3
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✓ Branch 3 taken 2 times.
2 codes.resize(std::min<size_t>(codes.size(), kOverlayTextChars));
216
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 const auto start = uint32_t(batch.hostCodes.size());
217
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (start + codes.size() >= (1u << 24)) {
218
0/1
✗ Branch 2 not taken.
✗ throw UnsupportedError("An overlay's texts hold more than {} characters", 1u << 24);
219 }
220
1/1
✓ Branch 1 taken 2 times.
2 batch.hostCodes.insert(batch.hostCodes.end(), codes.begin(), codes.end());
221
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 batch.maxChars = std::max(batch.maxChars, uint32_t(codes.size()));
222 2 HostRow packed = row;
223 2 packed.extra = start | uint32_t(codes.size()) << 24;
224
1/1
✓ Branch 2 taken 2 times.
2 AppendRow(batch, packed, PackColor(style.color), style.size, style.world, style.id);
225 2 }
226
227 8 int64_t Stride(const device::Tensor& tensor, size_t dim) {
228
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 if (!tensor.Strides().empty()) {
229 8 return tensor.Strides()[dim];
230 }
231 ✗ int64_t stride = 1;
232
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ for (size_t after = dim + 1; after < tensor.Shape().size(); ++after) {
233 ✗ stride *= tensor.Shape()[after];
234 }
235 return stride;
236 }
237
238 2 Layout CheckPoints(const device::Tensor& points, std::string_view batch, bool trail) {
239
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (points.Empty()) {
240
0/1
✗ Branch 2 not taken.
✗ throw Error("Overlay {} need points", batch);
241 }
242 2 const Span<const int64_t> shape = points.Shape();
243 2 const bool shaped =
244
2/10
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
2 trail ? shape.size() == 3 && shape[1] >= 2 : shape.size() == 2 || shape.size() == 3;
245
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 if (!shaped || shape.back() != 3 || points.Type() != device::DType::Float32) {
246
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (trail) {
247 ✗ throw Error("Overlay {}' points are an [N, samples, 3] float32 tensor of at least 2 "
248 "samples",
249
0/1
✗ Branch 2 not taken.
✗ batch);
250 }
251
0/1
✗ Branch 2 not taken.
✗ throw Error("Overlay {}' points are an [N, 3] or [worlds, N, 3] float32 tensor", batch);
252 }
253
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 if (Stride(points, shape.size() - 1) != 1) {
254
0/1
✗ Branch 2 not taken.
✗ throw UnsupportedError("Overlay {}' points need their x, y and z side by side", batch);
255 }
256
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 const int64_t rows = trail ? shape[0] : shape[shape.size() - 2];
257
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (rows > std::numeric_limits<uint32_t>::max()) {
258 ✗ throw Error("Overlay {} of {} rows; at most {} fit", batch, rows,
259
0/1
✗ Branch 2 not taken.
✗ std::numeric_limits<uint32_t>::max());
260 }
261
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (trail) {
262 ✗ return {0, rows, shape[1]};
263 }
264
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 return {shape.size() == 3 ? shape[0] : 0, rows, 0};
265 }
266
267 14 void CheckField(const device::Tensor& field, const device::Tensor& points, Layout layout,
268 std::string_view batch, std::string_view name, int64_t width,
269 std::span<const device::DType> types) {
270
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 2 times.
14 if (field.Empty()) {
271 12 return;
272 }
273 2 std::vector<int64_t> expected;
274
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (layout.worlds > 0) {
275
0/1
✗ Branch 1 not taken.
✗ expected.push_back(layout.worlds);
276 }
277
1/1
✓ Branch 1 taken 2 times.
2 expected.push_back(layout.rows);
278
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (width > 0) {
279
1/1
✓ Branch 1 taken 2 times.
2 expected.push_back(width);
280 }
281
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 const bool typed = std::ranges::find(types, field.Type()) != types.end();
282
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
4 if (!typed || !std::ranges::equal(field.Shape(), expected)) {
283 ✗ std::string names;
284
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ for (const device::DType type : types) {
285
0/6
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 8 not taken.
✗ names += std::string(names.empty() ? "" : " or ") +
286 (type == device::DType::Float32 ? "float32"
287
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ : type == device::DType::UInt8 ? "uint8"
288
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ : type == device::DType::Int32 ? "int32"
289 ✗ : "uint32");
290 }
291 ✗ throw Error("Overlay {}' {} are a [{}N{}] {} tensor, a row per point", batch, name,
292
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ layout.worlds > 0 ? "worlds, " : "",
293
0/10
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 7 not taken.
✗ Branch 10 not taken.
✗ Branch 13 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ width > 0 ? ", " + std::to_string(width) : "", names);
294 ✗ }
295
3/4
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 2 times.
2 if (&field.GetDevice() != &points.GetDevice()) {
296
0/1
✗ Branch 2 not taken.
✗ throw Error("Overlay {}' {} are on another device than their points", batch, name);
297 }
298
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
2 if (width > 0 && Stride(field, expected.size() - 1) != 1) {
299
0/1
✗ Branch 2 not taken.
✗ throw UnsupportedError("Overlay {}' {} need their channels side by side", batch, name);
300 }
301
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (field.Type() == device::DType::UInt8) {
302
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ for (size_t dim = 0; dim + 1 < expected.size(); ++dim) {
303
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (Stride(field, dim) % 4 != 0) {
304 ✗ throw UnsupportedError("Overlay {}' uint8 {} need rows a multiple of 4 bytes apart",
305
0/1
✗ Branch 2 not taken.
✗ batch, name);
306 }
307 }
308 }
309 2 }
310
311 2 void CheckItemCount(const Batch& batch, std::string_view name, int64_t counts) {
312
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (batch.itemCount.Empty()) {
313 return;
314 }
315
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (std::ranges::find(kWords, batch.itemCount.Type()) == std::end(kWords) ||
316
0/6
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ batch.itemCount.Shape().size() != 1 || batch.itemCount.Shape()[0] != counts) {
317
0/1
✗ Branch 2 not taken.
✗ throw Error("Overlay {}' item counts are a [{}] uint32 tensor", name, counts);
318 }
319 }
320
321 2 void Require(const device::Tensor& field, std::string_view batch, std::string_view name) {
322
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (field.Empty()) {
323
0/1
✗ Branch 2 not taken.
✗ throw Error("Overlay {} need {}", batch, name);
324 }
325 2 }
326
327 ✗ ImageLayout CheckImage(const device::Tensor& pixels, int64_t rows) {
328 ✗ const Span<const int64_t> shape = pixels.Shape();
329
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ const size_t rank = shape.size();
330
0/8
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ if (pixels.Type() != device::DType::UInt8 || (rank != 3 && rank != 4) || shape[rank - 1] != 4 ||
331
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ (rank == 4 && shape[0] != rows)) {
332 ✗ throw Error("An overlay image is a [height, width, 4] uint8 tensor, or [N, height, width, "
333
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ "4] with one per row");
334 }
335
0/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ if (Stride(pixels, rank - 1) != 1 || Stride(pixels, rank - 2) != 4 ||
336
0/6
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Stride(pixels, rank - 3) % 4 != 0 || (rank == 4 && Stride(pixels, 0) % 4 != 0) ||
337
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ pixels.Address() % 4 != 0) {
338
0/1
✗ Branch 2 not taken.
✗ throw UnsupportedError("An overlay image needs its pixels side by side, 4-byte aligned");
339 }
340
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ if (shape[rank - 2] > std::numeric_limits<uint32_t>::max() ||
341
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ shape[rank - 3] > std::numeric_limits<uint32_t>::max()) {
342 ✗ throw UnsupportedError("An overlay image of {} x {} pixels is too big", shape[rank - 2],
343
0/1
✗ Branch 2 not taken.
✗ shape[rank - 3]);
344 }
345 ✗ return {.width = uint32_t(shape[rank - 2]),
346 ✗ .height = uint32_t(shape[rank - 3]),
347 ✗ .rowStride = uint32_t(Stride(pixels, rank - 3) / 4),
348 ✗ .layerStride = rank == 4 ? uint32_t(Stride(pixels, 0) / 4) : 0,
349
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ .layered = rank == 4};
350 }
351
352 ✗ void CheckPrims(const Batch& batch) {
353 ✗ const device::Tensor& prims = batch.prims;
354
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.
✗ if (prims.Empty() || std::ranges::find(kWords, prims.Type()) == std::end(kWords) ||
355
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ prims.Shape().size() != 1) {
356
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ throw Error("Overlay prims are an [N] uint32 tensor of prim indices");
357 }
358
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ const Layout layout{0, prims.Shape()[0], 0};
359
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (!batch.groups.Empty()) {
360
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (batch.primShape != OverlayPrimShape::Bounds) {
361
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ throw Error("Only overlay prim bounds gather prims into groups");
362 }
363
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (batch.groupCount == 0) {
364
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ throw Error("Overlay prim groups need a group count");
365 }
366 ✗ CheckField(batch.groups, prims, layout, "prims", "groups", 0, kWords);
367 }
368
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ const Layout rows{0, batch.groups.Empty() ? layout.rows : int64_t(batch.groupCount), 0};
369
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ CheckField(batch.colors, batch.groups.Empty() ? prims : batch.groups, rows, "prims", "colors",
370 4, kColors);
371
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ CheckField(batch.sizes, batch.groups.Empty() ? prims : batch.groups, rows, "prims", "widths", 0,
372 kFloats);
373
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (!batch.pixels.Empty()) {
374
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (batch.primShape != OverlayPrimShape::CameraImages) {
375
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ throw Error("Only overlay camera images take pixels");
376 }
377 ✗ CheckImage(batch.pixels, layout.rows);
378 }
379 ✗ }
380
381 ✗ void CheckFill(const Batch& batch) {
382 ✗ const std::string_view name = KindName(batch.kind);
383 ✗ Layout layout;
384
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (batch.kind == OverlayKind::Triangles) {
385 ✗ const Span<const int64_t> shape = batch.points.Shape();
386
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.
✗ if (batch.points.Type() != device::DType::Float32 || shape.size() != 3 || shape[1] != 3 ||
387
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ shape[2] != 3) {
388
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ throw Error("Overlay triangles' points are an [N, 3, 3] float32 tensor");
389 }
390
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ if (Stride(batch.points, 2) != 1 || Stride(batch.points, 1) != 3) {
391
0/1
✗ Branch 2 not taken.
✗ throw UnsupportedError("Overlay triangles need each row's corners side by side");
392 }
393 ✗ layout = {0, shape[0], 0};
394 } else {
395 ✗ layout = CheckPoints(batch.points, name, false);
396
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (layout.worlds > 0) {
397
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ throw Error("Overlay images' points are an [N, 3] float32 tensor");
398 }
399 ✗ Require(batch.ends, name, "rights");
400 ✗ Require(batch.rotations, name, "downs");
401 ✗ CheckField(batch.ends, batch.points, layout, name, "rights", 3, kFloats);
402 ✗ CheckField(batch.rotations, batch.points, layout, name, "downs", 3, kFloats);
403 ✗ Require(batch.pixels, name, "pixels");
404 ✗ CheckImage(batch.pixels, layout.rows);
405 }
406 ✗ CheckField(batch.colors, batch.points, layout, name, "colors", 4, kColors);
407 ✗ CheckField(batch.worlds, batch.points, layout, name, "worlds", 0, kWords);
408 ✗ CheckItemCount(batch, name, 1);
409 ✗ }
410
411 ✗ void CheckText(const Batch& batch) {
412 ✗ const std::string_view name = KindName(batch.kind);
413 ✗ const bool numbers = batch.kind == OverlayKind::Numbers;
414
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ const device::Tensor& source = numbers ? batch.scalars : batch.chars;
415 ✗ Require(source, name, numbers ? "values" : "chars");
416 ✗ const Span<const int64_t> shape = source.Shape();
417
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.
✗ if (numbers ? source.Type() != device::DType::Float32 || shape.size() != 1
418
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.
✗ : source.Type() != device::DType::UInt8 || shape.size() != 2 || shape[1] < 1) {
419 ✗ throw Error(numbers ? "Overlay numbers' values are an [N] float32 tensor"
420
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 7 not taken.
✗ : "Overlay texts' chars are an [N, characters] uint8 tensor");
421 }
422
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.
✗ if (!numbers && !source.Strides().empty() && source.Strides()[1] != 1) {
423
0/1
✗ Branch 2 not taken.
✗ throw UnsupportedError("Overlay texts' chars need their characters side by side");
424 }
425
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ const Layout layout{0, shape[0], 0};
426
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ if (batch.points.Empty() && !batch.placement) {
427
0/1
✗ Branch 2 not taken.
✗ throw Error("Overlay {} need points, or a placement on the screen", name);
428 }
429
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (!batch.points.Empty()) {
430 ✗ const Layout placed = CheckPoints(batch.points, name, false);
431
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ if (placed.worlds > 0 || placed.rows != layout.rows) {
432
0/1
✗ Branch 2 not taken.
✗ throw Error("Overlay {}' points are an [N, 3] tensor, a row per text", name);
433 }
434 }
435 ✗ CheckField(batch.colors, source, layout, name, "colors", 4, kColors);
436 ✗ CheckField(batch.sizes, source, layout, name, "sizes", 0, kFloats);
437 ✗ CheckField(batch.worlds, source, layout, name, "worlds", 0, kWords);
438 ✗ CheckItemCount(batch, name, 1);
439
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (batch.format.decimals > kOverlayMostDecimals) {
440 ✗ throw Error("Overlay numbers with {} decimals; at most {} fit", batch.format.decimals,
441
0/1
✗ Branch 2 not taken.
✗ kOverlayMostDecimals);
442 }
443 ✗ }
444
445 2 void CheckBatch(const Batch& batch) {
446 2 CheckScreen(batch.kind, batch.style, batch.placement.has_value());
447
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (Textual(batch.kind)) {
448 ✗ CheckText(batch);
449 ✗ return;
450 }
451
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (batch.kind == OverlayKind::Prims) {
452 ✗ CheckPrims(batch);
453 ✗ return;
454 }
455
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (batch.kind == OverlayKind::Triangles || batch.kind == OverlayKind::Images) {
456 ✗ CheckFill(batch);
457 ✗ return;
458 }
459 2 const std::string_view name = KindName(batch.kind);
460 2 const OverlayKind kind = batch.kind;
461 2 const Layout layout = CheckPoints(batch.points, name, kind == OverlayKind::Trails);
462 2 const std::string_view ends = kind == OverlayKind::Boxes ? "half extents"
463
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 : kind == OverlayKind::Circles ? "normals"
464
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 : "ends";
465
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (kind == OverlayKind::Lines || kind == OverlayKind::Arrows || kind == OverlayKind::Boxes ||
466 kind == OverlayKind::Circles) {
467 2 Require(batch.ends, name, ends);
468 }
469 2 CheckField(batch.ends, batch.points, layout, name, ends, 3, kFloats);
470 2 CheckField(batch.rotations, batch.points, layout, name, "orientations", 4, kFloats);
471 2 const std::string_view scalars = kind == OverlayKind::Frames ? "lengths"
472
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 : kind == OverlayKind::Arrows ? "head lengths"
473
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 : "radii";
474 2 if (kind == OverlayKind::Spheres || kind == OverlayKind::Circles ||
475
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 kind == OverlayKind::Capsules) {
476 ✗ Require(batch.scalars, name, scalars);
477 }
478 2 CheckField(batch.scalars, batch.points, layout, name, scalars, 0, kFloats);
479
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (kind == OverlayKind::Capsules) {
480 ✗ Require(batch.extras, name, "half heights");
481 ✗ CheckField(batch.extras, batch.points, layout, name, "half heights", 0, kFloats);
482 } else {
483 2 CheckField(batch.extras, batch.points, layout, name, "heads", 0, kWords);
484 }
485 2 CheckField(batch.colors, batch.points, layout, name, "colors", 4, kColors);
486
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
4 CheckField(batch.sizes, batch.points, layout, name,
487 kind == OverlayKind::Markers ? "sizes" : "widths", 0, kFloats);
488
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
2 if (!batch.worlds.Empty() && layout.worlds > 0) {
489
0/1
✗ Branch 2 not taken.
✗ throw Error("Overlay {} with a world per row can't also have worlds", name);
490 }
491 2 CheckField(batch.worlds, batch.points, layout, name, "worlds", 0, kWords);
492
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
4 CheckItemCount(batch, name, std::max<int64_t>(layout.worlds, 1));
493
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!batch.itemCount.Empty()) {
494
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ if (&batch.itemCount.GetDevice() != &batch.points.GetDevice()) {
495
0/1
✗ Branch 2 not taken.
✗ throw Error("Overlay {}' item counts are on another device than their points", name);
496 }
497 }
498 }
499
500 2 void Push(OverlayData& data, Batch batch) {
501 2 CheckBatch(batch);
502 2 data.batches.push_back(std::move(batch));
503 2 }
504
505 4 uint32_t Words(int64_t value, std::string_view what) {
506
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (value < 0 || value > std::numeric_limits<uint32_t>::max()) {
507 ✗ throw UnsupportedError("An overlay's {} are {} words apart; at most {} fit", what, value,
508
0/1
✗ Branch 2 not taken.
✗ std::numeric_limits<uint32_t>::max());
509 }
510 4 return uint32_t(value);
511 }
512
513 342 void RequireDevice(const device::Tensor& tensor, device::Device& device, std::string_view what) {
514
3/4
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 334 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
342 if (!tensor.Empty() && &tensor.GetDevice() != &device) {
515 ✗ throw Error("Overlay {} are on {}, not the renderer's {}; Device::Copy or Device::Import "
516 "moves them",
517
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ what, tensor.GetDevice().Adapter().alias, device.Adapter().alias);
518 }
519 342 }
520
521 }
522
523 15 Overlay::Overlay() : m_data(std::make_shared<OverlayData>()) {}
524
525
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26 Overlay::~Overlay() = default;
526
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 Overlay::Overlay(const Overlay& other) = default;
527 ✗ Overlay::Overlay(Overlay&& other) noexcept = default;
528 ✗ Overlay& Overlay::operator=(const Overlay& other) = default;
529 ✗ Overlay& Overlay::operator=(Overlay&& other) noexcept = default;
530
531 34 OverlayData& Overlay::Edit() {
532
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 34 times.
34 if (!m_data) {
533
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ m_data = std::make_shared<OverlayData>();
534
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 34 times.
34 } else if (m_data.use_count() > 1) {
535
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ m_data = std::make_shared<OverlayData>(*m_data);
536 }
537 34 return *m_data;
538 }
539
540 3 Overlay& Overlay::Line(Float3 a, Float3 b, const OverlayStyle& style) {
541 3 AppendRow(HostBatch(Edit(), OverlayKind::Lines, style), {a, b}, style);
542 2 return *this;
543 }
544
545 1 Overlay& Overlay::Lines(Span<const Float3> endpoints, const OverlayStyle& style,
546 Span<const Float4> colors) {
547
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if (endpoints.size() % 2 != 0) {
548
0/1
✗ Branch 2 not taken.
✗ throw Error("Overlay lines of {} endpoints; they come in pairs", endpoints.size());
549 }
550
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 if (!colors.empty() && colors.size() != endpoints.size() / 2) {
551
0/1
✗ Branch 2 not taken.
✗ throw Error("{} colors for {} overlay lines", colors.size(), endpoints.size() / 2);
552 }
553
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 if (endpoints.empty()) {
554 return *this;
555 }
556 1 Batch& batch = HostBatch(Edit(), OverlayKind::Lines, style);
557
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 1 time.
3 for (size_t line = 0; line < endpoints.size() / 2; ++line) {
558 2 AppendRow(batch, {endpoints[2 * line], endpoints[2 * line + 1]},
559 2 PackColor(colors.empty() ? style.color : colors[line]), style.width, style.world,
560
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 style.id);
561 }
562 return *this;
563 }
564
565 5 Overlay& Overlay::Polyline(Span<const Float3> points, bool closed, const OverlayStyle& style) {
566
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (points.size() < 2) {
567 return *this;
568 }
569 5 Batch& batch = HostBatch(Edit(), OverlayKind::Lines, style);
570
2/2
✓ Branch 1 taken 26 times.
✓ Branch 2 taken 5 times.
31 for (size_t at = 0; at + 1 < points.size(); ++at) {
571 26 AppendRow(batch, {points[at], points[at + 1]}, style);
572 }
573
3/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
5 if (closed && points.size() > 2) {
574 3 AppendRow(batch, {points[points.size() - 1], points[0]}, style);
575 }
576 return *this;
577 }
578
579 2 Overlay& Overlay::Marker(Float3 point, const OverlayStyle& style) {
580 2 return Markers(Span<const Float3>(&point, 1), style);
581 }
582
583 3 Overlay& Overlay::Markers(Span<const Float3> points, const OverlayStyle& style,
584 Span<const Float4> colors) {
585
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
3 if (!colors.empty() && colors.size() != points.size()) {
586
0/1
✗ Branch 2 not taken.
✗ throw Error("{} colors for {} overlay markers", colors.size(), points.size());
587 }
588
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (points.empty()) {
589 return *this;
590 }
591 3 Batch& batch = HostBatch(Edit(), OverlayKind::Markers, style);
592
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 3 times.
9 for (size_t at = 0; at < points.size(); ++at) {
593 6 AppendRow(batch, {points[at]}, PackColor(colors.empty() ? style.color : colors[at]),
594
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 style.size, style.world, style.id);
595 }
596 return *this;
597 }
598
599 1 Overlay& Overlay::Box(Float3 center, Float3 halfExtents, const OverlayStyle& style,
600 Quatd orientation) {
601 1 AppendRow(HostBatch(Edit(), OverlayKind::Boxes, style),
602 1 {.point = center, .end = halfExtents, .rotation = Rotation(orientation)}, style);
603 1 return *this;
604 }
605
606 1 Overlay& Overlay::Sphere(Float3 center, float radius, const OverlayStyle& style) {
607 1 AppendRow(HostBatch(Edit(), OverlayKind::Spheres, style), {.point = center, .scalar = radius},
608 style);
609 1 return *this;
610 }
611
612 1 Overlay& Overlay::Circle(Float3 center, Float3 normal, float radius, const OverlayStyle& style) {
613 1 AppendRow(HostBatch(Edit(), OverlayKind::Circles, style),
614 {.point = center, .end = normal, .scalar = radius}, style);
615 1 return *this;
616 }
617
618 1 Overlay& Overlay::Frame(Float3 origin, Quatd orientation, float length, const OverlayStyle& style) {
619 1 AppendRow(HostBatch(Edit(), OverlayKind::Frames, style),
620 1 {.point = origin, .rotation = Rotation(orientation), .scalar = length}, style);
621 1 return *this;
622 }
623
624 1 Overlay& Overlay::Arrow(Float3 from, Float3 to, const OverlayStyle& style, float headLength) {
625 1 AppendRow(HostBatch(Edit(), OverlayKind::Arrows, style),
626 {.point = from, .end = to, .scalar = headLength}, style);
627 1 return *this;
628 }
629
630 1 Overlay& Overlay::Capsule(Float3 center, Quatd orientation, float radius, float halfHeight,
631 const OverlayStyle& style) {
632 1 AppendRow(HostBatch(Edit(), OverlayKind::Capsules, style),
633 {.point = center,
634 1 .rotation = Rotation(orientation),
635 .scalar = radius,
636 1 .extra = std::bit_cast<uint32_t>(halfHeight)},
637 style);
638 1 return *this;
639 }
640
641 2 Float2 ScreenPlacement::Alignment() const {
642
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ const auto side = [](float value) { return value < 0.5f ? 0.0f : value > 0.5f ? 1.0f : 0.5f; };
643
3/6
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 2 times.
2 return align ? *align : Float2{side(at.x), side(at.y)};
644 }
645
646 1 Overlay& Overlay::Label(Float3 point, std::string_view text, const OverlayStyle& style) {
647 1 AppendText(HostBatch(Edit(), OverlayKind::Labels, style), text, {.point = point}, style);
648 1 return *this;
649 }
650
651 2 Overlay& Overlay::Text(const ScreenPlacement& placement, std::string_view text,
652 const OverlayStyle& style) {
653 2 const Float2 align = placement.Alignment();
654 2 AppendText(HostBatch(Edit(), OverlayKind::Texts, style), text,
655 2 {.point = {placement.at.x, placement.at.y, 0},
656 2 .end = {placement.offset.x, placement.offset.y, 0},
657 2 .rotation = {align.x, align.y, 0, 1}},
658 style);
659 1 return *this;
660 }
661
662 1 Overlay& Overlay::Triangle(Float3 a, Float3 b, Float3 c, const OverlayStyle& style) {
663 1 AppendRow(HostBatch(Edit(), OverlayKind::Triangles, style),
664 1 {.point = a, .end = b, .rotation = {c.x, c.y, c.z, 1}, .extra = 7}, style);
665 1 return *this;
666 }
667
668 2 Overlay& Overlay::Polygon(Span<const Float3> points, const OverlayStyle& style) {
669
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (points.size() < 3) {
670 return *this;
671 }
672 2 Batch& batch = HostBatch(Edit(), OverlayKind::Triangles, style);
673 2 const size_t last = points.size() - 2;
674
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
6 for (size_t at = 1; at <= last; ++at) {
675
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 const Float3 c = points[at + 1];
676
4/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
6 const uint32_t edges = 2u | (at == 1 ? 1u : 0u) | (at == last ? 4u : 0u);
677 4 AppendRow(
678 batch,
679 4 {.point = points[0], .end = points[at], .rotation = {c.x, c.y, c.z, 1}, .extra = edges},
680 style);
681 }
682 return *this;
683 }
684
685 1 Overlay& Overlay::Curve(Span<const Float3> controls, const OverlayStyle& style,
686 uint32_t tessellation) {
687
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
1 if (controls.size() < 4 || (controls.size() - 1) % 3 != 0) {
688
0/1
✗ Branch 2 not taken.
✗ throw Error("An overlay curve has 3N + 1 control points, not {}", controls.size());
689 }
690
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if (tessellation == 0) {
691
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ throw Error("An overlay curve needs at least one segment per span");
692 }
693 1 std::vector<Float3> points = {controls[0]};
694
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 for (size_t span = 0; span + 3 < controls.size(); span += 3) {
695 1 const Float3 a = controls[span];
696 1 const Float3 b = controls[span + 1];
697 1 const Float3 c = controls[span + 2];
698 1 const Float3 d = controls[span + 3];
699
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 1 time.
17 for (uint32_t step = 1; step <= tessellation; ++step) {
700 16 const float t = float(step) / float(tessellation);
701 16 const float u = 1 - t;
702 16 const float wa = u * u * u;
703 16 const float wb = 3 * u * u * t;
704 16 const float wc = 3 * u * t * t;
705 16 const float wd = t * t * t;
706 16 points.push_back({wa * a.x + wb * b.x + wc * c.x + wd * d.x,
707 16 wa * a.y + wb * b.y + wc * c.y + wd * d.y,
708
1/1
✓ Branch 1 taken 16 times.
16 wa * a.z + wb * b.z + wc * c.z + wd * d.z});
709 }
710 }
711
1/1
✓ Branch 2 taken 1 time.
2 return Polyline(points, false, style);
712 1 }
713
714 1 Overlay& Overlay::Arc(Float3 center, Float3 normal, float radius, float begin, float end,
715 const OverlayStyle& style, bool filled) {
716
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 const float length = std::sqrt(normal.x * normal.x + normal.y * normal.y + normal.z * normal.z);
717
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
1 if (!(length > 0) || !(radius > 0)) {
718
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ throw Error("An overlay arc needs a normal and a positive radius");
719 }
720 1 const Float3 n = {normal.x / length, normal.y / length, normal.z / length};
721
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 const Float3 other = std::abs(n.z) < 0.9f ? Float3{0, 0, 1} : Float3{1, 0, 0};
722 1 Float3 u = {other.y * n.z - other.z * n.y, other.z * n.x - other.x * n.z,
723 1 other.x * n.y - other.y * n.x};
724
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 const float ul = std::sqrt(u.x * u.x + u.y * u.y + u.z * u.z);
725 1 u = {u.x / ul, u.y / ul, u.z / ul};
726 1 const Float3 v = {n.y * u.z - n.z * u.y, n.z * u.x - n.x * u.z, n.x * u.y - n.y * u.x};
727 1 constexpr float kRadians = 3.14159265358979f / 180;
728 1 const float sweep = (end - begin) * kRadians;
729 1 const auto steps =
730
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 uint32_t(std::clamp(std::ceil(std::abs(sweep) / (2 * 3.14159265f) * 64), 1.0f, 1024.0f));
731 1 std::vector<Float3> points;
732
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if (filled) {
733
0/1
✗ Branch 1 not taken.
✗ points.push_back(center);
734 }
735
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 time.
3 for (uint32_t step = 0; step <= steps; ++step) {
736 2 const float angle = begin * kRadians + sweep * float(step) / float(steps);
737
1/1
✓ Branch 1 taken 2 times.
2 const float c = std::cos(angle) * radius;
738
1/1
✓ Branch 1 taken 2 times.
2 const float s = std::sin(angle) * radius;
739 2 points.push_back({center.x + u.x * c + v.x * s, center.y + u.y * c + v.y * s,
740
1/1
✓ Branch 1 taken 2 times.
2 center.z + u.z * c + v.z * s});
741 }
742
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 8 taken 1 time.
2 return filled ? Polygon(points, style) : Polyline(points, false, style);
743 1 }
744
745 2 Overlay& Overlay::Rectangle(Float3 center, Float3 halfRight, Float3 halfUp,
746 const OverlayStyle& style, bool filled) {
747 10 const auto corner = [&](float across, float up) {
748 8 return Float3{center.x + halfRight.x * across + halfUp.x * up,
749 8 center.y + halfRight.y * across + halfUp.y * up,
750 8 center.z + halfRight.z * across + halfUp.z * up};
751 2 };
752 2 const std::vector<Float3> corners = {corner(-1, -1), corner(1, -1), corner(1, 1),
753 2 corner(-1, 1)};
754
4/4
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
✓ Branch 8 taken 1 time.
4 return filled ? Polygon(corners, style) : Polyline(corners, true, style);
755 2 }
756
757 1 Overlay& Overlay::Mesh(Span<const Float3> points, Span<const uint32_t> faceCounts,
758 Span<const uint32_t> indices, const OverlayStyle& style, bool wireframe) {
759 1 size_t used = 0;
760
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 for (const uint32_t count : faceCounts) {
761
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if (count < 3) {
762
0/1
✗ Branch 2 not taken.
✗ throw Error("An overlay mesh face has at least 3 corners, not {}", count);
763 }
764 1 used += count;
765 }
766
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if (used != indices.size()) {
767
0/1
✗ Branch 2 not taken.
✗ throw Error("An overlay mesh's faces use {} indices, but it has {}", used, indices.size());
768 }
769
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 time.
5 for (const uint32_t index : indices) {
770
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (index >= points.size()) {
771
0/1
✗ Branch 2 not taken.
✗ throw Error("An overlay mesh index {} is past its {} points", index, points.size());
772 }
773 }
774 1 size_t at = 0;
775 1 std::vector<Float3> face;
776
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 for (const uint32_t count : faceCounts) {
777 1 face.clear();
778
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 1 time.
5 for (uint32_t corner = 0; corner < count; ++corner) {
779
1/1
✓ Branch 1 taken 4 times.
4 face.push_back(points[indices[at + corner]]);
780 }
781 1 at += count;
782
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 if (wireframe) {
783
1/1
✓ Branch 2 taken 1 time.
1 Polyline(face, true, style);
784 } else {
785
0/1
✗ Branch 2 not taken.
✗ Polygon(face, style);
786 }
787 }
788 1 return *this;
789 1 }
790
791 ✗ Overlay& Overlay::Image(Float3 topLeft, Float3 right, Float3 down, device::Tensor pixels,
792 const OverlayStyle& style) {
793
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (pixels.Empty()) {
794
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ throw Error("An overlay image needs pixels");
795 }
796 ✗ CheckScreen(OverlayKind::Images, style, false);
797 ✗ CheckImage(pixels, 1);
798
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (pixels.Shape().size() != 3) {
799
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ throw Error("An overlay image is a [height, width, 4] uint8 tensor");
800 }
801
0/12
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 8 not taken.
✗ Branch 11 not taken.
✗ Branch 14 not taken.
✗ Branch 17 not taken.
✗ Branch 20 not taken.
✗ Branch 23 not taken.
✗ Branch 26 not taken.
✗ Branch 29 not taken.
✗ Branch 32 not taken.
✗ Branch 36 not taken.
✗ Batch batch{.kind = OverlayKind::Images, .style = style, .pixels = std::move(pixels)};
802
0/1
✗ Branch 1 not taken.
✗ AppendRow(batch, {.point = topLeft, .end = right, .rotation = {down.x, down.y, down.z, 1}},
803 style);
804
0/2
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ Edit().batches.push_back(std::move(batch));
805 ✗ return *this;
806 ✗ }
807
808 3 Overlay& Overlay::Prims(Span<const uint32_t> prims, OverlayPrimShape shape,
809 const OverlayStyle& style, float length, Span<const uint32_t> groups,
810 Span<const Float4> colors) {
811 3 CheckScreen(OverlayKind::Prims, style, false);
812
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
2 if (!groups.empty() && groups.size() != prims.size()) {
813
0/1
✗ Branch 2 not taken.
✗ throw Error("{} overlay prim groups for {} prims", groups.size(), prims.size());
814 }
815
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
2 if (!groups.empty() && shape != OverlayPrimShape::Bounds) {
816
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ throw Error("Only overlay prim bounds gather prims into groups");
817 }
818
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (prims.empty()) {
819 return *this;
820 }
821
13/13
✓ Branch 2 taken 2 times.
✓ Branch 5 taken 2 times.
✓ Branch 8 taken 2 times.
✓ Branch 11 taken 2 times.
✓ Branch 14 taken 2 times.
✓ Branch 17 taken 2 times.
✓ Branch 20 taken 2 times.
✓ Branch 23 taken 2 times.
✓ Branch 26 taken 2 times.
✓ Branch 29 taken 2 times.
✓ Branch 32 taken 2 times.
✓ Branch 35 taken 2 times.
✓ Branch 38 taken 2 times.
2 Batch batch{.kind = OverlayKind::Prims, .style = style, .primShape = shape, .length = length};
822
1/1
✓ Branch 1 taken 2 times.
2 batch.hostPrims.assign(prims.begin(), prims.end());
823
1/1
✓ Branch 1 taken 2 times.
2 batch.hostGroups.assign(groups.begin(), groups.end());
824 4 batch.groupCount =
825
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
2 groups.empty() ? 0 : *std::ranges::max_element(groups.begin(), groups.end()) + 1;
826
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 const size_t rows = groups.empty() ? prims.size() : batch.groupCount;
827
3/4
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
2 if (!colors.empty() && colors.size() != rows) {
828 3 throw Error("{} colors for {} overlay prim {}", colors.size(), rows,
829
2/3
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✓ Branch 4 taken 1 time.
1 groups.empty() ? "rows" : "groups");
830 }
831
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 if (colors.empty()) {
832
1/1
✓ Branch 2 taken 1 time.
1 batch.hostColors.assign(rows, PackColor(style.color));
833 } else {
834
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ for (const Float4 color : colors) {
835
0/1
✗ Branch 2 not taken.
✗ batch.hostColors.push_back(PackColor(color));
836 }
837 }
838
1/1
✓ Branch 1 taken 1 time.
1 batch.hostSizes.assign(rows, style.width);
839
1/1
✓ Branch 1 taken 1 time.
1 batch.hostIds.assign(rows, style.id);
840
2/2
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
1 Edit().batches.push_back(std::move(batch));
841 1 return *this;
842 2 }
843
844 8 Overlay& Overlay::Outline(Span<const uint32_t> prims, const OverlayStyle& style) {
845 8 CheckScreen(OverlayKind::Outlines, style, false);
846
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 if (prims.empty()) {
847 return *this;
848 }
849
13/13
✓ Branch 2 taken 7 times.
✓ Branch 5 taken 7 times.
✓ Branch 8 taken 7 times.
✓ Branch 11 taken 7 times.
✓ Branch 14 taken 7 times.
✓ Branch 17 taken 7 times.
✓ Branch 20 taken 7 times.
✓ Branch 23 taken 7 times.
✓ Branch 26 taken 7 times.
✓ Branch 29 taken 7 times.
✓ Branch 32 taken 7 times.
✓ Branch 35 taken 7 times.
✓ Branch 38 taken 7 times.
7 Batch batch{.kind = OverlayKind::Outlines, .style = style};
850
1/1
✓ Branch 1 taken 7 times.
7 batch.hostPrims.assign(prims.begin(), prims.end());
851
2/2
✓ Branch 1 taken 7 times.
✓ Branch 4 taken 7 times.
7 Edit().batches.push_back(std::move(batch));
852 7 return *this;
853 7 }
854
855 ✗ Overlay& Overlay::Add(OverlayTriangles triangles) {
856 ✗ Batch batch{.kind = OverlayKind::Triangles,
857 .style = triangles.style,
858 ✗ .points = std::move(triangles.points),
859 ✗ .colors = std::move(triangles.colors),
860 ✗ .worlds = std::move(triangles.worlds),
861 ✗ .itemCount = std::move(triangles.itemCount),
862
0/9
✗ Branch 2 not taken.
✗ Branch 6 not taken.
✗ Branch 11 not taken.
✗ Branch 14 not taken.
✗ Branch 17 not taken.
✗ Branch 20 not taken.
✗ Branch 23 not taken.
✗ Branch 26 not taken.
✗ Branch 29 not taken.
✗ .owner = std::move(triangles.owner)};
863
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Push(Edit(), std::move(batch));
864 ✗ return *this;
865 ✗ }
866
867 ✗ Overlay& Overlay::Add(OverlayImages images) {
868 ✗ Batch batch{.kind = OverlayKind::Images,
869 .style = images.style,
870 ✗ .points = std::move(images.points),
871 ✗ .ends = std::move(images.rights),
872 ✗ .colors = std::move(images.colors),
873 ✗ .worlds = std::move(images.worlds),
874 ✗ .itemCount = std::move(images.itemCount),
875 ✗ .rotations = std::move(images.downs),
876 ✗ .pixels = std::move(images.pixels),
877
0/6
✗ Branch 4 not taken.
✗ Branch 10 not taken.
✗ Branch 13 not taken.
✗ Branch 16 not taken.
✗ Branch 19 not taken.
✗ Branch 22 not taken.
✗ .owner = std::move(images.owner)};
878
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Push(Edit(), std::move(batch));
879 ✗ return *this;
880 ✗ }
881
882 ✗ Overlay& Overlay::Add(OverlayPrims prims) {
883 ✗ Batch batch{.kind = OverlayKind::Prims,
884 .style = prims.style,
885 ✗ .colors = std::move(prims.colors),
886 ✗ .sizes = std::move(prims.widths),
887 ✗ .prims = std::move(prims.prims),
888 ✗ .groups = std::move(prims.groups),
889 ✗ .pixels = std::move(prims.pixels),
890 ✗ .groupCount = prims.groupCount,
891 ✗ .primShape = prims.shape,
892 ✗ .length = prims.length,
893
0/7
✗ Branch 2 not taken.
✗ Branch 7 not taken.
✗ Branch 10 not taken.
✗ Branch 13 not taken.
✗ Branch 16 not taken.
✗ Branch 19 not taken.
✗ Branch 22 not taken.
✗ .owner = std::move(prims.owner)};
894
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Push(Edit(), std::move(batch));
895 ✗ return *this;
896 ✗ }
897
898 ✗ Overlay& Overlay::Add(OverlayTexts texts) {
899 ✗ Batch batch{.kind = OverlayKind::Labels,
900 .style = texts.style,
901 ✗ .points = std::move(texts.points),
902 ✗ .colors = std::move(texts.colors),
903 ✗ .sizes = std::move(texts.sizes),
904 ✗ .worlds = std::move(texts.worlds),
905 ✗ .itemCount = std::move(texts.itemCount),
906 ✗ .chars = std::move(texts.chars),
907 .placement = texts.placement,
908
0/7
✗ Branch 2 not taken.
✗ Branch 9 not taken.
✗ Branch 12 not taken.
✗ Branch 15 not taken.
✗ Branch 19 not taken.
✗ Branch 22 not taken.
✗ Branch 25 not taken.
✗ .owner = std::move(texts.owner)};
909
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Push(Edit(), std::move(batch));
910 ✗ return *this;
911 ✗ }
912
913 ✗ Overlay& Overlay::Add(OverlayNumbers numbers) {
914 ✗ Batch batch{.kind = OverlayKind::Numbers,
915 .style = numbers.style,
916 ✗ .points = std::move(numbers.points),
917 ✗ .colors = std::move(numbers.colors),
918 ✗ .sizes = std::move(numbers.sizes),
919 ✗ .worlds = std::move(numbers.worlds),
920 ✗ .itemCount = std::move(numbers.itemCount),
921 ✗ .scalars = std::move(numbers.values),
922 .placement = numbers.placement,
923 ✗ .format = std::move(numbers.format),
924
0/8
✗ Branch 2 not taken.
✗ Branch 9 not taken.
✗ Branch 13 not taken.
✗ Branch 16 not taken.
✗ Branch 19 not taken.
✗ Branch 22 not taken.
✗ Branch 25 not taken.
✗ Branch 29 not taken.
✗ .owner = std::move(numbers.owner)};
925 ✗ uint32_t words[4] = {};
926 ✗ uint32_t length = 0;
927
0/1
✗ Branch 1 not taken.
✗ PackAffix(batch.format.prefix, words, length, "prefix");
928
0/1
✗ Branch 1 not taken.
✗ PackAffix(batch.format.suffix, words, length, "suffix");
929
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Push(Edit(), std::move(batch));
930 ✗ return *this;
931 ✗ }
932
933 ✗ Overlay& Overlay::Add(OverlayLines lines) {
934 ✗ Batch batch{.kind = OverlayKind::Lines,
935 .style = lines.style,
936 ✗ .points = std::move(lines.points),
937 ✗ .ends = std::move(lines.ends),
938 ✗ .colors = std::move(lines.colors),
939 ✗ .sizes = std::move(lines.widths),
940 ✗ .worlds = std::move(lines.worlds),
941 ✗ .itemCount = std::move(lines.itemCount),
942
0/7
✗ Branch 7 not taken.
✗ Branch 10 not taken.
✗ Branch 13 not taken.
✗ Branch 16 not taken.
✗ Branch 19 not taken.
✗ Branch 22 not taken.
✗ Branch 25 not taken.
✗ .owner = std::move(lines.owner)};
943
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Push(Edit(), std::move(batch));
944 ✗ return *this;
945 ✗ }
946
947 ✗ Overlay& Overlay::Add(OverlayMarkers markers) {
948 ✗ Batch batch{.kind = OverlayKind::Markers,
949 .style = markers.style,
950 ✗ .points = std::move(markers.points),
951 ✗ .colors = std::move(markers.colors),
952 ✗ .sizes = std::move(markers.sizes),
953 ✗ .worlds = std::move(markers.worlds),
954 ✗ .itemCount = std::move(markers.itemCount),
955
0/8
✗ Branch 2 not taken.
✗ Branch 9 not taken.
✗ Branch 12 not taken.
✗ Branch 15 not taken.
✗ Branch 18 not taken.
✗ Branch 21 not taken.
✗ Branch 24 not taken.
✗ Branch 27 not taken.
✗ .owner = std::move(markers.owner)};
956
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Push(Edit(), std::move(batch));
957 ✗ return *this;
958 ✗ }
959
960 ✗ Overlay& Overlay::Add(OverlayBoxes boxes) {
961 ✗ Batch batch{.kind = OverlayKind::Boxes,
962 .style = boxes.style,
963 ✗ .points = std::move(boxes.points),
964 ✗ .ends = std::move(boxes.halfExtents),
965 ✗ .colors = std::move(boxes.colors),
966 ✗ .sizes = std::move(boxes.widths),
967 ✗ .worlds = std::move(boxes.worlds),
968 ✗ .itemCount = std::move(boxes.itemCount),
969 ✗ .rotations = std::move(boxes.orientations),
970
0/6
✗ Branch 8 not taken.
✗ Branch 11 not taken.
✗ Branch 14 not taken.
✗ Branch 17 not taken.
✗ Branch 20 not taken.
✗ Branch 23 not taken.
✗ .owner = std::move(boxes.owner)};
971
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Push(Edit(), std::move(batch));
972 ✗ return *this;
973 ✗ }
974
975 ✗ Overlay& Overlay::Add(OverlaySpheres spheres) {
976 ✗ Batch batch{.kind = OverlayKind::Spheres,
977 .style = spheres.style,
978 ✗ .points = std::move(spheres.points),
979 ✗ .colors = std::move(spheres.colors),
980 ✗ .sizes = std::move(spheres.widths),
981 ✗ .worlds = std::move(spheres.worlds),
982 ✗ .itemCount = std::move(spheres.itemCount),
983 ✗ .scalars = std::move(spheres.radii),
984
0/7
✗ Branch 2 not taken.
✗ Branch 9 not taken.
✗ Branch 13 not taken.
✗ Branch 16 not taken.
✗ Branch 19 not taken.
✗ Branch 22 not taken.
✗ Branch 25 not taken.
✗ .owner = std::move(spheres.owner)};
985
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Push(Edit(), std::move(batch));
986 ✗ return *this;
987 ✗ }
988
989 ✗ Overlay& Overlay::Add(OverlayCircles circles) {
990 ✗ Batch batch{.kind = OverlayKind::Circles,
991 .style = circles.style,
992 ✗ .points = std::move(circles.points),
993 ✗ .ends = std::move(circles.normals),
994 ✗ .colors = std::move(circles.colors),
995 ✗ .sizes = std::move(circles.widths),
996 ✗ .worlds = std::move(circles.worlds),
997 ✗ .itemCount = std::move(circles.itemCount),
998 ✗ .scalars = std::move(circles.radii),
999
0/6
✗ Branch 7 not taken.
✗ Branch 11 not taken.
✗ Branch 14 not taken.
✗ Branch 17 not taken.
✗ Branch 20 not taken.
✗ Branch 23 not taken.
✗ .owner = std::move(circles.owner)};
1000
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Push(Edit(), std::move(batch));
1001 ✗ return *this;
1002 ✗ }
1003
1004 ✗ Overlay& Overlay::Add(OverlayFrames frames) {
1005 ✗ Batch batch{.kind = OverlayKind::Frames,
1006 .style = frames.style,
1007 ✗ .points = std::move(frames.points),
1008 ✗ .sizes = std::move(frames.widths),
1009 ✗ .worlds = std::move(frames.worlds),
1010 ✗ .itemCount = std::move(frames.itemCount),
1011 ✗ .rotations = std::move(frames.orientations),
1012 ✗ .scalars = std::move(frames.lengths),
1013 ✗ .length = frames.length,
1014
0/7
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 13 not taken.
✗ Branch 16 not taken.
✗ Branch 19 not taken.
✗ Branch 22 not taken.
✗ Branch 25 not taken.
✗ .owner = std::move(frames.owner)};
1015
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Push(Edit(), std::move(batch));
1016 ✗ return *this;
1017 ✗ }
1018
1019 3 Overlay& Overlay::Add(OverlayArrows arrows) {
1020
4/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 1 time.
3 if (!arrows.ends.Empty() && !arrows.vectors.Empty()) {
1021
2/2
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
2 throw Error("Overlay arrows take ends or vectors, not both");
1022 }
1023 2 const bool vectors = !arrows.vectors.Empty();
1024 2 Batch batch{.kind = OverlayKind::Arrows,
1025 .style = arrows.style,
1026 2 .points = std::move(arrows.points),
1027 .ends = vectors ? std::move(arrows.vectors) : std::move(arrows.ends),
1028 2 .colors = std::move(arrows.colors),
1029 2 .sizes = std::move(arrows.widths),
1030 2 .worlds = std::move(arrows.worlds),
1031 2 .itemCount = std::move(arrows.itemCount),
1032 2 .scalars = std::move(arrows.headLengths),
1033 2 .length = arrows.headLength,
1034 1 .vectorScale = vectors ? std::optional(arrows.vectorScale) : std::nullopt,
1035
10/10
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✓ Branch 9 taken 2 times.
✓ Branch 13 taken 2 times.
✓ Branch 16 taken 2 times.
✓ Branch 19 taken 2 times.
✓ Branch 22 taken 2 times.
✓ Branch 25 taken 2 times.
✓ Branch 27 taken 1 time.
✓ Branch 28 taken 1 time.
2 .owner = std::move(arrows.owner)};
1036
2/2
✓ Branch 2 taken 2 times.
✓ Branch 5 taken 2 times.
2 Push(Edit(), std::move(batch));
1037 2 return *this;
1038 2 }
1039
1040 ✗ Overlay& Overlay::Add(OverlayCapsules capsules) {
1041 ✗ Batch batch{.kind = OverlayKind::Capsules,
1042 .style = capsules.style,
1043 ✗ .points = std::move(capsules.points),
1044 ✗ .colors = std::move(capsules.colors),
1045 ✗ .sizes = std::move(capsules.widths),
1046 ✗ .worlds = std::move(capsules.worlds),
1047 ✗ .itemCount = std::move(capsules.itemCount),
1048 ✗ .rotations = std::move(capsules.orientations),
1049 ✗ .scalars = std::move(capsules.radii),
1050 ✗ .extras = std::move(capsules.halfHeights),
1051
0/5
✗ Branch 2 not taken.
✗ Branch 12 not taken.
✗ Branch 15 not taken.
✗ Branch 18 not taken.
✗ Branch 21 not taken.
✗ .owner = std::move(capsules.owner)};
1052
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Push(Edit(), std::move(batch));
1053 ✗ return *this;
1054 ✗ }
1055
1056 ✗ Overlay& Overlay::Add(OverlayTrails trails) {
1057 ✗ Batch batch{.kind = OverlayKind::Trails,
1058 .style = trails.style,
1059 ✗ .points = std::move(trails.points),
1060 ✗ .colors = std::move(trails.colors),
1061 ✗ .sizes = std::move(trails.widths),
1062 ✗ .worlds = std::move(trails.worlds),
1063 ✗ .itemCount = std::move(trails.itemCount),
1064 ✗ .extras = std::move(trails.heads),
1065 ✗ .fade = trails.fade,
1066
0/7
✗ Branch 2 not taken.
✗ Branch 9 not taken.
✗ Branch 12 not taken.
✗ Branch 16 not taken.
✗ Branch 19 not taken.
✗ Branch 22 not taken.
✗ Branch 25 not taken.
✗ .owner = std::move(trails.owner)};
1067
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Push(Edit(), std::move(batch));
1068 ✗ return *this;
1069 ✗ }
1070
1071 13 bool Overlay::Empty() const {
1072
3/4
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 12 times.
13 return !m_data || m_data->batches.empty();
1073 }
1074
1075 2 OverlayPick::OverlayPick(std::shared_ptr<OverlayPickState> state) : m_state(std::move(state)) {}
1076
1077 1 bool OverlayPick::Ready() const {
1078
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 if (!m_state) {
1079 return true;
1080 }
1081 1 const std::lock_guard lock(m_state->mutex);
1082
3/5
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 3 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
1 return m_state->recorded && m_state->done.Ready();
1083 1 }
1084
1085 7 void OverlayPick::Wait() const {
1086
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (!m_state) {
1087 ✗ return;
1088 }
1089 7 device::Operation done;
1090 7 {
1091 7 std::unique_lock lock(m_state->mutex);
1092
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 m_state->served.wait(lock, [&] { return m_state->recorded; });
1093
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 done = m_state->done;
1094 7 }
1095 7 done.Wait();
1096 }
1097
1098 namespace {
1099
1100 5 std::optional<OverlayPickResult> PickResult(const std::shared_ptr<OverlayPickState>& state) {
1101
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
5 if (!state || !state->readback) {
1102 ✗ return std::nullopt;
1103 }
1104 5 OverlayPickResult result;
1105 5 std::memcpy(&result, state->readback->Mapped(), sizeof(result));
1106 5 return result;
1107 }
1108
1109 }
1110
1111 3 std::optional<OverlayHit> OverlayPick::Hit() const {
1112 3 Wait();
1113 3 const std::optional<OverlayPickResult> result = PickResult(m_state);
1114
4/8
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 3 times.
3 if (!result || result->hit == 0 || result->overlay >= m_state->names.size() ||
1115
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 result->batch >= m_state->sources.size()) {
1116 ✗ return std::nullopt;
1117 }
1118 6 return OverlayHit{.overlay = m_state->names[result->overlay],
1119 3 .id = result->id,
1120 3 .batch = m_state->sources[result->batch].batch,
1121 3 .item = result->row,
1122 3 .piece = result->piece,
1123 3 .world = result->world,
1124 3 .prim = result->prim,
1125 3 .distance = result->distance,
1126 3 .depth = result->depth,
1127 3 .position = {result->position[0], result->position[1], result->position[2]}};
1128 }
1129
1130 2 std::optional<OverlayRay> OverlayPick::Ray() const {
1131 2 Wait();
1132 2 const std::optional<OverlayPickResult> result = PickResult(m_state);
1133
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!result) {
1134 ✗ return std::nullopt;
1135 }
1136 2 return OverlayRay{{result->origin[0], result->origin[1], result->origin[2]},
1137 2 {result->direction[0], result->direction[1], result->direction[2]}};
1138 }
1139
1140 16 bool OverlayData::Outlined() const {
1141 16 return std::ranges::any_of(batches, [](const Batch& batch) {
1142
3/4
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 61 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
68 return batch.kind == OverlayKind::Outlines && !batch.hostPrims.empty();
1143 16 });
1144 }
1145
1146 8 OverlayPass::OverlayPass(device::Device& device)
1147 8 : m_device(device),
1148
3/3
✓ Branch 2 taken 8 times.
✓ Branch 5 taken 8 times.
✓ Branch 9 taken 8 times.
16 m_program(device.CreateProgram(
1149 {.modules = {"chrono_batch_render.overlay.kernels.project",
1150 "chrono_batch_render.overlay.kernels.groups",
1151 "chrono_batch_render.overlay.kernels.composite",
1152 "chrono_batch_render.overlay.kernels.selection",
1153 "chrono_batch_render.overlay.kernels.picks"},
1154 .library = &kOverlayKernels,
1155 .entries = {"ProjectOverlays", "ScatterOverlays", "FindOverlayTiles", "FoldOverlayTiles",
1156 "CompositeOverlays", "KeyOverlayRows", "StartOverlayGroups",
1157 "MeasureOverlayGroups", "ClearOverlayBoxes", "UniteOverlayBoxes",
1158 "OutlineOverlays", "ClearOverlayPicks", "MeasureOverlayPicks",
1159 "ChooseOverlayPicks", "ResolveOverlayPicks"},
1160
1/1
✓ Branch 1 taken 8 times.
8 .label = "overlays"})),
1161
4/4
✓ Branch 1 taken 8 times.
✓ Branch 4 taken 8 times.
✓ Branch 7 taken 8 times.
✓ Branch 11 taken 8 times.
16 m_maskProgram(device.CreateProgram({.modules = {"chrono_batch_render.overlay.kernels.mask"},
1162 .library = &kOverlayKernels,
1163 .entries = {"MaskOutlines"},
1164
1/1
✓ Branch 1 taken 8 times.
8 .label = "overlay outline masks"})),
1165
4/4
✓ Branch 1 taken 8 times.
✓ Branch 4 taken 8 times.
✓ Branch 7 taken 8 times.
✓ Branch 10 taken 8 times.
16 m_unused(device.CreateBuffer({.label = "overlay unused"}, std::vector<uint32_t>(16, ~0u))),
1166
1/1
✓ Branch 1 taken 8 times.
8 m_pairCount(device.CreateBuffer({.bytes = 4, .label = "overlay pairs"})),
1167
1/1
✓ Branch 1 taken 8 times.
8 m_pairsNeeded(device.CreateBuffer(
1168
2/2
✓ Branch 1 taken 8 times.
✓ Branch 4 taken 8 times.
16 {.bytes = 4, .memory = device::Memory::Readback, .label = "overlay pairs needed"})) {
1169 8 *static_cast<uint32_t*>(m_pairsNeeded->Mapped()) = 0;
1170 48 }
1171
1172
1/1
✓ Branch 1 taken 149 times.
149 Overlays::Overlays(device::Device& device) : m_impl(std::make_unique<OverlaysImpl>(device)) {}
1173
1174 298 Overlays::~Overlays() = default;
1175
1176 149 Ref<Overlays> Overlays::Create(device::Device& device) {
1177
1/1
✓ Branch 2 taken 149 times.
149 return Ref<Overlays>(new Overlays(device));
1178 }
1179
1180 13 void Overlays::Set(std::string_view name, Overlay overlay) {
1181 13 OverlayContents contents = OverlayPass::Share(overlay);
1182
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 1 time.
13 if (contents) {
1183
1/1
✓ Branch 1 taken 12 times.
12 OverlayPass::CheckDevice(*contents, m_impl->device);
1184 }
1185 13 OverlayContents previous;
1186
1/1
✓ Branch 1 taken 13 times.
13 const std::lock_guard lock(m_impl->mutex);
1187 13 const auto found = m_impl->layers.find(name);
1188
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 5 times.
13 if (found == m_impl->layers.end()) {
1189
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 if (contents) {
1190
2/2
✓ Branch 1 taken 8 times.
✓ Branch 4 taken 8 times.
16 m_impl->layers.emplace(std::string(name), std::move(contents));
1191 }
1192 8 return;
1193 }
1194
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
5 previous = std::exchange(found->second, std::move(contents));
1195
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 4 times.
5 if (!found->second) {
1196 1 m_impl->layers.erase(found);
1197 }
1198 18 }
1199
1200 1 void Overlays::Remove(std::string_view name) {
1201
1/1
✓ Branch 2 taken 1 time.
1 Set(name, Overlay());
1202 1 }
1203
1204 ✗ std::vector<std::string> Overlays::Names() const {
1205 ✗ const std::lock_guard lock(m_impl->mutex);
1206 ✗ std::vector<std::string> names;
1207
0/3
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ for (const auto& [name, contents] : m_impl->layers) {
1208
0/1
✗ Branch 1 not taken.
✗ names.push_back(name);
1209 }
1210 ✗ return names;
1211 ✗ }
1212
1213 2 OverlayPick Overlays::Pick(std::string_view product, Float2 pixel, float radius, uint32_t layer) {
1214
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!(radius >= 0)) {
1215
0/1
✗ Branch 2 not taken.
✗ throw Error("An overlay pick's radius is at least 0, not {}", radius);
1216 }
1217 2 auto state = std::make_shared<OverlayPickState>();
1218
1/3
✓ Branch 1 taken 2 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
2 state->product = std::string(product);
1219
1/1
✓ Branch 1 taken 2 times.
2 state->layer = layer;
1220 2 state->pixel = pixel;
1221 2 state->radius = radius;
1222
1/1
✓ Branch 1 taken 2 times.
2 const std::lock_guard lock(m_impl->mutex);
1223
1/1
✓ Branch 1 taken 2 times.
2 m_impl->picks.push_back(state);
1224
1/1
✓ Branch 1 taken 2 times.
4 return OverlayPick(std::move(state));
1225 2 }
1226
1227 3513 bool OverlaysImpl::Outlined() const {
1228 3513 const std::lock_guard lock(mutex);
1229 3529 return std::ranges::any_of(layers, [](const auto& entry) { return entry.second->Outlined(); });
1230 3513 }
1231
1232 std::vector<std::shared_ptr<OverlayPickState>>
1233 3513 OverlaysImpl::TakePicks(std::span<const std::string> products) {
1234 3513 const std::lock_guard lock(mutex);
1235 3513 std::vector<std::shared_ptr<OverlayPickState>> taken;
1236 3513 std::vector<std::shared_ptr<OverlayPickState>> waiting;
1237
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3513 times.
3515 for (std::shared_ptr<OverlayPickState>& pick : picks) {
1238
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 const bool served = std::ranges::find(products, pick->product) != products.end();
1239
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
4 (served ? taken : waiting).push_back(std::move(pick));
1240 }
1241 3513 picks = std::move(waiting);
1242 7026 return taken;
1243 3513 }
1244
1245 3513 void OverlayPass::Serve(std::span<const std::shared_ptr<OverlayPickState>> picks,
1246 const device::Operation& done) {
1247
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3513 times.
3515 for (const std::shared_ptr<OverlayPickState>& pick : picks) {
1248 2 {
1249 2 const std::lock_guard lock(pick->mutex);
1250 2 pick->recorded = true;
1251 2 pick->done = done;
1252 2 }
1253 2 pick->served.notify_all();
1254 }
1255 3513 }
1256
1257 std::vector<OverlayContents>
1258 16 OverlayPass::Draw(device::CommandEncoder& encoder, const Snapshot& snapshot, uint32_t worldCount,
1259 const OverlaysImpl& overlays, std::span<const OverlayOutput> outputs,
1260 std::span<const OverlayCameraSource> cameraImages,
1261 std::span<const std::shared_ptr<OverlayPickState>> picks) {
1262 16 std::vector<std::pair<std::string, OverlayContents>> named;
1263 16 {
1264
1/1
✓ Branch 1 taken 16 times.
16 const std::lock_guard lock(overlays.mutex);
1265
1/1
✓ Branch 1 taken 16 times.
16 named.assign(overlays.layers.begin(), overlays.layers.end());
1266 ✗ }
1267 31 const auto matches = [](const std::vector<std::string>& patterns, std::string_view name) {
1268
1/1
✓ Branch 1 taken 15 times.
15 return std::ranges::any_of(patterns, [&](const std::string& pattern) {
1269
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 return pattern.ends_with('*')
1270 15 ? name.starts_with(std::string_view(pattern).substr(0, pattern.size() - 1))
1271 ✗ : name == pattern;
1272 });
1273 };
1274
1275 16 std::vector<OverlayContents> used;
1276 16 std::vector<std::string> names;
1277
1/1
✓ Branch 1 taken 16 times.
16 std::vector<uint32_t> slots(named.size(), ~0u);
1278 16 std::vector<OverlayTarget> targets;
1279 16 std::vector<std::pair<std::string_view, uint32_t>> keys;
1280
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 16 times.
32 for (const OverlayOutput& view : outputs) {
1281
1/1
✓ Branch 1 taken 16 times.
16 encoder.Fill(view.image->GetBuffer());
1282 16 uint64_t mask = 0;
1283
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 16 times.
31 for (size_t at = 0; at < named.size(); ++at) {
1284
2/3
✓ Branch 1 taken 15 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 15 times.
15 if (!matches(*view.layers, named[at].first)) {
1285 ✗ continue;
1286 }
1287
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 if (slots[at] == ~0u) {
1288
1/1
✓ Branch 1 taken 15 times.
15 slots[at] = uint32_t(used.size());
1289
1/1
✓ Branch 1 taken 15 times.
15 used.push_back(named[at].second);
1290
1/1
✓ Branch 1 taken 15 times.
15 names.push_back(named[at].first);
1291 }
1292
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 if (slots[at] < kMostOverlays) {
1293 15 mask |= uint64_t(1) << slots[at];
1294 }
1295 }
1296
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 16 times.
32 for (OverlayTarget target : view.targets) {
1297 16 target.overlays = mask;
1298
1/1
✓ Branch 1 taken 16 times.
16 targets.push_back(target);
1299
1/1
✓ Branch 1 taken 16 times.
16 keys.emplace_back(view.product, target.layer);
1300 }
1301 }
1302
3/4
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✓ Branch 3 taken 1 time.
16 if (targets.empty() || used.empty()) {
1303 return used;
1304 }
1305 15 std::vector<OverlayPickRequest> requests;
1306 15 std::vector<std::shared_ptr<OverlayPickState>> answered;
1307
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 15 times.
17 for (const std::shared_ptr<OverlayPickState>& pick : picks) {
1308 2 const auto target = std::ranges::find(
1309 2 keys, std::pair<std::string_view, uint32_t>(pick->product, pick->layer));
1310
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (target == keys.end()) {
1311 ✗ continue;
1312 }
1313
1/1
✓ Branch 1 taken 2 times.
4 pick->readback = m_device.CreateBuffer({.bytes = sizeof(OverlayPickResult),
1314 .memory = device::Memory::Readback,
1315
2/3
✓ Branch 1 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
4 .label = "overlay pick"});
1316
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 requests.push_back(
1317 2 {uint32_t(target - keys.begin()), pick->pixel, pick->radius, pick->readback});
1318
1/1
✓ Branch 1 taken 2 times.
2 answered.push_back(pick);
1319 }
1320
1/1
✓ Branch 1 taken 15 times.
15 Record(encoder, snapshot, worldCount, targets, used, cameraImages, requests);
1321
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 15 times.
17 for (const std::shared_ptr<OverlayPickState>& pick : answered) {
1322
1/1
✓ Branch 1 taken 2 times.
2 pick->names = names;
1323
1/1
✓ Branch 1 taken 2 times.
2 pick->sources = m_batchSources;
1324 }
1325 15 return used;
1326 20 }
1327
1328 13 OverlayContents OverlayPass::Share(const Overlay& overlay) {
1329
7/8
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 12 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 12 times.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 12 times.
✓ Branch 8 taken 1 time.
37 return overlay.Empty() ? nullptr : overlay.m_data;
1330 }
1331
1332 12 void OverlayPass::CheckDevice(const OverlayData& overlay, device::Device& device) {
1333
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 12 times.
38 for (const Batch& batch : overlay.batches) {
1334 26 const std::string kind(KindName(batch.kind));
1335 702 for (const device::Tensor* tensor :
1336 26 {&batch.points, &batch.ends, &batch.colors, &batch.sizes, &batch.worlds,
1337 26 &batch.itemCount, &batch.rotations, &batch.scalars, &batch.extras, &batch.chars,
1338
2/2
✓ Branch 0 taken 338 times.
✓ Branch 1 taken 26 times.
364 &batch.prims, &batch.groups, &batch.pixels}) {
1339
1/1
✓ Branch 1 taken 338 times.
676 RequireDevice(*tensor, device, kind + "' arrays");
1340 }
1341 26 }
1342 350 }
1343
1344 132 void OverlayPass::Reserve(Ref<device::Buffer>& buffer, uint64_t bytes, const char* label) {
1345
3/4
✓ Branch 0 taken 40 times.
✓ Branch 1 taken 92 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 40 times.
132 if (!buffer || buffer->Bytes() < bytes) {
1346
2/3
✓ Branch 1 taken 92 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 92 times.
92 buffer = m_device.CreateBuffer({.bytes = std::max<uint64_t>(bytes, 256), .label = label});
1347 }
1348 260 }
1349
1350 ✗ void OverlayPass::Group(OverlayBatch& gpu, GroupKey key, uint32_t rows, uint32_t worldCount,
1351 uint64_t worlds, uint32_t worldStride, uint64_t itemCounts) {
1352
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ const size_t at = m_groupParams.size();
1353
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (m_groupings.size() <= at) {
1354 ✗ m_groupings.emplace_back();
1355 }
1356 ✗ Grouping& grouping = m_groupings[at];
1357 ✗ Reserve(grouping.keys, uint64_t(rows) * 4, "overlay group keys");
1358 ✗ Reserve(grouping.order, uint64_t(rows) * 4, "overlay group order");
1359 ✗ Reserve(grouping.starts, (uint64_t(worldCount) + 3) * 4, "overlay group starts");
1360 ✗ m_groupParams.push_back({.worlds = {worlds},
1361 .itemCounts = {itemCounts},
1362 ✗ .keys = {grouping.keys->Address()},
1363 ✗ .order = {grouping.order->Address()},
1364 ✗ .starts = {grouping.starts->Address()},
1365 .rows = rows,
1366 .worldStride = worldStride,
1367 .groups = worldCount,
1368 .need = uint32_t(at)});
1369 ✗ m_groupKeys.push_back(key);
1370 ✗ const auto learned = m_groupStrides.find(key);
1371
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ const uint32_t guess = worldCount > 0 ? rows / worldCount * 2 + 64 : rows;
1372
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ const uint32_t need = learned != m_groupStrides.end() ? learned->second : guess;
1373 ✗ gpu.flags |= kOverlayGrouped;
1374
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ gpu.order = {grouping.order->Address()};
1375 ✗ gpu.groupStarts = {grouping.starts->Address()};
1376 ✗ gpu.groups = worldCount;
1377
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ gpu.capacity = std::min(rows, need + need / 4 + 16) * gpu.pieces;
1378 ✗ }
1379
1380 ✗ void OverlayPass::Unite(OverlayBatch& gpu, uint32_t rows, uint32_t groups, uint64_t prims,
1381 uint32_t primStride, uint64_t groupIds, uint32_t groupStride) {
1382
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ const size_t at = m_uniteParams.size();
1383
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (m_boxes.size() <= at) {
1384 ✗ m_boxes.emplace_back();
1385 }
1386 ✗ Reserve(m_boxes[at], uint64_t(groups) * kOverlayBoxWords * 4, "overlay boxes");
1387 ✗ m_uniteParams.push_back({.prims = {prims},
1388 .groups = {groupIds},
1389 ✗ .boxes = {m_boxes[at]->Address()},
1390 .primCount = rows,
1391 .primStride = primStride,
1392 .groupStride = groupStride,
1393 .boxCount = groups});
1394 ✗ gpu.boxes = {m_boxes[at]->Address()};
1395 ✗ gpu.flags |= kOverlayUnited;
1396 ✗ }
1397
1398 15 void OverlayPass::AddBatches(const OverlayContents& overlay, uint32_t index, uint32_t worldCount,
1399 std::vector<OverlayBatch>& batches, uint32_t& firstItem) {
1400 15 Uploaded& uploaded = m_uploads[overlay.get()];
1401
4/4
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 12 times.
✓ Branch 4 taken 3 times.
18 if (uploaded.contents.lock() != overlay) {
1402 12 uploaded = {overlay, {}};
1403
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 12 times.
38 for (const Batch& batch : overlay->batches) {
1404
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 24 times.
26 if (!Hosted(batch)) {
1405 2 continue;
1406 }
1407 288 const auto upload = [&](const auto& values, const char* label) {
1408
5/10
auto chrono::batch_render::OverlayPass::AddBatches(std::shared_ptr<chrono::batch_render::OverlayData const> const&, unsigned int, unsigned int, std::vector<chrono::batch_render::OverlayBatch, std::allocator<chrono::batch_render::OverlayBatch> >&, unsigned int&)::{lambda(auto:1 const&, char const*)#1}::operator()<std::vector<chrono::batch_render::Float3, std::allocator<chrono::batch_render::Float3> > >(std::vector<chrono::batch_render::Float3, std::allocator<chrono::batch_render::Float3> > const&, char const*) const:
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
auto chrono::batch_render::OverlayPass::AddBatches(std::shared_ptr<chrono::batch_render::OverlayData const> const&, unsigned int, unsigned int, std::vector<chrono::batch_render::OverlayBatch, std::allocator<chrono::batch_render::OverlayBatch> >&, unsigned int&)::{lambda(auto:1 const&, char const*)#1}::operator()<std::vector<chrono::batch_render::Float4, std::allocator<chrono::batch_render::Float4> > >(std::vector<chrono::batch_render::Float4, std::allocator<chrono::batch_render::Float4> > const&, char const*) const:
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
auto chrono::batch_render::OverlayPass::AddBatches(std::shared_ptr<chrono::batch_render::OverlayData const> const&, unsigned int, unsigned int, std::vector<chrono::batch_render::OverlayBatch, std::allocator<chrono::batch_render::OverlayBatch> >&, unsigned int&)::{lambda(auto:1 const&, char const*)#1}::operator()<std::vector<float, std::allocator<float> > >(std::vector<float, std::allocator<float> > const&, char const*) const:
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
auto chrono::batch_render::OverlayPass::AddBatches(std::shared_ptr<chrono::batch_render::OverlayData const> const&, unsigned int, unsigned int, std::vector<chrono::batch_render::OverlayBatch, std::allocator<chrono::batch_render::OverlayBatch> >&, unsigned int&)::{lambda(auto:1 const&, char const*)#1}::operator()<std::vector<unsigned char, std::allocator<unsigned char> > >(std::vector<unsigned char, std::allocator<unsigned char> > const&, char const*) const:
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
auto chrono::batch_render::OverlayPass::AddBatches(std::shared_ptr<chrono::batch_render::OverlayData const> const&, unsigned int, unsigned int, std::vector<chrono::batch_render::OverlayBatch, std::allocator<chrono::batch_render::OverlayBatch> >&, unsigned int&)::{lambda(auto:1 const&, char const*)#1}::operator()<std::vector<unsigned int, std::allocator<unsigned int> > >(std::vector<unsigned int, std::allocator<unsigned int> > const&, char const*) const:
✗ Branch 0 not taken.
✓ Branch 1 taken 144 times.
288 uploaded.buffers.push_back(
1409
25/35
auto chrono::batch_render::OverlayPass::AddBatches(std::shared_ptr<chrono::batch_render::OverlayData const> const&, unsigned int, unsigned int, std::vector<chrono::batch_render::OverlayBatch, std::allocator<chrono::batch_render::OverlayBatch> >&, unsigned int&)::{lambda(auto:1 const&, char const*)#1}::operator()<std::vector<chrono::batch_render::Float3, std::allocator<chrono::batch_render::Float3> > >(std::vector<chrono::batch_render::Float3, std::allocator<chrono::batch_render::Float3> > const&, char const*) const:
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 32 times.
✓ Branch 4 taken 32 times.
✓ Branch 6 taken 32 times.
✓ Branch 7 taken 16 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
auto chrono::batch_render::OverlayPass::AddBatches(std::shared_ptr<chrono::batch_render::OverlayData const> const&, unsigned int, unsigned int, std::vector<chrono::batch_render::OverlayBatch, std::allocator<chrono::batch_render::OverlayBatch> >&, unsigned int&)::{lambda(auto:1 const&, char const*)#1}::operator()<std::vector<chrono::batch_render::Float4, std::allocator<chrono::batch_render::Float4> > >(std::vector<chrono::batch_render::Float4, std::allocator<chrono::batch_render::Float4> > const&, char const*) const:
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 16 times.
✓ Branch 4 taken 16 times.
✓ Branch 6 taken 16 times.
✓ Branch 7 taken 8 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
auto chrono::batch_render::OverlayPass::AddBatches(std::shared_ptr<chrono::batch_render::OverlayData const> const&, unsigned int, unsigned int, std::vector<chrono::batch_render::OverlayBatch, std::allocator<chrono::batch_render::OverlayBatch> >&, unsigned int&)::{lambda(auto:1 const&, char const*)#1}::operator()<std::vector<float, std::allocator<float> > >(std::vector<float, std::allocator<float> > const&, char const*) const:
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 33 times.
✓ Branch 4 taken 33 times.
✓ Branch 6 taken 33 times.
✓ Branch 7 taken 15 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
auto chrono::batch_render::OverlayPass::AddBatches(std::shared_ptr<chrono::batch_render::OverlayData const> const&, unsigned int, unsigned int, std::vector<chrono::batch_render::OverlayBatch, std::allocator<chrono::batch_render::OverlayBatch> >&, unsigned int&)::{lambda(auto:1 const&, char const*)#1}::operator()<std::vector<unsigned char, std::allocator<unsigned char> > >(std::vector<unsigned char, std::allocator<unsigned char> > const&, char const*) const:
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 22 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
auto chrono::batch_render::OverlayPass::AddBatches(std::shared_ptr<chrono::batch_render::OverlayData const> const&, unsigned int, unsigned int, std::vector<chrono::batch_render::OverlayBatch, std::allocator<chrono::batch_render::OverlayBatch> >&, unsigned int&)::{lambda(auto:1 const&, char const*)#1}::operator()<std::vector<unsigned int, std::allocator<unsigned int> > >(std::vector<unsigned int, std::allocator<unsigned int> > const&, char const*) const:
✓ Branch 0 taken 70 times.
✓ Branch 1 taken 74 times.
✓ Branch 4 taken 74 times.
✓ Branch 6 taken 74 times.
✓ Branch 7 taken 70 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
890 values.empty() ? m_unused : m_device.CreateBuffer({.label = label}, values));
1410 445 };
1411 24 upload(batch.hostPoints, "overlay points");
1412 24 upload(batch.hostEnds, "overlay ends");
1413 24 upload(batch.hostColors, "overlay colors");
1414 24 upload(batch.hostSizes, "overlay sizes");
1415 24 upload(batch.hostWorlds, "overlay worlds");
1416 24 upload(batch.hostRotations, "overlay rotations");
1417 24 upload(batch.hostScalars, "overlay scalars");
1418 24 upload(batch.hostExtras, "overlay extras");
1419 24 upload(batch.hostCodes, "overlay text");
1420
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 17 times.
24 if (batch.kind == OverlayKind::Outlines) {
1421 7 std::vector<uint32_t> bits(*std::ranges::max_element(batch.hostPrims) / 32 + 1);
1422
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 7 times.
14 for (const uint32_t prim : batch.hostPrims) {
1423 7 bits[prim / 32] |= 1u << (prim % 32);
1424 }
1425
1/1
✓ Branch 1 taken 7 times.
7 upload(bits, "overlay outline");
1426 7 } else {
1427 17 upload(batch.hostPrims, "overlay prims");
1428 }
1429 24 upload(batch.hostGroups, "overlay groups");
1430 24 upload(batch.hostIds, "overlay ids");
1431 }
1432 }
1433
1434 15 const uint64_t unused = m_unused->Address();
1435 15 size_t hostBatch = 0;
1436
2/2
✓ Branch 0 taken 55 times.
✓ Branch 1 taken 15 times.
70 for (uint32_t batchIndex = 0; batchIndex < overlay->batches.size(); ++batchIndex) {
1437
2/2
✓ Branch 0 taken 53 times.
✓ Branch 1 taken 2 times.
55 const Batch& batch = overlay->batches[batchIndex];
1438 55 const OverlayStyle& style = batch.style;
1439 55 const Ref<device::Buffer>* buffers =
1440
2/2
✓ Branch 0 taken 53 times.
✓ Branch 1 taken 2 times.
55 Hosted(batch) ? uploaded.buffers.data() + kHostBuffers * hostBatch++ : nullptr;
1441
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 48 times.
55 if (batch.kind == OverlayKind::Outlines) {
1442 7 m_outlines.push_back({.bits = {buffers[kHostPrims]->Address()},
1443 7 .bitWords = uint32_t(buffers[kHostPrims]->Bytes() / 4),
1444 7 .color = PackColor(style.color),
1445
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 .width = std::max(style.width, 0.5f),
1446 .overlay = index});
1447 7 continue;
1448 }
1449 48 OverlayBatch gpu{};
1450 48 gpu.ends = {unused};
1451 48 gpu.colors = {unused};
1452 48 gpu.sizes = {unused};
1453 48 gpu.worlds = {unused};
1454 48 gpu.itemCounts = {unused};
1455 48 gpu.rotations = {unused};
1456 48 gpu.scalars = {unused};
1457 48 gpu.extras = {unused};
1458 48 gpu.prims = {unused};
1459 48 gpu.primGroups = {unused};
1460 48 gpu.boxes = {unused};
1461 48 gpu.pieces = 1;
1462 48 gpu.length = batch.length;
1463 48 gpu.fade = batch.fade;
1464 48 gpu.chars = unused;
1465 48 gpu.background = PackColor(style.background);
1466 48 gpu.halo = PackColor(style.halo);
1467 48 gpu.kind = uint32_t(batch.kind);
1468 48 gpu.depth = uint32_t(style.depth);
1469 48 gpu.marker = uint32_t(style.marker);
1470 48 gpu.dash = PackPattern(style.dash);
1471
1/2
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
48 gpu.gap = PackPattern(style.gap > 0 ? style.gap : style.dash);
1472
1/2
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
48 gpu.flags = (style.units == OverlayUnits::Meters ? kOverlayMeters : 0) |
1473
2/2
✓ Branch 0 taken 47 times.
✓ Branch 1 taken 1 time.
48 (style.screenSized ? kOverlayScreenSized : 0) |
1474
2/4
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 48 times.
96 (style.facing ? kOverlayFacing : 0) | (style.pickable ? kOverlayPickable : 0);
1475 48 gpu.id = style.id;
1476 48 gpu.ids = {unused};
1477
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 47 times.
48 if (batch.vectorScale) {
1478 1 gpu.flags |= kOverlayEndVectors;
1479 1 gpu.vectorScale = *batch.vectorScale;
1480 }
1481
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 42 times.
48 if (style.screen) {
1482 6 gpu.flags |= kOverlayScreenPoints;
1483 6 gpu.depth = uint32_t(OverlayDepth::Always);
1484 6 gpu.atX = style.screen->x;
1485 6 gpu.atY = style.screen->y;
1486 }
1487 48 gpu.color = PackColor(style.color);
1488 96 gpu.size =
1489
4/4
✓ Branch 0 taken 40 times.
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 34 times.
48 batch.kind == OverlayKind::Markers || Textual(batch.kind) ? style.size : style.width;
1490 48 gpu.world = style.world;
1491
2/2
✓ Branch 0 taken 47 times.
✓ Branch 1 taken 1 time.
48 gpu.anchor = batch.kind == OverlayKind::Prims ? kNoPrim : style.anchor;
1492 48 gpu.overlay = index;
1493 48 gpu.alignX = style.align.x;
1494 48 gpu.alignY = style.align.y;
1495 48 gpu.justify = style.align.x;
1496 48 gpu.primShape = uint32_t(batch.primShape);
1497
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 42 times.
48 if (Textual(batch.kind)) {
1498 6 gpu.length = style.width;
1499 }
1500
3/4
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 42 times.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
48 const bool haloed = Textual(batch.kind) && style.halo.w > 0;
1501 60 const auto textPieces = [&](uint32_t chars) { return 1 + (haloed ? 2 : 1) * chars; };
1502
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
48 if (!batch.pixels.Empty()) {
1503
0/1
✗ Branch 2 not taken.
✗ RequireDevice(batch.pixels, m_device, std::string(KindName(batch.kind)) + "' pixels");
1504 ✗ const ImageLayout image = CheckImage(batch.pixels, batch.pixels.Shape()[0]);
1505 ✗ gpu.image = batch.pixels.Address();
1506 ✗ gpu.imageWidth = image.width;
1507 ✗ gpu.imageHeight = image.height;
1508 ✗ gpu.imageRowStride = image.rowStride;
1509 ✗ gpu.imageLayerStride = image.layerStride;
1510
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ gpu.flags |= image.layered ? kOverlayImageLayers : 0;
1511 }
1512
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 2 times.
48 if (Hosted(batch)) {
1513
1/2
✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
46 gpu.points = {buffers[kHostPoints]->Address()};
1514 46 gpu.pointStride = 3;
1515 46 gpu.ends = {buffers[kHostEnds]->Address()};
1516 46 gpu.endStride = 3;
1517 46 gpu.colors = {buffers[kHostColors]->Address()};
1518 46 gpu.colorStride = 1;
1519 46 gpu.sizes = {buffers[kHostSizes]->Address()};
1520 46 gpu.sizeStride = 1;
1521 46 gpu.rotations = {buffers[kHostRotations]->Address()};
1522 46 gpu.rotationStride = 4;
1523 46 gpu.scalars = {buffers[kHostScalars]->Address()};
1524 46 gpu.scalarStride = 1;
1525 46 gpu.extras = {buffers[kHostExtras]->Address()};
1526 46 gpu.extraStride = 1;
1527
1/2
✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
46 if (!batch.hostIds.empty()) {
1528 46 gpu.ids = {buffers[kHostIds]->Address()};
1529 46 gpu.idStride = 1;
1530 46 gpu.flags |= kOverlayHasIds;
1531 }
1532 46 gpu.flags |= kOverlayByteColors | kOverlayHasColors | kOverlayHasSizes |
1533 kOverlayHasRotations | kOverlayHasScalars | kOverlayHasExtras;
1534 46 gpu.pieces = PiecesPerRow(batch.kind, 0);
1535
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 40 times.
46 if (Textual(batch.kind)) {
1536
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 gpu.chars = buffers[kHostCodes]->Address();
1537 6 gpu.flags |= kOverlayPackedText;
1538 6 gpu.maxChars = batch.maxChars;
1539
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
12 gpu.pieces = textPieces(batch.maxChars);
1540 }
1541
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 45 times.
46 auto rows = uint32_t(batch.hostPoints.size());
1542
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 45 times.
46 if (batch.kind == OverlayKind::Prims) {
1543
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 gpu.prims = {buffers[kHostPrims]->Address()};
1544 1 gpu.pointStride = 1;
1545 1 gpu.pieces = PrimPieces(batch.primShape);
1546
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 gpu.primCount = uint32_t(batch.hostPrims.size());
1547 1 rows = gpu.primCount;
1548
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if (!batch.hostGroups.empty()) {
1549 ✗ rows = batch.groupCount;
1550 ✗ Unite(gpu, gpu.primCount, rows, buffers[kHostPrims]->Address(), 1,
1551 buffers[kHostGroups]->Address(), 1);
1552 }
1553 }
1554 46 gpu.capacity = rows * gpu.pieces;
1555
2/4
✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 46 times.
46 if (rows > 0 && std::ranges::any_of(batch.hostWorlds, [](uint32_t world) {
1556 return world != kGlobalWorld;
1557 })) {
1558 ✗ Group(gpu, {index, batchIndex}, rows, worldCount, buffers[kHostWorlds]->Address(),
1559 1, unused);
1560 }
1561 } else {
1562
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 const std::string kind(KindName(batch.kind));
1563 6 const auto field = [&](const device::Tensor& tensor, std::string_view name) {
1564
3/3
✓ Branch 2 taken 4 times.
✓ Branch 5 taken 4 times.
✓ Branch 8 taken 4 times.
8 RequireDevice(tensor, m_device, kind + "' " + std::string(name));
1565 4 };
1566 2 const bool trail = batch.kind == OverlayKind::Trails;
1567 2 const bool textual = Textual(batch.kind);
1568 2 const bool primal = batch.kind == OverlayKind::Prims;
1569 2 const bool triangles = batch.kind == OverlayKind::Triangles;
1570
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 const size_t rank = batch.points.Empty() ? 2 : batch.points.Shape().size();
1571
3/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 const bool perWorld = !trail && !textual && !primal && !triangles && rank == 3;
1572
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 const size_t rowDim = perWorld ? 1 : 0;
1573 6 const auto rowStride = [&](const device::Tensor& tensor, int64_t words,
1574 std::string_view name) {
1575 4 return Words(Stride(tensor, rowDim) / words, name);
1576 2 };
1577 6 const auto worldStride = [&](const device::Tensor& tensor, int64_t words,
1578 std::string_view name) {
1579
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 return perWorld ? Words(Stride(tensor, 0) / words, name) : 0u;
1580 2 };
1581
3/4
auto chrono::batch_render::OverlayPass::AddBatches(std::shared_ptr<chrono::batch_render::OverlayData const> const&, unsigned int, unsigned int, std::vector<chrono::batch_render::OverlayBatch, std::allocator<chrono::batch_render::OverlayBatch> >&, unsigned int&)::{lambda(chrono::batch_render::device::Tensor const&, std::basic_string_view<char, std::char_traits<char> >, long, auto:1&, unsigned int&, unsigned int&, unsigned int)#1}::operator()<chrono::batch_render::Ptr<float> >(chrono::batch_render::device::Tensor const&, std::basic_string_view<char, std::char_traits<char> >, long, chrono::batch_render::Ptr<float>&, unsigned int&, unsigned int&, unsigned int) const:
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
auto chrono::batch_render::OverlayPass::AddBatches(std::shared_ptr<chrono::batch_render::OverlayData const> const&, unsigned int, unsigned int, std::vector<chrono::batch_render::OverlayBatch, std::allocator<chrono::batch_render::OverlayBatch> >&, unsigned int&)::{lambda(chrono::batch_render::device::Tensor const&, std::basic_string_view<char, std::char_traits<char> >, long, auto:1&, unsigned int&, unsigned int&, unsigned int)#1}::operator()<chrono::batch_render::Ptr<unsigned int> >(chrono::batch_render::device::Tensor const&, std::basic_string_view<char, std::char_traits<char> >, long, chrono::batch_render::Ptr<unsigned int>&, unsigned int&, unsigned int&, unsigned int) const:
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
10 const auto bind = [&](const device::Tensor& tensor, std::string_view name,
1582 int64_t words, auto& pointer, uint32_t& stride,
1583 uint32_t& perWorldStride, uint32_t flag) {
1584
3/4
auto chrono::batch_render::OverlayPass::AddBatches(std::shared_ptr<chrono::batch_render::OverlayData const> const&, unsigned int, unsigned int, std::vector<chrono::batch_render::OverlayBatch, std::allocator<chrono::batch_render::OverlayBatch> >&, unsigned int&)::{lambda(chrono::batch_render::device::Tensor const&, std::basic_string_view<char, std::char_traits<char> >, long, auto:1&, unsigned int&, unsigned int&, unsigned int)#1}::operator()<chrono::batch_render::Ptr<float> >(chrono::batch_render::device::Tensor const&, std::basic_string_view<char, std::char_traits<char> >, long, chrono::batch_render::Ptr<float>&, unsigned int&, unsigned int&, unsigned int) const:
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
auto chrono::batch_render::OverlayPass::AddBatches(std::shared_ptr<chrono::batch_render::OverlayData const> const&, unsigned int, unsigned int, std::vector<chrono::batch_render::OverlayBatch, std::allocator<chrono::batch_render::OverlayBatch> >&, unsigned int&)::{lambda(chrono::batch_render::device::Tensor const&, std::basic_string_view<char, std::char_traits<char> >, long, auto:1&, unsigned int&, unsigned int&, unsigned int)#1}::operator()<chrono::batch_render::Ptr<unsigned int> >(chrono::batch_render::device::Tensor const&, std::basic_string_view<char, std::char_traits<char> >, long, chrono::batch_render::Ptr<unsigned int>&, unsigned int&, unsigned int&, unsigned int) const:
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
10 if (tensor.Empty()) {
1585 return;
1586 }
1587 2 field(tensor, name);
1588 2 pointer.address = tensor.Address();
1589 2 stride = rowStride(tensor, words, name);
1590 2 perWorldStride = worldStride(tensor, words, name);
1591 2 gpu.flags |= flag;
1592 };
1593
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 const device::Tensor& source = primal ? batch.prims
1594 : !textual ? batch.points
1595
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ : batch.kind == OverlayKind::Numbers ? batch.scalars
1596 : batch.chars;
1597 2 uint32_t rows =
1598
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 uint32_t(source.Shape()[trail || textual || primal || triangles ? 0 : rank - 2]);
1599
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 gpu.worldCount = perWorld ? uint32_t(batch.points.Shape()[0]) : 0;
1600 2 gpu.points = {unused};
1601
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (!batch.points.Empty()) {
1602
1/1
✓ Branch 1 taken 2 times.
2 field(batch.points, "points");
1603
1/1
✓ Branch 1 taken 2 times.
2 gpu.points = {batch.points.Address()};
1604
1/1
✓ Branch 1 taken 2 times.
2 gpu.pointStride = rowStride(batch.points, 1, "points");
1605
1/1
✓ Branch 1 taken 2 times.
2 gpu.pointWorldStride = worldStride(batch.points, 1, "points");
1606 }
1607
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (triangles) {
1608
0/1
✗ Branch 1 not taken.
✗ gpu.ends = {batch.points.Address() + 12};
1609 ✗ gpu.endStride = gpu.pointStride;
1610
0/1
✗ Branch 1 not taken.
✗ gpu.rotations = {batch.points.Address() + 24};
1611 ✗ gpu.rotationStride = gpu.pointStride;
1612 }
1613
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (primal) {
1614
0/1
✗ Branch 1 not taken.
✗ field(batch.prims, "prims");
1615
0/1
✗ Branch 1 not taken.
✗ gpu.prims = {batch.prims.Address()};
1616
0/1
✗ Branch 2 not taken.
✗ gpu.pointStride = Words(Stride(batch.prims, 0), "prims");
1617 ✗ gpu.pieces = PrimPieces(batch.primShape);
1618 ✗ gpu.primCount = rows;
1619
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (!batch.groups.Empty()) {
1620
0/1
✗ Branch 1 not taken.
✗ field(batch.groups, "groups");
1621
0/4
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 8 not taken.
✗ Branch 11 not taken.
✗ Unite(gpu, rows, batch.groupCount, batch.prims.Address(), gpu.pointStride,
1622 ✗ batch.groups.Address(), Words(Stride(batch.groups, 0), "groups"));
1623 ✗ rows = batch.groupCount;
1624 }
1625 }
1626
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (batch.kind == OverlayKind::Labels) {
1627
0/1
✗ Branch 1 not taken.
✗ field(batch.chars, "chars");
1628
0/1
✗ Branch 1 not taken.
✗ gpu.chars = batch.chars.Address();
1629
0/1
✗ Branch 2 not taken.
✗ gpu.charStride = Words(Stride(batch.chars, 0), "chars");
1630 ✗ gpu.maxChars =
1631
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ std::min<uint32_t>(uint32_t(batch.chars.Shape()[1]), kOverlayTextChars);
1632 }
1633
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (batch.kind == OverlayKind::Numbers) {
1634
0/1
✗ Branch 1 not taken.
✗ PackAffix(batch.format.prefix, gpu.prefix, gpu.prefixLength, "prefix");
1635
0/1
✗ Branch 1 not taken.
✗ PackAffix(batch.format.suffix, gpu.suffix, gpu.suffixLength, "suffix");
1636 ✗ gpu.decimals = batch.format.decimals;
1637
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ gpu.maxChars = std::min(gpu.prefixLength + gpu.suffixLength + 22 + gpu.decimals,
1638 kOverlayTextChars);
1639 }
1640
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (batch.placement) {
1641
0/1
✗ Branch 1 not taken.
✗ const Float2 align = batch.placement->Alignment();
1642 ✗ gpu.flags |= kOverlayScreen;
1643 ✗ gpu.atX = batch.placement->at.x;
1644 ✗ gpu.atY = batch.placement->at.y;
1645 ✗ gpu.offsetX = batch.placement->offset.x;
1646 ✗ gpu.offsetY = batch.placement->offset.y;
1647 ✗ gpu.alignX = align.x;
1648 ✗ gpu.alignY = align.y;
1649 }
1650
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (trail) {
1651 ✗ gpu.trailLength = uint32_t(batch.points.Shape()[1]);
1652
0/1
✗ Branch 2 not taken.
✗ gpu.endStride = Words(Stride(batch.points, 1), "points");
1653 }
1654
1/1
✓ Branch 1 taken 2 times.
2 bind(batch.ends, "ends", 1, gpu.ends, gpu.endStride, gpu.endWorldStride, 0);
1655
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!batch.colors.Empty()) {
1656
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ const bool bytes = batch.colors.Type() == device::DType::UInt8;
1657
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ const int64_t words = bytes ? 4 : 1;
1658
0/3
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ if (bytes && batch.colors.Address() % 4 != 0) {
1659 ✗ throw UnsupportedError("Overlay {}' uint8 colors start on a 4-byte boundary",
1660
0/1
✗ Branch 2 not taken.
✗ kind);
1661 }
1662
0/3
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ bind(batch.colors, "colors", words, gpu.colors, gpu.colorStride,
1663 gpu.colorWorldStride, kOverlayHasColors | (bytes ? kOverlayByteColors : 0));
1664 }
1665
1/1
✓ Branch 1 taken 2 times.
2 bind(batch.sizes, "widths", 1, gpu.sizes, gpu.sizeStride, gpu.sizeWorldStride,
1666 kOverlayHasSizes);
1667
1/1
✓ Branch 1 taken 2 times.
2 bind(batch.rotations, "orientations", 1, gpu.rotations, gpu.rotationStride,
1668 gpu.rotationWorldStride, kOverlayHasRotations);
1669
1/1
✓ Branch 1 taken 2 times.
2 bind(batch.scalars, "radii", 1, gpu.scalars, gpu.scalarStride, gpu.scalarWorldStride,
1670 kOverlayHasScalars);
1671
2/3
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 2 times.
4 bind(batch.extras, trail ? "heads" : "half heights", 1, gpu.extras, gpu.extraStride,
1672 gpu.extraWorldStride, kOverlayHasExtras);
1673
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!batch.worlds.Empty()) {
1674
0/1
✗ Branch 1 not taken.
✗ field(batch.worlds, "worlds");
1675
0/1
✗ Branch 1 not taken.
✗ gpu.worlds = {batch.worlds.Address()};
1676
0/1
✗ Branch 2 not taken.
✗ gpu.worldStride = Words(Stride(batch.worlds, 0), "worlds");
1677 ✗ gpu.flags |= kOverlayHasWorlds;
1678 }
1679
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!batch.itemCount.Empty()) {
1680
0/1
✗ Branch 1 not taken.
✗ field(batch.itemCount, "item counts");
1681
0/1
✗ Branch 1 not taken.
✗ gpu.itemCounts = {batch.itemCount.Address()};
1682
0/1
✗ Branch 2 not taken.
✗ gpu.itemCountStride = Words(Stride(batch.itemCount, 0), "item counts");
1683 }
1684
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (textual) {
1685
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ gpu.pieces = textPieces(gpu.maxChars);
1686
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 } else if (!primal) {
1687 2 gpu.pieces = PiecesPerRow(batch.kind, gpu.trailLength);
1688 }
1689 2 gpu.capacity = rows * gpu.pieces;
1690
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
2 if (!batch.worlds.Empty() && rows > 0) {
1691
0/1
✗ Branch 1 not taken.
✗ Group(gpu, {index, batchIndex}, rows, worldCount, gpu.worlds.address,
1692 gpu.worldStride, gpu.itemCounts.address);
1693 }
1694 2 }
1695
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
48 if (gpu.capacity == 0) {
1696 ✗ continue;
1697 }
1698 48 gpu.firstItem = firstItem;
1699 48 firstItem += gpu.capacity;
1700 48 batches.push_back(gpu);
1701 48 m_batchSources.push_back({index, batchIndex});
1702 }
1703 39 }
1704
1705 15 void OverlayPass::Record(device::CommandEncoder& encoder, const Snapshot& snapshot,
1706 uint32_t worldCount, std::span<const OverlayTarget> targets,
1707 std::span<const OverlayContents> overlays,
1708 std::span<const OverlayCameraSource> cameraImages,
1709 std::span<const OverlayPickRequest> picks) {
1710
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 4 times.
22 std::erase_if(m_uploads, [](const auto& entry) { return entry.second.contents.expired(); });
1711
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 if (overlays.size() > kMostOverlays) {
1712
0/1
✗ Branch 1 not taken.
✗ throw UnsupportedError("{} overlays in one step; at most {} fit", overlays.size(),
1713
0/1
✗ Branch 2 not taken.
✗ kMostOverlays);
1714 }
1715 15 uint64_t shown = 0;
1716
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 15 times.
30 for (const OverlayTarget& target : targets) {
1717 15 shown |= target.overlays;
1718 }
1719 15 std::vector<OverlayBatch> batches;
1720 15 uint32_t viewItemCount = 0;
1721
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 if (m_groupNeedsRead) {
1722 ✗ const auto* measured = static_cast<const uint32_t*>(m_groupNeedsRead->Mapped());
1723
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ for (size_t at = 0; at < m_measuredKeys.size(); ++at) {
1724
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (measured[at] > 0) {
1725
0/1
✗ Branch 1 not taken.
✗ m_groupStrides[m_measuredKeys[at]] = measured[at];
1726 }
1727 }
1728 }
1729 15 m_groupParams.clear();
1730 15 m_groupKeys.clear();
1731 15 m_uniteParams.clear();
1732 15 m_outlines.clear();
1733 15 m_batchSources.clear();
1734
2/2
✓ Branch 1 taken 15 times.
✓ Branch 2 taken 15 times.
30 for (uint32_t index = 0; index < overlays.size(); ++index) {
1735
2/4
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
15 if (overlays[index] && ((shown >> index) & 1) != 0) {
1736
1/1
✓ Branch 1 taken 15 times.
15 AddBatches(overlays[index], index, worldCount, batches, viewItemCount);
1737 }
1738 }
1739 15 uint64_t outlineMask = 0;
1740
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 15 times.
22 for (const OverlayOutline& outline : m_outlines) {
1741 7 outlineMask |= uint64_t(1) << outline.overlay;
1742 }
1743 15 const bool outlined = std::ranges::any_of(
1744
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 7 times.
15 targets, [&](const OverlayTarget& target) { return (target.overlays & outlineMask) != 0; });
1745
3/6
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
15 if (batches.empty() && !outlined && picks.empty()) {
1746 return;
1747 }
1748
1749 44 const auto tilesAcross = [](const OverlayTarget& target) {
1750 29 return uint64_t((target.width + kOverlayTile - 1) / kOverlayTile) *
1751 29 ((target.height + kOverlayTile - 1) / kOverlayTile);
1752 };
1753 15 uint64_t maskPixels = 0;
1754 15 uint64_t maskTiles = 0;
1755
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 15 times.
30 for (const OverlayTarget& target : targets) {
1756
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 8 times.
15 if ((target.overlays & outlineMask) != 0) {
1757 7 maskPixels += uint64_t(target.width) * target.height;
1758 7 maskTiles += tilesAcross(target);
1759 }
1760 }
1761
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 8 times.
15 if (maskPixels > 0) {
1762
1/1
✓ Branch 1 taken 7 times.
7 Reserve(m_outlineMasks, maskPixels * 4, "overlay outline masks");
1763
1/1
✓ Branch 1 taken 7 times.
7 Reserve(m_outlineDepths, maskPixels * 4, "overlay outline depths");
1764
1/1
✓ Branch 1 taken 7 times.
7 Reserve(m_outlineTiles, maskTiles * 8, "overlay outline tiles");
1765 }
1766 15 std::vector<OverlayView> views;
1767 15 uint32_t tileCount = 0;
1768 15 uint64_t maskOffset = 0;
1769 15 uint64_t maskTileOffset = 0;
1770
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 15 times.
30 for (const OverlayTarget& target : targets) {
1771 15 OverlayView view{};
1772 15 view.overlay = {target.overlay};
1773 15 view.depth = {target.depth};
1774 15 view.camera = target.camera;
1775 15 view.product = target.product;
1776 15 view.layer = target.layer;
1777 15 view.width = target.width;
1778 15 view.height = target.height;
1779 15 view.firstTile = tileCount;
1780 15 view.columnCount = (target.width + kOverlayTile - 1) / kOverlayTile;
1781 15 view.missDistance = target.missDistance;
1782 15 view.overlays = target.overlays;
1783
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 7 times.
15 view.outlined = (target.overlays & outlineMask) != 0 ? 1 : 0;
1784
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 8 times.
15 view.mask = {m_unused->Address()};
1785 15 view.maskDepth = {m_unused->Address()};
1786 15 view.maskTiles = {m_unused->Address()};
1787
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 8 times.
15 if (view.outlined != 0) {
1788 7 view.mask = {m_outlineMasks->Address() + maskOffset * 4};
1789 7 view.maskDepth = {m_outlineDepths->Address() + maskOffset * 4};
1790 7 view.maskTiles = {m_outlineTiles->Address() + maskTileOffset * 8};
1791 7 maskOffset += uint64_t(target.width) * target.height;
1792 7 maskTileOffset += tilesAcross(target);
1793 }
1794 15 tileCount += uint32_t(tilesAcross(target));
1795
1/1
✓ Branch 1 taken 15 times.
15 views.push_back(view);
1796 }
1797
1798 15 uint64_t fillCapacity = 0;
1799
2/2
✓ Branch 0 taken 48 times.
✓ Branch 1 taken 15 times.
63 for (const OverlayBatch& batch : batches) {
1800 48 const bool filled = batch.kind == uint32_t(OverlayKind::Triangles) ||
1801
4/4
✓ Branch 0 taken 43 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 42 times.
48 batch.kind == uint32_t(OverlayKind::Images) ||
1802 1 (batch.kind == uint32_t(OverlayKind::Prims) &&
1803
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 batch.primShape == uint32_t(OverlayPrimShape::CameraImages));
1804 48 fillCapacity += filled ? uint64_t(batch.capacity) * targets.size() : 0;
1805 }
1806
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 fillCapacity = std::min<uint64_t>(fillCapacity, std::numeric_limits<uint32_t>::max() / 2);
1807
3/3
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 5 times.
✓ Branch 3 taken 15 times.
25 Reserve(m_fills, std::max<uint64_t>(fillCapacity, 1) * sizeof(OverlayFill), "overlay fills");
1808
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 7 times.
15 if (!m_fillCount) {
1809
2/3
✓ Branch 1 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
8 m_fillCount = m_device.CreateBuffer({.bytes = 4, .label = "overlay fill count"});
1810 }
1811 15 std::vector<OverlayCameraImage> sources;
1812
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 15 times.
30 for (const OverlayCameraSource& source : cameraImages) {
1813 15 const Span<const int64_t> shape = source.pixels.Shape();
1814
1/1
✓ Branch 1 taken 15 times.
30 sources.push_back({.pixels = source.pixels.Address(),
1815
1/1
✓ Branch 1 taken 15 times.
15 .camera = source.camera,
1816
1/1
✓ Branch 1 taken 15 times.
15 .width = uint32_t(shape[shape.size() - 2]),
1817
1/1
✓ Branch 1 taken 15 times.
15 .height = uint32_t(shape[shape.size() - 3])});
1818 }
1819
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 if (!sources.empty()) {
1820
1/1
✓ Branch 1 taken 15 times.
15 Reserve(m_cameraImages, sources.size() * sizeof(OverlayCameraImage),
1821 "overlay camera images");
1822 }
1823
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 8 times.
15 if (!m_outlines.empty()) {
1824
1/1
✓ Branch 1 taken 7 times.
7 Reserve(m_outlineBuffer, m_outlines.size() * sizeof(OverlayOutline), "overlay outlines");
1825 }
1826 15 std::vector<OverlayPickQuery> queries;
1827
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 15 times.
17 for (const OverlayPickRequest& pick : picks) {
1828
1/1
✓ Branch 1 taken 2 times.
2 queries.push_back({pick.target, pick.pixel.x, pick.pixel.y, pick.radius});
1829 }
1830
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 13 times.
15 if (!queries.empty()) {
1831
1/1
✓ Branch 1 taken 2 times.
2 Reserve(m_pickQueries, queries.size() * sizeof(OverlayPickQuery), "overlay pick queries");
1832
1/1
✓ Branch 1 taken 2 times.
2 Reserve(m_pickResults, queries.size() * sizeof(OverlayPickResult), "overlay pick results");
1833 }
1834
1835
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 const uint64_t workCount = uint64_t(viewItemCount) * targets.size();
1836
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 if (workCount > algorithms::Scan::kMaxItems) {
1837 ✗ throw UnsupportedError("{} overlay items across {} images; at most {} fit one step",
1838
0/1
✗ Branch 2 not taken.
✗ viewItemCount, targets.size(), algorithms::Scan::kMaxItems);
1839 }
1840
4/4
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 7 times.
15 if (workCount > m_workCapacity || !m_scan) {
1841
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 m_workCapacity = uint32_t(std::min<uint64_t>(
1842
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 1 time.
8 std::max<uint64_t>(workCount + workCount / 2, 64), algorithms::Scan::kMaxItems));
1843
1/1
✓ Branch 1 taken 8 times.
8 m_scan.emplace(m_device, m_workCapacity);
1844
1/1
✓ Branch 1 taken 8 times.
8 Reserve(m_records, uint64_t(m_workCapacity) * sizeof(OverlayRecord), "overlay records");
1845
1/1
✓ Branch 1 taken 8 times.
8 Reserve(m_tileCounts, uint64_t(m_workCapacity) * 4, "overlay tile counts");
1846
1/1
✓ Branch 1 taken 8 times.
8 Reserve(m_offsets, uint64_t(m_workCapacity) * 4, "overlay offsets");
1847 }
1848
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 11 times.
15 const uint64_t needed = *static_cast<const uint32_t*>(m_pairsNeeded->Mapped());
1849 15 const uint64_t wanted =
1850
5/6
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 11 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 15 times.
17 std::max(kLeastPairs, needed > 0 ? std::max(needed, 2 * workCount) : 8 * workCount);
1851
3/4
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
15 if (wanted > m_pairCapacity || 3 * wanted < m_pairCapacity) {
1852
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
8 if (needed > m_pairCapacity && m_pairCapacity > 0) {
1853
0/1
✗ Branch 1 not taken.
✗ Log(LogLevel::Debug, "overlays needed {} tile pairs, so their buffers grow", needed);
1854 }
1855 16 m_pairCapacity =
1856
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 uint32_t(std::min<uint64_t>(wanted + wanted / 2, algorithms::RadixSort::kMaxItems));
1857
1/1
✓ Branch 1 taken 8 times.
8 m_sort.emplace(m_device, m_pairCapacity, 4);
1858
1/1
✓ Branch 1 taken 8 times.
8 Reserve(m_keys, uint64_t(m_pairCapacity) * 4, "overlay keys");
1859
1/1
✓ Branch 1 taken 8 times.
8 Reserve(m_values, uint64_t(m_pairCapacity) * 4, "overlay values");
1860 }
1861
1/1
✓ Branch 1 taken 15 times.
15 Reserve(m_views, views.size() * sizeof(OverlayView), "overlay views");
1862
3/3
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 8 times.
✓ Branch 3 taken 15 times.
22 Reserve(m_batches, std::max<size_t>(batches.size(), 1) * sizeof(OverlayBatch),
1863 "overlay batches");
1864 15 const uint64_t occupiedBytes = (kOverlayTileList + uint64_t(kOverlayTileWords) * tileCount) * 4;
1865
3/4
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
15 if (!m_occupied || m_occupied->Bytes() < occupiedBytes) {
1866
1/1
✓ Branch 1 taken 8 times.
16 m_occupied = m_device.CreateBuffer({.bytes = std::max<uint64_t>(occupiedBytes, 256),
1867 .usage = device::BufferUsage::Indirect,
1868
2/3
✓ Branch 1 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
16 .label = "overlay occupied tiles"});
1869 }
1870
1871 15 const bool textual = std::ranges::any_of(batches, [](const OverlayBatch& batch) {
1872
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 6 times.
32 return batch.kind >= uint32_t(OverlayKind::Labels);
1873 });
1874
4/4
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 3 times.
15 if (textual && !m_fontField) {
1875
1/1
✓ Branch 1 taken 3 times.
3 const OverlayFont& font = OverlayTextFont();
1876
1/1
✓ Branch 1 taken 3 times.
3 std::vector<uint8_t> field = font.field;
1877
1/1
✓ Branch 1 taken 3 times.
3 field.resize((field.size() + 3) / 4 * 4 + 4, 0);
1878
1/1
✓ Branch 1 taken 3 times.
3 std::vector<int8_t> kerning = font.kerning;
1879
1/1
✓ Branch 1 taken 3 times.
3 kerning.resize((kerning.size() + 3) / 4 * 4 + 4, 0);
1880
2/3
✓ Branch 1 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
3 m_fontField = m_device.CreateBuffer({.label = "overlay font"}, field);
1881
1/1
✓ Branch 1 taken 3 times.
6 m_fontGlyphs = m_device.CreateBuffer(
1882
1/1
✓ Branch 1 taken 3 times.
3 {.label = "overlay glyphs"},
1883
3/4
✓ Branch 1 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✓ Branch 6 taken 3 times.
9 std::vector<OverlayGlyph>(font.glyphs.begin(), font.glyphs.end()));
1884
2/3
✓ Branch 1 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
3 m_fontKerning = m_device.CreateBuffer({.label = "overlay kerning"}, kerning);
1885 3 }
1886
1887 15 OverlayParams params{};
1888
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 9 times.
15 params.font = m_fontField ? m_fontField->Address() : m_unused->Address();
1889
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 9 times.
15 params.glyphs = {m_fontGlyphs ? m_fontGlyphs->Address() : m_unused->Address()};
1890
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 9 times.
15 params.kerning = m_fontKerning ? m_fontKerning->Address() : m_unused->Address();
1891
3/3
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 9 times.
✓ Branch 3 taken 6 times.
15 params.textHeight = m_fontField ? OverlayTextFont().textHeight : 0;
1892 15 params.tables = {snapshot.tables};
1893
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 params.views = {m_views->Address()};
1894 15 params.batches = {m_batches->Address()};
1895 15 params.records = {m_records->Address()};
1896 15 params.tileCounts = {m_tileCounts->Address()};
1897 15 params.offsets = {m_offsets->Address()};
1898 15 params.keys = {m_keys->Address()};
1899 15 params.values = {m_values->Address()};
1900 15 params.pairCount = {m_pairCount->Address()};
1901 15 params.occupied = {m_occupied->Address()};
1902
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 params.viewCount = uint32_t(views.size());
1903 15 params.batchCount = uint32_t(batches.size());
1904 15 params.viewItemCount = viewItemCount;
1905 15 params.workCount = uint32_t(workCount);
1906 15 params.tileCount = tileCount;
1907 15 params.pairCapacity = m_pairCapacity;
1908 15 params.fills = {m_fills->Address()};
1909 15 params.fillCount = {m_fillCount->Address()};
1910 15 params.fillCapacity = uint32_t(fillCapacity);
1911
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 params.cameraImages = {sources.empty() ? m_unused->Address() : m_cameraImages->Address()};
1912
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 7 times.
15 params.cameraImageCount = uint32_t(sources.size());
1913
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 7 times.
15 params.outlines = {m_outlines.empty() ? m_unused->Address() : m_outlineBuffer->Address()};
1914
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 2 times.
15 params.outlineCount = uint32_t(m_outlines.size());
1915
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 2 times.
15 params.picks = {queries.empty() ? m_unused->Address() : m_pickQueries->Address()};
1916
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 2 times.
15 params.pickResults = {queries.empty() ? m_unused->Address() : m_pickResults->Address()};
1917
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 14 times.
15 params.pickCount = uint32_t(queries.size());
1918
1919
4/5
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 14 times.
✓ Branch 3 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 6 taken 1 time.
15 static const profiler::ZoneId zone = profiler::ZoneId::Named("Render/Overlays");
1920
1/1
✓ Branch 1 taken 15 times.
15 const device::ZoneScope scope(encoder, zone);
1921
1/1
✓ Branch 1 taken 15 times.
15 encoder.Barrier();
1922 54 const auto update = [&](device::Buffer& buffer, const auto& values) {
1923 54 const uint64_t bytes = values.size() * sizeof(values[0]);
1924 54 const auto* data = reinterpret_cast<const std::byte*>(values.data());
1925
10/10
auto chrono::batch_render::OverlayPass::Record(chrono::batch_render::device::CommandEncoder&, chrono::batch_render::Snapshot const&, unsigned int, std::span<chrono::batch_render::OverlayTarget const, 18446744073709551615ul>, std::span<std::shared_ptr<chrono::batch_render::OverlayData const> const, 18446744073709551615ul>, std::span<chrono::batch_render::OverlayCameraSource const, 18446744073709551615ul>, std::span<chrono::batch_render::OverlayPickRequest const, 18446744073709551615ul>)::{lambda(chrono::batch_render::device::Buffer&, auto:1 const&)#1}::operator()<std::vector<chrono::batch_render::OverlayBatch, std::allocator<chrono::batch_render::OverlayBatch> > >(chrono::batch_render::device::Buffer&, std::vector<chrono::batch_render::OverlayBatch, std::allocator<chrono::batch_render::OverlayBatch> > const&) const:
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 15 times.
auto chrono::batch_render::OverlayPass::Record(chrono::batch_render::device::CommandEncoder&, chrono::batch_render::Snapshot const&, unsigned int, std::span<chrono::batch_render::OverlayTarget const, 18446744073709551615ul>, std::span<std::shared_ptr<chrono::batch_render::OverlayData const> const, 18446744073709551615ul>, std::span<chrono::batch_render::OverlayCameraSource const, 18446744073709551615ul>, std::span<chrono::batch_render::OverlayPickRequest const, 18446744073709551615ul>)::{lambda(chrono::batch_render::device::Buffer&, auto:1 const&)#1}::operator()<std::vector<chrono::batch_render::OverlayCameraImage, std::allocator<chrono::batch_render::OverlayCameraImage> > >(chrono::batch_render::device::Buffer&, std::vector<chrono::batch_render::OverlayCameraImage, std::allocator<chrono::batch_render::OverlayCameraImage> > const&) const:
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 15 times.
auto chrono::batch_render::OverlayPass::Record(chrono::batch_render::device::CommandEncoder&, chrono::batch_render::Snapshot const&, unsigned int, std::span<chrono::batch_render::OverlayTarget const, 18446744073709551615ul>, std::span<std::shared_ptr<chrono::batch_render::OverlayData const> const, 18446744073709551615ul>, std::span<chrono::batch_render::OverlayCameraSource const, 18446744073709551615ul>, std::span<chrono::batch_render::OverlayPickRequest const, 18446744073709551615ul>)::{lambda(chrono::batch_render::device::Buffer&, auto:1 const&)#1}::operator()<std::vector<chrono::batch_render::OverlayOutline, std::allocator<chrono::batch_render::OverlayOutline> > >(chrono::batch_render::device::Buffer&, std::vector<chrono::batch_render::OverlayOutline, std::allocator<chrono::batch_render::OverlayOutline> > const&) const:
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 7 times.
auto chrono::batch_render::OverlayPass::Record(chrono::batch_render::device::CommandEncoder&, chrono::batch_render::Snapshot const&, unsigned int, std::span<chrono::batch_render::OverlayTarget const, 18446744073709551615ul>, std::span<std::shared_ptr<chrono::batch_render::OverlayData const> const, 18446744073709551615ul>, std::span<chrono::batch_render::OverlayCameraSource const, 18446744073709551615ul>, std::span<chrono::batch_render::OverlayPickRequest const, 18446744073709551615ul>)::{lambda(chrono::batch_render::device::Buffer&, auto:1 const&)#1}::operator()<std::vector<chrono::batch_render::OverlayPickQuery, std::allocator<chrono::batch_render::OverlayPickQuery> > >(chrono::batch_render::device::Buffer&, std::vector<chrono::batch_render::OverlayPickQuery, std::allocator<chrono::batch_render::OverlayPickQuery> > const&) const:
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
auto chrono::batch_render::OverlayPass::Record(chrono::batch_render::device::CommandEncoder&, chrono::batch_render::Snapshot const&, unsigned int, std::span<chrono::batch_render::OverlayTarget const, 18446744073709551615ul>, std::span<std::shared_ptr<chrono::batch_render::OverlayData const> const, 18446744073709551615ul>, std::span<chrono::batch_render::OverlayCameraSource const, 18446744073709551615ul>, std::span<chrono::batch_render::OverlayPickRequest const, 18446744073709551615ul>)::{lambda(chrono::batch_render::device::Buffer&, auto:1 const&)#1}::operator()<std::vector<chrono::batch_render::OverlayView, std::allocator<chrono::batch_render::OverlayView> > >(chrono::batch_render::device::Buffer&, std::vector<chrono::batch_render::OverlayView, std::allocator<chrono::batch_render::OverlayView> > const&) const:
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 15 times.
101 for (uint64_t at = 0; at < bytes; at += device::CommandEncoder::kMaxUpdateBytes) {
1926
5/10
auto chrono::batch_render::OverlayPass::Record(chrono::batch_render::device::CommandEncoder&, chrono::batch_render::Snapshot const&, unsigned int, std::span<chrono::batch_render::OverlayTarget const, 18446744073709551615ul>, std::span<std::shared_ptr<chrono::batch_render::OverlayData const> const, 18446744073709551615ul>, std::span<chrono::batch_render::OverlayCameraSource const, 18446744073709551615ul>, std::span<chrono::batch_render::OverlayPickRequest const, 18446744073709551615ul>)::{lambda(chrono::batch_render::device::Buffer&, auto:1 const&)#1}::operator()<std::vector<chrono::batch_render::OverlayBatch, std::allocator<chrono::batch_render::OverlayBatch> > >(chrono::batch_render::device::Buffer&, std::vector<chrono::batch_render::OverlayBatch, std::allocator<chrono::batch_render::OverlayBatch> > const&) const:
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
auto chrono::batch_render::OverlayPass::Record(chrono::batch_render::device::CommandEncoder&, chrono::batch_render::Snapshot const&, unsigned int, std::span<chrono::batch_render::OverlayTarget const, 18446744073709551615ul>, std::span<std::shared_ptr<chrono::batch_render::OverlayData const> const, 18446744073709551615ul>, std::span<chrono::batch_render::OverlayCameraSource const, 18446744073709551615ul>, std::span<chrono::batch_render::OverlayPickRequest const, 18446744073709551615ul>)::{lambda(chrono::batch_render::device::Buffer&, auto:1 const&)#1}::operator()<std::vector<chrono::batch_render::OverlayCameraImage, std::allocator<chrono::batch_render::OverlayCameraImage> > >(chrono::batch_render::device::Buffer&, std::vector<chrono::batch_render::OverlayCameraImage, std::allocator<chrono::batch_render::OverlayCameraImage> > const&) const:
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
auto chrono::batch_render::OverlayPass::Record(chrono::batch_render::device::CommandEncoder&, chrono::batch_render::Snapshot const&, unsigned int, std::span<chrono::batch_render::OverlayTarget const, 18446744073709551615ul>, std::span<std::shared_ptr<chrono::batch_render::OverlayData const> const, 18446744073709551615ul>, std::span<chrono::batch_render::OverlayCameraSource const, 18446744073709551615ul>, std::span<chrono::batch_render::OverlayPickRequest const, 18446744073709551615ul>)::{lambda(chrono::batch_render::device::Buffer&, auto:1 const&)#1}::operator()<std::vector<chrono::batch_render::OverlayOutline, std::allocator<chrono::batch_render::OverlayOutline> > >(chrono::batch_render::device::Buffer&, std::vector<chrono::batch_render::OverlayOutline, std::allocator<chrono::batch_render::OverlayOutline> > const&) const:
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
auto chrono::batch_render::OverlayPass::Record(chrono::batch_render::device::CommandEncoder&, chrono::batch_render::Snapshot const&, unsigned int, std::span<chrono::batch_render::OverlayTarget const, 18446744073709551615ul>, std::span<std::shared_ptr<chrono::batch_render::OverlayData const> const, 18446744073709551615ul>, std::span<chrono::batch_render::OverlayCameraSource const, 18446744073709551615ul>, std::span<chrono::batch_render::OverlayPickRequest const, 18446744073709551615ul>)::{lambda(chrono::batch_render::device::Buffer&, auto:1 const&)#1}::operator()<std::vector<chrono::batch_render::OverlayPickQuery, std::allocator<chrono::batch_render::OverlayPickQuery> > >(chrono::batch_render::device::Buffer&, std::vector<chrono::batch_render::OverlayPickQuery, std::allocator<chrono::batch_render::OverlayPickQuery> > const&) const:
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
auto chrono::batch_render::OverlayPass::Record(chrono::batch_render::device::CommandEncoder&, chrono::batch_render::Snapshot const&, unsigned int, std::span<chrono::batch_render::OverlayTarget const, 18446744073709551615ul>, std::span<std::shared_ptr<chrono::batch_render::OverlayData const> const, 18446744073709551615ul>, std::span<chrono::batch_render::OverlayCameraSource const, 18446744073709551615ul>, std::span<chrono::batch_render::OverlayPickRequest const, 18446744073709551615ul>)::{lambda(chrono::batch_render::device::Buffer&, auto:1 const&)#1}::operator()<std::vector<chrono::batch_render::OverlayView, std::allocator<chrono::batch_render::OverlayView> > >(chrono::batch_render::device::Buffer&, std::vector<chrono::batch_render::OverlayView, std::allocator<chrono::batch_render::OverlayView> > const&) const:
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
47 const uint64_t chunk = std::min(bytes - at, device::CommandEncoder::kMaxUpdateBytes);
1927 47 encoder.Update(buffer.Slice(at, chunk), data + at);
1928 }
1929 54 };
1930
1/1
✓ Branch 1 taken 15 times.
15 update(*m_views, views);
1931
1/1
✓ Branch 1 taken 15 times.
15 update(*m_batches, batches);
1932
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 if (!sources.empty()) {
1933
1/1
✓ Branch 1 taken 15 times.
15 update(*m_cameraImages, sources);
1934 }
1935
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 8 times.
15 if (!m_outlines.empty()) {
1936
1/1
✓ Branch 1 taken 7 times.
7 update(*m_outlineBuffer, m_outlines);
1937 }
1938
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 13 times.
15 if (!queries.empty()) {
1939
1/1
✓ Branch 1 taken 2 times.
2 update(*m_pickQueries, queries);
1940 }
1941
2/2
✓ Branch 1 taken 15 times.
✓ Branch 4 taken 15 times.
15 encoder.Fill(m_occupied->Slice(0, uint64_t(kOverlayTileList) * 4));
1942
1/1
✓ Branch 1 taken 15 times.
15 encoder.Fill(*m_fillCount);
1943
4/5
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 14 times.
✓ Branch 3 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 6 taken 1 time.
15 static const profiler::ZoneId projecting = profiler::ZoneId::Named("Render/Overlays/Project");
1944
4/5
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 14 times.
✓ Branch 3 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 6 taken 1 time.
15 static const profiler::ZoneId scanning = profiler::ZoneId::Named("Render/Overlays/Scan");
1945
4/5
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 14 times.
✓ Branch 3 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 6 taken 1 time.
15 static const profiler::ZoneId scattering = profiler::ZoneId::Named("Render/Overlays/Scatter");
1946
4/5
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 14 times.
✓ Branch 3 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 6 taken 1 time.
15 static const profiler::ZoneId sorting = profiler::ZoneId::Named("Render/Overlays/Sort");
1947
4/5
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 14 times.
✓ Branch 3 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 6 taken 1 time.
15 static const profiler::ZoneId ranging = profiler::ZoneId::Named("Render/Overlays/Tiles");
1948 15 static const profiler::ZoneId compositing =
1949
4/5
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 14 times.
✓ Branch 3 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 6 taken 1 time.
15 profiler::ZoneId::Named("Render/Overlays/Composite");
1950
4/5
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 14 times.
✓ Branch 3 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 6 taken 1 time.
15 static const profiler::ZoneId outlining = profiler::ZoneId::Named("Render/Overlays/Outline");
1951 30 const auto outline = [&] {
1952
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 7 times.
15 if (!outlined) {
1953 8 return;
1954 }
1955 7 encoder.Barrier();
1956 7 encoder.BeginZone(outlining);
1957
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 const uint32_t tiles = std::min(tileCount, kOverlayMostGroups);
1958 7 const device::Size3 groups{.x = tiles, .y = (tileCount + tiles - 1) / tiles};
1959 7 encoder.Dispatch(m_maskProgram->Entry("MaskOutlines"), params, groups);
1960 7 encoder.Barrier();
1961 7 encoder.Dispatch(m_program->Entry("OutlineOverlays"), params, groups);
1962 7 encoder.EndZone();
1963 15 };
1964
4/5
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 14 times.
✓ Branch 3 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 6 taken 1 time.
15 static const profiler::ZoneId picking = profiler::ZoneId::Named("Render/Overlays/Pick");
1965 30 const auto pick = [&] {
1966
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 13 times.
15 if (queries.empty()) {
1967 return;
1968 }
1969 2 encoder.Barrier();
1970 2 encoder.BeginZone(picking);
1971 2 const uint32_t count = params.pickCount;
1972 2 const uint64_t threads = uint64_t(count) * params.viewItemCount;
1973 2 encoder.Dispatch(m_program->Entry("ClearOverlayPicks"), params,
1974 2 {.x = device::GroupCount(count, kOverlayGroup)});
1975
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (threads > 0) {
1976 2 encoder.Barrier();
1977 2 encoder.Dispatch(m_program->Entry("MeasureOverlayPicks"), params,
1978 2 {.x = device::GroupCount(threads, kOverlayGroup)});
1979 2 encoder.Barrier();
1980 2 encoder.Dispatch(m_program->Entry("ChooseOverlayPicks"), params,
1981 2 {.x = device::GroupCount(threads, kOverlayGroup)});
1982 }
1983 2 encoder.Barrier();
1984 2 encoder.Dispatch(m_program->Entry("ResolveOverlayPicks"), params,
1985 2 {.x = device::GroupCount(count, kOverlayGroup)});
1986 2 encoder.Barrier();
1987
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
4 for (uint32_t at = 0; at < count; ++at) {
1988 2 encoder.Copy(m_pickResults->Slice(uint64_t(at) * sizeof(OverlayPickResult),
1989 sizeof(OverlayPickResult)),
1990 2 picks[at].readback->Slice(0, sizeof(OverlayPickResult)));
1991 }
1992 2 encoder.EndZone();
1993 15 };
1994
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 8 times.
15 if (batches.empty()) {
1995
1/1
✓ Branch 1 taken 7 times.
7 outline();
1996
1/1
✓ Branch 1 taken 7 times.
7 pick();
1997 7 return;
1998 }
1999
1/1
✓ Branch 1 taken 8 times.
8 RecordGroups(encoder);
2000
1/1
✓ Branch 1 taken 8 times.
8 RecordUnions(encoder, snapshot);
2001
1/1
✓ Branch 1 taken 8 times.
8 encoder.Barrier();
2002
1/1
✓ Branch 1 taken 8 times.
8 encoder.BeginZone(projecting);
2003
2/2
✓ Branch 1 taken 8 times.
✓ Branch 4 taken 8 times.
8 encoder.Dispatch(m_program->Entry("ProjectOverlays"), params,
2004
1/1
✓ Branch 1 taken 8 times.
8 {.x = device::GroupCount(params.workCount, kOverlayGroup)});
2005
1/1
✓ Branch 1 taken 8 times.
8 encoder.EndZone();
2006
1/1
✓ Branch 1 taken 8 times.
8 encoder.BeginZone(scanning);
2007
3/3
✓ Branch 1 taken 8 times.
✓ Branch 4 taken 8 times.
✓ Branch 7 taken 8 times.
8 m_scan->ExclusiveSum(encoder, m_tileCounts->Slice(0, workCount * 4),
2008 m_offsets->Slice(0, workCount * 4), params.workCount);
2009
1/1
✓ Branch 1 taken 8 times.
8 encoder.EndZone();
2010
1/1
✓ Branch 1 taken 8 times.
8 encoder.Barrier();
2011
1/1
✓ Branch 1 taken 8 times.
8 encoder.BeginZone(scattering);
2012
2/2
✓ Branch 1 taken 8 times.
✓ Branch 4 taken 8 times.
8 encoder.Dispatch(m_program->Entry("ScatterOverlays"), params,
2013
1/1
✓ Branch 1 taken 8 times.
8 {.x = device::GroupCount(params.workCount, kOverlayGroup)});
2014
1/1
✓ Branch 1 taken 8 times.
8 encoder.EndZone();
2015
1/1
✓ Branch 1 taken 8 times.
8 encoder.BeginZone(sorting);
2016
2/3
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 8 times.
16 m_sort->Sort(encoder, *m_keys, *m_values, *m_pairCount, m_pairCapacity,
2017
3/5
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 8 times.
24 uint32_t(std::max<int>(1, std::bit_width(tileCount - 1))));
2018
1/1
✓ Branch 1 taken 8 times.
8 encoder.EndZone();
2019
1/1
✓ Branch 1 taken 8 times.
8 encoder.Barrier();
2020
1/1
✓ Branch 1 taken 8 times.
8 encoder.BeginZone(ranging);
2021
2/2
✓ Branch 1 taken 8 times.
✓ Branch 4 taken 8 times.
8 encoder.Dispatch(m_program->Entry("FindOverlayTiles"), params,
2022
1/1
✓ Branch 1 taken 8 times.
8 {.x = device::GroupCount(m_pairCapacity, kOverlayGroup)});
2023
1/1
✓ Branch 1 taken 8 times.
8 encoder.EndZone();
2024
1/1
✓ Branch 1 taken 8 times.
8 encoder.Barrier();
2025
1/1
✓ Branch 1 taken 8 times.
8 encoder.BeginZone(compositing);
2026
2/2
✓ Branch 1 taken 8 times.
✓ Branch 4 taken 8 times.
8 encoder.Dispatch(m_program->Entry("FoldOverlayTiles"), params, {.x = 1});
2027
1/1
✓ Branch 1 taken 8 times.
8 encoder.Barrier();
2028
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 const uint32_t across = std::min(tileCount, kOverlayMostGroups);
2029
1/1
✓ Branch 1 taken 8 times.
8 encoder.Dispatch(m_program->Entry("CompositeOverlays"), params,
2030
1/1
✓ Branch 1 taken 8 times.
8 {.x = across, .y = (tileCount + across - 1) / across},
2031
1/1
✓ Branch 1 taken 8 times.
8 m_occupied->Slice(4, 12));
2032
1/1
✓ Branch 1 taken 8 times.
8 encoder.EndZone();
2033
1/1
✓ Branch 1 taken 8 times.
8 outline();
2034
1/1
✓ Branch 1 taken 8 times.
8 pick();
2035
1/1
✓ Branch 1 taken 8 times.
8 encoder.Barrier();
2036
1/1
✓ Branch 1 taken 8 times.
8 encoder.Copy(*m_pairCount, *m_pairsNeeded);
2037
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (!m_groupParams.empty()) {
2038
0/1
✗ Branch 1 not taken.
✗ const uint64_t bytes = m_groupParams.size() * 4;
2039
0/3
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ Branch 7 not taken.
✗ encoder.Copy(m_groupNeeds->Slice(0, bytes), m_groupNeedsRead->Slice(0, bytes));
2040 }
2041
1/1
✓ Branch 1 taken 8 times.
8 m_measuredKeys = m_groupKeys;
2042 67 }
2043
2044 8 void OverlayPass::RecordUnions(device::CommandEncoder& encoder, const Snapshot& snapshot) {
2045
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (m_uniteParams.empty()) {
2046 return;
2047 }
2048 ✗ encoder.Barrier();
2049
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ for (OverlayUniteParams& params : m_uniteParams) {
2050 ✗ params.tables = {snapshot.tables};
2051 ✗ encoder.Dispatch(
2052 ✗ m_program->Entry("ClearOverlayBoxes"), params,
2053 ✗ {.x = device::GroupCount(params.boxCount * kOverlayBoxWords, kOverlayGroup)});
2054 }
2055 ✗ encoder.Barrier();
2056
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ for (const OverlayUniteParams& params : m_uniteParams) {
2057 ✗ encoder.Dispatch(m_program->Entry("UniteOverlayBoxes"), params,
2058 ✗ {.x = device::GroupCount(params.primCount, kOverlayGroup)});
2059 }
2060 }
2061
2062 8 void OverlayPass::RecordGroups(device::CommandEncoder& encoder) {
2063
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 if (m_groupParams.empty()) {
2064 8 return;
2065 }
2066
0/5
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ static const profiler::ZoneId grouping = profiler::ZoneId::Named("Render/Overlays/Group");
2067 ✗ const device::ZoneScope scope(encoder, grouping);
2068 ✗ uint32_t rows = 0;
2069
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ for (const OverlayGroupParams& params : m_groupParams) {
2070
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ rows = std::max(rows, params.rows);
2071 }
2072
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (rows > m_groupSortCapacity) {
2073 ✗ m_groupSortCapacity = rows + rows / 2;
2074
0/1
✗ Branch 1 not taken.
✗ m_groupSort.emplace(m_device, m_groupSortCapacity, 4);
2075 }
2076
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ const uint64_t needBytes = std::max<uint64_t>(m_groupParams.size() * 4, 256);
2077
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ if (!m_groupNeeds || m_groupNeeds->Bytes() < needBytes) {
2078
0/3
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ m_groupNeeds = m_device.CreateBuffer({.bytes = needBytes, .label = "overlay group needs"});
2079
0/1
✗ Branch 1 not taken.
✗ m_groupNeedsRead = m_device.CreateBuffer({.bytes = needBytes,
2080 .memory = device::Memory::Readback,
2081
0/3
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ .label = "overlay group needs read"});
2082 ✗ std::fill_n(static_cast<uint32_t*>(m_groupNeedsRead->Mapped()), needBytes / 4, 0u);
2083 }
2084
0/2
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ encoder.Fill(m_groupNeeds->Slice(0, m_groupParams.size() * 4));
2085
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ for (OverlayGroupParams& params : m_groupParams) {
2086 ✗ params.needs = {m_groupNeeds->Address()};
2087 }
2088
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ for (size_t at = 0; at < m_groupParams.size(); ++at) {
2089
0/1
✗ Branch 1 not taken.
✗ const OverlayGroupParams& params = m_groupParams[at];
2090 ✗ const Grouping& buffers = m_groupings[at];
2091
0/1
✗ Branch 1 not taken.
✗ encoder.Barrier();
2092
0/2
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ encoder.Dispatch(m_program->Entry("KeyOverlayRows"), params,
2093
0/1
✗ Branch 1 not taken.
✗ {.x = device::GroupCount(params.rows, kOverlayGroup)});
2094
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 6 not taken.
✗ m_groupSort->Sort(encoder, buffers.keys->Slice(0, uint64_t(params.rows) * 4),
2095
0/1
✗ Branch 1 not taken.
✗ buffers.order->Slice(0, uint64_t(params.rows) * 4), params.rows,
2096
0/5
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ uint32_t(std::max<int>(1, std::bit_width(params.groups + 1))));
2097
0/1
✗ Branch 1 not taken.
✗ encoder.Barrier();
2098
0/2
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ encoder.Dispatch(m_program->Entry("StartOverlayGroups"), params,
2099
0/1
✗ Branch 1 not taken.
✗ {.x = device::GroupCount(params.groups + 3, kOverlayGroup)});
2100
0/1
✗ Branch 1 not taken.
✗ encoder.Barrier();
2101
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 6 not taken.
✗ encoder.Dispatch(m_program->Entry("MeasureOverlayGroups"), params,
2102
0/3
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ {.x = device::GroupCount(std::max(params.groups, 1u), kOverlayGroup)});
2103 }
2104 ✗ }
2105
2106 ✗ device::Tensor CompositeOverlay(const device::Tensor& color, const device::Tensor& overlay,
2107 const device::Operation& after, device::Operation* done) {
2108 ✗ const auto check = [](const device::Tensor& image, const char* what) {
2109 ✗ const Span<const int64_t> shape = image.Shape();
2110
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.
✗ if (image.Empty() || image.Type() != device::DType::UInt8 || shape.size() < 3 ||
2111
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ shape.back() != 4) {
2112
0/1
✗ Branch 2 not taken.
✗ throw Error("A composited {} is a [..., height, width, 4] uint8 tensor", what);
2113 }
2114
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (!image.Strides().empty()) {
2115 int64_t expected = 1;
2116
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ for (size_t axis = shape.size(); axis-- > 0;) {
2117
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ if (shape[axis] > 1 && image.Strides()[axis] != expected) {
2118
0/1
✗ Branch 2 not taken.
✗ throw UnsupportedError("A composited {} is contiguous", what);
2119 }
2120 ✗ expected *= shape[axis];
2121 }
2122 }
2123 ✗ };
2124 ✗ check(color, "color");
2125 ✗ check(overlay, "overlay");
2126
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ if (!std::ranges::equal(color.Shape(), overlay.Shape())) {
2127
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ throw Error("An overlay composites over color of its own shape");
2128 }
2129 ✗ device::Device& device = color.GetDevice();
2130
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (&overlay.GetDevice() != &device) {
2131 ✗ throw Error("An overlay composites over color on its own device, not {}",
2132
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ overlay.GetDevice().Adapter().alias);
2133 }
2134 ✗ const uint64_t pixels = uint64_t(color.Elements()) / 4;
2135
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (pixels > std::numeric_limits<uint32_t>::max()) {
2136 ✗ throw UnsupportedError("Compositing {} pixels at once; at most {} fit", pixels,
2137
0/1
✗ Branch 2 not taken.
✗ std::numeric_limits<uint32_t>::max());
2138 }
2139
0/1
✗ Branch 2 not taken.
✗ device::Tensor blended = device.CreateTensor(
2140
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ device::DType::UInt8, {color.Shape().begin(), color.Shape().end()}, "composited overlay");
2141 ✗ const Ref<device::Program> program =
2142
0/2
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ device.CreateProgram({.modules = {"chrono_batch_render.overlay.kernels.composite"},
2143 .library = &kOverlayKernels,
2144 .entries = {"BlendOverlay"},
2145
0/3
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ Branch 8 not taken.
✗ .label = "overlays"});
2146 ✗ device::Queue& queue = device.GetQueue();
2147
0/1
✗ Branch 1 not taken.
✗ queue.Follow(after);
2148
0/1
✗ Branch 1 not taken.
✗ device::CommandEncoder encoder = queue.CreateEncoder();
2149
0/5
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ static const profiler::ZoneId compositing = profiler::ZoneId::Named("Overlay/Composite");
2150
0/1
✗ Branch 1 not taken.
✗ encoder.BeginZone(compositing);
2151
0/3
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ Branch 7 not taken.
✗ encoder.Dispatch(
2152
0/1
✗ Branch 1 not taken.
✗ program->Entry("BlendOverlay"),
2153
0/1
✗ Branch 1 not taken.
✗ BlendParams{
2154
0/3
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ Branch 7 not taken.
✗ {color.Address()}, {overlay.Address()}, {blended.Address()}, uint32_t(pixels), 0},
2155
0/1
✗ Branch 1 not taken.
✗ {.x = device::GroupCount(pixels, kOverlayGroup)});
2156
0/1
✗ Branch 1 not taken.
✗ encoder.EndZone();
2157
0/2
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ const device::Operation blending = queue.Submit(encoder.Finish());
2158
0/4
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ queue.Retire(std::make_shared<std::pair<device::Tensor, device::Tensor>>(color, overlay),
2159 blending);
2160
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (done != nullptr) {
2161 ✗ *done = blending;
2162 }
2163 ✗ return blended;
2164 ✗ }
2165
2166 }
2167