GCC Code Coverage Report


ernie/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
79 / 86
91.9%
Branches:
27 / 56
48.2%
Functions:
10 / 13
76.9%

device/bindless.cpp
Line Branch Exec Source
1 // SPDX-License-Identifier: BSD-3-Clause
2 /// @file bindless.cpp
3 /// @brief The bindless descriptor set and its slot free lists.
4 #include "ernie/device/bindless.h"
5
6 #include "ernie/util/on_failure.h"
7
8 #include <algorithm>
9 #include <array>
10
11 namespace ernie {
12 namespace {
13
14 constexpr std::array kTypes{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
15 VK_DESCRIPTOR_TYPE_SAMPLER};
16 constexpr std::array kNames{"texture", "storage image", "sampler"};
17
18 } // namespace
19
20
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 BindlessHeap::BindlessHeap(DeviceContext& context) : context_(context) {
21 12 VkPhysicalDeviceDescriptorIndexingProperties indexing{
22 .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_PROPERTIES};
23 12 VkPhysicalDeviceProperties2 properties{.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
24 12 .pNext = &indexing};
25
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 vkGetPhysicalDeviceProperties2(context.Physical(), &properties);
26 // Generous where the device allows; within what every desktop driver and lavapipe offer. Every
27 // stage sees the bindings, so the per-stage limits bound them too.
28 12 const std::array capacities{
29 12 std::min({indexing.maxDescriptorSetUpdateAfterBindSampledImages,
30 12 indexing.maxPerStageDescriptorUpdateAfterBindSampledImages, 65536u}),
31 12 std::min({indexing.maxDescriptorSetUpdateAfterBindStorageImages,
32 12 indexing.maxPerStageDescriptorUpdateAfterBindStorageImages, 4096u}),
33 12 std::min({indexing.maxDescriptorSetUpdateAfterBindSamplers,
34 12 indexing.maxPerStageDescriptorUpdateAfterBindSamplers, 256u})};
35
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (capacities[0] + capacities[1] + capacities[2] >
36
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 indexing.maxPerStageUpdateAfterBindResources) {
37 ✗ throw DeviceError(DeviceErrorKind::Unsupported,
38
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ context.Info().name + " binds too few resources per stage");
39 }
40
41 12 std::array<VkDescriptorSetLayoutBinding, 3> bindings{};
42 12 std::array<VkDescriptorPoolSize, 3> sizes{};
43 12 const VkDescriptorBindingFlags flags = VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT |
44 VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT |
45 VK_DESCRIPTOR_BINDING_UPDATE_UNUSED_WHILE_PENDING_BIT;
46 12 const std::array bindingFlags{flags, flags, flags};
47
2/2
✓ Branch 0 taken 36 times.
✓ Branch 1 taken 12 times.
48 for (std::uint32_t i = 0; i < 3; ++i) {
48 36 slots_[i].capacity = capacities[i];
49 36 bindings[i] = {.binding = i,
50 36 .descriptorType = kTypes[i],
51 36 .descriptorCount = capacities[i],
52 .stageFlags = VK_SHADER_STAGE_ALL};
53 36 sizes[i] = {kTypes[i], capacities[i]};
54 }
55
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 const VkDescriptorSetLayoutBindingFlagsCreateInfo flagsInfo{
56 .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO,
57 .bindingCount = 3,
58 12 .pBindingFlags = bindingFlags.data()};
59 12 const VkDescriptorSetLayoutCreateInfo layout{
60 .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
61 .pNext = &flagsInfo,
62 .flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT,
63 .bindingCount = 3,
64
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 .pBindings = bindings.data()};
65
2/4
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
12 CheckVk(vkCreateDescriptorSetLayout(context.Device(), &layout, nullptr, &layout_),
66 "Creating the bindless layout");
67 12 const OnFailure freeLayout(
68
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 [&] { vkDestroyDescriptorSetLayout(context.Device(), layout_, nullptr); });
69
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 const VkDescriptorPoolCreateInfo pool{.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
70 .flags = VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT,
71 .maxSets = 1,
72 .poolSizeCount = 3,
73 12 .pPoolSizes = sizes.data()};
74
2/4
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
12 CheckVk(vkCreateDescriptorPool(context.Device(), &pool, nullptr, &pool_),
75 "Creating the bindless pool");
76
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 const OnFailure freePool([&] { vkDestroyDescriptorPool(context.Device(), pool_, nullptr); });
77 12 const VkDescriptorSetAllocateInfo allocate{.sType =
78 VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
79 12 .descriptorPool = pool_,
80 .descriptorSetCount = 1,
81 12 .pSetLayouts = &layout_};
82
2/4
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
12 CheckVk(vkAllocateDescriptorSets(context.Device(), &allocate, &set_),
83 "Allocating the bindless set");
84 12 }
85
86 12 BindlessHeap::~BindlessHeap() {
87 // Slots freed late write to this heap: they must come back before it goes.
88
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
24 Quietly([this] { context_.WaitIdle(); });
89 12 vkDestroyDescriptorPool(context_.Device(), pool_, nullptr);
90 12 vkDestroyDescriptorSetLayout(context_.Device(), layout_, nullptr);
91 12 }
92
93 4 BindlessIndex BindlessHeap::AddTexture(const DeviceImage& image) {
94 4 return Add(0, {.imageView = image.View(), .imageLayout = VK_IMAGE_LAYOUT_GENERAL});
95 }
96
97 10 BindlessIndex BindlessHeap::AddStorageImage(const DeviceImage& image) {
98 10 return Add(1, {.imageView = image.View(), .imageLayout = VK_IMAGE_LAYOUT_GENERAL});
99 }
100
101 1 BindlessIndex BindlessHeap::AddSampler(const Sampler& sampler) {
102 1 return Add(2, {.sampler = sampler.Handle()});
103 }
104
105 1 void BindlessHeap::RemoveTexture(BindlessIndex index) {
106 1 Remove(0, index);
107 1 }
108
109 2 void BindlessHeap::RemoveStorageImage(BindlessIndex index) {
110 2 Remove(1, index);
111 2 }
112
113 ✗ void BindlessHeap::RemoveSampler(BindlessIndex index) {
114 ✗ Remove(2, index);
115 ✗ }
116
117 15 BindlessIndex BindlessHeap::Add(std::uint32_t binding, const VkDescriptorImageInfo& info) {
118 15 const std::lock_guard lock(mutex_);
119
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 14 times.
15 Slots& slots = slots_[binding];
120 15 std::uint32_t index = slots.next;
121
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 14 times.
15 if (!slots.free.empty()) {
122 1 index = slots.free.back();
123 1 slots.free.pop_back();
124
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 } else if (slots.next == slots.capacity) {
125 ✗ throw DeviceError(DeviceErrorKind::OutOfMemory,
126
0/6
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ std::string("The bindless heap holds no more ") + kNames[binding] + "s");
127 } else {
128 14 ++slots.next;
129 }
130 15 const VkWriteDescriptorSet write{.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
131 15 .dstSet = set_,
132 .dstBinding = binding,
133 .dstArrayElement = index,
134 .descriptorCount = 1,
135 15 .descriptorType = kTypes[binding],
136
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 .pImageInfo = &info};
137
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 vkUpdateDescriptorSets(context_.Device(), 1, &write, 0, nullptr);
138 15 return {index};
139 15 }
140
141 3 void BindlessHeap::Remove(std::uint32_t binding, BindlessIndex index) {
142
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 context_.Retire([this, binding, index] {
143 3 const std::lock_guard lock(mutex_);
144
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 slots_[binding].free.push_back(index.value);
145 3 });
146 3 }
147
148 } // namespace ernie
149