| 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 |
|
|
|