Merge pull request #8649 from unknownbrackets/vulkan-buf

Dynamically reallocate buffers when out of space
This commit is contained in:
Henrik Rydgård 2016-03-20 23:32:08 +01:00
commit 2485c320f3
4 changed files with 132 additions and 61 deletions

View file

@ -20,29 +20,79 @@
#include "Common/Vulkan/VulkanMemory.h"
VulkanPushBuffer::VulkanPushBuffer(VulkanContext *vulkan, size_t size) : offset_(0), size_(size), writePtr_(nullptr), deviceMemory_(0) {
VkDevice device = vulkan->GetDevice();
VulkanPushBuffer::VulkanPushBuffer(VulkanContext *vulkan, size_t size) : ctx_(vulkan), buf_(0), offset_(0), size_(size), writePtr_(nullptr) {
bool res = AddBuffer();
assert(res);
}
bool VulkanPushBuffer::AddBuffer() {
VkDevice device = ctx_->GetDevice();
BufInfo info;
VkBufferCreateInfo b = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
b.size = size;
b.size = size_;
b.flags = 0;
b.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
b.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
b.queueFamilyIndexCount = 0;
b.pQueueFamilyIndices = nullptr;
VkResult res = vkCreateBuffer(device, &b, nullptr, &buffer_);
assert(VK_SUCCESS == res);
VkResult res = vkCreateBuffer(device, &b, nullptr, &info.buffer);
if (VK_SUCCESS != res) {
return false;
}
// Okay, that's the buffer. Now let's allocate some memory for it.
VkMemoryAllocateInfo alloc = {};
alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
alloc.pNext = nullptr;
VkMemoryAllocateInfo alloc = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };
ctx_->MemoryTypeFromProperties(0xFFFFFFFF, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &alloc.memoryTypeIndex);
alloc.allocationSize = size_;
vulkan->MemoryTypeFromProperties(0xFFFFFFFF, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &alloc.memoryTypeIndex);
alloc.allocationSize = size;
res = vkAllocateMemory(device, &alloc, nullptr, &info.deviceMemory);
if (VK_SUCCESS != res) {
return false;
}
res = vkBindBufferMemory(device, info.buffer, info.deviceMemory, 0);
if (VK_SUCCESS != res) {
return false;
}
res = vkAllocateMemory(device, &alloc, nullptr, &deviceMemory_);
assert(VK_SUCCESS == res);
res = vkBindBufferMemory(device, buffer_, deviceMemory_, 0);
assert(VK_SUCCESS == res);
buf_ = buffers_.size();
buffers_.resize(buf_ + 1);
buffers_[buf_] = info;
return true;
}
void VulkanPushBuffer::NextBuffer() {
VkDevice device = ctx_->GetDevice();
// First, unmap the current memory.
Unmap(device);
buf_++;
if (buf_ >= buffers_.size()) {
bool res = AddBuffer();
assert(res);
if (!res) {
// Let's try not to crash at least?
buf_ = 0;
}
}
// Now, move to the next buffer and map it.
offset_ = 0;
Map(device);
}
void VulkanPushBuffer::Defragment() {
if (buffers_.size() <= 1) {
return;
}
// Okay, we have more than one. Destroy them all and start over with a larger one.
size_t newSize = size_ * buffers_.size();
Destroy(ctx_);
size_ = newSize;
bool res = AddBuffer();
assert(res);
}

View file

@ -1,5 +1,6 @@
#pragma once
#include <vector>
#include "Common/Vulkan/VulkanContext.h"
// VulkanMemory
@ -13,69 +14,87 @@
// has completed.
//
// TODO: Make it possible to suballocate pushbuffers from a large DeviceMemory block.
// TODO: Make this auto-grow and shrink. Need to be careful about returning and using the new
// buffer handle on overflow.
class VulkanPushBuffer {
struct BufInfo {
VkBuffer buffer;
VkDeviceMemory deviceMemory;
};
public:
VulkanPushBuffer(VulkanContext *vulkan, size_t size);
~VulkanPushBuffer() {
assert(buffer_ == VK_NULL_HANDLE);
assert(deviceMemory_ == VK_NULL_HANDLE);
assert(buffers_.empty());
}
void Destroy(VulkanContext *vulkan) {
vulkan->Delete().QueueDeleteBuffer(buffer_);
vulkan->Delete().QueueDeleteDeviceMemory(deviceMemory_);
buffer_ = VK_NULL_HANDLE;
deviceMemory_ = VK_NULL_HANDLE;
for (const BufInfo &info : buffers_) {
vulkan->Delete().QueueDeleteBuffer(info.buffer);
vulkan->Delete().QueueDeleteDeviceMemory(info.deviceMemory);
}
buffers_.clear();
}
void Reset() { offset_ = 0; }
void Begin(VkDevice device) {
buf_ = 0;
offset_ = 0;
VkResult res = vkMapMemory(device, deviceMemory_, 0, size_, 0, (void **)(&writePtr_));
assert(VK_SUCCESS == res);
Defragment();
Map(device);
}
void End(VkDevice device) {
Unmap(device);
}
void Map(VkDevice device) {
assert(!writePtr_);
VkResult res = vkMapMemory(device, buffers_[buf_].deviceMemory, offset_, size_, 0, (void **)(&writePtr_));
assert(VK_SUCCESS == res);
}
void Unmap(VkDevice device) {
assert(writePtr_);
/*
VkMappedMemoryRange range = { VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE };
range.offset = 0;
range.size = offset_;
range.memory = deviceMemory_;
range.memory = buffers_[buf_].deviceMemory;
vkFlushMappedMemoryRanges(device, 1, &range);
*/
vkUnmapMemory(device, deviceMemory_);
vkUnmapMemory(device, buffers_[buf_].deviceMemory);
writePtr_ = nullptr;
}
// 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) {
assert(numBytes < size_);
size_t out = offset_;
offset_ += (numBytes + 3) & ~3; // Round up to 4 bytes.
if (offset_ >= size_) {
// TODO: Allocate a second buffer, then combine them on the next frame.
#ifdef _WIN32
DebugBreak();
#endif
NextBuffer();
out = offset_;
offset_ += (numBytes + 3) & ~3;
}
*vkbuf = buffer_;
*vkbuf = buffers_[buf_].buffer;
return out;
}
// TODO: Add alignment support?
// 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);
@ -88,14 +107,20 @@ public:
// "Zero-copy" variant - you can write the data directly as you compute it.
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;
}
private:
VkDeviceMemory deviceMemory_;
VkBuffer buffer_;
bool AddBuffer();
void NextBuffer();
void Defragment();
VulkanContext *ctx_;
std::vector<BufInfo> buffers_;
size_t buf_;
size_t offset_;
size_t size_;
uint8_t *writePtr_;

View file

@ -149,9 +149,9 @@ DrawEngineVulkan::DrawEngineVulkan(VulkanContext *vulkan)
for (int i = 0; i < 2; i++) {
VkResult res = vkCreateDescriptorPool(vulkan_->GetDevice(), &dp, nullptr, &frame_[i].descPool);
assert(VK_SUCCESS == res);
frame_[i].pushUBO = new VulkanPushBuffer(vulkan_, 16 * 1024 * 1024); // TODO: Do something more dynamic
frame_[i].pushVertex = new VulkanPushBuffer(vulkan_, 8 * 1024 * 1024); // TODO: Do something more dynamic
frame_[i].pushIndex = new VulkanPushBuffer(vulkan_, 2 * 1024 * 1024); // TODO: Do something more dynamic
frame_[i].pushUBO = new VulkanPushBuffer(vulkan_, 8 * 1024 * 1024);
frame_[i].pushVertex = new VulkanPushBuffer(vulkan_, 2 * 1024 * 1024);
frame_[i].pushIndex = new VulkanPushBuffer(vulkan_, 1 * 1024 * 1024);
}
VkPipelineLayoutCreateInfo pl;
@ -616,18 +616,7 @@ void DrawEngineVulkan::DoFlush(VkCommandBuffer cmd) {
}
vkCmdBindPipeline(cmd_, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->pipeline); // TODO: Avoid if same as last draw.
if ((dirtyUniforms_ & DIRTY_BASE_UNIFORMS) || baseBuf == VK_NULL_HANDLE) {
baseUBOOffset = shaderManager_->PushBaseBuffer(frame->pushUBO, &baseBuf);
dirtyUniforms_ &= ~DIRTY_BASE_UNIFORMS;
}
if ((dirtyUniforms_ & DIRTY_LIGHT_UNIFORMS) || lightBuf == VK_NULL_HANDLE) {
lightUBOOffset = shaderManager_->PushLightBuffer(frame->pushUBO, &lightBuf);
dirtyUniforms_ &= ~DIRTY_LIGHT_UNIFORMS;
}
if ((dirtyUniforms_ & DIRTY_BONE_UNIFORMS) || boneBuf == VK_NULL_HANDLE) {
boneUBOOffset = shaderManager_->PushBoneBuffer(frame->pushUBO, &boneBuf);
dirtyUniforms_ &= ~DIRTY_BONE_UNIFORMS;
}
UpdateUBOs(frame);
VkDescriptorSet ds = GetDescriptorSet(imageView, sampler, baseBuf, lightBuf, boneBuf);
@ -722,19 +711,8 @@ void DrawEngineVulkan::DoFlush(VkCommandBuffer cmd) {
}
vkCmdBindPipeline(cmd_, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->pipeline); // TODO: Avoid if same as last draw.
if ((dirtyUniforms_ & DIRTY_BASE_UNIFORMS) || baseBuf == VK_NULL_HANDLE) {
baseUBOOffset = shaderManager_->PushBaseBuffer(frame->pushUBO, &baseBuf);
dirtyUniforms_ &= ~DIRTY_BASE_UNIFORMS;
}
// Even if the first draw is through-mode, make sure we at least have one copy of these uniforms buffered
if ((dirtyUniforms_ & DIRTY_LIGHT_UNIFORMS) || lightBuf == VK_NULL_HANDLE) {
lightUBOOffset = shaderManager_->PushLightBuffer(frame->pushUBO, &lightBuf);
dirtyUniforms_ &= ~DIRTY_LIGHT_UNIFORMS;
}
if ((dirtyUniforms_ & DIRTY_BONE_UNIFORMS) || boneBuf == VK_NULL_HANDLE) {
boneUBOOffset = shaderManager_->PushBoneBuffer(frame->pushUBO, &boneBuf);
dirtyUniforms_ &= ~DIRTY_BONE_UNIFORMS;
}
UpdateUBOs(frame);
VkDescriptorSet ds = GetDescriptorSet(imageView, sampler, baseBuf, lightBuf, boneBuf);
const uint32_t dynamicUBOOffsets[3] = {
@ -827,6 +805,21 @@ void DrawEngineVulkan::DoFlush(VkCommandBuffer cmd) {
host->GPUNotifyDraw();
}
void DrawEngineVulkan::UpdateUBOs(FrameData *frame) {
if ((dirtyUniforms_ & DIRTY_BASE_UNIFORMS) || baseBuf == VK_NULL_HANDLE) {
baseUBOOffset = shaderManager_->PushBaseBuffer(frame->pushUBO, &baseBuf);
dirtyUniforms_ &= ~DIRTY_BASE_UNIFORMS;
}
if ((dirtyUniforms_ & DIRTY_LIGHT_UNIFORMS) || lightBuf == VK_NULL_HANDLE) {
lightUBOOffset = shaderManager_->PushLightBuffer(frame->pushUBO, &lightBuf);
dirtyUniforms_ &= ~DIRTY_LIGHT_UNIFORMS;
}
if ((dirtyUniforms_ & DIRTY_BONE_UNIFORMS) || boneBuf == VK_NULL_HANDLE) {
boneUBOOffset = shaderManager_->PushBoneBuffer(frame->pushUBO, &boneBuf);
dirtyUniforms_ &= ~DIRTY_BONE_UNIFORMS;
}
}
void DrawEngineVulkan::Resized() {
decJitCache_->Clear();
lastVTypeID_ = -1;

View file

@ -144,8 +144,11 @@ public:
void DirtyAllUBOs();
private:
struct FrameData;
void DecodeVerts(VulkanPushBuffer *push, uint32_t *bindOffset, VkBuffer *vkbuf);
void DoFlush(VkCommandBuffer cmd);
void UpdateUBOs(FrameData *frame);
VkDescriptorSet GetDescriptorSet(VkImageView imageView, VkSampler sampler, VkBuffer base, VkBuffer light, VkBuffer bone);