From ceb1bc219e2eb5a4151130c2b5fb2833854a8b45 Mon Sep 17 00:00:00 2001 From: Egor Yusov Date: Fri, 5 Apr 2019 08:00:59 -0700 Subject: Implemented staging textures in Vk backend --- .../include/DeviceContextVkImpl.h | 25 +- .../GraphicsEngineVulkan/include/TextureVkImpl.h | 31 +- .../include/VulkanUtilities/VulkanCommandBuffer.h | 16 + .../include/VulkanUtilities/VulkanLogicalDevice.h | 3 + Graphics/GraphicsEngineVulkan/src/BufferVkImpl.cpp | 2 +- .../src/DeviceContextVkImpl.cpp | 366 +++++++--- .../GraphicsEngineVulkan/src/TextureVkImpl.cpp | 775 ++++++++++++--------- .../src/VulkanUtilities/VulkanLogicalDevice.cpp | 10 + 8 files changed, 787 insertions(+), 441 deletions(-) (limited to 'Graphics/GraphicsEngineVulkan') diff --git a/Graphics/GraphicsEngineVulkan/include/DeviceContextVkImpl.h b/Graphics/GraphicsEngineVulkan/include/DeviceContextVkImpl.h index 09eaa1a5..52b6a7aa 100644 --- a/Graphics/GraphicsEngineVulkan/include/DeviceContextVkImpl.h +++ b/Graphics/GraphicsEngineVulkan/include/DeviceContextVkImpl.h @@ -296,14 +296,23 @@ private: Uint32 MipLevel, const Box& Region)const; - void CopyBufferToTexture(VkBuffer vkBuffer, - Uint32 BufferOffset, - Uint32 BufferRowStrideInTexels, - const Box& Region, - TextureVkImpl& TextureVk, - Uint32 MipLevel, - Uint32 ArraySlice, - RESOURCE_STATE_TRANSITION_MODE TextureTransitionMode); + void CopyBufferToTexture(VkBuffer vkSrcBuffer, + Uint32 SrcBufferOffset, + Uint32 SrcBufferRowStrideInTexels, + TextureVkImpl& DstTextureVk, + const Box& DstRegion, + Uint32 DstMipLevel, + Uint32 DstArraySlice, + RESOURCE_STATE_TRANSITION_MODE DstTextureTransitionMode); + + void CopyTextureToBuffer(TextureVkImpl& SrcTextureVk, + const Box& SrcRegion, + Uint32 SrcMipLevel, + Uint32 SrcArraySlice, + RESOURCE_STATE_TRANSITION_MODE SrcTextureTransitionMode, + VkBuffer vkDstBuffer, + Uint32 DstBufferOffset, + Uint32 DstBufferRowStrideInTexels); void DvpLogRenderPass_PSOMismatch(); diff --git a/Graphics/GraphicsEngineVulkan/include/TextureVkImpl.h b/Graphics/GraphicsEngineVulkan/include/TextureVkImpl.h index 4d19fe91..33f07b79 100644 --- a/Graphics/GraphicsEngineVulkan/include/TextureVkImpl.h +++ b/Graphics/GraphicsEngineVulkan/include/TextureVkImpl.h @@ -38,6 +38,18 @@ namespace Diligent class FixedBlockMemoryAllocator; +struct MipLevelProperties +{ + Uint32 Width = 0; + Uint32 Height = 0; + Uint32 Depth = 1; + Uint32 RowSize = 0; + Uint32 MipSize = 0; +}; + +MipLevelProperties GetMipLevelProperties(const TextureDesc& TexDesc, Uint32 MipLevel); +Uint32 GetStagingDataOffset(const TextureDesc& TexDesc, Uint32 ArraySlice, Uint32 MipLevel); + /// Base implementation of the Diligent::ITextureVk interface class TextureVkImpl final : public TextureBase { @@ -85,14 +97,31 @@ public: void SetLayout(VkImageLayout Layout)override final; VkImageLayout GetLayout()const override final; + VkBuffer GetVkStagingBuffer()const + { + return m_StagingBuffer; + } + + uint8_t* GetStagingDataCPUAddress()const + { + auto* StagingDataCPUAddress = reinterpret_cast(m_MemoryAllocation.Page->GetCPUMemory()); + VERIFY_EXPR(StagingDataCPUAddress != nullptr); + StagingDataCPUAddress += m_StagingDataAlignedOffset; + return StagingDataCPUAddress; + } + + void InvalidateStagingRange(VkDeviceSize Offset, VkDeviceSize Size); + protected: void CreateViewInternal( const struct TextureViewDesc& ViewDesc, ITextureView** ppView, bool bIsDefaultView )override; //void PrepareVkInitData(const TextureData &InitData, Uint32 NumSubresources, std::vector &VkInitData); VulkanUtilities::ImageViewWrapper CreateImageView(TextureViewDesc &ViewDesc); - VulkanUtilities::ImageWrapper m_VulkanImage; + 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; diff --git a/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/VulkanCommandBuffer.h b/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/VulkanCommandBuffer.h index d27a5594..3d35c581 100644 --- a/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/VulkanCommandBuffer.h +++ b/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/VulkanCommandBuffer.h @@ -378,6 +378,22 @@ namespace VulkanUtilities vkCmdCopyBufferToImage(m_VkCmdBuffer, srcBuffer, dstImage, dstImageLayout, regionCount, pRegions); } + void CopyImageToBuffer(VkImage srcImage, + VkImageLayout srcImageLayout, + VkBuffer dstBuffer, + uint32_t regionCount, + const VkBufferImageCopy* pRegions) + { + VERIFY_EXPR(m_VkCmdBuffer != VK_NULL_HANDLE); + if (m_State.RenderPass != VK_NULL_HANDLE) + { + // Copy operations must be performed outside of render pass. + EndRenderPass(); + } + + vkCmdCopyImageToBuffer(m_VkCmdBuffer, srcImage, srcImageLayout, dstBuffer, regionCount, pRegions); + } + void FlushBarriers(); void SetVkCmdBuffer(VkCommandBuffer VkCmdBuffer) diff --git a/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/VulkanLogicalDevice.h b/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/VulkanLogicalDevice.h index 0cd3bed0..1e7f2daa 100644 --- a/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/VulkanLogicalDevice.h +++ b/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/VulkanLogicalDevice.h @@ -130,6 +130,9 @@ namespace VulkanUtilities VkResult MapMemory(VkDeviceMemory memory, VkDeviceSize offset, VkDeviceSize size, VkMemoryMapFlags flags, void** ppData)const; void UnmapMemory(VkDeviceMemory memory)const; + VkResult InvalidateMappedMemoryRanges(uint32_t memoryRangeCount, const VkMappedMemoryRange* pMemoryRanges)const; + VkResult FlushMappedMemoryRanges(uint32_t memoryRangeCount, const VkMappedMemoryRange* pMemoryRanges)const; + VkResult GetFenceStatus(VkFence fence)const; VkResult ResetFence(VkFence fence)const; VkResult WaitForFences(uint32_t fenceCount, diff --git a/Graphics/GraphicsEngineVulkan/src/BufferVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/BufferVkImpl.cpp index c7967e8d..87170a74 100644 --- a/Graphics/GraphicsEngineVulkan/src/BufferVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/BufferVkImpl.cpp @@ -192,7 +192,7 @@ BufferVkImpl :: BufferVkImpl(IReferenceCounters* pRefCounters, VkBufferCreateInfo VkStaginBuffCI = VkBuffCI; VkStaginBuffCI.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - std::string StagingBufferName = "Staging buffer for '"; + std::string StagingBufferName = "Upload buffer for '"; StagingBufferName += m_Desc.Name; StagingBufferName += '\''; VulkanUtilities::BufferWrapper StagingBuffer = LogicalDevice.CreateBuffer(VkStaginBuffCI, StagingBufferName.c_str()); diff --git a/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp index eeb30132..20522acb 100644 --- a/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp @@ -1322,54 +1322,119 @@ namespace Diligent auto* pSrcTexVk = ValidatedCast( CopyAttribs.pSrcTexture ); auto* pDstTexVk = ValidatedCast( CopyAttribs.pDstTexture ); + const auto& SrcTexDesc = pSrcTexVk->GetDesc(); const auto& DstTexDesc = pDstTexVk->GetDesc(); - VkImageCopy CopyRegion = {}; - if (auto* pSrcBox = CopyAttribs.pSrcBox) + auto* pSrcBox = CopyAttribs.pSrcBox; + Box FullMipBox; + if (pSrcBox == nullptr) { + FullMipBox.MaxX = std::max(DstTexDesc.Width >> CopyAttribs.SrcMipLevel, 1u); + FullMipBox.MaxY = std::max(DstTexDesc.Height >> CopyAttribs.SrcMipLevel, 1u); + if(DstTexDesc.Type == RESOURCE_DIM_TEX_3D) + FullMipBox.MaxZ = std::max(DstTexDesc.Depth >> CopyAttribs.SrcMipLevel, 1u); + else + FullMipBox.MaxZ = 1; + pSrcBox = &FullMipBox; + } + const auto& FmtAttribs = GetDevice()->GetTextureFormatInfo(DstTexDesc.Format); + if (SrcTexDesc.Usage != USAGE_STAGING && DstTexDesc.Usage != USAGE_STAGING) + { + VkImageCopy CopyRegion = {}; CopyRegion.srcOffset.x = pSrcBox->MinX; CopyRegion.srcOffset.y = pSrcBox->MinY; CopyRegion.srcOffset.z = pSrcBox->MinZ; CopyRegion.extent.width = pSrcBox->MaxX - pSrcBox->MinX; CopyRegion.extent.height = std::max(pSrcBox->MaxY - pSrcBox->MinY, 1u); CopyRegion.extent.depth = std::max(pSrcBox->MaxZ - pSrcBox->MinZ, 1u); - } - else - { - CopyRegion.srcOffset = VkOffset3D{0,0,0}; - CopyRegion.extent.width = std::max(DstTexDesc.Width >> CopyAttribs.SrcMipLevel, 1u); - CopyRegion.extent.height = std::max(DstTexDesc.Height >> CopyAttribs.SrcMipLevel, 1u); - if(DstTexDesc.Type == RESOURCE_DIM_TEX_3D) - CopyRegion.extent.depth = std::max(DstTexDesc.Depth >> CopyAttribs.SrcMipLevel, 1u); + + VkImageAspectFlags aspectMask = 0; + if (FmtAttribs.ComponentType == COMPONENT_TYPE_DEPTH) + aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + else if (FmtAttribs.ComponentType == COMPONENT_TYPE_DEPTH_STENCIL) + { + aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + } else - CopyRegion.extent.depth = 1; - } + aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - const auto& FmtAttribs = GetDevice()->GetTextureFormatInfo(DstTexDesc.Format); - VkImageAspectFlags aspectMask = 0; - if (FmtAttribs.ComponentType == COMPONENT_TYPE_DEPTH) - aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - else if (FmtAttribs.ComponentType == COMPONENT_TYPE_DEPTH_STENCIL) - { - aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + CopyRegion.srcSubresource.baseArrayLayer = CopyAttribs.SrcSlice; + CopyRegion.srcSubresource.layerCount = 1; + CopyRegion.srcSubresource.mipLevel = CopyAttribs.SrcMipLevel; + CopyRegion.srcSubresource.aspectMask = aspectMask; + + CopyRegion.dstSubresource.baseArrayLayer = CopyAttribs.DstSlice; + CopyRegion.dstSubresource.layerCount = 1; + CopyRegion.dstSubresource.mipLevel = CopyAttribs.DstMipLevel; + CopyRegion.dstSubresource.aspectMask = aspectMask; + + CopyRegion.dstOffset.x = CopyAttribs.DstX; + CopyRegion.dstOffset.y = CopyAttribs.DstY; + CopyRegion.dstOffset.z = CopyAttribs.DstZ; + + CopyTextureRegion(pSrcTexVk, CopyAttribs.SrcTextureTransitionMode, pDstTexVk, CopyAttribs.DstTextureTransitionMode, CopyRegion); + } + else if (SrcTexDesc.Usage == USAGE_STAGING && DstTexDesc.Usage != USAGE_STAGING) + { + DEV_CHECK_ERR((SrcTexDesc.CPUAccessFlags & CPU_ACCESS_WRITE), "Attempting to copy from staging texture that was not created with CPU_ACCESS_WRITE flag"); + DEV_CHECK_ERR(pSrcTexVk->GetState() == RESOURCE_STATE_COPY_SOURCE, "Source staging texture must permanently be in RESOURCE_STATE_COPY_SOURCE state"); + + auto SrcBufferOffset = GetStagingDataOffset(SrcTexDesc, CopyAttribs.SrcSlice, CopyAttribs.SrcMipLevel); + auto SrcMipLevelAttribs = GetMipLevelProperties(SrcTexDesc, CopyAttribs.SrcMipLevel); + // address of (x,y,z) = region->bufferOffset + (((z * imageHeight) + y) * rowLength + x) * texelBlockSize; (18.4.1) + SrcBufferOffset += + (pSrcBox->MinZ * SrcMipLevelAttribs.Height + pSrcBox->MinY) * SrcMipLevelAttribs.RowSize + + (FmtAttribs.ComponentType != COMPONENT_TYPE_COMPRESSED ? + pSrcBox->MinX * FmtAttribs.ComponentSize * FmtAttribs.NumComponents : + pSrcBox->MinX / FmtAttribs.BlockWidth * FmtAttribs.ComponentSize); + + Box DstBox; + DstBox.MinX = CopyAttribs.DstX; + DstBox.MinY = CopyAttribs.DstY; + DstBox.MinZ = CopyAttribs.DstZ; + DstBox.MaxX = DstBox.MinX + pSrcBox->MaxX - pSrcBox->MinX; + DstBox.MaxY = DstBox.MinY + pSrcBox->MaxY - pSrcBox->MinY; + DstBox.MaxZ = DstBox.MinZ + pSrcBox->MaxZ - pSrcBox->MinZ; + + CopyBufferToTexture( + pSrcTexVk->GetVkStagingBuffer(), + SrcBufferOffset, + SrcMipLevelAttribs.Width, + *pDstTexVk, + DstBox, + CopyAttribs.DstMipLevel, + CopyAttribs.DstSlice, + CopyAttribs.DstTextureTransitionMode + ); + } + else if (SrcTexDesc.Usage != USAGE_STAGING && DstTexDesc.Usage == USAGE_STAGING) + { + DEV_CHECK_ERR((DstTexDesc.CPUAccessFlags & CPU_ACCESS_READ), "Attempting to copy to staging texture that was not created with CPU_ACCESS_READ flag"); + DEV_CHECK_ERR(pDstTexVk->GetState() == RESOURCE_STATE_COPY_DEST, "Destination staging texture must permanently be in RESOURCE_STATE_COPY_DEST state"); + + auto DstBufferOffset = GetStagingDataOffset(DstTexDesc, CopyAttribs.DstSlice, CopyAttribs.DstMipLevel); + auto DstMipLevelAttribs = GetMipLevelProperties(DstTexDesc, CopyAttribs.DstMipLevel); + // address of (x,y,z) = region->bufferOffset + (((z * imageHeight) + y) * rowLength + x) * texelBlockSize; (18.4.1) + DstBufferOffset += + (CopyAttribs.DstZ * DstMipLevelAttribs.Height + CopyAttribs.DstY) * DstMipLevelAttribs.RowSize * + (FmtAttribs.ComponentType != COMPONENT_TYPE_COMPRESSED ? + CopyAttribs.DstX * FmtAttribs.ComponentSize * FmtAttribs.NumComponents : + CopyAttribs.DstX / FmtAttribs.BlockWidth * FmtAttribs.ComponentSize); + + CopyTextureToBuffer( + *pSrcTexVk, + *pSrcBox, + CopyAttribs.SrcMipLevel, + CopyAttribs.SrcSlice, + CopyAttribs.SrcTextureTransitionMode, + pDstTexVk->GetVkStagingBuffer(), + DstBufferOffset, + DstMipLevelAttribs.Width + ); } else - aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - - CopyRegion.srcSubresource.baseArrayLayer = CopyAttribs.SrcSlice; - CopyRegion.srcSubresource.layerCount = 1; - CopyRegion.srcSubresource.mipLevel = CopyAttribs.SrcMipLevel; - CopyRegion.srcSubresource.aspectMask = aspectMask; - - CopyRegion.dstSubresource.baseArrayLayer = CopyAttribs.DstSlice; - CopyRegion.dstSubresource.layerCount = 1; - CopyRegion.dstSubresource.mipLevel = CopyAttribs.DstMipLevel; - CopyRegion.dstSubresource.aspectMask = aspectMask; - - CopyRegion.dstOffset.x = CopyAttribs.DstX; - CopyRegion.dstOffset.y = CopyAttribs.DstY; - CopyRegion.dstOffset.z = CopyAttribs.DstZ; - - CopyTextureRegion(pSrcTexVk, CopyAttribs.SrcTextureTransitionMode, pDstTexVk, CopyAttribs.DstTextureTransitionMode, CopyRegion); + { + UNSUPPORTED("Copying data between staging textures is not supported and is likely not want you really want to do"); + } } void DeviceContextVkImpl::CopyTextureRegion(TextureVkImpl* pSrcTexture, @@ -1504,8 +1569,8 @@ namespace Diligent CopyBufferToTexture(Allocation.vkBuffer, static_cast(Allocation.AlignedOffset), CopyInfo.StrideInTexels, - CopyInfo.Region, TextureVk, + CopyInfo.Region, MipLevel, Slice, TextureTransitionMode); @@ -1517,19 +1582,13 @@ namespace Diligent m_GenerateMipsHelper->GenerateMips(*ValidatedCast(pTexView), *this, *m_GenerateMipsSRB); } - void DeviceContextVkImpl::CopyBufferToTexture(VkBuffer vkBuffer, - Uint32 BufferOffset, - Uint32 BufferRowStrideInTexels, - const Box& Region, - TextureVkImpl& TextureVk, - Uint32 MipLevel, - Uint32 ArraySlice, - RESOURCE_STATE_TRANSITION_MODE TextureTransitionMode) + static VkBufferImageCopy GetBufferImageCopyInfo(Uint32 BufferOffset, + Uint32 BufferRowStrideInTexels, + const TextureDesc& TexDesc, + const Box& Region, + Uint32 MipLevel, + Uint32 ArraySlice) { - EnsureVkCmdBuffer(); - TransitionOrVerifyTextureState(TextureVk, TextureTransitionMode, RESOURCE_STATE_COPY_DEST, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - "Using texture as copy destination (DeviceContextVkImpl::CopyBufferToTexture)"); - VkBufferImageCopy CopyRegion = {}; VERIFY( (BufferOffset % 4) == 0, "Source buffer offset must be multiple of 4 (18.4)"); CopyRegion.bufferOffset = BufferOffset; // must be a multiple of 4 (18.4) @@ -1537,10 +1596,9 @@ namespace Diligent // bufferRowLength and bufferImageHeight specify the data in buffer memory as a subregion of a larger two- or // three-dimensional image, and control the addressing calculations of data in buffer memory. If either of these // values is zero, that aspect of the buffer memory is considered to be tightly packed according to the imageExtent (18.4). - CopyRegion.bufferRowLength = BufferRowStrideInTexels; + CopyRegion.bufferRowLength = BufferRowStrideInTexels; CopyRegion.bufferImageHeight = 0; - const auto& TexDesc = TextureVk.GetDesc(); const auto& FmtAttribs = GetTextureFormatAttribs(TexDesc.Format); // The aspectMask member of imageSubresource must only have a single bit set (18.4) if (FmtAttribs.ComponentType == COMPONENT_TYPE_DEPTH) @@ -1558,8 +1616,8 @@ namespace Diligent CopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; CopyRegion.imageSubresource.baseArrayLayer = ArraySlice; - CopyRegion.imageSubresource.layerCount = 1; - CopyRegion.imageSubresource.mipLevel = MipLevel; + CopyRegion.imageSubresource.layerCount = 1; + CopyRegion.imageSubresource.mipLevel = MipLevel; // - imageOffset.x and (imageExtent.width + imageOffset.x) must both be greater than or equal to 0 and // less than or equal to the image subresource width (18.4) // - imageOffset.y and (imageExtent.height + imageOffset.y) must both be greater than or equal to 0 and @@ -1580,15 +1638,59 @@ namespace Diligent static_cast(Region.MaxY - Region.MinY), static_cast(Region.MaxZ - Region.MinZ) }; - + + return CopyRegion; + } + + void DeviceContextVkImpl::CopyBufferToTexture(VkBuffer vkSrcBuffer, + Uint32 SrcBufferOffset, + Uint32 SrcBufferRowStrideInTexels, + TextureVkImpl& DstTextureVk, + const Box& DstRegion, + Uint32 DstMipLevel, + Uint32 DstArraySlice, + RESOURCE_STATE_TRANSITION_MODE DstTextureTransitionMode) + { + EnsureVkCmdBuffer(); + TransitionOrVerifyTextureState(DstTextureVk, DstTextureTransitionMode, RESOURCE_STATE_COPY_DEST, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + "Using texture as copy destination (DeviceContextVkImpl::CopyBufferToTexture)"); + + const auto& TexDesc = DstTextureVk.GetDesc(); + VkBufferImageCopy BuffImgCopy = GetBufferImageCopyInfo(SrcBufferOffset, SrcBufferRowStrideInTexels, TexDesc, DstRegion, DstMipLevel, DstArraySlice); + m_CommandBuffer.CopyBufferToImage( - vkBuffer, - TextureVk.GetVkImage(), + vkSrcBuffer, + DstTextureVk.GetVkImage(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, // must be VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL or VK_IMAGE_LAYOUT_GENERAL (18.4) 1, - &CopyRegion); + &BuffImgCopy); + } + + void DeviceContextVkImpl::CopyTextureToBuffer(TextureVkImpl& SrcTextureVk, + const Box& SrcRegion, + Uint32 SrcMipLevel, + Uint32 SrcArraySlice, + RESOURCE_STATE_TRANSITION_MODE SrcTextureTransitionMode, + VkBuffer vkDstBuffer, + Uint32 DstBufferOffset, + Uint32 DstBufferRowStrideInTexels) + { + EnsureVkCmdBuffer(); + TransitionOrVerifyTextureState(SrcTextureVk, SrcTextureTransitionMode, RESOURCE_STATE_COPY_SOURCE, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + "Using texture as source destination (DeviceContextVkImpl::CopyTextureToBuffer)"); + + const auto& TexDesc = SrcTextureVk.GetDesc(); + VkBufferImageCopy BuffImgCopy = GetBufferImageCopyInfo(DstBufferOffset, DstBufferRowStrideInTexels, TexDesc, SrcRegion, SrcMipLevel, SrcArraySlice); + + m_CommandBuffer.CopyImageToBuffer( + SrcTextureVk.GetVkImage(), + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, // must be VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL or VK_IMAGE_LAYOUT_GENERAL (18.4) + vkDstBuffer, + 1, + &BuffImgCopy); } + void DeviceContextVkImpl::MapTextureSubresource( ITexture* pTexture, Uint32 MipLevel, Uint32 ArraySlice, @@ -1601,16 +1703,7 @@ namespace Diligent TextureVkImpl& TextureVk = *ValidatedCast(pTexture); const auto& TexDesc = TextureVk.GetDesc(); - - if (MapType != MAP_WRITE) - { - LOG_ERROR("Textures can currently only be mapped for writing in Vulkan backend"); - MappedData = MappedTextureSubresource{}; - return; - } - - if ((MapFlags & (MAP_FLAG_DISCARD | MAP_FLAG_DO_NOT_SYNCHRONIZE)) != 0) - LOG_WARNING_MESSAGE_ONCE("Mapping textures with flags MAP_FLAG_DISCARD or MAP_FLAG_DO_NOT_SYNCHRONIZE has no effect in Vulkan backend"); + const auto& FmtAttribs = GetTextureFormatAttribs(TexDesc.Format); Box FullExtentBox; if (pMapRegion == nullptr) @@ -1621,27 +1714,84 @@ namespace Diligent FullExtentBox.MaxZ = std::max(TexDesc.Depth >> MipLevel, 1u); pMapRegion = &FullExtentBox; } - - auto CopyInfo = GetBufferToTextureCopyInfo(TexDesc, MipLevel, *pMapRegion); - const auto& DeviceLimits = m_pDevice.RawPtr()->GetPhysicalDevice().GetProperties().limits; - // Source buffer offset must be multiple of 4 (18.4) - auto Alignment = std::max(DeviceLimits.optimalBufferCopyOffsetAlignment, VkDeviceSize{4}); - // If the calling command's VkImage parameter is a compressed image, bufferOffset must be a multiple of - // the compressed texel block size in bytes (18.4) - const auto& FmtAttribs = GetTextureFormatAttribs(TexDesc.Format); - if (FmtAttribs.ComponentType == COMPONENT_TYPE_COMPRESSED) + + if (TexDesc.Usage == USAGE_DYNAMIC) { - Alignment = std::max(Alignment, VkDeviceSize{FmtAttribs.ComponentSize}); + if (MapType != MAP_WRITE) + { + LOG_ERROR("Textures can currently only be mapped for writing in Vulkan backend"); + MappedData = MappedTextureSubresource{}; + return; + } + + if ((MapFlags & (MAP_FLAG_DISCARD | MAP_FLAG_DO_NOT_SYNCHRONIZE)) != 0) + LOG_INFO_MESSAGE_ONCE("Mapping textures with flags MAP_FLAG_DISCARD or MAP_FLAG_DO_NOT_SYNCHRONIZE has no effect in Vulkan backend"); + + auto CopyInfo = GetBufferToTextureCopyInfo(TexDesc, MipLevel, *pMapRegion); + const auto& DeviceLimits = m_pDevice.RawPtr()->GetPhysicalDevice().GetProperties().limits; + // Source buffer offset must be multiple of 4 (18.4) + auto Alignment = std::max(DeviceLimits.optimalBufferCopyOffsetAlignment, VkDeviceSize{4}); + // If the calling command's VkImage parameter is a compressed image, bufferOffset must be a multiple of + // the compressed texel block size in bytes (18.4) + if (FmtAttribs.ComponentType == COMPONENT_TYPE_COMPRESSED) + { + Alignment = std::max(Alignment, VkDeviceSize{FmtAttribs.ComponentSize}); + } + auto Allocation = AllocateDynamicSpace(CopyInfo.MemorySize, static_cast(Alignment)); + + MappedData.pData = reinterpret_cast(Allocation.pDynamicMemMgr->GetCPUAddress()) + Allocation.AlignedOffset; + MappedData.Stride = CopyInfo.Stride; + MappedData.DepthStride = CopyInfo.DepthStride; + + auto it = m_MappedTextures.emplace(MappedTextureKey{&TextureVk, MipLevel, ArraySlice}, MappedTexture{CopyInfo, std::move(Allocation)}); + if(!it.second) + LOG_ERROR_MESSAGE("Mip level ", MipLevel, ", slice ", ArraySlice, " of texture '", TexDesc.Name, "' has already been mapped"); } - auto Allocation = AllocateDynamicSpace(CopyInfo.MemorySize, static_cast(Alignment)); + else if (TexDesc.Usage == USAGE_STAGING) + { + if( (MapFlags & MAP_FLAG_DO_NOT_SYNCHRONIZE) == 0 ) + { + LOG_WARNING_MESSAGE_ONCE("Mapping staging textures is never synchronized in Vulkan backend. " + "Application must use fences or other synchronization methods to explicitly synchronize " + "access and map texture with MAP_FLAG_DO_NOT_SYNCHRONIZE flag."); + } + + auto SubresourceOffset = GetStagingDataOffset(TexDesc, ArraySlice, MipLevel); + auto MipLevelAttribs = GetMipLevelProperties(TexDesc, MipLevel); + // address of (x,y,z) = region->bufferOffset + (((z * imageHeight) + y) * rowLength + x) * texelBlockSize; (18.4.1) + auto MapStartOffset = SubresourceOffset + + (pMapRegion->MinZ * MipLevelAttribs.Height + pMapRegion->MinY) * MipLevelAttribs.RowSize + + (FmtAttribs.ComponentType != COMPONENT_TYPE_COMPRESSED ? + pMapRegion->MinX * FmtAttribs.ComponentSize * FmtAttribs.NumComponents : + pMapRegion->MinX / FmtAttribs.BlockWidth * FmtAttribs.ComponentSize); - MappedData.pData = reinterpret_cast(Allocation.pDynamicMemMgr->GetCPUAddress()) + Allocation.AlignedOffset; - MappedData.Stride = CopyInfo.Stride; - MappedData.DepthStride = CopyInfo.DepthStride; + MappedData.pData = TextureVk.GetStagingDataCPUAddress() + MapStartOffset; + MappedData.Stride = MipLevelAttribs.RowSize; + MappedData.DepthStride = MipLevelAttribs.RowSize * MipLevelAttribs.Height; - auto it = m_MappedTextures.emplace(MappedTextureKey{&TextureVk, MipLevel, ArraySlice}, MappedTexture{CopyInfo, std::move(Allocation)}); - if(!it.second) - LOG_ERROR_MESSAGE("Mip level ", MipLevel, ", slice ", ArraySlice, " of texture '", TexDesc.Name, "' has already been mapped"); + if (MapType == MAP_READ) + { + DEV_CHECK_ERR((TexDesc.CPUAccessFlags & CPU_ACCESS_READ), "Texture '", TexDesc.Name, "' was not created with CPU_ACCESS_READ flag and can't be mapped for reading"); + // Reaback memory is not created with HOST_COHERENT flag, so we have to explicitly invalidate the mapped range + // to make device writes visible to CPU reads + VERIFY_EXPR(pMapRegion->MaxZ >= 1 && pMapRegion->MaxY >= 1); + auto MapEndOffset = SubresourceOffset + + ((pMapRegion->MaxZ-1) * MipLevelAttribs.Height + (pMapRegion->MaxY-1)) * MipLevelAttribs.RowSize + + (FmtAttribs.ComponentType != COMPONENT_TYPE_COMPRESSED ? + pMapRegion->MaxX * FmtAttribs.ComponentSize * FmtAttribs.NumComponents : + pMapRegion->MaxX / FmtAttribs.BlockWidth * FmtAttribs.ComponentSize); + TextureVk.InvalidateStagingRange(MapStartOffset, MapEndOffset - MapStartOffset); + } + else if (MapType == MAP_WRITE) + { + DEV_CHECK_ERR((TexDesc.CPUAccessFlags & CPU_ACCESS_WRITE), "Texture '", TexDesc.Name, "' was not created with CPU_ACCESS_WRITE flag and can't be mapped for writing"); + // Nothing needs to be done when mapping texture for writing + } + } + else + { + UNSUPPORTED(GetUsageString(TexDesc.Usage), " textures cannot currently be mapped in Vulkan back-end"); + } } void DeviceContextVkImpl::UnmapTextureSubresource(ITexture* pTexture, @@ -1652,23 +1802,43 @@ namespace Diligent TextureVkImpl& TextureVk = *ValidatedCast(pTexture); const auto& TexDesc = TextureVk.GetDesc(); - auto UploadSpaceIt = m_MappedTextures.find(MappedTextureKey{&TextureVk, MipLevel, ArraySlice}); - if(UploadSpaceIt != m_MappedTextures.end()) - { - auto& MappedTex = UploadSpaceIt->second; - CopyBufferToTexture(MappedTex.Allocation.pDynamicMemMgr->GetVkBuffer(), - static_cast(MappedTex.Allocation.AlignedOffset), - MappedTex.CopyInfo.StrideInTexels, - MappedTex.CopyInfo.Region, - TextureVk, - MipLevel, - ArraySlice, - RESOURCE_STATE_TRANSITION_MODE_TRANSITION); - m_MappedTextures.erase(UploadSpaceIt); + + if (TexDesc.Usage == USAGE_DYNAMIC) + { + auto UploadSpaceIt = m_MappedTextures.find(MappedTextureKey{&TextureVk, MipLevel, ArraySlice}); + if(UploadSpaceIt != m_MappedTextures.end()) + { + auto& MappedTex = UploadSpaceIt->second; + CopyBufferToTexture(MappedTex.Allocation.pDynamicMemMgr->GetVkBuffer(), + static_cast(MappedTex.Allocation.AlignedOffset), + MappedTex.CopyInfo.StrideInTexels, + TextureVk, + MappedTex.CopyInfo.Region, + MipLevel, + ArraySlice, + RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + m_MappedTextures.erase(UploadSpaceIt); + } + else + { + LOG_ERROR_MESSAGE("Failed to unmap mip level ", MipLevel, ", slice ", ArraySlice, " of texture '", TexDesc.Name, "'. The texture has either been unmapped already or has not been mapped"); + } + } + else if (TexDesc.Usage == USAGE_STAGING) + { + if (TexDesc.CPUAccessFlags & CPU_ACCESS_READ) + { + // Nothing needs to be done + } + else if (TexDesc.CPUAccessFlags & CPU_ACCESS_WRITE) + { + // Upload textures are currently created with VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, so + // there is no need to explicitly flush the mapped range. + } } else { - LOG_ERROR_MESSAGE("Failed to unmap mip level ", MipLevel, ", slice ", ArraySlice, " of texture '", TexDesc.Name, "'. The texture has either been unmapped already or has not been mapped"); + UNSUPPORTED(GetUsageString(TexDesc.Usage), " textures cannot currently be mapped in Vulkan back-end"); } } diff --git a/Graphics/GraphicsEngineVulkan/src/TextureVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/TextureVkImpl.cpp index 1c77c4b3..6c4e08a0 100644 --- a/Graphics/GraphicsEngineVulkan/src/TextureVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/TextureVkImpl.cpp @@ -31,11 +31,33 @@ #include "EngineMemory.h" #include "StringTools.h" -using namespace Diligent; - namespace Diligent { +MipLevelProperties GetMipLevelProperties(const TextureDesc& TexDesc, Uint32 MipLevel) +{ + MipLevelProperties MipProps; + const auto& FmtAttribs = GetTextureFormatAttribs(TexDesc.Format); + + MipProps.Width = std::max(TexDesc.Width >> MipLevel, 1u); + MipProps.Height = std::max(TexDesc.Height >> MipLevel, 1u); + MipProps.Depth = (TexDesc.Type == RESOURCE_DIM_TEX_3D) ? std::max(TexDesc.Depth >> MipLevel, 1u) : 1u; + if (FmtAttribs.ComponentType == COMPONENT_TYPE_COMPRESSED) + { + VERIFY_EXPR(FmtAttribs.BlockWidth > 1 && FmtAttribs.BlockHeight > 1); + MipProps.Width = (MipProps.Width + FmtAttribs.BlockWidth -1) / FmtAttribs.BlockWidth; + MipProps.Height = (MipProps.Height + FmtAttribs.BlockHeight-1) / FmtAttribs.BlockHeight; + MipProps.RowSize = MipProps.Width * Uint32{FmtAttribs.ComponentSize}; // ComponentSize is the block size + } + else + { + MipProps.RowSize = MipProps.Width * Uint32{FmtAttribs.ComponentSize} * Uint32{FmtAttribs.NumComponents}; + } + MipProps.MipSize = MipProps.RowSize * MipProps.Height * MipProps.Depth; + + return MipProps; +} + TextureVkImpl :: TextureVkImpl(IReferenceCounters* pRefCounters, FixedBlockMemoryAllocator& TexViewObjAllocator, RenderDeviceVkImpl* pRenderDeviceVk, @@ -52,394 +74,465 @@ TextureVkImpl :: TextureVkImpl(IReferenceCounters* pRefCounters, const auto& LogicalDevice = pRenderDeviceVk->GetLogicalDevice(); - VkImageCreateInfo ImageCI = {}; - ImageCI.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - ImageCI.pNext = nullptr; - ImageCI.flags = 0; - if(m_Desc.Type == RESOURCE_DIM_TEX_CUBE || m_Desc.Type == RESOURCE_DIM_TEX_CUBE_ARRAY) - ImageCI.flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; - if(FmtAttribs.IsTypeless) - ImageCI.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; // Specifies that the image can be used to create a - // VkImageView with a different format from the image. - - if (m_Desc.Type == RESOURCE_DIM_TEX_1D || m_Desc.Type == RESOURCE_DIM_TEX_1D_ARRAY) - ImageCI.imageType = VK_IMAGE_TYPE_1D; - else if (m_Desc.Type == RESOURCE_DIM_TEX_2D || m_Desc.Type == RESOURCE_DIM_TEX_2D_ARRAY || m_Desc.Type == RESOURCE_DIM_TEX_CUBE || m_Desc.Type == RESOURCE_DIM_TEX_CUBE_ARRAY) - ImageCI.imageType = VK_IMAGE_TYPE_2D; - else if (m_Desc.Type == RESOURCE_DIM_TEX_3D) + if (m_Desc.Usage == USAGE_STATIC || m_Desc.Usage == USAGE_DEFAULT || m_Desc.Usage == USAGE_DYNAMIC) { - ImageCI.imageType = VK_IMAGE_TYPE_3D; - ImageCI.flags |= VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT; - } - else - { - LOG_ERROR_AND_THROW("Unknown texture type"); - } + VkImageCreateInfo ImageCI = {}; + ImageCI.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + ImageCI.pNext = nullptr; + ImageCI.flags = 0; + if(m_Desc.Type == RESOURCE_DIM_TEX_CUBE || m_Desc.Type == RESOURCE_DIM_TEX_CUBE_ARRAY) + ImageCI.flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; + if(FmtAttribs.IsTypeless) + ImageCI.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; // Specifies that the image can be used to create a + // VkImageView with a different format from the image. + + if (m_Desc.Type == RESOURCE_DIM_TEX_1D || m_Desc.Type == RESOURCE_DIM_TEX_1D_ARRAY) + ImageCI.imageType = VK_IMAGE_TYPE_1D; + else if (m_Desc.Type == RESOURCE_DIM_TEX_2D || m_Desc.Type == RESOURCE_DIM_TEX_2D_ARRAY || m_Desc.Type == RESOURCE_DIM_TEX_CUBE || m_Desc.Type == RESOURCE_DIM_TEX_CUBE_ARRAY) + ImageCI.imageType = VK_IMAGE_TYPE_2D; + else if (m_Desc.Type == RESOURCE_DIM_TEX_3D) + { + ImageCI.imageType = VK_IMAGE_TYPE_3D; + ImageCI.flags |= VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT; + } + else + { + LOG_ERROR_AND_THROW("Unknown texture type"); + } - if (FmtAttribs.IsTypeless) - { - TEXTURE_VIEW_TYPE DefaultTexView; - if(m_Desc.BindFlags & BIND_DEPTH_STENCIL) - DefaultTexView = TEXTURE_VIEW_DEPTH_STENCIL; - else if (m_Desc.BindFlags & BIND_UNORDERED_ACCESS) - DefaultTexView = TEXTURE_VIEW_UNORDERED_ACCESS; - else if (m_Desc.BindFlags & BIND_RENDER_TARGET) - DefaultTexView = TEXTURE_VIEW_RENDER_TARGET; + if (FmtAttribs.IsTypeless) + { + TEXTURE_VIEW_TYPE DefaultTexView; + if(m_Desc.BindFlags & BIND_DEPTH_STENCIL) + DefaultTexView = TEXTURE_VIEW_DEPTH_STENCIL; + else if (m_Desc.BindFlags & BIND_UNORDERED_ACCESS) + DefaultTexView = TEXTURE_VIEW_UNORDERED_ACCESS; + else if (m_Desc.BindFlags & BIND_RENDER_TARGET) + DefaultTexView = TEXTURE_VIEW_RENDER_TARGET; + else + DefaultTexView = TEXTURE_VIEW_SHADER_RESOURCE; + auto DefaultViewFormat = GetDefaultTextureViewFormat(m_Desc, DefaultTexView); + ImageCI.format = TexFormatToVkFormat(DefaultViewFormat); + } else - DefaultTexView = TEXTURE_VIEW_SHADER_RESOURCE; - auto DefaultViewFormat = GetDefaultTextureViewFormat(m_Desc, DefaultTexView); - ImageCI.format = TexFormatToVkFormat(DefaultViewFormat); - } - else - { - ImageCI.format = TexFormatToVkFormat(m_Desc.Format); - } + { + ImageCI.format = TexFormatToVkFormat(m_Desc.Format); + } - 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.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.mipLevels = m_Desc.MipLevels; - if (m_Desc.Type == RESOURCE_DIM_TEX_1D_ARRAY || - m_Desc.Type == RESOURCE_DIM_TEX_2D_ARRAY || - m_Desc.Type == RESOURCE_DIM_TEX_CUBE || - m_Desc.Type == RESOURCE_DIM_TEX_CUBE_ARRAY) - ImageCI.arrayLayers = m_Desc.ArraySize; - else - ImageCI.arrayLayers = 1; + ImageCI.mipLevels = m_Desc.MipLevels; + if (m_Desc.Type == RESOURCE_DIM_TEX_1D_ARRAY || + m_Desc.Type == RESOURCE_DIM_TEX_2D_ARRAY || + m_Desc.Type == RESOURCE_DIM_TEX_CUBE || + m_Desc.Type == RESOURCE_DIM_TEX_CUBE_ARRAY) + ImageCI.arrayLayers = m_Desc.ArraySize; + else + ImageCI.arrayLayers = 1; - ImageCI.samples = static_cast(1 << (m_Desc.SampleCount-1)); - ImageCI.tiling = VK_IMAGE_TILING_OPTIMAL; + ImageCI.samples = static_cast(1 << (m_Desc.SampleCount-1)); + ImageCI.tiling = VK_IMAGE_TILING_OPTIMAL; - ImageCI.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; - if (m_Desc.BindFlags & BIND_RENDER_TARGET) - { - // VK_IMAGE_USAGE_TRANSFER_DST_BIT is required for vkCmdClearColorImage() - ImageCI.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; - } - if (m_Desc.BindFlags & BIND_DEPTH_STENCIL) - { - // 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)) - { - ImageCI.usage |= VK_IMAGE_USAGE_STORAGE_BIT; - } - if (m_Desc.BindFlags & BIND_SHADER_RESOURCE) - { - ImageCI.usage |= VK_IMAGE_USAGE_SAMPLED_BIT; - } + ImageCI.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + if (m_Desc.BindFlags & BIND_RENDER_TARGET) + { + // VK_IMAGE_USAGE_TRANSFER_DST_BIT is required for vkCmdClearColorImage() + ImageCI.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + } + if (m_Desc.BindFlags & BIND_DEPTH_STENCIL) + { + // 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)) + { + ImageCI.usage |= VK_IMAGE_USAGE_STORAGE_BIT; + } + if (m_Desc.BindFlags & BIND_SHADER_RESOURCE) + { + ImageCI.usage |= VK_IMAGE_USAGE_SAMPLED_BIT; + } - ImageCI.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - ImageCI.queueFamilyIndexCount = 0; - ImageCI.pQueueFamilyIndices = nullptr; + ImageCI.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + ImageCI.queueFamilyIndexCount = 0; + ImageCI.pQueueFamilyIndices = nullptr; - // initialLayout must be either VK_IMAGE_LAYOUT_UNDEFINED or VK_IMAGE_LAYOUT_PREINITIALIZED (11.4) - // If it is VK_IMAGE_LAYOUT_PREINITIALIZED, then the image data can be preinitialized by the host - // while using this layout, and the transition away from this layout will preserve that data. - // If it is VK_IMAGE_LAYOUT_UNDEFINED, then the contents of the data are considered to be undefined, - // and the transition away from this layout is not guaranteed to preserve that data. - ImageCI.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + // initialLayout must be either VK_IMAGE_LAYOUT_UNDEFINED or VK_IMAGE_LAYOUT_PREINITIALIZED (11.4) + // If it is VK_IMAGE_LAYOUT_PREINITIALIZED, then the image data can be preinitialized by the host + // while using this layout, and the transition away from this layout will preserve that data. + // If it is VK_IMAGE_LAYOUT_UNDEFINED, then the contents of the data are considered to be undefined, + // and the transition away from this layout is not guaranteed to preserve that data. + ImageCI.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - bool bInitializeTexture = (pInitData != nullptr && pInitData->pSubResources != nullptr && pInitData->NumSubresources > 0); + bool bInitializeTexture = (pInitData != nullptr && pInitData->pSubResources != nullptr && pInitData->NumSubresources > 0); - m_VulkanImage = LogicalDevice.CreateImage(ImageCI, m_Desc.Name); + m_VulkanImage = LogicalDevice.CreateImage(ImageCI, m_Desc.Name); - VkMemoryRequirements MemReqs = LogicalDevice.GetImageMemoryRequirements(m_VulkanImage); + VkMemoryRequirements MemReqs = LogicalDevice.GetImageMemoryRequirements(m_VulkanImage); - VkMemoryPropertyFlags ImageMemoryFlags = 0; - if (m_Desc.Usage == USAGE_STAGING) - ImageMemoryFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; - else - ImageMemoryFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + VkMemoryPropertyFlags ImageMemoryFlags = 0; + if (m_Desc.Usage == USAGE_STAGING) + ImageMemoryFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + else + ImageMemoryFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; - VERIFY( IsPowerOfTwo(MemReqs.alignment), "Alignment is not power of 2!"); - m_MemoryAllocation = pRenderDeviceVk->AllocateMemory(MemReqs, ImageMemoryFlags); - auto AlignedOffset = Align(m_MemoryAllocation.UnalignedOffset, MemReqs.alignment); - VERIFY_EXPR(m_MemoryAllocation.Size >= MemReqs.size + (AlignedOffset - m_MemoryAllocation.UnalignedOffset)); - auto Memory = m_MemoryAllocation.Page->GetVkMemory(); - auto err = LogicalDevice.BindImageMemory(m_VulkanImage, Memory, AlignedOffset); - CHECK_VK_ERROR_AND_THROW(err, "Failed to bind image memory"); + VERIFY( IsPowerOfTwo(MemReqs.alignment), "Alignment is not power of 2!"); + m_MemoryAllocation = pRenderDeviceVk->AllocateMemory(MemReqs, ImageMemoryFlags); + auto AlignedOffset = Align(m_MemoryAllocation.UnalignedOffset, MemReqs.alignment); + VERIFY_EXPR(m_MemoryAllocation.Size >= MemReqs.size + (AlignedOffset - m_MemoryAllocation.UnalignedOffset)); + auto Memory = m_MemoryAllocation.Page->GetVkMemory(); + auto err = LogicalDevice.BindImageMemory(m_VulkanImage, Memory, AlignedOffset); + CHECK_VK_ERROR_AND_THROW(err, "Failed to bind image memory"); - // Vulkan validation layers do not like uninitialized memory, so if no initial data - // is provided, we will clear the memory + // Vulkan validation layers do not like uninitialized memory, so if no initial data + // is provided, we will clear the memory - VulkanUtilities::CommandPoolWrapper CmdPool; - VkCommandBuffer vkCmdBuff; - pRenderDeviceVk->AllocateTransientCmdPool(CmdPool, vkCmdBuff, "Transient command pool to copy staging data to a device buffer"); + VulkanUtilities::CommandPoolWrapper CmdPool; + VkCommandBuffer vkCmdBuff; + pRenderDeviceVk->AllocateTransientCmdPool(CmdPool, vkCmdBuff, "Transient command pool to copy staging data to a device buffer"); - VkImageAspectFlags aspectMask = 0; - if (FmtAttribs.ComponentType == COMPONENT_TYPE_DEPTH) - aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - else if (FmtAttribs.ComponentType == COMPONENT_TYPE_DEPTH_STENCIL) - { - if(bInitializeTexture) - { - UNSUPPORTED("Initializing depth-stencil texture is not currently supported"); - // Only single aspect bit must be specified when copying texture data - } - aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; - } - else - aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - - // For either clear or copy command, dst layout must be VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL - VkImageSubresourceRange SubresRange; - SubresRange.aspectMask = aspectMask; - SubresRange.baseArrayLayer = 0; - SubresRange.layerCount = VK_REMAINING_ARRAY_LAYERS; - SubresRange.baseMipLevel = 0; - SubresRange.levelCount = VK_REMAINING_MIP_LEVELS; - auto EnabledGraphicsShaderStages = LogicalDevice.GetEnabledGraphicsShaderStages(); - VulkanUtilities::VulkanCommandBuffer::TransitionImageLayout(vkCmdBuff, m_VulkanImage, ImageCI.initialLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, SubresRange, EnabledGraphicsShaderStages); - SetState(RESOURCE_STATE_COPY_DEST); - const auto CurrentLayout = GetLayout(); - VERIFY_EXPR(CurrentLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - - if(bInitializeTexture) - { - Uint32 ExpectedNumSubresources = ImageCI.mipLevels * ImageCI.arrayLayers; - if (pInitData->NumSubresources != ExpectedNumSubresources ) - LOG_ERROR_AND_THROW("Incorrect number of subresources in init data. ", ExpectedNumSubresources, " expected, while ", pInitData->NumSubresources, " provided"); - - std::vector Regions(pInitData->NumSubresources); - - Uint64 uploadBufferSize = 0; - Uint32 subres = 0; - for(Uint32 layer = 0; layer < ImageCI.arrayLayers; ++layer) + VkImageAspectFlags aspectMask = 0; + if (FmtAttribs.ComponentType == COMPONENT_TYPE_DEPTH) + aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + else if (FmtAttribs.ComponentType == COMPONENT_TYPE_DEPTH_STENCIL) { - for(Uint32 mip = 0; mip < ImageCI.mipLevels; ++mip) + if(bInitializeTexture) { - const auto& SubResData = pInitData->pSubResources[subres]; (void)SubResData; - auto& CopyRegion = Regions[subres]; - - auto MipWidth = std::max(m_Desc.Width >> mip, 1u); - auto MipHeight = std::max(m_Desc.Height >> mip, 1u); - auto MipDepth = (m_Desc.Type == RESOURCE_DIM_TEX_3D) ? std::max(m_Desc.Depth >> mip, 1u) : 1u; - - CopyRegion.bufferOffset = uploadBufferSize; // offset in bytes from the start of the buffer object - // bufferRowLength and bufferImageHeight specify the data in buffer memory as a subregion - // of a larger two- or three-dimensional image, and control the addressing calculations of - // data in buffer memory. If either of these values is zero, that aspect of the buffer memory - // is considered to be tightly packed according to the imageExtent. (18.4) - CopyRegion.bufferRowLength = 0; - CopyRegion.bufferImageHeight = 0; - // For block-compression formats, all parameters are still specified in texels rather than compressed texel blocks (18.4.1) - CopyRegion.imageOffset = VkOffset3D{0, 0, 0}; - CopyRegion.imageExtent = VkExtent3D{MipWidth, MipHeight, MipDepth}; - - CopyRegion.imageSubresource.aspectMask = aspectMask; - CopyRegion.imageSubresource.mipLevel = mip; - CopyRegion.imageSubresource.baseArrayLayer = layer; - CopyRegion.imageSubresource.layerCount = 1; - - Uint32 RowSize = 0; - if(FmtAttribs.ComponentType == COMPONENT_TYPE_COMPRESSED) - { - VERIFY_EXPR(FmtAttribs.BlockWidth > 1 && FmtAttribs.BlockHeight > 1); - MipWidth = (MipWidth + FmtAttribs.BlockWidth -1) / FmtAttribs.BlockWidth; - MipHeight = (MipHeight + FmtAttribs.BlockHeight-1) / FmtAttribs.BlockHeight; - RowSize = MipWidth * Uint32{FmtAttribs.ComponentSize}; // ComponentSize is the block size - } - else - { - RowSize = MipWidth * Uint32{FmtAttribs.ComponentSize} * Uint32{FmtAttribs.NumComponents}; - } - auto MipSize = RowSize * MipHeight * MipDepth; - VERIFY(SubResData.Stride == 0 || SubResData.Stride >= RowSize, "Stride is too small"); - VERIFY(SubResData.DepthStride == 0 || SubResData.DepthStride >= RowSize * MipHeight, "Depth stride is too small"); - - // bufferOffset must be a multiple of 4 (18.4) - // If the calling command's VkImage parameter is a compressed image, bufferOffset - // must be a multiple of the compressed texel block size in bytes (18.4). This - // is automatically guaranteed as MipWidth and MipHeight are rounded to block size - uploadBufferSize += (MipSize + 3) & (~3); - ++subres; + UNSUPPORTED("Initializing depth-stencil texture is not currently supported"); + // Only single aspect bit must be specified when copying texture data } + aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; } - VERIFY_EXPR(subres == pInitData->NumSubresources); + else + aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + + // For either clear or copy command, dst layout must be VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL + VkImageSubresourceRange SubresRange; + SubresRange.aspectMask = aspectMask; + SubresRange.baseArrayLayer = 0; + SubresRange.layerCount = VK_REMAINING_ARRAY_LAYERS; + SubresRange.baseMipLevel = 0; + SubresRange.levelCount = VK_REMAINING_MIP_LEVELS; + auto EnabledGraphicsShaderStages = LogicalDevice.GetEnabledGraphicsShaderStages(); + VulkanUtilities::VulkanCommandBuffer::TransitionImageLayout(vkCmdBuff, m_VulkanImage, ImageCI.initialLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, SubresRange, EnabledGraphicsShaderStages); + SetState(RESOURCE_STATE_COPY_DEST); + const auto CurrentLayout = GetLayout(); + VERIFY_EXPR(CurrentLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - VkBufferCreateInfo VkStaginBuffCI = {}; - VkStaginBuffCI.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - VkStaginBuffCI.pNext = nullptr; - VkStaginBuffCI.flags = 0; - VkStaginBuffCI.size = uploadBufferSize; - VkStaginBuffCI.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - VkStaginBuffCI.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - VkStaginBuffCI.queueFamilyIndexCount = 0; - VkStaginBuffCI.pQueueFamilyIndices = nullptr; + if(bInitializeTexture) + { + Uint32 ExpectedNumSubresources = ImageCI.mipLevels * ImageCI.arrayLayers; + if (pInitData->NumSubresources != ExpectedNumSubresources ) + LOG_ERROR_AND_THROW("Incorrect number of subresources in init data. ", ExpectedNumSubresources, " expected, while ", pInitData->NumSubresources, " provided"); - std::string StagingBufferName = "Staging buffer for '"; - StagingBufferName += m_Desc.Name; - StagingBufferName += '\''; - VulkanUtilities::BufferWrapper StagingBuffer = LogicalDevice.CreateBuffer(VkStaginBuffCI, StagingBufferName.c_str()); + std::vector Regions(pInitData->NumSubresources); - VkMemoryRequirements StagingBufferMemReqs = LogicalDevice.GetBufferMemoryRequirements(StagingBuffer); - VERIFY( IsPowerOfTwo(StagingBufferMemReqs.alignment), "Alignment is not power of 2!"); - // VK_MEMORY_PROPERTY_HOST_COHERENT_BIT bit specifies that the host cache management commands vkFlushMappedMemoryRanges - // and vkInvalidateMappedMemoryRanges are NOT needed to flush host writes to the device or make device writes visible - // to the host (10.2) - auto StagingMemoryAllocation = pRenderDeviceVk->AllocateMemory(StagingBufferMemReqs, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - auto StagingBufferMemory = StagingMemoryAllocation.Page->GetVkMemory(); - auto AlignedStagingMemOffset = Align(StagingMemoryAllocation.UnalignedOffset, StagingBufferMemReqs.alignment); - VERIFY_EXPR(StagingMemoryAllocation.Size >= StagingBufferMemReqs.size + (AlignedStagingMemOffset - StagingMemoryAllocation.UnalignedOffset)); - - auto *StagingData = reinterpret_cast(StagingMemoryAllocation.Page->GetCPUMemory()); - VERIFY_EXPR(StagingData != nullptr); - StagingData += AlignedStagingMemOffset; - - subres = 0; - for(Uint32 layer = 0; layer < ImageCI.arrayLayers; ++layer) - { - for(Uint32 mip = 0; mip < ImageCI.mipLevels; ++mip) + Uint64 uploadBufferSize = 0; + Uint32 subres = 0; + for(Uint32 layer = 0; layer < ImageCI.arrayLayers; ++layer) { - const auto &SubResData = pInitData->pSubResources[subres]; - const auto &CopyRegion = Regions[subres]; - - auto MipWidth = CopyRegion.imageExtent.width; - auto MipHeight = CopyRegion.imageExtent.height; - auto MipDepth = CopyRegion.imageExtent.depth; - Uint32 RowSize = 0; - if(FmtAttribs.ComponentType == COMPONENT_TYPE_COMPRESSED) + for(Uint32 mip = 0; mip < ImageCI.mipLevels; ++mip) { - VERIFY_EXPR(FmtAttribs.BlockWidth > 1 && FmtAttribs.BlockHeight > 1); - MipWidth = (MipWidth + FmtAttribs.BlockWidth -1) / FmtAttribs.BlockWidth; - MipHeight = (MipHeight + FmtAttribs.BlockHeight-1) / FmtAttribs.BlockHeight; - RowSize = MipWidth * Uint32{FmtAttribs.ComponentSize}; // ComponentSize is the block size + const auto& SubResData = pInitData->pSubResources[subres]; (void)SubResData; + auto& CopyRegion = Regions[subres]; + + auto MipInfo = GetMipLevelProperties(m_Desc, mip); + + CopyRegion.bufferOffset = uploadBufferSize; // offset in bytes from the start of the buffer object + // bufferRowLength and bufferImageHeight specify the data in buffer memory as a subregion + // of a larger two- or three-dimensional image, and control the addressing calculations of + // data in buffer memory. If either of these values is zero, that aspect of the buffer memory + // is considered to be tightly packed according to the imageExtent. (18.4) + CopyRegion.bufferRowLength = 0; + CopyRegion.bufferImageHeight = 0; + // For block-compression formats, all parameters are still specified in texels rather than compressed texel blocks (18.4.1) + CopyRegion.imageOffset = VkOffset3D{0, 0, 0}; + CopyRegion.imageExtent = VkExtent3D{MipInfo.Width, MipInfo.Height, MipInfo.Depth}; + + CopyRegion.imageSubresource.aspectMask = aspectMask; + CopyRegion.imageSubresource.mipLevel = mip; + CopyRegion.imageSubresource.baseArrayLayer = layer; + CopyRegion.imageSubresource.layerCount = 1; + + VERIFY(SubResData.Stride == 0 || SubResData.Stride >= MipInfo.RowSize, "Stride is too small"); + VERIFY(SubResData.DepthStride == 0 || SubResData.DepthStride >= MipInfo.RowSize * MipInfo.Height, "Depth stride is too small"); + + // bufferOffset must be a multiple of 4 (18.4) + // If the calling command's VkImage parameter is a compressed image, bufferOffset + // must be a multiple of the compressed texel block size in bytes (18.4). This + // is automatically guaranteed as MipWidth and MipHeight are rounded to block size + uploadBufferSize += (MipInfo.MipSize + 3) & (~3); + ++subres; } - else + } + VERIFY_EXPR(subres == pInitData->NumSubresources); + + VkBufferCreateInfo VkStaginBuffCI = {}; + VkStaginBuffCI.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + VkStaginBuffCI.pNext = nullptr; + VkStaginBuffCI.flags = 0; + VkStaginBuffCI.size = uploadBufferSize; + VkStaginBuffCI.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + VkStaginBuffCI.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + VkStaginBuffCI.queueFamilyIndexCount = 0; + VkStaginBuffCI.pQueueFamilyIndices = nullptr; + + std::string StagingBufferName = "Upload buffer for '"; + StagingBufferName += m_Desc.Name; + StagingBufferName += '\''; + VulkanUtilities::BufferWrapper StagingBuffer = LogicalDevice.CreateBuffer(VkStaginBuffCI, StagingBufferName.c_str()); + + VkMemoryRequirements StagingBufferMemReqs = LogicalDevice.GetBufferMemoryRequirements(StagingBuffer); + VERIFY( IsPowerOfTwo(StagingBufferMemReqs.alignment), "Alignment is not power of 2!"); + // VK_MEMORY_PROPERTY_HOST_COHERENT_BIT bit specifies that the host cache management commands vkFlushMappedMemoryRanges + // and vkInvalidateMappedMemoryRanges are NOT needed to flush host writes to the device or make device writes visible + // to the host (10.2) + auto StagingMemoryAllocation = pRenderDeviceVk->AllocateMemory(StagingBufferMemReqs, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + auto StagingBufferMemory = StagingMemoryAllocation.Page->GetVkMemory(); + auto AlignedStagingMemOffset = Align(StagingMemoryAllocation.UnalignedOffset, StagingBufferMemReqs.alignment); + VERIFY_EXPR(StagingMemoryAllocation.Size >= StagingBufferMemReqs.size + (AlignedStagingMemOffset - StagingMemoryAllocation.UnalignedOffset)); + + auto *StagingData = reinterpret_cast(StagingMemoryAllocation.Page->GetCPUMemory()); + VERIFY_EXPR(StagingData != nullptr); + StagingData += AlignedStagingMemOffset; + + subres = 0; + for(Uint32 layer = 0; layer < ImageCI.arrayLayers; ++layer) + { + for(Uint32 mip = 0; mip < ImageCI.mipLevels; ++mip) { - RowSize = MipWidth * Uint32{FmtAttribs.ComponentSize} * Uint32{FmtAttribs.NumComponents}; - } - VERIFY(SubResData.Stride == 0 || SubResData.Stride >= RowSize, "Stride is too small"); - VERIFY(SubResData.DepthStride == 0 || SubResData.DepthStride >= RowSize * MipHeight, "Depth stride is too small"); + const auto &SubResData = pInitData->pSubResources[subres]; + const auto &CopyRegion = Regions[subres]; - for(Uint32 z=0; z < MipDepth; ++z) - { - for(Uint32 y=0; y < MipHeight; ++y) + auto MipInfo = GetMipLevelProperties(m_Desc, mip); + + VERIFY_EXPR(MipInfo.Width == CopyRegion.imageExtent.width); + VERIFY_EXPR(MipInfo.Height == CopyRegion.imageExtent.height); + VERIFY_EXPR(MipInfo.Depth == CopyRegion.imageExtent.depth); + + VERIFY(SubResData.Stride == 0 || SubResData.Stride >= MipInfo.RowSize, "Stride is too small"); + VERIFY(SubResData.DepthStride == 0 || SubResData.DepthStride >= MipInfo.RowSize * MipInfo.Height, "Depth stride is too small"); + + for(Uint32 z=0; z < MipInfo.Depth; ++z) { - memcpy(StagingData + CopyRegion.bufferOffset + (y + z * MipHeight) * RowSize, - reinterpret_cast(SubResData.pData) + y * SubResData.Stride + z * SubResData.DepthStride, - RowSize); + for(Uint32 y=0; y < MipInfo.Height; ++y) + { + memcpy(StagingData + CopyRegion.bufferOffset + (y + z * MipInfo.Height) * MipInfo.RowSize, + reinterpret_cast(SubResData.pData) + y * SubResData.Stride + z * SubResData.DepthStride, + MipInfo.RowSize); + } } - } - ++subres; + ++subres; + } } - } - VERIFY_EXPR(subres == pInitData->NumSubresources); + VERIFY_EXPR(subres == pInitData->NumSubresources); - err = LogicalDevice.BindBufferMemory(StagingBuffer, StagingBufferMemory, AlignedStagingMemOffset); - CHECK_VK_ERROR_AND_THROW(err, "Failed to bind staging bufer memory"); + err = LogicalDevice.BindBufferMemory(StagingBuffer, StagingBufferMemory, AlignedStagingMemOffset); + CHECK_VK_ERROR_AND_THROW(err, "Failed to bind staging bufer memory"); - VulkanUtilities::VulkanCommandBuffer::BufferMemoryBarrier(vkCmdBuff, StagingBuffer, 0, VK_ACCESS_TRANSFER_READ_BIT, EnabledGraphicsShaderStages); + VulkanUtilities::VulkanCommandBuffer::BufferMemoryBarrier(vkCmdBuff, StagingBuffer, 0, VK_ACCESS_TRANSFER_READ_BIT, EnabledGraphicsShaderStages); - // Copy commands MUST be recorded outside of a render pass instance. This is OK here - // as copy will be the only command in the cmd buffer - vkCmdCopyBufferToImage(vkCmdBuff, StagingBuffer, m_VulkanImage, - CurrentLayout, // dstImageLayout must be VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL or VK_IMAGE_LAYOUT_GENERAL (18.4) - static_cast(Regions.size()), Regions.data()); + // Copy commands MUST be recorded outside of a render pass instance. This is OK here + // as copy will be the only command in the cmd buffer + vkCmdCopyBufferToImage(vkCmdBuff, StagingBuffer, m_VulkanImage, + CurrentLayout, // dstImageLayout must be VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL or VK_IMAGE_LAYOUT_GENERAL (18.4) + static_cast(Regions.size()), Regions.data()); - Uint32 QueueIndex = 0; - pRenderDeviceVk->ExecuteAndDisposeTransientCmdBuff(QueueIndex, vkCmdBuff, std::move(CmdPool)); + Uint32 QueueIndex = 0; + pRenderDeviceVk->ExecuteAndDisposeTransientCmdBuff(QueueIndex, vkCmdBuff, std::move(CmdPool)); - // After command buffer is submitted, safe-release resources. This strategy - // is little overconservative as the resources will be released after the first - // command buffer submitted through the immediate context will be completed - pRenderDeviceVk->SafeReleaseDeviceObject(std::move(StagingBuffer), Uint64{1} << Uint64{QueueIndex}); - pRenderDeviceVk->SafeReleaseDeviceObject(std::move(StagingMemoryAllocation), Uint64{1} << Uint64{QueueIndex}); - } - else - { - VkImageSubresourceRange Subresource; - Subresource.aspectMask = aspectMask; - Subresource.baseMipLevel = 0; - Subresource.levelCount = VK_REMAINING_MIP_LEVELS; - Subresource.baseArrayLayer = 0; - Subresource.layerCount = VK_REMAINING_ARRAY_LAYERS; - if(aspectMask == VK_IMAGE_ASPECT_COLOR_BIT) + // After command buffer is submitted, safe-release resources. This strategy + // is little overconservative as the resources will be released after the first + // command buffer submitted through the immediate context will be completed + pRenderDeviceVk->SafeReleaseDeviceObject(std::move(StagingBuffer), Uint64{1} << Uint64{QueueIndex}); + pRenderDeviceVk->SafeReleaseDeviceObject(std::move(StagingMemoryAllocation), Uint64{1} << Uint64{QueueIndex}); + } + else { - if(FmtAttribs.ComponentType != COMPONENT_TYPE_COMPRESSED) + VkImageSubresourceRange Subresource; + Subresource.aspectMask = aspectMask; + Subresource.baseMipLevel = 0; + Subresource.levelCount = VK_REMAINING_MIP_LEVELS; + Subresource.baseArrayLayer = 0; + Subresource.layerCount = VK_REMAINING_ARRAY_LAYERS; + if(aspectMask == VK_IMAGE_ASPECT_COLOR_BIT) { - VkClearColorValue ClearColor = {}; - vkCmdClearColorImage(vkCmdBuff, m_VulkanImage, + if(FmtAttribs.ComponentType != COMPONENT_TYPE_COMPRESSED) + { + VkClearColorValue ClearColor = {}; + vkCmdClearColorImage(vkCmdBuff, m_VulkanImage, + CurrentLayout, // must be VK_IMAGE_LAYOUT_GENERAL or VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL + &ClearColor, 1, &Subresource); + } + } + else if(aspectMask == VK_IMAGE_ASPECT_DEPTH_BIT || + aspectMask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT) ) + { + VkClearDepthStencilValue ClearValue = {}; + vkCmdClearDepthStencilImage(vkCmdBuff, m_VulkanImage, CurrentLayout, // must be VK_IMAGE_LAYOUT_GENERAL or VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL - &ClearColor, 1, &Subresource); + &ClearValue, 1, &Subresource); + } + else + { + UNEXPECTED("Unexpected aspect mask"); } + Uint32 QueueIndex = 0; + pRenderDeviceVk->ExecuteAndDisposeTransientCmdBuff(QueueIndex, vkCmdBuff, std::move(CmdPool)); } - else if(aspectMask == VK_IMAGE_ASPECT_DEPTH_BIT || - aspectMask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT) ) + + + if(m_Desc.MiscFlags & MISC_TEXTURE_FLAG_GENERATE_MIPS) { - VkClearDepthStencilValue ClearValue = {}; - vkCmdClearDepthStencilImage(vkCmdBuff, m_VulkanImage, - CurrentLayout, // must be VK_IMAGE_LAYOUT_GENERAL or VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL - &ClearValue, 1, &Subresource); + 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 + } + else if(m_Desc.Usage == USAGE_STAGING) + { + VkBufferCreateInfo VkStaginBuffCI = {}; + VkStaginBuffCI.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + VkStaginBuffCI.pNext = nullptr; + VkStaginBuffCI.flags = 0; + VkStaginBuffCI.size = GetStagingDataOffset(m_Desc, m_Desc.ArraySize, 0); + + DEV_CHECK_ERR( (m_Desc.CPUAccessFlags & (CPU_ACCESS_READ | CPU_ACCESS_WRITE)) == CPU_ACCESS_READ || + (m_Desc.CPUAccessFlags & (CPU_ACCESS_READ | CPU_ACCESS_WRITE)) == CPU_ACCESS_WRITE, + "Exactly one of CPU_ACCESS_READ or CPU_ACCESS_WRITE flags must be specified"); + VkMemoryPropertyFlags MemProperties = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + if (m_Desc.CPUAccessFlags & CPU_ACCESS_READ) + { + VkStaginBuffCI.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; + MemProperties |= VK_MEMORY_PROPERTY_HOST_CACHED_BIT; + SetState(RESOURCE_STATE_COPY_DEST); + } + else if (m_Desc.CPUAccessFlags & CPU_ACCESS_WRITE) { - UNEXPECTED("Unexpected aspect mask"); + VkStaginBuffCI.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + // VK_MEMORY_PROPERTY_HOST_COHERENT_BIT bit specifies that the host cache management commands vkFlushMappedMemoryRanges + // and vkInvalidateMappedMemoryRanges are NOT needed to flush host writes to the device or make device writes visible + // to the host (10.2) + MemProperties |= VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + SetState(RESOURCE_STATE_COPY_SOURCE); } - Uint32 QueueIndex = 0; - pRenderDeviceVk->ExecuteAndDisposeTransientCmdBuff(QueueIndex, vkCmdBuff, std::move(CmdPool)); + else + UNEXPECTED("Unexpected CPU access"); + + VkStaginBuffCI.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + VkStaginBuffCI.queueFamilyIndexCount = 0; + VkStaginBuffCI.pQueueFamilyIndices = nullptr; + + std::string StagingBufferName = "Staging buffer for '"; + StagingBufferName += m_Desc.Name; + StagingBufferName += '\''; + m_StagingBuffer = LogicalDevice.CreateBuffer(VkStaginBuffCI, StagingBufferName.c_str()); + + VkMemoryRequirements StagingBufferMemReqs = LogicalDevice.GetBufferMemoryRequirements(m_StagingBuffer); + VERIFY( IsPowerOfTwo(StagingBufferMemReqs.alignment), "Alignment is not power of 2!"); + + m_MemoryAllocation = pRenderDeviceVk->AllocateMemory(StagingBufferMemReqs, MemProperties); + auto StagingBufferMemory = m_MemoryAllocation.Page->GetVkMemory(); + auto AlignedStagingMemOffset = Align(m_MemoryAllocation.UnalignedOffset, StagingBufferMemReqs.alignment); + VERIFY_EXPR(m_MemoryAllocation.Size >= StagingBufferMemReqs.size + (AlignedStagingMemOffset - m_MemoryAllocation.UnalignedOffset)); + + auto err = LogicalDevice.BindBufferMemory(m_StagingBuffer, StagingBufferMemory, AlignedStagingMemOffset); + CHECK_VK_ERROR_AND_THROW(err, "Failed to bind staging bufer memory"); + + m_StagingDataAlignedOffset = AlignedStagingMemOffset; + } + else + { + UNSUPPORTED("Unsupported usage ", GetUsageString(m_Desc.Usage)); } + VERIFY_EXPR(IsInKnownState()); +} - if(m_Desc.MiscFlags & MISC_TEXTURE_FLAG_GENERATE_MIPS) +Uint32 GetStagingDataOffset(const TextureDesc& TexDesc, Uint32 ArraySlice, Uint32 MipLevel) +{ + VERIFY_EXPR(ArraySlice < TexDesc.ArraySize && MipLevel < TexDesc.MipLevels || ArraySlice == TexDesc.ArraySize && MipLevel == 0); + + Uint32 Offset = 0; + if (ArraySlice > 0) { - if (m_Desc.Type != RESOURCE_DIM_TEX_2D && m_Desc.Type != RESOURCE_DIM_TEX_2D_ARRAY) + Uint32 ArraySliceSize = 0; + for (Uint32 mip = 0; mip < TexDesc.MipLevels; ++mip) { - LOG_ERROR_AND_THROW("Mipmap generation is only supported for 2D textures and texture arrays"); + auto MipInfo = GetMipLevelProperties(TexDesc, mip); + // bufferOffset must be a multiple of 4 (18.4) + // If the calling command's VkImage parameter is a compressed image, bufferOffset + // must be a multiple of the compressed texel block size in bytes (18.4). This + // is automatically guaranteed as MipWidth and MipHeight are rounded to block size + ArraySliceSize += (MipInfo.MipSize + 3) & (~3); } - 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); + Offset = ArraySliceSize; + if (TexDesc.Type == RESOURCE_DIM_TEX_1D_ARRAY || + TexDesc.Type == RESOURCE_DIM_TEX_2D_ARRAY || + TexDesc.Type == RESOURCE_DIM_TEX_CUBE || + TexDesc.Type == RESOURCE_DIM_TEX_CUBE_ARRAY) + Offset *= TexDesc.ArraySize; + } - 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); + for (Uint32 mip = 0; mip < MipLevel; ++mip) + { + auto MipInfo = GetMipLevelProperties(TexDesc, mip); + // bufferOffset must be a multiple of 4 (18.4) + Offset += (MipInfo.MipSize + 3) & (~3); } - VERIFY_EXPR(IsInKnownState()); + return Offset; } TextureVkImpl::TextureVkImpl(IReferenceCounters* pRefCounters, @@ -494,7 +587,10 @@ TextureVkImpl :: ~TextureVkImpl() { // Vk object can only be destroyed when it is no longer used by the GPU // Wrappers for external texture will not be destroyed as they are created with null device pointer - m_pDevice->SafeReleaseDeviceObject(std::move(m_VulkanImage), m_Desc.CommandQueueMask); + if (m_VulkanImage) + m_pDevice->SafeReleaseDeviceObject(std::move(m_VulkanImage), m_Desc.CommandQueueMask); + if (m_StagingBuffer) + m_pDevice->SafeReleaseDeviceObject(std::move(m_StagingBuffer), m_Desc.CommandQueueMask); m_pDevice->SafeReleaseDeviceObject(std::move(m_MemoryAllocation), m_Desc.CommandQueueMask); } @@ -650,4 +746,17 @@ VkImageLayout TextureVkImpl::GetLayout()const return ResourceStateToVkImageLayout(GetState()); } +void TextureVkImpl::InvalidateStagingRange(VkDeviceSize Offset, VkDeviceSize Size) +{ + const auto& LogicalDevice = m_pDevice->GetLogicalDevice(); + VkMappedMemoryRange InvalidateRange = {}; + InvalidateRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; + InvalidateRange.pNext = nullptr; + InvalidateRange.memory = m_MemoryAllocation.Page->GetVkMemory(); + InvalidateRange.offset = m_StagingDataAlignedOffset + Offset; + InvalidateRange.size = Size; + auto err = LogicalDevice.InvalidateMappedMemoryRanges(1, &InvalidateRange); + DEV_CHECK_ERR(err == VK_SUCCESS, "Failed to invalidated mapped texture memory range"); +} + } diff --git a/Graphics/GraphicsEngineVulkan/src/VulkanUtilities/VulkanLogicalDevice.cpp b/Graphics/GraphicsEngineVulkan/src/VulkanUtilities/VulkanLogicalDevice.cpp index 01155f6f..fbdb37a8 100644 --- a/Graphics/GraphicsEngineVulkan/src/VulkanUtilities/VulkanLogicalDevice.cpp +++ b/Graphics/GraphicsEngineVulkan/src/VulkanUtilities/VulkanLogicalDevice.cpp @@ -415,6 +415,16 @@ namespace VulkanUtilities vkUnmapMemory(m_VkDevice, memory); } + VkResult VulkanLogicalDevice::InvalidateMappedMemoryRanges(uint32_t memoryRangeCount, const VkMappedMemoryRange* pMemoryRanges)const + { + return vkInvalidateMappedMemoryRanges(m_VkDevice, memoryRangeCount, pMemoryRanges); + } + + VkResult VulkanLogicalDevice::FlushMappedMemoryRanges(uint32_t memoryRangeCount, const VkMappedMemoryRange* pMemoryRanges)const + { + return vkFlushMappedMemoryRanges(m_VkDevice, memoryRangeCount, pMemoryRanges); + } + VkResult VulkanLogicalDevice::GetFenceStatus(VkFence fence)const { return vkGetFenceStatus(m_VkDevice, fence); -- cgit v1.2.3