Skip to content

Commit

Permalink
Merge pull request #8649 from unknownbrackets/vulkan-buf
Browse files Browse the repository at this point in the history
Dynamically reallocate buffers when out of space
  • Loading branch information
hrydgard committed Mar 20, 2016
2 parents 1515754 + e3d9630 commit 2485c32
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 61 deletions.
78 changes: 64 additions & 14 deletions Common/Vulkan/VulkanMemory.cpp
Expand Up @@ -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_;

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;
}

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;
}

vulkan->MemoryTypeFromProperties(0xFFFFFFFF, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &alloc.memoryTypeIndex);
alloc.allocationSize = size;
// Okay, we have more than one. Destroy them all and start over with a larger one.
size_t newSize = size_ * buffers_.size();
Destroy(ctx_);

res = vkAllocateMemory(device, &alloc, nullptr, &deviceMemory_);
assert(VK_SUCCESS == res);
res = vkBindBufferMemory(device, buffer_, deviceMemory_, 0);
assert(VK_SUCCESS == res);
size_ = newSize;
bool res = AddBuffer();
assert(res);
}
65 changes: 45 additions & 20 deletions Common/Vulkan/VulkanMemory.h
@@ -1,5 +1,6 @@
#pragma once

#include <vector>
#include "Common/Vulkan/VulkanContext.h"

// VulkanMemory
Expand All @@ -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);
Expand All @@ -88,14 +107,20 @@ class VulkanPushBuffer {

// "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_;
Expand Down
47 changes: 20 additions & 27 deletions GPU/Vulkan/DrawEngineVulkan.cpp
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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] = {
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions GPU/Vulkan/DrawEngineVulkan.h
Expand Up @@ -144,8 +144,11 @@ class DrawEngineVulkan : public DrawEngineCommon {
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);

Expand Down

0 comments on commit 2485c32

Please sign in to comment.