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 | 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 | 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 | 12 | if (capacities[0] + capacities[1] + capacities[2] > | |
| 36 | 12 | indexing.maxPerStageUpdateAfterBindResources) { | |
| 37 | ✗ | throw DeviceError(DeviceErrorKind::Unsupported, | |
| 38 | ✗ | 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 | 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 | 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 | 12 | .pBindings = bindings.data()}; | |
| 65 | 12 | CheckVk(vkCreateDescriptorSetLayout(context.Device(), &layout, nullptr, &layout_), | |
| 66 | "Creating the bindless layout"); | ||
| 67 | 12 | const OnFailure freeLayout( | |
| 68 | 12 | [&] { vkDestroyDescriptorSetLayout(context.Device(), layout_, nullptr); }); | |
| 69 | 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 | 12 | CheckVk(vkCreateDescriptorPool(context.Device(), &pool, nullptr, &pool_), | |
| 75 | "Creating the bindless pool"); | ||
| 76 | 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 | 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 | 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 | 15 | Slots& slots = slots_[binding]; | |
| 120 | 15 | std::uint32_t index = slots.next; | |
| 121 | 15 | if (!slots.free.empty()) { | |
| 122 | 1 | index = slots.free.back(); | |
| 123 | 1 | slots.free.pop_back(); | |
| 124 | 14 | } else if (slots.next == slots.capacity) { | |
| 125 | ✗ | throw DeviceError(DeviceErrorKind::OutOfMemory, | |
| 126 | ✗ | 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 | 15 | .pImageInfo = &info}; | |
| 137 | 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 | 3 | context_.Retire([this, binding, index] { | |
| 143 | 3 | const std::lock_guard lock(mutex_); | |
| 144 | 3 | slots_[binding].free.push_back(index.value); | |
| 145 | 3 | }); | |
| 146 | 3 | } | |
| 147 | |||
| 148 | } // namespace ernie | ||
| 149 |