From 82a902382ba30eea7b07cb98e5cdb4a766dfa0a4 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 20 Mar 2016 15:13:17 -0700 Subject: [PATCH 1/5] Vulkan: Centralize shader UBO update, DRY. --- GPU/Vulkan/DrawEngineVulkan.cpp | 41 ++++++++++++++------------------- GPU/Vulkan/DrawEngineVulkan.h | 3 +++ 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index 173bbfc38b47..8d774fb72d52 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -586,18 +586,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); @@ -692,19 +681,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] = { @@ -797,6 +775,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; diff --git a/GPU/Vulkan/DrawEngineVulkan.h b/GPU/Vulkan/DrawEngineVulkan.h index afe1a8d60600..9dbd3f0a1772 100644 --- a/GPU/Vulkan/DrawEngineVulkan.h +++ b/GPU/Vulkan/DrawEngineVulkan.h @@ -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); From f2513ffcd6a14f393b810fc87588fe6f6475abd0 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 20 Mar 2016 15:15:13 -0700 Subject: [PATCH 2/5] Vulkan: Allocate more buffers on overflow. --- Common/Vulkan/VulkanMemory.cpp | 58 ++++++++++++++++++++++++-------- Common/Vulkan/VulkanMemory.h | 61 ++++++++++++++++++++++++---------- 2 files changed, 87 insertions(+), 32 deletions(-) diff --git a/Common/Vulkan/VulkanMemory.cpp b/Common/Vulkan/VulkanMemory.cpp index 2daf1d5bfb47..1a37cae12797 100644 --- a/Common/Vulkan/VulkanMemory.cpp +++ b/Common/Vulkan/VulkanMemory.cpp @@ -20,29 +20,59 @@ #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(); + + buf_ = buffers_.size(); + buffers_.resize(buf_ + 1); 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, &buffers_[buf_].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, &buffers_[buf_].deviceMemory); + if (VK_SUCCESS != res) { + return false; + } + res = vkBindBufferMemory(device, buffers_[buf_].buffer, buffers_[buf_].deviceMemory, 0); + if (VK_SUCCESS != res) { + return false; + } + return true; +} + +void VulkanPushBuffer::NextBuffer() { + VkDevice device = ctx_->GetDevice(); + + // First, unmap the current memory. + Unmap(device); - vulkan->MemoryTypeFromProperties(0xFFFFFFFF, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &alloc.memoryTypeIndex); - alloc.allocationSize = size; + buf_++; + if (buf_ >= buffers_.size()) { + bool res = AddBuffer(); + assert(res); + } - res = vkAllocateMemory(device, &alloc, nullptr, &deviceMemory_); - assert(VK_SUCCESS == res); - res = vkBindBufferMemory(device, buffer_, deviceMemory_, 0); - assert(VK_SUCCESS == res); + // Now, move to the next buffer and map it. + offset_ = 0; + Map(device); } diff --git a/Common/Vulkan/VulkanMemory.h b/Common/Vulkan/VulkanMemory.h index 8ea4cb08db5f..72e767b3f6f8 100644 --- a/Common/Vulkan/VulkanMemory.h +++ b/Common/Vulkan/VulkanMemory.h @@ -1,5 +1,6 @@ #pragma once +#include #include "Common/Vulkan/VulkanContext.h" // VulkanMemory @@ -16,66 +17,85 @@ // 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); + 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 +108,19 @@ 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(); + + VulkanContext *ctx_; + std::vector buffers_; + size_t buf_; size_t offset_; size_t size_; uint8_t *writePtr_; From 9e4389cbf574b29f82452bf09f3bbb48a6c428ed Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 20 Mar 2016 15:20:21 -0700 Subject: [PATCH 3/5] Vulkan: Defrag buffers when growing. --- Common/Vulkan/VulkanMemory.cpp | 14 ++++++++++++++ Common/Vulkan/VulkanMemory.h | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Common/Vulkan/VulkanMemory.cpp b/Common/Vulkan/VulkanMemory.cpp index 1a37cae12797..129273821f9b 100644 --- a/Common/Vulkan/VulkanMemory.cpp +++ b/Common/Vulkan/VulkanMemory.cpp @@ -76,3 +76,17 @@ void VulkanPushBuffer::NextBuffer() { 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); +} diff --git a/Common/Vulkan/VulkanMemory.h b/Common/Vulkan/VulkanMemory.h index 72e767b3f6f8..c7cfc37720e7 100644 --- a/Common/Vulkan/VulkanMemory.h +++ b/Common/Vulkan/VulkanMemory.h @@ -14,8 +14,6 @@ // 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; @@ -43,6 +41,7 @@ class VulkanPushBuffer { void Begin(VkDevice device) { buf_ = 0; offset_ = 0; + Defragment(); Map(device); } @@ -117,6 +116,7 @@ class VulkanPushBuffer { private: bool AddBuffer(); void NextBuffer(); + void Defragment(); VulkanContext *ctx_; std::vector buffers_; From f91cc6191adbc0ea55dc8db6f1098d4ae2a0e253 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 20 Mar 2016 15:23:03 -0700 Subject: [PATCH 4/5] Vulkan: Start with smaller buffers by default. --- GPU/Vulkan/DrawEngineVulkan.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index 8d774fb72d52..c5a9e608fac1 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -148,9 +148,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; From e3d9630b9a4a22bd24081f933a53d0b4b15d0287 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 20 Mar 2016 15:25:59 -0700 Subject: [PATCH 5/5] Vulkan: Handle oom slightly better. --- Common/Vulkan/VulkanMemory.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Common/Vulkan/VulkanMemory.cpp b/Common/Vulkan/VulkanMemory.cpp index 129273821f9b..9e59126999f9 100644 --- a/Common/Vulkan/VulkanMemory.cpp +++ b/Common/Vulkan/VulkanMemory.cpp @@ -27,9 +27,7 @@ VulkanPushBuffer::VulkanPushBuffer(VulkanContext *vulkan, size_t size) : ctx_(vu bool VulkanPushBuffer::AddBuffer() { VkDevice device = ctx_->GetDevice(); - - buf_ = buffers_.size(); - buffers_.resize(buf_ + 1); + BufInfo info; VkBufferCreateInfo b = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; b.size = size_; @@ -39,7 +37,7 @@ bool VulkanPushBuffer::AddBuffer() { b.queueFamilyIndexCount = 0; b.pQueueFamilyIndices = nullptr; - VkResult res = vkCreateBuffer(device, &b, nullptr, &buffers_[buf_].buffer); + VkResult res = vkCreateBuffer(device, &b, nullptr, &info.buffer); if (VK_SUCCESS != res) { return false; } @@ -49,14 +47,18 @@ bool VulkanPushBuffer::AddBuffer() { 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, &buffers_[buf_].deviceMemory); + res = vkAllocateMemory(device, &alloc, nullptr, &info.deviceMemory); if (VK_SUCCESS != res) { return false; } - res = vkBindBufferMemory(device, buffers_[buf_].buffer, buffers_[buf_].deviceMemory, 0); + 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; } @@ -70,6 +72,10 @@ void VulkanPushBuffer::NextBuffer() { 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.