ppsspp/Common/Vulkan/VulkanMemory.h

226 lines
6.3 KiB
C
Raw Normal View History

#pragma once
#include <vector>
#include <unordered_map>
#include "Common/Vulkan/VulkanContext.h"
// VulkanMemory
//
// Vulkan memory management utils.
// VulkanPushBuffer
// Simple incrementing allocator.
// Use these to push vertex, index and uniform data. Generally you'll have two of these
// and alternate on each frame. Make sure not to reset until the fence from the last time you used it
// has completed.
//
// TODO: Make it possible to suballocate pushbuffers from a large DeviceMemory block.
class VulkanPushBuffer {
struct BufInfo {
VkBuffer buffer;
VkDeviceMemory deviceMemory;
};
public:
// NOTE: If you create a push buffer with only VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
// then you can't use any of the push functions as pointers will not be reachable from the CPU.
// You must in this case use Allocate() only, and pass the returned offset and the VkBuffer to Vulkan APIs.
VulkanPushBuffer(VulkanContext *vulkan, size_t size, VkBufferUsageFlags usage, VkMemoryPropertyFlags memoryPropertyMask = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
2017-05-07 11:08:09 +02:00
~VulkanPushBuffer();
2017-05-07 11:08:09 +02:00
void Destroy(VulkanContext *vulkan);
void Reset() { offset_ = 0; }
// Needs context in case of defragment.
void Begin(VulkanContext *vulkan) {
buf_ = 0;
offset_ = 0;
// Note: we must defrag because some buffers may be smaller than size_.
Defragment(vulkan);
if (memoryPropertyMask_ & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)
Map();
}
2017-08-17 11:22:23 +02:00
void BeginNoReset() {
if (memoryPropertyMask_ & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)
Map();
2017-08-17 11:22:23 +02:00
}
void End() {
if (memoryPropertyMask_ & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)
Unmap();
}
void Map();
void Unmap();
// When using the returned memory, make sure to bind the returned vkbuf.
// This will later allow for handling overflow correctly.
size_t Allocate(size_t numBytes, VkBuffer *vkbuf) {
size_t out = offset_;
offset_ += (numBytes + 3) & ~3; // Round up to 4 bytes.
if (offset_ >= size_) {
NextBuffer(numBytes);
out = offset_;
offset_ += (numBytes + 3) & ~3;
}
*vkbuf = buffers_[buf_].buffer;
return out;
}
// Returns the offset that should be used when binding this buffer to get this data.
size_t Push(const void *data, size_t size, VkBuffer *vkbuf) {
assert(writePtr_);
size_t off = Allocate(size, vkbuf);
memcpy(writePtr_ + off, data, size);
return off;
}
uint32_t PushAligned(const void *data, size_t size, int align, VkBuffer *vkbuf) {
assert(writePtr_);
offset_ = (offset_ + align - 1) & ~(align - 1);
size_t off = Allocate(size, vkbuf);
memcpy(writePtr_ + off, data, size);
return (uint32_t)off;
}
size_t GetOffset() const {
return offset_;
}
// "Zero-copy" variant - you can write the data directly as you compute it.
// Recommended.
void *Push(size_t size, uint32_t *bindOffset, VkBuffer *vkbuf) {
assert(writePtr_);
size_t off = Allocate(size, vkbuf);
*bindOffset = (uint32_t)off;
return writePtr_ + off;
}
void *PushAligned(size_t size, uint32_t *bindOffset, VkBuffer *vkbuf, int align) {
assert(writePtr_);
offset_ = (offset_ + align - 1) & ~(align - 1);
size_t off = Allocate(size, vkbuf);
*bindOffset = (uint32_t)off;
return writePtr_ + off;
}
2017-08-17 11:22:23 +02:00
size_t GetTotalSize() const;
private:
bool AddBuffer();
void NextBuffer(size_t minSize);
void Defragment(VulkanContext *vulkan);
VulkanContext *vulkan_;
VkMemoryPropertyFlags memoryPropertyMask_;
std::vector<BufInfo> buffers_;
size_t buf_ = 0;
size_t offset_ = 0;
size_t size_ = 0;
uint8_t *writePtr_ = nullptr;
VkBufferUsageFlags usage_;
};
// VulkanDeviceAllocator
//
// Implements a slab based allocator that manages suballocations inside the slabs.
// Bitmaps are used to handle allocation state, with a 1KB grain.
class VulkanDeviceAllocator {
public:
// Slab sizes start at minSlabSize and double until maxSlabSize.
// Total slab count is unlimited, as long as there's free memory.
VulkanDeviceAllocator(VulkanContext *vulkan, size_t minSlabSize, size_t maxSlabSize);
~VulkanDeviceAllocator();
// Requires all memory be free beforehand (including all pending deletes.)
void Destroy();
void Begin() {
Decimate();
}
void End() {
}
// May return ALLOCATE_FAILED if the allocation fails.
size_t Allocate(const VkMemoryRequirements &reqs, VkDeviceMemory *deviceMemory, const std::string &tag);
// Crashes on a double or misfree.
void Free(VkDeviceMemory deviceMemory, size_t offset);
inline void Touch(VkDeviceMemory deviceMemory, size_t offset) {
if (TRACK_TOUCH) {
DoTouch(deviceMemory, offset);
}
}
static const size_t ALLOCATE_FAILED = -1;
// Set to true to report potential leaks / long held textures.
static const bool TRACK_TOUCH = false;
int GetSlabCount() const { return (int)slabs_.size(); }
int GetMinSlabSize() const { return (int)minSlabSize_; }
int GetMaxSlabSize() const { return (int)maxSlabSize_; }
int ComputeUsagePercent() const;
std::vector<uint8_t> GetSlabUsage(int slab) const;
private:
static const size_t SLAB_GRAIN_SIZE = 1024;
static const uint8_t SLAB_GRAIN_SHIFT = 10;
static const uint32_t UNDEFINED_MEMORY_TYPE = -1;
struct UsageInfo {
std::string tag;
float created;
float touched;
};
struct Slab {
VkDeviceMemory deviceMemory;
uint32_t memoryTypeIndex = UNDEFINED_MEMORY_TYPE;
std::vector<uint8_t> usage;
std::unordered_map<size_t, size_t> allocSizes;
std::unordered_map<size_t, UsageInfo> tags;
size_t nextFree;
size_t totalUsage;
size_t Size() {
return usage.size() * SLAB_GRAIN_SIZE;
}
};
struct FreeInfo {
explicit FreeInfo(VulkanDeviceAllocator *a, VkDeviceMemory d, size_t o)
: allocator(a), deviceMemory(d), offset(o) {
}
VulkanDeviceAllocator *allocator;
VkDeviceMemory deviceMemory;
size_t offset;
};
static void DispatchFree(void *userdata) {
auto freeInfo = static_cast<FreeInfo *>(userdata);
freeInfo->allocator->ExecuteFree(freeInfo); // this deletes freeInfo
}
bool AllocateSlab(VkDeviceSize minBytes, int memoryTypeIndex);
bool AllocateFromSlab(Slab &slab, size_t &start, size_t blocks, const std::string &tag);
void Decimate();
void DoTouch(VkDeviceMemory deviceMemory, size_t offset);
void ExecuteFree(FreeInfo *userdata);
void ReportOldUsage();
VulkanContext *const vulkan_;
std::vector<Slab> slabs_;
size_t lastSlab_ = 0;
size_t minSlabSize_;
const size_t maxSlabSize_;
bool destroyed_ = false;
};