diff --git a/filament/backend/src/vulkan/VulkanBufferProxy.cpp b/filament/backend/src/vulkan/VulkanBufferProxy.cpp index 62d6d9cd24..e82d9a1db1 100644 --- a/filament/backend/src/vulkan/VulkanBufferProxy.cpp +++ b/filament/backend/src/vulkan/VulkanBufferProxy.cpp @@ -20,6 +20,7 @@ #include "VulkanCommands.h" #include "VulkanContext.h" #include "VulkanMemory.h" +#include "VulkanHandles.h" using namespace bluevk; @@ -33,22 +34,16 @@ VulkanBufferProxy::VulkanBufferProxy(VulkanContext const& context, VmaAllocator mStagePool(stagePool), mBufferCache(bufferCache), mBuffer(mBufferCache.acquire(usage, numBytes)), - mUpdatedOffset(0), - mUpdatedBytes(0) {} + mLastReadAge(0) {} void VulkanBufferProxy::loadFromCpu(VulkanCommandBuffer& commands, const void* cpuData, uint32_t byteOffset, uint32_t numBytes) { - // The VulkanBuffer is available if the only object reference is hold by the - // `VulkanBufferProxy`. This means that the buffer is not currently referenced by a - // `VulkanCommandBuffer`. - bool const isAvailable = mBuffer->getCount() == 1; - if (isAvailable) { - // We are up to date with all the update operations, so no need to synchronize with previous - // updates. - mUpdatedBytes = 0; - mUpdatedOffset = 0; - } + // This means that we're recording a write into a command buffer without a previous read, so it + // should be safe to + // 1) Do a direct memcpy in UMA mode + // 2) Skip adding a barrier (to protect the write from writing over a read). + bool const isAvailable = commands.age() != mLastReadAge; // Keep track of the VulkanBuffer usage commands.acquire(mBuffer); @@ -67,6 +62,10 @@ void VulkanBufferProxy::loadFromCpu(VulkanCommandBuffer& commands, const void* c vmaFlushAllocation(mAllocator, mBuffer->getGpuBuffer()->vmaAllocation, byteOffset, numBytes); return; + + // TODO: to properly bypass staging buffer, we'd need to be able to swap out a VulkanBuffer, + // which represents a VkBuffer. This means that the corresponding descriptor sets also have + // to be updated. } // Note: this should be stored within the command buffer before going out of @@ -77,10 +76,9 @@ void VulkanBufferProxy::loadFromCpu(VulkanCommandBuffer& commands, const void* c memcpy(stage->mapping(), cpuData, numBytes); vmaFlushAllocation(mAllocator, stage->memory(), stage->offset(), numBytes); - // If there was a previous update, then we need to make sure the following write is properly + // If there was a previous read, then we need to make sure the following write is properly // synced with the previous read. - if (mUpdatedBytes > 0 && - (byteOffset >= mUpdatedOffset && byteOffset <= (mUpdatedOffset + mUpdatedBytes))) { + if (!isAvailable) { VkAccessFlags srcAccess = 0; VkPipelineStageFlags srcStage = 0; if (getUsage() == VulkanBufferUsage::UNIFORM) { @@ -115,9 +113,6 @@ void VulkanBufferProxy::loadFromCpu(VulkanCommandBuffer& commands, const void* c }; vkCmdCopyBuffer(commands.buffer(), stage->buffer(), getVkBuffer(), 1, ®ion); - mUpdatedOffset = byteOffset; - mUpdatedBytes = numBytes; - // Firstly, ensure that the copy finishes before the next draw call. // Secondly, in case the user decides to upload another chunk (without ever using the first one) // we need to ensure that this upload completes first (hence @@ -161,4 +156,9 @@ VulkanBufferUsage VulkanBufferProxy::getUsage() const noexcept { return mBuffer->getGpuBuffer()->usage; } +void VulkanBufferProxy::referencedBy(VulkanCommandBuffer& commands) { + commands.acquire(mBuffer); + mLastReadAge = commands.age(); +} + } // namespace filament::backend diff --git a/filament/backend/src/vulkan/VulkanBufferProxy.h b/filament/backend/src/vulkan/VulkanBufferProxy.h index c2b7f2703e..a76634dde5 100644 --- a/filament/backend/src/vulkan/VulkanBufferProxy.h +++ b/filament/backend/src/vulkan/VulkanBufferProxy.h @@ -25,6 +25,9 @@ namespace filament::backend { +struct VulkanDescriptorSet; +struct VulkanCommandBuffer; + // This class acts as a dynamic wrapper for a `VulkanBuffer`. It allows you to modify the // `VulkanBuffer` it references at runtime, wihtout affecting any external objects. class VulkanBufferProxy { @@ -38,17 +41,19 @@ public: VkBuffer getVkBuffer() const noexcept; - VulkanBufferUsage getUsage() const noexcept; + void referencedBy(VulkanCommandBuffer& commands); private: + VulkanBufferUsage getUsage() const noexcept; + bool const mStagingBufferBypassEnabled; VmaAllocator mAllocator; VulkanStagePool& mStagePool; VulkanBufferCache& mBufferCache; fvkmemory::resource_ptr mBuffer; - uint32_t mUpdatedOffset = 0; - uint32_t mUpdatedBytes = 0; + + uint32_t mLastReadAge; }; } // namespace filament::backend diff --git a/filament/backend/src/vulkan/VulkanCommands.cpp b/filament/backend/src/vulkan/VulkanCommands.cpp index 9feb8b3f5c..825f9e4bfe 100644 --- a/filament/backend/src/vulkan/VulkanCommands.cpp +++ b/filament/backend/src/vulkan/VulkanCommands.cpp @@ -86,6 +86,8 @@ bool VulkanGroupMarkers::empty() const noexcept { } #endif // FVK_DEBUG_GROUP_MARKERS +uint32_t VulkanCommandBuffer::sAgeCounter = 0; + VulkanCommandBuffer::VulkanCommandBuffer(VulkanContext const& context, VkDevice device, VkQueue queue, VkCommandPool pool, bool isProtected) : mContext(context), @@ -94,7 +96,8 @@ VulkanCommandBuffer::VulkanCommandBuffer(VulkanContext const& context, VkDevice mDevice(device), mQueue(queue), mBuffer(createCommandBuffer(device, pool)), - mFenceStatus(std::make_shared(VK_INCOMPLETE)) { + mFenceStatus(std::make_shared(VK_INCOMPLETE)), + mAge(++sAgeCounter) { VkSemaphoreCreateInfo sci{.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO}; vkCreateSemaphore(mDevice, &sci, VKALLOC, &mSubmission); @@ -111,6 +114,7 @@ void VulkanCommandBuffer::reset() noexcept { mMarkerCount = 0; mResources.clear(); mWaitSemaphores.clear(); + mAge = ++sAgeCounter; // Internally we use the VK_INCOMPLETE status to mean "not yet submitted". When this fence // gets, gets submitted, its status changes to VK_NOT_READY. Finally, when the GPU actually diff --git a/filament/backend/src/vulkan/VulkanCommands.h b/filament/backend/src/vulkan/VulkanCommands.h index ba1e86cc7c..b655dfb347 100644 --- a/filament/backend/src/vulkan/VulkanCommands.h +++ b/filament/backend/src/vulkan/VulkanCommands.h @@ -108,7 +108,13 @@ struct VulkanCommandBuffer { return mBuffer; } + uint32_t age() const { + return mAge; + } + private: + static uint32_t sAgeCounter; + VulkanContext const& mContext; uint8_t mMarkerCount; bool const isProtected; @@ -120,6 +126,7 @@ private: VkFence mFence; std::shared_ptr mFenceStatus; std::vector> mResources; + uint32_t mAge; }; struct CommandBufferPool { diff --git a/filament/backend/src/vulkan/VulkanDescriptorSetCache.cpp b/filament/backend/src/vulkan/VulkanDescriptorSetCache.cpp index a90f09aaf6..861272e639 100644 --- a/filament/backend/src/vulkan/VulkanDescriptorSetCache.cpp +++ b/filament/backend/src/vulkan/VulkanDescriptorSetCache.cpp @@ -290,7 +290,9 @@ void VulkanDescriptorSetCache::commit(VulkanCommandBuffer* commands, if (mLastBoundInfo.pipelineLayout == pipelineLayout) { auto& lastBoundSets = mLastBoundInfo.boundSets; curMask.forEachSetBit([&](size_t index) { - if (updateSets[index] == lastBoundSets[index] && !useExternalSamplers[index]) { + auto& set = updateSets[index]; + if (set == lastBoundSets[index] && !useExternalSamplers[index] && + set->uniqueDynamicUboCount == 0) { curMask.unset(index); } }); @@ -302,25 +304,24 @@ void VulkanDescriptorSetCache::commit(VulkanCommandBuffer* commands, VkCommandBuffer const cmdbuffer = commands->buffer(); VkDescriptorSet vkset = useExternalSamplers[index] ? set->getExternalSamplerVkSet() : set->getVkSet(); + commands->acquire(set); vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, index, 1, &vkset, set->uniqueDynamicUboCount, set->getOffsets()->data()); - commands->acquire(set); + set->referencedBy(*commands); }); - - mStashedSets = {}; - mLastBoundInfo = { pipelineLayout, setMask, updateSets, }; + mStashedSets = {}; } void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr set, uint8_t binding, fvkmemory::resource_ptr bufferObject, VkDeviceSize offset, VkDeviceSize size) noexcept { VkDescriptorBufferInfo const info = { - .buffer = bufferObject->buffer.getVkBuffer(), + .buffer = bufferObject->getVkBuffer(), .offset = offset, .range = size, }; diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index d849a4a699..6e203b8b24 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -1239,7 +1239,7 @@ void VulkanDriver::updateIndexBuffer(Handle ibh, BufferDescriptor VulkanCommandBuffer& commands = mCommands.get(); auto ib = resource_ptr::cast(&mResourceManager, ibh); commands.acquire(ib); - ib->buffer.loadFromCpu(commands, p.buffer, byteOffset, p.size); + ib->loadFromCpu(commands, p.buffer, byteOffset, p.size); scheduleDestroy(std::move(p)); } @@ -1255,7 +1255,7 @@ void VulkanDriver::updateBufferObject(Handle boh, BufferDescript auto bo = resource_ptr::cast(&mResourceManager, boh); commands.acquire(bo); - bo->buffer.loadFromCpu(commands, bd.buffer, byteOffset, bd.size); + bo->loadFromCpu(commands, bd.buffer, byteOffset, bd.size); scheduleDestroy(std::move(bd)); } @@ -1266,7 +1266,7 @@ void VulkanDriver::updateBufferObjectUnsynchronized(Handle boh, auto bo = resource_ptr::cast(&mResourceManager, boh); commands.acquire(bo); // TODO: implement unsynchronized version - bo->buffer.loadFromCpu(commands, bd.buffer, byteOffset, bd.size); + bo->loadFromCpu(commands, bd.buffer, byteOffset, bd.size); scheduleDestroy(std::move(bd)); } @@ -1915,7 +1915,7 @@ void VulkanDriver::bindRenderPrimitive(Handle rph) { // avoid rebinding these if they are already bound, but since we do not (yet) support subranges // it would be rare for a client to make consecutive draw calls with the same render primitive. vkCmdBindVertexBuffers(cmdbuffer, 0, bufferCount, buffers, offsets); - vkCmdBindIndexBuffer(cmdbuffer, prim->indexBuffer->buffer.getVkBuffer(), 0, + vkCmdBindIndexBuffer(cmdbuffer, prim->indexBuffer->getVkBuffer(), 0, prim->indexBuffer->indexType); } diff --git a/filament/backend/src/vulkan/VulkanHandles.cpp b/filament/backend/src/vulkan/VulkanHandles.cpp index d19226276d..b66a82f963 100644 --- a/filament/backend/src/vulkan/VulkanHandles.cpp +++ b/filament/backend/src/vulkan/VulkanHandles.cpp @@ -39,6 +39,20 @@ namespace filament::backend { namespace { +inline VulkanBufferUsage getBufferObjectUsage(BufferObjectBinding bindingType) noexcept { + switch (bindingType) { + case BufferObjectBinding::VERTEX: + return VulkanBufferUsage::VERTEX; + case BufferObjectBinding::UNIFORM: + return VulkanBufferUsage::UNIFORM; + case BufferObjectBinding::SHADER_STORAGE: + return VulkanBufferUsage::SHADER_STORAGE; + // when adding more buffer-types here, make sure to update VulkanBuffer::loadFromCpu() + // if necessary. + } + return VulkanBufferUsage::UNKNOWN; +} + void flipVertically(VkViewport* rect, uint32_t framebufferHeight) { rect->y = framebufferHeight - rect->y - rect->height; } @@ -166,14 +180,6 @@ VulkanAttachment createSwapchainAttachment(const fvkmemory::resource_ptr texture) { - mResources.push_back(texture); -} - -void VulkanDescriptorSet::acquire(fvkmemory::resource_ptr obj) { - mResources.push_back(obj); -} - VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(DescriptorSetLayout&& layout, VkDescriptorSetLayout vkLayout) : bitmask(fromBackendLayout(layout)), @@ -185,6 +191,17 @@ VulkanDescriptorSetLayout::Bitmask VulkanDescriptorSetLayout::Bitmask::fromLayou return fromBackendLayout(layout); } +// This method will store an age associated with this command buffer into the VulkanBuffer, which +// will allow us to determine whether a barrier is necessary or not. +void VulkanDescriptorSet::referencedBy(VulkanCommandBuffer& commands) { + mUboMask.forEachSetBit([this, &commands](size_t index) { + auto& res = mResources[index]; + fvkmemory::resource_ptr bo = + fvkmemory::resource_ptr::cast((VulkanBufferObject*) res.get()); + bo->referencedBy(commands); + }); +} + PushConstantDescription::PushConstantDescription(backend::Program const& program) { mRangeCount = 0; for (auto stage : { ShaderStage::VERTEX, ShaderStage::FRAGMENT, ShaderStage::COMPUTE }) { @@ -588,7 +605,7 @@ void VulkanVertexBuffer::setBuffer(fvkmemory::resource_ptr b int8_t const* const attribToBuffer = vbi->getAttributeToBuffer(); for (uint8_t attribIndex = 0; attribIndex < count; attribIndex++) { if (attribToBuffer[attribIndex] == static_cast(index)) { - vkbuffers[attribIndex] = bufferObject->buffer.getVkBuffer(); + vkbuffers[attribIndex] = bufferObject->getVkBuffer(); } } mResources.push_back(bufferObject); @@ -598,9 +615,8 @@ VulkanBufferObject::VulkanBufferObject(VulkanContext const& context, VmaAllocato VulkanStagePool& stagePool, VulkanBufferCache& bufferCache, uint32_t byteCount, BufferObjectBinding bindingType) : HwBufferObject(byteCount), - buffer(context, allocator, stagePool, bufferCache, getBufferObjectUsage(bindingType), - byteCount), - bindingType(bindingType) {} + bindingType(bindingType), + mBuffer(context, allocator, stagePool, bufferCache, getBufferObjectUsage(bindingType), byteCount) {} VulkanRenderPrimitive::VulkanRenderPrimitive(PrimitiveType pt, fvkmemory::resource_ptr vb, diff --git a/filament/backend/src/vulkan/VulkanHandles.h b/filament/backend/src/vulkan/VulkanHandles.h index ad076b1fa4..11de2bc3dc 100644 --- a/filament/backend/src/vulkan/VulkanHandles.h +++ b/filament/backend/src/vulkan/VulkanHandles.h @@ -26,6 +26,7 @@ #include "VulkanFboCache.h" #include "VulkanSwapChain.h" #include "VulkanTexture.h" +#include "vulkan/VulkanCommands.h" #include "vulkan/memory/Resource.h" #include "vulkan/utils/Definitions.h" #include "vulkan/utils/StaticVector.h" @@ -200,8 +201,15 @@ public: return &mOffsets; } - void acquire(fvkmemory::resource_ptr texture); - void acquire(fvkmemory::resource_ptr buffer); + template + void acquire(fvkmemory::resource_ptr res) { + if (res->template isType()) { + mUboMask.set(mResources.size()); + } + mResources.push_back(res); + } + + void referencedBy(VulkanCommandBuffer& commands); fvkutils::UniformBufferBitmask const dynamicUboMask; uint8_t const uniqueDynamicUboCount; @@ -214,6 +222,7 @@ private: std::vector> mResources; OnRecycle mOnRecycleFn; OnRecycle mOnRecycleExternalSamplerFn; + fvkutils::UniformBufferBitmask mUboMask; }; using PushConstantNameArray = utils::FixedCapacityVector; @@ -419,6 +428,9 @@ struct VulkanVertexBuffer : public HwVertexBuffer, fvkmemory::Resource { fvkmemory::resource_ptr vbi); void setBuffer(fvkmemory::resource_ptr bufferObject, uint32_t index); + // TODO: because VulkanBufferObject is backed by VulkanBufferProxy, which could switch out the + // backing VkBuffer, we cannot store the VkBuffers for optimization here. We could store a dirty + // bit to indicate if a VkBuffer has changed maybe. inline VkBuffer const* getVkBuffers() const { return mBuffers.data(); } inline VkBuffer* getVkBuffers() { return mBuffers.data(); } fvkmemory::resource_ptr vbi; @@ -433,12 +445,21 @@ struct VulkanIndexBuffer : public HwIndexBuffer, fvkmemory::Resource { VulkanStagePool& stagePool, VulkanBufferCache& bufferCache, uint8_t elementSize, uint32_t indexCount) : HwIndexBuffer(elementSize, indexCount), - buffer(context, allocator, stagePool, bufferCache, VulkanBufferUsage::INDEX, - elementSize * indexCount), - indexType(elementSize == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32) {} + indexType(elementSize == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32), + mBuffer(context, allocator, stagePool, bufferCache, VulkanBufferUsage::INDEX, + elementSize * indexCount) {} - VulkanBufferProxy buffer; - const VkIndexType indexType; + inline void loadFromCpu(VulkanCommandBuffer& commands, const void* cpuData, uint32_t byteOffset, + uint32_t numBytes) { + mBuffer.loadFromCpu(commands, cpuData, byteOffset, numBytes); + } + + inline VkBuffer getVkBuffer() const noexcept { return mBuffer.getVkBuffer(); } + + VkIndexType const indexType; + +private: + VulkanBufferProxy mBuffer; }; struct VulkanBufferObject : public HwBufferObject, fvkmemory::Resource { @@ -446,8 +467,21 @@ struct VulkanBufferObject : public HwBufferObject, fvkmemory::Resource { VulkanStagePool& stagePool, VulkanBufferCache& bufferCache, uint32_t byteCount, BufferObjectBinding bindingType); - VulkanBufferProxy buffer; - const BufferObjectBinding bindingType; + inline void loadFromCpu(VulkanCommandBuffer& commands, const void* cpuData, uint32_t byteOffset, + uint32_t numBytes) { + mBuffer.loadFromCpu(commands, cpuData, byteOffset, numBytes); + } + + inline VkBuffer getVkBuffer() const noexcept { return mBuffer.getVkBuffer(); } + + inline void referencedBy(VulkanCommandBuffer& commands) { + mBuffer.referencedBy(commands); + } + + BufferObjectBinding const bindingType; + +private: + VulkanBufferProxy mBuffer; }; struct VulkanRenderPrimitive : public HwRenderPrimitive, fvkmemory::Resource { @@ -459,21 +493,6 @@ struct VulkanRenderPrimitive : public HwRenderPrimitive, fvkmemory::Resource { fvkmemory::resource_ptr indexBuffer; }; -inline constexpr VulkanBufferUsage getBufferObjectUsage(BufferObjectBinding bindingType) noexcept { - switch (bindingType) { - case BufferObjectBinding::VERTEX: - return VulkanBufferUsage::VERTEX; - case BufferObjectBinding::UNIFORM: - return VulkanBufferUsage::UNIFORM; - case BufferObjectBinding::SHADER_STORAGE: - return VulkanBufferUsage::SHADER_STORAGE; - // when adding more buffer-types here, make sure to update VulkanBuffer::loadFromCpu() - // if necessary. - } - - return VulkanBufferUsage::UNKNOWN; -} - } // namespace filament::backend #endif // TNT_FILAMENT_BACKEND_VULKANHANDLES_H diff --git a/filament/backend/src/vulkan/memory/Resource.h b/filament/backend/src/vulkan/memory/Resource.h index 7590aa826c..ff79a8f79d 100644 --- a/filament/backend/src/vulkan/memory/Resource.h +++ b/filament/backend/src/vulkan/memory/Resource.h @@ -72,7 +72,10 @@ struct Resource { restype(ResourceType::UNDEFINED_TYPE), mHandleConsideredDestroyed(false) {} - uint32_t getCount() const { return mCount; } + template + bool isType() const { + return getTypeEnum() == restype; + } private: inline void inc() noexcept {