From 88dce8c523cbff9e3b8c2765b055d9e182952fa0 Mon Sep 17 00:00:00 2001 From: csmartdalton Date: Wed, 18 Sep 2024 18:04:50 +0000 Subject: [PATCH] Overhaul Vulkan synchronization This stabilizes Vulkan on Android. Also adds a "vkcore" backend, which tests vulkan without bonus features enabled. Diffs= adc240554 Overhaul Vulkan synchronization (#8166) Co-authored-by: Chris Dalton <99840794+csmartdalton@users.noreply.github.com> --- .rive_head | 2 +- .../vulkan/render_context_vulkan_impl.hpp | 59 +- .../include/rive/renderer/vulkan/vkutil.hpp | 30 +- .../renderer/vulkan/vkutil_resource_pool.hpp | 175 +++++ .../rive/renderer/vulkan/vulkan_context.hpp | 84 +- renderer/path_fiddle/fiddle_context.hpp | 1 + .../path_fiddle/fiddle_context_vulkan.cpp | 217 +++-- .../rive_vk_bootstrap/rive_vk_bootstrap.hpp | 10 +- .../rive_vk_bootstrap/vulkan_fence_pool.hpp | 96 --- .../rive_vk_bootstrap/rive_vk_bootstrap.cpp | 70 +- .../src/vulkan/render_context_vulkan_impl.cpp | 743 ++++++++++-------- renderer/src/vulkan/vkutil.cpp | 11 +- renderer/src/vulkan/vkutil_resource_pool.cpp | 83 ++ renderer/src/vulkan/vulkan_context.cpp | 175 ++--- tests/check_golds.sh | 7 +- tests/common/test_harness.cpp | 2 +- tests/common/testing_window.cpp | 58 +- tests/common/testing_window.hpp | 89 ++- .../common/testing_window_android_vulkan.cpp | 142 ++-- tests/common/testing_window_egl.cpp | 8 +- .../common/testing_window_fiddle_context.cpp | 11 +- .../common/testing_window_vulkan_texture.cpp | 217 +++-- tests/deploy_tests.py | 18 +- tests/for_all_adb_devices.sh | 1 + 24 files changed, 1291 insertions(+), 1018 deletions(-) create mode 100644 renderer/include/rive/renderer/vulkan/vkutil_resource_pool.hpp delete mode 100644 renderer/rive_vk_bootstrap/include/rive_vk_bootstrap/vulkan_fence_pool.hpp create mode 100644 renderer/src/vulkan/vkutil_resource_pool.cpp diff --git a/.rive_head b/.rive_head index 9818df5f..a0b34f25 100644 --- a/.rive_head +++ b/.rive_head @@ -1 +1 @@ -9dd20080cc462660396b8b24b9d3b1b72f53516b +adc2405543ea447b83fda212a0cd7cc4eafd4de9 diff --git a/renderer/include/rive/renderer/vulkan/render_context_vulkan_impl.hpp b/renderer/include/rive/renderer/vulkan/render_context_vulkan_impl.hpp index 22838ad8..40fb9809 100644 --- a/renderer/include/rive/renderer/vulkan/render_context_vulkan_impl.hpp +++ b/renderer/include/rive/renderer/vulkan/render_context_vulkan_impl.hpp @@ -6,10 +6,10 @@ #include "rive/renderer/render_context_impl.hpp" #include "rive/renderer/vulkan/vulkan_context.hpp" +#include "rive/renderer/vulkan/vkutil_resource_pool.hpp" #include #include #include -#include namespace rive::gpu { @@ -18,11 +18,20 @@ class TextureVulkanImpl; class RenderTargetVulkan : public RenderTarget { public: - void setTargetTextureView(rcp view) + void setTargetTextureView(rcp view, + VulkanContext::TextureAccess targetLastAccess) { m_targetTextureView = std::move(view); + setTargetLastAccess(targetLastAccess); } + void setTargetLastAccess(VulkanContext::TextureAccess lastAccess) + { + m_targetLastAccess = lastAccess; + } + + const VulkanContext::TextureAccess& targetLastAccess() const { return m_targetLastAccess; } + bool targetViewContainsUsageFlag(VkImageUsageFlagBits bit) const { return m_targetTextureView ? static_cast(m_targetTextureView->usageFlags() & bit) @@ -33,11 +42,11 @@ class RenderTargetVulkan : public RenderTarget VkImage targetTexture() const { return m_targetTextureView->info().image; } - VkImageView targetTextureView() const { return *m_targetTextureView; } + vkutil::TextureView* targetTextureView() const { return m_targetTextureView.get(); } - VkImageView offscreenColorTextureView() const + vkutil::TextureView* offscreenColorTextureView() const { - return m_offscreenColorTextureView ? *m_offscreenColorTextureView : nullptr; + return m_offscreenColorTextureView.get(); } VkImage offscreenColorTexture() const { return *m_offscreenColorTexture; } @@ -48,11 +57,11 @@ class RenderTargetVulkan : public RenderTarget // getters that lazy load if needed. - VkImageView ensureOffscreenColorTextureView(VkCommandBuffer); - VkImageView ensureCoverageTextureView(VkCommandBuffer); - VkImageView ensureClipTextureView(VkCommandBuffer); - VkImageView ensureScratchColorTextureView(VkCommandBuffer); - VkImageView ensureCoverageAtomicTextureView(VkCommandBuffer); + vkutil::TextureView* ensureOffscreenColorTextureView(); + vkutil::TextureView* ensureCoverageTextureView(); + vkutil::TextureView* ensureClipTextureView(); + vkutil::TextureView* ensureScratchColorTextureView(); + vkutil::TextureView* ensureCoverageAtomicTextureView(); private: friend class RenderContextVulkanImpl; @@ -67,6 +76,7 @@ class RenderTargetVulkan : public RenderTarget const rcp m_vk; const VkFormat m_framebufferFormat; rcp m_targetTextureView; + VulkanContext::TextureAccess m_targetLastAccess; rcp m_offscreenColorTexture; // Used when m_targetTextureView does not have // VK_ACCESS_INPUT_ATTACHMENT_READ_BIT @@ -125,7 +135,7 @@ class RenderContextVulkanImpl : public RenderContextImpl { \ return m_name.contentsAt(m_bufferRingIdx, mapSizeInBytes); \ } \ - void unmap##Name() override { m_name.flushMappedContentsAt(m_bufferRingIdx); } + void unmap##Name() override { m_name.flushContentsAt(m_bufferRingIdx); } #define IMPLEMENT_PLS_STRUCTURED_BUFFER(Name, m_name) \ void resize##Name(size_t sizeInBytes, gpu::StorageBufferStructure) override \ @@ -136,7 +146,7 @@ class RenderContextVulkanImpl : public RenderContextImpl { \ return m_name.contentsAt(m_bufferRingIdx, mapSizeInBytes); \ } \ - void unmap##Name() override { m_name.flushMappedContentsAt(m_bufferRingIdx); } + void unmap##Name() override { m_name.flushContentsAt(m_bufferRingIdx); } IMPLEMENT_PLS_BUFFER(FlushUniformBuffer, m_flushUniformBufferRing) IMPLEMENT_PLS_BUFFER(ImageDrawUniformBuffer, m_imageDrawUniformBufferRing) @@ -160,27 +170,26 @@ class RenderContextVulkanImpl : public RenderContextImpl // The vkutil::RenderingResource base ensures this class stays alive until its // command buffer finishes, at which point we free the allocated descriptor // sets and return the VkDescriptor to the renderContext. - class DescriptorSetPool final : public vkutil::RenderingResource + class DescriptorSetPool final : public RefCnt { public: - DescriptorSetPool(RenderContextVulkanImpl*); - ~DescriptorSetPool() final; + DescriptorSetPool(rcp); + ~DescriptorSetPool(); VkDescriptorSet allocateDescriptorSet(VkDescriptorSetLayout); - void freeDescriptorSets(); + void reset(); private: - // Recycles this instance in the renderContext's m_descriptorSetPoolPool, if - // the renderContext still exists. - void onRefCntReachedZero() const; + friend class RefCnt; + friend class vkutil::ResourcePool; + + void onRefCntReachedZero() const { m_pool->onResourceRefCntReachedZero(this); } - RenderContextVulkanImpl* const m_renderContextImpl; + const rcp m_vk; + rcp> m_pool; VkDescriptorPool m_vkDescriptorPool; - std::vector m_descriptorSets; }; - rcp makeDescriptorSetPool(); - void flush(const FlushDescriptor&) override; double secondsNow() const override @@ -243,8 +252,8 @@ class RenderContextVulkanImpl : public RenderContextImpl rcp m_frameCompletionFences[gpu::kBufferRingSize]; int m_bufferRingIdx = -1; - // Pool of DescriptorSetPools that have been fully released. These can be + // Pool of DescriptorSetPools that have been fully released. These will be // recycled once their expirationFrameIdx is reached. - std::deque> m_descriptorSetPoolPool; + rcp> m_descriptorSetPoolPool; }; } // namespace rive::gpu diff --git a/renderer/include/rive/renderer/vulkan/vkutil.hpp b/renderer/include/rive/renderer/vulkan/vkutil.hpp index 69c22e54..ce069ed7 100644 --- a/renderer/include/rive/renderer/vulkan/vkutil.hpp +++ b/renderer/include/rive/renderer/vulkan/vkutil.hpp @@ -106,7 +106,15 @@ class Buffer : public RenderingResource return m_contents; } - void flushMappedContents(size_t updatedSizeInBytes); + // Calls through to vkFlushMappedMemoryRanges(). + // Called after modifying contents() with the CPU. Makes those modifications + // available to the GPU. + void flushContents(size_t sizeInBytes = VK_WHOLE_SIZE); + + // Calls through to vkInvalidateMappedMemoryRanges(). + // Called after modifying the buffer with the GPU. Makes those modifications + // available to the CPU via contents(). + void invalidateContents(size_t sizeInBytes = VK_WHOLE_SIZE); private: friend class ::rive::gpu::VulkanContext; @@ -122,24 +130,6 @@ class Buffer : public RenderingResource void* m_contents; }; -// RAII utility to call flushMappedContents() on a buffer when the class goes out -// of scope. -class ScopedBufferFlush -{ -public: - ScopedBufferFlush(Buffer& buff, size_t mapSizeInBytes = VK_WHOLE_SIZE) : - m_buff(buff), m_mapSizeInBytes(mapSizeInBytes) - {} - ~ScopedBufferFlush() { m_buff.flushMappedContents(m_mapSizeInBytes); } - - operator void*() { return m_buff.contents(); } - template T as() { return reinterpret_cast(m_buff.contents()); } - -private: - Buffer& m_buff; - const size_t m_mapSizeInBytes; -}; - // Wraps a ring of VkBuffers so we can map one while other(s) are in-flight. class BufferRing { @@ -158,7 +148,7 @@ class BufferRing void setTargetSize(size_t size); void synchronizeSizeAt(int bufferRingIdx); void* contentsAt(int bufferRingIdx, size_t dirtySize = VK_WHOLE_SIZE); - void flushMappedContentsAt(int bufferRingIdx); + void flushContentsAt(int bufferRingIdx); private: size_t m_targetSize; diff --git a/renderer/include/rive/renderer/vulkan/vkutil_resource_pool.hpp b/renderer/include/rive/renderer/vulkan/vkutil_resource_pool.hpp new file mode 100644 index 00000000..c3f0b23e --- /dev/null +++ b/renderer/include/rive/renderer/vulkan/vkutil_resource_pool.hpp @@ -0,0 +1,175 @@ +/* + * Copyright 2024 Rive + */ + +#pragma once + +#include "rive/renderer/gpu.hpp" +#include "rive/renderer/vulkan/vulkan_context.hpp" + +namespace rive::gpu::vkutil +{ +class CommandBuffer; +class Fence; +class Semaphore; + +// Used by ResourcePool to construct resources. +template class ResourceFactory +{ +public: + ResourceFactory(rcp vk) : m_vk(std::move(vk)) {} + VulkanContext* vulkanContext() const { return m_vk.get(); } + rcp make() { return make_rcp(m_vk); } + +private: + rcp m_vk; +}; + +// ResourceFactory<> specialization for command buffers, which also require a +// VkCommandPool. +template <> class ResourceFactory +{ +public: + ResourceFactory(rcp vk, uint32_t queueFamilyIndex); + ~ResourceFactory(); + + VulkanContext* vulkanContext() const { return m_vk.get(); } + rcp make() { return make_rcp(m_vk, m_vkCommandPool); } + operator VkCommandPool() const { return m_vkCommandPool; } + +private: + rcp m_vk; + VkCommandPool m_vkCommandPool = VK_NULL_HANDLE; +}; + +// Manages a pool of recyclable Vulkan resources. When onRefCntReachedZero() is +// called on the resources owned by this pool, they are captured and recycled +// rather than deleted. +template +class ResourcePool : public RefCnt> +{ +public: + template + ResourcePool(FactoryArgs&&... factoryArgs) : + m_factory(std::forward(factoryArgs)...) + {} + + ~ResourcePool() + { + m_releasedResources.clear(); // Delete resources before freeing the factory. + } + + rcp make() + { + rcp resource; + if (!m_releasedResources.empty() && m_factory.vulkanContext()->currentFrameIdx() >= + m_releasedResources.front().expirationFrameIdx) + { + resource = ref_rcp(m_releasedResources.front().resource.release()); + m_releasedResources.pop_front(); + resource->reset(); + } + else + { + resource = m_factory.make(); + } + resource->m_pool = ref_rcp(static_cast*>(this)); + assert(resource->debugging_refcnt() == 1); + return resource; + } + + void onResourceRefCntReachedZero(const T* resource) + { + auto mutableResource = const_cast(resource); + assert(mutableResource->debugging_refcnt() == 0); + assert(mutableResource->m_pool.get() == this); + + if (m_releasedResources.size() < MaxResourcesInPool) + { + // Recycle the resource! + m_releasedResources.emplace_back(mutableResource, + m_factory.vulkanContext()->currentFrameIdx()); + // Do this last in case it deletes our "this". + mutableResource->m_pool = nullptr; + return; + } + + delete resource; + } + +protected: + ResourceFactory m_factory; + + // Pool of Resources that have been fully released. + // These can be recycled once their expirationFrameIdx is reached. + std::deque> m_releasedResources; +}; + +// VkCommandBuffer that lives in a ResourcePool. +class CommandBuffer : public RefCnt +{ +public: + CommandBuffer(rcp, VkCommandPool); + ~CommandBuffer(); + + void reset(); + operator VkCommandBuffer() const { return m_vkCommandBuffer; } + const VkCommandBuffer* vkCommandBufferAddressOf() const { return &m_vkCommandBuffer; } + +private: + friend class RefCnt; + friend class ResourcePool; + + void onRefCntReachedZero() const { m_pool->onResourceRefCntReachedZero(this); } + + const rcp m_vk; + const VkCommandPool m_vkCommandPool; + rcp> m_pool; + VkCommandBuffer m_vkCommandBuffer; +}; + +// VkSemaphore wrapper that lives in a ResourcePool. +class Semaphore : public RefCnt +{ +public: + Semaphore(rcp); + ~Semaphore(); + + void reset(); + + operator VkSemaphore() const { return m_vkSemaphore; } + const VkSemaphore* vkSemaphoreAddressOf() const { return &m_vkSemaphore; } + +private: + friend class RefCnt; + friend class ResourcePool; + + void onRefCntReachedZero() const { m_pool->onResourceRefCntReachedZero(this); } + + const rcp m_vk; + rcp> m_pool; + VkSemaphore m_vkSemaphore; +}; + +// VkFence wrapper that lives in a ResourcePool. +class Fence : public CommandBufferCompletionFence +{ +public: + Fence(rcp); + ~Fence() override; + + void reset(); + void wait() override; + + operator VkFence() const { return m_vkFence; } + +private: + friend class ResourcePool; + + void onRefCntReachedZero() const override { m_pool->onResourceRefCntReachedZero(this); } + + const rcp m_vk; + rcp> m_pool; + VkFence m_vkFence; +}; +} // namespace rive::gpu::vkutil diff --git a/renderer/include/rive/renderer/vulkan/vulkan_context.hpp b/renderer/include/rive/renderer/vulkan/vulkan_context.hpp index d67c7921..1e14fa25 100644 --- a/renderer/include/rive/renderer/vulkan/vulkan_context.hpp +++ b/renderer/include/rive/renderer/vulkan/vulkan_context.hpp @@ -14,6 +14,7 @@ namespace rive::gpu struct VulkanFeatures { uint32_t vulkanApiVersion = VK_API_VERSION_1_0; + uint32_t vendorID = 0; // VkPhysicalDeviceFeatures. bool independentBlend = false; @@ -50,9 +51,8 @@ class VulkanContext : public RefCnt const VulkanFeatures features; const VmaAllocator vmaAllocator; -#define RIVE_VULKAN_INSTANCE_COMMANDS(F) F(GetPhysicalDeviceProperties) - #define RIVE_VULKAN_DEVICE_COMMANDS(F) \ + F(AllocateCommandBuffers) \ F(AllocateDescriptorSets) \ F(CmdBeginRenderPass) \ F(CmdBindDescriptorSets) \ @@ -68,6 +68,7 @@ class VulkanContext : public RefCnt F(CmdPipelineBarrier) \ F(CmdSetScissor) \ F(CmdSetViewport) \ + F(CreateCommandPool) \ F(CreateDescriptorPool) \ F(CreateDescriptorSetLayout) \ F(CreateFence) \ @@ -77,7 +78,9 @@ class VulkanContext : public RefCnt F(CreatePipelineLayout) \ F(CreateRenderPass) \ F(CreateSampler) \ + F(CreateSemaphore) \ F(CreateShaderModule) \ + F(DestroyCommandPool) \ F(DestroyDescriptorPool) \ F(DestroyDescriptorSetLayout) \ F(DestroyFence) \ @@ -87,14 +90,16 @@ class VulkanContext : public RefCnt F(DestroyPipelineLayout) \ F(DestroyRenderPass) \ F(DestroySampler) \ + F(DestroySemaphore) \ F(DestroyShaderModule) \ + F(FreeCommandBuffers) \ + F(ResetCommandBuffer) \ F(ResetDescriptorPool) \ F(ResetFences) \ F(UpdateDescriptorSets) \ F(WaitForFences) #define DECLARE_VULKAN_COMMAND(CMD) const PFN_vk##CMD CMD; - RIVE_VULKAN_INSTANCE_COMMANDS(DECLARE_VULKAN_COMMAND) RIVE_VULKAN_DEVICE_COMMANDS(DECLARE_VULKAN_COMMAND) #undef DECLARE_VULKAN_COMMAND @@ -134,18 +139,67 @@ class VulkanContext : public RefCnt void updateBufferDescriptorSets(VkDescriptorSet, VkWriteDescriptorSet, std::initializer_list); - void insertImageMemoryBarrier(VkCommandBuffer, - VkImage, - VkImageLayout oldLayout, - VkImageLayout newLayout, - uint32_t mipLevel = 0, - uint32_t levelCount = 1); - void insertBufferMemoryBarrier(VkCommandBuffer, - VkAccessFlags srcAccessMask, - VkAccessFlags dstAccessMask, - VkBuffer, - VkDeviceSize offset = 0, - VkDeviceSize size = VK_WHOLE_SIZE); + + void memoryBarrier(VkCommandBuffer, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + VkDependencyFlags, + VkMemoryBarrier); + + void imageMemoryBarriers(VkCommandBuffer, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + VkDependencyFlags, + uint32_t count, + VkImageMemoryBarrier*); + + void imageMemoryBarrier(VkCommandBuffer commandBuffer, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + VkDependencyFlags dependencyFlags, + VkImageMemoryBarrier imageMemoryBarrier) + { + imageMemoryBarriers(commandBuffer, + srcStageMask, + dstStageMask, + dependencyFlags, + 1, + &imageMemoryBarrier); + } + + struct TextureAccess + { + VkPipelineStageFlags pipelineStages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkAccessFlags accessMask = VK_ACCESS_NONE; + VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; + }; + + const TextureAccess& simpleImageMemoryBarrier(VkCommandBuffer commandBuffer, + const TextureAccess& srcAccess, + const TextureAccess& dstAccess, + VkImage image, + VkDependencyFlags dependencyFlags = 0) + { + imageMemoryBarrier(commandBuffer, + srcAccess.pipelineStages, + dstAccess.pipelineStages, + dependencyFlags, + { + .srcAccessMask = srcAccess.accessMask, + .dstAccessMask = dstAccess.accessMask, + .oldLayout = srcAccess.layout, + .newLayout = dstAccess.layout, + .image = image, + }); + return dstAccess; + } + + void bufferMemoryBarrier(VkCommandBuffer, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + VkDependencyFlags, + VkBufferMemoryBarrier); + void blitSubRect(VkCommandBuffer commandBuffer, VkImage src, VkImage dst, const IAABB&); private: diff --git a/renderer/path_fiddle/fiddle_context.hpp b/renderer/path_fiddle/fiddle_context.hpp index 2b2097b4..b5a1209e 100644 --- a/renderer/path_fiddle/fiddle_context.hpp +++ b/renderer/path_fiddle/fiddle_context.hpp @@ -12,6 +12,7 @@ struct FiddleContextOptions bool synchronousShaderCompilations = false; bool enableReadPixels = false; bool disableRasterOrdering = false; + bool coreFeaturesOnly = false; // Allow rendering to a texture instead of an OS window. (Speeds up the execution of goldens & // gms significantly on Vulkan/Windows.) bool allowHeadlessRendering = false; diff --git a/renderer/path_fiddle/fiddle_context_vulkan.cpp b/renderer/path_fiddle/fiddle_context_vulkan.cpp index 62509601..b33270e1 100644 --- a/renderer/path_fiddle/fiddle_context_vulkan.cpp +++ b/renderer/path_fiddle/fiddle_context_vulkan.cpp @@ -14,9 +14,9 @@ std::unique_ptr FiddleContext::MakeVulkanPLS(FiddleContextOptions #else #include "rive_vk_bootstrap/rive_vk_bootstrap.hpp" -#include "rive_vk_bootstrap/vulkan_fence_pool.hpp" #include "rive/renderer/rive_renderer.hpp" #include "rive/renderer/vulkan/render_context_vulkan_impl.hpp" +#include "rive/renderer/vulkan/vkutil_resource_pool.hpp" #include #include #include @@ -26,10 +26,6 @@ std::unique_ptr FiddleContext::MakeVulkanPLS(FiddleContextOptions using namespace rive; using namespace rive::gpu; -// +1 because PLS doesn't wait for the previous fence until partway through flush. -// (After we need to acquire a new image from the swapchain.) -static constexpr int kResourcePoolSize = gpu::kBufferRingSize + 1; - class FiddleContextVulkanPLS : public FiddleContext { public: @@ -50,53 +46,28 @@ class FiddleContextVulkanPLS : public FiddleContext #endif .enable_extensions(glfwExtensionCount, glfwExtensions) .build()); - m_instanceDispatch = m_instance.make_table(); + m_instanceTable = m_instance.make_table(); VulkanFeatures vulkanFeatures; std::tie(m_physicalDevice, vulkanFeatures) = rive_vkb::select_physical_device( vkb::PhysicalDeviceSelector(m_instance).defer_surface_initialization(), + m_options.coreFeaturesOnly ? rive_vkb::FeatureSet::coreOnly + : rive_vkb::FeatureSet::allAvailable, m_options.gpuNameFilter); m_device = VKB_CHECK(vkb::DeviceBuilder(m_physicalDevice).build()); - m_vkDispatch = m_device.make_table(); + m_vkbTable = m_device.make_table(); m_queue = VKB_CHECK(m_device.get_queue(vkb::QueueType::graphics)); - - VkCommandPoolCreateInfo commandPoolCreateInfo = { - .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, - .flags = VkCommandPoolCreateFlagBits::VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, - .queueFamilyIndex = *m_device.get_queue_index(vkb::QueueType::graphics), - }; - - VK_CHECK(m_vkDispatch.createCommandPool(&commandPoolCreateInfo, nullptr, &m_commandPool)); - - VkCommandBufferAllocateInfo commandBufferAllocateInfo = { - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, - .commandPool = m_commandPool, - .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, - .commandBufferCount = 1, - }; - - for (VkCommandBuffer& commandBuffer : m_commandBuffers) - { - VK_CHECK( - m_vkDispatch.allocateCommandBuffers(&commandBufferAllocateInfo, &commandBuffer)); - } - m_renderContext = RenderContextVulkanImpl::MakeContext(m_instance, m_physicalDevice, m_device, vulkanFeatures, m_instance.fp_vkGetInstanceProcAddr, m_instance.fp_vkGetDeviceProcAddr); - - VkSemaphoreCreateInfo semaphoreCreateInfo = { - .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, - }; - for (VkSemaphore& semaphore : m_semaphores) - { - VK_CHECK(m_vkDispatch.createSemaphore(&semaphoreCreateInfo, nullptr, &semaphore)); - } - - m_fencePool = make_rcp(ref_rcp(vk())); + m_commandBufferPool = make_rcp>( + ref_rcp(vk()), + *m_device.get_queue_index(vkb::QueueType::graphics)); + m_semaphorePool = make_rcp>(ref_rcp(vk())); + m_fencePool = make_rcp>(ref_rcp(vk())); } ~FiddleContextVulkanPLS() @@ -109,15 +80,15 @@ class FiddleContextVulkanPLS : public FiddleContext m_fencePool.reset(); m_frameFence.reset(); - VK_CHECK(m_vkDispatch.queueWaitIdle(m_queue)); + VK_CHECK(m_vkbTable.queueWaitIdle(m_queue)); - m_vkDispatch.freeCommandBuffers(m_commandPool, kResourcePoolSize, m_commandBuffers); - m_vkDispatch.destroyCommandPool(m_commandPool, nullptr); + m_swapchainSemaphore = nullptr; + m_frameFence = nullptr; + m_frameCommandBuffer = nullptr; - for (VkSemaphore semaphore : m_semaphores) - { - m_vkDispatch.destroySemaphore(semaphore, nullptr); - } + m_commandBufferPool = nullptr; + m_semaphorePool = nullptr; + m_fencePool = nullptr; if (m_swapchain != VK_NULL_HANDLE) { @@ -126,7 +97,7 @@ class FiddleContextVulkanPLS : public FiddleContext if (m_windowSurface != VK_NULL_HANDLE) { - m_instanceDispatch.destroySurfaceKHR(m_windowSurface, nullptr); + m_instanceTable.destroySurfaceKHR(m_windowSurface, nullptr); } vkb::destroy_device(m_device); @@ -150,7 +121,7 @@ class FiddleContextVulkanPLS : public FiddleContext void onSizeChanged(GLFWwindow* window, int width, int height, uint32_t sampleCount) override { - VK_CHECK(m_vkDispatch.queueWaitIdle(m_queue)); + VK_CHECK(m_vkbTable.queueWaitIdle(m_queue)); if (m_swapchain != VK_NULL_HANDLE) { @@ -159,16 +130,15 @@ class FiddleContextVulkanPLS : public FiddleContext if (m_windowSurface != VK_NULL_HANDLE) { - m_instanceDispatch.destroySurfaceKHR(m_windowSurface, nullptr); + m_instanceTable.destroySurfaceKHR(m_windowSurface, nullptr); } VK_CHECK(glfwCreateWindowSurface(m_instance, window, nullptr, &m_windowSurface)); VkSurfaceCapabilitiesKHR windowCapabilities; - VK_CHECK( - m_instanceDispatch.fp_vkGetPhysicalDeviceSurfaceCapabilitiesKHR(m_physicalDevice, - m_windowSurface, - &windowCapabilities)); + VK_CHECK(m_instanceTable.fp_vkGetPhysicalDeviceSurfaceCapabilitiesKHR(m_physicalDevice, + m_windowSurface, + &windowCapabilities)); vkb::SwapchainBuilder swapchainBuilder(m_device, m_windowSurface); swapchainBuilder @@ -184,7 +154,8 @@ class FiddleContextVulkanPLS : public FiddleContext .add_fallback_present_mode(VK_PRESENT_MODE_MAILBOX_KHR) .add_fallback_present_mode(VK_PRESENT_MODE_FIFO_RELAXED_KHR) .add_fallback_present_mode(VK_PRESENT_MODE_FIFO_KHR); - if (windowCapabilities.supportedUsageFlags & VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT) + if (!m_options.coreFeaturesOnly && + (windowCapabilities.supportedUsageFlags & VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT)) { swapchainBuilder.add_image_usage_flags(VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT); if (m_options.enableReadPixels) @@ -219,10 +190,6 @@ class FiddleContextVulkanPLS : public FiddleContext })); } - m_swapchainImageLayouts = std::vector(m_swapchainImages.size(), VK_IMAGE_LAYOUT_UNDEFINED); - - m_swapchainImageIndex = 0; - m_renderTarget = impl()->makeRenderTarget(width, height, m_swapchain.image_format); m_pixelReadBuffer = nullptr; @@ -237,35 +204,32 @@ class FiddleContextVulkanPLS : public FiddleContext void begin(const RenderContext::FrameDescriptor& frameDescriptor) override { - m_vkDispatch.acquireNextImageKHR(m_swapchain, - UINT64_MAX, - m_semaphores[m_resourcePoolIdx], - VK_NULL_HANDLE, - &m_swapchainImageIndex); + m_swapchainSemaphore = m_semaphorePool->make(); + m_vkbTable.acquireNextImageKHR(m_swapchain, + UINT64_MAX, + *m_swapchainSemaphore, + VK_NULL_HANDLE, + &m_swapchainImageIndex); m_renderContext->beginFrame(std::move(frameDescriptor)); - VkCommandBuffer commandBuffer = m_commandBuffers[m_resourcePoolIdx]; - m_vkDispatch.resetCommandBuffer(commandBuffer, {}); + m_frameCommandBuffer = m_commandBufferPool->make(); VkCommandBufferBeginInfo commandBufferBeginInfo = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, }; + m_vkbTable.beginCommandBuffer(*m_frameCommandBuffer, &commandBufferBeginInfo); - m_vkDispatch.beginCommandBuffer(commandBuffer, &commandBufferBeginInfo); - - m_renderTarget->setTargetTextureView(m_swapchainImageViews[m_swapchainImageIndex]); + m_renderTarget->setTargetTextureView(m_swapchainImageViews[m_swapchainImageIndex], {}); - insertSwapchainImageBarrier(VK_IMAGE_LAYOUT_GENERAL); - - m_frameFence = m_fencePool->makeFence(); + m_frameFence = m_fencePool->make(); } void flushPLSContext() final { m_renderContext->flush({ .renderTarget = m_renderTarget.get(), - .externalCommandBuffer = m_commandBuffers[m_resourcePoolIdx], + .externalCommandBuffer = *m_frameCommandBuffer, .frameCompletionFence = m_frameFence.get(), }); } @@ -274,7 +238,6 @@ class FiddleContextVulkanPLS : public FiddleContext { flushPLSContext(); - VkCommandBuffer commandBuffer = m_commandBuffers[m_resourcePoolIdx]; uint32_t w = m_renderTarget->width(); uint32_t h = m_renderTarget->height(); @@ -292,6 +255,16 @@ class FiddleContextVulkanPLS : public FiddleContext } assert(m_pixelReadBuffer->info().size == h * w * 4); + m_renderTarget->setTargetLastAccess( + vk()->simpleImageMemoryBarrier(*m_frameCommandBuffer, + m_renderTarget->targetLastAccess(), + { + .pipelineStages = VK_PIPELINE_STAGE_TRANSFER_BIT, + .accessMask = VK_ACCESS_TRANSFER_READ_BIT, + .layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + }, + m_swapchainImages[m_swapchainImageIndex])); + VkBufferImageCopy imageCopyDesc = { .imageSubresource = { @@ -303,46 +276,61 @@ class FiddleContextVulkanPLS : public FiddleContext .imageExtent = {w, h, 1}, }; - insertSwapchainImageBarrier(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); - - m_vkDispatch.cmdCopyImageToBuffer(commandBuffer, - m_swapchainImages[m_swapchainImageIndex], - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - *m_pixelReadBuffer, - 1, - &imageCopyDesc); - - vk()->insertBufferMemoryBarrier(commandBuffer, - VK_ACCESS_TRANSFER_WRITE_BIT, - VK_ACCESS_HOST_READ_BIT, - *m_pixelReadBuffer); + m_vkbTable.cmdCopyImageToBuffer(*m_frameCommandBuffer, + m_swapchainImages[m_swapchainImageIndex], + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + *m_pixelReadBuffer, + 1, + &imageCopyDesc); + + vk()->bufferMemoryBarrier(*m_frameCommandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_HOST_BIT, + 0, + { + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_HOST_READ_BIT, + .buffer = *m_pixelReadBuffer, + }); } - insertSwapchainImageBarrier(VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + m_renderTarget->setTargetLastAccess(vk()->simpleImageMemoryBarrier( + *m_frameCommandBuffer, + m_renderTarget->targetLastAccess(), + { + .pipelineStages = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + .accessMask = VK_ACCESS_NONE, + .layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + }, + m_swapchainImages[m_swapchainImageIndex])); - VK_CHECK(m_vkDispatch.endCommandBuffer(commandBuffer)); + VK_CHECK(m_vkbTable.endCommandBuffer(*m_frameCommandBuffer)); - VkPipelineStageFlags waitDstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + auto flushSemaphore = m_semaphorePool->make(); + VkPipelineStageFlags waitDstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; VkSubmitInfo submitInfo = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .waitSemaphoreCount = 1, + .pWaitSemaphores = m_swapchainSemaphore->vkSemaphoreAddressOf(), .pWaitDstStageMask = &waitDstStageMask, .commandBufferCount = 1, - .pCommandBuffers = &commandBuffer, + .pCommandBuffers = m_frameCommandBuffer->vkCommandBufferAddressOf(), + .signalSemaphoreCount = 1, + .pSignalSemaphores = flushSemaphore->vkSemaphoreAddressOf(), }; - submitInfo.waitSemaphoreCount = 1; - submitInfo.pWaitSemaphores = &m_semaphores[m_resourcePoolIdx]; - VK_CHECK(m_vkDispatch.queueSubmit(m_queue, 1, &submitInfo, *m_frameFence)); + VK_CHECK(m_vkbTable.queueSubmit(m_queue, 1, &submitInfo, *m_frameFence)); if (pixelData != nullptr) { - // Copy the buffer containing the framebuffer contents to pixelData. - pixelData->resize(h * w * 4); - // Wait for all rendering to complete before transferring the // framebuffer data to pixelData. m_frameFence->wait(); + m_pixelReadBuffer->invalidateContents(); + + // Copy the buffer containing the framebuffer contents to pixelData. + pixelData->resize(h * w * 4); assert(m_pixelReadBuffer->info().size == h * w * 4); for (uint32_t y = 0; y < h; ++y) @@ -363,15 +351,18 @@ class FiddleContextVulkanPLS : public FiddleContext VkPresentInfoKHR presentInfo = { .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .waitSemaphoreCount = 1, + .pWaitSemaphores = flushSemaphore->vkSemaphoreAddressOf(), .swapchainCount = 1, .pSwapchains = &m_swapchain.swapchain, .pImageIndices = &m_swapchainImageIndex, }; - m_vkDispatch.queuePresentKHR(m_queue, &presentInfo); + m_vkbTable.queuePresentKHR(m_queue, &presentInfo); - m_resourcePoolIdx = (m_resourcePoolIdx + 1) % kResourcePoolSize; + m_swapchainSemaphore = nullptr; m_frameFence = nullptr; + m_frameCommandBuffer = nullptr; } private: @@ -382,42 +373,32 @@ class FiddleContextVulkanPLS : public FiddleContext VulkanContext* vk() const { return impl()->vulkanContext(); } - void insertSwapchainImageBarrier(VkImageLayout newLayout) - { - vk()->insertImageMemoryBarrier(m_commandBuffers[m_resourcePoolIdx], - m_swapchainImages[m_swapchainImageIndex], - m_swapchainImageLayouts[m_swapchainImageIndex], - newLayout); - m_swapchainImageLayouts[m_swapchainImageIndex] = newLayout; - } - const FiddleContextOptions m_options; vkb::Instance m_instance; - vkb::InstanceDispatchTable m_instanceDispatch; + vkb::InstanceDispatchTable m_instanceTable; vkb::PhysicalDevice m_physicalDevice; vkb::Device m_device; - vkb::DispatchTable m_vkDispatch; + vkb::DispatchTable m_vkbTable; VkQueue m_queue; VkSurfaceKHR m_windowSurface = VK_NULL_HANDLE; vkb::Swapchain m_swapchain; std::vector m_swapchainImages; std::vector> m_swapchainImageViews; - std::vector m_swapchainImageLayouts; - uint32_t m_swapchainImageIndex; + uint32_t m_swapchainImageIndex = 0; - VkCommandPool m_commandPool; - VkCommandBuffer m_commandBuffers[kResourcePoolSize]; - VkSemaphore m_semaphores[kResourcePoolSize]; + rcp> m_commandBufferPool; + rcp m_frameCommandBuffer; - rcp m_fencePool; - rcp m_frameFence; + rcp> m_semaphorePool; + rcp m_swapchainSemaphore; + + rcp> m_fencePool; + rcp m_frameFence; std::unique_ptr m_renderContext; rcp m_renderTarget; rcp m_pixelReadBuffer; - - int m_resourcePoolIdx = 0; }; std::unique_ptr FiddleContext::MakeVulkanPLS(FiddleContextOptions options) diff --git a/renderer/rive_vk_bootstrap/include/rive_vk_bootstrap/rive_vk_bootstrap.hpp b/renderer/rive_vk_bootstrap/include/rive_vk_bootstrap/rive_vk_bootstrap.hpp index 26666f26..4613074d 100644 --- a/renderer/rive_vk_bootstrap/include/rive_vk_bootstrap/rive_vk_bootstrap.hpp +++ b/renderer/rive_vk_bootstrap/include/rive_vk_bootstrap/rive_vk_bootstrap.hpp @@ -32,17 +32,25 @@ VKAPI_ATTR VkBool32 VKAPI_CALL default_debug_callback(VkDebugUtilsMessageSeverit const VkDebugUtilsMessengerCallbackDataEXT*, void* pUserData); +enum class FeatureSet +{ + coreOnly, + allAvailable, +}; + // Select a GPU, using 'gpuNameFilter' or 'getenv("RIVE_GPU")', otherwise // preferring discrete. Abort if the filter matches more than one name. std::tuple select_physical_device( vkb::PhysicalDeviceSelector& selector, + FeatureSet, const char* gpuNameFilter = nullptr); inline std::tuple select_physical_device( vkb::Instance instance, + FeatureSet featureSet, const char* gpuNameFilter = nullptr) { vkb::PhysicalDeviceSelector selector(instance); - return select_physical_device(selector, gpuNameFilter); + return select_physical_device(selector, featureSet, gpuNameFilter); } } // namespace rive_vkb diff --git a/renderer/rive_vk_bootstrap/include/rive_vk_bootstrap/vulkan_fence_pool.hpp b/renderer/rive_vk_bootstrap/include/rive_vk_bootstrap/vulkan_fence_pool.hpp deleted file mode 100644 index f2517e77..00000000 --- a/renderer/rive_vk_bootstrap/include/rive_vk_bootstrap/vulkan_fence_pool.hpp +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2024 Rive - */ - -#pragma once - -#include "rive/renderer/gpu.hpp" -#include "rive/renderer/vulkan/vulkan_context.hpp" - -namespace rive::gpu -{ -class VulkanFence; - -// Manages a pool of recyclable VulkanFences. -class VulkanFencePool : public RefCnt -{ -public: - VulkanFencePool(rcp vk) : m_vk(std::move(vk)) {} - - rcp makeFence(); - -private: - friend class VulkanFence; - const rcp m_vk; - std::vector> m_fences; -}; - -// Wraps a VkFence created specifically for a PLS flush. -// Recycles the VulkanFence in its pool when its ref count reaches zero. -class VulkanFence : public CommandBufferCompletionFence -{ -public: - VulkanFence(rcp pool) : m_vk(pool->m_vk), m_pool(std::move(pool)) - { - VkFenceCreateInfo fenceInfo = { - .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, - }; - VK_CHECK(m_vk->CreateFence(m_vk->device, &fenceInfo, nullptr, &m_vkFence)); - } - - ~VulkanFence() override { m_vk->DestroyFence(m_vk->device, m_vkFence, nullptr); } - - operator VkFence() const { return m_vkFence; } - - void wait() override - { - while (m_vk->WaitForFences(m_vk->device, 1, &m_vkFence, VK_TRUE, 1000) == VK_TIMEOUT) - { - // Keep waiting. - } - } - -private: - // Recycles the fence in the VulkanFencePool. - void onRefCntReachedZero() const override - { - constexpr static uint32_t kMaxFencesInPool = 32; - - if (m_pool->m_fences.size() < kMaxFencesInPool) - { - // Recycle the fence! - m_vk->ResetFences(m_vk->device, 1, &m_vkFence); - auto mutableThis = const_cast(this); - rcp pool = std::move(mutableThis->m_pool); - assert(mutableThis->m_pool == nullptr); - pool->m_fences.push_back(std::unique_ptr(mutableThis)); - assert(debugging_refcnt() == 0); - return; - } - - delete this; - } - - friend class VulkanFencePool; - const rcp m_vk; - rcp m_pool; - VkFence m_vkFence; -}; - -inline rcp VulkanFencePool::makeFence() -{ - rcp fence; - if (!m_fences.empty()) - { - fence = ref_rcp(m_fences.back().release()); - fence->m_pool = ref_rcp(this); - m_fences.pop_back(); - } - else - { - fence.reset(new VulkanFence(ref_rcp(this))); - } - assert(fence->debugging_refcnt() == 1); - return fence; -} -} // namespace rive::gpu diff --git a/renderer/rive_vk_bootstrap/rive_vk_bootstrap.cpp b/renderer/rive_vk_bootstrap/rive_vk_bootstrap.cpp index b9ea7cad..309e37de 100644 --- a/renderer/rive_vk_bootstrap/rive_vk_bootstrap.cpp +++ b/renderer/rive_vk_bootstrap/rive_vk_bootstrap.cpp @@ -88,6 +88,7 @@ static const char* physical_device_type_name(VkPhysicalDeviceType type) // Abort if the filter matches more than one name. std::tuple select_physical_device( vkb::PhysicalDeviceSelector& selector, + FeatureSet featureSet, const char* gpuNameFilter) { if (const char* rive_gpu = getenv("RIVE_GPU")) @@ -150,40 +151,25 @@ std::tuple select_physical_devic auto physicalDevice = VKB_CHECK(selectResult); physicalDevice.enable_features_if_present({ - .independentBlend = VK_TRUE, + .independentBlend = featureSet != FeatureSet::coreOnly, .fillModeNonSolid = VK_TRUE, .fragmentStoresAndAtomics = VK_TRUE, }); - rive::gpu::VulkanFeatures riveVulkanFeatures; - riveVulkanFeatures.independentBlend = physicalDevice.features.independentBlend; - riveVulkanFeatures.fillModeNonSolid = physicalDevice.features.fillModeNonSolid; - riveVulkanFeatures.fragmentStoresAndAtomics = physicalDevice.features.fragmentStoresAndAtomics; + rive::gpu::VulkanFeatures riveVulkanFeatures = { + riveVulkanFeatures.vulkanApiVersion = VK_API_VERSION_1_0, + riveVulkanFeatures.vendorID = physicalDevice.properties.vendorID, + riveVulkanFeatures.independentBlend = physicalDevice.features.independentBlend, + riveVulkanFeatures.fillModeNonSolid = physicalDevice.features.fillModeNonSolid, + riveVulkanFeatures.fragmentStoresAndAtomics = + physicalDevice.features.fragmentStoresAndAtomics, + }; - { - printf("==== Vulkan GPU (%s): %s [", - physical_device_type_name(physicalDevice.properties.deviceType), - physicalDevice.properties.deviceName); - const char* prefix = ""; - if (riveVulkanFeatures.independentBlend) - printf("%sindependentBlend", std::exchange(prefix, ", ")); - if (riveVulkanFeatures.fillModeNonSolid) - printf("%sfillModeNonSolid", std::exchange(prefix, ", ")); - if (riveVulkanFeatures.fragmentStoresAndAtomics) - printf("%sfragmentStoresAndAtomics", std::exchange(prefix, ", ")); - printf("] ====\n"); - } - -#if 0 - printf("Extensions:\n"); - for (const auto& ext : physicalDevice.get_available_extensions()) - { - printf(" %s\n", ext.c_str()); - } -#endif - if (physicalDevice.enable_extension_if_present( - VK_EXT_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_EXTENSION_NAME) || - physicalDevice.enable_extension_if_present("VK_AMD_rasterization_order_attachment_access")) + if (featureSet != FeatureSet::coreOnly && + (physicalDevice.enable_extension_if_present( + VK_EXT_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_EXTENSION_NAME) || + physicalDevice.enable_extension_if_present( + "VK_AMD_rasterization_order_attachment_access"))) { constexpr static VkPhysicalDeviceRasterizationOrderAttachmentAccessFeaturesEXT rasterOrderFeatures = { @@ -194,10 +180,34 @@ std::tuple select_physical_devic if (physicalDevice.enable_extension_features_if_present(rasterOrderFeatures)) { riveVulkanFeatures.rasterizationOrderColorAttachmentAccess = true; - printf(" rasterizationOrderColorAttachmentAccess"); } } + printf("==== Vulkan GPU (%s): %s [ ", + physical_device_type_name(physicalDevice.properties.deviceType), + physicalDevice.properties.deviceName); + struct CommaSeparator + { + const char* m_separator = ""; + const char* operator*() { return std::exchange(m_separator, ", "); } + } commaSeparator; + if (riveVulkanFeatures.independentBlend) + printf("%sindependentBlend", *commaSeparator); + if (riveVulkanFeatures.fillModeNonSolid) + printf("%sfillModeNonSolid", *commaSeparator); + if (riveVulkanFeatures.fragmentStoresAndAtomics) + printf("%sfragmentStoresAndAtomics", *commaSeparator); + if (riveVulkanFeatures.rasterizationOrderColorAttachmentAccess) + printf("%srasterizationOrderColorAttachmentAccess", *commaSeparator); +#if 0 + printf("Extensions:\n"); + for (const auto& ext : physicalDevice.get_available_extensions()) + { + printf(" %s\n", ext.c_str()); + } +#endif + printf(" ] ====\n"); + return {physicalDevice, riveVulkanFeatures}; } } // namespace rive_vkb diff --git a/renderer/src/vulkan/render_context_vulkan_impl.cpp b/renderer/src/vulkan/render_context_vulkan_impl.cpp index b4b2006b..703c6d39 100644 --- a/renderer/src/vulkan/render_context_vulkan_impl.cpp +++ b/renderer/src/vulkan/render_context_vulkan_impl.cpp @@ -85,7 +85,7 @@ class RenderBufferVulkanImpl : public lite_rtti_overrideinfo().size); + memcpy(m_imageUploadBuffer->contents(), imageDataRGBA, m_imageUploadBuffer->info().size); + m_imageUploadBuffer->flushContents(); } bool hasUpdates() const { return m_imageUploadBuffer != nullptr; } @@ -143,12 +142,22 @@ class TextureVulkanImpl : public Texture .imageExtent = {width(), height(), 1}, }; - m_vk->insertImageMemoryBarrier(commandBuffer, - *m_texture, - VK_IMAGE_LAYOUT_UNDEFINED, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 0, - m_texture->info().mipLevels); + m_vk->imageMemoryBarrier(commandBuffer, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, + { + .srcAccessMask = VK_ACCESS_SHADER_READ_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .image = *m_texture, + .subresourceRange = + { + .baseMipLevel = 0, + .levelCount = m_texture->info().mipLevels, + }, + }); m_vk->CmdCopyBufferToImage(commandBuffer, *m_imageUploadBuffer, @@ -187,12 +196,22 @@ class TextureVulkanImpl : public Texture imageBlit.dstOffsets[0] = {0, 0, 0}; imageBlit.dstOffsets[1] = {dstSize.x, dstSize.y, 1}; - m_vk->insertImageMemoryBarrier(commandBuffer, - *m_texture, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - level - 1, - 1); + m_vk->imageMemoryBarrier(commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, + { + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .image = *m_texture, + .subresourceRange = + { + .baseMipLevel = level - 1, + .levelCount = 1, + }, + }); m_vk->CmdBlitImage(commandBuffer, *m_texture, @@ -204,20 +223,40 @@ class TextureVulkanImpl : public Texture VK_FILTER_LINEAR); } - m_vk->insertImageMemoryBarrier(commandBuffer, - *m_texture, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - 0, - mipLevels - 1); + m_vk->imageMemoryBarrier(commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + 0, + { + .srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .image = *m_texture, + .subresourceRange = + { + .baseMipLevel = 0, + .levelCount = mipLevels - 1, + }, + }); } - m_vk->insertImageMemoryBarrier(commandBuffer, - *m_texture, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - mipLevels - 1, - 1); + m_vk->imageMemoryBarrier(commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + 0, + { + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .image = *m_texture, + .subresourceRange = + { + .baseMipLevel = mipLevels - 1, + .levelCount = 1, + }, + }); m_imageUploadBuffer = nullptr; } @@ -389,7 +428,7 @@ class RenderContextVulkanImpl::ColorRampPipeline .loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, .initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - .finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, }; VkAttachmentReference attachmentReference = { .attachment = 0, @@ -634,8 +673,8 @@ class RenderContextVulkanImpl::TessellatePipeline .samples = VK_SAMPLE_COUNT_1_BIT, .loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, - .initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - .finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, }; VkAttachmentReference attachmentReference = { .attachment = 0, @@ -1028,13 +1067,16 @@ class RenderContextVulkanImpl::DrawPipelineLayout if (m_renderPasses[renderPassVariantIdx] == VK_NULL_HANDLE) { // Create the render pass. + VkAttachmentLoadOp colorLoadOp = LoadOpFromRenderPassVariant(renderPassVariantIdx); VkAttachmentDescription attachmentDescriptions[] = { { .format = FormatFromRenderPassVariant(renderPassVariantIdx), .samples = VK_SAMPLE_COUNT_1_BIT, - .loadOp = LoadOpFromRenderPassVariant(renderPassVariantIdx), + .loadOp = colorLoadOp, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, - .initialLayout = VK_IMAGE_LAYOUT_GENERAL, + .initialLayout = colorLoadOp == VK_ATTACHMENT_LOAD_OP_LOAD + ? VK_IMAGE_LAYOUT_GENERAL + : VK_IMAGE_LAYOUT_UNDEFINED, .finalLayout = VK_IMAGE_LAYOUT_GENERAL, }, { @@ -1042,7 +1084,7 @@ class RenderContextVulkanImpl::DrawPipelineLayout .samples = VK_SAMPLE_COUNT_1_BIT, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, - .initialLayout = VK_IMAGE_LAYOUT_GENERAL, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .finalLayout = VK_IMAGE_LAYOUT_GENERAL, }, { @@ -1050,7 +1092,7 @@ class RenderContextVulkanImpl::DrawPipelineLayout .samples = VK_SAMPLE_COUNT_1_BIT, .loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, - .initialLayout = VK_IMAGE_LAYOUT_GENERAL, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .finalLayout = VK_IMAGE_LAYOUT_GENERAL, }, { @@ -1058,7 +1100,7 @@ class RenderContextVulkanImpl::DrawPipelineLayout .samples = VK_SAMPLE_COUNT_1_BIT, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, - .initialLayout = VK_IMAGE_LAYOUT_GENERAL, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .finalLayout = VK_IMAGE_LAYOUT_GENERAL, }, }; @@ -1110,11 +1152,11 @@ class RenderContextVulkanImpl::DrawPipelineLayout .pColorAttachments = attachmentReferences, }; - if (m_interlockMode == gpu::InterlockMode::rasterOrdering) + if (m_interlockMode == gpu::InterlockMode::rasterOrdering && + m_vk->features.rasterizationOrderColorAttachmentAccess) { // With EXT_rasterization_order_attachment_access, we just need - // this flag and all "subpassLoad" dependencies are implicit. - assert(m_vk->features.rasterizationOrderColorAttachmentAccess); + // this flag and all subpass dependencies are implicit. subpassDescription.flags |= VK_SUBPASS_DESCRIPTION_RASTERIZATION_ORDER_ATTACHMENT_COLOR_ACCESS_BIT_EXT; } @@ -1127,10 +1169,11 @@ class RenderContextVulkanImpl::DrawPipelineLayout .pSubpasses = &subpassDescription, }; - if (m_interlockMode == gpu::InterlockMode::atomics) + if (!(subpassDescription.flags & + VK_SUBPASS_DESCRIPTION_RASTERIZATION_ORDER_ATTACHMENT_COLOR_ACCESS_BIT_EXT)) { - // Without EXT_rasterization_order_attachment_access (aka atomic mode), - // "subpassLoad" calls require explicit dependencies and barriers. + // Without EXT_rasterization_order_attachment_access, we need to + // declare a "subpassLoad" dependency. constexpr static VkSubpassDependency kSubpassLoadDependency = { .srcSubpass = 0, .dstSubpass = 0, @@ -1596,9 +1639,9 @@ class RenderContextVulkanImpl::DrawPipeline .pAttachments = blendColorAttachments, }; - if (interlockMode == gpu::InterlockMode::rasterOrdering) + if (interlockMode == gpu::InterlockMode::rasterOrdering && + m_vk->features.rasterizationOrderColorAttachmentAccess) { - assert(m_vk->features.rasterizationOrderColorAttachmentAccess); pipelineColorBlendStateCreateInfo.flags |= VK_PIPELINE_COLOR_BLEND_STATE_CREATE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_BIT_EXT; } @@ -1674,16 +1717,15 @@ RenderContextVulkanImpl::RenderContextVulkanImpl(VkInstance instance, m_tessSpanBufferRing(m_vk, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, vkutil::Mappability::writeOnly), m_triangleBufferRing(m_vk, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, vkutil::Mappability::writeOnly), m_colorRampPipeline(std::make_unique(m_vk)), - m_tessellatePipeline(std::make_unique(m_vk)) + m_tessellatePipeline(std::make_unique(m_vk)), + m_descriptorSetPoolPool(make_rcp>(m_vk)) { m_platformFeatures.supportsRasterOrdering = features.rasterizationOrderColorAttachmentAccess; m_platformFeatures.supportsFragmentShaderAtomics = features.fragmentStoresAndAtomics; m_platformFeatures.invertOffscreenY = false; m_platformFeatures.uninvertOnScreenY = true; - VkPhysicalDeviceProperties physicalDeviceProperties; - m_vk->GetPhysicalDeviceProperties(m_vk->physicalDevice, &physicalDeviceProperties); - if (physicalDeviceProperties.vendorID == vkutil::kVendorQualcomm) + if (features.vendorID == vkutil::kVendorQualcomm) { // Qualcomm advertises EXT_rasterization_order_attachment_access, but it's // slow. Use atomics instead on this platform. @@ -1691,6 +1733,13 @@ RenderContextVulkanImpl::RenderContextVulkanImpl(VkInstance instance, // Pixel4 struggles with fine-grained fp16 path IDs. m_platformFeatures.pathIDGranularity = 2; } + else if (features.vendorID == vkutil::kVendorARM) + { + // This is undocumented, but raster ordering always works on ARM Mali GPUs + // if you define a subpass dependency, even without + // EXT_rasterization_order_attachment_access. + m_platformFeatures.supportsRasterOrdering = true; + } } void RenderContextVulkanImpl::initGPUObjects() @@ -1732,9 +1781,8 @@ void RenderContextVulkanImpl::initGPUObjects() .usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT, }, vkutil::Mappability::writeOnly); - memcpy(vkutil::ScopedBufferFlush(*m_tessSpanIndexBuffer), - gpu::kTessSpanIndices, - sizeof(gpu::kTessSpanIndices)); + memcpy(m_tessSpanIndexBuffer->contents(), gpu::kTessSpanIndices, sizeof(gpu::kTessSpanIndices)); + m_tessSpanIndexBuffer->flushContents(); m_pathPatchVertexBuffer = m_vk->makeBuffer( { @@ -1749,8 +1797,10 @@ void RenderContextVulkanImpl::initGPUObjects() }, vkutil::Mappability::writeOnly); gpu::GeneratePatchBufferData( - vkutil::ScopedBufferFlush(*m_pathPatchVertexBuffer).as(), - vkutil::ScopedBufferFlush(*m_pathPatchIndexBuffer).as()); + reinterpret_cast(m_pathPatchVertexBuffer->contents()), + reinterpret_cast(m_pathPatchIndexBuffer->contents())); + m_pathPatchVertexBuffer->flushContents(); + m_pathPatchIndexBuffer->flushContents(); m_imageRectVertexBuffer = m_vk->makeBuffer( { @@ -1758,18 +1808,20 @@ void RenderContextVulkanImpl::initGPUObjects() .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, }, vkutil::Mappability::writeOnly); - memcpy(vkutil::ScopedBufferFlush(*m_imageRectVertexBuffer), + memcpy(m_imageRectVertexBuffer->contents(), gpu::kImageRectVertices, sizeof(gpu::kImageRectVertices)); + m_imageRectVertexBuffer->flushContents(); m_imageRectIndexBuffer = m_vk->makeBuffer( { .size = sizeof(gpu::kImageRectIndices), .usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT, }, vkutil::Mappability::writeOnly); - memcpy(vkutil::ScopedBufferFlush(*m_imageRectIndexBuffer), + memcpy(m_imageRectIndexBuffer->contents(), gpu::kImageRectIndices, sizeof(gpu::kImageRectIndices)); + m_imageRectIndexBuffer->flushContents(); } RenderContextVulkanImpl::~RenderContextVulkanImpl() @@ -1883,9 +1935,8 @@ constexpr static uint32_t kMaxStorageBufferUpdates = 6; constexpr static uint32_t kMaxDescriptorSets = 3 + kMaxImageTextureUpdates; } // namespace descriptor_pool_limits -RenderContextVulkanImpl::DescriptorSetPool::DescriptorSetPool( - RenderContextVulkanImpl* renderContextImpl) : - RenderingResource(renderContextImpl->m_vk), m_renderContextImpl(renderContextImpl) +RenderContextVulkanImpl::DescriptorSetPool::DescriptorSetPool(rcp vk) : + m_vk(std::move(vk)) { VkDescriptorPoolSize descriptorPoolSizes[] = { { @@ -1931,7 +1982,6 @@ RenderContextVulkanImpl::DescriptorSetPool::DescriptorSetPool( RenderContextVulkanImpl::DescriptorSetPool::~DescriptorSetPool() { - freeDescriptorSets(); m_vk->DestroyDescriptorPool(m_vk->device, m_vkDescriptorPool, nullptr); } @@ -1945,55 +1995,19 @@ VkDescriptorSet RenderContextVulkanImpl::DescriptorSetPool::allocateDescriptorSe .pSetLayouts = &layout, }; - VK_CHECK(m_vk->AllocateDescriptorSets(m_vk->device, - &descriptorSetAllocateInfo, - &m_descriptorSets.emplace_back())); + VkDescriptorSet descriptorSet; + VK_CHECK( + m_vk->AllocateDescriptorSets(m_vk->device, &descriptorSetAllocateInfo, &descriptorSet)); - return m_descriptorSets.back(); + return descriptorSet; } -void RenderContextVulkanImpl::DescriptorSetPool::freeDescriptorSets() +void RenderContextVulkanImpl::DescriptorSetPool::reset() { m_vk->ResetDescriptorPool(m_vk->device, m_vkDescriptorPool, 0); } -void RenderContextVulkanImpl::DescriptorSetPool::onRefCntReachedZero() const -{ - constexpr static uint32_t kMaxDescriptorSetPoolsInPool = 64; - - if (m_renderContextImpl->m_descriptorSetPoolPool.size() < kMaxDescriptorSetPoolsInPool) - { - // Hang out in the renderContext's m_descriptorSetPoolPool until in-flight - // command buffers have finished using our descriptors. - m_renderContextImpl->m_descriptorSetPoolPool.emplace_back( - const_cast(this), - m_vk->currentFrameIdx()); - } - else - { - delete this; - } -} - -rcp RenderContextVulkanImpl::makeDescriptorSetPool() -{ - rcp pool; - if (!m_descriptorSetPoolPool.empty() && - m_descriptorSetPoolPool.front().expirationFrameIdx <= m_vk->currentFrameIdx()) - { - pool = ref_rcp(m_descriptorSetPoolPool.front().resource.release()); - pool->freeDescriptorSets(); - m_descriptorSetPoolPool.pop_front(); - } - else - { - pool = make_rcp(this); - } - assert(pool->debugging_refcnt() == 1); - return pool; -} - -VkImageView RenderTargetVulkan::ensureOffscreenColorTextureView(VkCommandBuffer commandBuffer) +vkutil::TextureView* RenderTargetVulkan::ensureOffscreenColorTextureView() { if (m_offscreenColorTextureView == nullptr) { @@ -2004,17 +2018,13 @@ VkImageView RenderTargetVulkan::ensureOffscreenColorTextureView(VkCommandBuffer VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, }); - m_vk->insertImageMemoryBarrier(commandBuffer, - *m_offscreenColorTexture, - VK_IMAGE_LAYOUT_UNDEFINED, - VK_IMAGE_LAYOUT_GENERAL); m_offscreenColorTextureView = m_vk->makeTextureView(m_offscreenColorTexture); } - return *m_offscreenColorTextureView; + return m_offscreenColorTextureView.get(); } -VkImageView RenderTargetVulkan::ensureCoverageTextureView(VkCommandBuffer commandBuffer) +vkutil::TextureView* RenderTargetVulkan::ensureCoverageTextureView() { if (m_coverageTextureView == nullptr) { @@ -2025,17 +2035,13 @@ VkImageView RenderTargetVulkan::ensureCoverageTextureView(VkCommandBuffer comman VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT, }); - m_vk->insertImageMemoryBarrier(commandBuffer, - *m_coverageTexture, - VK_IMAGE_LAYOUT_UNDEFINED, - VK_IMAGE_LAYOUT_GENERAL); m_coverageTextureView = m_vk->makeTextureView(m_coverageTexture); } - return *m_coverageTextureView; + return m_coverageTextureView.get(); } -VkImageView RenderTargetVulkan::ensureClipTextureView(VkCommandBuffer commandBuffer) +vkutil::TextureView* RenderTargetVulkan::ensureClipTextureView() { if (m_clipTextureView == nullptr) { @@ -2046,17 +2052,13 @@ VkImageView RenderTargetVulkan::ensureClipTextureView(VkCommandBuffer commandBuf VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT, }); - m_vk->insertImageMemoryBarrier(commandBuffer, - *m_clipTexture, - VK_IMAGE_LAYOUT_UNDEFINED, - VK_IMAGE_LAYOUT_GENERAL); m_clipTextureView = m_vk->makeTextureView(m_clipTexture); } - return *m_clipTextureView; + return m_clipTextureView.get(); } -VkImageView RenderTargetVulkan::ensureScratchColorTextureView(VkCommandBuffer commandBuffer) +vkutil::TextureView* RenderTargetVulkan::ensureScratchColorTextureView() { if (m_scratchColorTextureView == nullptr) { @@ -2067,18 +2069,13 @@ VkImageView RenderTargetVulkan::ensureScratchColorTextureView(VkCommandBuffer co VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT, }); - m_vk->insertImageMemoryBarrier(commandBuffer, - *m_scratchColorTexture, - VK_IMAGE_LAYOUT_UNDEFINED, - VK_IMAGE_LAYOUT_GENERAL); - m_scratchColorTextureView = m_vk->makeTextureView(m_scratchColorTexture); } - return *m_scratchColorTextureView; + return m_scratchColorTextureView.get(); } -VkImageView RenderTargetVulkan::ensureCoverageAtomicTextureView(VkCommandBuffer commandBuffer) +vkutil::TextureView* RenderTargetVulkan::ensureCoverageAtomicTextureView() { if (m_coverageAtomicTextureView == nullptr) { @@ -2089,38 +2086,107 @@ VkImageView RenderTargetVulkan::ensureCoverageAtomicTextureView(VkCommandBuffer VK_IMAGE_USAGE_TRANSFER_DST_BIT, // For vkCmdClearColorImage }); - m_vk->insertImageMemoryBarrier(commandBuffer, - *m_coverageAtomicTexture, - VK_IMAGE_LAYOUT_UNDEFINED, - VK_IMAGE_LAYOUT_GENERAL); - m_coverageAtomicTextureView = m_vk->makeTextureView(m_coverageAtomicTexture); } - return *m_coverageAtomicTextureView; + return m_coverageAtomicTextureView.get(); } void RenderContextVulkanImpl::flush(const FlushDescriptor& desc) { + constexpr static VkDeviceSize zeroOffset[1] = {0}; + constexpr static uint32_t zeroOffset32[1] = {0}; + if (desc.interlockMode == gpu::InterlockMode::depthStencil) { return; } auto commandBuffer = reinterpret_cast(desc.externalCommandBuffer); - rcp descriptorSetPool = makeDescriptorSetPool(); + rcp descriptorSetPool = m_descriptorSetPoolPool->make(); - constexpr static VkDeviceSize zeroOffset[1] = {0}; - constexpr static uint32_t zeroOffset32[1] = {0}; + // Apply pending texture updates. + if (m_nullImageTexture->hasUpdates()) + { + m_nullImageTexture->synchronize(commandBuffer); + } + for (const DrawBatch& batch : *desc.drawList) + { + if (auto imageTextureVulkan = static_cast(batch.imageTexture)) + { + if (imageTextureVulkan->hasUpdates()) + { + imageTextureVulkan->synchronize(commandBuffer); + } + } + } + + VulkanContext::TextureAccess lastGradTextureAccess, lastTessTextureAccess; + lastTessTextureAccess = lastGradTextureAccess = { + // The last thing to access the gradient and tessellation textures was the + // previous flush. Make sure our barriers account for this so we don't + // overwrite these textures before previous draws are done reading them. + .pipelineStages = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + .accessMask = VK_ACCESS_SHADER_READ_BIT, + // Transition from an "UNDEFINED" layout because we don't care about + // preserving color ramp content from the previous frame. + .layout = VK_IMAGE_LAYOUT_UNDEFINED, + }; + + // Copy the simple color ramps to the gradient texture. + if (desc.simpleGradTexelsHeight > 0) + { + // Wait for previous accesses to finish before copying to the gradient + // texture. + lastGradTextureAccess = + m_vk->simpleImageMemoryBarrier(commandBuffer, + lastGradTextureAccess, + { + .pipelineStages = VK_PIPELINE_STAGE_TRANSFER_BIT, + .accessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + }, + *m_gradientTexture); + + VkBufferImageCopy bufferImageCopy{ + .bufferOffset = desc.simpleGradDataOffsetInBytes, + .bufferRowLength = gpu::kGradTextureWidth, + .imageSubresource = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .layerCount = 1, + }, + .imageExtent = + { + desc.simpleGradTexelsWidth, + desc.simpleGradTexelsHeight, + 1, + }, + }; - m_vk->insertImageMemoryBarrier(commandBuffer, + m_vk->CmdCopyBufferToImage(commandBuffer, + m_simpleColorRampsBufferRing.vkBufferAt(m_bufferRingIdx), *m_gradientTexture, - VK_IMAGE_LAYOUT_UNDEFINED, - VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + &bufferImageCopy); + } // Render the complex color ramps to the gradient texture. if (desc.complexGradSpanCount > 0) { + // Wait for previous accesses to finish before rendering to the gradient + // texture. + lastGradTextureAccess = m_vk->simpleImageMemoryBarrier( + commandBuffer, + lastGradTextureAccess, + { + .pipelineStages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .accessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + }, + *m_gradientTexture); + VkRect2D renderArea = { .offset = {0, static_cast(desc.complexGradRowsTop)}, .extent = {gpu::kGradTextureWidth, desc.complexGradRowsHeight}, @@ -2174,53 +2240,38 @@ void RenderContextVulkanImpl::flush(const FlushDescriptor& desc) m_vk->CmdDraw(commandBuffer, 4, desc.complexGradSpanCount, 0, 0); m_vk->CmdEndRenderPass(commandBuffer); - } - - m_vk->insertImageMemoryBarrier(commandBuffer, - *m_gradientTexture, - VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - - // Copy the simple color ramps to the gradient texture. - if (desc.simpleGradTexelsHeight > 0) - { - VkBufferImageCopy bufferImageCopy{ - .bufferOffset = desc.simpleGradDataOffsetInBytes, - .bufferRowLength = gpu::kGradTextureWidth, - .imageSubresource = - { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .layerCount = 1, - }, - .imageExtent = - { - desc.simpleGradTexelsWidth, - desc.simpleGradTexelsHeight, - 1, - }, - }; - m_vk->CmdCopyBufferToImage(commandBuffer, - m_simpleColorRampsBufferRing.vkBufferAt(m_bufferRingIdx), - *m_gradientTexture, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, - &bufferImageCopy); + // The render pass transitioned the gradient texture to + // VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL. + lastGradTextureAccess.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; } - m_vk->insertImageMemoryBarrier(commandBuffer, - *m_gradientTexture, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - - m_vk->insertImageMemoryBarrier(commandBuffer, - *m_tessVertexTexture, - VK_IMAGE_LAYOUT_UNDEFINED, - VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + // Ensure the gradient texture has finished updating before the path fragment + // shaders read it. + m_vk->simpleImageMemoryBarrier(commandBuffer, + lastGradTextureAccess, + { + .pipelineStages = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + .accessMask = VK_ACCESS_SHADER_READ_BIT, + .layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + }, + *m_gradientTexture); // Tessellate all curves into vertices in the tessellation texture. if (desc.tessVertexSpanCount > 0) { + // Don't render new vertices until the previous flush has finished using + // the tessellation texture. + lastTessTextureAccess = m_vk->simpleImageMemoryBarrier( + commandBuffer, + lastTessTextureAccess, + { + .pipelineStages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .accessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + }, + *m_tessVertexTexture); + VkRect2D renderArea = { .extent = {gpu::kTessTextureWidth, desc.tessDataHeight}, }; @@ -2303,29 +2354,23 @@ void RenderContextVulkanImpl::flush(const FlushDescriptor& desc) 0); m_vk->CmdEndRenderPass(commandBuffer); - } - m_vk->insertImageMemoryBarrier(commandBuffer, - *m_tessVertexTexture, - VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - - // Apply pending texture updates. - if (m_nullImageTexture->hasUpdates()) - { - m_nullImageTexture->synchronize(commandBuffer); - } - for (const DrawBatch& batch : *desc.drawList) - { - if (auto imageTextureVulkan = static_cast(batch.imageTexture)) - { - if (imageTextureVulkan->hasUpdates()) - { - imageTextureVulkan->synchronize(commandBuffer); - } - } + // The render pass transitioned the tessellation texture to + // VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL. + lastTessTextureAccess.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; } + // Ensure the tessellation texture has finished rendering before the path + // vertex shaders read it. + m_vk->simpleImageMemoryBarrier(commandBuffer, + lastTessTextureAccess, + { + .pipelineStages = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT, + .accessMask = VK_ACCESS_SHADER_READ_BIT, + .layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + }, + *m_tessVertexTexture); + auto pipelineLayoutOptions = DrawPipelineLayoutOptions::none; if (m_vk->features.independentBlend && desc.interlockMode == gpu::InterlockMode::atomics && !(desc.combinedShaderFeatures & gpu::ShaderFeatures::ENABLE_ADVANCED_BLEND)) @@ -2343,55 +2388,77 @@ void RenderContextVulkanImpl::flush(const FlushDescriptor& desc) std::make_unique(this, desc.interlockMode, pipelineLayoutOptions); } DrawPipelineLayout& pipelineLayout = *m_drawPipelineLayouts[pipelineLayoutIdx]; + bool fixedFunctionColorBlend = + pipelineLayout.options() & DrawPipelineLayoutOptions::fixedFunctionColorBlend; auto* renderTarget = static_cast(desc.renderTarget); - auto targetView = + vkutil::TextureView* colorView = renderTarget->targetViewContainsUsageFlag(VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT) || - (pipelineLayout.options() & DrawPipelineLayoutOptions::fixedFunctionColorBlend) + fixedFunctionColorBlend ? renderTarget->targetTextureView() - : renderTarget->ensureOffscreenColorTextureView(commandBuffer); - auto clipView = renderTarget->ensureClipTextureView(commandBuffer); - auto scratchColorTextureView = desc.interlockMode == gpu::InterlockMode::atomics - ? VK_NULL_HANDLE - : renderTarget->ensureScratchColorTextureView(commandBuffer); - auto coverageTextureView = desc.interlockMode == gpu::InterlockMode::atomics - ? renderTarget->ensureCoverageAtomicTextureView(commandBuffer) - : renderTarget->ensureCoverageTextureView(commandBuffer); - - if (desc.colorLoadAction == gpu::LoadAction::preserveRenderTarget && - targetView == renderTarget->offscreenColorTextureView()) + : renderTarget->ensureOffscreenColorTextureView(); + vkutil::TextureView* clipView = renderTarget->ensureClipTextureView(); + vkutil::TextureView* scratchColorTextureView = + desc.interlockMode == gpu::InterlockMode::atomics + ? VK_NULL_HANDLE + : renderTarget->ensureScratchColorTextureView(); + vkutil::TextureView* coverageTextureView = desc.interlockMode == gpu::InterlockMode::atomics + ? renderTarget->ensureCoverageAtomicTextureView() + : renderTarget->ensureCoverageTextureView(); + + VulkanContext::TextureAccess initialColorAccess = renderTarget->targetLastAccess(); + + if (desc.colorLoadAction != gpu::LoadAction::preserveRenderTarget) { - // Copy the target into our offscreen color texture before rendering. + // No need to preserve what was in the render target before. + initialColorAccess.layout = VK_IMAGE_LAYOUT_UNDEFINED; + } + else if (colorView == renderTarget->offscreenColorTextureView()) + { + // The access we just declared was actually for the *renderTarget* texture, + // not the actual color attachment we will render to, which will be the the + // offscreen texture. + VulkanContext::TextureAccess initialRenderTargetAccess = initialColorAccess; - auto targetTexture = renderTarget->targetTexture(); - // we know the offscreenColorTexture exists because of the if condition - auto offScreenTexture = renderTarget->offscreenColorTexture(); + // The initial *color attachment* (aka offscreenColorTexture) access is + // the previous frame's copy into the renderTarget texture. + initialColorAccess = { + .pipelineStages = VK_PIPELINE_STAGE_TRANSFER_BIT, + .accessMask = VK_ACCESS_TRANSFER_READ_BIT, + .layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + }; - m_vk->insertImageMemoryBarrier(commandBuffer, - targetTexture, - VK_IMAGE_LAYOUT_GENERAL, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + // Copy the target into our offscreen color texture before rendering. + VkImageMemoryBarrier imageMemoryBarriers[] = { + { + .srcAccessMask = initialRenderTargetAccess.accessMask, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .oldLayout = initialRenderTargetAccess.layout, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .image = renderTarget->targetTexture(), + }, + { + .srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .dstAccessMask = initialColorAccess.accessMask, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = initialColorAccess.layout, + .image = renderTarget->offscreenColorTexture(), + }, + }; - m_vk->insertImageMemoryBarrier(commandBuffer, - offScreenTexture, - VK_IMAGE_LAYOUT_GENERAL, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + m_vk->imageMemoryBarriers( + commandBuffer, + initialRenderTargetAccess.pipelineStages | VK_PIPELINE_STAGE_TRANSFER_BIT, + initialColorAccess.pipelineStages | VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, + std::size(imageMemoryBarriers), + imageMemoryBarriers); m_vk->blitSubRect(commandBuffer, - targetTexture, - offScreenTexture, + renderTarget->targetTexture(), + renderTarget->offscreenColorTexture(), desc.renderTargetUpdateBounds); - - m_vk->insertImageMemoryBarrier(commandBuffer, - targetTexture, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - VK_IMAGE_LAYOUT_GENERAL); - - m_vk->insertImageMemoryBarrier(commandBuffer, - offScreenTexture, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - VK_IMAGE_LAYOUT_GENERAL); } int renderPassVariantIdx = @@ -2400,10 +2467,10 @@ void RenderContextVulkanImpl::flush(const FlushDescriptor& desc) VkRenderPass vkRenderPass = pipelineLayout.renderPassAt(renderPassVariantIdx); VkImageView imageViews[] = { - targetView, - clipView, - scratchColorTextureView, - desc.interlockMode == gpu::InterlockMode::atomics ? VK_NULL_HANDLE : coverageTextureView, + *colorView, + *clipView, + scratchColorTextureView != nullptr ? *scratchColorTextureView : VK_NULL_HANDLE, + coverageTextureView != nullptr ? *coverageTextureView : VK_NULL_HANDLE, }; static_assert(COLOR_PLANE_IDX == 0); @@ -2436,32 +2503,45 @@ void RenderContextVulkanImpl::flush(const FlushDescriptor& desc) static_assert(SCRATCH_COLOR_PLANE_IDX == 2); static_assert(COVERAGE_PLANE_IDX == 3); + // Ensure any previous accesses to the color texture complete before we begin + // rendering. + m_vk->simpleImageMemoryBarrier( + commandBuffer, + initialColorAccess, + { + // "Load" operations always occur in + // VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT. + .pipelineStages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .accessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .layout = VK_IMAGE_LAYOUT_GENERAL, + }, + colorView->info().image); + bool needsBarrierBeforeNextDraw = false; if (desc.interlockMode == gpu::InterlockMode::atomics) { - // If the color attachment will be cleared, make sure we get a barrier on - // it before shaders access it via subpassLoad(). - needsBarrierBeforeNextDraw = -#if 0 - // TODO: If we end up using HW blend when not using advanced blend, we - // don't need a barrier after the clear. - desc.combinedShaderFeatures & - gpu::ShaderFeatures::ENABLE_ADVANCED_BLEND && -#endif - desc.colorLoadAction == gpu::LoadAction::clear; - // Clear the coverage texture, which is not an attachment. - m_vk->insertImageMemoryBarrier(commandBuffer, - renderTarget->coverageAtomicTexture(), - VK_IMAGE_LAYOUT_GENERAL, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - VkImageSubresourceRange clearRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .levelCount = 1, .layerCount = 1, }; + // Don't clear the coverage texture until shaders in the previous flush + // have finished using it. + m_vk->imageMemoryBarrier( + commandBuffer, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, + { + .srcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .image = renderTarget->coverageAtomicTexture(), + }); + m_vk->CmdClearColorImage(commandBuffer, renderTarget->coverageAtomicTexture(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, @@ -2469,10 +2549,36 @@ void RenderContextVulkanImpl::flush(const FlushDescriptor& desc) 1, &clearRange); - m_vk->insertImageMemoryBarrier(commandBuffer, - renderTarget->coverageAtomicTexture(), - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - VK_IMAGE_LAYOUT_GENERAL); + // Don't use the coverage texture in shaders until the clear finishes. + m_vk->imageMemoryBarrier( + commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + 0, + { + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .image = renderTarget->coverageAtomicTexture(), + }); + + // Ensure all reads to any internal attachments complete before we execute the + // load operations. + m_vk->memoryBarrier(commandBuffer, + // "Load" operations always occur in + // VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT. + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + 0, + { + .srcAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + }); + + // Make sure we get a barrier between load operations and the first color + // attachment accesses. + needsBarrierBeforeNextDraw = true; } VkRenderPassBeginInfo renderPassBeginInfo = { @@ -2584,7 +2690,7 @@ void RenderContextVulkanImpl::flush(const FlushDescriptor& desc) VkDescriptorSet inputAttachmentDescriptorSet = descriptorSetPool->allocateDescriptorSet(pipelineLayout.plsLayout()); - if (!(pipelineLayoutOptions & DrawPipelineLayoutOptions::fixedFunctionColorBlend)) + if (!fixedFunctionColorBlend) { m_vk->updateImageDescriptorSets(inputAttachmentDescriptorSet, { @@ -2592,7 +2698,7 @@ void RenderContextVulkanImpl::flush(const FlushDescriptor& desc) .descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, }, {{ - .imageView = targetView, + .imageView = *colorView, .imageLayout = VK_IMAGE_LAYOUT_GENERAL, }}); } @@ -2603,7 +2709,7 @@ void RenderContextVulkanImpl::flush(const FlushDescriptor& desc) .descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, }, {{ - .imageView = clipView, + .imageView = *clipView, .imageLayout = VK_IMAGE_LAYOUT_GENERAL, }}); @@ -2615,7 +2721,7 @@ void RenderContextVulkanImpl::flush(const FlushDescriptor& desc) .descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, }, {{ - .imageView = scratchColorTextureView, + .imageView = *scratchColorTextureView, .imageLayout = VK_IMAGE_LAYOUT_GENERAL, }}); } @@ -2629,7 +2735,7 @@ void RenderContextVulkanImpl::flush(const FlushDescriptor& desc) : VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, }, {{ - .imageView = coverageTextureView, + .imageView = *coverageTextureView, .imageLayout = VK_IMAGE_LAYOUT_GENERAL, }}); @@ -2681,7 +2787,7 @@ void RenderContextVulkanImpl::flush(const FlushDescriptor& desc) { // We ran out of room for image texture updates. Allocate a new // pool. - descriptorSetPool = makeDescriptorSetPool(); + descriptorSetPool = m_descriptorSetPoolPool->make(); imageTextureUpdateCount = 0; } @@ -2756,23 +2862,14 @@ void RenderContextVulkanImpl::flush(const FlushDescriptor& desc) if (needsBarrierBeforeNextDraw) { assert(desc.interlockMode == gpu::InterlockMode::atomics); - - VkMemoryBarrier memoryBarrier = { - .sType = VkStructureType::VK_STRUCTURE_TYPE_MEMORY_BARRIER, - .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, - .dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT, - }; - - m_vk->CmdPipelineBarrier(commandBuffer, - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, - VK_DEPENDENCY_BY_REGION_BIT, - 1, - &memoryBarrier, - 0, - nullptr, - 0, - nullptr); + m_vk->memoryBarrier(commandBuffer, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + VK_DEPENDENCY_BY_REGION_BIT, + { + .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT, + }); } switch (drawType) @@ -2869,37 +2966,63 @@ void RenderContextVulkanImpl::flush(const FlushDescriptor& desc) m_vk->CmdEndRenderPass(commandBuffer); - if (targetView == renderTarget->offscreenColorTextureView()) + VulkanContext::TextureAccess finalRenderTargetAccess = { + // "Store" operations always occur in + // VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT. + .pipelineStages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .accessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .layout = VK_IMAGE_LAYOUT_GENERAL, + }; + + if (colorView == renderTarget->offscreenColorTextureView()) { - // Copy our offscreen color texture back to the render target now that we've finished - // rendering. - auto dstImage = renderTarget->targetTexture(); - // we know the offscreenColorTexture exists because of the if condition - auto offScreenTexture = renderTarget->offscreenColorTexture(); - - m_vk->insertImageMemoryBarrier(commandBuffer, - offScreenTexture, - VK_IMAGE_LAYOUT_GENERAL, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); - - m_vk->insertImageMemoryBarrier(commandBuffer, - dstImage, - VK_IMAGE_LAYOUT_GENERAL, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - - m_vk->blitSubRect(commandBuffer, offScreenTexture, dstImage, desc.renderTargetUpdateBounds); - - m_vk->insertImageMemoryBarrier(commandBuffer, - dstImage, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - VK_IMAGE_LAYOUT_GENERAL); - - m_vk->insertImageMemoryBarrier(commandBuffer, - offScreenTexture, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - VK_IMAGE_LAYOUT_GENERAL); + // The access we just declared was actually for the offscreen texture, not + // the final target. + VulkanContext::TextureAccess finalOffscreenAccess = finalRenderTargetAccess; + + // The final *renderTarget* access will be a copy from the offscreen + // texture. + finalRenderTargetAccess = { + .pipelineStages = VK_PIPELINE_STAGE_TRANSFER_BIT, + .accessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + }; + + // Copy our offscreen color texture back to the render target now that + // we've finished rendering. + VkImageMemoryBarrier imageMemoryBarriers[] = { + { + .srcAccessMask = finalOffscreenAccess.accessMask, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .oldLayout = finalOffscreenAccess.layout, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .image = renderTarget->offscreenColorTexture(), + }, + { + .srcAccessMask = VK_ACCESS_NONE, + .dstAccessMask = finalRenderTargetAccess.accessMask, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = finalRenderTargetAccess.layout, + .image = renderTarget->targetTexture(), + }, + }; + + m_vk->imageMemoryBarriers( + commandBuffer, + finalOffscreenAccess.pipelineStages | VK_PIPELINE_STAGE_TRANSFER_BIT, + finalRenderTargetAccess.pipelineStages | VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, + std::size(imageMemoryBarriers), + imageMemoryBarriers); + + m_vk->blitSubRect(commandBuffer, + renderTarget->offscreenColorTexture(), + renderTarget->targetTexture(), + desc.renderTargetUpdateBounds); } + renderTarget->setTargetLastAccess(finalRenderTargetAccess); + if (desc.isFinalFlushOfFrame) { m_frameCompletionFences[m_bufferRingIdx] = ref_rcp(desc.frameCompletionFence); diff --git a/renderer/src/vulkan/vkutil.cpp b/renderer/src/vulkan/vkutil.cpp index ddd9665b..376bc355 100644 --- a/renderer/src/vulkan/vkutil.cpp +++ b/renderer/src/vulkan/vkutil.cpp @@ -82,13 +82,18 @@ void Buffer::init() } } -void Buffer::flushMappedContents(size_t updatedSizeInBytes) +void Buffer::flushContents(size_t updatedSizeInBytes) { // Leave the buffer constantly mapped and let the OS/drivers handle the // rest. vmaFlushAllocation(m_vk->vmaAllocator, m_vmaAllocation, 0, updatedSizeInBytes); } +void Buffer::invalidateContents(size_t updatedSizeInBytes) +{ + vmaInvalidateAllocation(m_vk->vmaAllocator, m_vmaAllocation, 0, updatedSizeInBytes); +} + BufferRing::BufferRing(rcp vk, VkBufferUsageFlags usage, Mappability mappability, @@ -136,10 +141,10 @@ void* BufferRing::contentsAt(int bufferRingIdx, size_t dirtySize) return m_buffers[bufferRingIdx]->contents(); } -void BufferRing::flushMappedContentsAt(int bufferRingIdx) +void BufferRing::flushContentsAt(int bufferRingIdx) { assert(m_pendingFlushSize > 0); - m_buffers[bufferRingIdx]->flushMappedContents(m_pendingFlushSize); + m_buffers[bufferRingIdx]->flushContents(m_pendingFlushSize); m_pendingFlushSize = 0; } diff --git a/renderer/src/vulkan/vkutil_resource_pool.cpp b/renderer/src/vulkan/vkutil_resource_pool.cpp new file mode 100644 index 00000000..13ac9d80 --- /dev/null +++ b/renderer/src/vulkan/vkutil_resource_pool.cpp @@ -0,0 +1,83 @@ +/* + * Copyright 2024 Rive + */ + +#include "rive/renderer/vulkan/vkutil_resource_pool.hpp" + +namespace rive::gpu::vkutil +{ +ResourceFactory::ResourceFactory(rcp vk, uint32_t queueFamilyIndex) : + m_vk(std::move(vk)) +{ + VkCommandPoolCreateInfo commandPoolCreateInfo = { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .flags = VkCommandPoolCreateFlagBits::VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, + .queueFamilyIndex = queueFamilyIndex, + }; + VK_CHECK( + m_vk->CreateCommandPool(m_vk->device, &commandPoolCreateInfo, nullptr, &m_vkCommandPool)); +} + +ResourceFactory::~ResourceFactory() +{ + m_vk->DestroyCommandPool(m_vk->device, m_vkCommandPool, nullptr); +} + +CommandBuffer::CommandBuffer(rcp vk, VkCommandPool vkCommandPool) : + m_vk(std::move(vk)), m_vkCommandPool(vkCommandPool) +{ + VkCommandBufferAllocateInfo commandBufferAllocateInfo = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = m_vkCommandPool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1, + }; + VK_CHECK( + m_vk->AllocateCommandBuffers(m_vk->device, &commandBufferAllocateInfo, &m_vkCommandBuffer)); +} + +CommandBuffer::~CommandBuffer() +{ + m_vk->FreeCommandBuffers(m_vk->device, m_vkCommandPool, 1, &m_vkCommandBuffer); +} + +void CommandBuffer::reset() { m_vk->ResetCommandBuffer(m_vkCommandBuffer, {}); } + +Semaphore::Semaphore(rcp vk) : m_vk(std::move(vk)) +{ + VkSemaphoreCreateInfo semaphoreCreateInfo = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + }; + VK_CHECK(m_vk->CreateSemaphore(m_vk->device, &semaphoreCreateInfo, nullptr, &m_vkSemaphore)); +} + +Semaphore::~Semaphore() { m_vk->DestroySemaphore(m_vk->device, m_vkSemaphore, nullptr); } + +void Semaphore::reset() +{ + // Semaphores get reset automatically afrer a wait operation: + // + // "semaphore wait operations set the semaphores created with a + // VkSemaphoreType of VK_SEMAPHORE_TYPE_BINARY to the unsignaled state" +} + +Fence::Fence(rcp vk) : m_vk(std::move(vk)) +{ + VkFenceCreateInfo fenceInfo = { + .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, + }; + VK_CHECK(m_vk->CreateFence(m_vk->device, &fenceInfo, nullptr, &m_vkFence)); +} + +Fence::~Fence() { m_vk->DestroyFence(m_vk->device, m_vkFence, nullptr); } + +void Fence::reset() { m_vk->ResetFences(m_vk->device, 1, &m_vkFence); } + +void Fence::wait() +{ + while (m_vk->WaitForFences(m_vk->device, 1, &m_vkFence, VK_TRUE, 1000) == VK_TIMEOUT) + { + // Keep waiting. + } +} +} // namespace rive::gpu::vkutil diff --git a/renderer/src/vulkan/vulkan_context.cpp b/renderer/src/vulkan/vulkan_context.cpp index 71bf42cc..e80f5e36 100644 --- a/renderer/src/vulkan/vulkan_context.cpp +++ b/renderer/src/vulkan/vulkan_context.cpp @@ -26,7 +26,8 @@ VulkanContext::VulkanContext(VkInstance instance, device(device_), features(features_), vmaAllocator(make_vma_allocator({ - .flags = VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT, // We are single-threaded. + // We are single-threaded. + .flags = VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT, .physicalDevice = physicalDevice, .device = device, .pVulkanFunctions = @@ -39,7 +40,6 @@ VulkanContext::VulkanContext(VkInstance instance, , CMD(reinterpret_cast(fp_vkGetInstanceProcAddr(instance, "vk" #CMD))) #define LOAD_VULKAN_DEVICE_COMMAND(CMD) \ , CMD(reinterpret_cast(fp_vkGetDeviceProcAddr(device, "vk" #CMD))) - RIVE_VULKAN_INSTANCE_COMMANDS(LOAD_VULKAN_INSTANCE_COMMAND) RIVE_VULKAN_DEVICE_COMMANDS(LOAD_VULKAN_DEVICE_COMMAND) #undef LOAD_VULKAN_DEVICE_COMMAND #undef LOAD_VULKAN_INSTANCE_COMMAND @@ -184,136 +184,76 @@ void VulkanContext::updateBufferDescriptorSets( UpdateDescriptorSets(device, 1, &writeSet, 0, nullptr); } -static VkAccessFlags pipeline_stage_for_layout(VkImageLayout layout) +void VulkanContext::memoryBarrier(VkCommandBuffer commandBuffer, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + VkDependencyFlags dependencyFlags, + VkMemoryBarrier memoryBarrier) { - switch (layout) - { - case VK_IMAGE_LAYOUT_UNDEFINED: - return VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - case VK_IMAGE_LAYOUT_GENERAL: - return VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: - return VK_PIPELINE_STAGE_TRANSFER_BIT; - case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: - return VK_PIPELINE_STAGE_TRANSFER_BIT; - case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: - return VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: - return VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR: - return VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - default: - fprintf(stderr, - "vkutil::insert_image_memory_barrier: layout 0x%x is not " - "supported\n", - layout); - } - RIVE_UNREACHABLE(); + memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; + CmdPipelineBarrier(commandBuffer, + srcStageMask, + dstStageMask, + dependencyFlags, + 1, + &memoryBarrier, + 0, + nullptr, + 0, + nullptr); } -static VkAccessFlags access_flags_for_layout(VkImageLayout layout) +void VulkanContext::imageMemoryBarriers(VkCommandBuffer commandBuffer, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + VkDependencyFlags dependencyFlags, + uint32_t count, + VkImageMemoryBarrier* imageMemoryBarriers) { - switch (layout) + for (uint32_t i = 0; i < count; ++i) { - case VK_IMAGE_LAYOUT_UNDEFINED: - return VK_ACCESS_NONE; - case VK_IMAGE_LAYOUT_GENERAL: - return VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; - case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: - return VK_ACCESS_TRANSFER_WRITE_BIT; - case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: - return VK_ACCESS_TRANSFER_READ_BIT; - case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: - return VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: - return VK_ACCESS_SHADER_READ_BIT; - case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR: - return VK_ACCESS_NONE; - default: - fprintf(stderr, - "vkutil::insert_image_memory_barrier: layout 0x%x is not " - "supported\n", - layout); + auto& imageMemoryBarrier = imageMemoryBarriers[i]; + imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + if (imageMemoryBarrier.subresourceRange.aspectMask == 0) + { + imageMemoryBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + } + if (imageMemoryBarrier.subresourceRange.levelCount == 0) + { + imageMemoryBarrier.subresourceRange.levelCount = 1; + } + if (imageMemoryBarrier.subresourceRange.layerCount == 0) + { + imageMemoryBarrier.subresourceRange.layerCount = 1; + } } - RIVE_UNREACHABLE(); -} - -void VulkanContext::insertImageMemoryBarrier(VkCommandBuffer commandBuffer, - VkImage image, - VkImageLayout oldLayout, - VkImageLayout newLayout, - uint32_t mipLevel, - uint32_t levelCount) -{ - VkImageMemoryBarrier imageMemoryBarrier = { - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .srcAccessMask = access_flags_for_layout(oldLayout), - .dstAccessMask = access_flags_for_layout(newLayout), - .oldLayout = oldLayout, - .newLayout = newLayout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = image, - .subresourceRange = - { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .baseMipLevel = mipLevel, - .levelCount = levelCount, - .layerCount = VK_REMAINING_ARRAY_LAYERS, - }, - }; - CmdPipelineBarrier(commandBuffer, - pipeline_stage_for_layout(oldLayout), - pipeline_stage_for_layout(newLayout), - 0, + srcStageMask, + dstStageMask, + dependencyFlags, 0, nullptr, 0, nullptr, - 1, - &imageMemoryBarrier); + count, + imageMemoryBarriers); } -static VkAccessFlags pipeline_stage_for_buffer_access(VkAccessFlags access) +void VulkanContext::bufferMemoryBarrier(VkCommandBuffer commandBuffer, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + VkDependencyFlags dependencyFlags, + VkBufferMemoryBarrier bufferMemoryBarrier) { - switch (access) + bufferMemoryBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; + if (bufferMemoryBarrier.size == 0) { - case VK_ACCESS_TRANSFER_WRITE_BIT: - return VK_PIPELINE_STAGE_TRANSFER_BIT; - case VK_ACCESS_HOST_READ_BIT: - return VK_PIPELINE_STAGE_HOST_BIT; - default: - fprintf(stderr, - "vkutil::insert_buffer_memory_barrier: access %u is not " - "supported\n", - access); + bufferMemoryBarrier.size = VK_WHOLE_SIZE; } - RIVE_UNREACHABLE(); -} - -void VulkanContext::insertBufferMemoryBarrier(VkCommandBuffer commandBuffer, - VkAccessFlags srcAccessMask, - VkAccessFlags dstAccessMask, - VkBuffer buffer, - VkDeviceSize offset, - VkDeviceSize size) -{ - VkBufferMemoryBarrier bufferMemoryBarrier = { - .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, - .srcAccessMask = srcAccessMask, - .dstAccessMask = dstAccessMask, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .buffer = buffer, - .offset = offset, - .size = size, - }; - CmdPipelineBarrier(commandBuffer, - pipeline_stage_for_buffer_access(srcAccessMask), - pipeline_stage_for_buffer_access(dstAccessMask), - 0, + srcStageMask, + dstStageMask, + dependencyFlags, 0, nullptr, 1, @@ -327,6 +267,11 @@ void VulkanContext::blitSubRect(VkCommandBuffer commandBuffer, VkImage dst, const IAABB& blitBounds) { + if (blitBounds.empty()) + { + return; + } + VkImageBlit imageBlit = { .srcSubresource = { diff --git a/tests/check_golds.sh b/tests/check_golds.sh index 702991d5..f71cbc94 100755 --- a/tests/check_golds.sh +++ b/tests/check_golds.sh @@ -43,7 +43,7 @@ while :; do shift ;; -n) - ARGS="$ARGS --no-rebuild" + ARGS="$ARGS --no-rebuild --no-install" shift ;; *) @@ -62,7 +62,8 @@ open_file() { fi } -# Updated to "--no-rebuild" after the first backend (so we only rebuild once). +# Updated to "--no-rebuild --no-install" after the first backend (so we only +# rebuild once). NO_REBUILD= for BACKEND in "${@:-$DEFAULT_BACKEND}" @@ -104,5 +105,5 @@ do || open_file .gold/diffs/$ID/index.html fi - NO_REBUILD=--no-rebuild + NO_REBUILD="--no-rebuild --no-install" done diff --git a/tests/common/test_harness.cpp b/tests/common/test_harness.cpp index 8b9e4f1d..8437c901 100644 --- a/tests/common/test_harness.cpp +++ b/tests/common/test_harness.cpp @@ -59,7 +59,7 @@ static void sig_handler(int signo) printf("Received signal %i (\"%s\")\n", signo, strsignal(signo)); signal(signo, SIG_DFL); TestHarness::Instance().onApplicationCrash(strsignal(signo)); - exit(-1); + abort(); } static void check_early_exit() diff --git a/tests/common/testing_window.cpp b/tests/common/testing_window.cpp index e7e57a09..0ed4e55d 100644 --- a/tests/common/testing_window.cpp +++ b/tests/common/testing_window.cpp @@ -12,8 +12,9 @@ #include #endif -// Don't explicitly delete this object. Calling eglDestroyContext during app teardown causes a crash -// on Pixel 4. The OS will clean this up for us automatically when we exit. +// Don't explicitly delete this object. Calling eglDestroyContext during app +// teardown causes a crash on Pixel 4. The OS will clean this up for us +// automatically when we exit. std::unique_ptr s_TestingWindow = nullptr; const char* TestingWindow::BackendName(Backend backend) @@ -34,18 +35,18 @@ const char* TestingWindow::BackendName(Backend backend) return "metal"; case TestingWindow::Backend::metalatomic: return "metalatomic"; - case TestingWindow::Backend::vulkan: - return "vulkan"; - case TestingWindow::Backend::vulkanatomic: - return "vulkanatomic"; + case TestingWindow::Backend::vk: + return "vk"; + case TestingWindow::Backend::vkcore: + return "vkcore"; case TestingWindow::Backend::moltenvk: return "moltenvk"; - case TestingWindow::Backend::moltenvkatomic: - return "moltenvkatomic"; + case TestingWindow::Backend::moltenvkcore: + return "moltenvkcore"; case TestingWindow::Backend::swiftshader: return "swiftshader"; - case TestingWindow::Backend::swiftshaderatomic: - return "swiftshaderatomic"; + case TestingWindow::Backend::swiftshadercore: + return "swiftshadercore"; case TestingWindow::Backend::angle: return "angle"; case TestingWindow::Backend::anglemsaa: @@ -72,7 +73,8 @@ static std::vector split(const char* str, char delimiter) TestingWindow::Backend TestingWindow::ParseBackend(const char* name, std::string* gpuNameFilter) { - // Backends can come in the form , or /. + // Backends can come in the form , or + // /. std::vector tokens = split(name, '/'); assert(!tokens.empty()); if (gpuNameFilter != nullptr) @@ -95,17 +97,17 @@ TestingWindow::Backend TestingWindow::ParseBackend(const char* name, std::string if (nameStr == "metalatomic") return Backend::metalatomic; if (nameStr == "vulkan" || nameStr == "vk") - return Backend::vulkan; - if (nameStr == "vulkanatomic" || nameStr == "vkatomic") - return Backend::vulkanatomic; + return Backend::vk; + if (nameStr == "vulkancore" || nameStr == "vkcore") + return Backend::vkcore; if (nameStr == "moltenvk" || nameStr == "mvk") return Backend::moltenvk; - if (nameStr == "moltenvkatomic" || nameStr == "mvkatomic") - return Backend::moltenvkatomic; + if (nameStr == "moltenvkcore" || nameStr == "mvkcore") + return Backend::moltenvkcore; if (nameStr == "swiftshader" || nameStr == "sw") return Backend::swiftshader; - if (nameStr == "swiftshaderatomic" || nameStr == "swatomic") - return Backend::swiftshaderatomic; + if (nameStr == "swiftshadercore" || nameStr == "swcore") + return Backend::swiftshadercore; if (nameStr == "angle") return Backend::angle; if (nameStr == "anglemsaa") @@ -163,23 +165,25 @@ TestingWindow* TestingWindow::Init(Backend backend, s_TestingWindow = MakeEGL(backend, platformWindow); } break; - case Backend::vulkan: - case Backend::vulkanatomic: + case Backend::vk: + case Backend::vkcore: case Backend::moltenvk: - case Backend::moltenvkatomic: + case Backend::moltenvkcore: case Backend::swiftshader: - case Backend::swiftshaderatomic: - if (backend == Backend::moltenvk || backend == Backend::moltenvkatomic) + case Backend::swiftshadercore: + if (backend == Backend::moltenvk || backend == Backend::moltenvkcore) { - // Use the MoltenVK built by packages/runtime/renderer/make_moltenvk.sh + // Use the MoltenVK built by + // packages/runtime/renderer/make_moltenvk.sh constexpr static char kMoltenVKICD[] = "../renderer/dependencies/MoltenVK/Package/Release/" "MoltenVK/dynamic/dylib/macOS/MoltenVK_icd.json"; set_environment_variable("VK_ICD_FILENAMES", kMoltenVKICD); } - else if (backend == Backend::swiftshader || backend == Backend::swiftshaderatomic) + else if (backend == Backend::swiftshader || backend == Backend::swiftshadercore) { - // Use the swiftshader built by packages/runtime/renderer/make_swiftshader.sh + // Use the swiftshader built by + // packages/runtime/renderer/make_swiftshader.sh constexpr static char kSwiftShaderICD[] = #ifdef __APPLE__ "../renderer/dependencies/SwiftShader/build/Darwin/" @@ -202,7 +206,7 @@ TestingWindow* TestingWindow::Init(Backend backend, #endif if (visibility == Visibility::headless) { - s_TestingWindow = TestingWindow::MakeVulkanTexture(gpuNameFilter); + s_TestingWindow = TestingWindow::MakeVulkanTexture(IsCore(backend), gpuNameFilter); } else { diff --git a/tests/common/testing_window.hpp b/tests/common/testing_window.hpp index f578edc6..86d44590 100644 --- a/tests/common/testing_window.hpp +++ b/tests/common/testing_window.hpp @@ -23,8 +23,8 @@ class RenderTarget; } // namespace gpu }; // namespace rive -// Wraps a factory for rive::Renderer and a singleton target for it to render into (GL window, HTML -// canvas, software buffer, etc.): +// Wraps a factory for rive::Renderer and a singleton target for it to render into +// (GL window, HTML canvas, software buffer, etc.): // // TestingWindow::Init(type); // renderer = TestingWindow::Get()->reset(width, height); @@ -44,19 +44,19 @@ class TestingWindow metalatomic, // System default Vulkan driver. - vulkan, - vulkanatomic, + vk, + vkcore, // Vulkan with as few features enabled as possible. // Vulkan on Metal, aka MoltenVK. - // (defaults to /usr/local/share/vulkan/icd.d/MoltenVK_icd.json if VK_ICD_FILENAMES is not - // set.) + // (defaults to /usr/local/share/vulkan/icd.d/MoltenVK_icd.json if + // VK_ICD_FILENAMES is not set.) moltenvk, - moltenvkatomic, + moltenvkcore, // Swiftshader, Google's CPU implementation of Vulkan. // (defaults to ./vk_swiftshader_icd.json if VK_ICD_FILENAMES is not set.) swiftshader, - swiftshaderatomic, + swiftshadercore, angle, anglemsaa, @@ -78,12 +78,12 @@ class TestingWindow case Backend::d3datomic: case Backend::metal: case Backend::metalatomic: - case Backend::vulkan: - case Backend::vulkanatomic: + case Backend::vk: + case Backend::vkcore: case Backend::moltenvk: - case Backend::moltenvkatomic: + case Backend::moltenvkcore: case Backend::swiftshader: - case Backend::swiftshaderatomic: + case Backend::swiftshadercore: case Backend::dawn: case Backend::coregraphics: return false; @@ -105,12 +105,12 @@ class TestingWindow case Backend::d3datomic: case Backend::metal: case Backend::metalatomic: - case Backend::vulkan: - case Backend::vulkanatomic: + case Backend::vk: + case Backend::vkcore: case Backend::moltenvk: - case Backend::moltenvkatomic: + case Backend::moltenvkcore: case Backend::swiftshader: - case Backend::swiftshaderatomic: + case Backend::swiftshadercore: case Backend::dawn: case Backend::coregraphics: return false; @@ -122,12 +122,12 @@ class TestingWindow { switch (backend) { - case Backend::vulkan: - case Backend::vulkanatomic: + case Backend::vk: + case Backend::vkcore: case Backend::moltenvk: - case Backend::moltenvkatomic: + case Backend::moltenvkcore: case Backend::swiftshader: - case Backend::swiftshaderatomic: + case Backend::swiftshadercore: return true; case Backend::gl: case Backend::glatomic: @@ -152,15 +152,42 @@ class TestingWindow case Backend::glatomic: case Backend::d3datomic: case Backend::metalatomic: - case Backend::vulkanatomic: - case Backend::moltenvkatomic: - case Backend::swiftshaderatomic: + case Backend::vkcore: + case Backend::moltenvkcore: + case Backend::swiftshadercore: return true; case Backend::gl: case Backend::glmsaa: case Backend::d3d: case Backend::metal: - case Backend::vulkan: + case Backend::vk: + case Backend::moltenvk: + case Backend::swiftshader: + case Backend::angle: + case Backend::anglemsaa: + case Backend::dawn: + case Backend::coregraphics: + return false; + } + RIVE_UNREACHABLE(); + } + + constexpr static bool IsCore(Backend backend) + { + switch (backend) + { + case Backend::vkcore: + case Backend::moltenvkcore: + case Backend::swiftshadercore: + return true; + case Backend::glatomic: + case Backend::d3datomic: + case Backend::metalatomic: + case Backend::gl: + case Backend::glmsaa: + case Backend::d3d: + case Backend::metal: + case Backend::vk: case Backend::moltenvk: case Backend::swiftshader: case Backend::angle: @@ -182,13 +209,13 @@ class TestingWindow case Backend::glatomic: case Backend::d3datomic: case Backend::metalatomic: - case Backend::vulkanatomic: - case Backend::moltenvkatomic: - case Backend::swiftshaderatomic: + case Backend::vkcore: + case Backend::moltenvkcore: + case Backend::swiftshadercore: case Backend::gl: case Backend::d3d: case Backend::metal: - case Backend::vulkan: + case Backend::vk: case Backend::moltenvk: case Backend::swiftshader: case Backend::angle: @@ -240,7 +267,8 @@ class TestingWindow virtual rive::gpu::RenderContextGLImpl* renderContextGLImpl() const { return nullptr; } virtual rive::gpu::RenderTarget* renderTarget() const { return nullptr; } - // For testing render pass breaks. Caller must call renderContext()->beginFrame() again. + // For testing render pass breaks. Caller must call + // renderContext()->beginFrame() again. virtual void flushPLSContext() {} // Blocks until a key is pressed. @@ -270,7 +298,8 @@ class TestingWindow Visibility, const char* gpuNameFilter, void* platformWindow); - static std::unique_ptr MakeVulkanTexture(const char* gpuNameFilter); + static std::unique_ptr MakeVulkanTexture(bool coreFeaturesOnly, + const char* gpuNameFilter); static std::unique_ptr MakeAndroidVulkan(void* platformWindow); }; diff --git a/tests/common/testing_window_android_vulkan.cpp b/tests/common/testing_window_android_vulkan.cpp index 7ac8db12..aacacee1 100644 --- a/tests/common/testing_window_android_vulkan.cpp +++ b/tests/common/testing_window_android_vulkan.cpp @@ -14,9 +14,9 @@ std::unique_ptr TestingWindow::MakeAndroidVulkan(void* platformWi #else #include "rive_vk_bootstrap/rive_vk_bootstrap.hpp" -#include "rive_vk_bootstrap/vulkan_fence_pool.hpp" #include "rive/renderer/rive_renderer.hpp" #include "rive/renderer/vulkan/render_context_vulkan_impl.hpp" +#include "rive/renderer/vulkan/vkutil_resource_pool.hpp" #include #include #include @@ -24,10 +24,6 @@ std::unique_ptr TestingWindow::MakeAndroidVulkan(void* platformWi using namespace rive; using namespace rive::gpu; -// +1 because PLS doesn't wait for the previous fence until partway through flush. -// (After we need to acquire a new image from the swapchain.) -static constexpr int kResourcePoolSize = gpu::kBufferRingSize + 1; - class TestingWindowAndroidVulkan : public TestingWindow { public: @@ -62,9 +58,10 @@ class TestingWindowAndroidVulkan : public TestingWindow VulkanFeatures vulkanFeatures; std::tie(m_physicalDevice, vulkanFeatures) = rive_vkb::select_physical_device( - vkb::PhysicalDeviceSelector(m_instance).set_surface(m_windowSurface)); + vkb::PhysicalDeviceSelector(m_instance).set_surface(m_windowSurface), + rive_vkb::FeatureSet::allAvailable); m_device = VKB_CHECK(vkb::DeviceBuilder(m_physicalDevice).build()); - m_vkTable = m_device.make_table(); + m_vkbTable = m_device.make_table(); m_queue = VKB_CHECK(m_device.get_queue(vkb::QueueType::graphics)); m_renderContext = RenderContextVulkanImpl::MakeContext(m_instance, m_physicalDevice, @@ -96,7 +93,8 @@ class TestingWindowAndroidVulkan : public TestingWindow } else { - printf("Android window does NOT support VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT. " + printf("Android window does NOT support " + "VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT. " "Performance will suffer.\n"); swapchainBuilder.add_image_usage_flags(VK_IMAGE_USAGE_TRANSFER_SRC_BIT) .add_image_usage_flags(VK_IMAGE_USAGE_TRANSFER_DST_BIT); @@ -124,38 +122,12 @@ class TestingWindowAndroidVulkan : public TestingWindow })); } - m_swapchainImageLayouts = std::vector(m_swapchainImages.size(), VK_IMAGE_LAYOUT_UNDEFINED); - - m_swapchainImageIndex = 0; - m_renderTarget = impl()->makeRenderTarget(m_width, m_height, m_swapchain.image_format); - - VkCommandPoolCreateInfo commandPoolCreateInfo = { - .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, - .flags = VkCommandPoolCreateFlagBits::VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, - .queueFamilyIndex = *m_device.get_queue_index(vkb::QueueType::graphics), - }; - - VK_CHECK(m_vkTable.createCommandPool(&commandPoolCreateInfo, nullptr, &m_commandPool)); - - VkCommandBufferAllocateInfo commandBufferAllocateInfo = { - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, - .commandPool = m_commandPool, - .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, - .commandBufferCount = static_cast(std::size(m_commandBuffers)), - }; - - VK_CHECK(m_vkTable.allocateCommandBuffers(&commandBufferAllocateInfo, m_commandBuffers)); - - VkSemaphoreCreateInfo semaphoreCreateInfo = { - .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, - }; - for (VkSemaphore& semaphore : m_semaphores) - { - VK_CHECK(m_vkTable.createSemaphore(&semaphoreCreateInfo, nullptr, &semaphore)); - } - - m_fencePool = make_rcp(ref_rcp(vk())); + m_commandBufferPool = make_rcp>( + ref_rcp(vk()), + *m_device.get_queue_index(vkb::QueueType::graphics)); + m_semaphorePool = make_rcp>(ref_rcp(vk())); + m_fencePool = make_rcp>(ref_rcp(vk())); } ~TestingWindowAndroidVulkan() @@ -166,15 +138,11 @@ class TestingWindowAndroidVulkan : public TestingWindow m_swapchainImageViews.clear(); m_fencePool.reset(); - VK_CHECK(m_vkTable.queueWaitIdle(m_queue)); - - m_vkTable.freeCommandBuffers(m_commandPool, kResourcePoolSize, m_commandBuffers); - m_vkTable.destroyCommandPool(m_commandPool, nullptr); + VK_CHECK(m_vkbTable.queueWaitIdle(m_queue)); - for (VkSemaphore semaphore : m_semaphores) - { - m_vkTable.destroySemaphore(semaphore, nullptr); - } + m_commandBufferPool = nullptr; + m_semaphorePool = nullptr; + m_fencePool = nullptr; if (m_swapchain != VK_NULL_HANDLE) { @@ -222,60 +190,68 @@ class TestingWindowAndroidVulkan : public TestingWindow void endFrame(std::vector* pixelData) override { - m_vkTable.acquireNextImageKHR(m_swapchain, - UINT64_MAX, - m_semaphores[m_resourcePoolIdx], - VK_NULL_HANDLE, - &m_swapchainImageIndex); + auto swapchainSemaphore = m_semaphorePool->make(); + m_vkbTable.acquireNextImageKHR(m_swapchain, + UINT64_MAX, + *swapchainSemaphore, + VK_NULL_HANDLE, + &m_swapchainImageIndex); - VkCommandBuffer commandBuffer = m_commandBuffers[m_resourcePoolIdx]; - m_vkTable.resetCommandBuffer(commandBuffer, {}); + auto commandBuffer = m_commandBufferPool->make(); VkCommandBufferBeginInfo commandBufferBeginInfo = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, }; + m_vkbTable.beginCommandBuffer(*commandBuffer, &commandBufferBeginInfo); - m_vkTable.beginCommandBuffer(commandBuffer, &commandBufferBeginInfo); - - m_renderTarget->setTargetTextureView(m_swapchainImageViews[m_swapchainImageIndex]); - - insertSwapchainImageBarrier(VK_IMAGE_LAYOUT_GENERAL); + m_renderTarget->setTargetTextureView(m_swapchainImageViews[m_swapchainImageIndex], {}); - rcp frameFence = m_fencePool->makeFence(); + rcp frameFence = m_fencePool->make(); m_renderContext->flush({ .renderTarget = m_renderTarget.get(), - .externalCommandBuffer = m_commandBuffers[m_resourcePoolIdx], + .externalCommandBuffer = *commandBuffer, .frameCompletionFence = frameFence.get(), }); - insertSwapchainImageBarrier(VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + m_renderTarget->setTargetLastAccess(vk()->simpleImageMemoryBarrier( + *commandBuffer, + m_renderTarget->targetLastAccess(), + { + .pipelineStages = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + .accessMask = VK_ACCESS_NONE, + .layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + }, + m_swapchainImages[m_swapchainImageIndex])); - VK_CHECK(m_vkTable.endCommandBuffer(commandBuffer)); + VK_CHECK(m_vkbTable.endCommandBuffer(*commandBuffer)); - VkPipelineStageFlags waitDstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + auto flushSemaphore = m_semaphorePool->make(); + VkPipelineStageFlags waitDstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; VkSubmitInfo submitInfo = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .waitSemaphoreCount = 1, + .pWaitSemaphores = swapchainSemaphore->vkSemaphoreAddressOf(), .pWaitDstStageMask = &waitDstStageMask, .commandBufferCount = 1, - .pCommandBuffers = &commandBuffer, + .pCommandBuffers = commandBuffer->vkCommandBufferAddressOf(), + .signalSemaphoreCount = 1, + .pSignalSemaphores = flushSemaphore->vkSemaphoreAddressOf(), }; - submitInfo.waitSemaphoreCount = 1; - submitInfo.pWaitSemaphores = &m_semaphores[m_resourcePoolIdx]; - VK_CHECK(m_vkTable.queueSubmit(m_queue, 1, &submitInfo, *frameFence)); + VK_CHECK(m_vkbTable.queueSubmit(m_queue, 1, &submitInfo, *frameFence)); VkPresentInfoKHR presentInfo = { .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .waitSemaphoreCount = 1, + .pWaitSemaphores = flushSemaphore->vkSemaphoreAddressOf(), .swapchainCount = 1, .pSwapchains = &m_swapchain.swapchain, .pImageIndices = &m_swapchainImageIndex, }; - m_vkTable.queuePresentKHR(m_queue, &presentInfo); - - m_resourcePoolIdx = (m_resourcePoolIdx + 1) % kResourcePoolSize; + m_vkbTable.queuePresentKHR(m_queue, &presentInfo); } private: @@ -286,39 +262,25 @@ class TestingWindowAndroidVulkan : public TestingWindow VulkanContext* vk() const { return impl()->vulkanContext(); } - void insertSwapchainImageBarrier(VkImageLayout newLayout) - { - vk()->insertImageMemoryBarrier(m_commandBuffers[m_resourcePoolIdx], - m_swapchainImages[m_swapchainImageIndex], - m_swapchainImageLayouts[m_swapchainImageIndex], - newLayout); - m_swapchainImageLayouts[m_swapchainImageIndex] = newLayout; - } - vkb::Instance m_instance; vkb::InstanceDispatchTable m_instanceFns; vkb::PhysicalDevice m_physicalDevice; vkb::Device m_device; - vkb::DispatchTable m_vkTable; + vkb::DispatchTable m_vkbTable; VkQueue m_queue; VkSurfaceKHR m_windowSurface = VK_NULL_HANDLE; vkb::Swapchain m_swapchain; std::vector m_swapchainImages; std::vector> m_swapchainImageViews; - std::vector m_swapchainImageLayouts; - uint32_t m_swapchainImageIndex; - - VkCommandPool m_commandPool; - VkCommandBuffer m_commandBuffers[kResourcePoolSize]; - VkSemaphore m_semaphores[kResourcePoolSize]; + uint32_t m_swapchainImageIndex = 0; - rcp m_fencePool; + rcp> m_commandBufferPool; + rcp> m_semaphorePool; + rcp> m_fencePool; std::unique_ptr m_renderContext; rcp m_renderTarget; - - int m_resourcePoolIdx = 0; }; std::unique_ptr TestingWindow::MakeAndroidVulkan(void* platformWindow) diff --git a/tests/common/testing_window_egl.cpp b/tests/common/testing_window_egl.cpp index 15f97d9b..15ee36e3 100644 --- a/tests/common/testing_window_egl.cpp +++ b/tests/common/testing_window_egl.cpp @@ -548,12 +548,12 @@ std::unique_ptr TestingWindow::MakeEGL(Backend backend, void* pla case Backend::d3datomic: case Backend::metal: case Backend::metalatomic: - case Backend::vulkan: - case Backend::vulkanatomic: + case Backend::vk: + case Backend::vkcore: case Backend::moltenvk: - case Backend::moltenvkatomic: + case Backend::moltenvkcore: case Backend::swiftshader: - case Backend::swiftshaderatomic: + case Backend::swiftshadercore: case Backend::dawn: case Backend::coregraphics: printf("Invalid backend for TestingWindow::MakeEGLPbuffer."); diff --git a/tests/common/testing_window_fiddle_context.cpp b/tests/common/testing_window_fiddle_context.cpp index 2a7ec565..65908963 100644 --- a/tests/common/testing_window_fiddle_context.cpp +++ b/tests/common/testing_window_fiddle_context.cpp @@ -47,7 +47,7 @@ static void key_callback(GLFWwindow* window, int key, int scancode, int action, { switch (key) { - // clang-format off + // clang-format off case GLFW_KEY_1: key = '!'; break; case GLFW_KEY_2: key = '@'; break; case GLFW_KEY_3: key = '#'; break; @@ -182,6 +182,7 @@ class TestingWindowFiddleContext : public TestingWindow .synchronousShaderCompilations = true, .enableReadPixels = true, .disableRasterOrdering = IsAtomic(backend), + .coreFeaturesOnly = IsCore(backend), .allowHeadlessRendering = visibility == Visibility::headless, .enableVulkanValidationLayers = true, .gpuNameFilter = gpuNameFilter, @@ -206,12 +207,12 @@ class TestingWindowFiddleContext : public TestingWindow case Backend::metalatomic: m_fiddleContext = FiddleContext::MakeMetalPLS(fiddleOptions); break; - case Backend::vulkan: - case Backend::vulkanatomic: + case Backend::vk: + case Backend::vkcore: case Backend::moltenvk: - case Backend::moltenvkatomic: + case Backend::moltenvkcore: case Backend::swiftshader: - case Backend::swiftshaderatomic: + case Backend::swiftshadercore: m_fiddleContext = FiddleContext::MakeVulkanPLS(fiddleOptions); break; case Backend::dawn: diff --git a/tests/common/testing_window_vulkan_texture.cpp b/tests/common/testing_window_vulkan_texture.cpp index c77379b3..874de340 100644 --- a/tests/common/testing_window_vulkan_texture.cpp +++ b/tests/common/testing_window_vulkan_texture.cpp @@ -6,7 +6,8 @@ #ifndef RIVE_VULKAN -std::unique_ptr TestingWindow::MakeVulkanTexture(const char* gpuNameFilter) +std::unique_ptr TestingWindow::MakeVulkanTexture(bool coreFeaturesOnly, + const char* gpuNameFilter) { return nullptr; } @@ -14,50 +15,16 @@ std::unique_ptr TestingWindow::MakeVulkanTexture(const char* gpuN #else #include "rive_vk_bootstrap/rive_vk_bootstrap.hpp" -#include "rive_vk_bootstrap/vulkan_fence_pool.hpp" #include "rive/renderer/rive_renderer.hpp" #include "rive/renderer/vulkan/render_context_vulkan_impl.hpp" +#include "rive/renderer/vulkan/vkutil_resource_pool.hpp" namespace rive::gpu { -class CommandBuffer : public vkutil::RenderingResource -{ -public: - CommandBuffer(const vkb::DispatchTable& vkbTable, - rcp vk, - VkCommandBufferAllocateInfo commandBufferAllocateInfo) : - RenderingResource(std::move(vk)), - fp_vkFreeCommandBuffers(vkbTable.fp_vkFreeCommandBuffers), - m_commandPool(commandBufferAllocateInfo.commandPool) - { - commandBufferAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; - commandBufferAllocateInfo.commandBufferCount = 1, - VK_CHECK(vkbTable.allocateCommandBuffers(&commandBufferAllocateInfo, &m_commandBuffer)); - - VkCommandBufferBeginInfo commandBufferBeginInfo = { - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, - }; - vkbTable.beginCommandBuffer(m_commandBuffer, &commandBufferBeginInfo); - } - - ~CommandBuffer() override - { - fp_vkFreeCommandBuffers(m_vk->device, m_commandPool, 1, &m_commandBuffer); - } - - operator VkCommandBuffer() const { return m_commandBuffer; } - const VkCommandBuffer* vkCommandBufferAddressOf() const { return &m_commandBuffer; } - -private: - const PFN_vkFreeCommandBuffers fp_vkFreeCommandBuffers; - const VkCommandPool m_commandPool; - VkCommandBuffer m_commandBuffer; -}; - class TestingWindowVulkanTexture : public TestingWindow { public: - TestingWindowVulkanTexture(const char* gpuNameFilter) + TestingWindowVulkanTexture(bool coreFeaturesOnly, const char* gpuNameFilter) { rive_vkb::load_vulkan(); @@ -70,8 +37,10 @@ class TestingWindowVulkanTexture : public TestingWindow .build()); VulkanFeatures vulkanFeatures; - std::tie(m_physicalDevice, vulkanFeatures) = - rive_vkb::select_physical_device(m_instance, gpuNameFilter); + std::tie(m_physicalDevice, vulkanFeatures) = rive_vkb::select_physical_device( + m_instance, + coreFeaturesOnly ? rive_vkb::FeatureSet::coreOnly : rive_vkb::FeatureSet::allAvailable, + gpuNameFilter); m_device = VKB_CHECK(vkb::DeviceBuilder(m_physicalDevice).build()); m_queue = VKB_CHECK(m_device.get_queue(vkb::QueueType::graphics)); m_renderContext = RenderContextVulkanImpl::MakeContext(m_instance, @@ -82,13 +51,16 @@ class TestingWindowVulkanTexture : public TestingWindow m_instance.fp_vkGetDeviceProcAddr); m_vkbTable = m_device.make_table(); - VkCommandPoolCreateInfo commandPoolCreateInfo = { - .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, - .queueFamilyIndex = *m_device.get_queue_index(vkb::QueueType::graphics), - }; - VK_CHECK(m_vkbTable.createCommandPool(&commandPoolCreateInfo, nullptr, &m_commandPool)); + m_semaphorePool = make_rcp>(ref_rcp(vk())); + m_fencePool = make_rcp>(ref_rcp(vk())); + m_commandBufferPool = make_rcp>( + ref_rcp(vk()), + *m_device.get_queue_index(vkb::QueueType::graphics)); - m_fencePool = make_rcp(ref_rcp(vk())); + m_textureUsageFlags = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | + (coreFeaturesOnly ? VK_IMAGE_USAGE_TRANSFER_DST_BIT + : VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT); } ~TestingWindowVulkanTexture() @@ -96,14 +68,16 @@ class TestingWindowVulkanTexture : public TestingWindow // Destroy these before destroying the VkDevice. m_renderContext.reset(); m_renderTarget.reset(); - m_pixelReadBuffer.reset(); m_texture.reset(); - m_fencePool.reset(); - m_frameFence.reset(); + m_pixelReadBuffer.reset(); + m_lastFrameSemaphore.reset(); + m_lastFrameFence.reset(); VK_CHECK(m_vkbTable.queueWaitIdle(m_queue)); - m_vkbTable.destroyCommandPool(m_commandPool, nullptr); + m_semaphorePool.reset(); + m_fencePool.reset(); + m_commandBufferPool.reset(); vkb::destroy_device(m_device); vkb::destroy_instance(m_instance); @@ -115,18 +89,17 @@ class TestingWindowVulkanTexture : public TestingWindow std::unique_ptr beginFrame(uint32_t clearColor, bool doClear) override { - if (m_frameFence != nullptr) + if (m_lastFrameFence != nullptr) { - m_frameFence->wait(); + m_lastFrameFence->wait(); } - m_frameFence = m_fencePool->makeFence(); + m_lastFrameFence = m_fencePool->make(); - m_commandBuffer = make_rcp(m_vkbTable, - ref_rcp(vk()), - VkCommandBufferAllocateInfo{ - .commandPool = m_commandPool, - .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, - }); + m_commandBuffer = m_commandBufferPool->make(); + VkCommandBufferBeginInfo commandBufferBeginInfo = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + }; + m_vkbTable.beginCommandBuffer(*m_commandBuffer, &commandBufferBeginInfo); rive::gpu::RenderContext::FrameDescriptor frameDescriptor = { .renderTargetWidth = m_width, @@ -147,30 +120,22 @@ class TestingWindowVulkanTexture : public TestingWindow m_texture = vk()->makeTexture({ .format = VK_FORMAT_B8G8R8A8_UNORM, .extent = {static_cast(m_width), static_cast(m_height), 1}, - .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT | - VK_IMAGE_USAGE_TRANSFER_SRC_BIT, + .usage = m_textureUsageFlags, }); - m_textureCurrentLayout = VK_IMAGE_LAYOUT_UNDEFINED; - m_renderTarget = impl()->makeRenderTarget(m_width, m_height, m_texture->info().format); - m_renderTarget->setTargetTextureView(vk()->makeTextureView(m_texture)); m_pixelReadBuffer = vk()->makeBuffer( { .size = static_cast(m_height) * m_width * 4, .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT, }, vkutil::Mappability::readWrite); + m_renderTarget = impl()->makeRenderTarget(m_width, m_height, m_texture->info().format); + m_renderTarget->setTargetTextureView(vk()->makeTextureView(m_texture), {}); } - vk()->insertImageMemoryBarrier(*m_commandBuffer, - *m_texture, - m_textureCurrentLayout, - VK_IMAGE_LAYOUT_GENERAL); - m_textureCurrentLayout = VK_IMAGE_LAYOUT_GENERAL; - m_renderContext->flush({ .renderTarget = m_renderTarget.get(), .externalCommandBuffer = *m_commandBuffer, - .frameCompletionFence = m_frameFence.get(), + .frameCompletionFence = m_lastFrameFence.get(), }); } @@ -178,64 +143,77 @@ class TestingWindowVulkanTexture : public TestingWindow { flushPLSContext(); - if (pixelData != nullptr) - { - // Copy the framebuffer out to a buffer. - assert(m_pixelReadBuffer->info().size == m_height * m_width * 4); + // Copy the framebuffer out to a buffer. + m_renderTarget->setTargetLastAccess( + vk()->simpleImageMemoryBarrier(*m_commandBuffer, + m_renderTarget->targetLastAccess(), + { + .pipelineStages = VK_PIPELINE_STAGE_TRANSFER_BIT, + .accessMask = VK_ACCESS_TRANSFER_READ_BIT, + .layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + }, + *m_texture)); + + VkBufferImageCopy imageCopyDesc = { + .imageSubresource = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1, + }, + .imageExtent = {m_width, m_height, 1}, + }; - VkBufferImageCopy imageCopyDesc = { - .imageSubresource = - { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .mipLevel = 0, - .baseArrayLayer = 0, - .layerCount = 1, - }, - .imageExtent = {m_width, m_height, 1}, - }; - - vk()->insertImageMemoryBarrier(*m_commandBuffer, - *m_texture, - m_textureCurrentLayout, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); - m_textureCurrentLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; - - m_vkbTable.cmdCopyImageToBuffer(*m_commandBuffer, - *m_texture, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - *m_pixelReadBuffer, - 1, - &imageCopyDesc); - - vk()->insertBufferMemoryBarrier(*m_commandBuffer, - VK_ACCESS_TRANSFER_WRITE_BIT, - VK_ACCESS_HOST_READ_BIT, - *m_pixelReadBuffer); - } + m_vkbTable.cmdCopyImageToBuffer(*m_commandBuffer, + *m_texture, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + *m_pixelReadBuffer, + 1, + &imageCopyDesc); + + vk()->bufferMemoryBarrier(*m_commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_HOST_BIT, + 0, + { + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_HOST_READ_BIT, + .buffer = *m_pixelReadBuffer, + }); VK_CHECK(m_vkbTable.endCommandBuffer(*m_commandBuffer)); - VkPipelineStageFlags waitDstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + auto nextFrameSemaphore = m_semaphorePool->make(); + VkPipelineStageFlags waitDstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; VkSubmitInfo submitInfo = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .pWaitDstStageMask = &waitDstStageMask, .commandBufferCount = 1, .pCommandBuffers = m_commandBuffer->vkCommandBufferAddressOf(), + .signalSemaphoreCount = 1, + .pSignalSemaphores = nextFrameSemaphore->vkSemaphoreAddressOf(), }; + if (m_lastFrameSemaphore != nullptr) + { + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = m_lastFrameSemaphore->vkSemaphoreAddressOf(); + } - VK_CHECK(m_vkbTable.queueSubmit(m_queue, 1, &submitInfo, *m_frameFence)); + VK_CHECK(m_vkbTable.queueSubmit(m_queue, 1, &submitInfo, *m_lastFrameFence)); + + m_lastFrameSemaphore = std::move(nextFrameSemaphore); if (pixelData != nullptr) { - // Copy the buffer containing the framebuffer contents to pixelData. - pixelData->resize(m_height * m_width * 4); - // Wait for all rendering to complete before transferring the // framebuffer data to pixelData. - m_frameFence->wait(); + m_lastFrameFence->wait(); + m_pixelReadBuffer->invalidateContents(); - assert(m_pixelReadBuffer->info().size == m_height * m_width * 4); + // Copy the buffer containing the framebuffer contents to pixelData. + pixelData->resize(m_height * m_width * 4); for (uint32_t y = 0; y < m_height; ++y) { auto src = @@ -270,20 +248,23 @@ class TestingWindowVulkanTexture : public TestingWindow VkQueue m_queue; std::unique_ptr m_renderContext; vkb::DispatchTable m_vkbTable; - VkCommandPool m_commandPool; - rcp m_commandBuffer; - rcp m_fencePool; - rcp m_frameFence; + rcp> m_commandBufferPool; + rcp m_commandBuffer; + rcp> m_semaphorePool; + rcp m_lastFrameSemaphore; + rcp> m_fencePool; + rcp m_lastFrameFence; + VkImageUsageFlags m_textureUsageFlags; rcp m_texture; - VkImageLayout m_textureCurrentLayout; - rcp m_renderTarget; rcp m_pixelReadBuffer; + rcp m_renderTarget; }; }; // namespace rive::gpu -std::unique_ptr TestingWindow::MakeVulkanTexture(const char* gpuNameFilter) +std::unique_ptr TestingWindow::MakeVulkanTexture(bool coreFeaturesOnly, + const char* gpuNameFilter) { - return std::make_unique(gpuNameFilter); + return std::make_unique(coreFeaturesOnly, gpuNameFilter); } #endif diff --git a/tests/deploy_tests.py b/tests/deploy_tests.py index 33e65637..5a4c7044 100644 --- a/tests/deploy_tests.py +++ b/tests/deploy_tests.py @@ -510,8 +510,13 @@ def main(): args.jobs_per_tool = 1 # Only print the command for each job once. # Build the native tools. + rive_tools_dir = os.path.dirname(os.path.realpath(__file__)) + if "ios" in args.target: + # ios links statically, so we need to build every tool every time. + build_targets = ["gms", "goldens", "player"] + else: + build_targets = args.tools if not args.no_rebuild and not args.no_install: - rive_tools_dir = os.path.dirname(os.path.realpath(__file__)) build_rive = [os.path.join(rive_tools_dir, "../build/build_rive.sh")] if os.name == "nt": if subprocess.run(["which", "msbuild.exe"]).returncode == 0: @@ -520,11 +525,6 @@ def main(): else: # msbuild.exe is not on the path; go through build_rive.bat. build_rive[0] = os.path.splitext(build_rive[0])[0] + '.bat' - if "ios" in args.target: - # ios links statically, so we need to build every tool every time. - build_targets = ["gms", "goldens", "player"] - else: - build_targets = args.tools subprocess.check_call(build_rive + ["rebuild", args.builddir] + build_targets) if not args.no_install: @@ -618,6 +618,9 @@ def keyboard_interrupt_handler(signal, frame): test_harness_server.wait_for_shutdown_event() procs[0].join() procs = [] + if args.target == "android": + force_stop_android_tests_apk() + time.sleep(3) else: procs += launch_gms(test_harness_server) @@ -630,6 +633,9 @@ def keyboard_interrupt_handler(signal, frame): test_harness_server.wait_for_shutdown_event() procs[0].join() procs = [] + if args.target == "android": + force_stop_android_tests_apk() + time.sleep(3) else: procs += launch_goldens(test_harness_server) diff --git a/tests/for_all_adb_devices.sh b/tests/for_all_adb_devices.sh index 8776eb35..eee6e877 100755 --- a/tests/for_all_adb_devices.sh +++ b/tests/for_all_adb_devices.sh @@ -1,3 +1,4 @@ +set -e for DEVICE in $(adb devices | grep -v '^List' | cut -f1) do ANDROID_SERIAL=$DEVICE "$@" &