GCC Code Coverage Report


ernie/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
251 / 260
96.5%
Branches:
185 / 270
68.5%
Functions:
22 / 22
100.0%

render/geometry.cpp
Line Branch Exec Source
1 // SPDX-License-Identifier: BSD-3-Clause
2 /// @file geometry.cpp
3 /// @brief Following the scene's gprims: meshes uploaded as triangles, implicit shapes placed on
4 /// shared unit shapes.
5 #include "ernie/render/geometry.h"
6
7 #include "ernie/geometry/triangulation.h"
8 #include "ernie/scene/convert.h"
9 #include "ernie/scene/schema/geom.h"
10 #include "ernie/scene/schema/mesh.h"
11 #include "ernie/scene/schema/planet.h"
12
13 #include <glm/gtc/matrix_transform.hpp>
14
15 #include <array>
16 #include <stdexcept>
17 #include <string>
18
19 namespace ernie {
20 namespace {
21
22 /// Metres from a ground plane's centre to its edges: far past any horizon a camera near it sees.
23 constexpr double kGroundHalfWidth = 1e5;
24
25 117 std::optional<GeometryKind> FindGeometryKind(const Scene& scene, PrimId prim) {
26 117 const SchemaDef* schema = scene.Schema(prim);
27 117 const std::array<std::pair<const SchemaDef*, GeometryKind>, 8> kinds{{
28 117 {&Mesh::Definition(), GeometryKind::Triangles},
29 117 {&Sphere::Definition(), GeometryKind::Sphere},
30 117 {&Cube::Definition(), GeometryKind::Cube},
31 117 {&Cylinder::Definition(), GeometryKind::Cylinder},
32 117 {&Cone::Definition(), GeometryKind::Cone},
33 117 {&Capsule::Definition(), GeometryKind::Capsule},
34 117 {&Plane::Definition(), GeometryKind::Plane},
35 117 {&GroundPlane::Definition(), GeometryKind::Plane},
36 117 }};
37
2/2
✓ Branch 1 taken 726 times.
✓ Branch 2 taken 76 times.
802 for (const auto& [definition, kind] : kinds) {
38
2/2
✓ Branch 1 taken 41 times.
✓ Branch 2 taken 685 times.
726 if (IsA(schema, *definition)) {
39 41 return kind;
40 }
41 }
42 76 return std::nullopt;
43 }
44
45 41 double RequireNumber(const Scene& scene, PrimId prim, std::string_view attribute) {
46 41 const std::optional<Value> value = scene.GetOrFallback(prim, attribute);
47
1/2
✓ Branch 0 taken 41 times.
✗ Branch 1 not taken.
41 const std::optional<double> number = value ? ToNumber(*value) : std::nullopt;
48
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 41 times.
41 if (!number) {
49
0/6
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ throw std::invalid_argument("'" + scene.PrimPath(prim).String() + "' has no number " +
50
0/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ std::string(attribute));
51 }
52 41 return *number;
53 41 }
54
55 /// @return The prim's display colour, authored in double or float precision.
56 41 glm::vec3 DisplayColour(const Scene& scene, PrimId prim) {
57 41 const std::optional<Value> value = scene.GetOrFallback(prim, Gprim::kDisplayColor);
58
1/2
✓ Branch 0 taken 41 times.
✗ Branch 1 not taken.
41 if (value) {
59
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 41 times.
41 if (const auto* colour = value->Get<glm::dvec3>()) {
60 41 return glm::vec3(*colour);
61 }
62
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (const auto* colour = value->Get<glm::vec3>()) {
63 ✗ return *colour;
64 }
65 }
66 ✗ return glm::vec3(0.5f);
67 41 }
68
69 /// @return The turn taking a unit shape's +z to the prim's axis, cyclically, keeping handedness.
70 8 glm::dmat4 AxisTurn(const Scene& scene, PrimId prim) {
71 8 const std::optional<Value> axis = scene.GetOrFallback(prim, Cylinder::kAxis);
72
2/4
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
8 const auto* name = axis ? axis->Get<std::string>() : nullptr;
73
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 const char letter = name != nullptr && !name->empty() ? name->front() : 'Z';
74 8 glm::dmat4 turn(0.0);
75 8 turn[3][3] = 1.0;
76 // Columns are the images of the unit x, y and z axes.
77
3/4
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
8 const int z = letter == 'X' ? 0 : letter == 'Y' ? 1 : 2;
78 8 turn[0][(z + 1) % 3] = 1.0;
79 8 turn[1][(z + 2) % 3] = 1.0;
80 8 turn[2][z] = 1.0;
81 8 return turn;
82 8 }
83
84 /// @return The transform from a kind's unit shape to the prim's shape.
85 41 glm::dmat4 ShapeTransform(const Scene& scene, PrimId prim, GeometryKind kind) {
86 73 const auto scaled = [](const glm::dvec3& halves) {
87 32 return glm::scale(glm::dmat4(1.0), halves);
88 };
89
6/6
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 9 times.
41 switch (kind) {
90 21 case GeometryKind::Sphere:
91 21 return scaled(glm::dvec3(RequireNumber(scene, prim, Sphere::kRadius)));
92 1 case GeometryKind::Cube:
93 1 return scaled(glm::dvec3(0.5 * RequireNumber(scene, prim, Cube::kSize)));
94 2 case GeometryKind::Cylinder:
95 2 case GeometryKind::Cone: {
96 2 const double radius = RequireNumber(scene, prim, Cylinder::kRadius);
97 2 return AxisTurn(scene, prim) *
98 4 scaled({radius, radius, 0.5 * RequireNumber(scene, prim, Cylinder::kHeight)});
99 }
100 3 case GeometryKind::Capsule:
101 3 return AxisTurn(scene, prim) *
102 6 scaled(glm::dvec3(RequireNumber(scene, prim, Capsule::kRadius)));
103 5 case GeometryKind::Plane: {
104
2/2
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 3 times.
5 if (IsA(scene.Schema(prim), GroundPlane::Definition())) {
105 2 return scaled({kGroundHalfWidth, kGroundHalfWidth, 1.0});
106 }
107 // Width along x, or z for an X normal; length along y, or z for a Y normal: under the
108 // cyclic turn, the unit square's x carries the length unless the normal is z.
109 3 const double width = 0.5 * RequireNumber(scene, prim, Plane::kWidth);
110 3 const double length = 0.5 * RequireNumber(scene, prim, Plane::kLength);
111 3 const glm::dmat4 turn = AxisTurn(scene, prim);
112 3 const bool normalZ = turn[2][2] == 1.0;
113 3 return turn *
114
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 2 times.
3 scaled(normalZ ? glm::dvec3(width, length, 1.0) : glm::dvec3(length, width, 1.0));
115 }
116 case GeometryKind::Triangles:
117 break;
118 }
119 9 return glm::dmat4(1.0);
120 }
121
122 /// What a changed attribute does to a gprim: reshapes it, moves a mesh's points, remakes its
123 /// topology, or recolours it.
124 struct Touched {
125 bool shape = false;
126 bool points = false;
127 bool topology = false;
128 bool colour = false;
129
130 105 bool Any() const {
131
8/8
✓ Branch 0 taken 81 times.
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 75 times.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 63 times.
✓ Branch 5 taken 12 times.
✓ Branch 6 taken 60 times.
✓ Branch 7 taken 3 times.
105 return shape || points || topology || colour;
132 }
133 };
134
135 105 Touched ClassifyAttribute(std::string_view attribute) {
136
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 102 times.
105 if (attribute == Gprim::kDisplayColor) {
137 3 return {.colour = true};
138 }
139
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 96 times.
102 if (attribute == Mesh::kPoints) {
140 6 return {.points = true};
141 }
142
4/4
✓ Branch 0 taken 90 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 84 times.
96 if (attribute == Mesh::kFaceVertexCounts || attribute == Mesh::kFaceVertexIndices) {
143 12 return {.topology = true};
144 }
145 404 for (const std::string_view name : {Sphere::kRadius, Cube::kSize, Cylinder::kHeight,
146
2/2
✓ Branch 0 taken 428 times.
✓ Branch 1 taken 60 times.
488 Cylinder::kAxis, Plane::kWidth, Plane::kLength}) {
147
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 404 times.
428 if (attribute == name) {
148 24 return {.shape = true};
149 }
150 }
151 60 return {};
152 }
153
154 } // namespace
155
156 17 GeometryPool::GeometryPool(DeviceContext& context, Arenas& arenas, const Scene& scene)
157 17 : context_(context), arenas_(arenas), scene_(scene), uploader_(context, context.Main()),
158
1/2
✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
17 unitShapes_(static_cast<std::size_t>(GeometryKind::Plane) + 1),
159 17 records_(std::make_unique<DeviceColumn>(
160
1/2
✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
17 arenas, "geometry records", DType::UInt32,
161
2/4
✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 17 times.
✗ Branch 5 not taken.
34 std::vector<std::int64_t>{sizeof(GeometryRecord) / sizeof(std::uint32_t)}, 0)) {}
162
163 43 void GeometryPool::Touch(GeometryId geometry) {
164 43 dirtyRecords_.push_back(geometry);
165 43 }
166
167 61 std::optional<PrimGeometry> GeometryPool::Find(PrimId prim) const {
168
5/6
✓ Branch 0 taken 59 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 58 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 58 times.
✗ Branch 5 not taken.
61 if (prim.slot >= prims_.size() || !prims_[prim.slot] || !(prims_[prim.slot]->prim == prim)) {
169 3 return std::nullopt;
170 }
171 58 return prims_[prim.slot];
172 }
173
174 31 GeometryId GeometryPool::Allocate(const Geometry& geometry) {
175 31 GeometryId id = 0;
176
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
31 if (!unused_.empty()) {
177 ✗ id = unused_.back();
178 ✗ unused_.pop_back();
179 ✗ geometries_[id] = geometry;
180 } else {
181 31 id = static_cast<GeometryId>(geometries_.size());
182 31 geometries_.push_back(geometry);
183 }
184 31 Touch(id);
185 31 return id;
186 }
187
188 2 void GeometryPool::Free(GeometryId id, Changes& changes) {
189 2 Geometry& geometry = geometries_[id];
190
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
8 for (const BufferRange* range : {&geometry.vertices, &geometry.indices, &geometry.box}) {
191
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 if (range->size > 0) {
192 3 arenas_.Free(*range);
193 }
194 }
195 2 geometry = {};
196 2 unused_.push_back(id);
197 2 Touch(id);
198 2 changes.freed.push_back(id);
199 2 }
200
201 26 void GeometryPool::UploadBox(const Geometry& geometry, const glm::vec3& low,
202 const glm::vec3& high) {
203 26 const std::array<float, 6> box{low.x, low.y, low.z, high.x, high.y, high.z};
204 26 uploader_.Write(arenas_.Storage(geometry.box), geometry.box.offset,
205 std::span<const float>(box));
206 26 }
207
208 29 GeometryId GeometryPool::UnitShape(GeometryKind kind) {
209
2/2
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 6 times.
29 std::optional<GeometryId>& shared = unitShapes_[static_cast<std::size_t>(kind)];
210
2/2
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 6 times.
29 if (!shared) {
211 23 Geometry geometry{.kind = kind, .box = arenas_.Allocate(6 * sizeof(float))};
212 // The unit square is flat: its box has no depth.
213
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 5 times.
41 UploadBox(geometry, glm::vec3(-1, -1, kind == GeometryKind::Plane ? 0 : -1), glm::vec3(1));
214 23 shared = Allocate(geometry);
215 }
216 29 return *shared;
217 }
218
219 8 void GeometryPool::UploadMesh(PrimId prim, GeometryId id, bool topology, Changes& changes) {
220
1/2
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
8 const std::string path = scene_.PrimPath(prim).String();
221
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 const Tensor* points = scene_.Read(prim, Mesh::kPoints);
222 8 const std::vector<float> positions =
223
3/6
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 8 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 8 times.
✗ Branch 7 not taken.
16 points != nullptr ? CopyFloats(*points, 3, path + "'s points") : std::vector<float>();
224
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 1 time.
8 const auto vertexCount = static_cast<std::uint32_t>(positions.size() / 3);
225
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 1 time.
8 Geometry& geometry = geometries_[id];
226 8 const bool resized = vertexCount != geometry.vertexCount;
227
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 1 time.
8 if (topology || resized) {
228 21 const auto indices = [&](std::string_view attribute) {
229 14 const Tensor* tensor = scene_.Read(prim, attribute);
230
7/20
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 14 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 14 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 14 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 14 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 14 times.
✗ Branch 14 not taken.
✓ Branch 15 taken 14 times.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
70 return tensor != nullptr ? CopyInts(*tensor, path + "'s " + std::string(attribute))
231
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
28 : std::vector<std::int64_t>();
232 7 };
233 7 const std::vector<std::uint32_t> triangles = TriangulateFaces(
234
3/6
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 7 times.
✗ Branch 8 not taken.
14 indices(Mesh::kFaceVertexCounts), indices(Mesh::kFaceVertexIndices), vertexCount);
235
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 7 times.
21 for (BufferRange* range : {&geometry.vertices, &geometry.indices}) {
236
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 10 times.
14 if (range->size > 0) {
237
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 arenas_.Free(*range);
238 4 *range = {};
239 }
240 }
241 7 geometry.vertexCount = vertexCount;
242
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 geometry.triangleCount = static_cast<std::uint32_t>(triangles.size() / 3);
243
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 if (!triangles.empty()) {
244
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 geometry.vertices = arenas_.Allocate(positions.size() * sizeof(float));
245
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 geometry.indices = arenas_.Allocate(triangles.size() * sizeof(std::uint32_t));
246
2/4
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
7 uploader_.Write(arenas_.Storage(geometry.indices), geometry.indices.offset,
247 std::span<const std::uint32_t>(triangles));
248 }
249
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 Touch(id);
250
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 changes.rebuilt.push_back(id);
251 7 } else {
252
1/2
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
1 changes.refitted.push_back(id);
253 }
254
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 if (geometry.triangleCount > 0) {
255
2/4
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
8 uploader_.Write(arenas_.Storage(geometry.vertices), geometry.vertices.offset,
256 std::span<const float>(positions));
257 }
258 8 }
259
260 117 void GeometryPool::Place(PrimId prim, bool points, bool topology, Changes& changes) {
261 117 const std::optional<GeometryKind> kind = FindGeometryKind(scene_, prim);
262
2/2
✓ Branch 0 taken 76 times.
✓ Branch 1 taken 41 times.
117 if (!kind) {
263 76 return;
264 }
265
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 12 times.
41 if (prim.slot >= prims_.size()) {
266
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 20 times.
38 prims_.resize(std::max<std::size_t>(prim.slot + 1, 2 * prims_.size()));
267 }
268
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 34 times.
41 std::optional<PrimGeometry>& placed = prims_[prim.slot];
269
3/4
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 34 times.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
41 const bool known = placed && placed->prim == prim;
270
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 32 times.
41 PrimGeometry next{.prim = prim};
271
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 32 times.
41 if (*kind == GeometryKind::Triangles) {
272
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 5 times.
9 next.geometry = known ? placed->geometry : Allocate({.kind = GeometryKind::Triangles});
273
4/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1 time.
9 if (!known || points || topology) {
274 8 UploadMesh(prim, next.geometry, topology || !known, changes);
275 }
276
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 29 times.
32 } else if (*kind == GeometryKind::Capsule) {
277 3 const double radius = RequireNumber(scene_, prim, Capsule::kRadius);
278 3 const auto halfLength =
279 3 static_cast<float>(0.5 * RequireNumber(scene_, prim, Capsule::kHeight) / radius);
280
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 next.geometry = known ? placed->geometry
281 3 : Allocate({.kind = GeometryKind::Capsule,
282 3 .box = arenas_.Allocate(6 * sizeof(float))});
283
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 Geometry& geometry = geometries_[next.geometry];
284
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
3 if (!known || geometry.halfLength != halfLength) {
285 3 geometry.halfLength = halfLength;
286 6 UploadBox(geometry, glm::vec3(-1, -1, -1 - halfLength),
287 3 glm::vec3(1, 1, 1 + halfLength));
288 3 Touch(next.geometry);
289 3 changes.rebuilt.push_back(next.geometry);
290 }
291 } else {
292 29 next.geometry = UnitShape(*kind);
293
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 3 times.
29 if (!known) {
294 26 changes.rebuilt.push_back(next.geometry);
295 }
296 }
297 41 next.shape = ShapeTransform(scene_, prim, *kind);
298 41 next.colour = DisplayColour(scene_, prim);
299
5/6
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 34 times.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 6 times.
✓ Branch 6 taken 1 time.
41 if (!known || placed->geometry != next.geometry || placed->shape != next.shape ||
300
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
6 placed->colour != next.colour) {
301 37 changes.placed.push_back(next);
302 }
303 41 placed = next;
304 }
305
306 34 GeometryPool::Changes GeometryPool::Update() {
307 34 ERNIE_ZONE("Update geometry");
308
1/2
✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
34 const PrimStore& store = scene_.Store();
309
1/2
✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
34 const ChangeSet changeSet = store.ChangesSince(cursor_);
310 34 cursor_ = changeSet.version;
311
1/2
✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
34 const PrimRows::Changes followed = rows_.Follow(store, changeSet);
312 34 Changes changes;
313
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 34 times.
38 for (const PrimId prim : followed.removed) {
314
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if (const std::optional<PrimGeometry> placed = Find(prim)) {
315
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 const Geometry& geometry = geometries_[placed->geometry];
316
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (geometry.kind == GeometryKind::Triangles ||
317 geometry.kind == GeometryKind::Capsule) {
318
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 Free(placed->geometry, changes);
319 }
320
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 prims_[prim.slot].reset();
321
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 changes.removed.push_back(prim);
322 }
323 }
324
2/2
✓ Branch 0 taken 110 times.
✓ Branch 1 taken 34 times.
144 for (const PrimId prim : followed.added) {
325
1/2
✓ Branch 1 taken 110 times.
✗ Branch 2 not taken.
110 Place(prim, true, true, changes);
326 }
327 // Prims just added were placed from all their values: none of them is an edit.
328
1/2
✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
34 std::vector<bool> fresh(prims_.size(), false);
329
2/2
✓ Branch 0 taken 110 times.
✓ Branch 1 taken 34 times.
144 for (const PrimId prim : followed.added) {
330
2/2
✓ Branch 0 taken 40 times.
✓ Branch 1 taken 70 times.
110 if (prim.slot < fresh.size()) {
331 40 fresh[prim.slot] = true;
332 }
333 }
334 // Values the host authored: shapes, points, topology and colours.
335
2/2
✓ Branch 0 taken 105 times.
✓ Branch 1 taken 34 times.
139 for (const ChangeSet::Values& values : changeSet.values) {
336 105 const Touched touched = ClassifyAttribute(store.Tokens().Text(values.attribute));
337
2/2
✓ Branch 0 taken 60 times.
✓ Branch 1 taken 45 times.
105 if (!touched.Any()) {
338 60 continue;
339 }
340 45 const Bucket& bucket = store.GetBucket(values.bucket);
341
2/2
✓ Branch 0 taken 45 times.
✓ Branch 1 taken 45 times.
90 for (const RowRange& range : values.rows) {
342
3/4
✗ Branch 0 not taken.
✓ Branch 1 taken 93 times.
✓ Branch 2 taken 48 times.
✓ Branch 3 taken 45 times.
93 for (std::uint32_t row = range.begin; row < std::min(range.end, bucket.Rows()); ++row) {
343
1/2
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
48 const PrimId prim = bucket.prims[row];
344
3/4
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 7 times.
✓ Branch 4 taken 41 times.
48 if (Find(prim) && !fresh[prim.slot]) {
345
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 Place(prim, touched.points, touched.topology, changes);
346 }
347 }
348 }
349 }
350 68 return changes;
351 68 }
352
353 34 void GeometryPool::AddUploads(graph::Graph& graph) {
354
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 16 times.
34 if (!dirtyRecords_.empty()) {
355 18 hostRecords_.resize(geometries_.size());
356 18 records_->Resize(geometries_.size(), context_.Main());
357 18 SortUnique(dirtyRecords_);
358 86 const auto address = [&](const BufferRange& range) {
359
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 56 times.
68 return range.size > 0 ? arenas_.Address(range) : std::uint64_t{0};
360 18 };
361
2/2
✓ Branch 0 taken 34 times.
✓ Branch 1 taken 18 times.
52 for (const std::uint32_t id : dirtyRecords_) {
362 34 const Geometry& geometry = geometries_[id];
363 34 hostRecords_[id] = {.kind = static_cast<std::uint32_t>(geometry.kind),
364 34 .halfLength = geometry.halfLength,
365 34 .vertexCount = geometry.vertexCount,
366 34 .triangleCount = geometry.triangleCount,
367 34 .vertices = {address(geometry.vertices)},
368 34 .indices = {address(geometry.indices)}};
369 }
370 18 StageRows(uploader_, *records_, hostRecords_, dirtyRecords_);
371 18 dirtyRecords_.clear();
372 }
373
2/2
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 15 times.
34 if (uploader_.Staged()) {
374 19 std::vector<graph::Buffer> buffers = Import(graph);
375
1/2
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
19 graph::AddUploadPass(graph, "Upload geometry", uploader_, buffers);
376 19 }
377 34 }
378
379 38 std::vector<graph::Buffer> GeometryPool::Import(graph::Graph& graph) const {
380 38 std::vector<graph::Buffer> buffers;
381
2/2
✓ Branch 0 taken 38 times.
✓ Branch 1 taken 38 times.
76 for (std::uint32_t arena = 0; arena < arenas_.Count(); ++arena) {
382
1/2
✓ Branch 1 taken 38 times.
✗ Branch 2 not taken.
38 const Buffer& buffer = arenas_.Storage(BufferRange{.arena = arena});
383
1/2
✓ Branch 1 taken 38 times.
✗ Branch 2 not taken.
76 buffers.push_back(graph.Import(buffer, 0, buffer.Size(), "geometry"));
384 }
385 38 return buffers;
386 ✗ }
387
388 } // namespace ernie
389