/* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "stdafx.h" #include "rendering/vulkan14/vulkan14memory.h" #include "rendering/vulkan14/vulkan14commands.h" #include "rendering/vulkan14/vulkan14instance.h" #include "rendering/vulkan14/vulkan14device.h" #include #include namespace vk14 { // address registry namespace address_registry { namespace { struct entry { VkDeviceAddress address{0}; // of a buffer; 0 for an image, placed by its memory and offset VkDeviceMemory memory{VK_NULL_HANDLE}; VkDeviceSize offset{0}; VkDeviceSize size{0}; std::string name; bool image{false}; bool dedicated{false}; // an image in memory of its own, which has no device address: it isn't placed std::chrono::steady_clock::time_point freed{}; }; std::atomic g_enabled{false}; std::mutex g_mutex; // by the handle of the buffer or the image std::unordered_map g_live; // the last ones let go, the oldest first std::deque g_freed; std::size_t constexpr freed_kept{16384}; void add(std::uint64_t const Handle, entry &&Entry) { std::lock_guard lock(g_mutex); g_live[Handle] = std::move(Entry); } // called by the destructors of buffers and images, so it lets no exception out: the registry only serves the report after // a device loss, and a resource it fails to move to the freed ones mustn't stop the resource from being released void remove(std::uint64_t const Handle) noexcept { try { std::scoped_lock lock(g_mutex); auto const lookup{g_live.find(Handle)}; if (lookup == g_live.end()) { return; } lookup->second.freed = std::chrono::steady_clock::now(); g_freed.push_back(std::move(lookup->second)); g_live.erase(lookup); if (g_freed.size() > freed_kept) { g_freed.pop_front(); } } catch (std::exception const &) { // the entry stays among the live ones, which the report can live with } } std::string hex(std::uint64_t const Value) { char text[24]; std::snprintf(text, sizeof(text), "0x%" PRIx64, Value); return text; } std::string bytes(VkDeviceSize const Size) { return (Size >= 1024 * 1024 ? std::to_string(Size / (1024 * 1024)) + " MiB" : Size >= 1024 ? std::to_string(Size / 1024) + " KiB" : std::to_string(Size) + " B"); } } // namespace void enable(bool const Enabled) { std::lock_guard lock(g_mutex); g_enabled = Enabled; g_live.clear(); g_freed.clear(); } bool enabled() { return g_enabled; } std::string describe(VkDeviceAddress const Lower, VkDeviceAddress const Upper) { if (false == g_enabled) { return {}; } std::lock_guard lock(g_mutex); // the addresses of the blocks of memory, from the buffers in them: an image is at the address of its block and its offset std::unordered_map blocks; for (auto const &[handle, item] : g_live) { if ((item.address != 0) && (item.memory != VK_NULL_HANDLE) && (item.address >= item.offset)) { blocks.emplace(item.memory, item.address - item.offset); } } auto const placed = [&](entry const &Item) -> VkDeviceAddress { if (Item.address != 0) { return Item.address; } auto const block{blocks.find(Item.memory)}; return (block != blocks.end() ? block->second + Item.offset : 0); }; auto const label = [&](entry const &Item, VkDeviceAddress const Address) { return std::string(Item.image ? "image" : "buffer") + " \"" + Item.name + "\" (" + hex(Address) + ", " + bytes(Item.size) + ")"; }; auto const now{std::chrono::steady_clock::now()}; std::string result; auto const note = [&](std::string const &Text) { result += (result.empty() ? "" : ", ") + Text; }; // what holds the addresses entry const *below{nullptr}; entry const *above{nullptr}; VkDeviceAddress belowend{0}, abovestart{0}; std::size_t holders{0}; for (auto const &[handle, item] : g_live) { auto const address{placed(item)}; if (address == 0) { continue; } auto const end{address + item.size}; if ((address <= Upper) && (end > Lower)) { if (++holders <= 4) { note("in the " + label(item, address) + " at its byte " + std::to_string(Lower > address ? Lower - address : 0)); } continue; } if ((end <= Lower) && ((below == nullptr) || (end > belowend))) { below = &item; belowend = end; } if ((address > Upper) && ((above == nullptr) || (address < abovestart))) { above = &item; abovestart = address; } } // what was let go there not long ago, the last ones first std::size_t freed{0}; for (auto it = g_freed.rbegin(); (it != g_freed.rend()) && (freed < 4); ++it) { auto const address{placed(*it)}; if ((address != 0) && (address <= Upper) && (address + it->size > Lower)) { ++freed; auto const ago{std::chrono::duration(now - it->freed).count()}; note("in the " + label(*it, address) + " let go " + std::to_string(static_cast(ago)) + " ms ago"); } } if (holders == 0) { if (below != nullptr) { note(bytes(Lower - belowend) + " past the end of the " + label(*below, belowend - below->size)); } if (above != nullptr) { note(bytes(abovestart - Upper) + " before the " + label(*above, abovestart)); } if (freed == 0) { // the images in memory of their own aren't placed: those let go just before may be what the address was std::string recent; std::size_t count{0}; for (auto it = g_freed.rbegin(); it != g_freed.rend(); ++it) { auto const ago{std::chrono::duration(now - it->freed).count()}; if (ago > 5000.0) { break; } if (it->dedicated && (++count <= 8)) { recent += (recent.empty() ? "" : ", ") + std::string("\"") + it->name + "\" (" + bytes(it->size) + ", " + std::to_string(static_cast(ago)) + " ms ago)"; } } std::size_t live{0}; for (auto const &[handle, item] : g_live) { live += (item.dedicated ? 1 : 0); } note(std::to_string(live) + " images in memory of their own aren't placed; let go of them in the last 5 s: " + (recent.empty() ? std::string("none") : recent)); } } // an index below zero of a buffer read through a descriptor wraps around the 32 bits of the offset, to almost 4 GiB past its // start: what is there 4 GiB lower is the buffer read auto constexpr wrap{VkDeviceAddress{1} << 32}; if ((holders == 0) && (Lower >= wrap)) { auto const lower{Lower - wrap}; auto const upper{Upper - wrap}; entry const *holder{nullptr}; entry const *next{nullptr}; VkDeviceAddress holderstart{0}, nextstart{0}; for (auto const &[handle, item] : g_live) { auto const address{placed(item)}; if (address == 0) { continue; } if ((address <= upper) && (address + item.size > lower)) { holder = &item; holderstart = address; break; } if ((address > upper) && ((next == nullptr) || (address < nextstart))) { next = &item; nextstart = address; } } if (holder != nullptr) { note("4 GiB lower (an offset wrapped around 32 bits) in the " + label(*holder, holderstart) + " at its byte " + std::to_string(lower > holderstart ? lower - holderstart : 0)); } else if (next != nullptr) { note("4 GiB lower (an index below zero wrapped around 32 bits?) " + bytes(nextstart - upper) + " before the " + label(*next, nextstart)); } } return (result.empty() ? std::string("in nothing known") : result); } // the device address of the memory of an image in a block of the allocator: a buffer bound to the same memory tells it, and goes. // 0 where that can't be (the memory type or the alignment doesn't take a buffer) VkDeviceAddress image_address(VmaAllocator const Allocator, VkDevice const Device, VmaAllocation const Allocation, VmaAllocationInfo const &Allocated) { VkBufferCreateInfo info{}; info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; info.size = Allocated.size; info.usage = VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT; info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; VkBuffer probe{VK_NULL_HANDLE}; if (vkCreateBuffer(Device, &info, nullptr, &probe) != VK_SUCCESS) { return 0; } VkDeviceAddress address{0}; VkMemoryRequirements requirements{}; vkGetBufferMemoryRequirements(Device, probe, &requirements); if (((requirements.memoryTypeBits & (1u << Allocated.memoryType)) != 0) && (requirements.alignment > 0) && ((Allocated.offset % requirements.alignment) == 0) && (requirements.size <= Allocated.size) && (vmaBindBufferMemory2(Allocator, Allocation, 0, probe, nullptr) == VK_SUCCESS)) { VkBufferDeviceAddressInfo addressinfo{}; addressinfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; addressinfo.buffer = probe; address = vkGetBufferDeviceAddress(Device, &addressinfo); } vkDestroyBuffer(Device, probe, nullptr); return address; } } // namespace address_registry // allocator allocator::~allocator() { destroy(); } bool allocator::create(instance const &Instance, device const &Device, std::string &Reason) { VmaAllocatorCreateInfo info{}; info.vulkanApiVersion = VK_API_VERSION_1_4; info.instance = Instance.handle(); info.physicalDevice = Device.physical(); info.device = Device.handle(); info.flags = VMA_ALLOCATOR_CREATE_KHR_MAINTENANCE5_BIT; if (Device.memory_budget_enabled()) { info.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT; } if (Device.raytracing_enabled()) { // the acceleration structures are built from the device addresses of the buffers info.flags |= VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; } // the addresses of the buffers and the images for the report of a lost device, where the driver tells where the gpu went wrong address_registry::enable(Device.fault_enabled() && Device.raytracing_enabled()); // the functions as volk loaded them VmaVulkanFunctions functions{}; if (!VK14_VERIFY(vmaImportVulkanFunctionsFromVolk(&info, &functions))) { Reason = "the functions of the memory allocator can't be loaded"; return false; } info.pVulkanFunctions = &functions; if (!VK14_VERIFY(vmaCreateAllocator(&info, &m_allocator))) { m_allocator = VK_NULL_HANDLE; Reason = "the memory allocator can't be made"; return false; } m_device = Device.handle(); VkPhysicalDeviceMemoryProperties memory{}; vkGetPhysicalDeviceMemoryProperties(Device.physical(), &memory); m_heapcount = memory.memoryHeapCount; m_deviceheaps.assign(m_heapcount, false); for (std::uint32_t i = 0; i < m_heapcount; ++i) { m_deviceheaps[i] = ((memory.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0); } return true; } void allocator::destroy() { if (m_allocator != VK_NULL_HANDLE) { vmaDestroyAllocator(m_allocator); m_allocator = VK_NULL_HANDLE; } m_device = VK_NULL_HANDLE; } void allocator::share_between(std::uint32_t const First, std::uint32_t const Second) { m_shared = {First, Second}; } VkSharingMode allocator::sharing(bool const Shared, std::uint32_t &Count, std::uint32_t const *&Families) const { Count = 0; Families = nullptr; if ((false == Shared) || (m_shared[0] == VK_QUEUE_FAMILY_IGNORED) || (m_shared[1] == VK_QUEUE_FAMILY_IGNORED) || (m_shared[0] == m_shared[1])) { return VK_SHARING_MODE_EXCLUSIVE; } Count = 2; Families = m_shared.data(); return VK_SHARING_MODE_CONCURRENT; } void allocator::budget(VkDeviceSize &Usage, VkDeviceSize &Budget) const { Usage = 0; Budget = 0; if (m_allocator == VK_NULL_HANDLE) { return; } std::vector budgets(m_heapcount); vmaGetHeapBudgets(m_allocator, budgets.data()); for (std::uint32_t i = 0; i < m_heapcount; ++i) { if (m_deviceheaps[i]) { Usage += budgets[i].usage; Budget += budgets[i].budget; } } } // buffer buffer::buffer(buffer &&Other) noexcept { *this = std::move(Other); } buffer &buffer::operator=(buffer &&Other) noexcept { if (this != &Other) { destroy(); std::swap(m_allocator, Other.m_allocator); std::swap(m_buffer, Other.m_buffer); std::swap(m_allocation, Other.m_allocation); std::swap(m_size, Other.m_size); std::swap(m_mapped, Other.m_mapped); std::swap(m_address, Other.m_address); } return *this; } buffer::~buffer() { destroy(); } bool buffer::create(allocator const &Allocator, VkDeviceSize Size, VkBufferUsageFlags Usage, memory_kind Kind, std::string const &Name, bool const Shared) { destroy(); VkBufferCreateInfo info{}; info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; info.size = std::max(Size, 4); info.usage = Usage; info.sharingMode = Allocator.sharing(Shared, info.queueFamilyIndexCount, info.pQueueFamilyIndices); VmaAllocationCreateInfo allocation{}; allocation.usage = VMA_MEMORY_USAGE_AUTO; switch (Kind) { case memory_kind::device: allocation.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; break; case memory_kind::upload: allocation.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; break; case memory_kind::readback: allocation.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; break; } VmaAllocationInfo allocated{}; auto const registered{address_registry::enabled()}; if (registered) { // every buffer has its address, so the report of a lost device can tell which one the gpu went wrong in info.usage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; } if (!VK14_VERIFY(vmaCreateBuffer(Allocator.handle(), &info, &allocation, &m_buffer, &m_allocation, &allocated))) { m_buffer = VK_NULL_HANDLE; m_allocation = VK_NULL_HANDLE; return false; } if (registered) { VkBufferDeviceAddressInfo addressinfo{}; addressinfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; addressinfo.buffer = m_buffer; m_address = vkGetBufferDeviceAddress(Allocator.device_handle(), &addressinfo); address_registry::add(handle_value(m_buffer), {m_address, allocated.deviceMemory, allocated.offset, info.size, Name.empty() ? std::string("unnamed") : Name, false, false, {}}); } m_allocator = Allocator.handle(); m_size = Size; m_mapped = static_cast(allocated.pMappedData); name_object(Allocator.device_handle(), VK_OBJECT_TYPE_BUFFER, handle_value(m_buffer), Name); return true; } void buffer::destroy() { if (m_buffer != VK_NULL_HANDLE) { if (address_registry::enabled()) { address_registry::remove(handle_value(m_buffer)); } vmaDestroyBuffer(m_allocator, m_buffer, m_allocation); } m_buffer = VK_NULL_HANDLE; m_allocation = VK_NULL_HANDLE; m_size = 0; m_mapped = nullptr; m_address = 0; } VkDeviceAddress buffer::address() const { if ((m_buffer == VK_NULL_HANDLE) || (m_address != 0)) { return m_address; } VmaAllocatorInfo allocator{}; vmaGetAllocatorInfo(m_allocator, &allocator); VkBufferDeviceAddressInfo info{}; info.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; info.buffer = m_buffer; m_address = vkGetBufferDeviceAddress(allocator.device, &info); return m_address; } void buffer::flush(VkDeviceSize Offset, VkDeviceSize Size) const { if ((m_allocation != VK_NULL_HANDLE) && (Size > 0)) { // vma does nothing for coherent memory vmaFlushAllocation(m_allocator, m_allocation, Offset, Size); } } void buffer::invalidate(VkDeviceSize Offset, VkDeviceSize Size) const { if ((m_allocation != VK_NULL_HANDLE) && (Size > 0)) { vmaInvalidateAllocation(m_allocator, m_allocation, Offset, Size); } } // image image::~image() { destroy(); } VkImageAspectFlags image::aspect() const { switch (m_desc.format) { case VK_FORMAT_D16_UNORM: case VK_FORMAT_D32_SFLOAT: case VK_FORMAT_X8_D24_UNORM_PACK32: return VK_IMAGE_ASPECT_DEPTH_BIT; case VK_FORMAT_D24_UNORM_S8_UINT: case VK_FORMAT_D32_SFLOAT_S8_UINT: case VK_FORMAT_D16_UNORM_S8_UINT: return VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; default: break; } return VK_IMAGE_ASPECT_COLOR_BIT; } bool image::create(allocator const &Allocator, image_desc const &Desc, std::string const &Name) { destroy(); m_desc = Desc; VkImageCreateInfo info{}; info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; info.flags = Desc.flags; info.imageType = Desc.type; info.format = Desc.format; info.extent = Desc.extent; info.mipLevels = std::max(Desc.mips, 1u); info.arrayLayers = std::max(Desc.layers, 1u); info.samples = Desc.samples; info.tiling = VK_IMAGE_TILING_OPTIMAL; info.usage = Desc.usage; info.sharingMode = Allocator.sharing(Desc.shared, info.queueFamilyIndexCount, info.pQueueFamilyIndices); info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; VmaAllocationCreateInfo allocation{}; allocation.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; VmaAllocationInfo allocated{}; if (!VK14_VERIFY(vmaCreateImage(Allocator.handle(), &info, &allocation, &m_image, &m_allocation, &allocated))) { m_image = VK_NULL_HANDLE; m_allocation = VK_NULL_HANDLE; return false; } m_allocator = Allocator.handle(); m_device = Allocator.device_handle(); m_bytes = allocated.size; if (address_registry::enabled()) { // an image in a block of the allocator (the memory has device addresses there) is placed exactly; one in memory of its own isn't VmaAllocationInfo2 extended{}; vmaGetAllocationInfo2(m_allocator, m_allocation, &extended); auto const dedicated{extended.dedicatedMemory == VK_TRUE}; auto const address{dedicated ? VkDeviceAddress{0} : address_registry::image_address(m_allocator, m_device, m_allocation, allocated)}; address_registry::add(handle_value(m_image), {address, allocated.deviceMemory, allocated.offset, allocated.size, Name.empty() ? std::string("unnamed") : Name, true, dedicated, {}}); } VkImageViewCreateInfo viewinfo{}; viewinfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewinfo.image = m_image; viewinfo.viewType = Desc.view_type; viewinfo.format = Desc.format; viewinfo.components = Desc.components; viewinfo.subresourceRange = {aspect(), 0, info.mipLevels, 0, info.arrayLayers}; if (!VK14_VERIFY(vkCreateImageView(m_device, &viewinfo, nullptr, &m_view))) { m_view = VK_NULL_HANDLE; destroy(); return false; } state = image_state{}; name_object(m_device, VK_OBJECT_TYPE_IMAGE, handle_value(m_image), Name); name_object(m_device, VK_OBJECT_TYPE_IMAGE_VIEW, handle_value(m_view), Name); return true; } void image::destroy() { if (m_view != VK_NULL_HANDLE) { vkDestroyImageView(m_device, m_view, nullptr); } if (m_image != VK_NULL_HANDLE) { if (address_registry::enabled()) { address_registry::remove(handle_value(m_image)); } vmaDestroyImage(m_allocator, m_image, m_allocation); } m_view = VK_NULL_HANDLE; m_image = VK_NULL_HANDLE; m_allocation = VK_NULL_HANDLE; m_bytes = 0; state = image_state{}; } void transition(command_stream *Commands, image &Image, VkImageLayout Layout, VkPipelineStageFlags2 Stage, VkAccessFlags2 Access) { auto const writes{VK_ACCESS_2_SHADER_WRITE_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT | VK_ACCESS_2_HOST_WRITE_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT}; auto const &from{Image.state}; // reads after reads in the same layout need nothing, unless they are in stages the barrier after the last write didn't wait // for: those wait for the stages which read the image already (which came after the write) if ((from.layout == Layout) && ((from.access & writes) == 0) && ((Access & writes) == 0)) { if ((Stage & ~from.stage) != 0) { VkMemoryBarrier2 chain{}; chain.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2; chain.srcStageMask = (from.stage != VK_PIPELINE_STAGE_2_NONE ? from.stage : VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT); chain.dstStageMask = Stage; chain.dstAccessMask = Access; VkDependencyInfo dependency{}; dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; dependency.memoryBarrierCount = 1; dependency.pMemoryBarriers = &chain; Commands->pipeline_barrier2(&dependency); } Image.state.stage |= Stage; Image.state.access |= Access; return; } VkImageMemoryBarrier2 barrier{}; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; barrier.srcStageMask = (from.stage != VK_PIPELINE_STAGE_2_NONE ? from.stage : VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT); barrier.srcAccessMask = (from.access & writes); barrier.dstStageMask = Stage; barrier.dstAccessMask = Access; barrier.oldLayout = from.layout; barrier.newLayout = Layout; barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.image = Image.handle(); barrier.subresourceRange = {Image.aspect(), 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS}; VkDependencyInfo dependency{}; dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; dependency.imageMemoryBarrierCount = 1; dependency.pImageMemoryBarriers = &barrier; Commands->pipeline_barrier2(&dependency); Image.state = image_state{Layout, Stage, Access}; } // deletion queue void deletion_queue::push(std::uint64_t Value, std::function &&Deleter) { std::lock_guard lock(m_mutex); m_entries.emplace_back(Value, std::move(Deleter)); } void deletion_queue::collect(std::uint64_t Completed) { // the values only grow, so the queue is in their order (an object let go by another thread while the frame is made takes the // value of the frame too). the deleters run outside of the lock std::vector> deleters; { std::lock_guard lock(m_mutex); while ((false == m_entries.empty()) && (m_entries.front().first <= Completed)) { deleters.emplace_back(std::move(m_entries.front().second)); m_entries.pop_front(); } } for (auto &deleter : deleters) { deleter(); } } void deletion_queue::flush() { std::deque>> entries; { std::lock_guard lock(m_mutex); entries.swap(m_entries); } for (auto &entry : entries) { entry.second(); } } // frame ring bool frame_ring::create(allocator const &Allocator, VkDeviceSize SizePerFrame, std::uint32_t Frames, VkBufferUsageFlags Usage, std::string const &Name, VkDeviceSize Tail, bool const Shared) { m_sizeperframe = SizePerFrame; m_base = 0; m_used = 0; return m_buffer.create(Allocator, SizePerFrame * Frames + Tail, Usage, memory_kind::upload, Name, Shared) && (m_buffer.mapped() != nullptr); } void frame_ring::destroy() { m_buffer.destroy(); m_sizeperframe = 0; m_used = 0; } void frame_ring::begin_frame(std::uint32_t Frame) { std::lock_guard lock(m_mutex); m_base = m_sizeperframe * Frame; m_used = 0; } ring_slice frame_ring::allocate(VkDeviceSize Size, VkDeviceSize Alignment) { std::lock_guard lock(m_mutex); auto const alignment{std::max(Alignment, 4)}; auto const offset{(m_used + alignment - 1) / alignment * alignment}; if ((m_buffer.mapped() == nullptr) || (offset + Size > m_sizeperframe)) { return {}; } m_used = offset + Size; ring_slice slice; slice.buffer = m_buffer.handle(); slice.offset = m_base + offset; slice.size = Size; slice.data = m_buffer.mapped() + slice.offset; return slice; } void frame_ring::flush() const { std::lock_guard lock(m_mutex); m_buffer.flush(m_base, m_used); } void ring_cache::reset(frame_ring *const Ring, VkDeviceSize const Part) { m_ring = Ring; m_partsize = Part; m_part = ring_slice{}; m_used = 0; } VkDeviceSize ring_cache::next(VkDeviceSize const Alignment) const { auto const alignment{std::max(Alignment, 4)}; return (m_part.offset + m_used + alignment - 1) / alignment * alignment; } ring_slice ring_cache::allocate_before(VkDeviceSize const Size, VkDeviceSize const Alignment, VkDeviceSize const Base, VkDeviceSize const Limit) { if (false == bool(m_part)) { return {}; } auto const offset{next(Alignment)}; if ((offset < Base) || (offset + Size > m_part.offset + m_part.size) || (offset + Size > Limit)) { return {}; } m_used = offset + Size - m_part.offset; ring_slice slice; slice.buffer = m_part.buffer; slice.offset = offset; slice.size = Size; slice.data = m_part.data + (offset - m_part.offset); return slice; } ring_slice ring_cache::allocate(VkDeviceSize const Size, VkDeviceSize const Alignment) { if (auto const slice{allocate_before(Size, Alignment, 0, std::numeric_limits::max())}) { return slice; } if (m_ring == nullptr) { return {}; } // the parts begin aligned for any of the slices auto const partalignment{std::max(Alignment, 256)}; if (Size > m_partsize / 2) { // a large one gets a part of its own, the part held is kept for the small ones return m_ring->allocate(Size, partalignment); } m_part = m_ring->allocate(m_partsize, partalignment); m_used = 0; if (false == bool(m_part)) { // the ring is nearly full: the rest of it in a part of the size of the slice m_part = m_ring->allocate(Size, partalignment); if (false == bool(m_part)) { return {}; } } return allocate_before(Size, Alignment, 0, std::numeric_limits::max()); } } // namespace vk14