Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 72 additions & 12 deletions src/gpgmm/vk/ResourceAllocatorVk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@

#include "gpgmm/vk/ResourceAllocatorVk.h"

#include "gpgmm/common/BuddyMemoryAllocator.h"
#include "gpgmm/common/Defaults.h"
#include "gpgmm/common/EventMessage.h"
#include "gpgmm/common/PooledMemoryAllocator.h"
#include "gpgmm/common/SegmentedMemoryAllocator.h"
#include "gpgmm/common/SizeClass.h"
#include "gpgmm/common/SlabMemoryAllocator.h"
#include "gpgmm/vk/BackendVk.h"
#include "gpgmm/vk/CapsVk.h"
#include "gpgmm/vk/DeviceMemoryAllocatorVk.h"
Expand Down Expand Up @@ -57,6 +61,9 @@ namespace gpgmm::vk {

VkMemoryRequirements requirements = {};
allocator->GetBufferMemoryRequirements(buffer, &requirements);
if (requirements.size == 0) {
return VK_INCOMPLETE;
}

// Create memory for the buffer.
GpResourceAllocation allocation = VK_NULL_HANDLE;
Expand Down Expand Up @@ -164,6 +171,9 @@ namespace gpgmm::vk {
memoryTypeIndex++) {
mDeviceAllocatorsPerType.emplace_back(
CreateDeviceMemoryAllocator(info, memoryTypeIndex, kNoRequiredAlignment));

mResourceAllocatorsPerType.emplace_back(
CreateResourceSubAllocator(info, memoryTypeIndex, kNoRequiredAlignment));
}
}
}
Expand Down Expand Up @@ -213,27 +223,38 @@ namespace gpgmm::vk {
mVulkanFunctions.GetBufferMemoryRequirements(mDevice, buffer, requirementsOut);
}

VkResult GpResourceAllocator_T::TryAllocateMemory(
const VkMemoryRequirements& requirements,
const GpResourceAllocationCreateInfo& allocationInfo,
GpResourceAllocation* allocationOut) {
VkResult GpResourceAllocator_T::TryAllocateMemory(const VkMemoryRequirements& requirements,
const GpResourceAllocationCreateInfo& info,
GpResourceAllocation* allocationOut) {
uint32_t memoryTypeIndex;
ReturnIfFailed(
FindMemoryTypeIndex(requirements.memoryTypeBits, allocationInfo, &memoryTypeIndex));
ReturnIfFailed(FindMemoryTypeIndex(requirements.memoryTypeBits, info, &memoryTypeIndex));

MemoryAllocator* allocator = mDeviceAllocatorsPerType[memoryTypeIndex].get();
const bool neverSubAllocate = info.flags & GP_ALLOCATION_CREATE_NEVER_SUBALLOCATE_MEMORY;

MemoryAllocationRequest request = {};
request.SizeInBytes = requirements.size;
request.Alignment = requirements.alignment;
request.NeverAllocate = (allocationInfo.flags & GP_ALLOCATION_CREATE_NEVER_ALLOCATE_MEMORY);
request.NeverAllocate = (info.flags & GP_ALLOCATION_CREATE_NEVER_ALLOCATE_MEMORY);
request.AlwaysCacheSize = false;
request.AlwaysPrefetch =
(allocationInfo.flags & GP_ALLOCATION_CREATE_ALWAYS_PREFETCH_MEMORY);
request.AlwaysPrefetch = (info.flags & GP_ALLOCATION_CREATE_ALWAYS_PREFETCH_MEMORY);
request.AvailableForAllocation = kInvalidSize;

// Attempt to allocate using the most effective allocator.
MemoryAllocator* allocator = nullptr;

std::unique_ptr<MemoryAllocation> memoryAllocation;
if (!neverSubAllocate) {
allocator = mResourceAllocatorsPerType[memoryTypeIndex].get();
memoryAllocation = allocator->TryAllocateMemory(request);
}

if (memoryAllocation == nullptr) {
allocator = mDeviceAllocatorsPerType[memoryTypeIndex].get();
memoryAllocation = allocator->TryAllocateMemory(request);
}

std::unique_ptr<MemoryAllocation> memoryAllocation = allocator->TryAllocateMemory(request);
if (memoryAllocation == nullptr) {
InfoEvent("GpResourceAllocator.TryAllocateResource", EventMessageId::AllocatorFailed)
ErrorEvent("GpResourceAllocator.TryAllocateResource", EventMessageId::AllocatorFailed)
<< std::string(allocator->GetTypename()) +
" failed to allocate memory for resource.";

Expand Down Expand Up @@ -292,4 +313,43 @@ namespace gpgmm::vk {
return deviceMemoryAllocator;
}

std::unique_ptr<MemoryAllocator> GpResourceAllocator_T::CreateResourceSubAllocator(
const GpAllocatorCreateInfo& info,
uint64_t memoryTypeIndex,
uint64_t memoryAlignment) {
std::unique_ptr<MemoryAllocator> pooledOrNonPooledAllocator =
CreateDeviceMemoryAllocator(info, memoryTypeIndex, memoryAlignment);

// TODO: Figure out how to specify this using Vulkan API.
static constexpr uint64_t kMaxDeviceMemorySize = GPGMM_GB_TO_BYTES(32);

const uint64_t memoryGrowthFactor =
(info.MemoryGrowthFactor >= 1.0) ? info.MemoryGrowthFactor : kDefaultMemoryGrowthFactor;

switch (info.SubAllocationAlgorithm) {
case GP_ALLOCATOR_ALGORITHM_BUDDY_SYSTEM: {
return std::make_unique<BuddyMemoryAllocator>(
/*systemSize*/ kMaxDeviceMemorySize,
/*memorySize*/ std::max(memoryAlignment, info.preferredDeviceMemorySize),
/*memoryAlignment*/ memoryAlignment,
/*memoryAllocator*/ std::move(pooledOrNonPooledAllocator));
}
case GP_ALLOCATOR_ALGORITHM_SLAB: {
return std::make_unique<SlabCacheAllocator>(
/*maxSlabSize*/ kMaxDeviceMemorySize,
/*minSlabSize*/ std::max(memoryAlignment, info.preferredDeviceMemorySize),
/*slabAlignment*/ memoryAlignment,
/*slabFragmentationLimit*/ info.MemoryFragmentationLimit,
/*allowSlabPrefetch*/
!(info.flags & GP_ALLOCATOR_CREATE_DISABLE_MEMORY_PREFETCH),
/*slabGrowthFactor*/ memoryGrowthFactor,
/*memoryAllocator*/ std::move(pooledOrNonPooledAllocator));
}
default: {
UNREACHABLE();
return {};
}
}
}

} // namespace gpgmm::vk
55 changes: 55 additions & 0 deletions src/gpgmm/vk/ResourceAllocatorVk.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ namespace gpgmm::vk {
*/
GP_ALLOCATOR_CREATE_NONE = 0x0,

/** \brief Disables pre-fetching of GPU memory.

Should be only used for debugging and testing purposes.
*/
GP_ALLOCATOR_CREATE_DISABLE_MEMORY_PREFETCH = 0x4,

/** \brief Tell GPGMM to allocate exactly what is needed, and to de-allocate
memory immediately once no longer needed (instead of re-using it).

Expand Down Expand Up @@ -135,6 +141,15 @@ namespace gpgmm::vk {
*/
GpAllocatorCreateFlags flags;

/** \brief Specifies the algorithm to use for sub-allocation.

Used to evaluate how allocation implementations perform with various algorithms that
sub-divide devie memory.

Optional parameter. By default, the slab allocator is used.
*/
GpAllocatorAlgorithm SubAllocationAlgorithm = GP_ALLOCATOR_ALGORITHM_SLAB;

/** \brief Specifies the algorithm to use for device memory pooling.

Used to evaluate how allocation implementations perform with various algorithms that
Expand All @@ -153,6 +168,40 @@ namespace gpgmm::vk {
device memory size to be a multiple of minimum device memory size allowed by Vulkan.
*/
uint64_t preferredDeviceMemorySize;

/** \brief Memory fragmentation limit, expressed as a percentage of the device memory size,
that is acceptable to be wasted due to fragmentation.

Fragmentation occurs when the allocation is larger then the resource size.
This occurs when the type of resource (buffer or texture) and allocator have different
alignment requirements. For example, a 192KB resource may need to allocate 256KB of
allocated space, which is equivalent to a fragmentation limit of 33%.

When |preferredDeviceMemorySize| is non-zero, the MemoryFragmentationLimit could be
exceeded. Also, the MemoryFragmentationLimit should never be zero, as some fragmentation
can occur.

Optional parameter. When 0 is specified, the default fragmentation limit is 1/8th the
device memory size.
*/
double MemoryFragmentationLimit;

/** \brief Memory growth factor, expressed as a multipler of the device memory size
that will monotonically increase.

A factor value of 1.0 specifies no growth, where the device memory size is always determined
by other limits or constraints. If no factor gets specified (or a value less than 1 is
specified), GPGMM will allocate a device memory size with enough space to fit exactly one
resource.

Memory growth avoids the need to specify |preferredDeviceMemorySize|, which
especially helps in situations where the resource size cannot be predicated (eg.
user-defined), by allowing the device memory size to gradually increase in size
per demand to achieve a balance of memory usage and performance.

Optional parameter. When 0 is specified, the default of 1.25 is used (or 25% growth).
*/
double MemoryGrowthFactor;
};

/** \enum GpResourceAllocationCreateFlags
Expand Down Expand Up @@ -285,10 +334,16 @@ namespace gpgmm::vk {
uint64_t memoryTypeIndex,
uint64_t memoryAlignment);

std::unique_ptr<MemoryAllocator> CreateResourceSubAllocator(
const GpAllocatorCreateInfo& info,
uint64_t memoryTypeIndex,
uint64_t memoryAlignment);

VkDevice mDevice;
VulkanFunctions mVulkanFunctions;
std::unique_ptr<Caps> mCaps;

std::vector<std::unique_ptr<MemoryAllocator>> mResourceAllocatorsPerType;
std::vector<std::unique_ptr<MemoryAllocator>> mDeviceAllocatorsPerType;
std::vector<VkMemoryType> mMemoryTypes;
};
Expand Down
35 changes: 35 additions & 0 deletions src/tests/end2end/VKResourceAllocatorTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,38 @@ TEST_F(VKResourceAllocatorTests, CreateBuffer) {
gpDestroyBuffer(resourceAllocator, buffer, allocation);
gpDestroyResourceAllocator(resourceAllocator);
}

TEST_F(VKResourceAllocatorTests, CreateBufferManyDeallocateAtEnd) {
GpResourceAllocator resourceAllocator;
ASSERT_SUCCESS(gpCreateResourceAllocator(CreateBasicAllocatorInfo(), &resourceAllocator));

VkBufferCreateInfo bufferInfo = {};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;

GpResourceAllocationCreateInfo allocationInfo = {};

// TODO: Figure this value out
constexpr uint64_t kBufferMemoryAlignment = GPGMM_KB_TO_BYTES(64);

std::set<std::tuple<VkBuffer, GpResourceAllocation>> allocs = {};
for (auto& alloc : GPGMMTestBase::GenerateTestAllocations(kBufferMemoryAlignment)) {
VkBuffer buffer;
GpResourceAllocation allocation = VK_NULL_HANDLE;
bufferInfo.size = alloc.size;
EXPECT_EQ(gpCreateBuffer(resourceAllocator, &bufferInfo, &buffer, &allocationInfo,
&allocation) == VK_SUCCESS,
alloc.succeeds);
if (allocation == VK_NULL_HANDLE) {
continue;
}

ASSERT_NE(allocation, VK_NULL_HANDLE);
EXPECT_TRUE(allocs.insert(std::make_tuple(buffer, allocation)).second);
}

for (auto& alloc : allocs) {
gpDestroyBuffer(resourceAllocator, std::get<0>(alloc), std::get<1>(alloc));
}

gpDestroyResourceAllocator(resourceAllocator);
}