GCC Code Coverage Report


ernie/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
225 / 304
74.0%
Branches:
137 / 257
53.3%
Functions:
28 / 39
71.8%

render/scene_columns.cpp
Line Branch Exec Source
1 // SPDX-License-Identifier: BSD-3-Clause
2 /// @file scene_columns.cpp
3 /// @brief Replaying bucket row changes on device columns, and converting host rows to floats.
4 #include "ernie/render/scene_columns.h"
5
6 #include <algorithm>
7 #include <cstring>
8 #include <limits>
9 #include <stdexcept>
10 #include <type_traits>
11 #include <unordered_map>
12
13 namespace ernie {
14 namespace {
15
16 /// Where a row's values come from after a sync: a row of the device column before it, or the
17 /// host.
18 constexpr std::uint32_t kFromHost = std::numeric_limits<std::uint32_t>::max();
19
20 /// Writes one host value as the device element's bytes.
21 /// @return The bytes after it.
22 523 template <typename T> std::byte* WriteDevice(const T& value, std::byte* out) {
23 523 const auto write = [&](const auto* data, std::size_t count) {
24 const std::size_t bytes = count * sizeof(*data);
25 std::memcpy(out, data, bytes);
26 return out + bytes;
27 };
28 if constexpr (std::is_same_v<T, double>) {
29 11 const float converted = static_cast<float>(value);
30 11 return write(&converted, 1);
31 } else if constexpr (std::is_same_v<T, glm::dquat>) {
32 128 const glm::vec4 converted(value.x, value.y, value.z, value.w);
33 128 return write(&converted.x, 4);
34 } else if constexpr (std::is_same_v<T, glm::dmat4>) {
35 ✗ const glm::mat4 converted(value);
36 ✗ return write(&converted[0][0], 16);
37 } else if constexpr (std::is_same_v<T, glm::dvec2> || std::is_same_v<T, glm::dvec3> ||
38 std::is_same_v<T, glm::dvec4>) {
39 384 const glm::vec<T::length(), float> converted(value);
40 384 return write(&converted.x, T::length());
41 } else {
42 ✗ return write(&value, 1);
43 }
44 }
45
46 /// Converts rows of the host column into `out`, a device row each.
47 11 void ConvertRows(const ColumnValues& values, std::uint32_t begin, std::uint32_t end,
48 std::span<std::byte> out) {
49 11 WithColumnType(values.Type(), [&]<typename T>(std::type_identity<T>) {
50 if constexpr (std::is_trivially_copyable_v<T> && !std::is_same_v<T, Token> &&
51 !std::is_same_v<T, Target>) {
52 11 const std::vector<T>& rows = static_cast<const TypedColumn<T>&>(values).Values();
53 11 std::byte* next = out.data();
54
6/16
auto ernie::(anonymous namespace)::ConvertRows(ernie::ColumnValues const&, unsigned int, unsigned int, std::span<std::byte, 18446744073709551615ul>)::{lambda<typename $T0>(std::type_identity<$T0>)#1}::operator()<double>(std::type_identity<double>) const:
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 3 times.
auto ernie::(anonymous namespace)::ConvertRows(ernie::ColumnValues const&, unsigned int, unsigned int, std::span<std::byte, 18446744073709551615ul>)::{lambda<typename $T0>(std::type_identity<$T0>)#1}::operator()<glm::mat<4, 4, double, (glm::qualifier)0> >(std::type_identity<glm::mat<4, 4, double, (glm::qualifier)0> >) const:
✗ Branch 0 not taken.
✗ Branch 1 not taken.
auto ernie::(anonymous namespace)::ConvertRows(ernie::ColumnValues const&, unsigned int, unsigned int, std::span<std::byte, 18446744073709551615ul>)::{lambda<typename $T0>(std::type_identity<$T0>)#1}::operator()<glm::qua<double, (glm::qualifier)0> >(std::type_identity<glm::qua<double, (glm::qualifier)0> >) const:
✓ Branch 0 taken 128 times.
✓ Branch 1 taken 1 time.
auto ernie::(anonymous namespace)::ConvertRows(ernie::ColumnValues const&, unsigned int, unsigned int, std::span<std::byte, 18446744073709551615ul>)::{lambda<typename $T0>(std::type_identity<$T0>)#1}::operator()<glm::vec<2, double, (glm::qualifier)0> >(std::type_identity<glm::vec<2, double, (glm::qualifier)0> >) const:
✗ Branch 0 not taken.
✗ Branch 1 not taken.
auto ernie::(anonymous namespace)::ConvertRows(ernie::ColumnValues const&, unsigned int, unsigned int, std::span<std::byte, 18446744073709551615ul>)::{lambda<typename $T0>(std::type_identity<$T0>)#1}::operator()<glm::vec<3, double, (glm::qualifier)0> >(std::type_identity<glm::vec<3, double, (glm::qualifier)0> >) const:
✓ Branch 0 taken 384 times.
✓ Branch 1 taken 7 times.
auto ernie::(anonymous namespace)::ConvertRows(ernie::ColumnValues const&, unsigned int, unsigned int, std::span<std::byte, 18446744073709551615ul>)::{lambda<typename $T0>(std::type_identity<$T0>)#1}::operator()<glm::vec<4, double, (glm::qualifier)0> >(std::type_identity<glm::vec<4, double, (glm::qualifier)0> >) const:
✗ Branch 0 not taken.
✗ Branch 1 not taken.
auto ernie::(anonymous namespace)::ConvertRows(ernie::ColumnValues const&, unsigned int, unsigned int, std::span<std::byte, 18446744073709551615ul>)::{lambda<typename $T0>(std::type_identity<$T0>)#1}::operator()<int>(std::type_identity<int>) const:
✗ Branch 0 not taken.
✗ Branch 1 not taken.
auto ernie::(anonymous namespace)::ConvertRows(ernie::ColumnValues const&, unsigned int, unsigned int, std::span<std::byte, 18446744073709551615ul>)::{lambda<typename $T0>(std::type_identity<$T0>)#1}::operator()<unsigned char>(std::type_identity<unsigned char>) const:
✗ Branch 0 not taken.
✗ Branch 1 not taken.
534 for (std::uint32_t row = begin; row < end; ++row) {
55 523 next = WriteDevice(rows[row], next);
56 }
57 }
58 11 });
59 11 }
60
61 /// @return Every row of the host column as the device column's bytes.
62 7 std::vector<std::byte> DeviceBytes(const ColumnValues& values, const DeviceColumn& column) {
63 7 std::vector<std::byte> bytes(column.Bytes());
64
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 ConvertRows(values, 0, static_cast<std::uint32_t>(column.Rows()), bytes);
65 7 return bytes;
66 ✗ }
67
68 } // namespace
69
70 12 std::optional<DeviceElement> ToDeviceElement(ColumnType type) {
71
6/8
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 1 time.
12 switch (type) {
72 ✗ case ColumnType::Bool:
73 ✗ return DeviceElement{DType::UInt8, {}};
74 1 case ColumnType::Int:
75 1 return DeviceElement{DType::Int32, {}};
76 2 case ColumnType::Double:
77 2 return DeviceElement{DType::Float32, {}};
78 ✗ case ColumnType::Vec2:
79 ✗ return DeviceElement{DType::Float32, {2}};
80 5 case ColumnType::Vec3:
81 5 return DeviceElement{DType::Float32, {3}};
82 2 case ColumnType::Vec4:
83 2 case ColumnType::Quat:
84 2 return DeviceElement{DType::Float32, {4}};
85 1 case ColumnType::Matrix:
86 1 return DeviceElement{DType::Float32, {4, 4}};
87 1 default:
88 1 return std::nullopt;
89 }
90 }
91
92 19 SceneColumns::SceneColumns(DeviceContext& context, Arenas& arenas, const Scene& scene)
93 19 : context_(context), arenas_(arenas), scene_(scene), uploader_(context, context.Main()) {}
94
95 19 SceneColumns::~SceneColumns() = default;
96
97 9 SceneColumns::Host SceneColumns::HostColumn(BucketId bucket, std::string_view attribute) const {
98 9 const PrimStore& store = scene_.Store();
99 9 const std::optional<Token> token = store.Tokens().Find(attribute);
100
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 1 time.
9 const Column* column = token ? store.GetBucket(bucket).Find(*token) : nullptr;
101
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (column == nullptr) {
102
3/6
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 time.
✗ Branch 6 not taken.
✓ Branch 8 taken 1 time.
✗ Branch 9 not taken.
2 throw std::invalid_argument("The bucket has no column " + std::string(attribute));
103 }
104 8 const std::optional<DeviceElement> element = ToDeviceElement(column->values->Type());
105
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (!element) {
106
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ throw std::invalid_argument(std::string(attribute) +
107
0/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ " holds values of no fixed size, which stay on the host");
108 }
109
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 return {*token, column, *element, store.GetBucket(bucket).Rows()};
110
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 }
111
112 9 const DeviceColumn& SceneColumns::Bind(BucketId bucket, std::string_view attribute) {
113 9 const Host host = HostColumn(bucket, attribute);
114
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 7 times.
8 if (const Binding* bound = FindBinding(bucket, host.attribute)) {
115 1 return *bound->column;
116 }
117 7 auto column = std::make_unique<DeviceColumn>(arenas_, attribute, host.element.type,
118
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 host.element.shape, host.rows);
119
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 std::vector<std::byte> shadow = DeviceBytes(*host.column->values, *column);
120 7 Binding& binding = bindings_.emplace_back(Binding{
121
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 bucket, host.attribute, scene_.Store().Version(), std::move(column), std::move(shadow)});
122
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 uploader_.Write(binding.column->Storage(), binding.column->Range().offset,
123
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 std::span<const std::byte>(binding.shadow));
124 7 return *binding.column;
125 15 }
126
127 ✗ const DeviceColumn& SceneColumns::Bind(BucketId bucket, std::string_view attribute,
128 std::unique_ptr<DeviceColumn> column) {
129 ✗ const Host host = HostColumn(bucket, attribute);
130
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (FindBinding(bucket, host.attribute) != nullptr) {
131
0/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ throw std::invalid_argument(std::string(attribute) + " is bound already");
132 }
133
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 (column->Rows() != host.rows || column->Type() != host.element.type ||
134
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ column->Element() != host.element.shape) {
135
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ throw std::invalid_argument(column->Name() + " does not hold the bucket's " +
136
0/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ std::string(attribute) + " rows");
137 }
138 // The device's values stand; the shadow holds the host's, to tell its later edits apart.
139
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ std::vector<std::byte> shadow = DeviceBytes(*host.column->values, *column);
140 ✗ Binding& binding = bindings_.emplace_back(Binding{
141
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ bucket, host.attribute, scene_.Store().Version(), std::move(column), std::move(shadow)});
142 ✗ return *binding.column;
143 ✗ }
144
145 1 const DeviceColumn* SceneColumns::Find(BucketId bucket, std::string_view attribute) const {
146 1 const std::optional<Token> token = scene_.Store().Tokens().Find(attribute);
147
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 const Binding* binding = token ? FindBinding(bucket, *token) : nullptr;
148
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 return binding ? binding->column.get() : nullptr;
149 }
150
151 9 const SceneColumns::Binding* SceneColumns::FindBinding(BucketId bucket,
152 AttributeId attribute) const {
153
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 7 times.
11 for (const Binding& binding : bindings_) {
154
3/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
4 if (binding.bucket == bucket && binding.attribute == attribute) {
155 2 return &binding;
156 }
157 }
158 7 return nullptr;
159 }
160
161 14 void SceneColumns::Upload(const Binding& binding, std::span<const std::uint32_t> rows) {
162 14 const DeviceColumn& device = *binding.column;
163 14 uploader_.WriteRows(device.Storage(), device.Range().offset, device.RowBytes(), binding.shadow,
164 rows);
165 14 }
166
167 14 SceneColumns::Origins SceneColumns::RowOrigins(const ChangeSet& changes, BucketId bucket) {
168 14 Origins origins;
169
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 14 times.
25 for (const ChangeSet::Rows& entry : changes.rows) {
170
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
11 if (entry.bucket != bucket) {
171 ✗ continue;
172 }
173 11 const StructuralChange& change = entry.change;
174
3/5
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
11 switch (change.kind) {
175 1 case StructuralChange::Kind::Added:
176
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 for (std::uint32_t row = change.row; row < change.row + change.count; ++row) {
177
1/2
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
1 origins[row] = kFromHost;
178 }
179 1 break;
180 5 case StructuralChange::Kind::Removed:
181 // Unless it was the last, a Moved change brings the last row here next.
182 5 origins.erase(change.row);
183 break;
184 5 case StructuralChange::Kind::Moved:
185
1/2
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 origins[change.row] = Origin(origins, change.from);
186 5 origins.erase(change.from);
187 break;
188 ✗ case StructuralChange::Kind::Reset:
189 ✗ origins.clear();
190
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ for (std::uint32_t row = 0; row < change.count; ++row) {
191
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ origins[row] = kFromHost;
192 }
193 ✗ break;
194 }
195 }
196 14 return origins;
197 ✗ }
198
199 134 std::uint32_t SceneColumns::Origin(const Origins& origins, std::uint32_t row) {
200 134 const auto found = origins.find(row);
201
2/2
✓ Branch 0 taken 128 times.
✓ Branch 1 taken 6 times.
140 return found == origins.end() ? row : found->second;
202 }
203
204 14 std::vector<VkBufferCopy> SceneColumns::MoveRows(Binding& binding, const Origins& origins,
205 std::uint32_t rows,
206 std::vector<std::uint32_t>& hostRows) {
207
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 const std::uint64_t rowBytes = binding.column->RowBytes();
208 14 std::vector<VkBufferCopy> copies;
209
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
✓ Branch 3 taken 14 times.
✗ Branch 4 not taken.
14 binding.shadow.resize(std::max<std::uint64_t>(binding.shadow.size(), rows * rowBytes));
210
3/4
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 14 times.
26 for (const auto& [row, source] : origins) {
211
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (row >= rows) {
212 ✗ continue;
213 }
214
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 5 times.
6 if (source == kFromHost) {
215
1/2
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
1 hostRows.push_back(row);
216
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 } else if (source != row) {
217 // Every source is a row removed or past the end, never another copy's target.
218
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 copies.push_back({source * rowBytes, row * rowBytes, rowBytes});
219 5 std::memcpy(binding.shadow.data() + row * rowBytes,
220 5 binding.shadow.data() + source * rowBytes, rowBytes);
221 }
222 }
223
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 binding.shadow.resize(rows * rowBytes);
224 14 return copies;
225 ✗ }
226
227 14 void SceneColumns::FindAuthoredRows(const ChangeSet& changes, Binding& binding,
228 const Origins& origins, std::uint32_t rows,
229 std::vector<std::uint32_t>& hostRows) {
230 // Changes come by chunk, so rows the device wrote are compared with what the host last gave,
231 // not taken on trust.
232 14 const ColumnValues& host =
233 14 *scene_.Store().GetBucket(binding.bucket).Find(binding.attribute)->values;
234 14 const std::uint64_t rowBytes = binding.column->RowBytes();
235
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 14 times.
46 for (const ChangeSet::Values& values : changes.values) {
236
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 3 times.
32 if (values.bucket != binding.bucket || values.attribute != binding.attribute) {
237 29 continue;
238 }
239
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 for (const RowRange& range : values.rows) {
240
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 const std::uint32_t end = std::min(range.end, rows);
241
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (range.begin >= end) {
242 ✗ continue;
243 }
244 3 scratch_.resize((end - range.begin) * rowBytes);
245 3 ConvertRows(host, range.begin, end, scratch_);
246
2/2
✓ Branch 0 taken 129 times.
✓ Branch 1 taken 3 times.
132 for (std::uint32_t row = range.begin; row < end; ++row) {
247 129 const std::byte* given = scratch_.data() + (row - range.begin) * rowBytes;
248 129 std::byte* shadow = binding.shadow.data() + row * rowBytes;
249
2/2
✓ Branch 1 taken 128 times.
✓ Branch 2 taken 1 time.
129 if (Origin(origins, row) != kFromHost &&
250
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 126 times.
128 std::memcmp(given, shadow, rowBytes) != 0) {
251 2 std::memcpy(shadow, given, rowBytes);
252 2 hostRows.push_back(row);
253 }
254 }
255 }
256 }
257 14 }
258
259 6 Ticket SceneColumns::Sync(std::span<const Ticket> waits) {
260 6 ERNIE_ZONE("Sync scene columns");
261
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 const std::vector<Ticket> after = Prepare(waits);
262
3/4
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
6 if (moves_.empty() && !uploader_.Staged()) {
263
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 return context_.Main().Last();
264 }
265 1 std::vector<std::shared_ptr<const void>> kept;
266
1/2
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
1 const Ticket synced = context_.Main().Submit(
267
1/2
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
1 [&](VkCommandBuffer commands) {
268 2 Record(commands,
269
3/6
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✓ Branch 6 taken 1 time.
✗ Branch 7 not taken.
2 [&](std::shared_ptr<const void> object) { kept.push_back(std::move(object)); });
270 1 },
271 after);
272
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 for (std::shared_ptr<const void>& object : kept) {
273
1/2
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
2 context_.Retire(std::move(object));
274 }
275 1 return synced;
276 7 }
277
278 29 void SceneColumns::AddSync(graph::Graph& graph) {
279 29 ERNIE_ZONE("Sync scene columns");
280 // What the host work waits on was submitted to the main queue, before the graph's work.
281
1/2
✓ Branch 1 taken 29 times.
✗ Branch 2 not taken.
29 Prepare({});
282
3/4
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 27 times.
✗ Branch 3 not taken.
29 if (moves_.empty() && !uploader_.Staged()) {
283 27 return;
284 }
285 2 std::vector<graph::Buffer> columns;
286
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 for (const Binding& binding : bindings_) {
287
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
4 columns.push_back(graph.Import(*binding.column, binding.column->Name()));
288 }
289 4 graph.Add(
290 "Sync scene columns", graph::Kind::Copy,
291 6 [&](graph::PassBuilder& pass) {
292
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 for (graph::Buffer& column : columns) {
293 2 pass.Read(column, graph::Use::CopySrc);
294 2 column = pass.Write(column, graph::Use::CopyDst);
295 }
296 2 },
297
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 [this](graph::PassContext& pass) {
298 4 Record(pass.commands,
299
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
4 [&](std::shared_ptr<const void> object) { pass.Keep(std::move(object)); });
300 2 });
301 29 }
302
303 3 void SceneColumns::Record(VkCommandBuffer commands,
304 const std::function<void(std::shared_ptr<const void>)>& keep) {
305
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (!moves_.empty()) {
306 3 std::vector<BufferCopies> copies;
307
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 for (auto& [column, regions] : moves_) {
308
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 3 times.
8 for (VkBufferCopy& region : regions) {
309 5 region.srcOffset += column->Range().offset;
310 5 region.dstOffset += column->Range().offset;
311 }
312
2/4
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
3 const VkBuffer buffer = column->Storage().Handle();
313
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 copies.push_back({buffer, buffer, regions});
314 }
315
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 CopyBuffers(commands, copies);
316 3 moves_.clear();
317 3 }
318
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 3 times.
5 for (std::shared_ptr<const Buffer>& staging : uploader_.Record(commands)) {
319
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
4 keep(std::move(staging));
320 3 }
321 3 }
322
323 35 std::vector<Ticket> SceneColumns::Prepare(std::span<const Ticket> waits) {
324 35 const PrimStore& store = scene_.Store();
325 35 std::vector<Ticket> after(waits.begin(), waits.end());
326 // Values staged by `Bind` land before any column moves to a larger range.
327
1/2
✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
35 after.push_back(uploader_.Flush(waits));
328 35 moves_.clear();
329 // Bindings mostly share a cursor, and so the changes since it.
330 35 std::optional<ChangeSet> changes;
331 35 std::uint64_t changesCursor = 0;
332
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 35 times.
49 for (Binding& binding : bindings_) {
333
3/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
14 if (!changes || changesCursor != binding.cursor) {
334 10 changesCursor = binding.cursor;
335
1/4
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
20 changes = store.ChangesSince(binding.cursor);
336 }
337
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 binding.cursor = changes->version;
338
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 const Origins origins = RowOrigins(*changes, binding.bucket);
339
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 const std::uint32_t rows = store.GetBucket(binding.bucket).Rows();
340 14 std::vector<std::uint32_t> hostRows;
341
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 std::vector<VkBufferCopy> copies = MoveRows(binding, origins, rows, hostRows);
342
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 11 times.
14 if (!copies.empty()) {
343
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 moves_.emplace_back(binding.column.get(), std::move(copies));
344 }
345
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 DeviceColumn& column = *binding.column;
346
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 if (rows > column.Rows()) {
347
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ after.push_back(column.Resize(rows, context_.Main(), after));
348 } else {
349
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 column.Resize(rows, context_.Main());
350 }
351 // Rows new from the host take its values into the shadow; authored rows did already.
352 14 const ColumnValues& host = *store.GetBucket(binding.bucket).Find(binding.attribute)->values;
353 14 const std::uint64_t rowBytes = column.RowBytes();
354
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 14 times.
15 for (const std::uint32_t row : hostRows) {
355
1/2
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
1 ConvertRows(host, row, row + 1,
356
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
2 std::span(binding.shadow).subspan(row * rowBytes, rowBytes));
357 }
358
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 FindAuthoredRows(*changes, binding, origins, rows, hostRows);
359 14 std::ranges::sort(hostRows);
360
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 Upload(binding, hostRows);
361 14 }
362
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 25 times.
35 return after;
363 35 }
364
365 } // namespace ernie
366