diff --git a/Common/Vulkan/VulkanMemory.cpp b/Common/Vulkan/VulkanMemory.cpp index 2daf1d5bfb..9e59126999 100644 --- a/Common/Vulkan/VulkanMemory.cpp +++ b/Common/Vulkan/VulkanMemory.cpp @@ -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); } diff --git a/Common/Vulkan/VulkanMemory.h b/Common/Vulkan/VulkanMemory.h index 8ea4cb08db..c7cfc37720 100644 --- a/Common/Vulkan/VulkanMemory.h +++ b/Common/Vulkan/VulkanMemory.h @@ -1,5 +1,6 @@ #pragma once +#include #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 buffers_; + size_t buf_; size_t offset_; size_t size_; uint8_t *writePtr_; diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index 31ddea0aef..6f878c879f 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -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; diff --git a/GPU/Vulkan/DrawEngineVulkan.h b/GPU/Vulkan/DrawEngineVulkan.h index 0ee65acd62..9a54647fd3 100644 --- a/GPU/Vulkan/DrawEngineVulkan.h +++ b/GPU/Vulkan/DrawEngineVulkan.h @@ -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);