device/buffer.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | /// @file buffer.cpp | ||
| 3 | /// @brief Allocating buffers through VMA by their use, and sharing them. | ||
| 4 | #include "ernie/device/buffer.h" | ||
| 5 | |||
| 6 | #include <atomic> | ||
| 7 | #include <utility> | ||
| 8 | |||
| 9 | namespace ernie { | ||
| 10 | namespace { | ||
| 11 | |||
| 12 | /// Every buffer's usage: on devices with ray queries, any may hold or feed an acceleration | ||
| 13 | /// structure, so geometry in columns builds one in place. | ||
| 14 | 343 | VkBufferCreateInfo BufferInfo(const DeviceContext& context, std::uint64_t size, | |
| 15 | VkBufferUsageFlags usage) { | ||
| 16 | 343 | const VkBufferUsageFlags acceleration = | |
| 17 | 343 | context.Capabilities().rayQuery | |
| 18 | 343 | ? VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | | |
| 19 | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | ||
| 20 | 343 | : 0; | |
| 21 | 343 | return {.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, | |
| 22 | .size = size, | ||
| 23 | 343 | .usage = usage | kBufferUsage | acceleration, | |
| 24 | 343 | .sharingMode = VK_SHARING_MODE_EXCLUSIVE}; | |
| 25 | } | ||
| 26 | |||
| 27 | 2 | void RequireExternal(const DeviceContext& context) { | |
| 28 | 2 | if (!context.Capabilities().externalMemory) { | |
| 29 | ✗ | throw DeviceError(DeviceErrorKind::Unsupported, | |
| 30 | ✗ | context.Info().name + " cannot share memory with other APIs"); | |
| 31 | } | ||
| 32 | 2 | } | |
| 33 | |||
| 34 | } // namespace | ||
| 35 | |||
| 36 | 342 | Buffer::Buffer(DeviceContext& context, std::uint64_t size, MemoryUse use, std::string_view name, | |
| 37 | 342 | VkBufferUsageFlags usage) | |
| 38 | 342 | : allocator_(context.Allocator()), device_(context.Device()), shared_(use == MemoryUse::Shared), | |
| 39 | 342 | size_(size) { | |
| 40 | 342 | const VkBufferCreateInfo info = BufferInfo(context, size, usage); | |
| 41 | 342 | VmaAllocationInfo allocated{}; | |
| 42 | 342 | if (shared_) { | |
| 43 | 1 | RequireExternal(context); | |
| 44 | 1 | CreateExportableBuffer(allocator_, info, buffer_, allocation_, allocated); | |
| 45 | 1 | Finish(context, allocated, name); | |
| 46 | 1 | return; | |
| 47 | } | ||
| 48 | 341 | VmaAllocationCreateInfo allocation{.usage = VMA_MEMORY_USAGE_AUTO}; | |
| 49 | 341 | if (use == MemoryUse::Upload) { | |
| 50 | // Cached host memory: staging is written a few rows at a time, which write-combined or | ||
| 51 | // device-local mappings turn into a bus transaction each. | ||
| 52 | 121 | allocation.flags = | |
| 53 | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; | ||
| 54 | 220 | } else if (use == MemoryUse::Readback) { | |
| 55 | 86 | allocation.flags = | |
| 56 | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; | ||
| 57 | } else { | ||
| 58 | 134 | allocation.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; | |
| 59 | } | ||
| 60 | 682 | CheckVk(vmaCreateBuffer(allocator_, &info, &allocation, &buffer_, &allocation_, &allocated), | |
| 61 | 1023 | "Allocating " + std::to_string(size) + " bytes for " + std::string(name)); | |
| 62 | 341 | Finish(context, allocated, name); | |
| 63 | } | ||
| 64 | |||
| 65 | 1 | Buffer::Buffer(DeviceContext& context, ExternalHandle handle, std::uint64_t size, | |
| 66 | 1 | std::string_view name, SharedFrom from) | |
| 67 | 1 | : allocator_(context.Allocator()), device_(context.Device()), shared_(true), size_(size) { | |
| 68 | 1 | RequireExternal(context); | |
| 69 | 1 | VmaAllocationInfo allocated{}; | |
| 70 | 1 | if (from == SharedFrom::Vulkan) { | |
| 71 | 1 | CreateImportedBuffer(allocator_, BufferInfo(context, size, 0), handle, buffer_, allocation_, | |
| 72 | allocated); | ||
| 73 | } else { | ||
| 74 | ✗ | CreateImportedBuffer(device_, context.Physical(), BufferInfo(context, size, 0), handle, | |
| 75 | ✗ | buffer_, memory_); | |
| 76 | ✗ | allocated.size = size; | |
| 77 | } | ||
| 78 | 1 | Finish(context, allocated, name); | |
| 79 | 1 | } | |
| 80 | |||
| 81 | 549 | void Buffer::Destroy() { | |
| 82 | 549 | if (memory_ != VK_NULL_HANDLE) { | |
| 83 | ✗ | vkDestroyBuffer(device_, buffer_, nullptr); | |
| 84 | ✗ | vkFreeMemory(device_, memory_, nullptr); | |
| 85 | 549 | } else if (buffer_ != VK_NULL_HANDLE) { | |
| 86 | 343 | vmaDestroyBuffer(allocator_, buffer_, allocation_); | |
| 87 | } | ||
| 88 | 549 | } | |
| 89 | |||
| 90 | 343 | void Buffer::Finish(DeviceContext& context, const VmaAllocationInfo& allocated, | |
| 91 | std::string_view name) { | ||
| 92 | 343 | const VkBufferDeviceAddressInfo addressInfo{ | |
| 93 | 343 | .sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, .buffer = buffer_}; | |
| 94 | 343 | address_ = vkGetBufferDeviceAddress(device_, &addressInfo); | |
| 95 | 343 | memorySize_ = allocated.size; | |
| 96 | 343 | static std::atomic<std::uint64_t> ids = 0; | |
| 97 | 343 | id_ = ++ids; | |
| 98 | 343 | if (allocated.pMappedData != nullptr) { | |
| 99 | 207 | mapped_ = {static_cast<std::byte*>(allocated.pMappedData), static_cast<std::size_t>(size_)}; | |
| 100 | } | ||
| 101 | 343 | context.Name(VK_OBJECT_TYPE_BUFFER, reinterpret_cast<std::uint64_t>(buffer_), name); | |
| 102 | 343 | } | |
| 103 | |||
| 104 | 446 | Buffer::~Buffer() { | |
| 105 | 446 | Destroy(); | |
| 106 | 446 | } | |
| 107 | |||
| 108 | ✗ | Buffer::Buffer(Buffer&& other) noexcept { | |
| 109 | ✗ | *this = std::move(other); | |
| 110 | ✗ | } | |
| 111 | |||
| 112 | 103 | Buffer& Buffer::operator=(Buffer&& other) noexcept { | |
| 113 | 103 | if (this != &other) { | |
| 114 | 103 | Destroy(); | |
| 115 | 103 | allocator_ = std::exchange(other.allocator_, VK_NULL_HANDLE); | |
| 116 | 103 | device_ = std::exchange(other.device_, VK_NULL_HANDLE); | |
| 117 | 103 | shared_ = std::exchange(other.shared_, false); | |
| 118 | 103 | buffer_ = std::exchange(other.buffer_, VK_NULL_HANDLE); | |
| 119 | 103 | allocation_ = std::exchange(other.allocation_, VK_NULL_HANDLE); | |
| 120 | 103 | memory_ = std::exchange(other.memory_, VK_NULL_HANDLE); | |
| 121 | 103 | size_ = std::exchange(other.size_, 0); | |
| 122 | 103 | memorySize_ = std::exchange(other.memorySize_, 0); | |
| 123 | 103 | id_ = std::exchange(other.id_, 0); | |
| 124 | 103 | address_ = std::exchange(other.address_, 0); | |
| 125 | 103 | mapped_ = std::exchange(other.mapped_, {}); | |
| 126 | } | ||
| 127 | 103 | return *this; | |
| 128 | } | ||
| 129 | |||
| 130 | 121 | void Buffer::Flush(std::uint64_t offset, std::uint64_t size) const { | |
| 131 | 121 | CheckVk(vmaFlushAllocation(allocator_, allocation_, offset, size), "Flushing a buffer"); | |
| 132 | 121 | } | |
| 133 | |||
| 134 | 86 | void Buffer::Invalidate(std::uint64_t offset, std::uint64_t size) const { | |
| 135 | 86 | CheckVk(vmaInvalidateAllocation(allocator_, allocation_, offset, size), | |
| 136 | "Invalidating a buffer"); | ||
| 137 | 86 | } | |
| 138 | |||
| 139 | 2 | ExternalHandle Buffer::Export() const { | |
| 140 | 2 | if (!shared_) { | |
| 141 | 2 | throw DeviceError(DeviceErrorKind::Unsupported, "Only shared buffers can be exported"); | |
| 142 | } | ||
| 143 | 1 | if (memory_ != VK_NULL_HANDLE) { | |
| 144 | ✗ | return ExportMemory(device_, memory_, "a shared buffer"); | |
| 145 | } | ||
| 146 | 1 | VmaAllocationInfo allocated{}; | |
| 147 | 1 | vmaGetAllocationInfo(allocator_, allocation_, &allocated); | |
| 148 | // Shared buffers have dedicated memory: the buffer starts it. | ||
| 149 | 1 | return ExportMemory(device_, allocated.deviceMemory, "a shared buffer"); | |
| 150 | } | ||
| 151 | |||
| 152 | } // namespace ernie | ||
| 153 |