TerrainGen coverage


terraingen/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
93 / 103
90.3%
Branches:
79 / 107
73.8%
Functions:
11 / 11
100.0%

gpu/backend.cpp
Line Branch Exec Source
1 #include "terraingen/gpu/backend.h"
2
3 #include "terraingen/gpu/cuda.h"
4 #include "terraingen/gpu/metal.h"
5 #include "terraingen/gpu/vulkan.h"
6
7 #include <atomic>
8 #include <cctype>
9 #include <cstdlib>
10 #include <stdexcept>
11 #include <string>
12
13 namespace terraingen {
14
15 namespace {
16
17 /// The backend `SetDefaultBackend` set, or -1 for none.
18 std::atomic<int> gDefault{-1};
19
20 /// The GPU backends, best first: what `Gpu` and `Auto` choose from.
21 constexpr Backend kGpuBackends[] = {Backend::Metal, Backend::Cuda, Backend::Vulkan};
22
23 /// Whether a backend runs here, on which device, or why not. CUDA and Metal also need their
24 /// kernels in the build (`TERRAINGEN_HAS_CUDA_KERNELS`, `TERRAINGEN_HAS_METAL_KERNELS`).
25 116 BackendStatus ProbeBackend(Backend backend) {
26 116 BackendStatus status{backend, false, {}, {}};
27 68 const auto from = [&](const auto* compute, const std::string& reason) {
28 68 status.available = compute != nullptr;
29
2/4
auto terraingen::(anonymous namespace)::ProbeBackend(terraingen::Backend)::{lambda(auto:1 const*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)#1}::operator()<terraingen::CudaCompute>(terraingen::CudaCompute const*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) const:
✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
auto terraingen::(anonymous namespace)::ProbeBackend(terraingen::Backend)::{lambda(auto:1 const*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)#1}::operator()<terraingen::VulkanCompute>(terraingen::VulkanCompute const*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) const:
✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
68 if (compute != nullptr) {
30 37 status.device = compute->DeviceName();
31 } else {
32 31 status.reason = reason;
33 }
34 68 };
35
4/5
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 37 times.
✓ Branch 2 taken 31 times.
✓ Branch 3 taken 31 times.
✗ Branch 4 not taken.
116 switch (backend) {
36 17 case Backend::Cpu:
37 17 status.available = true;
38
1/1
✓ Branch 1 taken 17 times.
17 status.device = "host";
39 17 break;
40 37 case Backend::Vulkan:
41
3/3
✓ Branch 1 taken 37 times.
✓ Branch 4 taken 37 times.
✓ Branch 7 taken 37 times.
37 from(VulkanCompute::Get(), VulkanCompute::Reason());
42 37 break;
43 31 case Backend::Cuda:
44 if (TERRAINGEN_HAS_CUDA_KERNELS) {
45
3/3
✓ Branch 1 taken 31 times.
✓ Branch 4 taken 31 times.
✓ Branch 7 taken 31 times.
31 from(CudaCompute::Get(), CudaCompute::Reason());
46 } else {
47 status.reason =
48 "this build has no CUDA kernels (NVRTC does not run where it was built)";
49 }
50 31 break;
51 31 case Backend::Metal:
52 if (TERRAINGEN_HAS_METAL_KERNELS) {
53 from(MetalCompute::Get(), MetalCompute::Reason());
54 } else {
55
1/1
✓ Branch 1 taken 31 times.
31 status.reason = MetalCompute::Reason();
56 }
57 31 break;
58 ✗ default:
59
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ status.reason = std::string(BackendName(backend)) + " is not a device backend";
60 ✗ break;
61 }
62 232 return status;
63 }
64
65 /// The best GPU backend available; `std::nullopt` when there is none.
66 13 std::optional<Backend> BestGpu() {
67
1/2
✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
39 for (const Backend backend : kGpuBackends) {
68
3/3
✓ Branch 1 taken 39 times.
✓ Branch 4 taken 13 times.
✓ Branch 5 taken 26 times.
39 if (ProbeBackend(backend).available) {
69 13 return backend;
70 }
71 }
72 ✗ return std::nullopt;
73 }
74
75 } // namespace
76
77 34 std::optional<Backend> BackendNamed(std::string_view name) {
78
1/1
✓ Branch 1 taken 34 times.
34 std::string lower(name);
79
2/2
✓ Branch 4 taken 144 times.
✓ Branch 5 taken 34 times.
178 for (char& c : lower) {
80 144 c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
81 }
82 136 for (const Backend backend : {Backend::Auto, Backend::Cpu, Backend::Gpu, Backend::Vulkan,
83
2/2
✓ Branch 2 taken 129 times.
✓ Branch 3 taken 7 times.
170 Backend::Cuda, Backend::Metal}) {
84
2/2
✓ Branch 3 taken 27 times.
✓ Branch 4 taken 102 times.
129 if (lower == BackendName(backend)) {
85 27 return backend;
86 }
87 }
88 7 return std::nullopt;
89 34 }
90
91 17467 std::string_view BackendName(Backend backend) {
92
7/7
✓ Branch 0 taken 36 times.
✓ Branch 1 taken 17329 times.
✓ Branch 2 taken 46 times.
✓ Branch 3 taken 26 times.
✓ Branch 4 taken 16 times.
✓ Branch 5 taken 13 times.
✓ Branch 6 taken 1 time.
17467 switch (backend) {
93 36 case Backend::Auto:
94 36 return "auto";
95 17329 case Backend::Cpu:
96 17329 return "cpu";
97 46 case Backend::Vulkan:
98 46 return "vulkan";
99 26 case Backend::Gpu:
100 26 return "gpu";
101 16 case Backend::Cuda:
102 16 return "cuda";
103 13 case Backend::Metal:
104 13 return "metal";
105 }
106 1 return "unknown";
107 }
108
109 90 std::string DescribeBackend(Backend backend) {
110
4/6
✓ Branch 0 taken 86 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 86 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 86 times.
90 if (backend == Backend::Vulkan || backend == Backend::Cuda || backend == Backend::Metal) {
111
2/3
✓ Branch 1 taken 4 times.
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
4 if (const BackendStatus status = ProbeBackend(backend); status.available) {
112
4/4
✓ Branch 2 taken 4 times.
✓ Branch 5 taken 4 times.
✓ Branch 8 taken 4 times.
✓ Branch 11 taken 4 times.
4 return std::string(BackendName(backend)) + " (" + status.device + ")";
113
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
4 }
114 }
115
1/1
✓ Branch 2 taken 86 times.
86 return std::string(BackendName(backend));
116 }
117
118 17 std::vector<BackendStatus> Backends() {
119 return {ProbeBackend(Backend::Cpu), ProbeBackend(Backend::Vulkan), ProbeBackend(Backend::Cuda),
120
3/5
✓ Branch 1 taken 17 times.
✓ Branch 3 taken 68 times.
✓ Branch 4 taken 17 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
102 ProbeBackend(Backend::Metal)};
121
4/8
✓ Branch 1 taken 17 times.
✓ Branch 4 taken 17 times.
✓ Branch 7 taken 17 times.
✓ Branch 10 taken 17 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
17 }
122
123 1229 Backend DefaultBackend() {
124
2/2
✓ Branch 0 taken 587 times.
✓ Branch 1 taken 642 times.
1229 if (const int set = gDefault.load(); set >= 0) {
125 587 return static_cast<Backend>(set);
126 }
127 642 const char* name = std::getenv("TERRAINGEN_BACKEND");
128
4/4
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 636 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 5 times.
642 if (name == nullptr || *name == '\0') {
129 637 return Backend::Cpu;
130 }
131
3/3
✓ Branch 2 taken 5 times.
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 1 time.
5 if (const auto backend = BackendNamed(name)) {
132 4 return *backend;
133 }
134
3/3
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
✓ Branch 7 taken 1 time.
2 throw std::invalid_argument("TERRAINGEN_BACKEND is \"" + std::string(name) +
135
1/1
✓ Branch 2 taken 1 time.
3 "\"; expected auto, cpu, gpu, vulkan, cuda or metal");
136 }
137
138 9 void SetDefaultBackend(Backend backend) {
139 gDefault.store(static_cast<int>(backend));
140 9 }
141
142 1229 Backend ResolveBackend(Backend requested) {
143
4/5
✓ Branch 0 taken 1211 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 11 times.
✗ Branch 4 not taken.
1229 switch (requested) {
144 1211 case Backend::Cpu:
145 1211 return Backend::Cpu;
146 5 case Backend::Vulkan:
147 case Backend::Cuda:
148 case Backend::Metal:
149
3/3
✓ Branch 1 taken 5 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 3 times.
5 if (const BackendStatus status = ProbeBackend(requested); !status.available) {
150
3/3
✓ Branch 2 taken 2 times.
✓ Branch 5 taken 2 times.
✓ Branch 8 taken 2 times.
4 throw std::runtime_error("The " + std::string(BackendName(requested)) +
151
2/2
✓ Branch 2 taken 2 times.
✓ Branch 5 taken 2 times.
6 " backend is unavailable: " + status.reason);
152 5 }
153 3 return requested;
154 2 case Backend::Gpu:
155
2/3
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 if (const std::optional<Backend> gpu = BestGpu()) {
156 2 return *gpu;
157 }
158 {
159 ✗ std::string reasons;
160
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ for (const Backend backend : kGpuBackends) {
161 reasons +=
162
0/6
✗ Branch 1 not taken.
✗ Branch 5 not taken.
✗ Branch 8 not taken.
✗ Branch 11 not taken.
✗ Branch 14 not taken.
✗ Branch 17 not taken.
✗ "; " + std::string(BackendName(backend)) + ": " + ProbeBackend(backend).reason;
163 }
164
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ throw std::runtime_error("No GPU backend is available" + reasons);
165 }
166 11 case Backend::Auto:
167 11 break;
168 }
169
1/1
✓ Branch 1 taken 11 times.
11 return BestGpu().value_or(Backend::Cpu);
170 }
171
172 } // namespace terraingen
173