CircularBuffer: Allow users to bind their own memory to our buffer.

This commit is contained in:
Dr. Chat 2016-08-01 16:30:28 -05:00
parent 22794902f3
commit 99090e0a22
5 changed files with 86 additions and 27 deletions

View File

@ -28,11 +28,17 @@ constexpr VkDeviceSize kConstantRegisterUniformRange =
BufferCache::BufferCache(RegisterFile* register_file, BufferCache::BufferCache(RegisterFile* register_file,
ui::vulkan::VulkanDevice* device, size_t capacity) ui::vulkan::VulkanDevice* device, size_t capacity)
: register_file_(register_file), device_(*device) { : register_file_(register_file), device_(*device) {
transient_buffer_ = std::make_unique<ui::vulkan::CircularBuffer>(device); transient_buffer_ = std::make_unique<ui::vulkan::CircularBuffer>(
if (!transient_buffer_->Initialize(capacity, device,
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT)) { capacity);
VkMemoryRequirements pool_reqs;
transient_buffer_->GetBufferMemoryRequirements(&pool_reqs);
gpu_memory_pool_ = device->AllocateMemory(pool_reqs);
if (!transient_buffer_->Initialize(gpu_memory_pool_, 0)) {
assert_always(); assert_always();
} }
@ -134,6 +140,10 @@ BufferCache::~BufferCache() {
vkDestroyDescriptorSetLayout(device_, descriptor_set_layout_, nullptr); vkDestroyDescriptorSetLayout(device_, descriptor_set_layout_, nullptr);
vkDestroyDescriptorPool(device_, descriptor_pool_, nullptr); vkDestroyDescriptorPool(device_, descriptor_pool_, nullptr);
transient_buffer_->Shutdown(); transient_buffer_->Shutdown();
if (gpu_memory_pool_) {
vkFreeMemory(device_, gpu_memory_pool_, nullptr);
}
} }
std::pair<VkDeviceSize, VkDeviceSize> BufferCache::UploadConstantRegisters( std::pair<VkDeviceSize, VkDeviceSize> BufferCache::UploadConstantRegisters(
@ -221,8 +231,6 @@ std::pair<VkDeviceSize, VkDeviceSize> BufferCache::UploadConstantRegisters(
std::pair<VkBuffer, VkDeviceSize> BufferCache::UploadIndexBuffer( std::pair<VkBuffer, VkDeviceSize> BufferCache::UploadIndexBuffer(
const void* source_ptr, size_t source_length, IndexFormat format, const void* source_ptr, size_t source_length, IndexFormat format,
std::shared_ptr<ui::vulkan::Fence> fence) { std::shared_ptr<ui::vulkan::Fence> fence) {
// TODO(benvanik): check cache.
// Allocate space in the buffer for our data. // Allocate space in the buffer for our data.
auto offset = AllocateTransientData(source_length, fence); auto offset = AllocateTransientData(source_length, fence);
if (offset == VK_WHOLE_SIZE) { if (offset == VK_WHOLE_SIZE) {
@ -249,8 +257,6 @@ std::pair<VkBuffer, VkDeviceSize> BufferCache::UploadIndexBuffer(
std::pair<VkBuffer, VkDeviceSize> BufferCache::UploadVertexBuffer( std::pair<VkBuffer, VkDeviceSize> BufferCache::UploadVertexBuffer(
const void* source_ptr, size_t source_length, Endian endian, const void* source_ptr, size_t source_length, Endian endian,
std::shared_ptr<ui::vulkan::Fence> fence) { std::shared_ptr<ui::vulkan::Fence> fence) {
// TODO(benvanik): check cache.
// Allocate space in the buffer for our data. // Allocate space in the buffer for our data.
auto offset = AllocateTransientData(source_length, fence); auto offset = AllocateTransientData(source_length, fence);
if (offset == VK_WHOLE_SIZE) { if (offset == VK_WHOLE_SIZE) {
@ -324,7 +330,7 @@ void BufferCache::InvalidateCache() {
} }
void BufferCache::ClearCache() { void BufferCache::ClearCache() {
// TODO(benvanik): caching. transient_cache_.clear();
} }
void BufferCache::Scavenge() { transient_buffer_->Scavenge(); } void BufferCache::Scavenge() { transient_buffer_->Scavenge(); }

View File

@ -17,6 +17,8 @@
#include "xenia/ui/vulkan/vulkan.h" #include "xenia/ui/vulkan/vulkan.h"
#include "xenia/ui/vulkan/vulkan_device.h" #include "xenia/ui/vulkan/vulkan_device.h"
#include <unordered_map>
namespace xe { namespace xe {
namespace gpu { namespace gpu {
namespace vulkan { namespace vulkan {
@ -101,9 +103,12 @@ class BufferCache {
RegisterFile* register_file_ = nullptr; RegisterFile* register_file_ = nullptr;
VkDevice device_ = nullptr; VkDevice device_ = nullptr;
VkDeviceMemory gpu_memory_pool_ = nullptr;
// Staging ringbuffer we cycle through fast. Used for data we don't // Staging ringbuffer we cycle through fast. Used for data we don't
// plan on keeping past the current frame. // plan on keeping past the current frame.
std::unique_ptr<ui::vulkan::CircularBuffer> transient_buffer_ = nullptr; std::unique_ptr<ui::vulkan::CircularBuffer> transient_buffer_ = nullptr;
std::unordered_map<uint64_t, VkDeviceSize> transient_cache_;
VkDescriptorPool descriptor_pool_ = nullptr; VkDescriptorPool descriptor_pool_ = nullptr;
VkDescriptorSetLayout descriptor_set_layout_ = nullptr; VkDescriptorSetLayout descriptor_set_layout_ = nullptr;

View File

@ -111,7 +111,8 @@ TextureCache::TextureCache(Memory* memory, RegisterFile* register_file,
register_file_(register_file), register_file_(register_file),
trace_writer_(trace_writer), trace_writer_(trace_writer),
device_(device), device_(device),
staging_buffer_(device) { staging_buffer_(device, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
kStagingBufferSize) {
// Descriptor pool used for all of our cached descriptors. // Descriptor pool used for all of our cached descriptors.
VkDescriptorPoolCreateInfo descriptor_pool_info; VkDescriptorPoolCreateInfo descriptor_pool_info;
descriptor_pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; descriptor_pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
@ -152,8 +153,7 @@ TextureCache::TextureCache(Memory* memory, RegisterFile* register_file,
nullptr, &texture_descriptor_set_layout_); nullptr, &texture_descriptor_set_layout_);
CheckResult(err, "vkCreateDescriptorSetLayout"); CheckResult(err, "vkCreateDescriptorSetLayout");
if (!staging_buffer_.Initialize(kStagingBufferSize, if (!staging_buffer_.Initialize()) {
VK_BUFFER_USAGE_TRANSFER_SRC_BIT)) {
assert_always(); assert_always();
} }

View File

@ -19,13 +19,10 @@ namespace xe {
namespace ui { namespace ui {
namespace vulkan { namespace vulkan {
CircularBuffer::CircularBuffer(VulkanDevice* device) : device_(device) {} CircularBuffer::CircularBuffer(VulkanDevice* device, VkBufferUsageFlags usage,
CircularBuffer::~CircularBuffer() { Shutdown(); } VkDeviceSize capacity, VkDeviceSize alignment)
: device_(device), capacity_(capacity) {
bool CircularBuffer::Initialize(VkDeviceSize capacity, VkBufferUsageFlags usage,
VkDeviceSize alignment) {
VkResult status = VK_SUCCESS; VkResult status = VK_SUCCESS;
capacity = xe::round_up(capacity, alignment);
// Create our internal buffer. // Create our internal buffer.
VkBufferCreateInfo buffer_info; VkBufferCreateInfo buffer_info;
@ -40,15 +37,52 @@ bool CircularBuffer::Initialize(VkDeviceSize capacity, VkBufferUsageFlags usage,
status = vkCreateBuffer(*device_, &buffer_info, nullptr, &gpu_buffer_); status = vkCreateBuffer(*device_, &buffer_info, nullptr, &gpu_buffer_);
CheckResult(status, "vkCreateBuffer"); CheckResult(status, "vkCreateBuffer");
if (status != VK_SUCCESS) { if (status != VK_SUCCESS) {
assert_always();
}
VkMemoryRequirements reqs;
vkGetBufferMemoryRequirements(*device_, gpu_buffer_, &reqs);
alignment_ = reqs.alignment;
}
CircularBuffer::~CircularBuffer() { Shutdown(); }
bool CircularBuffer::Initialize(VkDeviceMemory memory, VkDeviceSize offset) {
assert_true(offset % alignment_ == 0);
gpu_memory_ = memory;
gpu_base_ = offset;
VkResult status = VK_SUCCESS;
// Bind the buffer to its backing memory.
status = vkBindBufferMemory(*device_, gpu_buffer_, gpu_memory_, gpu_base_);
CheckResult(status, "vkBindBufferMemory");
if (status != VK_SUCCESS) {
XELOGE("CircularBuffer::Initialize - Failed to bind memory!");
Shutdown();
return false; return false;
} }
// Map the memory so we can access it.
status = vkMapMemory(*device_, gpu_memory_, gpu_base_, capacity_, 0,
reinterpret_cast<void**>(&host_base_));
CheckResult(status, "vkMapMemory");
if (status != VK_SUCCESS) {
XELOGE("CircularBuffer::Initialize - Failed to map memory!");
Shutdown();
return false;
}
return true;
}
bool CircularBuffer::Initialize() {
VkResult status = VK_SUCCESS;
VkMemoryRequirements reqs; VkMemoryRequirements reqs;
vkGetBufferMemoryRequirements(*device_, gpu_buffer_, &reqs); vkGetBufferMemoryRequirements(*device_, gpu_buffer_, &reqs);
// Allocate memory from the device to back the buffer. // Allocate memory from the device to back the buffer.
assert_true(reqs.size == capacity); owns_gpu_memory_ = true;
reqs.alignment = std::max(alignment, reqs.alignment);
gpu_memory_ = device_->AllocateMemory(reqs); gpu_memory_ = device_->AllocateMemory(reqs);
if (!gpu_memory_) { if (!gpu_memory_) {
XELOGE("CircularBuffer::Initialize - Failed to allocate memory!"); XELOGE("CircularBuffer::Initialize - Failed to allocate memory!");
@ -56,7 +90,6 @@ bool CircularBuffer::Initialize(VkDeviceSize capacity, VkBufferUsageFlags usage,
return false; return false;
} }
alignment_ = reqs.alignment;
capacity_ = reqs.size; capacity_ = reqs.size;
gpu_base_ = 0; gpu_base_ = 0;
@ -92,12 +125,16 @@ void CircularBuffer::Shutdown() {
vkDestroyBuffer(*device_, gpu_buffer_, nullptr); vkDestroyBuffer(*device_, gpu_buffer_, nullptr);
gpu_buffer_ = nullptr; gpu_buffer_ = nullptr;
} }
if (gpu_memory_) { if (gpu_memory_ && owns_gpu_memory_) {
vkFreeMemory(*device_, gpu_memory_, nullptr); vkFreeMemory(*device_, gpu_memory_, nullptr);
gpu_memory_ = nullptr; gpu_memory_ = nullptr;
} }
} }
void CircularBuffer::GetBufferMemoryRequirements(VkMemoryRequirements* reqs) {
vkGetBufferMemoryRequirements(*device_, gpu_buffer_, reqs);
}
bool CircularBuffer::CanAcquire(VkDeviceSize length) { bool CircularBuffer::CanAcquire(VkDeviceSize length) {
// Make sure the length is aligned. // Make sure the length is aligned.
length = xe::round_up(length, alignment_); length = xe::round_up(length, alignment_);
@ -166,7 +203,8 @@ CircularBuffer::Allocation* CircularBuffer::Acquire(
return alloc; return alloc;
} else if ((read_head_ - 0) >= aligned_length) { } else if ((read_head_ - 0) >= aligned_length) {
// Free space from begin -> read // Not enough space from write -> capacity, but there is enough free space
// from begin -> read
auto alloc = new Allocation(); auto alloc = new Allocation();
alloc->host_ptr = host_base_ + 0; alloc->host_ptr = host_base_ + 0;
alloc->gpu_memory = gpu_memory_; alloc->gpu_memory = gpu_memory_;
@ -220,6 +258,11 @@ void CircularBuffer::Scavenge() {
delete *it; delete *it;
it = allocations_.erase(it); it = allocations_.erase(it);
} }
if (allocations_.empty()) {
// Reset R/W heads to work around fragmentation issues.
read_head_ = write_head_ = 0;
}
} }
} // namespace vulkan } // namespace vulkan

View File

@ -27,7 +27,8 @@ namespace vulkan {
// ends of the buffer), where trailing older allocations are freed after use. // ends of the buffer), where trailing older allocations are freed after use.
class CircularBuffer { class CircularBuffer {
public: public:
CircularBuffer(VulkanDevice* device); CircularBuffer(VulkanDevice* device, VkBufferUsageFlags usage,
VkDeviceSize capacity, VkDeviceSize alignment = 256);
~CircularBuffer(); ~CircularBuffer();
struct Allocation { struct Allocation {
@ -42,10 +43,12 @@ class CircularBuffer {
std::shared_ptr<Fence> fence; std::shared_ptr<Fence> fence;
}; };
bool Initialize(VkDeviceSize capacity, VkBufferUsageFlags usage, bool Initialize(VkDeviceMemory memory, VkDeviceSize offset);
VkDeviceSize alignment = 256); bool Initialize();
void Shutdown(); void Shutdown();
void GetBufferMemoryRequirements(VkMemoryRequirements* reqs);
VkDeviceSize alignment() const { return alignment_; } VkDeviceSize alignment() const { return alignment_; }
VkDeviceSize capacity() const { return capacity_; } VkDeviceSize capacity() const { return capacity_; }
VkBuffer gpu_buffer() const { return gpu_buffer_; } VkBuffer gpu_buffer() const { return gpu_buffer_; }
@ -66,12 +69,14 @@ class CircularBuffer {
void Scavenge(); void Scavenge();
private: private:
// All of these variables are relative to gpu_base
VkDeviceSize capacity_ = 0; VkDeviceSize capacity_ = 0;
VkDeviceSize alignment_ = 0; VkDeviceSize alignment_ = 0;
VkDeviceSize write_head_ = 0; VkDeviceSize write_head_ = 0;
VkDeviceSize read_head_ = 0; VkDeviceSize read_head_ = 0;
VulkanDevice* device_; VulkanDevice* device_;
bool owns_gpu_memory_ = false;
VkBuffer gpu_buffer_ = nullptr; VkBuffer gpu_buffer_ = nullptr;
VkDeviceMemory gpu_memory_ = nullptr; VkDeviceMemory gpu_memory_ = nullptr;
VkDeviceSize gpu_base_ = 0; VkDeviceSize gpu_base_ = 0;