GCC Code Coverage Report


ernie/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
50 / 82
61.0%
Branches:
15 / 68
22.1%
Functions:
16 / 18
88.9%

device/vulkan.h
Line Branch Exec Source
1 // SPDX-License-Identifier: BSD-3-Clause
2 /// @file vulkan.h
3 /// @brief Vulkan and its allocator as the device layer sees them: results checked into device
4 /// errors, and physical devices probed against Ernie's requirements, each extension declared once
5 /// with its feature bits. Internal: no Vulkan type leaves `device/`.
6 #pragma once
7
8 #include "ernie/device/device.h"
9 #include "ernie/util/log.h"
10 #include "ernie/util/profiler.h"
11
12 #include <vk_mem_alloc.h>
13 #include <volk.h>
14
15 #include <array>
16 #include <concepts>
17 #include <cstdint>
18 #include <span>
19 #include <string>
20 #include <string_view>
21 #include <vector>
22
23 namespace ernie {
24
25 /// The usage every buffer takes, so shaders reach it by address and copies need no extra flags.
26 constexpr VkBufferUsageFlags kBufferUsage =
27 VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
28 VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
29
30 /// @throws DeviceError naming `what` unless `result` succeeded.
31 void CheckVk(VkResult result, std::string_view what);
32 /// @brief As above, describing what failed only when it did, for calls on hot paths.
33 2957 template <std::invocable Describe> void CheckVk(VkResult result, Describe&& describe) {
34
7/16
void ernie::CheckVk<ernie::AccelerationStructure::AccelerationStructure(ernie::AccelerationPool&, ernie::AccelerationSizes const&, std::basic_string_view<char, std::char_traits<char> >)::{lambda()#1}>(VkResult, ernie::AccelerationStructure::AccelerationStructure(ernie::AccelerationPool&, ernie::AccelerationSizes const&, std::basic_string_view<char, std::char_traits<char> >)::{lambda()#1}&&):
✗ Branch 0 not taken.
✓ Branch 1 taken 76 times.
void ernie::CheckVk<ernie::Queue::Submit(std::function<void (VkCommandBuffer_T*)> const&, std::span<ernie::Ticket const, 18446744073709551615ul>, ernie::BinarySemaphores)::{lambda()#1}>(VkResult, ernie::Queue::Submit(std::function<void (VkCommandBuffer_T*)> const&, std::span<ernie::Ticket const, 18446744073709551615ul>, ernie::BinarySemaphores)::{lambda()#1}&&):
✗ Branch 0 not taken.
✓ Branch 1 taken 271 times.
void ernie::CheckVk<ernie::Queue::Submit(std::function<void (VkCommandBuffer_T*)> const&, std::span<ernie::Ticket const, 18446744073709551615ul>, ernie::BinarySemaphores)::{lambda()#2}>(VkResult, ernie::Queue::Submit(std::function<void (VkCommandBuffer_T*)> const&, std::span<ernie::Ticket const, 18446744073709551615ul>, ernie::BinarySemaphores)::{lambda()#2}&&):
✗ Branch 0 not taken.
✓ Branch 1 taken 271 times.
void ernie::CheckVk<ernie::Queue::Submit(std::function<void (VkCommandBuffer_T*)> const&, std::span<ernie::Ticket const, 18446744073709551615ul>, ernie::BinarySemaphores)::{lambda()#3}>(VkResult, ernie::Queue::Submit(std::function<void (VkCommandBuffer_T*)> const&, std::span<ernie::Ticket const, 18446744073709551615ul>, ernie::BinarySemaphores)::{lambda()#3}&&):
✗ Branch 0 not taken.
✓ Branch 1 taken 268 times.
void ernie::CheckVk<ernie::Queue::Submit(std::function<void (VkCommandBuffer_T*)> const&, std::span<ernie::Ticket const, 18446744073709551615ul>, ernie::BinarySemaphores)::{lambda()#4}>(VkResult, ernie::Queue::Submit(std::function<void (VkCommandBuffer_T*)> const&, std::span<ernie::Ticket const, 18446744073709551615ul>, ernie::BinarySemaphores)::{lambda()#4}&&):
✗ Branch 0 not taken.
✓ Branch 1 taken 268 times.
void ernie::CheckVk<ernie::Ticket::Wait() const::{lambda()#1}>(VkResult, ernie::Ticket::Wait() const::{lambda()#1}&&):
✗ Branch 0 not taken.
✓ Branch 1 taken 374 times.
void ernie::CheckVk<ernie::Timeline::Reached() const::{lambda()#1}>(VkResult, ernie::Timeline::Reached() const::{lambda()#1}&&):
✗ Branch 0 not taken.
✓ Branch 1 taken 1429 times.
void ernie::CheckVk<ernie::Timeline::Signal(unsigned long) const::{lambda()#1}>(VkResult, ernie::Timeline::Signal(unsigned long) const::{lambda()#1}&&):
✗ Branch 0 not taken.
✗ Branch 1 not taken.
2957 if (result < VK_SUCCESS) {
35
0/16
void ernie::CheckVk<ernie::AccelerationStructure::AccelerationStructure(ernie::AccelerationPool&, ernie::AccelerationSizes const&, std::basic_string_view<char, std::char_traits<char> >)::{lambda()#1}>(VkResult, ernie::AccelerationStructure::AccelerationStructure(ernie::AccelerationPool&, ernie::AccelerationSizes const&, std::basic_string_view<char, std::char_traits<char> >)::{lambda()#1}&&):
✗ Branch 1 not taken.
✗ Branch 2 not taken.
void ernie::CheckVk<ernie::Queue::Submit(std::function<void (VkCommandBuffer_T*)> const&, std::span<ernie::Ticket const, 18446744073709551615ul>, ernie::BinarySemaphores)::{lambda()#1}>(VkResult, ernie::Queue::Submit(std::function<void (VkCommandBuffer_T*)> const&, std::span<ernie::Ticket const, 18446744073709551615ul>, ernie::BinarySemaphores)::{lambda()#1}&&):
✗ Branch 2 not taken.
✗ Branch 3 not taken.
void ernie::CheckVk<ernie::Queue::Submit(std::function<void (VkCommandBuffer_T*)> const&, std::span<ernie::Ticket const, 18446744073709551615ul>, ernie::BinarySemaphores)::{lambda()#2}>(VkResult, ernie::Queue::Submit(std::function<void (VkCommandBuffer_T*)> const&, std::span<ernie::Ticket const, 18446744073709551615ul>, ernie::BinarySemaphores)::{lambda()#2}&&):
✗ Branch 1 not taken.
✗ Branch 2 not taken.
void ernie::CheckVk<ernie::Queue::Submit(std::function<void (VkCommandBuffer_T*)> const&, std::span<ernie::Ticket const, 18446744073709551615ul>, ernie::BinarySemaphores)::{lambda()#3}>(VkResult, ernie::Queue::Submit(std::function<void (VkCommandBuffer_T*)> const&, std::span<ernie::Ticket const, 18446744073709551615ul>, ernie::BinarySemaphores)::{lambda()#3}&&):
✗ Branch 1 not taken.
✗ Branch 2 not taken.
void ernie::CheckVk<ernie::Queue::Submit(std::function<void (VkCommandBuffer_T*)> const&, std::span<ernie::Ticket const, 18446744073709551615ul>, ernie::BinarySemaphores)::{lambda()#4}>(VkResult, ernie::Queue::Submit(std::function<void (VkCommandBuffer_T*)> const&, std::span<ernie::Ticket const, 18446744073709551615ul>, ernie::BinarySemaphores)::{lambda()#4}&&):
✗ Branch 1 not taken.
✗ Branch 2 not taken.
void ernie::CheckVk<ernie::Ticket::Wait() const::{lambda()#1}>(VkResult, ernie::Ticket::Wait() const::{lambda()#1}&&):
✗ Branch 1 not taken.
✗ Branch 2 not taken.
void ernie::CheckVk<ernie::Timeline::Reached() const::{lambda()#1}>(VkResult, ernie::Timeline::Reached() const::{lambda()#1}&&):
✗ Branch 1 not taken.
✗ Branch 2 not taken.
void ernie::CheckVk<ernie::Timeline::Signal(unsigned long) const::{lambda()#1}>(VkResult, ernie::Timeline::Signal(unsigned long) const::{lambda()#1}&&):
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ CheckVk(result, std::string(describe()));
36 }
37 2957 }
38
39 /// @brief Runs teardown work, logging what it throws: destructors must not throw, and on device
40 /// loss every wait does.
41
4/10
void ernie::Quietly<ernie::Arenas::~Arenas()::{lambda()#1}>(ernie::Arenas::~Arenas()::{lambda()#1}&&):
✓ Branch 1 taken 109 times.
✗ Branch 2 not taken.
void ernie::Quietly<ernie::BindlessHeap::~BindlessHeap()::{lambda()#1}>(ernie::BindlessHeap::~BindlessHeap()::{lambda()#1}&&):
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
void ernie::Quietly<ernie::CudaInterop::~CudaInterop()::{lambda()#1}>(ernie::CudaInterop::~CudaInterop()::{lambda()#1}&&):
✗ Branch 1 not taken.
✗ Branch 2 not taken.
void ernie::Quietly<ernie::DeviceContext::~DeviceContext()::{lambda()#1}>(ernie::DeviceContext::~DeviceContext()::{lambda()#1}&&):
✓ Branch 1 taken 77 times.
✗ Branch 2 not taken.
void ernie::Quietly<ernie::DeviceContext::~DeviceContext()::{lambda()#2}>(ernie::DeviceContext::~DeviceContext()::{lambda()#2}&&):
✓ Branch 1 taken 77 times.
✗ Branch 2 not taken.
471 template <typename Teardown> void Quietly(Teardown&& teardown) noexcept {
42 try {
43
4/8
void ernie::Quietly<ernie::Queue::~Queue()::{lambda()#1}>(ernie::Queue::~Queue()::{lambda()#1}&&):
✓ Branch 1 taken 77 times.
✗ Branch 2 not taken.
void ernie::Quietly<ernie::Uploader::~Uploader()::{lambda()#1}>(ernie::Uploader::~Uploader()::{lambda()#1}&&):
✓ Branch 1 taken 115 times.
✗ Branch 2 not taken.
void ernie::Quietly<ernie::app::ImGuiRenderer::~ImGuiRenderer()::{lambda()#1}>(ernie::app::ImGuiRenderer::~ImGuiRenderer()::{lambda()#1}&&):
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
void ernie::Quietly<ernie::app::Viewer::~Viewer()::{lambda()#1}>(ernie::app::Viewer::~Viewer()::{lambda()#1}&&):
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
471 teardown();
44
0/18
void ernie::Quietly<ernie::Arenas::~Arenas()::{lambda()#1}>(ernie::Arenas::~Arenas()::{lambda()#1}&&):
✗ Branch 0 not taken.
✗ Branch 1 not taken.
void ernie::Quietly<ernie::BindlessHeap::~BindlessHeap()::{lambda()#1}>(ernie::BindlessHeap::~BindlessHeap()::{lambda()#1}&&):
✗ Branch 0 not taken.
✗ Branch 1 not taken.
void ernie::Quietly<ernie::CudaInterop::~CudaInterop()::{lambda()#1}>(ernie::CudaInterop::~CudaInterop()::{lambda()#1}&&):
✗ Branch 0 not taken.
✗ Branch 1 not taken.
void ernie::Quietly<ernie::DeviceContext::~DeviceContext()::{lambda()#1}>(ernie::DeviceContext::~DeviceContext()::{lambda()#1}&&):
✗ Branch 0 not taken.
✗ Branch 1 not taken.
void ernie::Quietly<ernie::DeviceContext::~DeviceContext()::{lambda()#2}>(ernie::DeviceContext::~DeviceContext()::{lambda()#2}&&):
✗ Branch 0 not taken.
✗ Branch 1 not taken.
void ernie::Quietly<ernie::Queue::~Queue()::{lambda()#1}>(ernie::Queue::~Queue()::{lambda()#1}&&):
✗ Branch 0 not taken.
✗ Branch 1 not taken.
void ernie::Quietly<ernie::Uploader::~Uploader()::{lambda()#1}>(ernie::Uploader::~Uploader()::{lambda()#1}&&):
✗ Branch 0 not taken.
✗ Branch 1 not taken.
void ernie::Quietly<ernie::app::ImGuiRenderer::~ImGuiRenderer()::{lambda()#1}>(ernie::app::ImGuiRenderer::~ImGuiRenderer()::{lambda()#1}&&):
✗ Branch 0 not taken.
✗ Branch 1 not taken.
void ernie::Quietly<ernie::app::Viewer::~Viewer()::{lambda()#1}>(ernie::app::Viewer::~Viewer()::{lambda()#1}&&):
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ } catch (const std::exception& error) {
45 ✗ log::Info(std::string("Ignored in teardown: ") + error.what());
46 }
47 471 }
48
49 constexpr VkAccessFlags2 kAnyAccess = VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT;
50
51 /// @brief Regions to copy from one buffer to another.
52 struct BufferCopies {
53 VkBuffer source = VK_NULL_HANDLE;
54 VkBuffer destination = VK_NULL_HANDLE;
55 std::span<const VkBufferCopy> regions;
56 };
57
58 /// @brief Records copies between buffers, ordered after earlier work on them and before later
59 /// work by one barrier either side.
60 void CopyBuffers(VkCommandBuffer commands, std::span<const BufferCopies> copies);
61 void CopyBuffer(VkCommandBuffer commands, VkBuffer source, VkBuffer destination,
62 std::span<const VkBufferCopy> regions);
63
64 /// @brief Records a global memory barrier: `src` work and writes before `dst` work and accesses.
65 /// The frame graph places barriers between passes; this is for work recorded by hand.
66 void Barrier(VkCommandBuffer commands, VkPipelineStageFlags2 srcStage, VkAccessFlags2 srcAccess,
67 VkPipelineStageFlags2 dstStage, VkAccessFlags2 dstAccess);
68
69 /// @brief The feature structs Ernie reads and enables, chained.
70 struct FeatureChain {
71 VkPhysicalDeviceFeatures2 core{.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
72 VkPhysicalDeviceVulkan11Features v11{.sType =
73 VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES};
74 VkPhysicalDeviceVulkan12Features v12{.sType =
75 VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES};
76 VkPhysicalDeviceVulkan13Features v13{.sType =
77 VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES};
78 VkPhysicalDeviceAccelerationStructureFeaturesKHR accelerationStructure{
79 .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR};
80 VkPhysicalDeviceRayQueryFeaturesKHR rayQuery{
81 .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_QUERY_FEATURES_KHR};
82 VkPhysicalDeviceFaultFeaturesEXT fault{
83 .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT};
84
85 391 FeatureChain() {
86 391 Relink();
87 391 }
88 77 FeatureChain(const FeatureChain& other) : FeatureChain() {
89 77 *this = other;
90 }
91 FeatureChain& operator=(const FeatureChain& other);
92
93 /// Chains the core structs and the optional ones named, in place: `core` heads the chain.
94 /// Optional structs whose extensions are absent must stay out of it.
95 void Link(bool accelerationStructures, bool rayQueries, bool faults);
96
97 private:
98 void Relink();
99 std::array<bool, 3> optional_{};
100 };
101
102 /// @brief A physical device as Ernie judges it.
103 struct PhysicalDevice {
104 VkPhysicalDevice handle = VK_NULL_HANDLE;
105 VulkanDeviceInfo info;
106 /// The device-local heaps' bytes, for choosing between devices.
107 std::uint64_t deviceLocalBytes = 0;
108 /// The first requirement it fails; empty when it meets them all.
109 std::string missing;
110 /// What it supports, read back.
111 FeatureChain features;
112 std::vector<std::string> extensions;
113
114 /// @return The extensions to enable: the optional ones of its capabilities (Vulkan 1.3 core
115 /// covers every requirement).
116 std::vector<const char*> EnabledExtensions() const;
117 /// @return The features to enable: the required ones and its capabilities', nothing else.
118 FeatureChain EnabledFeatures() const;
119 /// @return Higher for a better device: one meeting the requirements, discrete over integrated
120 /// over CPU, then more memory.
121 std::uint64_t Score() const;
122 };
123
124 /// @return What `handle` is and supports.
125 PhysicalDevice Probe(VkPhysicalDevice handle);
126
127 } // namespace ernie
128