diff --git a/rendering/vulkan14/vulkan14device.cpp b/rendering/vulkan14/vulkan14device.cpp index e91fb1ad..3fb29599 100644 --- a/rendering/vulkan14/vulkan14device.cpp +++ b/rendering/vulkan14/vulkan14device.cpp @@ -10,6 +10,7 @@ http://mozilla.org/MPL/2.0/. #include "stdafx.h" #include "rendering/vulkan14/vulkan14device.h" #include "rendering/vulkan14/vulkan14instance.h" +#include "rendering/vulkan14/vulkan14memory.h" #include "utilities/Globals.h" #include @@ -549,6 +550,14 @@ std::string device::fault_report() const { report += " (" + hex(lower) + " to " + hex(lower + precision - 1) + ")"; } + // what the memory there is, for the reads and writes (not the addresses of the code of the shaders) + if ((address.addressType == VK_DEVICE_FAULT_ADDRESS_TYPE_READ_INVALID_EXT) || (address.addressType == VK_DEVICE_FAULT_ADDRESS_TYPE_WRITE_INVALID_EXT)) + { + if (auto const holder{address_registry::describe(lower, lower + precision - 1)}; false == holder.empty()) + { + report += ": " + holder; + } + } } for (auto const &entry : vendor) { diff --git a/rendering/vulkan14/vulkan14device.h b/rendering/vulkan14/vulkan14device.h index f0068a00..fc92e3a4 100644 --- a/rendering/vulkan14/vulkan14device.h +++ b/rendering/vulkan14/vulkan14device.h @@ -102,6 +102,11 @@ class device // what the driver tells of the fault which lost the device (VK_EXT_device_fault, where the device has it): its description, the // addresses the gpu went wrong at and what the vendor adds. empty without the extension std::string fault_report() const; + // VK_EXT_device_fault is on + bool fault_enabled() const + { + return m_device_fault; + } private: static queue_families find_queue_families(VkInstance Instance, VkPhysicalDevice Physical, VkSurfaceKHR Surface, std::uint32_t &TimestampBits); diff --git a/rendering/vulkan14/vulkan14memory.cpp b/rendering/vulkan14/vulkan14memory.cpp index 9c14e8bf..6d48197a 100644 --- a/rendering/vulkan14/vulkan14memory.cpp +++ b/rendering/vulkan14/vulkan14memory.cpp @@ -13,9 +13,179 @@ http://mozilla.org/MPL/2.0/. #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}; + 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); +} + +void remove(std::uint64_t const Handle) +{ + std::lock_guard 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(); + } +} + +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)); + } + } + return (result.empty() ? std::string("in nothing known") : result); +} + +} // namespace address_registry + // allocator allocator::~allocator() @@ -40,6 +210,8 @@ bool allocator::create(instance const &Instance, device const &Device, std::stri // 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))) @@ -168,12 +340,26 @@ bool buffer::create(allocator const &Allocator, VkDeviceSize Size, VkBufferUsage } 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, {}}); + } m_allocator = Allocator.handle(); m_size = Size; m_mapped = static_cast(allocated.pMappedData); @@ -185,6 +371,10 @@ 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; @@ -282,6 +472,10 @@ bool image::create(allocator const &Allocator, image_desc const &Desc, std::stri m_allocator = Allocator.handle(); m_device = Allocator.device_handle(); m_bytes = allocated.size; + if (address_registry::enabled()) + { + address_registry::add(handle_value(m_image), {0, allocated.deviceMemory, allocated.offset, allocated.size, Name.empty() ? std::string("unnamed") : Name, true, {}}); + } VkImageViewCreateInfo viewinfo{}; viewinfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; @@ -310,6 +504,10 @@ void image::destroy() } 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; diff --git a/rendering/vulkan14/vulkan14memory.h b/rendering/vulkan14/vulkan14memory.h index 10a328e3..2f0172e9 100644 --- a/rendering/vulkan14/vulkan14memory.h +++ b/rendering/vulkan14/vulkan14memory.h @@ -62,6 +62,18 @@ class allocator std::vector m_deviceheaps; }; +// what the memory of the gpu holds by its device addresses, for the report of a lost device (device::fault_report()): the buffers, all +// made with device addresses while it's on, and the images, placed by the addresses of the buffers in the same blocks of memory (an +// image in a block of its own isn't placed). those let go not long ago are kept too. on with the fault info of the driver and the +// device addresses of the ray tracing +namespace address_registry +{ +void enable(bool Enabled); +bool enabled(); +// what holds the addresses Lower to Upper the gpu went wrong at, or what is next to them +std::string describe(VkDeviceAddress Lower, VkDeviceAddress Upper); +} // namespace address_registry + // where the memory of a buffer is enum class memory_kind {