From 8ad6ca05f069b59af9544bc99bed7e5d1f785950 Mon Sep 17 00:00:00 2001 From: Egor Yusov Date: Thu, 11 Apr 2019 08:39:32 -0700 Subject: Reworked automatic mipmap generation in Vulkan backend to support sRGB textures, enable mipmap generation for partial texture views and optimize state transitions --- .../include/GenerateMipsVkHelper.h | 4 + .../include/TextureViewVkImpl.h | 29 ++- .../GraphicsEngineVulkan/include/TextureVkImpl.h | 18 +- .../include/VulkanUtilities/VulkanCommandBuffer.h | 18 ++ .../include/VulkanUtilities/VulkanPhysicalDevice.h | 1 + .../src/DeviceContextVkImpl.cpp | 6 +- .../src/GenerateMipsVkHelper.cpp | 203 ++++++++++++++++----- .../GraphicsEngineVulkan/src/TextureViewVkImpl.cpp | 14 +- .../GraphicsEngineVulkan/src/TextureVkImpl.cpp | 190 +++++++++++-------- .../src/VulkanUtilities/VulkanPhysicalDevice.cpp | 7 + 10 files changed, 348 insertions(+), 142 deletions(-) (limited to 'Graphics/GraphicsEngineVulkan') diff --git a/Graphics/GraphicsEngineVulkan/include/GenerateMipsVkHelper.h b/Graphics/GraphicsEngineVulkan/include/GenerateMipsVkHelper.h index 4c878ae7..17b20d4b 100644 --- a/Graphics/GraphicsEngineVulkan/include/GenerateMipsVkHelper.h +++ b/Graphics/GraphicsEngineVulkan/include/GenerateMipsVkHelper.h @@ -49,11 +49,15 @@ namespace Diligent void GenerateMips(TextureViewVkImpl& TexView, DeviceContextVkImpl& Ctx, IShaderResourceBinding& SRB); void CreateSRB(IShaderResourceBinding** ppSRB); + void WarmUpCache(TEXTURE_FORMAT Fmt); private: std::array, 4> CreatePSOs(TEXTURE_FORMAT Fmt); std::array, 4>& FindPSOs (TEXTURE_FORMAT Fmt); + VkImageLayout GenerateMipsCS (TextureViewVkImpl& TexView, DeviceContextVkImpl& Ctx, IShaderResourceBinding& SRB, VkImageSubresourceRange& SubresRange); + VkImageLayout GenerateMipsBlit(TextureViewVkImpl& TexView, DeviceContextVkImpl& Ctx, IShaderResourceBinding& SRB, VkImageSubresourceRange& SubresRange); + RenderDeviceVkImpl& m_DeviceVkImpl; std::mutex m_PSOMutex; diff --git a/Graphics/GraphicsEngineVulkan/include/TextureViewVkImpl.h b/Graphics/GraphicsEngineVulkan/include/TextureViewVkImpl.h index 8989c245..2e69e34e 100644 --- a/Graphics/GraphicsEngineVulkan/include/TextureViewVkImpl.h +++ b/Graphics/GraphicsEngineVulkan/include/TextureViewVkImpl.h @@ -54,9 +54,36 @@ public: VkImageView GetVulkanImageView()const override final{return m_ImageView;} + bool HasMipLevelViews() const + { + return m_MipLevelViews != nullptr; + } + + TextureViewVkImpl* GetMipLevelSRV(Uint32 MipLevel) + { + VERIFY_EXPR(m_MipLevelViews != nullptr && MipLevel < m_Desc.NumMipLevels); + return m_MipLevelViews[MipLevel*2].get(); + } + + TextureViewVkImpl* GetMipLevelUAV(Uint32 MipLevel) + { + VERIFY_EXPR(m_MipLevelViews != nullptr && MipLevel < m_Desc.NumMipLevels); + return m_MipLevelViews[MipLevel*2 + 1].get(); + } + + using MipLevelViewAutoPtrType = std::unique_ptr >; + + void AssignMipLevelViews(MipLevelViewAutoPtrType* MipLevelViews) + { + m_MipLevelViews = MipLevelViews; + } + protected: /// Vulkan image view descriptor handle - VulkanUtilities::ImageViewWrapper m_ImageView; + VulkanUtilities::ImageViewWrapper m_ImageView; + + /// Individual mip level views used for mipmap generation + MipLevelViewAutoPtrType* m_MipLevelViews = nullptr; }; } diff --git a/Graphics/GraphicsEngineVulkan/include/TextureVkImpl.h b/Graphics/GraphicsEngineVulkan/include/TextureVkImpl.h index 33f07b79..d3d0883a 100644 --- a/Graphics/GraphicsEngineVulkan/include/TextureVkImpl.h +++ b/Graphics/GraphicsEngineVulkan/include/TextureVkImpl.h @@ -83,17 +83,6 @@ public: return vkImage; } - ITextureView* GetMipLevelSRV(Uint32 MipLevel) - { - return m_MipLevelSRV[MipLevel].get(); - } - - ITextureView* GetMipLevelUAV(Uint32 MipLevel) - { - return m_MipLevelUAV[MipLevel].get(); - } - - void SetLayout(VkImageLayout Layout)override final; VkImageLayout GetLayout()const override final; @@ -116,16 +105,15 @@ protected: void CreateViewInternal( const struct TextureViewDesc& ViewDesc, ITextureView** ppView, bool bIsDefaultView )override; //void PrepareVkInitData(const TextureData &InitData, Uint32 NumSubresources, std::vector &VkInitData); + bool CheckCSBasedMipGenerationSupport(VkFormat vkFmt)const; + VulkanUtilities::ImageViewWrapper CreateImageView(TextureViewDesc &ViewDesc); VulkanUtilities::ImageWrapper m_VulkanImage; VulkanUtilities::BufferWrapper m_StagingBuffer; VulkanUtilities::VulkanMemoryAllocation m_MemoryAllocation; VkDeviceSize m_StagingDataAlignedOffset; - - // Texture views needed for mipmap generation - std::vector > > m_MipLevelSRV; - std::vector > > m_MipLevelUAV; + bool m_bCSBasedMipGenerationSupported = false; }; } diff --git a/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/VulkanCommandBuffer.h b/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/VulkanCommandBuffer.h index 3d35c581..8fc47e3b 100644 --- a/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/VulkanCommandBuffer.h +++ b/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/VulkanCommandBuffer.h @@ -394,6 +394,24 @@ namespace VulkanUtilities vkCmdCopyImageToBuffer(m_VkCmdBuffer, srcImage, srcImageLayout, dstBuffer, regionCount, pRegions); } + void BlitImage(VkImage srcImage, + VkImageLayout srcImageLayout, + VkImage dstImage, + VkImageLayout dstImageLayout, + uint32_t regionCount, + const VkImageBlit* pRegions, + VkFilter filter) + { + VERIFY_EXPR(m_VkCmdBuffer != VK_NULL_HANDLE); + if (m_State.RenderPass != VK_NULL_HANDLE) + { + // Blit must be performed outside of render pass. + EndRenderPass(); + } + + vkCmdBlitImage(m_VkCmdBuffer, srcImage, srcImageLayout, dstImage, dstImageLayout, regionCount, pRegions, filter); + } + void FlushBarriers(); void SetVkCmdBuffer(VkCommandBuffer VkCmdBuffer) diff --git a/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/VulkanPhysicalDevice.h b/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/VulkanPhysicalDevice.h index 7056268f..a264c80c 100644 --- a/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/VulkanPhysicalDevice.h +++ b/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/VulkanPhysicalDevice.h @@ -48,6 +48,7 @@ namespace VulkanUtilities uint32_t GetMemoryTypeIndex(uint32_t typeBits, VkMemoryPropertyFlags properties)const; const VkPhysicalDeviceProperties& GetProperties() const {return m_Properties;} const VkPhysicalDeviceFeatures& GetFeatures() const {return m_Features; } + VkFormatProperties GetPhysicalDeviceFormatProperties(VkFormat imageFormat)const; private: VulkanPhysicalDevice(VkPhysicalDevice vkDevice); diff --git a/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp index 384a342d..03eb0992 100644 --- a/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp @@ -2023,7 +2023,11 @@ namespace Diligent void DeviceContextVkImpl::TransitionImageLayout(TextureVkImpl& TextureVk, VkImageLayout OldLayout, VkImageLayout NewLayout, const VkImageSubresourceRange& SubresRange) { - VERIFY(TextureVk.GetLayout() != NewLayout, "The texture is already transitioned to correct layout"); + // Note that the method may be used to transition texture subresources when global texture state is not altered, + // so the debug check below can't be used + // VERIFY(!TextureVk.IsInKnownState() || TextureVk.GetLayout() != NewLayout, "The texture is already transitioned to correct layout"); + + VERIFY(OldLayout != NewLayout, "Old and new layouts are the same"); EnsureVkCmdBuffer(); auto vkImg = TextureVk.GetVkImage(); m_CommandBuffer.TransitionImageLayout(vkImg, OldLayout, NewLayout, SubresRange); diff --git a/Graphics/GraphicsEngineVulkan/src/GenerateMipsVkHelper.cpp b/Graphics/GraphicsEngineVulkan/src/GenerateMipsVkHelper.cpp index 3f4a716c..58b2b122 100644 --- a/Graphics/GraphicsEngineVulkan/src/GenerateMipsVkHelper.cpp +++ b/Graphics/GraphicsEngineVulkan/src/GenerateMipsVkHelper.cpp @@ -176,8 +176,6 @@ namespace Diligent FindPSOs(TEX_FORMAT_RGBA8_UNORM); FindPSOs(TEX_FORMAT_BGRA8_UNORM); - FindPSOs(TEX_FORMAT_RGBA8_UNORM_SRGB); - FindPSOs(TEX_FORMAT_BGRA8_UNORM_SRGB); } void GenerateMipsVkHelper::CreateSRB(IShaderResourceBinding** ppSRB) @@ -196,31 +194,29 @@ namespace Diligent return it->second; } + void GenerateMipsVkHelper::WarmUpCache(TEXTURE_FORMAT Fmt) + { + FindPSOs(Fmt); + } + void GenerateMipsVkHelper::GenerateMips(TextureViewVkImpl& TexView, DeviceContextVkImpl& Ctx, IShaderResourceBinding& SRB) { auto* pTexVk = TexView.GetTexture(); - const auto& TexDesc = pTexVk->GetDesc(); - if (!pTexVk->IsInKnownState()) { - LOG_ERROR_MESSAGE("Unable to generate mips for texture '", TexDesc.Name, "' because the texture state is unknown"); + LOG_ERROR_MESSAGE("Unable to generate mips for texture '", pTexVk->GetDesc().Name, "' because the texture state is unknown"); return; } - const auto& ViewDesc = TexView.GetDesc(); - auto* pSrcMipVar = SRB.GetVariableByName(SHADER_TYPE_COMPUTE, "SrcMip"); - IShaderResourceVariable* pOutMipVar[4] = - { - SRB.GetVariableByName(SHADER_TYPE_COMPUTE, "OutMip0"), - SRB.GetVariableByName(SHADER_TYPE_COMPUTE, "OutMip1"), - SRB.GetVariableByName(SHADER_TYPE_COMPUTE, "OutMip2"), - SRB.GetVariableByName(SHADER_TYPE_COMPUTE, "OutMip3") - }; + DEV_CHECK_ERR(TexView.GetDesc().NumMipLevels > 1, "Number of mip levels in the view must be greater than 1"); - auto& PSOs = FindPSOs(ViewDesc.Format); + const auto OriginalState = pTexVk->GetState(); + const auto OriginalLayout = pTexVk->GetLayout(); + const auto& TexDesc = pTexVk->GetDesc(); + const auto& ViewDesc = TexView.GetDesc(); - const auto& FmtAttribs = GetTextureFormatAttribs(TexDesc.Format); - VkImageSubresourceRange SubresRange; + const auto& FmtAttribs = GetTextureFormatAttribs(ViewDesc.Format); + VkImageSubresourceRange SubresRange = {}; if (FmtAttribs.ComponentType == COMPONENT_TYPE_DEPTH) SubresRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; else if (FmtAttribs.ComponentType == COMPONENT_TYPE_DEPTH_STENCIL) @@ -232,27 +228,84 @@ namespace Diligent } else SubresRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - SubresRange.baseArrayLayer = 0; - SubresRange.layerCount = VK_REMAINING_ARRAY_LAYERS; + SubresRange.baseArrayLayer = ViewDesc.FirstArraySlice; + SubresRange.layerCount = ViewDesc.NumArraySlices; + SubresRange.baseMipLevel = ViewDesc.MostDetailedMip; + SubresRange.levelCount = 1; + + VkImageLayout AffectedMipLevelLayout; + if (TexView.HasMipLevelViews()) + { + AffectedMipLevelLayout = GenerateMipsCS(TexView, Ctx, SRB, SubresRange); + } + else + { + AffectedMipLevelLayout = GenerateMipsBlit(TexView, Ctx, SRB, SubresRange); + } + + // All affected mip levels are now in VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL state + if (AffectedMipLevelLayout != OriginalLayout) + { + bool IsAllSlices = (TexDesc.Type != RESOURCE_DIM_TEX_1D_ARRAY && + TexDesc.Type != RESOURCE_DIM_TEX_2D_ARRAY && + TexDesc.Type != RESOURCE_DIM_TEX_CUBE_ARRAY) || + TexDesc.ArraySize == ViewDesc.NumArraySlices; + bool IsAllMips = ViewDesc.NumMipLevels == TexDesc.MipLevels; + if (IsAllSlices && IsAllMips) + { + pTexVk->SetLayout(AffectedMipLevelLayout); + } + else + { + SubresRange.baseMipLevel = ViewDesc.MostDetailedMip; + SubresRange.levelCount = ViewDesc.NumMipLevels; + // Transition all affected subresources back to original layout + Ctx.TransitionImageLayout(*pTexVk, AffectedMipLevelLayout, OriginalLayout, SubresRange); + VERIFY_EXPR(pTexVk->GetLayout() == OriginalLayout); + } + } + } + + VkImageLayout GenerateMipsVkHelper::GenerateMipsCS(TextureViewVkImpl& TexView, DeviceContextVkImpl& Ctx, IShaderResourceBinding& SRB, VkImageSubresourceRange& SubresRange) + { + auto* pTexVk = TexView.GetTexture(); + const auto& TexDesc = pTexVk->GetDesc(); + + VERIFY(TexDesc.Type == RESOURCE_DIM_TEX_2D || TexDesc.Type == RESOURCE_DIM_TEX_2D_ARRAY, + "CS-based mipmap generation is only supported for 2D textures and texture arrays"); + + const auto& ViewDesc = TexView.GetDesc(); + auto* pSrcMipVar = SRB.GetVariableByName(SHADER_TYPE_COMPUTE, "SrcMip"); + IShaderResourceVariable* pOutMipVar[4] = + { + SRB.GetVariableByName(SHADER_TYPE_COMPUTE, "OutMip0"), + SRB.GetVariableByName(SHADER_TYPE_COMPUTE, "OutMip1"), + SRB.GetVariableByName(SHADER_TYPE_COMPUTE, "OutMip2"), + SRB.GetVariableByName(SHADER_TYPE_COMPUTE, "OutMip3") + }; + + auto& PSOs = FindPSOs(ViewDesc.Format); - const auto CurrState = pTexVk->GetState(); - const auto CurrLayout = ResourceStateToVkImageLayout(CurrState); + const auto OriginalState = pTexVk->GetState(); + const auto OriginalLayout = pTexVk->GetLayout(); // Transition the lowest mip level to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL - SubresRange.baseMipLevel = 0; - SubresRange.levelCount = 1; - if (CurrState != RESOURCE_STATE_SHADER_RESOURCE) - Ctx.TransitionTextureState(*pTexVk, CurrState, RESOURCE_STATE_SHADER_RESOURCE, false, &SubresRange); + SubresRange.baseMipLevel = ViewDesc.MostDetailedMip; + SubresRange.levelCount = 1; + if (OriginalState != RESOURCE_STATE_SHADER_RESOURCE) + Ctx.TransitionTextureState(*pTexVk, OriginalState, RESOURCE_STATE_SHADER_RESOURCE, false /*UpdateTextureState*/, &SubresRange); VERIFY_EXPR(ResourceStateToVkImageLayout(RESOURCE_STATE_SHADER_RESOURCE) == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - for (uint32_t TopMip = 0; TopMip < TexDesc.MipLevels - 1; ) + // Note that mip levels are relative to the view's most detailed mip + auto LowestMip = ViewDesc.NumMipLevels - 1; + for (uint32_t TopMip = 0; TopMip < LowestMip; ) { // In Vulkan all subresources of a view must be transitioned to the same layout, so // we can't bind the entire texture and have to bind single mip level at a time - pSrcMipVar->Set(pTexVk->GetMipLevelSRV(TopMip)); + pSrcMipVar->Set(TexView.GetMipLevelSRV(TopMip)); - uint32_t SrcWidth = std::max(TexDesc.Width >> TopMip, 1u); - uint32_t SrcHeight = std::max(TexDesc.Height >> TopMip, 1u); + uint32_t SrcWidth = std::max(TexDesc.Width >> (TopMip + ViewDesc.MostDetailedMip), 1u); + uint32_t SrcHeight = std::max(TexDesc.Height >> (TopMip + ViewDesc.MostDetailedMip), 1u); uint32_t DstWidth = std::max(SrcWidth >> 1, 1u); uint32_t DstHeight = std::max(SrcHeight >> 1, 1u); @@ -268,8 +321,8 @@ namespace Diligent // in the low bits. Zeros indicate we can divide by two without truncating. uint32_t AdditionalMips = PlatformMisc::GetLSB(DstWidth | DstHeight); uint32_t NumMips = 1 + (AdditionalMips > 3 ? 3 : AdditionalMips); - if (TopMip + NumMips > TexDesc.MipLevels - 1) - NumMips = TexDesc.MipLevels - 1 - TopMip; + if (TopMip + NumMips > LowestMip) + NumMips = LowestMip - TopMip; // These are clamped to 1 after computing additional mips because clamped // dimensions should not limit us from downsampling multiple times. (E.g. @@ -286,7 +339,7 @@ namespace Diligent Int32 NumMipLevels; // Number of OutMips to write: [1, 4] Int32 ArraySlice; Int32 Dummy; - float TexelSize[2]; // 1.0 / OutMip1.Dimensions + float TexelSize[2]; // 1.0 / OutMip1.Dimensions }; MapHelper MappedData(&Ctx, m_ConstantsCB, MAP_WRITE, MAP_FLAG_DISCARD); @@ -294,8 +347,8 @@ namespace Diligent { static_cast(TopMip), static_cast(NumMips), - static_cast(ViewDesc.FirstArraySlice), - 0, + 0, // Array slices are relative to the view's first array slice + 0, // Unused {1.0f / static_cast(DstWidth), 1.0f / static_cast(DstHeight)} }; } @@ -303,14 +356,14 @@ namespace Diligent constexpr const Uint32 MaxMipsHandledByCS = 4; // Max number of mip levels processed by one CS shader invocation for (Uint32 u = 0; u < MaxMipsHandledByCS; ++u) { - auto* MipLevelUAV = pTexVk->GetMipLevelUAV(std::min(TopMip + u + 1, TexDesc.MipLevels - 1)); + auto* MipLevelUAV = TexView.GetMipLevelUAV(std::min(TopMip + u + 1, LowestMip)); pOutMipVar[u]->Set(MipLevelUAV); } - SubresRange.baseMipLevel = TopMip + 1; - SubresRange.levelCount = std::min(4u, TexDesc.MipLevels - (TopMip + 1)); - if (CurrLayout != VK_IMAGE_LAYOUT_GENERAL) - Ctx.TransitionImageLayout(*pTexVk, CurrLayout, VK_IMAGE_LAYOUT_GENERAL, SubresRange); + SubresRange.baseMipLevel = ViewDesc.MostDetailedMip + TopMip + 1; + SubresRange.levelCount = std::min(4u, LowestMip - TopMip); + if (OriginalLayout != VK_IMAGE_LAYOUT_GENERAL) + Ctx.TransitionImageLayout(*pTexVk, OriginalLayout, VK_IMAGE_LAYOUT_GENERAL, SubresRange); Ctx.CommitShaderResources(&SRB, RESOURCE_STATE_TRANSITION_MODE_NONE); DispatchComputeAttribs DispatchAttrs((DstWidth + 7) / 8, (DstHeight + 7) / 8, ViewDesc.NumArraySlices); @@ -321,8 +374,74 @@ namespace Diligent TopMip += NumMips; } - // All mip levels are now in VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL state - pTexVk->SetState(RESOURCE_STATE_SHADER_RESOURCE); - VERIFY_EXPR(pTexVk->GetLayout() == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + } + + VkImageLayout GenerateMipsVkHelper::GenerateMipsBlit(TextureViewVkImpl& TexView, DeviceContextVkImpl& Ctx, IShaderResourceBinding& SRB, VkImageSubresourceRange& SubresRange) + { + auto* pTexVk = TexView.GetTexture(); + const auto& TexDesc = pTexVk->GetDesc(); + const auto& ViewDesc = TexView.GetDesc(); + auto vkImage = pTexVk->GetVkImage(); + + const auto OriginalState = pTexVk->GetState(); + const auto OriginalLayout = ResourceStateToVkImageLayout(OriginalState); + + VkImageBlit BlitRegion = {}; + BlitRegion.srcSubresource.baseArrayLayer = ViewDesc.FirstArraySlice; + BlitRegion.srcSubresource.layerCount = ViewDesc.NumArraySlices; + BlitRegion.srcSubresource.aspectMask = SubresRange.aspectMask; + BlitRegion.dstSubresource.baseArrayLayer = BlitRegion.srcSubresource.baseArrayLayer; + BlitRegion.dstSubresource.layerCount = BlitRegion.srcSubresource.layerCount; + BlitRegion.dstSubresource.aspectMask = BlitRegion.srcSubresource.aspectMask; + BlitRegion.srcOffsets[0] = VkOffset3D{0, 0, 0}; + BlitRegion.dstOffsets[0] = VkOffset3D{0, 0, 0}; + + SubresRange.baseMipLevel = ViewDesc.MostDetailedMip; + SubresRange.levelCount = 1; + if (OriginalState != RESOURCE_STATE_COPY_SOURCE) + Ctx.TransitionTextureState(*pTexVk, OriginalState, RESOURCE_STATE_COPY_SOURCE, false /*UpdateTextureState*/, &SubresRange); + + auto& CmdBuffer = Ctx.GetCommandBuffer(); + for (uint32_t mip = ViewDesc.MostDetailedMip + 1; mip < ViewDesc.MostDetailedMip + ViewDesc.NumMipLevels; ++mip) + { + BlitRegion.srcSubresource.mipLevel = mip-1; + BlitRegion.dstSubresource.mipLevel = mip; + + BlitRegion.srcOffsets[1] = + VkOffset3D + { + static_cast(std::max(TexDesc.Width >> (mip-1), 1u)), + static_cast(std::max(TexDesc.Height >> (mip-1), 1u)), + 1 + }; + BlitRegion.dstOffsets[1] = + VkOffset3D + { + static_cast(std::max(TexDesc.Width >> mip, 1u)), + static_cast(std::max(TexDesc.Height >> mip, 1u)), + 1 + }; + if (TexDesc.Type == RESOURCE_DIM_TEX_3D) + { + BlitRegion.srcOffsets[1].z = std::max(TexDesc.Depth >> (mip-1), 1u); + BlitRegion.dstOffsets[1].z = std::max(TexDesc.Depth >> mip, 1u); + } + + SubresRange.baseMipLevel = mip; + if (OriginalLayout != VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) + Ctx.TransitionImageLayout(*pTexVk, OriginalLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, SubresRange); + + CmdBuffer.BlitImage(vkImage, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, // must be VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL or VK_IMAGE_LAYOUT_GENERAL + vkImage, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, // must be VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL or VK_IMAGE_LAYOUT_GENERAL + 1, + &BlitRegion, + VK_FILTER_LINEAR); + Ctx.TransitionImageLayout(*pTexVk, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, SubresRange); + } + + return VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; } } diff --git a/Graphics/GraphicsEngineVulkan/src/TextureViewVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/TextureViewVkImpl.cpp index f12d217a..8ad690fa 100644 --- a/Graphics/GraphicsEngineVulkan/src/TextureViewVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/TextureViewVkImpl.cpp @@ -34,14 +34,24 @@ TextureViewVkImpl::TextureViewVkImpl( IReferenceCounters* pRefCo const TextureViewDesc& ViewDesc, ITexture* pTexture, VulkanUtilities::ImageViewWrapper&& ImgView, - bool bIsDefaultView ) : + bool bIsDefaultView) : TTextureViewBase( pRefCounters, pDevice, ViewDesc, pTexture, bIsDefaultView ), - m_ImageView(std::move(ImgView)) + m_ImageView (std::move(ImgView)) { } TextureViewVkImpl::~TextureViewVkImpl() { + if (m_MipLevelViews != nullptr) + { + for (Uint32 MipView=0; MipView < m_Desc.NumMipLevels * 2; ++MipView) + { + m_MipLevelViews[MipView].~MipLevelViewAutoPtrType(); + } + // Memory allocated in TextureVkImpl::CreateViewInternal() + GetRawAllocator().Free(m_MipLevelViews); + } + if(m_Desc.ViewType == TEXTURE_VIEW_DEPTH_STENCIL || m_Desc.ViewType == TEXTURE_VIEW_RENDER_TARGET) m_pDevice->GetFramebufferCache().OnDestroyImageView(m_ImageView); m_pDevice->SafeReleaseDeviceObject(std::move(m_ImageView), m_pTexture->GetDesc().CommandQueueMask); diff --git a/Graphics/GraphicsEngineVulkan/src/TextureVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/TextureVkImpl.cpp index 6c4e08a0..4d05b81f 100644 --- a/Graphics/GraphicsEngineVulkan/src/TextureVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/TextureVkImpl.cpp @@ -69,9 +69,6 @@ TextureVkImpl :: TextureVkImpl(IReferenceCounters* pRefCounters, LOG_ERROR_AND_THROW("Static textures must be initialized with data at creation time: pInitData can't be null"); const auto& FmtAttribs = GetTextureFormatAttribs(m_Desc.Format); - if ((m_Desc.MiscFlags & MISC_TEXTURE_FLAG_GENERATE_MIPS) != 0 && FmtAttribs.IsTypeless) - LOG_ERROR_AND_THROW("Textures created with MISC_TEXTURE_FLAG_GENERATE_MIPS flag can't use typeless formats. The following format was provided: ", FmtAttribs.Name, " when attempting to create texture '", m_Desc.Name, "'"); - const auto& LogicalDevice = pRenderDeviceVk->GetLogicalDevice(); if (m_Desc.Usage == USAGE_STATIC || m_Desc.Usage == USAGE_DEFAULT || m_Desc.Usage == USAGE_DYNAMIC) @@ -100,28 +97,26 @@ TextureVkImpl :: TextureVkImpl(IReferenceCounters* pRefCounters, LOG_ERROR_AND_THROW("Unknown texture type"); } + TEXTURE_FORMAT InternalTexFmt = m_Desc.Format; if (FmtAttribs.IsTypeless) { - TEXTURE_VIEW_TYPE DefaultTexView; - if(m_Desc.BindFlags & BIND_DEPTH_STENCIL) - DefaultTexView = TEXTURE_VIEW_DEPTH_STENCIL; + TEXTURE_VIEW_TYPE PrimaryViewType; + if (m_Desc.BindFlags & BIND_DEPTH_STENCIL) + PrimaryViewType = TEXTURE_VIEW_DEPTH_STENCIL; else if (m_Desc.BindFlags & BIND_UNORDERED_ACCESS) - DefaultTexView = TEXTURE_VIEW_UNORDERED_ACCESS; + PrimaryViewType = TEXTURE_VIEW_UNORDERED_ACCESS; else if (m_Desc.BindFlags & BIND_RENDER_TARGET) - DefaultTexView = TEXTURE_VIEW_RENDER_TARGET; + PrimaryViewType = TEXTURE_VIEW_RENDER_TARGET; else - DefaultTexView = TEXTURE_VIEW_SHADER_RESOURCE; - auto DefaultViewFormat = GetDefaultTextureViewFormat(m_Desc, DefaultTexView); - ImageCI.format = TexFormatToVkFormat(DefaultViewFormat); - } - else - { - ImageCI.format = TexFormatToVkFormat(m_Desc.Format); + PrimaryViewType = TEXTURE_VIEW_SHADER_RESOURCE; + InternalTexFmt = GetDefaultTextureViewFormat(m_Desc, PrimaryViewType); } + + ImageCI.format = TexFormatToVkFormat(InternalTexFmt); - ImageCI.extent.width = m_Desc.Width; + ImageCI.extent.width = m_Desc.Width; ImageCI.extent.height = (m_Desc.Type == RESOURCE_DIM_TEX_1D || m_Desc.Type == RESOURCE_DIM_TEX_1D_ARRAY) ? 1 : m_Desc.Height; - ImageCI.extent.depth = (m_Desc.Type == RESOURCE_DIM_TEX_3D) ? m_Desc.Depth : 1; + ImageCI.extent.depth = (m_Desc.Type == RESOURCE_DIM_TEX_3D) ? m_Desc.Depth : 1; ImageCI.mipLevels = m_Desc.MipLevels; if (m_Desc.Type == RESOURCE_DIM_TEX_1D_ARRAY || @@ -146,7 +141,7 @@ TextureVkImpl :: TextureVkImpl(IReferenceCounters* pRefCounters, // VK_IMAGE_USAGE_TRANSFER_DST_BIT is required for vkCmdClearDepthStencilImage() ImageCI.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; } - if ((m_Desc.BindFlags & BIND_UNORDERED_ACCESS) || (m_Desc.MiscFlags & MISC_TEXTURE_FLAG_GENERATE_MIPS)) + if (m_Desc.BindFlags & BIND_UNORDERED_ACCESS) { ImageCI.usage |= VK_IMAGE_USAGE_STORAGE_BIT; } @@ -155,6 +150,28 @@ TextureVkImpl :: TextureVkImpl(IReferenceCounters* pRefCounters, ImageCI.usage |= VK_IMAGE_USAGE_SAMPLED_BIT; } + if (m_Desc.MiscFlags & MISC_TEXTURE_FLAG_GENERATE_MIPS) + { + if (CheckCSBasedMipGenerationSupport(ImageCI.format)) + { + ImageCI.usage |= VK_IMAGE_USAGE_STORAGE_BIT; + m_bCSBasedMipGenerationSupported = true; + // TODO: warm-up generate mips PSO cache + } + else + { + const auto& PhysicalDevice = pRenderDeviceVk->GetPhysicalDevice(); + auto FmtProperties = PhysicalDevice.GetPhysicalDeviceFormatProperties(ImageCI.format); (void)FmtProperties; + DEV_CHECK_ERR((FmtProperties.optimalTilingFeatures & (VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT)) == (VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT), + "Texture format ", GetTextureFormatAttribs(InternalTexFmt).Name, " does not support blitting. " + "Automatic mipmap generation can't be done neither by CS nor by blitting."); + + DEV_CHECK_ERR((FmtProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) != 0, + "Texture format ", GetTextureFormatAttribs(InternalTexFmt).Name, " does not support linear filtering. " + "Automatic mipmap generation can't be done neither by CS nor by blitting."); + } + } + ImageCI.sharingMode = VK_SHARING_MODE_EXCLUSIVE; ImageCI.queueFamilyIndexCount = 0; ImageCI.pQueueFamilyIndices = nullptr; @@ -384,60 +401,6 @@ TextureVkImpl :: TextureVkImpl(IReferenceCounters* pRefCounters, Uint32 QueueIndex = 0; pRenderDeviceVk->ExecuteAndDisposeTransientCmdBuff(QueueIndex, vkCmdBuff, std::move(CmdPool)); } - - - if(m_Desc.MiscFlags & MISC_TEXTURE_FLAG_GENERATE_MIPS) - { - if (m_Desc.Type != RESOURCE_DIM_TEX_2D && m_Desc.Type != RESOURCE_DIM_TEX_2D_ARRAY) - { - LOG_ERROR_AND_THROW("Mipmap generation is only supported for 2D textures and texture arrays"); - } - - m_MipLevelUAV.reserve(m_Desc.MipLevels); - for(Uint32 MipLevel = 0; MipLevel < m_Desc.MipLevels; ++MipLevel) - { - // Create mip level UAV - TextureViewDesc UAVDesc; - std::stringstream name_ss; - name_ss << "Mip " << MipLevel << " UAV for texture '" << m_Desc.Name << "'"; - auto name = name_ss.str(); - UAVDesc.Name = name.c_str(); - // Always create texture array UAV - UAVDesc.TextureDim = RESOURCE_DIM_TEX_2D_ARRAY; - UAVDesc.ViewType = TEXTURE_VIEW_UNORDERED_ACCESS; - UAVDesc.FirstArraySlice = 0; - UAVDesc.NumArraySlices = m_Desc.ArraySize; - UAVDesc.MostDetailedMip = MipLevel; - if (m_Desc.Format == TEX_FORMAT_RGBA8_UNORM_SRGB) - UAVDesc.Format = TEX_FORMAT_RGBA8_UNORM; - ITextureView* pMipUAV = nullptr; - CreateViewInternal( UAVDesc, &pMipUAV, true ); - m_MipLevelUAV.emplace_back(ValidatedCast(pMipUAV), STDDeleter(TexViewObjAllocator)); - } - VERIFY_EXPR(m_MipLevelUAV.size() == m_Desc.MipLevels); - - m_MipLevelSRV.reserve(m_Desc.MipLevels); - for(Uint32 MipLevel = 0; MipLevel < m_Desc.MipLevels; ++MipLevel) - { - // Create mip level SRV - TextureViewDesc TexArraySRVDesc; - std::stringstream name_ss; - name_ss << "Mip " << MipLevel << " SRV for texture '" << m_Desc.Name << "'"; - auto name = name_ss.str(); - TexArraySRVDesc.Name = name.c_str(); - // Alaways create texture array view - TexArraySRVDesc.TextureDim = RESOURCE_DIM_TEX_2D_ARRAY; - TexArraySRVDesc.ViewType = TEXTURE_VIEW_SHADER_RESOURCE; - TexArraySRVDesc.FirstArraySlice = 0; - TexArraySRVDesc.NumArraySlices = m_Desc.ArraySize; - TexArraySRVDesc.MostDetailedMip = MipLevel; - TexArraySRVDesc.NumMipLevels = 1; - ITextureView* pMipLevelSRV = nullptr; - CreateViewInternal( TexArraySRVDesc, &pMipLevelSRV, true ); - m_MipLevelSRV.emplace_back(ValidatedCast(pMipLevelSRV), STDDeleter(TexViewObjAllocator)); - } - VERIFY_EXPR(m_MipLevelSRV.size() == m_Desc.MipLevels); - } } else if(m_Desc.Usage == USAGE_STAGING) { @@ -549,7 +512,7 @@ TextureVkImpl::TextureVkImpl(IReferenceCounters* pRefCounters, IMPLEMENT_QUERY_INTERFACE( TextureVkImpl, IID_TextureVk, TTextureBase ) -void TextureVkImpl::CreateViewInternal( const struct TextureViewDesc &ViewDesc, ITextureView **ppView, bool bIsDefaultView ) +void TextureVkImpl::CreateViewInternal(const TextureViewDesc& ViewDesc, ITextureView** ppView, bool bIsDefaultView) { VERIFY( ppView != nullptr, "View pointer address is null" ); if( !ppView )return; @@ -559,22 +522,71 @@ void TextureVkImpl::CreateViewInternal( const struct TextureViewDesc &ViewDesc, try { - auto &TexViewAllocator = m_pDevice->GetTexViewObjAllocator(); + auto& TexViewAllocator = m_pDevice->GetTexViewObjAllocator(); VERIFY( &TexViewAllocator == &m_dbgTexViewObjAllocator, "Texture view allocator does not match allocator provided during texture initialization" ); auto UpdatedViewDesc = ViewDesc; CorrectTextureViewDesc( UpdatedViewDesc ); VulkanUtilities::ImageViewWrapper ImgView = CreateImageView(UpdatedViewDesc); - auto pViewVk = NEW_RC_OBJ(TexViewAllocator, "TextureViewVkImpl instance", TextureViewVkImpl, bIsDefaultView ? this : nullptr) - (GetDevice(), UpdatedViewDesc, this, std::move(ImgView), bIsDefaultView ); + (GetDevice(), UpdatedViewDesc, this, std::move(ImgView), bIsDefaultView); VERIFY( pViewVk->GetDesc().ViewType == ViewDesc.ViewType, "Incorrect view type" ); - if( bIsDefaultView ) + if (bIsDefaultView) *ppView = pViewVk; else pViewVk->QueryInterface(IID_TextureView, reinterpret_cast(ppView) ); + + + if ((UpdatedViewDesc.Flags & TEXTURE_VIEW_FLAG_ALLOW_MIP_MAP_GENERATION) != 0 && + m_bCSBasedMipGenerationSupported && + CheckCSBasedMipGenerationSupport(TexFormatToVkFormat(pViewVk->GetDesc().Format))) + { + auto* pMipLevelViewsRawMem = ALLOCATE(GetRawAllocator(), "Raw memory for mip level views", sizeof(TextureViewVkImpl::MipLevelViewAutoPtrType) * UpdatedViewDesc.NumMipLevels * 2); + auto* pMipLevelViews = reinterpret_cast(pMipLevelViewsRawMem); + for (Uint32 MipLevel = 0; MipLevel < UpdatedViewDesc.NumMipLevels; ++MipLevel) + { + auto CreateMipLevelView = [&](TEXTURE_VIEW_TYPE ViewType, Uint32 MipLevel, TextureViewVkImpl::MipLevelViewAutoPtrType* ppMipLevelView) + { + TextureViewDesc MipLevelViewDesc = pViewVk->GetDesc(); + // Always create texture array views + std::stringstream name_ss; + name_ss << "Internal " << (ViewType == TEXTURE_VIEW_SHADER_RESOURCE ? "SRV" : "UAV") + << " of mip level " << MipLevel << " of texture view '" << pViewVk->GetDesc().Name << "'"; + auto name = name_ss.str(); + MipLevelViewDesc.Name = name.c_str(); + MipLevelViewDesc.TextureDim = RESOURCE_DIM_TEX_2D_ARRAY; + MipLevelViewDesc.ViewType = ViewType; + MipLevelViewDesc.MostDetailedMip += MipLevel; + MipLevelViewDesc.NumMipLevels = 1; + + if (ViewType == TEXTURE_VIEW_UNORDERED_ACCESS) + { + if (MipLevelViewDesc.Format == TEX_FORMAT_RGBA8_UNORM_SRGB) + MipLevelViewDesc.Format = TEX_FORMAT_RGBA8_UNORM; + } + + VulkanUtilities::ImageViewWrapper ImgView = CreateImageView(MipLevelViewDesc); + // Attach to parent view + auto pMipLevelViewVk = NEW_RC_OBJ(TexViewAllocator, "TextureViewVkImpl instance", TextureViewVkImpl, pViewVk) + (GetDevice(), MipLevelViewDesc, this, std::move(ImgView), bIsDefaultView); + new (ppMipLevelView) TextureViewVkImpl::MipLevelViewAutoPtrType(pMipLevelViewVk, STDDeleter(TexViewAllocator)); + }; + + if ((MipLevel % 4) == 0) + { + // Mip levles are generated 4 at a time, so we only need SRV for every 4-th level + CreateMipLevelView(TEXTURE_VIEW_SHADER_RESOURCE, MipLevel, &pMipLevelViews[MipLevel * 2]); + } + else + { + new (&pMipLevelViews[MipLevel * 2]) TextureViewVkImpl::MipLevelViewAutoPtrType{}; + } + CreateMipLevelView(TEXTURE_VIEW_UNORDERED_ACCESS, MipLevel, &pMipLevelViews[MipLevel * 2 + 1]); + } + pViewVk->AssignMipLevelViews(pMipLevelViews); + } } catch( const std::runtime_error & ) { @@ -638,11 +650,11 @@ VulkanUtilities::ImageViewWrapper TextureVkImpl::CreateImageView(TextureViewDesc else { ImageViewCI.viewType = VK_IMAGE_VIEW_TYPE_3D; - Uint32 MipDepth = std::max(m_Desc.Depth >> ViewDesc.MostDetailedMip, 1U); + Uint32 MipDepth = std::max(Uint32{m_Desc.Depth} >> Uint32{ViewDesc.MostDetailedMip}, 1U); if (ViewDesc.FirstDepthSlice != 0 || ViewDesc.NumDepthSlices != MipDepth) { - LOG_ERROR("3D texture view '", (ViewDesc.Name ? ViewDesc.Name : ""), "' (most detailed mip: ", ViewDesc.MostDetailedMip, - "; mip levels: ", ViewDesc.NumMipLevels, "; first slice: ", ViewDesc.FirstDepthSlice, + LOG_ERROR("3D texture view '", (ViewDesc.Name ? ViewDesc.Name : ""), "' (most detailed mip: ", Uint32{ViewDesc.MostDetailedMip}, + "; mip levels: ", Uint32{ViewDesc.NumMipLevels}, "; first slice: ", ViewDesc.FirstDepthSlice, "; num depth slices: ", ViewDesc.NumDepthSlices, ") of texture '", m_Desc.Name, "' does not references" " all depth slices (", MipDepth, ") in the mip level. 3D texture views in Vulkan must address all depth slices." ); ViewDesc.FirstDepthSlice = 0; @@ -663,7 +675,7 @@ VulkanUtilities::ImageViewWrapper TextureVkImpl::CreateImageView(TextureViewDesc } TEXTURE_FORMAT CorrectedViewFormat = ViewDesc.Format; - if(m_Desc.BindFlags & BIND_DEPTH_STENCIL) + if (m_Desc.BindFlags & BIND_DEPTH_STENCIL) CorrectedViewFormat = GetDefaultTextureViewFormat(CorrectedViewFormat, TEXTURE_VIEW_DEPTH_STENCIL, m_Desc.BindFlags); ImageViewCI.format = TexFormatToVkFormat(CorrectedViewFormat); ImageViewCI.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A }; @@ -736,6 +748,22 @@ VulkanUtilities::ImageViewWrapper TextureVkImpl::CreateImageView(TextureViewDesc return LogicalDevice.CreateImageView(ImageViewCI, ViewName.c_str()); } +bool TextureVkImpl::CheckCSBasedMipGenerationSupport(VkFormat vkFmt)const +{ + VERIFY_EXPR(m_Desc.MiscFlags & MISC_TEXTURE_FLAG_GENERATE_MIPS); + if (m_Desc.Type == RESOURCE_DIM_TEX_2D || m_Desc.Type == RESOURCE_DIM_TEX_2D_ARRAY) + { + const auto& PhysicalDevice = m_pDevice->GetPhysicalDevice(); + auto FmtProperties = PhysicalDevice.GetPhysicalDeviceFormatProperties(vkFmt); + if ((FmtProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0) + { + return true; + } + } + + return false; +} + void TextureVkImpl::SetLayout(VkImageLayout Layout) { SetState(VkImageLayoutToResourceState(Layout)); diff --git a/Graphics/GraphicsEngineVulkan/src/VulkanUtilities/VulkanPhysicalDevice.cpp b/Graphics/GraphicsEngineVulkan/src/VulkanUtilities/VulkanPhysicalDevice.cpp index d9766c52..2a5b010e 100644 --- a/Graphics/GraphicsEngineVulkan/src/VulkanUtilities/VulkanPhysicalDevice.cpp +++ b/Graphics/GraphicsEngineVulkan/src/VulkanUtilities/VulkanPhysicalDevice.cpp @@ -191,4 +191,11 @@ namespace VulkanUtilities } return InvalidMemoryTypeIndex; } + + VkFormatProperties VulkanPhysicalDevice::GetPhysicalDeviceFormatProperties(VkFormat imageFormat)const + { + VkFormatProperties formatProperties; + vkGetPhysicalDeviceFormatProperties(m_VkDevice, imageFormat, &formatProperties); + return formatProperties; + } } -- cgit v1.2.3