1
0
mirror of https://github.com/doitsujin/dxvk.git synced 2025-01-31 14:52:11 +01:00

[dxvk] Create global buffer for allocated memory chunks

This commit is contained in:
Philip Rebohle 2024-09-13 11:54:23 +02:00
parent 11ec603540
commit 901861c20b
3 changed files with 236 additions and 25 deletions

View File

@ -12,6 +12,7 @@ namespace dxvk {
DxvkMemoryAllocator* alloc,
DxvkMemoryChunk* chunk,
DxvkMemoryType* type,
VkBuffer buffer,
VkDeviceMemory memory,
VkDeviceSize offset,
VkDeviceSize length,
@ -19,6 +20,7 @@ namespace dxvk {
: m_alloc (alloc),
m_chunk (chunk),
m_type (type),
m_buffer (buffer),
m_memory (memory),
m_offset (offset),
m_length (length),
@ -29,6 +31,7 @@ namespace dxvk {
: m_alloc (std::exchange(other.m_alloc, nullptr)),
m_chunk (std::exchange(other.m_chunk, nullptr)),
m_type (std::exchange(other.m_type, nullptr)),
m_buffer (std::exchange(other.m_buffer, VkBuffer(VK_NULL_HANDLE))),
m_memory (std::exchange(other.m_memory, VkDeviceMemory(VK_NULL_HANDLE))),
m_offset (std::exchange(other.m_offset, 0)),
m_length (std::exchange(other.m_length, 0)),
@ -40,6 +43,7 @@ namespace dxvk {
m_alloc = std::exchange(other.m_alloc, nullptr);
m_chunk = std::exchange(other.m_chunk, nullptr);
m_type = std::exchange(other.m_type, nullptr);
m_buffer = std::exchange(other.m_buffer, VkBuffer(VK_NULL_HANDLE));
m_memory = std::exchange(other.m_memory, VkDeviceMemory(VK_NULL_HANDLE));
m_offset = std::exchange(other.m_offset, 0);
m_length = std::exchange(other.m_length, 0);
@ -126,7 +130,7 @@ namespace dxvk {
// Create the memory object with the aligned slice
return DxvkMemory(m_alloc, this, m_type,
m_memory.memHandle, allocStart, allocEnd - allocStart,
m_memory.buffer, m_memory.memHandle, allocStart, allocEnd - allocStart,
reinterpret_cast<char*>(m_memory.memPointer) + allocStart);
}
@ -195,10 +199,13 @@ namespace dxvk {
m_memTypes[i].memType = m_memProps.memoryTypes[i];
m_memTypes[i].memTypeId = i;
m_memTypes[i].chunkSize = MinChunkSize;
m_memTypes[i].bufferUsage = 0;
}
if (device->features().core.features.sparseBinding)
m_sparseMemoryTypes = determineSparseMemoryTypes(device);
determineBufferUsageFlagsPerMemoryType();
}
@ -366,8 +373,10 @@ namespace dxvk {
DxvkDeviceMemory devMem = this->tryAllocDeviceMemory(type, size, info, hints);
if (devMem.memHandle != VK_NULL_HANDLE)
memory = DxvkMemory(this, nullptr, type, devMem.memHandle, 0, size, devMem.memPointer);
if (devMem.memHandle != VK_NULL_HANDLE) {
memory = DxvkMemory(this, nullptr, type,
devMem.buffer, devMem.memHandle, 0, size, devMem.memPointer);
}
}
if (memory) {
@ -396,11 +405,16 @@ namespace dxvk {
if (hints.test(DxvkMemoryFlag::GpuWritable))
priority = 1.0f;
bool dedicated = false;
DxvkDeviceMemory result;
result.memSize = size;
result.memFlags = info.flags;
result.priority = priority;
VkMemoryAllocateFlagsInfo memoryFlags = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO };
memoryFlags.flags |= VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT;
VkMemoryPriorityAllocateInfoEXT priorityInfo = { VK_STRUCTURE_TYPE_MEMORY_PRIORITY_ALLOCATE_INFO_EXT };
priorityInfo.priority = priority;
@ -420,6 +434,9 @@ namespace dxvk {
if (useMemoryPriority)
priorityInfo.pNext = std::exchange(memoryInfo.pNext, &priorityInfo);
if (!info.dedicated.image && (type->bufferUsage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT))
memoryFlags.pNext = std::exchange(memoryInfo.pNext, &memoryFlags);
if (vk->vkAllocateMemory(vk->device(), &memoryInfo, nullptr, &result.memHandle))
return DxvkDeviceMemory();
@ -433,6 +450,43 @@ namespace dxvk {
}
}
if (type->bufferUsage && !dedicated) {
VkBuffer buffer = VK_NULL_HANDLE;
VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufferInfo.size = result.memSize;
bufferInfo.usage = type->bufferUsage;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VkResult status = vk->vkCreateBuffer(vk->device(), &bufferInfo, nullptr, &buffer);
if (status == VK_SUCCESS) {
VkBufferMemoryRequirementsInfo2 memInfo = { VK_STRUCTURE_TYPE_BUFFER_MEMORY_REQUIREMENTS_INFO_2 };
memInfo.buffer = buffer;
VkMemoryRequirements2 requirements = { VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2 };
vk->vkGetBufferMemoryRequirements2(vk->device(), &memInfo, &requirements);
if ((requirements.memoryRequirements.size == size)
&& (requirements.memoryRequirements.memoryTypeBits & (1u << type->memTypeId))) {
status = vk->vkBindBufferMemory(vk->device(), buffer, result.memHandle, 0);
if (status == VK_SUCCESS)
result.buffer = buffer;
}
if (!result.buffer)
vk->vkDestroyBuffer(vk->device(), buffer, nullptr);
}
if (!result.buffer) {
Logger::warn(str::format("Failed to create global buffer:",
"\n size: ", std::dec, size,
"\n usage: ", std::hex, type->bufferUsage,
"\n type: ", std::dec, type->memTypeId));
}
}
type->heap->stats.memoryAllocated += size;
m_device->notifyMemoryAlloc(type->heapId, size);
return result;
@ -452,6 +506,7 @@ namespace dxvk {
memory.m_length);
} else {
DxvkDeviceMemory devMem;
devMem.buffer = memory.m_buffer;
devMem.memHandle = memory.m_memory;
devMem.memPointer = nullptr;
devMem.memSize = memory.m_length;
@ -487,6 +542,7 @@ namespace dxvk {
DxvkMemoryType* type,
DxvkDeviceMemory memory) {
auto vk = m_device->vkd();
vk->vkDestroyBuffer(vk->device(), memory.buffer, nullptr);
vk->vkFreeMemory(vk->device(), memory.memHandle, nullptr);
type->heap->stats.memoryAllocated -= memory.memSize;
@ -593,7 +649,7 @@ namespace dxvk {
DxvkDevice* device) const {
auto vk = device->vkd();
VkMemoryRequirements requirements = { };
VkMemoryRequirements2 requirements = { VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2 };
uint32_t typeMask = ~0u;
// Create sparse dummy buffer to find available memory types
@ -613,16 +669,8 @@ namespace dxvk {
| VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VkBuffer buffer = VK_NULL_HANDLE;
if (vk->vkCreateBuffer(vk->device(), &bufferInfo, nullptr, &buffer)) {
Logger::err("Failed to create dummy buffer to query sparse memory types");
return 0;
}
vk->vkGetBufferMemoryRequirements(vk->device(), buffer, &requirements);
vk->vkDestroyBuffer(vk->device(), buffer, nullptr);
typeMask &= requirements.memoryTypeBits;
if (getBufferMemoryRequirements(bufferInfo, requirements))
typeMask &= requirements.memoryRequirements.memoryTypeBits;
// Create sparse dummy image to find available memory types
VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
@ -643,16 +691,8 @@ namespace dxvk {
| VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VkImage image = VK_NULL_HANDLE;
if (vk->vkCreateImage(vk->device(), &imageInfo, nullptr, &image)) {
Logger::err("Failed to create dummy image to query sparse memory types");
return 0;
}
vk->vkGetImageMemoryRequirements(vk->device(), image, &requirements);
vk->vkDestroyImage(vk->device(), image, nullptr);
typeMask &= requirements.memoryTypeBits;
if (getImageMemoryRequirements(imageInfo, requirements))
typeMask &= requirements.memoryRequirements.memoryTypeBits;
Logger::log(typeMask ? LogLevel::Info : LogLevel::Error,
str::format("Memory type mask for sparse resources: 0x", std::hex, typeMask));
@ -660,6 +700,127 @@ namespace dxvk {
}
void DxvkMemoryAllocator::determineBufferUsageFlagsPerMemoryType() {
VkBufferUsageFlags flags = VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT
| VK_BUFFER_USAGE_VERTEX_BUFFER_BIT
| VK_BUFFER_USAGE_STORAGE_BUFFER_BIT
| VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT
| VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT;
// Lock storage texel buffer usage to maintenance5 support since we will
// otherwise not be able to legally use formats that support one type of
// texel buffer but not the other. Also lock index buffer usage since we
// cannot explicitly specify a buffer range otherwise.
if (m_device->features().khrMaintenance5.maintenance5) {
flags |= VK_BUFFER_USAGE_INDEX_BUFFER_BIT
| VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT;
}
if (m_device->features().extTransformFeedback.transformFeedback) {
flags |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT
| VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT;
}
if (m_device->features().vk12.bufferDeviceAddress)
flags |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
// Check which individual flags are supported on each memory type. This is a
// bit dodgy since the spec technically does not require a combination of flags
// to be supported, but we need to be robust around buffer creation anyway.
VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufferInfo.size = 65536;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VkMemoryRequirements2 requirements = { VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2 };
while (flags) {
VkBufferCreateFlags flag = flags & -flags;
bufferInfo.usage = flag
| VK_BUFFER_USAGE_TRANSFER_DST_BIT
| VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
if (getBufferMemoryRequirements(bufferInfo, requirements)) {
uint32_t typeMask = requirements.memoryRequirements.memoryTypeBits;
while (typeMask) {
uint32_t type = bit::tzcnt(typeMask);
if (type < m_memProps.memoryTypeCount)
m_memTypes.at(type).bufferUsage |= bufferInfo.usage;
typeMask &= typeMask - 1;
}
}
flags &= ~flag;
}
// Only use a minimal set of usage flags for the global buffer if the
// full combination of flags is not supported for whatever reason.
for (uint32_t i = 0; i < m_memProps.memoryTypeCount; i++) {
bufferInfo.usage = m_memTypes[i].bufferUsage;
if (!getBufferMemoryRequirements(bufferInfo, requirements)
|| !(requirements.memoryRequirements.memoryTypeBits & (1u << i))) {
m_memTypes[i].bufferUsage &= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT
| VK_BUFFER_USAGE_TRANSFER_DST_BIT
| VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
}
}
}
bool DxvkMemoryAllocator::getBufferMemoryRequirements(
const VkBufferCreateInfo& createInfo,
VkMemoryRequirements2& memoryRequirements) const {
auto vk = m_device->vkd();
if (m_device->features().vk13.maintenance4) {
VkDeviceBufferMemoryRequirements info = { VK_STRUCTURE_TYPE_DEVICE_BUFFER_MEMORY_REQUIREMENTS };
info.pCreateInfo = &createInfo;
vk->vkGetDeviceBufferMemoryRequirements(vk->device(), &info, &memoryRequirements);
return true;
} else {
VkBufferMemoryRequirementsInfo2 info = { VK_STRUCTURE_TYPE_BUFFER_MEMORY_REQUIREMENTS_INFO_2 };
VkResult vr = vk->vkCreateBuffer(vk->device(), &createInfo, nullptr, &info.buffer);
if (vr != VK_SUCCESS)
return false;
vk->vkGetBufferMemoryRequirements2(vk->device(), &info, &memoryRequirements);
vk->vkDestroyBuffer(vk->device(), info.buffer, nullptr);
return true;
}
}
bool DxvkMemoryAllocator::getImageMemoryRequirements(
const VkImageCreateInfo& createInfo,
VkMemoryRequirements2& memoryRequirements) const {
auto vk = m_device->vkd();
if (m_device->features().vk13.maintenance4) {
VkDeviceImageMemoryRequirements info = { VK_STRUCTURE_TYPE_DEVICE_IMAGE_MEMORY_REQUIREMENTS };
info.pCreateInfo = &createInfo;
vk->vkGetDeviceImageMemoryRequirements(vk->device(), &info, &memoryRequirements);
return true;
} else {
VkImageMemoryRequirementsInfo2 info = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2 };
VkResult vr = vk->vkCreateImage(vk->device(), &createInfo, nullptr, &info.image);
if (vr != VK_SUCCESS)
return false;
vk->vkGetImageMemoryRequirements2(vk->device(), &info, &memoryRequirements);
vk->vkDestroyImage(vk->device(), info.image, nullptr);
return true;
}
}
void DxvkMemoryAllocator::logMemoryError(const VkMemoryRequirements& req) const {
std::stringstream sstr;
sstr << "DxvkMemoryAllocator: Memory allocation failed" << std::endl

View File

@ -49,6 +49,7 @@ namespace dxvk {
* be persistently mapped.
*/
struct DxvkDeviceMemory {
VkBuffer buffer = VK_NULL_HANDLE;
VkDeviceMemory memHandle = VK_NULL_HANDLE;
void* memPointer = nullptr;
VkDeviceSize memSize = 0;
@ -84,6 +85,7 @@ namespace dxvk {
uint32_t memTypeId;
VkDeviceSize chunkSize;
VkBufferUsageFlags bufferUsage;
std::vector<Rc<DxvkMemoryChunk>> chunks;
};
@ -104,6 +106,7 @@ namespace dxvk {
DxvkMemoryAllocator* alloc,
DxvkMemoryChunk* chunk,
DxvkMemoryType* type,
VkBuffer buffer,
VkDeviceMemory memory,
VkDeviceSize offset,
VkDeviceSize length,
@ -123,6 +126,16 @@ namespace dxvk {
return m_memory;
}
/**
* \brief Buffer object
*
* Global buffer covering the entire memory allocation.
* \returns Buffer object
*/
VkBuffer buffer() const {
return m_buffer;
}
/**
* \brief Offset into device memory
*
@ -162,12 +175,21 @@ namespace dxvk {
explicit operator bool () const {
return m_memory != VK_NULL_HANDLE;
}
/**
* \brief Queries global buffer usage flags
* \returns Global buffer usage flags, if any
*/
VkBufferUsageFlags getBufferUsage() const {
return m_buffer ? m_type->bufferUsage : 0u;
}
private:
DxvkMemoryAllocator* m_alloc = nullptr;
DxvkMemoryChunk* m_chunk = nullptr;
DxvkMemoryType* m_type = nullptr;
VkBuffer m_buffer = VK_NULL_HANDLE;
VkDeviceMemory m_memory = VK_NULL_HANDLE;
VkDeviceSize m_offset = 0;
VkDeviceSize m_length = 0;
@ -355,6 +377,30 @@ namespace dxvk {
return m_memHeaps[heap].stats;
}
/**
* \brief Queries buffer memory requirements
*
* Can be used to get memory requirements without having
* to create a buffer object first.
* \param [in] createInfo Buffer create info
* \param [in,out] memoryRequirements Memory requirements
*/
bool getBufferMemoryRequirements(
const VkBufferCreateInfo& createInfo,
VkMemoryRequirements2& memoryRequirements) const;
/**
* \brief Queries image memory requirements
*
* Can be used to get memory requirements without having
* to create an image object first.
* \param [in] createInfo Image create info
* \param [in,out] memoryRequirements Memory requirements
*/
bool getImageMemoryRequirements(
const VkImageCreateInfo& createInfo,
VkMemoryRequirements2& memoryRequirements) const;
private:
DxvkDevice* m_device;
@ -421,6 +467,8 @@ namespace dxvk {
uint32_t determineSparseMemoryTypes(
DxvkDevice* device) const;
void determineBufferUsageFlagsPerMemoryType();
void logMemoryError(
const VkMemoryRequirements& req) const;

View File

@ -198,8 +198,10 @@ namespace dxvk::vk {
VULKAN_FN(vkBindImageMemory);
VULKAN_FN(vkGetBufferMemoryRequirements);
VULKAN_FN(vkGetBufferMemoryRequirements2);
VULKAN_FN(vkGetDeviceBufferMemoryRequirements);
VULKAN_FN(vkGetImageMemoryRequirements);
VULKAN_FN(vkGetImageMemoryRequirements2);
VULKAN_FN(vkGetDeviceImageMemoryRequirements);
VULKAN_FN(vkGetImageSparseMemoryRequirements);
VULKAN_FN(vkGetImageSparseMemoryRequirements2);
VULKAN_FN(vkQueueBindSparse);