blob: bf428ec567792d1a07eca7c7162f1c83f5080afd [file] [log] [blame]
//
// Copyright 2022 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Suballocation.h:
// Defines class interface for BufferBlock and Suballocation and other related classes.
//
#ifndef LIBANGLE_RENDERER_VULKAN_SUBALLOCATION_H_
#define LIBANGLE_RENDERER_VULKAN_SUBALLOCATION_H_
#include "common/SimpleMutex.h"
#include "common/debug.h"
#include "libANGLE/angletypes.h"
#include "libANGLE/renderer/serial_utils.h"
#include "libANGLE/renderer/vulkan/vk_cache_utils.h"
#include "libANGLE/renderer/vulkan/vk_resource.h"
#include "libANGLE/renderer/vulkan/vk_utils.h"
#include "libANGLE/renderer/vulkan/vk_wrapper.h"
namespace rx
{
enum class MemoryAllocationType;
namespace vk
{
class ErrorContext;
// BufferBlock
class BufferBlock final : angle::NonCopyable
{
public:
BufferBlock();
BufferBlock(BufferBlock &&other);
~BufferBlock();
void destroy(Renderer *renderer);
VkResult init(ErrorContext *context,
Buffer &buffer,
uint32_t memoryTypeIndex,
vma::VirtualBlockCreateFlags flags,
DeviceMemory &deviceMemory,
VkMemoryPropertyFlags memoryPropertyFlags,
VkDeviceSize size);
void initWithoutVirtualBlock(ErrorContext *context,
Buffer &buffer,
MemoryAllocationType memoryAllocationType,
uint32_t memoryTypeIndex,
DeviceMemory &deviceMemory,
VkMemoryPropertyFlags memoryPropertyFlags,
VkDeviceSize size,
VkDeviceSize allocatedBufferSize);
BufferBlock &operator=(BufferBlock &&other);
const Buffer &getBuffer() const { return mBuffer; }
const DeviceMemory &getDeviceMemory() const { return mDeviceMemory; }
DeviceMemory &getDeviceMemory() { return mDeviceMemory; }
BufferSerial getBufferSerial() const { return mSerial; }
VkMemoryPropertyFlags getMemoryPropertyFlags() const;
VkDeviceSize getMemorySize() const;
VkDeviceSize getAllocatedBufferSize() const;
VkResult allocate(VkDeviceSize size,
VkDeviceSize alignment,
VmaVirtualAllocation *allocationOut,
VkDeviceSize *offsetOut);
void free(VmaVirtualAllocation allocation, VkDeviceSize offset);
VkBool32 isEmpty();
bool hasVirtualBlock() const { return mVirtualBlock.valid(); }
bool isHostVisible() const;
bool isCoherent() const;
bool isCached() const;
bool isMapped() const;
VkResult map(const VkDevice device);
void unmap(const VkDevice device);
uint8_t *getMappedMemory() const;
// This should be called whenever this found to be empty. The total number of count of empty is
// returned.
int32_t getAndIncrementEmptyCounter();
void calculateStats(vma::StatInfo *pStatInfo) const;
private:
mutable angle::SimpleMutex mVirtualBlockMutex;
VirtualBlock mVirtualBlock;
Buffer mBuffer;
DeviceMemory mDeviceMemory;
VkMemoryPropertyFlags mMemoryPropertyFlags;
// Memory size that user of this object thinks we have.
VkDeviceSize mSize;
// Memory size that was actually allocated for this object.
VkDeviceSize mAllocatedBufferSize;
// Memory allocation type used for this object.
MemoryAllocationType mMemoryAllocationType;
// Memory type index used for the allocation. It can be used to determine the heap index.
uint32_t mMemoryTypeIndex;
uint8_t *mMappedMemory;
BufferSerial mSerial;
// Heuristic information for pruneEmptyBuffer. This tracks how many times (consecutively) this
// buffer block is found to be empty when pruneEmptyBuffer is called. This gets reset whenever
// it becomes non-empty.
int32_t mCountRemainsEmpty;
};
using BufferBlockPointer = std::unique_ptr<BufferBlock>;
using BufferBlockPointerVector = std::vector<BufferBlockPointer>;
class BufferBlockGarbageList final : angle::NonCopyable
{
public:
BufferBlockGarbageList() : mBufferBlockQueue(kInitialQueueCapacity) {}
~BufferBlockGarbageList() { ASSERT(mBufferBlockQueue.empty()); }
void add(BufferBlock *bufferBlock)
{
std::unique_lock<angle::SimpleMutex> lock(mMutex);
if (mBufferBlockQueue.full())
{
size_t newCapacity = mBufferBlockQueue.capacity() << 1;
mBufferBlockQueue.updateCapacity(newCapacity);
}
mBufferBlockQueue.push(bufferBlock);
}
// Number of buffer blocks destroyed is returned.
size_t pruneEmptyBufferBlocks(Renderer *renderer)
{
size_t blocksDestroyed = 0;
if (!mBufferBlockQueue.empty())
{
std::unique_lock<angle::SimpleMutex> lock(mMutex);
size_t count = mBufferBlockQueue.size();
for (size_t i = 0; i < count; i++)
{
BufferBlock *block = mBufferBlockQueue.front();
mBufferBlockQueue.pop();
if (block->isEmpty())
{
block->destroy(renderer);
++blocksDestroyed;
}
else
{
mBufferBlockQueue.push(block);
}
}
}
return blocksDestroyed;
}
bool empty() const { return mBufferBlockQueue.empty(); }
private:
static constexpr size_t kInitialQueueCapacity = 4;
angle::SimpleMutex mMutex;
angle::FixedQueue<BufferBlock *> mBufferBlockQueue;
};
// BufferSuballocation
class BufferSuballocation final : angle::NonCopyable
{
public:
BufferSuballocation();
BufferSuballocation(BufferSuballocation &&other);
BufferSuballocation &operator=(BufferSuballocation &&other);
void destroy(Renderer *renderer);
void init(BufferBlock *block,
VmaVirtualAllocation allocation,
VkDeviceSize offset,
VkDeviceSize size);
void initWithEntireBuffer(ErrorContext *context,
Buffer &buffer,
MemoryAllocationType memoryAllocationType,
uint32_t memoryTypeIndex,
DeviceMemory &deviceMemory,
VkMemoryPropertyFlags memoryPropertyFlags,
VkDeviceSize size,
VkDeviceSize allocatedBufferSize);
const Buffer &getBuffer() const;
VkDeviceSize getSize() const;
const DeviceMemory &getDeviceMemory() const;
VkMemoryMapFlags getMemoryPropertyFlags() const;
bool isHostVisible() const;
bool isCoherent() const;
bool isCached() const;
bool isMapped() const;
uint8_t *getMappedMemory() const;
void flush(Renderer *renderer);
void invalidate(Renderer *renderer);
VkDeviceSize getOffset() const;
bool valid() const;
VkResult map(ErrorContext *context);
BufferSerial getBlockSerial() const;
uint8_t *getBlockMemory() const;
VkDeviceSize getBlockMemorySize() const;
bool isSuballocated() const { return mBufferBlock->hasVirtualBlock(); }
BufferBlock *getBufferBlock() const { return mBufferBlock; }
private:
// Only used by DynamicBuffer where DynamicBuffer does the actual suballocation and pass the
// offset/size to this object. Since DynamicBuffer does not have a VMA virtual allocator, they
// will be ignored at destroy time. The offset/size is set here mainly for easy retrieval when
// the BufferHelper object is passed around.
friend class BufferHelper;
void setOffsetAndSize(VkDeviceSize offset, VkDeviceSize size);
BufferBlock *mBufferBlock;
VmaVirtualAllocation mAllocation;
VkDeviceSize mOffset;
VkDeviceSize mSize;
};
class BufferSuballocationGarbage
{
public:
BufferSuballocationGarbage() = default;
BufferSuballocationGarbage(BufferSuballocationGarbage &&other)
: mLifetime(other.mLifetime),
mSuballocation(std::move(other.mSuballocation)),
mBuffer(std::move(other.mBuffer))
{}
BufferSuballocationGarbage &operator=(BufferSuballocationGarbage &&other)
{
mLifetime = other.mLifetime;
mSuballocation = std::move(other.mSuballocation);
mBuffer = std::move(other.mBuffer);
return *this;
}
BufferSuballocationGarbage(const ResourceUse &use,
BufferSuballocation &&suballocation,
Buffer &&buffer)
: mLifetime(use), mSuballocation(std::move(suballocation)), mBuffer(std::move(buffer))
{}
~BufferSuballocationGarbage() = default;
bool destroyIfComplete(Renderer *renderer);
bool hasResourceUseSubmitted(Renderer *renderer) const;
VkDeviceSize getSize() const { return mSuballocation.getSize(); }
bool isSuballocated() const { return mSuballocation.isSuballocated(); }
private:
ResourceUse mLifetime;
BufferSuballocation mSuballocation;
Buffer mBuffer;
};
// BufferBlock implementation.
ANGLE_INLINE VkMemoryPropertyFlags BufferBlock::getMemoryPropertyFlags() const
{
return mMemoryPropertyFlags;
}
ANGLE_INLINE VkDeviceSize BufferBlock::getMemorySize() const
{
return mSize;
}
ANGLE_INLINE VkDeviceSize BufferBlock::getAllocatedBufferSize() const
{
return mAllocatedBufferSize;
}
ANGLE_INLINE VkBool32 BufferBlock::isEmpty()
{
std::unique_lock<angle::SimpleMutex> lock(mVirtualBlockMutex);
return vma::IsVirtualBlockEmpty(mVirtualBlock.getHandle());
}
ANGLE_INLINE bool BufferBlock::isHostVisible() const
{
return (mMemoryPropertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0;
}
ANGLE_INLINE bool BufferBlock::isCoherent() const
{
return (mMemoryPropertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
}
ANGLE_INLINE bool BufferBlock::isCached() const
{
return (mMemoryPropertyFlags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) != 0;
}
ANGLE_INLINE bool BufferBlock::isMapped() const
{
return mMappedMemory != nullptr;
}
ANGLE_INLINE uint8_t *BufferBlock::getMappedMemory() const
{
ASSERT(mMappedMemory != nullptr);
return mMappedMemory;
}
// BufferSuballocation implementation.
ANGLE_INLINE BufferSuballocation::BufferSuballocation()
: mBufferBlock(nullptr), mAllocation(VK_NULL_HANDLE), mOffset(0), mSize(0)
{}
ANGLE_INLINE BufferSuballocation::BufferSuballocation(BufferSuballocation &&other)
: BufferSuballocation()
{
*this = std::move(other);
}
ANGLE_INLINE BufferSuballocation &BufferSuballocation::operator=(BufferSuballocation &&other)
{
std::swap(mBufferBlock, other.mBufferBlock);
std::swap(mSize, other.mSize);
std::swap(mAllocation, other.mAllocation);
std::swap(mOffset, other.mOffset);
return *this;
}
ANGLE_INLINE bool BufferSuballocation::valid() const
{
return mBufferBlock != nullptr;
}
ANGLE_INLINE void BufferSuballocation::destroy(Renderer *renderer)
{
if (valid())
{
ASSERT(mBufferBlock);
if (mBufferBlock->hasVirtualBlock())
{
mBufferBlock->free(mAllocation, mOffset);
mBufferBlock = nullptr;
}
else
{
// When virtual block is invalid, this is the standalone buffer that are created by
// BufferSuballocation::initWithEntireBuffer call. In this case, vmaBufferSuballocation
// owns block, we must properly delete the block object.
mBufferBlock->destroy(renderer);
SafeDelete(mBufferBlock);
}
mAllocation = VK_NULL_HANDLE;
mOffset = 0;
mSize = 0;
}
}
ANGLE_INLINE void BufferSuballocation::init(BufferBlock *block,
VmaVirtualAllocation allocation,
VkDeviceSize offset,
VkDeviceSize size)
{
ASSERT(!valid());
ASSERT(block != nullptr);
ASSERT(allocation != VK_NULL_HANDLE);
ASSERT(offset != VK_WHOLE_SIZE);
mBufferBlock = block;
mAllocation = allocation;
mOffset = offset;
mSize = size;
}
ANGLE_INLINE void BufferSuballocation::initWithEntireBuffer(
ErrorContext *context,
Buffer &buffer,
MemoryAllocationType memoryAllocationType,
uint32_t memoryTypeIndex,
DeviceMemory &deviceMemory,
VkMemoryPropertyFlags memoryPropertyFlags,
VkDeviceSize size,
VkDeviceSize allocatedBufferSize)
{
ASSERT(!valid());
std::unique_ptr<BufferBlock> block = std::make_unique<BufferBlock>();
block->initWithoutVirtualBlock(context, buffer, memoryAllocationType, memoryTypeIndex,
deviceMemory, memoryPropertyFlags, size, allocatedBufferSize);
mBufferBlock = block.release();
mAllocation = VK_NULL_HANDLE;
mOffset = 0;
mSize = mBufferBlock->getMemorySize();
}
ANGLE_INLINE const Buffer &BufferSuballocation::getBuffer() const
{
return mBufferBlock->getBuffer();
}
ANGLE_INLINE VkDeviceSize BufferSuballocation::getSize() const
{
return mSize;
}
ANGLE_INLINE const DeviceMemory &BufferSuballocation::getDeviceMemory() const
{
return mBufferBlock->getDeviceMemory();
}
ANGLE_INLINE VkMemoryMapFlags BufferSuballocation::getMemoryPropertyFlags() const
{
return mBufferBlock->getMemoryPropertyFlags();
}
ANGLE_INLINE bool BufferSuballocation::isHostVisible() const
{
return mBufferBlock->isHostVisible();
}
ANGLE_INLINE bool BufferSuballocation::isCoherent() const
{
return mBufferBlock->isCoherent();
}
ANGLE_INLINE bool BufferSuballocation::isCached() const
{
return mBufferBlock->isCached();
}
ANGLE_INLINE bool BufferSuballocation::isMapped() const
{
return mBufferBlock->isMapped();
}
ANGLE_INLINE uint8_t *BufferSuballocation::getMappedMemory() const
{
return mBufferBlock->getMappedMemory() + getOffset();
}
ANGLE_INLINE VkDeviceSize BufferSuballocation::getOffset() const
{
return mOffset;
}
ANGLE_INLINE void BufferSuballocation::setOffsetAndSize(VkDeviceSize offset, VkDeviceSize size)
{
mOffset = offset;
mSize = size;
}
ANGLE_INLINE uint8_t *BufferSuballocation::getBlockMemory() const
{
return mBufferBlock->getMappedMemory();
}
ANGLE_INLINE VkDeviceSize BufferSuballocation::getBlockMemorySize() const
{
return mBufferBlock->getMemorySize();
}
ANGLE_INLINE BufferSerial BufferSuballocation::getBlockSerial() const
{
ASSERT(valid());
return mBufferBlock->getBufferSerial();
}
} // namespace vk
} // namespace rx
#endif // LIBANGLE_RENDERER_VULKAN_SUBALLOCATION_H_