|
|
@ -30,26 +30,6 @@ namespace Vulkan { |
|
|
|
|
|
|
|
|
// Helpers translating MemoryUsage to flags/usage
|
|
|
// Helpers translating MemoryUsage to flags/usage
|
|
|
|
|
|
|
|
|
[[maybe_unused]] VkMemoryPropertyFlags MemoryUsagePropertyFlags(MemoryUsage usage) { |
|
|
|
|
|
switch (usage) { |
|
|
|
|
|
case MemoryUsage::DeviceLocal: |
|
|
|
|
|
return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; |
|
|
|
|
|
case MemoryUsage::Upload: |
|
|
|
|
|
return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | |
|
|
|
|
|
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; |
|
|
|
|
|
case MemoryUsage::Download: |
|
|
|
|
|
return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | |
|
|
|
|
|
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | |
|
|
|
|
|
VK_MEMORY_PROPERTY_HOST_CACHED_BIT; |
|
|
|
|
|
case MemoryUsage::Stream: |
|
|
|
|
|
return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | |
|
|
|
|
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | |
|
|
|
|
|
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; |
|
|
|
|
|
} |
|
|
|
|
|
ASSERT_MSG(false, "Invalid memory usage={}", usage); |
|
|
|
|
|
return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
[[nodiscard]] VkMemoryPropertyFlags MemoryUsagePreferredVmaFlags(MemoryUsage usage) { |
|
|
[[nodiscard]] VkMemoryPropertyFlags MemoryUsagePreferredVmaFlags(MemoryUsage usage) { |
|
|
if (usage == MemoryUsage::Download) { |
|
|
if (usage == MemoryUsage::Download) { |
|
|
return VK_MEMORY_PROPERTY_HOST_CACHED_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; |
|
|
return VK_MEMORY_PROPERTY_HOST_CACHED_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; |
|
|
@ -86,125 +66,11 @@ namespace Vulkan { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// This avoids calling vkGetBufferMemoryRequirements* directly.
|
|
|
|
|
|
template<typename T> |
|
|
|
|
|
static VkBuffer GetVkHandleFromBuffer(const T &buf) { |
|
|
|
|
|
if constexpr (requires { static_cast<VkBuffer>(buf); }) { |
|
|
|
|
|
return static_cast<VkBuffer>(buf); |
|
|
|
|
|
} else if constexpr (requires {{ buf.GetHandle() } -> std::convertible_to<VkBuffer>; }) { |
|
|
|
|
|
return buf.GetHandle(); |
|
|
|
|
|
} else if constexpr (requires {{ buf.Handle() } -> std::convertible_to<VkBuffer>; }) { |
|
|
|
|
|
return buf.Handle(); |
|
|
|
|
|
} else if constexpr (requires {{ buf.vk_handle() } -> std::convertible_to<VkBuffer>; }) { |
|
|
|
|
|
return buf.vk_handle(); |
|
|
|
|
|
} else { |
|
|
|
|
|
static_assert(sizeof(T) == 0, "Cannot extract VkBuffer handle from vk::Buffer"); |
|
|
|
|
|
return VK_NULL_HANDLE; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
//MemoryCommit is now VMA-backed
|
|
|
|
|
|
MemoryCommit::MemoryCommit(VmaAllocator alloc, VmaAllocation a, |
|
|
|
|
|
const VmaAllocationInfo &info) noexcept |
|
|
|
|
|
: allocator{alloc}, allocation{a}, memory{info.deviceMemory}, |
|
|
|
|
|
offset{info.offset}, size{info.size}, mapped_ptr{info.pMappedData} { |
|
|
|
|
|
// Log GPU memory allocation
|
|
|
|
|
|
if (GPU::Logging::IsActive() && |
|
|
|
|
|
Settings::values.gpu_log_memory_tracking.GetValue()) { |
|
|
|
|
|
GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation( |
|
|
|
|
|
reinterpret_cast<uintptr_t>(memory), |
|
|
|
|
|
static_cast<u64>(size), |
|
|
|
|
|
0 // Memory property flags (not easily available from VMA)
|
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
MemoryCommit::~MemoryCommit() { Release(); } |
|
|
|
|
|
|
|
|
|
|
|
MemoryCommit::MemoryCommit(MemoryCommit &&rhs) noexcept |
|
|
|
|
|
: allocator{std::exchange(rhs.allocator, nullptr)}, |
|
|
|
|
|
allocation{std::exchange(rhs.allocation, nullptr)}, |
|
|
|
|
|
memory{std::exchange(rhs.memory, VK_NULL_HANDLE)}, |
|
|
|
|
|
offset{std::exchange(rhs.offset, 0)}, |
|
|
|
|
|
size{std::exchange(rhs.size, 0)}, |
|
|
|
|
|
mapped_ptr{std::exchange(rhs.mapped_ptr, nullptr)} {} |
|
|
|
|
|
|
|
|
|
|
|
MemoryCommit &MemoryCommit::operator=(MemoryCommit &&rhs) noexcept { |
|
|
|
|
|
if (this != &rhs) { |
|
|
|
|
|
Release(); |
|
|
|
|
|
allocator = std::exchange(rhs.allocator, nullptr); |
|
|
|
|
|
allocation = std::exchange(rhs.allocation, nullptr); |
|
|
|
|
|
memory = std::exchange(rhs.memory, VK_NULL_HANDLE); |
|
|
|
|
|
offset = std::exchange(rhs.offset, 0); |
|
|
|
|
|
size = std::exchange(rhs.size, 0); |
|
|
|
|
|
mapped_ptr = std::exchange(rhs.mapped_ptr, nullptr); |
|
|
|
|
|
} |
|
|
|
|
|
return *this; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
std::span<u8> MemoryCommit::Map() |
|
|
|
|
|
{ |
|
|
|
|
|
if (!allocation) return {}; |
|
|
|
|
|
if (!mapped_ptr) { |
|
|
|
|
|
if (vmaMapMemory(allocator, allocation, &mapped_ptr) != VK_SUCCESS) return {}; |
|
|
|
|
|
} |
|
|
|
|
|
const size_t n = static_cast<size_t>(std::min<VkDeviceSize>(size, |
|
|
|
|
|
(std::numeric_limits<size_t>::max)())); |
|
|
|
|
|
return std::span<u8>{static_cast<u8 *>(mapped_ptr), n}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
std::span<const u8> MemoryCommit::Map() const |
|
|
|
|
|
{ |
|
|
|
|
|
if (!allocation) return {}; |
|
|
|
|
|
if (!mapped_ptr) { |
|
|
|
|
|
void *p = nullptr; |
|
|
|
|
|
if (vmaMapMemory(allocator, allocation, &p) != VK_SUCCESS) return {}; |
|
|
|
|
|
const_cast<MemoryCommit *>(this)->mapped_ptr = p; |
|
|
|
|
|
} |
|
|
|
|
|
const size_t n = static_cast<size_t>(std::min<VkDeviceSize>(size, |
|
|
|
|
|
(std::numeric_limits<size_t>::max)())); |
|
|
|
|
|
return std::span<const u8>{static_cast<const u8 *>(mapped_ptr), n}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void MemoryCommit::Unmap() |
|
|
|
|
|
{ |
|
|
|
|
|
if (allocation && mapped_ptr) { |
|
|
|
|
|
vmaUnmapMemory(allocator, allocation); |
|
|
|
|
|
mapped_ptr = nullptr; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void MemoryCommit::Release() { |
|
|
|
|
|
if (allocation && allocator) { |
|
|
|
|
|
// Log GPU memory deallocation
|
|
|
|
|
|
if (GPU::Logging::IsActive() && |
|
|
|
|
|
Settings::values.gpu_log_memory_tracking.GetValue() && |
|
|
|
|
|
memory != VK_NULL_HANDLE) { |
|
|
|
|
|
GPU::Logging::GPULogger::GetInstance().LogMemoryDeallocation( |
|
|
|
|
|
reinterpret_cast<uintptr_t>(memory) |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (mapped_ptr) { |
|
|
|
|
|
vmaUnmapMemory(allocator, allocation); |
|
|
|
|
|
mapped_ptr = nullptr; |
|
|
|
|
|
} |
|
|
|
|
|
vmaFreeMemory(allocator, allocation); |
|
|
|
|
|
} |
|
|
|
|
|
allocation = nullptr; |
|
|
|
|
|
allocator = nullptr; |
|
|
|
|
|
memory = VK_NULL_HANDLE; |
|
|
|
|
|
offset = 0; |
|
|
|
|
|
size = 0; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
MemoryAllocator::MemoryAllocator(const Device &device_) |
|
|
MemoryAllocator::MemoryAllocator(const Device &device_) |
|
|
: device{device_}, allocator{device.GetAllocator()}, |
|
|
: device{device_}, allocator{device.GetAllocator()}, |
|
|
properties{device_.GetPhysical().GetMemoryProperties().memoryProperties}, |
|
|
|
|
|
buffer_image_granularity{ |
|
|
|
|
|
device_.GetPhysical().GetProperties().limits.bufferImageGranularity} { |
|
|
|
|
|
|
|
|
properties{device_.GetPhysical().GetMemoryProperties().memoryProperties} { |
|
|
|
|
|
|
|
|
// Preserve the previous "RenderDoc small heap" trimming behavior that we had in original vma minus the heap bug
|
|
|
// Preserve the previous "RenderDoc small heap" trimming behavior that we had in original vma minus the heap bug
|
|
|
if (device.HasDebuggingToolAttached()) |
|
|
if (device.HasDebuggingToolAttached()) |
|
|
@ -226,7 +92,7 @@ namespace Vulkan { |
|
|
|
|
|
|
|
|
void MemoryAllocator::SetReclaimCallback(ReclaimCallback callback) { |
|
|
void MemoryAllocator::SetReclaimCallback(ReclaimCallback callback) { |
|
|
reclaim_callback = std::move(callback); |
|
|
reclaim_callback = std::move(callback); |
|
|
owner_thread = std::this_thread::get_id(); |
|
|
|
|
|
|
|
|
vk::SetAllocatorOwnerThread(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
bool MemoryAllocator::ReclaimAtLeast(u64 hint_bytes) const { |
|
|
bool MemoryAllocator::ReclaimAtLeast(u64 hint_bytes) const { |
|
|
@ -239,12 +105,6 @@ namespace Vulkan { |
|
|
return freed > 0; |
|
|
return freed > 0; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void MemoryAllocator::AssertOwnerThread() const { |
|
|
|
|
|
DEBUG_ASSERT_MSG(owner_thread == std::thread::id{} || |
|
|
|
|
|
owner_thread == std::this_thread::get_id(), |
|
|
|
|
|
"VmaAllocator is externally synchronized but was used off-thread"); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo &ci) const |
|
|
vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo &ci) const |
|
|
{ |
|
|
{ |
|
|
const VmaAllocationCreateInfo alloc_ci = { |
|
|
const VmaAllocationCreateInfo alloc_ci = { |
|
|
@ -261,7 +121,7 @@ namespace Vulkan { |
|
|
VkImage handle{}; |
|
|
VkImage handle{}; |
|
|
VmaAllocation allocation{}; |
|
|
VmaAllocation allocation{}; |
|
|
VmaAllocationInfo alloc_info{}; |
|
|
VmaAllocationInfo alloc_info{}; |
|
|
AssertOwnerThread(); |
|
|
|
|
|
|
|
|
DEBUG_ASSERT(vk::OnAllocatorOwnerThread()); |
|
|
|
|
|
|
|
|
VkResult res = vmaCreateImage(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info); |
|
|
VkResult res = vmaCreateImage(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info); |
|
|
|
|
|
|
|
|
@ -317,7 +177,7 @@ namespace Vulkan { |
|
|
VmaAllocation allocation{}; |
|
|
VmaAllocation allocation{}; |
|
|
VkMemoryPropertyFlags property_flags{}; |
|
|
VkMemoryPropertyFlags property_flags{}; |
|
|
|
|
|
|
|
|
AssertOwnerThread(); |
|
|
|
|
|
|
|
|
DEBUG_ASSERT(vk::OnAllocatorOwnerThread()); |
|
|
|
|
|
|
|
|
VkResult res = vmaCreateBuffer(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info); |
|
|
VkResult res = vmaCreateBuffer(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info); |
|
|
|
|
|
|
|
|
@ -360,77 +220,4 @@ namespace Vulkan { |
|
|
device.GetDispatchLoader()); |
|
|
device.GetDispatchLoader()); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
MemoryCommit MemoryAllocator::Commit(const VkMemoryRequirements &reqs, MemoryUsage usage) |
|
|
|
|
|
{ |
|
|
|
|
|
const auto vma_usage = MemoryUsageVma(usage); |
|
|
|
|
|
VmaAllocationCreateInfo ci{}; |
|
|
|
|
|
ci.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage); |
|
|
|
|
|
ci.usage = vma_usage; |
|
|
|
|
|
ci.memoryTypeBits = reqs.memoryTypeBits & valid_memory_types; |
|
|
|
|
|
ci.requiredFlags = 0; |
|
|
|
|
|
ci.preferredFlags = MemoryUsagePreferredVmaFlags(usage); |
|
|
|
|
|
|
|
|
|
|
|
VmaAllocation a{}; |
|
|
|
|
|
VmaAllocationInfo info{}; |
|
|
|
|
|
|
|
|
|
|
|
VkResult res = vmaAllocateMemory(allocator, &reqs, &ci, &a, &info); |
|
|
|
|
|
|
|
|
|
|
|
if (res != VK_SUCCESS) { |
|
|
|
|
|
// Relax 1: drop budget constraint
|
|
|
|
|
|
auto ci2 = ci; |
|
|
|
|
|
ci2.flags &= ~VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT; |
|
|
|
|
|
res = vmaAllocateMemory(allocator, &reqs, &ci2, &a, &info); |
|
|
|
|
|
|
|
|
|
|
|
// Relax 2: if we preferred DEVICE_LOCAL, drop that preference
|
|
|
|
|
|
if (res != VK_SUCCESS && (ci.preferredFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) { |
|
|
|
|
|
auto ci3 = ci2; |
|
|
|
|
|
ci3.preferredFlags &= ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; |
|
|
|
|
|
res = vmaAllocateMemory(allocator, &reqs, &ci3, &a, &info); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
vk::Check(res); |
|
|
|
|
|
return MemoryCommit(allocator, a, info); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
MemoryCommit MemoryAllocator::Commit(const vk::Buffer &buffer, MemoryUsage usage) { |
|
|
|
|
|
// Allocate memory appropriate for this buffer automatically
|
|
|
|
|
|
const auto vma_usage = MemoryUsageVma(usage); |
|
|
|
|
|
|
|
|
|
|
|
VmaAllocationCreateInfo ci{}; |
|
|
|
|
|
ci.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage); |
|
|
|
|
|
ci.usage = vma_usage; |
|
|
|
|
|
ci.requiredFlags = 0; |
|
|
|
|
|
ci.preferredFlags = MemoryUsagePreferredVmaFlags(usage); |
|
|
|
|
|
ci.pool = VK_NULL_HANDLE; |
|
|
|
|
|
ci.pUserData = nullptr; |
|
|
|
|
|
ci.priority = 0.0f; |
|
|
|
|
|
|
|
|
|
|
|
const VkBuffer raw = *buffer; |
|
|
|
|
|
|
|
|
|
|
|
VmaAllocation a{}; |
|
|
|
|
|
VmaAllocationInfo info{}; |
|
|
|
|
|
|
|
|
|
|
|
// Let VMA infer memory requirements from the buffer
|
|
|
|
|
|
VkResult res = vmaAllocateMemoryForBuffer(allocator, raw, &ci, &a, &info); |
|
|
|
|
|
|
|
|
|
|
|
if (res != VK_SUCCESS) { |
|
|
|
|
|
auto ci2 = ci; |
|
|
|
|
|
ci2.flags &= ~VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT; |
|
|
|
|
|
res = vmaAllocateMemoryForBuffer(allocator, raw, &ci2, &a, &info); |
|
|
|
|
|
|
|
|
|
|
|
if (res != VK_SUCCESS && (ci.preferredFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) { |
|
|
|
|
|
auto ci3 = ci2; |
|
|
|
|
|
ci3.preferredFlags &= ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; |
|
|
|
|
|
res = vmaAllocateMemoryForBuffer(allocator, raw, &ci3, &a, &info); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
vk::Check(res); |
|
|
|
|
|
vk::Check(vmaBindBufferMemory2(allocator, a, 0, raw, nullptr)); |
|
|
|
|
|
return MemoryCommit(allocator, a, info); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace Vulkan
|
|
|
} // namespace Vulkan
|