GCC Code Coverage Report


ernie/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
68 / 78
87.2%
Branches:
73 / 147
49.7%
Functions:
1 / 1, 11 excl
100.0%

device/context.h
Line Branch Exec Source
1 // SPDX-License-Identifier: BSD-3-Clause
2 /// @file context.h
3 /// @brief A device and everything that serves it: its queues, its allocator, object names and the
4 /// retirement of objects the GPU may still use.
5 #pragma once
6
7 #include "ernie/device/instance.h"
8 #include "ernie/device/profile.h"
9 #include "ernie/device/queue.h"
10 #include "ernie/device/vulkan.h"
11
12 #include <atomic>
13 #include <condition_variable>
14 #include <filesystem>
15 #include <functional>
16 #include <memory>
17 #include <mutex>
18 #include <string>
19 #include <string_view>
20 #include <vector>
21
22 namespace ernie {
23
24 struct DeviceContextOptions {
25 /// The Khronos validation layer with synchronization validation, when installed.
26 bool validation = false;
27 /// The device to use: its UUID in hex, or part of its name; empty for the best scoring one.
28 std::string device;
29 std::vector<std::string> instanceExtensions;
30 /// A file the pipeline cache is loaded from, when it was saved for the same driver and device,
31 /// and saved to on destruction; empty to keep it in memory only.
32 std::filesystem::path pipelineCache;
33 };
34
35 /// @brief Bulk bytes moved: between the host and the device through staging, and copied on the
36 /// device because another API's memory could not be shared.
37 struct Transfers {
38 1 std::uint64_t uploaded = 0;
39 1 std::uint64_t readBack = 0;
40 1 std::uint64_t copied = 0;
41
3/12
✗ Branch 0 not taken.
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✗ Branch 5 not taken.
1 bool operator==(const Transfers&) const = default;
42 };
43
44 /// @brief One device. Nothing is global: two contexts in a process, on one device or two, keep
45 /// their queues, memory and messages apart.
46 class DeviceContext {
47 public:
48 /// @throws DeviceError if no device meets Ernie's requirements, or none matches `device`.
49 explicit DeviceContext(const DeviceContextOptions& options = {});
50 ~DeviceContext();
51 DeviceContext(const DeviceContext&) = delete;
52 DeviceContext& operator=(const DeviceContext&) = delete;
53
54 2 const VulkanDeviceInfo& Info() const {
55
0/4
✗ Branch 1 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 2 not taken.
2 return physical_.info;
56 }
57 18 const DeviceCapabilities& Capabilities() const {
58
3/8
<unknown function 54>:
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
<unknown function 57>:
✗ Branch 0 not taken.
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✗ Branch 1 not taken.
<unknown function 58>:
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
24 return physical_.info.capabilities;
59 }
60 177 const VkPhysicalDeviceLimits& Limits() const {
61
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 176 times.
177 return limits_;
62 }
63 /// @return The largest buffer a shader can address whole: arenas are no larger.
64 892 std::uint64_t MaxBufferSize() const {
65
4/4
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 781 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 109 times.
892 return maxBufferSize_;
66 }
67
68 /// The queue for graphics and compute work.
69 803 Queue& Main() const {
70
35/63
<unknown function 38>:
✓ 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.
✓ Branch 11 taken 1 time.
✗ Branch 12 not taken.
✓ Branch 14 taken 1 time.
✗ Branch 15 not taken.
<unknown function 54>:
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
<unknown function 57>:
✓ Branch 1 taken 27 times.
✗ Branch 2 not taken.
<unknown function 64>:
✓ 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.
<unknown function 69>:
✓ Branch 1 taken 8 times.
✓ Branch 1 taken 22 times.
✓ Branch 2 taken 3 times.
✓ Branch 2 taken 23 times.
✗ Branch 3 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✓ Branch 4 taken 1 time.
✓ Branch 4 taken 19 times.
✗ Branch 5 not taken.
✓ Branch 5 taken 5 times.
✓ Branch 5 taken 2 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 time.
✓ Branch 7 taken 8 times.
✗ Branch 8 not taken.
✗ Branch 8 not taken.
✓ Branch 10 taken 2 times.
✓ Branch 10 taken 1 time.
✗ Branch 11 not taken.
✓ Branch 11 taken 1 time.
✓ Branch 13 taken 1 time.
✓ Branch 13 taken 1 time.
✗ Branch 14 not taken.
✗ Branch 14 not taken.
✓ Branch 16 taken 3 times.
✓ Branch 16 taken 1 time.
✗ Branch 17 not taken.
✗ Branch 17 not taken.
✓ Branch 19 taken 1 time.
✓ Branch 19 taken 1 time.
✗ Branch 20 not taken.
✗ Branch 20 not taken.
✓ Branch 22 taken 2 times.
✗ Branch 23 not taken.
✓ Branch 25 taken 2 times.
✗ Branch 26 not taken.
✓ Branch 28 taken 1 time.
✗ Branch 29 not taken.
✓ Branch 31 taken 1 time.
✗ Branch 32 not taken.
742 return *main_;
71 }
72 /// The queue for asynchronous compute: a compute-only family's, else the main queue.
73 Queue& Compute() const {
74 return *compute_;
75 }
76 /// The queue for uploads: a transfer-only family's, else the compute queue.
77 Queue& Transfer() const {
78 return *transfer_;
79 }
80 /// @return Each distinct queue once.
81 std::vector<Queue*> Queues() const;
82
83 /// @brief Keeps `object` alive until every queue's work submitted so far is done.
84 void Retire(std::shared_ptr<const void> object);
85 /// @brief Runs `release` once every queue's work submitted so far is done: how memory the GPU
86 /// may still read is given back.
87 void Retire(std::function<void()> release);
88 /// @brief Frees retired objects whose work is done.
89 void Collect();
90 /// @brief Waits for every queue, then frees everything retired.
91 void WaitIdle();
92
93 /// @return The bulk bytes moved since the context was created, so a step can be checked to
94 /// copy nothing.
95 15 ernie::Transfers Transfers() const {
96 15 return {uploaded_.load(), readBack_.load(), copied_.load()};
97 }
98 480 void CountUpload(std::uint64_t bytes) {
99
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 479 times.
480 uploaded_ += bytes;
100 }
101 84 void CountReadback(std::uint64_t bytes) {
102
1/2
✓ Branch 1 taken 84 times.
✗ Branch 2 not taken.
84 readBack_ += bytes;
103 }
104 ✗ void CountCopy(std::uint64_t bytes) {
105 ✗ copied_ += bytes;
106 }
107
108 /// @brief Names a Vulkan object for validation messages, captures and crash reports.
109 void Name(VkObjectType type, std::uint64_t handle, std::string_view name) const;
110 /// @brief Opens and closes a named region of commands, as a capture or validation message
111 /// shows it.
112 450 void BeginLabel(VkCommandBuffer commands, const char* name) const {
113 450 instance_.BeginLabel(commands, name);
114 }
115 450 void EndLabel(VkCommandBuffer commands) const {
116 450 instance_.EndLabel(commands);
117 }
118 /// @return The validation layer's messages since the last call; empty without validation.
119 67 std::vector<std::string> TakeValidationMessages() {
120 67 return instance_.TakeMessages();
121 }
122
123 2 VkInstance Instance() const {
124
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 return instance_.Handle();
125 }
126 298 VkPhysicalDevice Physical() const {
127
3/6
<unknown function 54>:
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
<unknown function 60>:
✓ Branch 3 taken 77 times.
✗ Branch 4 not taken.
<unknown function 69>:
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
298 return physical_.handle;
128 }
129 946 VkDevice Device() const {
130
13/32
<unknown function 126>:
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 341 times.
✓ Branch 3 taken 76 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 76 times.
✗ Branch 7 not taken.
<unknown function 54>:
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 15 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 12 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 12 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 12 times.
✗ Branch 13 not taken.
<unknown function 58>:
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
<unknown function 60>:
✗ Branch 0 not taken.
✓ Branch 1 taken 77 times.
<unknown function 69>:
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 3 times.
✗ 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.
934 return device_;
131 }
132 /// @return The GPU profiler, or null when GPU zones record nothing.
133 472 GpuProfiler* Profiler() const {
134
1/2
✓ Branch 1 taken 450 times.
✗ Branch 2 not taken.
472 return profiler_.get();
135 }
136 377 VmaAllocator Allocator() const {
137
3/4
<unknown function 126>:
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 341 times.
<unknown function 54>:
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
375 return allocator_;
138 }
139 /// @return The cache every pipeline on this device is created through.
140 103 VkPipelineCache PipelineCache() const {
141
2/4
<unknown function 60>:
✓ Branch 1 taken 98 times.
✗ Branch 2 not taken.
<unknown function 69>:
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
103 return pipelineCache_;
142 }
143 /// @brief Writes the pipeline cache to `DeviceContextOptions::pipelineCache`, as destruction
144 /// does; nothing without one.
145 /// @throws std::runtime_error If the file cannot be written.
146 void SavePipelineCache() const;
147
148 private:
149 3581 struct Retired {
150 std::function<void()> release;
151 std::vector<Ticket> tickets;
152 };
153
154 ernie::Instance instance_;
155 PhysicalDevice physical_;
156 VkPhysicalDeviceLimits limits_{};
157 std::uint64_t maxBufferSize_ = 0;
158 VkDevice device_ = VK_NULL_HANDLE;
159 VmaAllocator allocator_ = VK_NULL_HANDLE;
160 std::filesystem::path pipelineCachePath_;
161 VkPipelineCache pipelineCache_ = VK_NULL_HANDLE;
162 std::unique_ptr<GpuProfiler> profiler_;
163 std::vector<std::unique_ptr<Queue>> queues_;
164 Queue* main_ = nullptr;
165 Queue* compute_ = nullptr;
166 Queue* transfer_ = nullptr;
167 std::mutex retiredMutex_;
168 std::vector<Retired> retired_;
169 /// Releases taken by `Collect` and still running.
170 std::size_t releasing_ = 0;
171 std::condition_variable released_;
172 std::atomic<std::uint64_t> uploaded_ = 0;
173 std::atomic<std::uint64_t> readBack_ = 0;
174 std::atomic<std::uint64_t> copied_ = 0;
175 };
176
177 } // namespace ernie
178