You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
376 lines
8.7 KiB
376 lines
8.7 KiB
// Copyright 2022 Simon Boyé
|
|
#include <vk/Memory.h>
|
|
#include <vk/Context.h>
|
|
|
|
#include <stdexcept>
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
|
|
|
|
namespace vk {
|
|
|
|
|
|
MemoryBlock::MemoryBlock() noexcept {
|
|
}
|
|
|
|
MemoryBlock::MemoryBlock(
|
|
VkDeviceMemory device_memory,
|
|
VkDeviceSize size,
|
|
VkDeviceSize offset,
|
|
MemoryPage* memory_page,
|
|
uint32_t memory_type
|
|
) noexcept
|
|
: m_size(size)
|
|
, m_offset(offset)
|
|
, m_device_memory(device_memory)
|
|
, m_memory_page(memory_page)
|
|
, m_memory_type(memory_type)
|
|
{}
|
|
|
|
MemoryBlock::MemoryBlock(MemoryBlock&& other) noexcept
|
|
: m_size(other.m_size)
|
|
, m_offset(other.m_offset)
|
|
, m_device_memory(other.m_device_memory)
|
|
, m_memory_page(other.m_memory_page)
|
|
, m_memory_type(other.m_memory_type)
|
|
{
|
|
other.m_size = 0;
|
|
other.m_offset = 0;
|
|
other.m_device_memory = VK_NULL_HANDLE;
|
|
other.m_memory_page = nullptr;
|
|
other.m_memory_type = 0;
|
|
}
|
|
|
|
MemoryBlock::~MemoryBlock() noexcept {
|
|
if(!is_null()) {
|
|
logger.warning() << "MemoryBlock deleted before being freed";
|
|
free();
|
|
}
|
|
}
|
|
|
|
MemoryBlock& MemoryBlock::operator=(MemoryBlock&& other) noexcept {
|
|
if(&other != this) {
|
|
using std::swap;
|
|
swap(m_size, other.m_size);
|
|
swap(m_offset, other.m_offset);
|
|
swap(m_device_memory, other.m_device_memory);
|
|
swap(m_memory_page, other.m_memory_page);
|
|
swap(m_memory_type, other.m_memory_type);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
|
|
VkMemoryType MemoryBlock::memory_type_info(Context& context) const noexcept {
|
|
assert(!is_null());
|
|
assert(context);
|
|
|
|
return context.memory_properties().memoryTypes[m_memory_type];
|
|
}
|
|
|
|
|
|
void MemoryBlock::free() noexcept {
|
|
assert(!is_null());
|
|
|
|
m_memory_page->p_free(*this);
|
|
|
|
m_size = 0;
|
|
m_offset = 0;
|
|
m_device_memory = VK_NULL_HANDLE;
|
|
m_memory_page = nullptr;
|
|
m_memory_type = 0;
|
|
}
|
|
|
|
|
|
void* MemoryBlock::map(Context& context) {
|
|
return map(context, 0, m_size);
|
|
}
|
|
|
|
void* MemoryBlock::map(Context& context, VkDeviceSize offset, VkDeviceSize size) {
|
|
assert(!is_null());
|
|
assert(context);
|
|
assert(offset + size <= m_size);
|
|
|
|
void* ptr;
|
|
if(vkMapMemory(
|
|
context.device(),
|
|
m_device_memory,
|
|
m_offset + offset,
|
|
size,
|
|
0,
|
|
&ptr
|
|
) != VK_SUCCESS)
|
|
throw std::runtime_error("failed to map memory");
|
|
|
|
return ptr;
|
|
}
|
|
|
|
void MemoryBlock::unmap(Context& context) noexcept {
|
|
assert(!is_null());
|
|
|
|
vkUnmapMemory(
|
|
context.device(),
|
|
m_device_memory
|
|
);
|
|
}
|
|
|
|
|
|
void MemoryBlock::flush(Context& context) {
|
|
assert(!is_null());
|
|
|
|
VkMappedMemoryRange range {
|
|
.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
|
|
.memory = m_device_memory,
|
|
.offset = m_offset,
|
|
.size = m_size,
|
|
};
|
|
if(vkFlushMappedMemoryRanges(
|
|
context.device(),
|
|
1,
|
|
&range
|
|
) != VK_SUCCESS)
|
|
std::runtime_error("failed to flush memory");
|
|
}
|
|
|
|
void MemoryBlock::invalidate(Context& context) {
|
|
assert(!is_null());
|
|
|
|
VkMappedMemoryRange range {
|
|
.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
|
|
.memory = m_device_memory,
|
|
.offset = m_offset,
|
|
.size = m_size,
|
|
};
|
|
if(vkInvalidateMappedMemoryRanges(
|
|
context.device(),
|
|
1,
|
|
&range
|
|
) != VK_SUCCESS)
|
|
std::runtime_error("failed to invalidate memory");
|
|
}
|
|
|
|
|
|
|
|
|
|
MemoryPage::MemoryPage(
|
|
VkDeviceSize size,
|
|
VkDeviceMemory device_memory,
|
|
uint32_t memory_type
|
|
) noexcept
|
|
: m_size(size)
|
|
, m_device_memory(device_memory)
|
|
, m_blocks{
|
|
Block{ 0, true },
|
|
Block{ m_size, false },
|
|
}
|
|
, m_memory_type(memory_type)
|
|
{}
|
|
|
|
MemoryPage::MemoryPage(MemoryPage&&) = default;
|
|
MemoryPage::~MemoryPage() = default;
|
|
|
|
MemoryPage& MemoryPage::operator=(MemoryPage&&) = default;
|
|
|
|
MemoryBlock MemoryPage::allocate(VkDeviceSize size) noexcept {
|
|
const auto block_it = find_free_block(size);
|
|
if(block_it == m_blocks.end())
|
|
return MemoryBlock();
|
|
|
|
block_it[0].is_free = false;
|
|
if (block_it[0].offset != block_it[1].offset) {
|
|
m_blocks.emplace(std::next(block_it), Block{
|
|
block_it[0].offset + size, true
|
|
});
|
|
}
|
|
|
|
return MemoryBlock(m_device_memory, size, block_it[0].offset, this, m_memory_type);
|
|
}
|
|
|
|
void MemoryPage::p_free(MemoryBlock& block) noexcept {
|
|
assert(block.device_memory() == m_device_memory);
|
|
|
|
const auto offset = block.offset();
|
|
const auto block_it = std::lower_bound(
|
|
m_blocks.begin(),
|
|
m_blocks.end(),
|
|
Block{ offset, false },
|
|
[] (const Block& lhs, const Block& rhs) {
|
|
return lhs.offset < rhs.offset;
|
|
}
|
|
);
|
|
assert(block_it != m_blocks.end() && block_it->offset == offset);
|
|
|
|
// Merge with next block if also free
|
|
if (block_it[1].is_free) {
|
|
m_blocks.erase(std::next(block_it));
|
|
}
|
|
|
|
// Merge with previous block if also free
|
|
if (block_it != m_blocks.begin() && block_it[-1].is_free) {
|
|
m_blocks.erase(block_it);
|
|
}
|
|
else {
|
|
block_it->is_free = true;
|
|
}
|
|
}
|
|
|
|
void MemoryPage::free_device_memory(Context& context) noexcept {
|
|
vkFreeMemory(
|
|
context.device(),
|
|
m_device_memory,
|
|
nullptr
|
|
);
|
|
|
|
m_size = 0;
|
|
m_device_memory = VK_NULL_HANDLE;
|
|
m_blocks.clear();
|
|
m_memory_type = 0;
|
|
}
|
|
|
|
|
|
MemoryPage::BlockList::iterator MemoryPage::find_free_block(VkDeviceSize size) {
|
|
const auto block_end = std::prev(m_blocks.end());
|
|
for(auto block_it = m_blocks.begin(); block_it != block_end; ++block_it) {
|
|
if(block_it[0].is_free && block_it[1].offset - block_it[0].offset >= size)
|
|
return block_it;
|
|
}
|
|
|
|
return m_blocks.end();
|
|
}
|
|
|
|
|
|
|
|
|
|
Allocator::Allocator(Context* context) noexcept
|
|
: m_context(context)
|
|
, m_memory_properties{}
|
|
, m_page_map{}
|
|
{
|
|
vkGetPhysicalDeviceMemoryProperties(
|
|
m_context->physical_device(),
|
|
&m_memory_properties
|
|
);
|
|
|
|
for(auto& next_page_size: m_next_page_sizes)
|
|
next_page_size = BasePageSize;
|
|
}
|
|
|
|
Allocator::~Allocator() = default;
|
|
|
|
|
|
int32_t Allocator::find_memory_type(
|
|
uint32_t type_mask,
|
|
VkMemoryPropertyFlags required_properties
|
|
) noexcept {
|
|
for(uint32_t type_index = 0;
|
|
type_index < m_memory_properties.memoryTypeCount;
|
|
type_index += 1
|
|
) {
|
|
if((type_mask & (1 << type_index)) == 0)
|
|
continue;
|
|
|
|
const auto memory_properties =
|
|
m_memory_properties.memoryTypes[type_index].propertyFlags;
|
|
if((memory_properties & required_properties) == required_properties)
|
|
return type_index;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int32_t Allocator::find_memory_type(
|
|
uint32_t type_mask,
|
|
std::initializer_list<VkMemoryPropertyFlags> properties_list
|
|
) noexcept {
|
|
for(const auto& properties: properties_list) {
|
|
const auto memory_type = find_memory_type(type_mask, properties);
|
|
if(memory_type >= 0)
|
|
return memory_type;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
MemoryBlock Allocator::allocate(
|
|
VkDeviceSize size,
|
|
uint32_t memory_type
|
|
) noexcept {
|
|
assert(memory_type < VK_MAX_MEMORY_TYPES);
|
|
|
|
auto& pages = m_page_map[memory_type];
|
|
|
|
for(auto& page: pages) {
|
|
auto block = page.allocate(size);
|
|
if(block)
|
|
return block;
|
|
}
|
|
|
|
const VkDeviceSize new_page_size = std::max(
|
|
size,
|
|
m_next_page_sizes[memory_type]
|
|
);
|
|
|
|
VkMemoryAllocateInfo allocate_info {
|
|
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
|
.allocationSize = new_page_size,
|
|
.memoryTypeIndex = memory_type,
|
|
};
|
|
VkDeviceMemory device_memory;
|
|
vkAllocateMemory(
|
|
m_context->device(),
|
|
&allocate_info,
|
|
nullptr,
|
|
&device_memory
|
|
);
|
|
|
|
pages.emplace_back(
|
|
new_page_size,
|
|
device_memory,
|
|
memory_type
|
|
);
|
|
|
|
if(new_page_size == m_next_page_sizes[memory_type])
|
|
m_next_page_sizes[memory_type] *= 2;
|
|
|
|
return pages.back().allocate(size);
|
|
}
|
|
|
|
MemoryBlock Allocator::allocate(
|
|
VkDeviceSize size,
|
|
uint32_t type_mask,
|
|
VkMemoryPropertyFlags properties
|
|
) noexcept {
|
|
const auto memory_type = find_memory_type(
|
|
type_mask,
|
|
properties
|
|
);
|
|
if(memory_type < 0)
|
|
return MemoryBlock();
|
|
|
|
return allocate(size, uint32_t(memory_type));
|
|
}
|
|
|
|
MemoryBlock Allocator::allocate(
|
|
VkDeviceSize size,
|
|
uint32_t type_mask,
|
|
std::initializer_list<VkMemoryPropertyFlags> properties_list
|
|
) noexcept {
|
|
const auto memory_type = find_memory_type(
|
|
type_mask,
|
|
properties_list
|
|
);
|
|
if(memory_type < 0)
|
|
return MemoryBlock();
|
|
|
|
return allocate(size, uint32_t(memory_type));
|
|
}
|
|
|
|
void Allocator::free_all_pages() noexcept {
|
|
for(uint32_t memory_type = 0; memory_type < VK_MAX_MEMORY_TYPES; memory_type += 1) {
|
|
for(auto& page: m_page_map[memory_type])
|
|
page.free_device_memory(*m_context);
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|