GCC Code Coverage Report


ernie/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
354 / 382
92.7%
Branches:
288 / 471
61.1%
Functions:
49 / 51
96.1%

graph/graph.cpp
Line Branch Exec Source
1 // SPDX-License-Identifier: BSD-3-Clause
2 /// @file graph.cpp
3 /// @brief Declaring, culling, backing and recording the frame graph's passes.
4 #include "ernie/graph/graph.h"
5
6 #include <algorithm>
7 #include <cstdlib>
8 #include <sstream>
9 #include <string_view>
10
11 #ifndef ERNIE_GRAPH_FULL_BARRIERS
12 #define ERNIE_GRAPH_FULL_BARRIERS 0
13 #endif
14
15 namespace ernie::graph {
16 namespace {
17
18 47 bool FullBarriersByDefault() {
19 #if ERNIE_GRAPH_FULL_BARRIERS
20 return true;
21 #else
22 47 const char* value = std::getenv("ERNIE_GRAPH_FULL_BARRIERS");
23
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 47 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
47 return value != nullptr && std::string_view(value) == "1";
24 #endif
25 }
26
27 2473 bool Writes(Use use) {
28
2/2
✓ Branch 0 taken 1314 times.
✓ Branch 1 taken 1159 times.
2473 switch (use) {
29 case Use::StorageWrite:
30 case Use::StorageReadWrite:
31 case Use::ColorWrite:
32 case Use::ColorReadWrite:
33 case Use::DepthWrite:
34 case Use::DepthReadWrite:
35 case Use::CopyDst:
36 case Use::BuildScratch:
37 case Use::BuildOutput:
38 return true;
39 1314 default:
40 1314 return false;
41 }
42 }
43
44 867 bool Reads(Use use) {
45
4/5
✓ Branch 0 taken 429 times.
✓ Branch 1 taken 438 times.
✓ Branch 2 taken 43 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 386 times.
867 return !Writes(use) || use == Use::StorageReadWrite || use == Use::ColorReadWrite ||
46 867 use == Use::DepthReadWrite;
47 }
48
49 867 ImageUsage NeededUsage(Use use) {
50
3/4
✓ Branch 0 taken 458 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 403 times.
✓ Branch 3 taken 6 times.
867 switch (use) {
51 case Use::Sampled:
52 return ImageUsage::Sampled;
53 458 case Use::StorageRead:
54 458 case Use::StorageWrite:
55 458 case Use::StorageReadWrite:
56 458 return ImageUsage::Storage;
57 ✗ case Use::ColorWrite:
58 ✗ case Use::ColorReadWrite:
59 ✗ case Use::DepthWrite:
60 ✗ case Use::DepthReadWrite:
61 ✗ case Use::DepthRead:
62 ✗ return ImageUsage::Attachment;
63 403 default:
64 // Every image takes transfer usage.
65 403 return ImageUsage{};
66 }
67 }
68
69 } // namespace
70
71 871 std::uint32_t PassBuilder::Declare(std::uint32_t id, std::uint32_t version, Shape shape, Use use,
72 Layers layers, Reach reach, bool write) {
73 1742 const auto resourceShape = [](const Graph::Resource& resource) {
74
2/2
✓ Branch 0 taken 845 times.
✓ Branch 1 taken 26 times.
871 return resource.image ? Shape::Image
75 67 : resource.Structure() ? Shape::Acceleration
76 : Shape::Buffer;
77 };
78
4/6
✓ Branch 0 taken 871 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 845 times.
✓ Branch 3 taken 26 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 871 times.
1716 if (id >= graph_.resources_.size() || resourceShape(graph_.resources_[id]) != shape) {
79 ✗ static constexpr const char* kShapes[] = {"image", "buffer", "acceleration structure"};
80
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ throw std::invalid_argument(std::string("An invalid ") +
81
0/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ kShapes[static_cast<std::size_t>(shape)] + " handle");
82 }
83 871 Graph::Resource& resource = graph_.resources_[id];
84
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 871 times.
871 Graph::Pass& pass = graph_.passes_[pass_];
85
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 871 times.
871 if (Writes(use) != write) {
86
0/8
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ throw std::invalid_argument("Pass '" + pass.name + "' declares " + ToString(use) + " on '" +
87
0/8
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ resource.name + "' as a " + (write ? "write" : "read"));
88 }
89 // Structures are built and traced; the memory they live in is no plain buffer, and the other
90 // way round.
91 871 const bool structureUse = use == Use::BuildOutput || use == Use::Trace;
92
4/4
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 822 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 48 times.
871 if (structureUse != (shape == Shape::Acceleration) && use != Use::BuildInput) {
93 1 throw std::invalid_argument(
94
6/12
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 7 taken 1 time.
✗ Branch 8 not taken.
✓ Branch 10 taken 1 time.
✗ Branch 11 not taken.
✓ Branch 13 taken 1 time.
✗ Branch 14 not taken.
✓ Branch 16 taken 1 time.
✗ Branch 17 not taken.
6 "Pass '" + pass.name + "' declares " + ToString(use) + " on '" + resource.name +
95
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
2 "', which " +
96
1/2
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
3 (structureUse ? "is no acceleration structure" : "is an acceleration structure"));
97 }
98
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 870 times.
870 if (version > resource.version) {
99
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 GraphError("Pass '" + pass.name + "' uses a version of '" + resource.name +
100 ✗ "' not written yet");
101 }
102 870 pass.accesses.push_back({id, use, layers, reach});
103
2/2
✓ Branch 0 taken 431 times.
✓ Branch 1 taken 439 times.
870 if (write) {
104 431 ++resource.version;
105 }
106 870 return resource.version;
107 }
108
109 10 Image PassBuilder::Read(Image image, Use use, Layers layers) {
110 10 Declare(image.id, image.version, Shape::Image, use, layers, Reach::Descriptor, false);
111 10 return image;
112 }
113
114 16 Image PassBuilder::Write(Image image, Use use, Layers layers) {
115 16 return {image.id,
116 16 Declare(image.id, image.version, Shape::Image, use, layers, Reach::Descriptor, true)};
117 }
118
119 46 Buffer PassBuilder::Read(Buffer buffer, Use use) {
120 46 Declare(buffer.id, buffer.version, Shape::Buffer, use, {}, Reach::Descriptor, false);
121 45 return buffer;
122 }
123
124 229 Buffer PassBuilder::Write(Buffer buffer, Use use) {
125 229 return {buffer.id,
126 229 Declare(buffer.id, buffer.version, Shape::Buffer, use, {}, Reach::Descriptor, true)};
127 }
128
129 ✗ Image PassBuilder::ReadBindless(Image image, Use use, Layers layers) {
130 ✗ Declare(image.id, image.version, Shape::Image, use, layers, Reach::Bindless, false);
131 ✗ return image;
132 }
133
134 310 Buffer PassBuilder::ReadAddress(Buffer buffer, Use use) {
135 310 Declare(buffer.id, buffer.version, Shape::Buffer, use, {}, Reach::Address, false);
136 310 return buffer;
137 }
138
139 137 Buffer PassBuilder::WriteAddress(Buffer buffer, Use use) {
140 137 return {buffer.id,
141 137 Declare(buffer.id, buffer.version, Shape::Buffer, use, {}, Reach::Address, true)};
142 }
143
144 49 Acceleration PassBuilder::Build(Acceleration structure) {
145 49 return {structure.id, Declare(structure.id, structure.version, Shape::Acceleration,
146 49 Use::BuildOutput, {}, Reach::Address, true)};
147 }
148
149 75 Acceleration PassBuilder::Read(Acceleration structure, Use use) {
150
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 74 times.
75 if (use != Use::Trace && use != Use::BuildInput) {
151
1/2
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
1 throw std::invalid_argument(std::string("An acceleration structure is traced or read by "
152
2/4
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
2 "builds, not used as ") +
153
1/2
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
3 ToString(use));
154 }
155 74 Declare(structure.id, structure.version, Shape::Acceleration, use, {}, Reach::Address, false);
156 74 return structure;
157 }
158
159 8 void PassBuilder::NeverCull() {
160 8 graph_.passes_[pass_].neverCull = true;
161 8 }
162
163 304 void PassContext::Check(std::uint32_t id) const {
164 304 const Graph::Pass& pass = graph_.passes_[pass_];
165
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 304 times.
304 if (std::ranges::none_of(
166
2/2
✓ Branch 0 taken 779 times.
✓ Branch 1 taken 304 times.
1387 pass.accesses, [&](const DependencyAccess& access) { return access.resource == id; })) {
167 ✗ throw std::logic_error(
168
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ "Pass '" + pass.name + "' did not declare '" +
169
0/8
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ (id < graph_.resources_.size() ? graph_.resources_[id].name : std::string("?")) + "'");
170 }
171 304 }
172
173 8 DeviceImage& PassContext::Get(Image image) const {
174 8 Check(image.id);
175 8 return *graph_.resources_[image.id].deviceImage;
176 }
177
178 287 BufferSlice PassContext::Get(Buffer buffer) const {
179 287 Check(buffer.id);
180 287 return graph_.resources_[buffer.id].slice;
181 }
182
183 1 const AccelerationStructure& PassContext::Get(Acceleration structure) const {
184 1 Check(structure.id);
185
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 const Graph::Resource& resource = graph_.resources_[structure.id];
186
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if (resource.acceleration == nullptr) {
187
0/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ throw std::logic_error("'" + resource.name + "' is a pool of structures, not one");
188 }
189 1 return *resource.acceleration;
190 }
191
192 8 VkImageLayout PassContext::Layout(Image image) const {
193 8 Check(image.id);
194 8 const Graph::Pass& pass = graph_.passes_[pass_];
195 8 const auto access = std::ranges::find(pass.accesses, image.id, &DependencyAccess::resource);
196 8 return ToState(pass.kind, access->use, true).layout;
197 }
198
199 ✗ DeviceContext& PassContext::Context() const {
200 ✗ return graph_.context_;
201 }
202
203 81 void PassContext::Keep(std::shared_ptr<const void> object) const {
204 81 graph_.kept_.push_back(std::move(object));
205 81 }
206
207 47 Graph::Graph(DeviceContext& context, TransientPool& pool)
208 47 : context_(context), pool_(pool), fullBarriers_(FullBarriersByDefault()) {}
209
210 47 Graph::~Graph() {
211 47 ReleaseLeases();
212 47 }
213
214 548 std::uint32_t Graph::AddResource(Resource resource) {
215 548 resources_.push_back(std::move(resource));
216 548 return static_cast<std::uint32_t>(resources_.size() - 1);
217 }
218
219 12 Image Graph::Import(DeviceImage& image, std::string_view name) {
220 12 const auto found = std::ranges::find(resources_, &image, &Resource::deviceImage);
221
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (found != resources_.end()) {
222 ✗ return {static_cast<std::uint32_t>(found - resources_.begin()), found->version};
223 }
224
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 return {AddResource({.name = std::string(name),
225 .image = true,
226 .imported = true,
227 .deviceImage = &image,
228 .origin = Origin::Imported}),
229 12 0};
230
2/4
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
24 }
231
232 493 Buffer Graph::Import(const DeviceColumn& column, std::string_view name) {
233
2/2
✓ Branch 0 taken 475 times.
✓ Branch 1 taken 18 times.
968 return Import(column.Storage(), column.Range().offset, column.Bytes(),
234
2/2
✓ Branch 0 taken 475 times.
✓ Branch 1 taken 18 times.
493 name.empty() ? column.Name() : name);
235 }
236
237 31 Acceleration Graph::Import(const AccelerationStructure& structure) {
238 31 const auto found = std::ranges::find(resources_, &structure, &Resource::acceleration);
239
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 30 times.
31 if (found != resources_.end()) {
240 1 return {static_cast<std::uint32_t>(found - resources_.begin()), found->version};
241 }
242
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
60 return {AddResource(
243
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 {.name = structure.Name(),
244 .imported = true,
245
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 .slice = {&structure.Storage(), structure.Range().offset, structure.Range().size},
246 .acceleration = &structure,
247 .origin = Origin::Imported}),
248 30 0};
249
2/4
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 30 times.
✗ Branch 5 not taken.
60 }
250
251 48 Acceleration Graph::Import(const AccelerationPool& pool) {
252 48 const auto found = std::ranges::find(resources_, &pool, &Resource::structures);
253
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 28 times.
48 if (found != resources_.end()) {
254 20 return {static_cast<std::uint32_t>(found - resources_.begin()), found->version};
255 }
256
1/2
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
56 return {AddResource({.name = pool.Name(),
257 .imported = true,
258 .structures = &pool,
259 .origin = Origin::Imported}),
260 28 0};
261
2/4
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 28 times.
✗ Branch 5 not taken.
28 }
262
263 534 Buffer Graph::Import(const ernie::Buffer& buffer, std::uint64_t offset, std::uint64_t size,
264 std::string_view name) {
265 534 const auto found = std::ranges::find_if(resources_, [&](const Resource& resource) {
266
2/2
✓ Branch 0 taken 3505 times.
✓ Branch 1 taken 30 times.
3535 return resource.imported && !resource.image && !resource.Structure() &&
267
6/6
✓ Branch 0 taken 3535 times.
✓ Branch 1 taken 224 times.
✓ Branch 2 taken 3324 times.
✓ Branch 3 taken 5 times.
✓ Branch 4 taken 159 times.
✓ Branch 5 taken 3165 times.
7088 resource.slice.buffer == &buffer && resource.slice.offset == offset &&
268
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 152 times.
159 resource.slice.size == size;
269 });
270
2/2
✓ Branch 0 taken 152 times.
✓ Branch 1 taken 382 times.
534 if (found != resources_.end()) {
271 152 return {static_cast<std::uint32_t>(found - resources_.begin()), found->version};
272 }
273
1/2
✓ Branch 1 taken 382 times.
✗ Branch 2 not taken.
382 return {AddResource({.name = std::string(name),
274 .imported = true,
275 .slice = {&buffer, offset, size},
276 .origin = Origin::Imported}),
277 382 0};
278
2/4
✓ Branch 1 taken 382 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 382 times.
✗ Branch 5 not taken.
764 }
279
280 4 Image Graph::Create(const ImageDescription& description, std::string_view name) {
281 4 return {AddResource({.name = std::string(name), .image = true, .description = description}), 0};
282
1/2
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
8 }
283
284 92 Buffer Graph::Create(std::uint64_t bytes, std::string_view name) {
285
1/2
✓ Branch 1 taken 92 times.
✗ Branch 2 not taken.
92 return {AddResource({.name = std::string(name), .bytes = bytes}), 0};
286
1/2
✓ Branch 2 taken 92 times.
✗ Branch 3 not taken.
184 }
287
288 244 std::uint32_t Graph::NewPass(std::string_view name, Kind kind) {
289
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 244 times.
244 if (compiled_) {
290
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::logic_error("Passes cannot be added after Compile");
291 }
292
1/2
✓ Branch 1 taken 244 times.
✗ Branch 2 not taken.
244 passes_.push_back({.name = std::string(name), .kind = kind, .scope = scope_});
293 244 return static_cast<std::uint32_t>(passes_.size() - 1);
294
1/2
✓ Branch 2 taken 244 times.
✗ Branch 3 not taken.
488 }
295
296 55 Graph::ScopeGuard::~ScopeGuard() {
297 55 graph_.scope_ = graph_.scopes_[graph_.scope_].parent;
298 55 }
299
300 55 Graph::ScopeGuard Graph::Scope(std::string_view name) {
301
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 54 times.
55 const std::uint32_t depth = scope_ == kNoScope ? 0 : scopes_[scope_].depth + 1;
302 110 scopes_.push_back({.name = std::string(name), .parent = scope_, .depth = depth});
303 55 scope_ = static_cast<std::uint32_t>(scopes_.size() - 1);
304 55 return ScopeGuard(*this);
305
1/2
✓ Branch 2 taken 55 times.
✗ Branch 3 not taken.
110 }
306
307 46 std::vector<std::uint32_t> Graph::LivePasses() const {
308 // Walk back from the passes that must run, keeping passes that write what a kept pass reads.
309
1/2
✓ Branch 2 taken 46 times.
✗ Branch 3 not taken.
46 std::vector<bool> needed(resources_.size(), false);
310
1/2
✓ Branch 1 taken 46 times.
✗ Branch 2 not taken.
46 std::vector<bool> keep(passes_.size(), false);
311
2/2
✓ Branch 0 taken 240 times.
✓ Branch 1 taken 46 times.
286 for (std::size_t p = passes_.size(); p-- > 0;) {
312
2/2
✓ Branch 0 taken 232 times.
✓ Branch 1 taken 8 times.
240 const Pass& pass = passes_[p];
313 240 const bool live =
314
4/4
✓ Branch 0 taken 232 times.
✓ Branch 1 taken 8 times.
✓ Branch 3 taken 231 times.
✓ Branch 4 taken 1 time.
240 pass.neverCull || std::ranges::any_of(pass.accesses, [&](const DependencyAccess& a) {
315
6/6
✓ Branch 0 taken 291 times.
✓ Branch 1 taken 430 times.
✓ Branch 2 taken 90 times.
✓ Branch 3 taken 201 times.
✓ Branch 5 taken 30 times.
✓ Branch 6 taken 60 times.
721 return Writes(a.use) && (resources_[a.resource].imported || needed[a.resource]);
316 240 });
317 240 if (!live) {
318 1 continue;
319 }
320 239 keep[p] = true;
321
2/2
✓ Branch 0 taken 867 times.
✓ Branch 1 taken 239 times.
1106 for (const DependencyAccess& access : pass.accesses) {
322
2/2
✓ Branch 0 taken 481 times.
✓ Branch 1 taken 386 times.
867 if (Reads(access.use)) {
323 481 needed[access.resource] = true;
324 }
325 }
326 }
327 46 std::vector<std::uint32_t> live;
328
2/2
✓ Branch 0 taken 240 times.
✓ Branch 1 taken 46 times.
286 for (std::size_t p = 0; p < passes_.size(); ++p) {
329
2/2
✓ Branch 1 taken 239 times.
✓ Branch 2 taken 1 time.
240 if (keep[p]) {
330
1/2
✓ Branch 1 taken 239 times.
✗ Branch 2 not taken.
239 live.push_back(static_cast<std::uint32_t>(p));
331 }
332 }
333 46 return live;
334 46 }
335
336 46 void Graph::Lease(const std::vector<std::uint32_t>& live) {
337
1/2
✓ Branch 2 taken 46 times.
✗ Branch 3 not taken.
46 std::vector<bool> used(resources_.size(), false);
338
1/2
✓ Branch 1 taken 46 times.
✗ Branch 2 not taken.
46 std::vector<ImageUsage> usage(resources_.size(), ImageUsage{});
339
2/2
✓ Branch 0 taken 239 times.
✓ Branch 1 taken 46 times.
285 for (const std::uint32_t p : live) {
340
2/2
✓ Branch 0 taken 867 times.
✓ Branch 1 taken 239 times.
1106 for (const DependencyAccess& access : passes_[p].accesses) {
341 867 used[access.resource] = true;
342 867 usage[access.resource] = usage[access.resource] | NeededUsage(access.use);
343 }
344 }
345
2/2
✓ Branch 0 taken 545 times.
✓ Branch 1 taken 46 times.
591 for (std::size_t i = 0; i < resources_.size(); ++i) {
346
2/2
✓ Branch 0 taken 96 times.
✓ Branch 1 taken 449 times.
545 Resource& resource = resources_[i];
347
4/4
✓ Branch 0 taken 96 times.
✓ Branch 1 taken 449 times.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 90 times.
545 if (resource.imported || !used[i]) {
348 455 continue;
349 }
350
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 86 times.
90 if (resource.image) {
351 4 ImageDescription description = resource.description;
352
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 description.usage = description.usage | usage[i];
353
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 const TransientPool::ImageLease lease = pool_.AcquireImage(description);
354 4 resource.deviceImage = lease.image;
355
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
6 resource.origin = lease.recycled ? Origin::Recycled : Origin::Fresh;
356 } else {
357 // Freed ranges come back only once the device is done with them.
358
1/2
✓ Branch 1 taken 86 times.
✗ Branch 2 not taken.
86 resource.range = pool_.AcquireBuffer(resource.bytes);
359
1/2
✓ Branch 1 taken 86 times.
✗ Branch 2 not taken.
86 resource.slice = {&pool_.Buffers().Storage(resource.range), resource.range.offset,
360 86 resource.bytes};
361 86 resource.origin = Origin::Fresh;
362 }
363 90 leased_ = true;
364 }
365 46 }
366
367 93 void Graph::ReleaseLeases() {
368
2/2
✓ Branch 0 taken 37 times.
✓ Branch 1 taken 56 times.
93 if (!leased_) {
369 return;
370 }
371
2/2
✓ Branch 0 taken 522 times.
✓ Branch 1 taken 37 times.
559 for (Resource& resource : resources_) {
372
2/2
✓ Branch 0 taken 426 times.
✓ Branch 1 taken 96 times.
522 if (resource.imported) {
373 426 continue;
374 }
375
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 92 times.
96 if (resource.deviceImage != nullptr) {
376 4 pool_.Release(*resource.deviceImage);
377 4 resource.deviceImage = nullptr;
378 }
379
2/2
✓ Branch 0 taken 86 times.
✓ Branch 1 taken 10 times.
96 if (resource.slice.buffer != nullptr) {
380 86 pool_.Release(resource.range);
381 86 resource.slice = {};
382 }
383 }
384 37 leased_ = false;
385 }
386
387 46 void Graph::Compile() {
388
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 46 times.
46 if (compiled_) {
389 ✗ return;
390 }
391 46 ERNIE_ZONE("Compile the frame graph");
392
1/2
✓ Branch 1 taken 46 times.
✗ Branch 2 not taken.
46 live_ = LivePasses();
393
1/2
✓ Branch 1 taken 46 times.
✗ Branch 2 not taken.
46 Lease(live_);
394 46 std::vector<DependencyResource> resources;
395
1/2
✓ Branch 1 taken 46 times.
✗ Branch 2 not taken.
46 resources.reserve(resources_.size());
396
2/2
✓ Branch 0 taken 545 times.
✓ Branch 1 taken 46 times.
591 for (const Resource& resource : resources_) {
397 545 resources.push_back({resource.name, resource.image, resource.origin});
398 }
399 46 std::vector<DependencyPass> passes;
400
1/2
✓ Branch 1 taken 46 times.
✗ Branch 2 not taken.
46 passes.reserve(live_.size());
401
2/2
✓ Branch 0 taken 239 times.
✓ Branch 1 taken 46 times.
285 for (const std::uint32_t p : live_) {
402
2/4
✓ Branch 1 taken 239 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 239 times.
✗ Branch 5 not taken.
717 passes.push_back({passes_[p].name, passes_[p].kind, passes_[p].accesses});
403 }
404
1/2
✓ Branch 1 taken 46 times.
✗ Branch 2 not taken.
46 dependencies_ = ComputeDependencies(resources, passes);
405
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 45 times.
46 if (!dependencies_.errors.empty()) {
406
1/2
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
1 ReleaseLeases();
407
1/2
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
1 std::string message = "The frame graph cannot run:";
408
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 for (const std::string& error : dependencies_.errors) {
409
1/2
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
3 message += "\n " + error;
410 }
411
1/2
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1 throw GraphError(message);
412 1 }
413 45 compiled_ = true;
414
5/10
✓ Branch 1 taken 545 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 545 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 239 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 239 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 239 times.
✗ Branch 14 not taken.
832 }
415
416 283 void Graph::Record(VkCommandBuffer commands, const Dependency& dependency) const {
417
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 275 times.
283 if (dependency.Empty()) {
418 8 return;
419 }
420 275 std::vector<VkImageMemoryBarrier2> images;
421
1/2
✓ Branch 1 taken 275 times.
✗ Branch 2 not taken.
275 images.reserve(dependency.images.size());
422
2/2
✓ Branch 0 taken 36 times.
✓ Branch 1 taken 275 times.
311 for (const Barrier& b : dependency.images) {
423 36 const Layers layers = b.layers;
424 36 images.push_back({.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
425 36 .srcStageMask = b.srcStages,
426 36 .srcAccessMask = b.srcAccess,
427 36 .dstStageMask = b.dstStages,
428 36 .dstAccessMask = b.dstAccess,
429 36 .oldLayout = b.oldLayout,
430 36 .newLayout = b.newLayout,
431 .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
432 .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
433
1/2
✓ Branch 1 taken 36 times.
✗ Branch 2 not taken.
36 .image = resources_[b.resource].deviceImage->Handle(),
434 .subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
435 .levelCount = VK_REMAINING_MIP_LEVELS,
436 36 .baseArrayLayer = layers.first,
437 .layerCount = layers.count == Layers::kAll
438 ? VK_REMAINING_ARRAY_LAYERS
439
1/2
✓ Branch 1 taken 36 times.
✗ Branch 2 not taken.
36 : layers.count}});
440 }
441 // Buffers are ranges reached by address: their dependencies are one memory barrier.
442 275 VkMemoryBarrier2 memory{.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2};
443
2/2
✓ Branch 0 taken 1017 times.
✓ Branch 1 taken 275 times.
1292 for (const Barrier& b : dependency.buffers) {
444 1017 memory.srcStageMask |= b.srcStages;
445 1017 memory.srcAccessMask |= b.srcAccess;
446 1017 memory.dstStageMask |= b.dstStages;
447 1017 memory.dstAccessMask |= b.dstAccess;
448 }
449 275 const VkDependencyInfo info{.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
450
2/2
✓ Branch 0 taken 267 times.
✓ Branch 1 taken 8 times.
275 .memoryBarrierCount = dependency.buffers.empty() ? 0u : 1u,
451 .pMemoryBarriers = &memory,
452 .imageMemoryBarrierCount =
453 275 static_cast<std::uint32_t>(images.size()),
454
1/2
✓ Branch 1 taken 275 times.
✗ Branch 2 not taken.
275 .pImageMemoryBarriers = images.data()};
455
1/2
✓ Branch 1 taken 275 times.
✗ Branch 2 not taken.
275 vkCmdPipelineBarrier2(commands, &info);
456 275 }
457
458 391 void Graph::RecordPass(VkCommandBuffer commands, std::uint32_t order, std::uint32_t depth) const {
459 391 const Pass& pass = passes_[live_[order]];
460 // Scopes open outermost first, each zone living on the stack of the call nested in it.
461 391 std::uint32_t scope = pass.scope;
462
4/4
✓ Branch 0 taken 306 times.
✓ Branch 1 taken 86 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 305 times.
392 while (scope != kNoScope && scopes_[scope].depth > depth) {
463 1 scope = scopes_[scope].parent;
464 }
465
4/4
✓ Branch 0 taken 305 times.
✓ Branch 1 taken 86 times.
✓ Branch 2 taken 152 times.
✓ Branch 3 taken 153 times.
391 if (scope != kNoScope && scopes_[scope].depth == depth) {
466 153 ERNIE_GPU_ZONE(context_, commands, scopes_[scope].name.c_str());
467
1/2
✓ Branch 1 taken 153 times.
✗ Branch 2 not taken.
153 RecordPass(commands, order, depth + 1);
468 153 return;
469 153 }
470 238 ERNIE_ZONE("Record a pass");
471 238 ERNIE_ZONE_TEXT(pass.name);
472
1/2
✓ Branch 1 taken 238 times.
✗ Branch 2 not taken.
238 ERNIE_GPU_ZONE(context_, commands, pass.name.c_str());
473
1/2
✓ Branch 1 taken 238 times.
✗ Branch 2 not taken.
238 Record(commands, dependencies_.passes[order]);
474
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 234 times.
238 if (fullBarriers_) {
475
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 ernie::Barrier(commands, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_MEMORY_WRITE_BIT,
476 VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, kAnyAccess);
477 }
478
1/2
✓ Branch 0 taken 238 times.
✗ Branch 1 not taken.
238 PassContext context(*this, live_[order]);
479 238 context.commands = commands;
480
1/2
✓ Branch 0 taken 238 times.
✗ Branch 1 not taken.
238 if (pass.run) {
481
1/2
✓ Branch 1 taken 238 times.
✗ Branch 2 not taken.
238 pass.run(context);
482 }
483 238 }
484
485 45 Ticket Graph::Execute(Queue& queue, std::span<const Ticket> waits, BinarySemaphores binary) {
486
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 45 times.
45 if (executed_) {
487
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::logic_error("A frame graph executes once");
488 }
489 45 Compile();
490 45 executed_ = true;
491 // Transient images new to the pool are made ready on the main queue.
492 45 std::vector<Ticket> after(waits.begin(), waits.end());
493
2/2
✓ Branch 0 taken 543 times.
✓ Branch 1 taken 45 times.
588 for (const Resource& resource : resources_) {
494
3/4
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 527 times.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
543 if (resource.image && resource.deviceImage != nullptr) {
495
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
16 after.push_back(resource.deviceImage->Ready());
496 }
497 }
498 45 Ticket ticket;
499
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 44 times.
45 if (flushEachPass_) {
500 // The first submission waits on the binary semaphore; the last signals its own.
501
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 time.
3 for (std::uint32_t order = 0; order < live_.size(); ++order) {
502 2 ticket = queue.Submit([&](VkCommandBuffer commands) { RecordPass(commands, order); },
503
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 after, {.wait = std::exchange(binary.wait, VK_NULL_HANDLE)});
504
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 ticket.Wait();
505 }
506 }
507
1/2
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
45 ticket = queue.Submit(
508
1/2
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
45 [&](VkCommandBuffer commands) {
509
2/2
✓ Branch 0 taken 44 times.
✓ Branch 1 taken 1 time.
45 if (!flushEachPass_) {
510
2/2
✓ Branch 0 taken 236 times.
✓ Branch 1 taken 44 times.
280 for (std::uint32_t order = 0; order < live_.size(); ++order) {
511 236 RecordPass(commands, order);
512 }
513 }
514 45 Record(commands, dependencies_.after);
515 45 },
516 after, binary);
517
1/2
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
45 ReleaseLeases();
518
2/2
✓ Branch 0 taken 81 times.
✓ Branch 1 taken 45 times.
126 for (std::shared_ptr<const void>& object : kept_) {
519
1/2
✓ Branch 1 taken 81 times.
✗ Branch 2 not taken.
162 context_.Retire(std::move(object));
520 }
521 45 kept_.clear();
522 45 return ticket;
523 45 }
524
525 3 std::vector<std::string> Graph::ExecutedPasses() const {
526 3 std::vector<std::string> names;
527
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 3 times.
16 for (const std::uint32_t p : live_) {
528
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 names.push_back(passes_[p].name);
529 }
530 3 return names;
531 ✗ }
532
533 2 std::string Graph::Dot() const {
534 2 std::vector<bool> live(passes_.size(), !compiled_);
535
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 for (const std::uint32_t p : live_) {
536 2 live[p] = true;
537 }
538
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 std::ostringstream dot;
539
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 dot << "digraph FrameGraph {\n rankdir=LR;\n";
540
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 2 times.
13 for (std::size_t r = 0; r < resources_.size(); ++r) {
541
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 const Resource& resource = resources_[r];
542
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 const char* shape = resource.image ? "ellipse"
543 ✗ : resource.Structure() ? "hexagon"
544 11 : "cylinder";
545
3/6
✓ Branch 1 taken 11 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 11 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 11 times.
✗ Branch 8 not taken.
22 dot << " r" << r << " [shape=" << shape << ", label=\"" << resource.name << "\""
546
8/14
✓ Branch 1 taken 11 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 11 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 11 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 11 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 4 times.
✓ Branch 13 taken 7 times.
✓ Branch 15 taken 11 times.
✗ Branch 16 not taken.
✓ Branch 18 taken 11 times.
✗ Branch 19 not taken.
26 << (resource.imported ? ", style=bold" : "") << "];\n";
547 }
548
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
8 for (std::size_t p = 0; p < passes_.size(); ++p) {
549
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 const Pass& pass = passes_[p];
550
3/6
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 6 times.
✗ Branch 8 not taken.
12 dot << " p" << p << " [shape=box, label=\"" << pass.name << "\""
551
6/10
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 1 time.
✓ Branch 8 taken 5 times.
✓ Branch 10 taken 6 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 6 times.
✗ Branch 14 not taken.
13 << (live[p] ? "" : ", style=dashed") << "];\n";
552
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 6 times.
20 for (const DependencyAccess& access : pass.accesses) {
553
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 6 times.
14 if (Writes(access.use)) {
554
4/8
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 8 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 8 times.
✗ Branch 11 not taken.
8 dot << " p" << p << " -> r" << access.resource;
555 } else {
556
4/8
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 6 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 6 times.
✗ Branch 11 not taken.
6 dot << " r" << access.resource << " -> p" << p;
557 }
558 28 dot << " [label=\"" << ToString(access.use) << "\""
559
8/14
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 14 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 14 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 14 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 10 times.
✓ Branch 13 taken 4 times.
✓ Branch 15 taken 14 times.
✗ Branch 16 not taken.
✓ Branch 18 taken 14 times.
✗ Branch 19 not taken.
24 << (access.reach == Reach::Descriptor ? "" : ", style=dotted") << "];\n";
560 }
561 }
562
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 dot << "}\n";
563
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 return dot.str();
564 2 }
565
566 91 void AddUploadPass(Graph& graph, std::string_view name, Uploader& uploader,
567 std::span<Buffer> targets) {
568
2/2
✓ Branch 0 taken 73 times.
✓ Branch 1 taken 18 times.
91 if (!uploader.Staged()) {
569 return;
570 }
571 73 graph.Add(
572 name, Kind::Copy,
573 219 [&](PassBuilder& pass) {
574
2/2
✓ Branch 0 taken 228 times.
✓ Branch 1 taken 73 times.
301 for (Buffer& target : targets) {
575
2/2
✓ Branch 0 taken 180 times.
✓ Branch 1 taken 48 times.
228 if (target.Valid()) {
576 180 target = pass.Write(target, Use::CopyDst);
577 }
578 }
579 73 },
580 73 [&uploader](PassContext& pass) {
581
2/2
✓ Branch 1 taken 73 times.
✓ Branch 2 taken 73 times.
146 for (std::shared_ptr<const ernie::Buffer>& staging : uploader.Record(pass.commands)) {
582
1/2
✓ Branch 1 taken 73 times.
✗ Branch 2 not taken.
146 pass.Keep(std::move(staging));
583 73 }
584 73 });
585 }
586
587 } // namespace ernie::graph
588