diff options
| author | Egor Yusov <egor.yusov@gmail.com> | 2018-07-07 22:40:03 +0000 |
|---|---|---|
| committer | Egor Yusov <egor.yusov@gmail.com> | 2018-07-07 22:40:03 +0000 |
| commit | 6896a3c7bae82319682f3bb0af80c20a0b907a1d (patch) | |
| tree | bf7e822be87f5a9dcfc9692d7aea3b622a205c77 /Graphics/GraphicsEngineVulkan | |
| parent | Fixed Vulkan swapchain present error when window is minimized (diff) | |
| download | DiligentCore-6896a3c7bae82319682f3bb0af80c20a0b907a1d.tar.gz DiligentCore-6896a3c7bae82319682f3bb0af80c20a0b907a1d.zip | |
Implemented mipmap generation in Vulkan
Diffstat (limited to 'Graphics/GraphicsEngineVulkan')
19 files changed, 1000 insertions, 343 deletions
diff --git a/Graphics/GraphicsEngineVulkan/CMakeLists.txt b/Graphics/GraphicsEngineVulkan/CMakeLists.txt index d93101be..0c675546 100644 --- a/Graphics/GraphicsEngineVulkan/CMakeLists.txt +++ b/Graphics/GraphicsEngineVulkan/CMakeLists.txt @@ -13,7 +13,7 @@ set(INCLUDE include/DeviceContextVkImpl.h include/VulkanDynamicHeap.h include/FramebufferCache.h - include/GenerateMips.h + include/GenerateMipsVkHelper.h include/pch.h include/PipelineLayout.h include/PipelineStateVkImpl.h @@ -74,7 +74,7 @@ set(SRC src/DeviceContextVkImpl.cpp src/VulkanDynamicHeap.cpp src/FramebufferCache.cpp - src/GenerateMips.cpp + src/GenerateMipsVkHelper.cpp src/PipelineLayout.cpp src/PipelineStateVkImpl.cpp src/RenderDeviceVkImpl.cpp @@ -105,25 +105,34 @@ set(VULKAN_UTILS_SRC src/VulkanUtilities/VulkanUploadHeap.cpp ) -#set(SHADERS -# shaders/GenerateMips/GenerateMipsGammaCS.hlsl -# shaders/GenerateMips/GenerateMipsGammaOddCS.hlsl -# shaders/GenerateMips/GenerateMipsGammaOddXCS.hlsl -# shaders/GenerateMips/GenerateMipsGammaOddYCS.hlsl -# shaders/GenerateMips/GenerateMipsLinearCS.hlsl -# shaders/GenerateMips/GenerateMipsLinearOddCS.hlsl -# shaders/GenerateMips/GenerateMipsLinearOddXCS.hlsl -# shaders/GenerateMips/GenerateMipsLinearOddYCS.hlsl -#) -# -#set_source_files_properties(${SHADERS} PROPERTIES -# VS_SHADER_TYPE Compute -# VS_SHADER_ENTRYPOINT main -# VS_SHADER_MODEL 5.0 -# VS_SHADER_VARIABLE_NAME "g_p%(Filename)" -# VS_SHADER_OUTPUT_HEADER_FILE "${CMAKE_CURRENT_BINARY_DIR}/CompiledShaders/GenerateMips/%(Filename).h" -# #VS_SHADER_FLAGS "/O3" -#) +set(GENERATE_MIPS_SHADER + shaders/GenerateMipsCS.csh +) +set(GENERATE_MIPS_SHADER_INC + shaders/GenerateMipsCS_inc.h +) + +set_source_files_properties( + ${CMAKE_CURRENT_SOURCE_DIR}/${GENERATE_MIPS_SHADER_INC} + PROPERTIES GENERATED TRUE +) + +# Create custom target to convert GenerateMipsCS.csh to GenerateMipsCS_inc.h +add_custom_target(ProcessGenerateMipsVkShader +SOURCES + ${GENERATE_MIPS_SHADER} +) + +add_custom_command(TARGET ProcessGenerateMipsVkShader + # Unfortunately it is not possible to set TARGET directly to GraphicsEngineVk-* + # because PRE_BUILD is only supported on Visual Studio 8 or later. For all other generators + # PRE_BUILD is treated as PRE_LINK. + COMMAND ${FILE2STRING_PATH} ${GENERATE_MIPS_SHADER} ${GENERATE_MIPS_SHADER_INC} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Processing GenerateMipsCS.csh" + VERBATIM +) + add_library(GraphicsEngineVkInterface INTERFACE) target_include_directories(GraphicsEngineVkInterface @@ -136,29 +145,28 @@ INTERFACE ) add_library(GraphicsEngineVk-static STATIC - ${SRC} ${VULKAN_UTILS_SRC} ${INTERFACE} ${INCLUDE} ${VULKAN_UTILS_INCLUDE} ${SHADERS} + ${SRC} ${VULKAN_UTILS_SRC} ${INTERFACE} ${INCLUDE} ${VULKAN_UTILS_INCLUDE} ${GENERATE_MIPS_SHADER} ${GENERATE_MIPS_SHADER_INC} readme.md - #shaders/GenerateMips/GenerateMipsCS.hlsli ) add_library(GraphicsEngineVk-shared SHARED - ${SRC} ${VULKAN_UTILS_SRC} ${INTERFACE} ${INCLUDE} ${VULKAN_UTILS_INCLUDE} ${SHADERS} + ${SRC} ${VULKAN_UTILS_SRC} ${INTERFACE} ${INCLUDE} ${VULKAN_UTILS_INCLUDE} ${GENERATE_MIPS_SHADER} ${GENERATE_MIPS_SHADER_INC} src/DLLMain.cpp src/GraphicsEngineVk.def readme.md - #shaders/GenerateMips/GenerateMipsCS.hlsli ) +add_dependencies(GraphicsEngineVk-static ProcessGenerateMipsVkShader) +add_dependencies(GraphicsEngineVk-shared ProcessGenerateMipsVkShader) + target_include_directories(GraphicsEngineVk-static PRIVATE - #${CMAKE_CURRENT_BINARY_DIR}/CompiledShaders include ../../External/vulkan ) target_include_directories(GraphicsEngineVk-shared PRIVATE - #${CMAKE_CURRENT_BINARY_DIR}/CompiledShaders include ../../External/vulkan ) @@ -209,10 +217,12 @@ source_group("dll" FILES source_group("include" FILES ${INCLUDE}) source_group("interface" FILES ${INTERFACE}) source_group("include\\Vulkan Utilities" FILES ${VULKAN_UTILS_INCLUDE}) -#source_group("shaders" FILES -# ${SHADERS} -# shaders/GenerateMips/GenerateMipsCS.hlsli -#) +source_group("shaders" FILES + ${GENERATE_MIPS_SHADER} +) +source_group("shaders\\generated" FILES + ${GENERATE_MIPS_SHADER_INC} +) set_target_properties(GraphicsEngineVk-static PROPERTIES FOLDER Core/Graphics @@ -224,3 +234,7 @@ set_target_properties(GraphicsEngineVk-shared PROPERTIES set_source_files_properties( readme.md PROPERTIES HEADER_FILE_ONLY TRUE ) + +set_target_properties(ProcessGenerateMipsVkShader PROPERTIES + FOLDER Core/Graphics/Helper +)
\ No newline at end of file diff --git a/Graphics/GraphicsEngineVulkan/include/BufferVkImpl.h b/Graphics/GraphicsEngineVulkan/include/BufferVkImpl.h index 407eb4d7..3d4f11a0 100644 --- a/Graphics/GraphicsEngineVulkan/include/BufferVkImpl.h +++ b/Graphics/GraphicsEngineVulkan/include/BufferVkImpl.h @@ -95,8 +95,18 @@ public: return VkBuffer; } - virtual void SetAccessFlags(VkAccessFlags AccessFlags)override final{ m_AccessFlags = AccessFlags; } - bool CheckAccessFlags(VkAccessFlags Flags)const{return (m_AccessFlags & Flags) == Flags;} + virtual void SetAccessFlags(VkAccessFlags AccessFlags)override final + { + m_AccessFlags = AccessFlags; + } + bool CheckAccessFlags(VkAccessFlags Flags)const + { + return (m_AccessFlags & Flags) == Flags; + } + VkAccessFlags GetAccessFlags()const + { + return m_AccessFlags; + } private: friend class DeviceContextVkImpl; diff --git a/Graphics/GraphicsEngineVulkan/include/DeviceContextVkImpl.h b/Graphics/GraphicsEngineVulkan/include/DeviceContextVkImpl.h index 5242cce8..79cbc04a 100644 --- a/Graphics/GraphicsEngineVulkan/include/DeviceContextVkImpl.h +++ b/Graphics/GraphicsEngineVulkan/include/DeviceContextVkImpl.h @@ -29,7 +29,6 @@ #include "DeviceContextVk.h" #include "DeviceContextBase.h" -#include "GenerateMips.h" #include "VulkanUtilities/VulkanCommandBufferPool.h" #include "VulkanUtilities/VulkanCommandBuffer.h" #include "VulkanUtilities/VulkanUploadHeap.h" @@ -37,6 +36,7 @@ #include "ResourceReleaseQueue.h" #include "DescriptorPoolManager.h" #include "PipelineLayout.h" +#include "GenerateMipsVkHelper.h" #ifdef _DEBUG # define VERIFY_CONTEXT_BINDINGS @@ -51,11 +51,12 @@ class DeviceContextVkImpl : public DeviceContextBase<IDeviceContextVk> public: typedef DeviceContextBase<IDeviceContextVk> TDeviceContextBase; - DeviceContextVkImpl(IReferenceCounters* pRefCounters, - class RenderDeviceVkImpl* pDevice, - bool bIsDeferred, - const EngineVkAttribs& Attribs, - Uint32 ContextId); + DeviceContextVkImpl(IReferenceCounters* pRefCounters, + class RenderDeviceVkImpl* pDevice, + bool bIsDeferred, + const EngineVkAttribs& Attribs, + Uint32 ContextId, + std::shared_ptr<GenerateMipsVkHelper> GenerateMipsHelper); ~DeviceContextVkImpl(); virtual void QueryInterface( const Diligent::INTERFACE_ID& IID, IObject** ppInterface )override final; @@ -97,6 +98,7 @@ public: virtual void ExecuteCommandList(class ICommandList* pCommandList)override final; void TransitionImageLayout(class TextureVkImpl &TextureVk, VkImageLayout NewLayout); + void TransitionImageLayout(class TextureVkImpl &TextureVk, VkImageLayout OldLayout, VkImageLayout NewLayout, const VkImageSubresourceRange& SubresRange); virtual void TransitionImageLayout(ITexture* pTexture, VkImageLayout NewLayout)override final; void BufferMemoryBarrier(class BufferVkImpl &BufferVk, VkAccessFlags NewAccessFlags); @@ -117,9 +119,6 @@ public: ///// that are only kept alive by references in the cache //void ClearShaderStateCache(); - ///// Number of different shader types (Vertex, Pixel, Geometry, Domain, Hull, Compute) - //static constexpr int NumShaderTypes = 6; - void UpdateBufferRegion(class BufferVkImpl* pBuffVk, Uint64 DstOffset, Uint64 NumBytes, VkBuffer vkSrcBuffer, Uint64 SrcOffset); void UpdateBufferRegion(class BufferVkImpl* pBuffVk, const void* pData, Uint64 DstOffset, Uint64 NumBytes); @@ -128,7 +127,11 @@ public: #if 0 void CopyTextureRegion(IBuffer* pSrcBuffer, Uint32 SrcStride, Uint32 SrcDepthStride, class TextureVkImpl* pTextureVk, Uint32 DstSubResIndex, const Box &DstBox); #endif - void GenerateMips(class TextureViewVkImpl* pTexView); + void GenerateMips(class TextureViewVkImpl& TexView) + { + m_GenerateMipsHelper->GenerateMips(TexView, *this, *m_GenerateMipsSRB); + } + void* AllocateUploadSpace(BufferVkImpl* pBuffer, size_t NumBytes); void CopyAndFreeDynamicUploadData(BufferVkImpl* pBuffer); @@ -193,10 +196,6 @@ private: /// This framebuffer may or may not be currently set in the command buffer VkFramebuffer m_Framebuffer = VK_NULL_HANDLE; -#if 0 - GenerateMipsHelper m_MipsGenerator; -#endif - FixedBlockMemoryAllocator m_CmdListAllocator; const Uint32 m_ContextId; @@ -220,6 +219,8 @@ private: PipelineLayout::DescriptorSetBindInfo m_DesrSetBindInfo; VulkanDynamicHeap m_DynamicHeap; + std::shared_ptr<GenerateMipsVkHelper> m_GenerateMipsHelper; + RefCntAutoPtr<IShaderResourceBinding> m_GenerateMipsSRB; }; } diff --git a/Graphics/GraphicsEngineVulkan/include/GenerateMips.h b/Graphics/GraphicsEngineVulkan/include/GenerateMipsVkHelper.h index 5f10e5d1..bb7f7c6d 100644 --- a/Graphics/GraphicsEngineVulkan/include/GenerateMips.h +++ b/Graphics/GraphicsEngineVulkan/include/GenerateMipsVkHelper.h @@ -21,44 +21,44 @@ * of the possibility of such damages. */ - -// The source code in this file is derived from ColorBuffer.h and GraphicsCore.h developed by Minigraph -// Original source files header: - -// -// Copyright (c) Microsoft. All rights reserved. -// This code is licensed under the MIT License (MIT). -// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF -// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY -// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR -// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. -// -// Developed by Minigraph -// -// Author: James Stanard -// - - #pragma once -/// \file + /// \file /// Implementation of mipmap generation routines +#include <array> +#include <unordered_map> +#include "VulkanUtilities/VulkanLogicalDevice.h" +#include "VulkanUtilities/VulkanCommandBuffer.h" namespace Diligent { - class GenerateMipsHelper + class RenderDeviceVkImpl; + class TextureViewVkImpl; + class DeviceContextVkImpl; + + class GenerateMipsVkHelper { -#if 0 public: - GenerateMipsHelper(ID3D12Device *pd3d12Device); + GenerateMipsVkHelper(RenderDeviceVkImpl& DeviceVkImpl); + + GenerateMipsVkHelper (const GenerateMipsVkHelper&) = delete; + GenerateMipsVkHelper ( GenerateMipsVkHelper&&) = delete; + GenerateMipsVkHelper& operator = (const GenerateMipsVkHelper&) = delete; + GenerateMipsVkHelper& operator = ( GenerateMipsVkHelper&&) = delete; - void GenerateMips(class RenderDeviceD3D12Impl *pRenderDeviceD3D12, class TextureViewD3D12Impl *pTexView, class CommandContext &Ctx); + void GenerateMips(TextureViewVkImpl& TexView, DeviceContextVkImpl& Ctx, IShaderResourceBinding& SRB); + void CreateSRB(IShaderResourceBinding** ppSRB); private: - CComPtr<ID3D12RootSignature> m_pGenerateMipsRS; - CComPtr<ID3D12PipelineState> m_pGenerateMipsLinearPSO[4]; - CComPtr<ID3D12PipelineState> m_pGenerateMipsGammaPSO[4]; -#endif + std::array<RefCntAutoPtr<IPipelineState>, 4> CreatePSOs(TEXTURE_FORMAT Fmt); + std::array<RefCntAutoPtr<IPipelineState>, 4>& FindPSOs (TEXTURE_FORMAT Fmt); + + RenderDeviceVkImpl& m_DeviceVkImpl; + + std::mutex m_PSOMutex; + std::unordered_map< TEXTURE_FORMAT, std::array<RefCntAutoPtr<IPipelineState>, 4> > m_PSOHash; + static void GetGlImageFormat(const TextureFormatAttribs& FmtAttribs, std::array<char, 16>& GlFmt); + RefCntAutoPtr<IBuffer> m_ConstantsCB; }; } diff --git a/Graphics/GraphicsEngineVulkan/include/PipelineStateVkImpl.h b/Graphics/GraphicsEngineVulkan/include/PipelineStateVkImpl.h index ab78374b..fa50a025 100644 --- a/Graphics/GraphicsEngineVulkan/include/PipelineStateVkImpl.h +++ b/Graphics/GraphicsEngineVulkan/include/PipelineStateVkImpl.h @@ -66,7 +66,7 @@ public: void CommitAndTransitionShaderResources(IShaderResourceBinding* pShaderResourceBinding, DeviceContextVkImpl* pCtxVkImpl, bool CommitResources, - bool TransitionResources, + Uint32 Flags, PipelineLayout::DescriptorSetBindInfo* pDescrSetBindInfo)const; void BindDescriptorSetsWithDynamicOffsets(DeviceContextVkImpl* pCtxVkImpl, diff --git a/Graphics/GraphicsEngineVulkan/include/ShaderVkImpl.h b/Graphics/GraphicsEngineVulkan/include/ShaderVkImpl.h index 3daa9d20..6764f301 100644 --- a/Graphics/GraphicsEngineVulkan/include/ShaderVkImpl.h +++ b/Graphics/GraphicsEngineVulkan/include/ShaderVkImpl.h @@ -33,7 +33,7 @@ #include "SPIRVShaderResources.h" #include "ShaderVariableVk.h" -#ifdef _DEBUG +#ifdef DEVELOPMENT # define VERIFY_SHADER_BINDINGS #endif diff --git a/Graphics/GraphicsEngineVulkan/include/TextureVkImpl.h b/Graphics/GraphicsEngineVulkan/include/TextureVkImpl.h index f2f186c6..20223b3e 100644 --- a/Graphics/GraphicsEngineVulkan/include/TextureVkImpl.h +++ b/Graphics/GraphicsEngineVulkan/include/TextureVkImpl.h @@ -83,17 +83,16 @@ public: Uint32 DstX, Uint32 DstY, Uint32 DstZ); -/* - Vk_CPU_DESCRIPTOR_HANDLE GetMipLevelUAV(Uint32 Mip) + + ITextureView* GetMipLevelSRV(Uint32 MipLevel) { - return m_MipUAVs.GetCpuHandle(Mip); + return m_MipLevelSRV[MipLevel].get(); } - Vk_CPU_DESCRIPTOR_HANDLE GetTexArraySRV() + ITextureView* GetMipLevelUAV(Uint32 MipLevel) { - return m_TexArraySRV.GetCpuHandle(); + return m_MipLevelUAV[MipLevel].get(); } -*/ void SetLayout(VkImageLayout NewLayout){ m_CurrentLayout = NewLayout;} VkImageLayout GetLayout()const{return m_CurrentLayout;} @@ -105,18 +104,13 @@ protected: VulkanUtilities::ImageViewWrapper CreateImageView(TextureViewDesc &ViewDesc); -/* - // UAVs for every mip level to facilitate mipmap generation - DescriptorHeapAllocation m_MipUAVs; - // SRV as texture array (even for a non-array texture) required for mipmap generation - DescriptorHeapAllocation m_TexArraySRV; - - friend class RenderDeviceVkImpl; -*/ - VulkanUtilities::ImageWrapper m_VulkanImage; VulkanUtilities::VulkanMemoryAllocation m_MemoryAllocation; VkImageLayout m_CurrentLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + // Texture views needed for mipmap generation + std::vector<std::unique_ptr<TextureViewVkImpl, STDDeleter<TextureViewVkImpl, FixedBlockMemoryAllocator> > > m_MipLevelSRV; + std::vector<std::unique_ptr<TextureViewVkImpl, STDDeleter<TextureViewVkImpl, FixedBlockMemoryAllocator> > > m_MipLevelUAV; }; } diff --git a/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/VulkanDebug.h b/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/VulkanDebug.h index 50b1a0a8..f5f2bc78 100644 --- a/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/VulkanDebug.h +++ b/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/VulkanDebug.h @@ -1,5 +1,6 @@ #pragma once +#include <string> #include "vulkan.h" namespace VulkanUtilities @@ -103,5 +104,8 @@ namespace VulkanUtilities void SetVulkanObjectName(VkDevice device, VkFence fence, const char * name); void SetVulkanObjectName(VkDevice device, VkEvent _event, const char * name); - const char* VkResultToString(VkResult errorCode); + const char* VkResultToString (VkResult errorCode); + const char* VkAccessFlagBitToString(VkAccessFlagBits Bit); + const char* VkImageLayoutToString (VkImageLayout Layout); + std::string VkAccessFlagsToString (VkAccessFlags Flags); } diff --git a/Graphics/GraphicsEngineVulkan/shaders/GenerateMipsCS.csh b/Graphics/GraphicsEngineVulkan/shaders/GenerateMipsCS.csh new file mode 100644 index 00000000..b85e0bf2 --- /dev/null +++ b/Graphics/GraphicsEngineVulkan/shaders/GenerateMipsCS.csh @@ -0,0 +1,211 @@ +#ifndef NON_POWER_OF_TWO +#define NON_POWER_OF_TWO 0 +#endif + +#ifndef CONVERT_TO_SRGB +#define CONVERT_TO_SRGB 0 +#endif + +#ifndef IMG_FORMAT +#define IMG_FORMAT rgba8 +#endif + +layout(IMG_FORMAT) uniform writeonly image2DArray OutMip[4]; + +uniform sampler2DArray SrcMip; + +uniform CB +{ + int SrcMipLevel; // Texture level of source mip + int NumMipLevels; // Number of OutMips to write: [1, 4] + int FirstArraySlice; + int Dummy; + vec2 TexelSize; // 1.0 / OutMip1.Dimensions +}; + +// +// The reason for separating channels is to reduce bank conflicts in the +// local data memory controller. A large stride will cause more threads +// to collide on the same memory bank. +shared float gs_R[64]; +shared float gs_G[64]; +shared float gs_B[64]; +shared float gs_A[64]; + +void StoreColor( uint Index, vec4 Color ) +{ + gs_R[Index] = Color.r; + gs_G[Index] = Color.g; + gs_B[Index] = Color.b; + gs_A[Index] = Color.a; +} + +vec4 LoadColor( uint Index ) +{ + return vec4( gs_R[Index], gs_G[Index], gs_B[Index], gs_A[Index]); +} + +float LinearToSRGB(float x) +{ + // This is exactly the sRGB curve + //return x < 0.0031308 ? 12.92 * x : 1.055 * pow(abs(x), 1.0 / 2.4) - 0.055; + + // This is cheaper but nearly equivalent + return x < 0.0031308 ? 12.92 * x : 1.13005 * sqrt(abs(x - 0.00228)) - 0.13448 * x + 0.005719; +} + +vec4 PackColor(vec4 Linear) +{ +#if CONVERT_TO_SRGB + return vec4(LinearToSRGB(Linear.r), LinearToSRGB(Linear.g), LinearToSRGB(Linear.b), Linear.a); +#else + return Linear; +#endif +} + +void GroupMemoryBarrierWithGroupSync() +{ + // OpenGL.org: groupMemoryBarrier() waits on the completion of all memory accesses + // performed by an invocation of a compute shader relative to the same access performed + // by other invocations in the same work group and then returns with no other effect. + + // groupMemoryBarrier() acts like memoryBarrier(), ordering memory writes for all kinds + // of variables, but it only orders read/writes for the current work group. + groupMemoryBarrier(); + + // OpenGL.org: memoryBarrierShared() waits on the completion of + // all memory accesses resulting from the use of SHARED variables + // and then returns with no other effect. + memoryBarrierShared(); + + // Thread execution barrier + barrier(); +} + +layout (local_size_x = 8, local_size_y = 8, local_size_z = 1) in; +void main() +{ + uint LocalInd = gl_LocalInvocationIndex; + uvec3 GlobalInd = gl_GlobalInvocationID; + + ivec3 SrcMipSize = textureSize(SrcMip, 0); // SrcMip is the view of the source mip level + bool IsValidThread = GlobalInd.x < SrcMipSize.x && GlobalInd.y < SrcMipSize.y; + int ArraySlice = FirstArraySlice + int(GlobalInd.z); + + vec4 Src1 = vec4(0.0, 0.0, 0.0, 0.0); + float fSrcMipLevel = 0.0; // SrcMip is the view of the source mip level + if( IsValidThread ) + { + // One bilinear sample is insufficient when scaling down by more than 2x. + // You will slightly undersample in the case where the source dimension + // is odd. This is why it's a really good idea to only generate mips on + // power-of-two sized textures. Trying to handle the undersampling case + // will force this shader to be slower and more complicated as it will + // have to take more source texture samples. +#if NON_POWER_OF_TWO == 0 + vec2 UV = TexelSize * (vec2(GlobalInd.xy) + 0.5); + Src1 = textureLod(SrcMip, vec3(UV, ArraySlice), fSrcMipLevel); +#elif NON_POWER_OF_TWO == 1 + // > 2:1 in X dimension + // Use 2 bilinear samples to guarantee we don't undersample when downsizing by more than 2x + // horizontally. + vec2 UV1 = TexelSize * (vec2(GlobalInd.xy) + vec2(0.25, 0.5)); + vec2 Off = TexelSize * vec2(0.5, 0.0); + Src1 = 0.5 * (textureLod(SrcMip, vec3(UV1, ArraySlice), fSrcMipLevel) + + textureLod(SrcMip, vec3(UV1 + Off, ArraySlice), fSrcMipLevel)); +#elif NON_POWER_OF_TWO == 2 + // > 2:1 in Y dimension + // Use 2 bilinear samples to guarantee we don't undersample when downsizing by more than 2x + // vertically. + vec2 UV1 = TexelSize * (vec2(GlobalInd.xy) + vec2(0.5, 0.25)); + vec2 Off = TexelSize * vec2(0.0, 0.5); + Src1 = 0.5 * (textureLod(SrcMip, vec3(UV1, ArraySlice), fSrcMipLevel) + + textureLod(SrcMip, vec3(UV1 + Off, ArraySlice), fSrcMipLevel)); +#elif NON_POWER_OF_TWO == 3 + // > 2:1 in in both dimensions + // Use 4 bilinear samples to guarantee we don't undersample when downsizing by more than 2x + // in both directions. + vec2 UV1 = TexelSize * (vec2(GlobalInd.xy) + vec2(0.25, 0.25)); + vec2 Off = TexelSize * 0.5; + Src1 += textureLod(SrcMip, vec3(UV1, ArraySlice), fSrcMipLevel); + Src1 += textureLod(SrcMip, vec3(UV1 + vec2(Off.x, 0.0), ArraySlice), fSrcMipLevel); + Src1 += textureLod(SrcMip, vec3(UV1 + vec2(0.0, Off.y), ArraySlice), fSrcMipLevel); + Src1 += textureLod(SrcMip, vec3(UV1 + vec2(Off.x, Off.y), ArraySlice), fSrcMipLevel); + Src1 *= 0.25; +#endif + + imageStore(OutMip[0], ivec3(GlobalInd.xy, ArraySlice), PackColor(Src1)); + } + + // A scalar (constant) branch can exit all threads coherently. + if (NumMipLevels == 1) + return; + + if( IsValidThread ) + { + // Without lane swizzle operations, the only way to share data with other + // threads is through LDS. + StoreColor(LocalInd, Src1); + } + + // This guarantees all LDS writes are complete and that all threads have + // executed all instructions so far (and therefore have issued their LDS + // write instructions.) + GroupMemoryBarrierWithGroupSync(); + + if( IsValidThread ) + { + // With low three bits for X and high three bits for Y, this bit mask + // (binary: 001001) checks that X and Y are even. + if ((LocalInd & 0x9) == 0) + { + vec4 Src2 = LoadColor(LocalInd + 0x01); + vec4 Src3 = LoadColor(LocalInd + 0x08); + vec4 Src4 = LoadColor(LocalInd + 0x09); + Src1 = 0.25 * (Src1 + Src2 + Src3 + Src4); + + imageStore(OutMip[1], ivec3(GlobalInd.xy / 2, ArraySlice), PackColor(Src1)); + StoreColor(LocalInd, Src1); + } + } + + if (NumMipLevels == 2) + return; + + GroupMemoryBarrierWithGroupSync(); + + if( IsValidThread ) + { + // This bit mask (binary: 011011) checks that X and Y are multiples of four. + if ((LocalInd & 0x1B) == 0) + { + vec4 Src2 = LoadColor(LocalInd + 0x02); + vec4 Src3 = LoadColor(LocalInd + 0x10); + vec4 Src4 = LoadColor(LocalInd + 0x12); + Src1 = 0.25 * (Src1 + Src2 + Src3 + Src4); + + imageStore(OutMip[2], ivec3(GlobalInd.xy / 4, ArraySlice), PackColor(Src1)); + StoreColor(LocalInd, Src1); + } + } + + if (NumMipLevels == 3) + return; + + GroupMemoryBarrierWithGroupSync(); + + if( IsValidThread ) + { + // This bit mask would be 111111 (X & Y multiples of 8), but only one + // thread fits that criteria. + if (LocalInd == 0) + { + vec4 Src2 = LoadColor(LocalInd + 0x04); + vec4 Src3 = LoadColor(LocalInd + 0x20); + vec4 Src4 = LoadColor(LocalInd + 0x24); + Src1 = 0.25 * (Src1 + Src2 + Src3 + Src4); + + imageStore(OutMip[3], ivec3(GlobalInd.xy / 8, ArraySlice), PackColor(Src1)); + } + } +} diff --git a/Graphics/GraphicsEngineVulkan/shaders/GenerateMipsCS_inc.h b/Graphics/GraphicsEngineVulkan/shaders/GenerateMipsCS_inc.h new file mode 100644 index 00000000..8d02d782 --- /dev/null +++ b/Graphics/GraphicsEngineVulkan/shaders/GenerateMipsCS_inc.h @@ -0,0 +1,211 @@ +"#ifndef NON_POWER_OF_TWO\n" +"#define NON_POWER_OF_TWO 0\n" +"#endif\n" +"\n" +"#ifndef CONVERT_TO_SRGB\n" +"#define CONVERT_TO_SRGB 0\n" +"#endif\n" +"\n" +"#ifndef IMG_FORMAT\n" +"#define IMG_FORMAT rgba8\n" +"#endif\n" +"\n" +"layout(IMG_FORMAT) uniform writeonly image2DArray OutMip[4];\n" +"\n" +"uniform sampler2DArray SrcMip;\n" +"\n" +"uniform CB\n" +"{\n" +" int SrcMipLevel; // Texture level of source mip\n" +" int NumMipLevels; // Number of OutMips to write: [1, 4]\n" +" int FirstArraySlice;\n" +" int Dummy;\n" +" vec2 TexelSize; // 1.0 / OutMip1.Dimensions\n" +"};\n" +"\n" +"//\n" +"// The reason for separating channels is to reduce bank conflicts in the\n" +"// local data memory controller. A large stride will cause more threads\n" +"// to collide on the same memory bank.\n" +"shared float gs_R[64];\n" +"shared float gs_G[64];\n" +"shared float gs_B[64];\n" +"shared float gs_A[64];\n" +"\n" +"void StoreColor( uint Index, vec4 Color )\n" +"{\n" +" gs_R[Index] = Color.r;\n" +" gs_G[Index] = Color.g;\n" +" gs_B[Index] = Color.b;\n" +" gs_A[Index] = Color.a;\n" +"}\n" +"\n" +"vec4 LoadColor( uint Index )\n" +"{\n" +" return vec4( gs_R[Index], gs_G[Index], gs_B[Index], gs_A[Index]);\n" +"}\n" +"\n" +"float LinearToSRGB(float x)\n" +"{\n" +" // This is exactly the sRGB curve\n" +" //return x < 0.0031308 ? 12.92 * x : 1.055 * pow(abs(x), 1.0 / 2.4) - 0.055;\n" +" \n" +" // This is cheaper but nearly equivalent\n" +" return x < 0.0031308 ? 12.92 * x : 1.13005 * sqrt(abs(x - 0.00228)) - 0.13448 * x + 0.005719;\n" +"}\n" +"\n" +"vec4 PackColor(vec4 Linear)\n" +"{\n" +"#if CONVERT_TO_SRGB\n" +" return vec4(LinearToSRGB(Linear.r), LinearToSRGB(Linear.g), LinearToSRGB(Linear.b), Linear.a);\n" +"#else\n" +" return Linear;\n" +"#endif\n" +"}\n" +"\n" +"void GroupMemoryBarrierWithGroupSync()\n" +"{\n" +" // OpenGL.org: groupMemoryBarrier() waits on the completion of all memory accesses \n" +" // performed by an invocation of a compute shader relative to the same access performed \n" +" // by other invocations in the same work group and then returns with no other effect.\n" +"\n" +" // groupMemoryBarrier() acts like memoryBarrier(), ordering memory writes for all kinds \n" +" // of variables, but it only orders read/writes for the current work group.\n" +" groupMemoryBarrier();\n" +"\n" +" // OpenGL.org: memoryBarrierShared() waits on the completion of \n" +" // all memory accesses resulting from the use of SHARED variables\n" +" // and then returns with no other effect. \n" +" memoryBarrierShared();\n" +"\n" +" // Thread execution barrier\n" +" barrier();\n" +"}\n" +"\n" +"layout (local_size_x = 8, local_size_y = 8, local_size_z = 1) in;\n" +"void main()\n" +"{\n" +" uint LocalInd = gl_LocalInvocationIndex;\n" +" uvec3 GlobalInd = gl_GlobalInvocationID;\n" +" \n" +" ivec3 SrcMipSize = textureSize(SrcMip, 0); // SrcMip is the view of the source mip level\n" +" bool IsValidThread = GlobalInd.x < SrcMipSize.x && GlobalInd.y < SrcMipSize.y;\n" +" int ArraySlice = FirstArraySlice + int(GlobalInd.z);\n" +"\n" +" vec4 Src1 = vec4(0.0, 0.0, 0.0, 0.0);\n" +" float fSrcMipLevel = 0.0; // SrcMip is the view of the source mip level\n" +" if( IsValidThread )\n" +" {\n" +" // One bilinear sample is insufficient when scaling down by more than 2x.\n" +" // You will slightly undersample in the case where the source dimension\n" +" // is odd. This is why it\'s a really good idea to only generate mips on\n" +" // power-of-two sized textures. Trying to handle the undersampling case\n" +" // will force this shader to be slower and more complicated as it will\n" +" // have to take more source texture samples.\n" +"#if NON_POWER_OF_TWO == 0\n" +" vec2 UV = TexelSize * (vec2(GlobalInd.xy) + 0.5);\n" +" Src1 = textureLod(SrcMip, vec3(UV, ArraySlice), fSrcMipLevel);\n" +"#elif NON_POWER_OF_TWO == 1\n" +" // > 2:1 in X dimension\n" +" // Use 2 bilinear samples to guarantee we don\'t undersample when downsizing by more than 2x\n" +" // horizontally.\n" +" vec2 UV1 = TexelSize * (vec2(GlobalInd.xy) + vec2(0.25, 0.5));\n" +" vec2 Off = TexelSize * vec2(0.5, 0.0);\n" +" Src1 = 0.5 * (textureLod(SrcMip, vec3(UV1, ArraySlice), fSrcMipLevel) +\n" +" textureLod(SrcMip, vec3(UV1 + Off, ArraySlice), fSrcMipLevel));\n" +"#elif NON_POWER_OF_TWO == 2\n" +" // > 2:1 in Y dimension\n" +" // Use 2 bilinear samples to guarantee we don\'t undersample when downsizing by more than 2x\n" +" // vertically.\n" +" vec2 UV1 = TexelSize * (vec2(GlobalInd.xy) + vec2(0.5, 0.25));\n" +" vec2 Off = TexelSize * vec2(0.0, 0.5);\n" +" Src1 = 0.5 * (textureLod(SrcMip, vec3(UV1, ArraySlice), fSrcMipLevel) +\n" +" textureLod(SrcMip, vec3(UV1 + Off, ArraySlice), fSrcMipLevel));\n" +"#elif NON_POWER_OF_TWO == 3\n" +" // > 2:1 in in both dimensions\n" +" // Use 4 bilinear samples to guarantee we don\'t undersample when downsizing by more than 2x\n" +" // in both directions.\n" +" vec2 UV1 = TexelSize * (vec2(GlobalInd.xy) + vec2(0.25, 0.25));\n" +" vec2 Off = TexelSize * 0.5;\n" +" Src1 += textureLod(SrcMip, vec3(UV1, ArraySlice), fSrcMipLevel);\n" +" Src1 += textureLod(SrcMip, vec3(UV1 + vec2(Off.x, 0.0), ArraySlice), fSrcMipLevel);\n" +" Src1 += textureLod(SrcMip, vec3(UV1 + vec2(0.0, Off.y), ArraySlice), fSrcMipLevel);\n" +" Src1 += textureLod(SrcMip, vec3(UV1 + vec2(Off.x, Off.y), ArraySlice), fSrcMipLevel);\n" +" Src1 *= 0.25;\n" +"#endif\n" +"\n" +" imageStore(OutMip[0], ivec3(GlobalInd.xy, ArraySlice), PackColor(Src1));\n" +" }\n" +"\n" +" // A scalar (constant) branch can exit all threads coherently.\n" +" if (NumMipLevels == 1)\n" +" return;\n" +"\n" +" if( IsValidThread )\n" +" {\n" +" // Without lane swizzle operations, the only way to share data with other\n" +" // threads is through LDS.\n" +" StoreColor(LocalInd, Src1);\n" +" }\n" +"\n" +" // This guarantees all LDS writes are complete and that all threads have\n" +" // executed all instructions so far (and therefore have issued their LDS\n" +" // write instructions.)\n" +" GroupMemoryBarrierWithGroupSync();\n" +"\n" +" if( IsValidThread )\n" +" {\n" +" // With low three bits for X and high three bits for Y, this bit mask\n" +" // (binary: 001001) checks that X and Y are even.\n" +" if ((LocalInd & 0x9) == 0)\n" +" {\n" +" vec4 Src2 = LoadColor(LocalInd + 0x01);\n" +" vec4 Src3 = LoadColor(LocalInd + 0x08);\n" +" vec4 Src4 = LoadColor(LocalInd + 0x09);\n" +" Src1 = 0.25 * (Src1 + Src2 + Src3 + Src4);\n" +"\n" +" imageStore(OutMip[1], ivec3(GlobalInd.xy / 2, ArraySlice), PackColor(Src1));\n" +" StoreColor(LocalInd, Src1);\n" +" }\n" +" }\n" +"\n" +" if (NumMipLevels == 2)\n" +" return;\n" +"\n" +" GroupMemoryBarrierWithGroupSync();\n" +"\n" +" if( IsValidThread )\n" +" {\n" +" // This bit mask (binary: 011011) checks that X and Y are multiples of four.\n" +" if ((LocalInd & 0x1B) == 0)\n" +" {\n" +" vec4 Src2 = LoadColor(LocalInd + 0x02);\n" +" vec4 Src3 = LoadColor(LocalInd + 0x10);\n" +" vec4 Src4 = LoadColor(LocalInd + 0x12);\n" +" Src1 = 0.25 * (Src1 + Src2 + Src3 + Src4);\n" +"\n" +" imageStore(OutMip[2], ivec3(GlobalInd.xy / 4, ArraySlice), PackColor(Src1));\n" +" StoreColor(LocalInd, Src1);\n" +" }\n" +" }\n" +"\n" +" if (NumMipLevels == 3)\n" +" return;\n" +"\n" +" GroupMemoryBarrierWithGroupSync();\n" +"\n" +" if( IsValidThread )\n" +" {\n" +" // This bit mask would be 111111 (X & Y multiples of 8), but only one\n" +" // thread fits that criteria.\n" +" if (LocalInd == 0)\n" +" {\n" +" vec4 Src2 = LoadColor(LocalInd + 0x04);\n" +" vec4 Src3 = LoadColor(LocalInd + 0x20);\n" +" vec4 Src4 = LoadColor(LocalInd + 0x24);\n" +" Src1 = 0.25 * (Src1 + Src2 + Src3 + Src4);\n" +"\n" +" imageStore(OutMip[3], ivec3(GlobalInd.xy / 8, ArraySlice), PackColor(Src1));\n" +" }\n" +" }\n" +"}\n" diff --git a/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp index a538ab1b..fda3cf55 100644 --- a/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp @@ -59,14 +59,14 @@ namespace Diligent return "Dynamic heap of immediate context"; } - DeviceContextVkImpl::DeviceContextVkImpl( IReferenceCounters* pRefCounters, - RenderDeviceVkImpl* pDeviceVkImpl, - bool bIsDeferred, - const EngineVkAttribs& Attribs, - Uint32 ContextId) : + DeviceContextVkImpl::DeviceContextVkImpl( IReferenceCounters* pRefCounters, + RenderDeviceVkImpl* pDeviceVkImpl, + bool bIsDeferred, + const EngineVkAttribs& Attribs, + Uint32 ContextId, + std::shared_ptr<GenerateMipsVkHelper> GenerateMipsHelper) : TDeviceContextBase{pRefCounters, pDeviceVkImpl, bIsDeferred}, m_NumCommandsToFlush{bIsDeferred ? std::numeric_limits<decltype(m_NumCommandsToFlush)>::max() : Attribs.NumCommandsToFlushCmdBuffer}, - /*m_MipsGenerator(pDeviceVkImpl->GetVkDevice()),*/ m_CmdListAllocator{ GetRawAllocator(), sizeof(CommandListVkImpl), 64 }, m_ContextId{ContextId}, // Command pools for deferred contexts must be thread safe because finished command buffers are executed and released from another thread @@ -113,8 +113,10 @@ namespace Diligent pDeviceVkImpl->GetDynamicHeapRingBuffer(), GetDynamicHeapName(bIsDeferred, ContextId), bIsDeferred ? Attribs.DeferredCtxDynamicHeapPageSize : Attribs.ImmediateCtxDynamicHeapPageSize - } + }, + m_GenerateMipsHelper(std::move(GenerateMipsHelper)) { + m_GenerateMipsHelper->CreateSRB(&m_GenerateMipsSRB); } DeviceContextVkImpl::~DeviceContextVkImpl() @@ -244,7 +246,7 @@ namespace Diligent VERIFY_EXPR(pPipelineState != nullptr); auto *pPipelineStateVk = ValidatedCast<PipelineStateVkImpl>(pPipelineState); - pPipelineStateVk->CommitAndTransitionShaderResources(pShaderResourceBinding, this, false, true, nullptr); + pPipelineStateVk->CommitAndTransitionShaderResources(pShaderResourceBinding, this, false, COMMIT_SHADER_RESOURCES_FLAG_TRANSITION_RESOURCES, nullptr); } void DeviceContextVkImpl::CommitShaderResources(IShaderResourceBinding *pShaderResourceBinding, Uint32 Flags) @@ -253,7 +255,7 @@ namespace Diligent return; auto *pPipelineStateVk = m_pPipelineState.RawPtr<PipelineStateVkImpl>(); - pPipelineStateVk->CommitAndTransitionShaderResources(pShaderResourceBinding, this, true, (Flags & COMMIT_SHADER_RESOURCES_FLAG_TRANSITION_RESOURCES) != 0, &m_DesrSetBindInfo); + pPipelineStateVk->CommitAndTransitionShaderResources(pShaderResourceBinding, this, true, Flags, &m_DesrSetBindInfo); } void DeviceContextVkImpl::SetStencilRef(Uint32 StencilRef) @@ -1150,16 +1152,6 @@ namespace Diligent } #endif - void DeviceContextVkImpl::GenerateMips(TextureViewVkImpl *pTexView) - { - UNSUPPORTED("Not yet implemented"); -#if 0 - auto *pCtx = RequestCmdContext(); - m_MipsGenerator.GenerateMips(m_pDevice.RawPtr<RenderDeviceVkImpl>(), pTexView, *pCtx); - ++m_State.NumCommands; -#endif - } - void DeviceContextVkImpl::FinishCommandList(class ICommandList **ppCommandList) { if (m_CommandBuffer.GetState().RenderPass != VK_NULL_HANDLE) @@ -1255,15 +1247,14 @@ namespace Diligent } } - void DeviceContextVkImpl::TransitionImageLayout(TextureVkImpl &TextureVk, VkImageLayout NewLayout) + void DeviceContextVkImpl::TransitionImageLayout(TextureVkImpl& TextureVk, VkImageLayout NewLayout) { VERIFY(TextureVk.GetLayout() != NewLayout, "The texture is already transitioned to correct layout"); EnsureVkCmdBuffer(); auto vkImg = TextureVk.GetVkImage(); const auto& TexDesc = TextureVk.GetDesc(); - auto Fmt = TexDesc.Format; - const auto& FmtAttribs = GetTextureFormatAttribs(Fmt); + const auto& FmtAttribs = GetTextureFormatAttribs(TexDesc.Format); VkImageSubresourceRange SubresRange; if (FmtAttribs.ComponentType == COMPONENT_TYPE_DEPTH) SubresRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; @@ -1284,6 +1275,14 @@ namespace Diligent TextureVk.SetLayout(NewLayout); } + void DeviceContextVkImpl::TransitionImageLayout(TextureVkImpl &TextureVk, VkImageLayout OldLayout, VkImageLayout NewLayout, const VkImageSubresourceRange& SubresRange) + { + VERIFY(TextureVk.GetLayout() != NewLayout, "The texture is already transitioned to correct layout"); + EnsureVkCmdBuffer(); + auto vkImg = TextureVk.GetVkImage(); + m_CommandBuffer.TransitionImageLayout(vkImg, OldLayout, NewLayout, SubresRange); + } + void DeviceContextVkImpl::BufferMemoryBarrier(IBuffer *pBuffer, VkAccessFlags NewAccessFlags) { VERIFY_EXPR(pBuffer != nullptr); diff --git a/Graphics/GraphicsEngineVulkan/src/GenerateMips.cpp b/Graphics/GraphicsEngineVulkan/src/GenerateMips.cpp deleted file mode 100644 index fc9facf1..00000000 --- a/Graphics/GraphicsEngineVulkan/src/GenerateMips.cpp +++ /dev/null @@ -1,196 +0,0 @@ -/* Copyright 2015-2018 Egor Yusov - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF ANY PROPRIETARY RIGHTS. - * - * In no event and under no legal theory, whether in tort (including negligence), - * contract, or otherwise, unless required by applicable law (such as deliberate - * and grossly negligent acts) or agreed to in writing, shall any Contributor be - * liable for any damages, including any direct, indirect, special, incidental, - * or consequential damages of any character arising as a result of this License or - * out of the use or inability to use the software (including but not limited to damages - * for loss of goodwill, work stoppage, computer failure or malfunction, or any and - * all other commercial damages or losses), even if such Contributor has been advised - * of the possibility of such damages. - */ - -// The source code in this file is derived from ColorBuffer.cpp and GraphicsCore.cpp developed by Minigraph -// Original source files header: - -// -// Copyright (c) Microsoft. All rights reserved. -// This code is licensed under the MIT License (MIT). -// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF -// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY -// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR -// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. -// -// Developed by Minigraph -// -// Author: James Stanard -// - - -#include "pch.h" - -/* -#include "RenderDeviceVkImpl.h" -#include "GenerateMips.h" -#include "CommandContext.h" -#include "TextureViewVkImpl.h" -#include "TextureVkImpl.h" - -#include "GenerateMips/GenerateMipsLinearCS.h" -#include "GenerateMips/GenerateMipsLinearOddCS.h" -#include "GenerateMips/GenerateMipsLinearOddXCS.h" -#include "GenerateMips/GenerateMipsLinearOddYCS.h" -#include "GenerateMips/GenerateMipsGammaCS.h" -#include "GenerateMips/GenerateMipsGammaOddCS.h" -#include "GenerateMips/GenerateMipsGammaOddXCS.h" -#include "GenerateMips/GenerateMipsGammaOddYCS.h" - -namespace Diligent -{ - GenerateMipsHelper::GenerateMipsHelper(IVkDevice *pVkDevice) - { - CD3DX12_ROOT_PARAMETER Params[3]; - Params[0].InitAsConstants(6, 0); - CD3DX12_DESCRIPTOR_RANGE SRVRange(Vk_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0); - Params[1].InitAsDescriptorTable(1, &SRVRange); - CD3DX12_DESCRIPTOR_RANGE UAVRange(Vk_DESCRIPTOR_RANGE_TYPE_UAV, 4, 0); - Params[2].InitAsDescriptorTable(1, &UAVRange); - CD3DX12_STATIC_SAMPLER_DESC SamplerLinearClampDesc( - 0, Vk_FILTER_MIN_MAG_MIP_LINEAR, Vk_TEXTURE_ADDRESS_MODE_CLAMP, Vk_TEXTURE_ADDRESS_MODE_CLAMP, Vk_TEXTURE_ADDRESS_MODE_CLAMP); - CD3DX12_ROOT_SIGNATURE_DESC RootSigDesc; - RootSigDesc.NumParameters = _countof(Params); - RootSigDesc.pParameters = Params; - RootSigDesc.NumStaticSamplers = 1; - RootSigDesc.pStaticSamplers = &SamplerLinearClampDesc; - RootSigDesc.Flags = Vk_ROOT_SIGNATURE_FLAG_NONE; - - CComPtr<ID3DBlob> signature; - CComPtr<ID3DBlob> error; - HRESULT hr = VkSerializeRootSignature(&RootSigDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error); - hr = pVkDevice->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), __uuidof(m_pGenerateMipsRS), reinterpret_cast<void**>( static_cast<IVkRootSignature**>(&m_pGenerateMipsRS))); - CHECK_D3D_RESULT_THROW(hr, "Failed to create root signature for mipmap generation") - - Vk_COMPUTE_PIPELINE_STATE_DESC PSODesc = {}; - PSODesc.pRootSignature = m_pGenerateMipsRS; - PSODesc.NodeMask = 0; - PSODesc.Flags = Vk_PIPELINE_STATE_FLAG_NONE; - -#define CreatePSO(PSO, ShaderByteCode) \ - PSODesc.CS.pShaderBytecode = ShaderByteCode;\ - PSODesc.CS.BytecodeLength = sizeof(ShaderByteCode);\ - hr = pVkDevice->CreateComputePipelineState(&PSODesc, __uuidof(PSO), reinterpret_cast<void**>( static_cast<IVkPipelineState**>(&PSO))); \ - CHECK_D3D_RESULT_THROW(hr, "Failed to create Pipeline state for mipmap generation") \ - PSO->SetName(L"Generate mips PSO"); - - CreatePSO(m_pGenerateMipsLinearPSO[0], g_pGenerateMipsLinearCS); - CreatePSO(m_pGenerateMipsLinearPSO[1], g_pGenerateMipsLinearOddXCS); - CreatePSO(m_pGenerateMipsLinearPSO[2], g_pGenerateMipsLinearOddYCS); - CreatePSO(m_pGenerateMipsLinearPSO[3], g_pGenerateMipsLinearOddCS); - CreatePSO(m_pGenerateMipsGammaPSO[0], g_pGenerateMipsGammaCS); - CreatePSO(m_pGenerateMipsGammaPSO[1], g_pGenerateMipsGammaOddXCS); - CreatePSO(m_pGenerateMipsGammaPSO[2], g_pGenerateMipsGammaOddYCS); - CreatePSO(m_pGenerateMipsGammaPSO[3], g_pGenerateMipsGammaOddCS); - } - - void GenerateMipsHelper::GenerateMips(RenderDeviceVkImpl *pRenderDeviceVk, TextureViewVkImpl *pTexView, CommandContext& Ctx) - { - auto &ComputeCtx = Ctx.AsComputeContext(); - ComputeCtx.SetRootSignature(m_pGenerateMipsRS); - auto *pTexture = pTexView->GetTexture(); - auto *pTexVk = ValidatedCast<TextureVkImpl>( pTexture ); - auto &TexDesc = pTexture->GetDesc(); - auto SRVDescriptorHandle = pTexVk->GetTexArraySRV(); - - Ctx.TransitionResource(pTexVk, Vk_RESOURCE_STATE_UNORDERED_ACCESS); - auto *pVkDevice = pRenderDeviceVk->GetVkDevice(); - - const auto &ViewDesc = pTexView->GetDesc(); - for (Uint32 ArrSlice = ViewDesc.FirstArraySlice; ArrSlice < ViewDesc.FirstArraySlice + ViewDesc.NumArraySlices; ++ArrSlice) - { - for (uint32_t TopMip = 0; TopMip < TexDesc.MipLevels - 1; ) - { - uint32_t SrcWidth = TexDesc.Width >> TopMip; - uint32_t SrcHeight = TexDesc.Height >> TopMip; - uint32_t DstWidth = SrcWidth >> 1; - uint32_t DstHeight = SrcHeight >> 1; - - // Determine if the first downsample is more than 2:1. This happens whenever - // the source width or height is odd. - uint32_t NonPowerOfTwo = (SrcWidth & 1) | (SrcHeight & 1) << 1; - if (TexDesc.Format == TEX_FORMAT_RGBA8_UNORM_SRGB) - ComputeCtx.SetPipelineState(m_pGenerateMipsGammaPSO[NonPowerOfTwo]); - else - ComputeCtx.SetPipelineState(m_pGenerateMipsLinearPSO[NonPowerOfTwo]); - - // We can downsample up to four times, but if the ratio between levels is not - // exactly 2:1, we have to shift our blend weights, which gets complicated or - // expensive. Maybe we can update the code later to compute sample weights for - // each successive downsample. We use _BitScanForward to count number of zeros - // in the low bits. Zeros indicate we can divide by two without truncating. - uint32_t AdditionalMips; - _BitScanForward((unsigned long*)&AdditionalMips, DstWidth | DstHeight); - uint32_t NumMips = 1 + (AdditionalMips > 3 ? 3 : AdditionalMips); - if (TopMip + NumMips > TexDesc.MipLevels - 1) - NumMips = TexDesc.MipLevels - 1 - TopMip; - - // These are clamped to 1 after computing additional mips because clamped - // dimensions should not limit us from downsampling multiple times. (E.g. - // 16x1 -> 8x1 -> 4x1 -> 2x1 -> 1x1.) - if (DstWidth == 0) - DstWidth = 1; - if (DstHeight == 0) - DstHeight = 1; - - Vk_DESCRIPTOR_HEAP_TYPE HeapType = Vk_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; - auto DescriptorAlloc = Ctx.AllocateDynamicGPUVisibleDescriptor(HeapType, 5); - CommandContext::ShaderDescriptorHeaps Heaps(DescriptorAlloc.GetDescriptorHeap()); - ComputeCtx.SetDescriptorHeaps(Heaps); - Ctx.GetCommandList()->SetComputeRootDescriptorTable(1, DescriptorAlloc.GetGpuHandle(0)); - Ctx.GetCommandList()->SetComputeRootDescriptorTable(2, DescriptorAlloc.GetGpuHandle(1)); - struct RootCBData - { - Uint32 SrcMipLevel; // Texture level of source mip - Uint32 NumMipLevels; // Number of OutMips to write: [1, 4] - Uint32 ArraySlice; - Uint32 Dummy; - float TexelSize[2]; // 1.0 / OutMip1.Dimensions - }CBData = { TopMip, NumMips, ArrSlice, 0, 1.0f / static_cast<float>(DstWidth), 1.0f / static_cast<float>(DstHeight) }; - Ctx.GetCommandList()->SetComputeRoot32BitConstants(0, 6, &CBData, 0); - - // TODO: Shouldn't we transition top mip to shader resource state? - Vk_CPU_DESCRIPTOR_HANDLE DstDescriptorRange = DescriptorAlloc.GetCpuHandle(); - const Uint32 MaxMipsHandledByCS = 4; // Max number of mip levels processed by one CS shader invocation - UINT DstRangeSize = 1 + MaxMipsHandledByCS; - Vk_CPU_DESCRIPTOR_HANDLE SrcDescriptorRanges[5] = {}; - SrcDescriptorRanges[0] = SRVDescriptorHandle; - UINT SrcRangeSizes[5] = { 1,1,1,1,1 }; - // On Resource Binding Tier 2 hardware, all descriptor tables of type CBV and UAV declared in the set - // Root Signature must be populated and initialized, even if the shaders do not need the descriptor. - // So we must populate all 4 slots even though we may actually process less than 4 mip levels - // Copy top mip level UAV descriptor handle to all unused slots - for (Uint32 u = 0; u < MaxMipsHandledByCS; ++u) - SrcDescriptorRanges[1 + u] = pTexVk->GetMipLevelUAV(std::min(TopMip + u + 1, TexDesc.MipLevels - 1)); - - pVkDevice->CopyDescriptors(1, &DstDescriptorRange, &DstRangeSize, 1 + MaxMipsHandledByCS, SrcDescriptorRanges, SrcRangeSizes, Vk_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); - - ComputeCtx.Dispatch((DstWidth + 7) / 8, (DstHeight + 7) / 8); - - Ctx.InsertUAVBarrier(*pTexVk, *pTexVk); - - TopMip += NumMips; - } - } - } -} -*/
\ No newline at end of file diff --git a/Graphics/GraphicsEngineVulkan/src/GenerateMipsVkHelper.cpp b/Graphics/GraphicsEngineVulkan/src/GenerateMipsVkHelper.cpp new file mode 100644 index 00000000..8427ea78 --- /dev/null +++ b/Graphics/GraphicsEngineVulkan/src/GenerateMipsVkHelper.cpp @@ -0,0 +1,310 @@ +/* Copyright 2015-2018 Egor Yusov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF ANY PROPRIETARY RIGHTS. + * + * In no event and under no legal theory, whether in tort (including negligence), + * contract, or otherwise, unless required by applicable law (such as deliberate + * and grossly negligent acts) or agreed to in writing, shall any Contributor be + * liable for any damages, including any direct, indirect, special, incidental, + * or consequential damages of any character arising as a result of this License or + * out of the use or inability to use the software (including but not limited to damages + * for loss of goodwill, work stoppage, computer failure or malfunction, or any and + * all other commercial damages or losses), even if such Contributor has been advised + * of the possibility of such damages. + */ + +#include "pch.h" +#include <sstream> +#include "GenerateMipsVkHelper.h" +#include "RenderDeviceVkImpl.h" +#include "DeviceContextVkImpl.h" +#include "TextureViewVkImpl.h" +#include "TextureVkImpl.h" +#include "MapHelper.h" +#include "../../GraphicsTools/include/ShaderMacroHelper.h" +#include "../../GraphicsTools/include/CommonlyUsedStates.h" + + +static const char* g_GenerateMipsCSSource = +{ + #include "../shaders/GenerateMipsCS_inc.h" +}; + +namespace Diligent +{ + void GenerateMipsVkHelper::GetGlImageFormat(const TextureFormatAttribs& FmtAttribs, std::array<char, 16>& GlFmt) + { + size_t pos = 0; + GlFmt[pos++] = 'r'; + if(FmtAttribs.NumComponents >= 2) + GlFmt[pos++] = 'g'; + if(FmtAttribs.NumComponents >= 3) + GlFmt[pos++] = 'b'; + if(FmtAttribs.NumComponents >= 4) + GlFmt[pos++] = 'a'; + VERIFY_EXPR(FmtAttribs.NumComponents <= 4); + auto ComponentSize = FmtAttribs.ComponentSize * 8; + int pow10 = 1; + while(ComponentSize / (10*pow10) != 0) + pow10 *= 10; + VERIFY_EXPR(ComponentSize !=0); + while(ComponentSize != 0) + { + char digit = static_cast<char>(ComponentSize/pow10); + GlFmt[pos++] = '0' + digit; + ComponentSize -= digit * pow10; + pow10 /= 10; + } + + switch(FmtAttribs.ComponentType) + { + case COMPONENT_TYPE_FLOAT: + GlFmt[pos++] = 'f'; + break; + + case COMPONENT_TYPE_UNORM: + case COMPONENT_TYPE_UNORM_SRGB: + // No suffix + break; + + case COMPONENT_TYPE_SNORM: + GlFmt[pos++] = '_'; + GlFmt[pos++] = 's'; + GlFmt[pos++] = 'n'; + GlFmt[pos++] = 'o'; + GlFmt[pos++] = 'r'; + GlFmt[pos++] = 'm'; + break; + + case COMPONENT_TYPE_SINT: + GlFmt[pos++] = 'i'; + break; + + case COMPONENT_TYPE_UINT: + GlFmt[pos++] = 'u'; + GlFmt[pos++] = 'i'; + break; + + default: + UNSUPPORTED("Unsupported component type"); + } + + GlFmt[pos] = 0; + } + + std::array<RefCntAutoPtr<IPipelineState>, 4> GenerateMipsVkHelper::CreatePSOs(TEXTURE_FORMAT Fmt) + { + ShaderCreationAttribs CSCreateAttribs; + std::array<RefCntAutoPtr<IPipelineState>, 4> PSOs; + + CSCreateAttribs.Source = g_GenerateMipsCSSource; + CSCreateAttribs.EntryPoint = "main"; + CSCreateAttribs.SourceLanguage = SHADER_SOURCE_LANGUAGE_GLSL; + CSCreateAttribs.Desc.ShaderType = SHADER_TYPE_COMPUTE; + CSCreateAttribs.Desc.DefaultVariableType = SHADER_VARIABLE_TYPE_DYNAMIC; + + ShaderVariableDesc VarDesc("CB", SHADER_VARIABLE_TYPE_STATIC); + CSCreateAttribs.Desc.VariableDesc = &VarDesc; + CSCreateAttribs.Desc.NumVariables = 1; + + const StaticSamplerDesc StaticSampler("SrcMip", Sam_LinearClamp); + CSCreateAttribs.Desc.StaticSamplers = &StaticSampler; + CSCreateAttribs.Desc.NumStaticSamplers = 1; + + const auto& FmtAttribs = GetTextureFormatAttribs(Fmt); + bool IsGamma = FmtAttribs.ComponentType == COMPONENT_TYPE_UNORM_SRGB; + std::array<char, 16> GlFmt; + GetGlImageFormat(FmtAttribs, GlFmt); + + for(Uint32 NonPowOfTwo=0; NonPowOfTwo < 4; ++NonPowOfTwo) + { + ShaderMacroHelper Macros; + Macros.AddShaderMacro("NON_POWER_OF_TWO", NonPowOfTwo); + Macros.AddShaderMacro("CONVERT_TO_SRGB", IsGamma); + Macros.AddShaderMacro("IMG_FORMAT", GlFmt.data()); + + Macros.Finalize(); + CSCreateAttribs.Macros = Macros; + + std::stringstream name_ss; + name_ss << "Generate mips " << GlFmt.data(); + switch(NonPowOfTwo) + { + case 0: name_ss << " even"; break; + case 1: name_ss << " odd X"; break; + case 2: name_ss << " odd Y"; break; + case 3: name_ss << " odd XY"; break; + default: UNEXPECTED("Unexpected value"); + } + auto name = name_ss.str(); + CSCreateAttribs.Desc.Name = name.c_str(); + RefCntAutoPtr<IShader> pCS; + + m_DeviceVkImpl.CreateShader(CSCreateAttribs, &pCS); + PipelineStateDesc PSODesc; + PSODesc.IsComputePipeline = true; + PSODesc.Name = name.c_str(); + PSODesc.ComputePipeline.pCS = pCS; + pCS->GetShaderVariable("CB")->Set(m_ConstantsCB); + m_DeviceVkImpl.CreatePipelineState(PSODesc, &PSOs[NonPowOfTwo]); + } + + return PSOs; + } + + GenerateMipsVkHelper::GenerateMipsVkHelper(RenderDeviceVkImpl& DeviceVkImpl) : + m_DeviceVkImpl(DeviceVkImpl) + { + BufferDesc ConstantsCBDesc; + ConstantsCBDesc.Name = "Constants CB buffer"; + ConstantsCBDesc.BindFlags = BIND_UNIFORM_BUFFER; + ConstantsCBDesc.Usage = USAGE_DYNAMIC; + ConstantsCBDesc.CPUAccessFlags = CPU_ACCESS_WRITE; + ConstantsCBDesc.uiSizeInBytes = 32; + DeviceVkImpl.CreateBuffer(ConstantsCBDesc, BufferData(), &m_ConstantsCB); + + 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) + { + // All PSOs are compatible + auto& PSO = FindPSOs(TEX_FORMAT_RGBA8_UNORM); + PSO[0]->CreateShaderResourceBinding(ppSRB); + } + + std::array<RefCntAutoPtr<IPipelineState>, 4>& GenerateMipsVkHelper::FindPSOs(TEXTURE_FORMAT Fmt) + { + std::lock_guard<std::mutex> Lock(m_PSOMutex); + auto it = m_PSOHash.find(Fmt); + if(it == m_PSOHash.end()) + it = m_PSOHash.emplace(Fmt, CreatePSOs(Fmt)).first; + return it->second; + } + + void GenerateMipsVkHelper::GenerateMips(TextureViewVkImpl& TexView, DeviceContextVkImpl& Ctx, IShaderResourceBinding& SRB) + { + auto* pTexVk = TexView.GetTexture<TextureVkImpl>(); + const auto& TexDesc = pTexVk->GetDesc(); + const auto& ViewDesc = TexView.GetDesc(); + auto* pSrcMipVar = SRB.GetVariable(SHADER_TYPE_COMPUTE, "SrcMip"); + auto* pOutMipVar = SRB.GetVariable(SHADER_TYPE_COMPUTE, "OutMip"); + + auto& PSOs = FindPSOs(ViewDesc.Format); + + const auto& FmtAttribs = GetTextureFormatAttribs(TexDesc.Format); + VkImageSubresourceRange SubresRange; + if (FmtAttribs.ComponentType == COMPONENT_TYPE_DEPTH) + SubresRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + else if (FmtAttribs.ComponentType == COMPONENT_TYPE_DEPTH_STENCIL) + { + // If image has a depth / stencil format with both depth and stencil components, then the + // aspectMask member of subresourceRange must include both VK_IMAGE_ASPECT_DEPTH_BIT and + // VK_IMAGE_ASPECT_STENCIL_BIT (6.7.3) + SubresRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + } + else + SubresRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + SubresRange.baseArrayLayer = 0; + SubresRange.layerCount = VK_REMAINING_ARRAY_LAYERS; + + auto CurrLayout = pTexVk->GetLayout(); + + // Transition the lowest mip level to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL + SubresRange.baseMipLevel = 0; + SubresRange.levelCount = 1; + if (CurrLayout != VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) + Ctx.TransitionImageLayout(*pTexVk, CurrLayout, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, SubresRange); + + for (uint32_t TopMip = 0; TopMip < TexDesc.MipLevels - 1; ) + { + // 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)); + + uint32_t SrcWidth = std::max(TexDesc.Width >> TopMip, 1u); + uint32_t SrcHeight = std::max(TexDesc.Height >> TopMip, 1u); + uint32_t DstWidth = std::max(SrcWidth >> 1, 1u); + uint32_t DstHeight = std::max(SrcHeight >> 1, 1u); + + // Determine if the first downsample is more than 2:1. This happens whenever + // the source width or height is odd. + uint32_t NonPowerOfTwo = (SrcWidth & 1) | (SrcHeight & 1) << 1; + Ctx.SetPipelineState(PSOs[NonPowerOfTwo]); + + // We can downsample up to four times, but if the ratio between levels is not + // exactly 2:1, we have to shift our blend weights, which gets complicated or + // expensive. Maybe we can update the code later to compute sample weights for + // each successive downsample. We use _BitScanForward to count number of zeros + // in the low bits. Zeros indicate we can divide by two without truncating. + uint32_t AdditionalMips; + _BitScanForward((unsigned long*)&AdditionalMips, DstWidth | DstHeight); + uint32_t NumMips = 1 + (AdditionalMips > 3 ? 3 : AdditionalMips); + if (TopMip + NumMips > TexDesc.MipLevels - 1) + NumMips = TexDesc.MipLevels - 1 - TopMip; + + // These are clamped to 1 after computing additional mips because clamped + // dimensions should not limit us from downsampling multiple times. (E.g. + // 16x1 -> 8x1 -> 4x1 -> 2x1 -> 1x1.) + if (DstWidth == 0) + DstWidth = 1; + if (DstHeight == 0) + DstHeight = 1; + + { + struct CBData + { + Int32 SrcMipLevel; // Texture level of source mip + Int32 NumMipLevels; // Number of OutMips to write: [1, 4] + Int32 ArraySlice; + Int32 Dummy; + float TexelSize[2]; // 1.0 / OutMip1.Dimensions + }; + MapHelper<CBData> MappedData(&Ctx, m_ConstantsCB, MAP_WRITE, MAP_FLAG_DISCARD); + + *MappedData = + { + static_cast<Int32>(TopMip), + static_cast<Int32>(NumMips), + static_cast<Int32>(ViewDesc.FirstArraySlice), + 0, + 1.0f / static_cast<float>(DstWidth), + 1.0f / static_cast<float>(DstHeight) + }; + } + + constexpr const Uint32 MaxMipsHandledByCS = 4; // Max number of mip levels processed by one CS shader invocation + std::array<IDeviceObject*, MaxMipsHandledByCS> MipLevelUAVs; + for (Uint32 u = 0; u < MaxMipsHandledByCS; ++u) + MipLevelUAVs[u] = pTexVk->GetMipLevelUAV(std::min(TopMip + u + 1, TexDesc.MipLevels - 1)); + pOutMipVar->SetArray(MipLevelUAVs.data(), 0, MaxMipsHandledByCS); + + 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); + + Ctx.CommitShaderResources(&SRB, 0); + DispatchComputeAttribs DispatchAttrs((DstWidth + 7) / 8, (DstHeight + 7) / 8, ViewDesc.NumArraySlices); + Ctx.DispatchCompute(DispatchAttrs); + + Ctx.TransitionImageLayout(*pTexVk, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, SubresRange); + + TopMip += NumMips; + } + + // All mip levels are now in VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL state + pTexVk->SetLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + } +} diff --git a/Graphics/GraphicsEngineVulkan/src/PipelineStateVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/PipelineStateVkImpl.cpp index ed3d334e..a202c59c 100644 --- a/Graphics/GraphicsEngineVulkan/src/PipelineStateVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/PipelineStateVkImpl.cpp @@ -491,7 +491,7 @@ bool PipelineStateVkImpl::IsCompatibleWith(const IPipelineState *pPSO)const void PipelineStateVkImpl::CommitAndTransitionShaderResources(IShaderResourceBinding* pShaderResourceBinding, DeviceContextVkImpl* pCtxVkImpl, bool CommitResources, - bool TransitionResources, + Uint32 Flags, PipelineLayout::DescriptorSetBindInfo* pDescrSetBindInfo)const { if (!m_HasStaticResources && !m_HasNonStaticResources) @@ -546,9 +546,9 @@ void PipelineStateVkImpl::CommitAndTransitionShaderResources(IShaderResourceBind if (CommitResources) { - if (TransitionResources) + if (Flags & COMMIT_SHADER_RESOURCES_FLAG_TRANSITION_RESOURCES) ResourceCache.TransitionResources<false>(pCtxVkImpl); - else + else if (Flags & COMMIT_SHADER_RESOURCES_FLAG_VERIFY_STATES) { #ifdef VERIFY_SHADER_BINDINGS ResourceCache.TransitionResources<true>(pCtxVkImpl); @@ -578,7 +578,7 @@ void PipelineStateVkImpl::CommitAndTransitionShaderResources(IShaderResourceBind } else { - VERIFY(TransitionResources, "Resources should be transitioned or committed or both"); + VERIFY( (Flags & COMMIT_SHADER_RESOURCES_FLAG_TRANSITION_RESOURCES) != 0 , "Resources should be transitioned or committed or both"); ResourceCache.TransitionResources<false>(pCtxVkImpl); } } diff --git a/Graphics/GraphicsEngineVulkan/src/RenderDeviceFactoryVk.cpp b/Graphics/GraphicsEngineVulkan/src/RenderDeviceFactoryVk.cpp index b02c4d23..4508a9ce 100644 --- a/Graphics/GraphicsEngineVulkan/src/RenderDeviceFactoryVk.cpp +++ b/Graphics/GraphicsEngineVulkan/src/RenderDeviceFactoryVk.cpp @@ -150,19 +150,20 @@ void EngineFactoryVkImpl::CreateDeviceAndContextsVk( const EngineVkAttribs& Crea DeviceCreateInfo.queueCreateInfoCount = 1; DeviceCreateInfo.pQueueCreateInfos = &QueueInfo; VkPhysicalDeviceFeatures DeviceFeatures = {}; - DeviceFeatures.depthBiasClamp = CreationAttribs.EnabledFeatures.depthBiasClamp ? VK_TRUE : VK_FALSE; - DeviceFeatures.fillModeNonSolid = CreationAttribs.EnabledFeatures.fillModeNonSolid ? VK_TRUE : VK_FALSE; - DeviceFeatures.depthClamp = CreationAttribs.EnabledFeatures.depthClamp ? VK_TRUE : VK_FALSE; - DeviceFeatures.independentBlend = CreationAttribs.EnabledFeatures.independentBlend ? VK_TRUE : VK_FALSE; - DeviceFeatures.samplerAnisotropy = CreationAttribs.EnabledFeatures.samplerAnisotropy ? VK_TRUE : VK_FALSE; - DeviceFeatures.geometryShader = CreationAttribs.EnabledFeatures.geometryShader ? VK_TRUE : VK_FALSE; - DeviceFeatures.tessellationShader = CreationAttribs.EnabledFeatures.tessellationShader ? VK_TRUE : VK_FALSE; - DeviceFeatures.dualSrcBlend = CreationAttribs.EnabledFeatures.dualSrcBlend ? VK_TRUE : VK_FALSE; - DeviceFeatures.multiViewport = CreationAttribs.EnabledFeatures.multiViewport ? VK_TRUE : VK_FALSE; - DeviceFeatures.imageCubeArray = CreationAttribs.EnabledFeatures.imageCubeArray ? VK_TRUE : VK_FALSE; - DeviceFeatures.textureCompressionBC = CreationAttribs.EnabledFeatures.textureCompressionBC ? VK_TRUE : VK_FALSE; - DeviceFeatures.vertexPipelineStoresAndAtomics = CreationAttribs.EnabledFeatures.vertexPipelineStoresAndAtomics ? VK_TRUE : VK_FALSE; - DeviceFeatures.fragmentStoresAndAtomics = CreationAttribs.EnabledFeatures.fragmentStoresAndAtomics ? VK_TRUE : VK_FALSE; + DeviceFeatures.depthBiasClamp = CreationAttribs.EnabledFeatures.depthBiasClamp ? VK_TRUE : VK_FALSE; + DeviceFeatures.fillModeNonSolid = CreationAttribs.EnabledFeatures.fillModeNonSolid ? VK_TRUE : VK_FALSE; + DeviceFeatures.depthClamp = CreationAttribs.EnabledFeatures.depthClamp ? VK_TRUE : VK_FALSE; + DeviceFeatures.independentBlend = CreationAttribs.EnabledFeatures.independentBlend ? VK_TRUE : VK_FALSE; + DeviceFeatures.samplerAnisotropy = CreationAttribs.EnabledFeatures.samplerAnisotropy ? VK_TRUE : VK_FALSE; + DeviceFeatures.geometryShader = CreationAttribs.EnabledFeatures.geometryShader ? VK_TRUE : VK_FALSE; + DeviceFeatures.tessellationShader = CreationAttribs.EnabledFeatures.tessellationShader ? VK_TRUE : VK_FALSE; + DeviceFeatures.dualSrcBlend = CreationAttribs.EnabledFeatures.dualSrcBlend ? VK_TRUE : VK_FALSE; + DeviceFeatures.multiViewport = CreationAttribs.EnabledFeatures.multiViewport ? VK_TRUE : VK_FALSE; + DeviceFeatures.imageCubeArray = CreationAttribs.EnabledFeatures.imageCubeArray ? VK_TRUE : VK_FALSE; + DeviceFeatures.textureCompressionBC = CreationAttribs.EnabledFeatures.textureCompressionBC ? VK_TRUE : VK_FALSE; + DeviceFeatures.vertexPipelineStoresAndAtomics = CreationAttribs.EnabledFeatures.vertexPipelineStoresAndAtomics ? VK_TRUE : VK_FALSE; + DeviceFeatures.fragmentStoresAndAtomics = CreationAttribs.EnabledFeatures.fragmentStoresAndAtomics ? VK_TRUE : VK_FALSE; + DeviceFeatures.shaderStorageImageExtendedFormats = CreationAttribs.EnabledFeatures.shaderStorageImageExtendedFormats ? VK_TRUE : VK_FALSE; DeviceCreateInfo.pEnabledFeatures = &DeviceFeatures; // NULL or a pointer to a VkPhysicalDeviceFeatures structure that contains // boolean indicators of all the features to be enabled. @@ -240,7 +241,9 @@ void EngineFactoryVkImpl::AttachToVulkanDevice(std::shared_ptr<VulkanUtilities:: RenderDeviceVkImpl *pRenderDeviceVk( NEW_RC_OBJ(RawMemAllocator, "RenderDeviceVkImpl instance", RenderDeviceVkImpl)(RawMemAllocator, EngineAttribs, pCommandQueue, Instance, std::move(PhysicalDevice), LogicalDevice, NumDeferredContexts ) ); pRenderDeviceVk->QueryInterface(IID_RenderDevice, reinterpret_cast<IObject**>(ppDevice) ); - RefCntAutoPtr<DeviceContextVkImpl> pImmediateCtxVk( NEW_RC_OBJ(RawMemAllocator, "DeviceContextVkImpl instance", DeviceContextVkImpl)(pRenderDeviceVk, false, EngineAttribs, 0) ); + std::shared_ptr<GenerateMipsVkHelper> GenerateMipsHelper(new GenerateMipsVkHelper(*pRenderDeviceVk)); + + RefCntAutoPtr<DeviceContextVkImpl> pImmediateCtxVk( NEW_RC_OBJ(RawMemAllocator, "DeviceContextVkImpl instance", DeviceContextVkImpl)(pRenderDeviceVk, false, EngineAttribs, 0, GenerateMipsHelper) ); // We must call AddRef() (implicitly through QueryInterface()) because pRenderDeviceVk will // keep a weak reference to the context pImmediateCtxVk->QueryInterface(IID_DeviceContext, reinterpret_cast<IObject**>(ppContexts) ); @@ -248,7 +251,7 @@ void EngineFactoryVkImpl::AttachToVulkanDevice(std::shared_ptr<VulkanUtilities:: for (Uint32 DeferredCtx = 0; DeferredCtx < NumDeferredContexts; ++DeferredCtx) { - RefCntAutoPtr<DeviceContextVkImpl> pDeferredCtxVk( NEW_RC_OBJ(RawMemAllocator, "DeviceContextVkImpl instance", DeviceContextVkImpl)(pRenderDeviceVk, true, EngineAttribs, 1+DeferredCtx) ); + RefCntAutoPtr<DeviceContextVkImpl> pDeferredCtxVk( NEW_RC_OBJ(RawMemAllocator, "DeviceContextVkImpl instance", DeviceContextVkImpl)(pRenderDeviceVk, true, EngineAttribs, 1+DeferredCtx, GenerateMipsHelper) ); // We must call AddRef() (implicitly through QueryInterface()) because pRenderDeviceVk will // keep a weak reference to the context pDeferredCtxVk->QueryInterface(IID_DeviceContext, reinterpret_cast<IObject**>(ppContexts + 1 + DeferredCtx) ); diff --git a/Graphics/GraphicsEngineVulkan/src/ShaderResourceCacheVk.cpp b/Graphics/GraphicsEngineVulkan/src/ShaderResourceCacheVk.cpp index ec05b846..e1091a88 100644 --- a/Graphics/GraphicsEngineVulkan/src/ShaderResourceCacheVk.cpp +++ b/Graphics/GraphicsEngineVulkan/src/ShaderResourceCacheVk.cpp @@ -114,7 +114,12 @@ void ShaderResourceCacheVk::TransitionResources(DeviceContextVkImpl *pCtxVkImpl) if(!pBufferVk->CheckAccessFlags(RequiredAccessFlags)) { if(VerifyOnly) - LOG_ERROR_MESSAGE("Buffer \"", pBufferVk->GetDesc().Name, "\" is not in correct state. Did you forget to call TransitionShaderResources() or specify COMMIT_SHADER_RESOURCES_FLAG_TRANSITION_RESOURCES flag in a call to CommitShaderResources()?"); + { + LOG_ERROR_MESSAGE("State of buffer \"", pBufferVk->GetDesc().Name, "\" is incorrect. Required access flags: ", + VulkanUtilities::VkAccessFlagsToString(RequiredAccessFlags), ". Actual access flags: ", + VulkanUtilities::VkAccessFlagsToString(pBufferVk->GetAccessFlags()), + ". Call TransitionShaderResources() or provide COMMIT_SHADER_RESOURCES_FLAG_TRANSITION_RESOURCES flag to CommitShaderResources()"); + } else pCtxVkImpl->BufferMemoryBarrier(*pBufferVk, RequiredAccessFlags); } @@ -134,7 +139,12 @@ void ShaderResourceCacheVk::TransitionResources(DeviceContextVkImpl *pCtxVkImpl) if (!pBufferVk->CheckAccessFlags(RequiredAccessFlags)) { if (VerifyOnly) - LOG_ERROR_MESSAGE("Buffer \"", pBufferVk->GetDesc().Name, "\" is not in correct state. Did you forget to call TransitionShaderResources() or specify COMMIT_SHADER_RESOURCES_FLAG_TRANSITION_RESOURCES flag in a call to CommitShaderResources()?"); + { + LOG_ERROR_MESSAGE("State of buffer \"", pBufferVk->GetDesc().Name, "\" is incorrect. Required access flags: ", + VulkanUtilities::VkAccessFlagsToString(RequiredAccessFlags), ". Actual access flags: ", + VulkanUtilities::VkAccessFlagsToString(pBufferVk->GetAccessFlags()), + ". Call TransitionShaderResources() or provide COMMIT_SHADER_RESOURCES_FLAG_TRANSITION_RESOURCES flag to CommitShaderResources()"); + } else pCtxVkImpl->BufferMemoryBarrier(*pBufferVk, RequiredAccessFlags); } @@ -172,7 +182,12 @@ void ShaderResourceCacheVk::TransitionResources(DeviceContextVkImpl *pCtxVkImpl) if(pTextureVk->GetLayout() != RequiredLayout) { if (VerifyOnly) - LOG_ERROR_MESSAGE("Texture \"", pTextureVk->GetDesc().Name, "\" is not in correct state. Did you forget to call TransitionShaderResources() or specify COMMIT_SHADER_RESOURCES_FLAG_TRANSITION_RESOURCES flag in a call to CommitShaderResources()?"); + { + LOG_ERROR_MESSAGE("State of texture \"", pTextureVk->GetDesc().Name, "\" is incorrect. Required layout: ", + VulkanUtilities::VkImageLayoutToString(RequiredLayout), ". Actual layout: ", + VulkanUtilities::VkImageLayoutToString(pTextureVk->GetLayout()), + ". Call TransitionShaderResources() or specify COMMIT_SHADER_RESOURCES_FLAG_TRANSITION_RESOURCES flag in a call to CommitShaderResources()"); + } else pCtxVkImpl->TransitionImageLayout(*pTextureVk, RequiredLayout); } diff --git a/Graphics/GraphicsEngineVulkan/src/TextureViewVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/TextureViewVkImpl.cpp index f798e3b2..a1263f98 100644 --- a/Graphics/GraphicsEngineVulkan/src/TextureViewVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/TextureViewVkImpl.cpp @@ -58,8 +58,8 @@ void TextureViewVkImpl::GenerateMips( IDeviceContext *pContext ) LOG_ERROR("GenerateMips() is allowed for shader resource views only, ", GetTexViewTypeLiteralName(m_Desc.ViewType), " is not allowed."); return; } - auto *pDeviceCtxVk = ValidatedCast<DeviceContextVkImpl>( pContext ); - pDeviceCtxVk->GenerateMips(this); + auto* pDeviceCtxVk = ValidatedCast<DeviceContextVkImpl>( pContext ); + pDeviceCtxVk->GenerateMips(*this); } } diff --git a/Graphics/GraphicsEngineVulkan/src/TextureVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/TextureVkImpl.cpp index 16fb400d..eb396192 100644 --- a/Graphics/GraphicsEngineVulkan/src/TextureVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/TextureVkImpl.cpp @@ -361,7 +361,7 @@ TextureVkImpl :: TextureVkImpl(IReferenceCounters* pRefCounters, pRenderDeviceVk->ExecuteAndDisposeTransientCmdBuff(vkCmdBuff, std::move(CmdPool)); } -#if 0 + 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) @@ -369,10 +369,15 @@ TextureVkImpl :: TextureVkImpl(IReferenceCounters* pRefCounters, LOG_ERROR_AND_THROW("Mipmap generation is only supported for 2D textures and texture arrays"); } - m_MipUAVs = pRenderDeviceVk->AllocateDescriptor(Vk_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, m_Desc.MipLevels); + 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; @@ -381,23 +386,34 @@ TextureVkImpl :: TextureVkImpl(IReferenceCounters* pRefCounters, UAVDesc.MostDetailedMip = MipLevel; if (m_Desc.Format == TEX_FORMAT_RGBA8_UNORM_SRGB) UAVDesc.Format = TEX_FORMAT_RGBA8_UNORM; - CreateUAV( UAVDesc, m_MipUAVs.GetCpuHandle(MipLevel) ); + ITextureView* pMipUAV = nullptr; + CreateViewInternal( UAVDesc, &pMipUAV, true ); + m_MipLevelUAV.emplace_back(ValidatedCast<TextureViewVkImpl>(pMipUAV), STDDeleter<TextureViewVkImpl, FixedBlockMemoryAllocator>(TexViewObjAllocator)); } + VERIFY_EXPR(m_MipLevelUAV.size() == m_Desc.MipLevels); + m_MipLevelSRV.reserve(m_Desc.MipLevels); + for(Uint32 MipLevel = 0; MipLevel < m_Desc.MipLevels; ++MipLevel) { - m_TexArraySRV = pRenderDeviceVk->AllocateDescriptor(Vk_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, 1); + // Create mip level SRV TextureViewDesc TexArraySRVDesc; - // Create texture array SRV + 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 = 0; - TexArraySRVDesc.NumMipLevels = m_Desc.MipLevels; - CreateSRV( TexArraySRVDesc, m_TexArraySRV.GetCpuHandle() ); + TexArraySRVDesc.MostDetailedMip = MipLevel; + TexArraySRVDesc.NumMipLevels = 1; + ITextureView* pMipLevelSRV = nullptr; + CreateViewInternal( TexArraySRVDesc, &pMipLevelSRV, true ); + m_MipLevelSRV.emplace_back(ValidatedCast<TextureViewVkImpl>(pMipLevelSRV), STDDeleter<TextureViewVkImpl, FixedBlockMemoryAllocator>(TexViewObjAllocator)); } + VERIFY_EXPR(m_MipLevelSRV.size() == m_Desc.MipLevels); } -#endif } TextureVkImpl::TextureVkImpl(IReferenceCounters* pRefCounters, diff --git a/Graphics/GraphicsEngineVulkan/src/VulkanUtilities/VulkanDebug.cpp b/Graphics/GraphicsEngineVulkan/src/VulkanUtilities/VulkanDebug.cpp index 0aa12c89..f971bcab 100644 --- a/Graphics/GraphicsEngineVulkan/src/VulkanUtilities/VulkanDebug.cpp +++ b/Graphics/GraphicsEngineVulkan/src/VulkanUtilities/VulkanDebug.cpp @@ -429,4 +429,69 @@ namespace VulkanUtilities return "UNKNOWN_ERROR"; } } + + const char* VkAccessFlagBitToString(VkAccessFlagBits Bit) + { + VERIFY(Bit != 0 && (Bit & (Bit-1)) == 0, "Single bit is expected"); + switch(Bit) + { +#define ACCESS_FLAG_BIT_TO_STRING(ACCESS_FLAG_BIT)case ACCESS_FLAG_BIT: return #ACCESS_FLAG_BIT; + ACCESS_FLAG_BIT_TO_STRING(VK_ACCESS_INDIRECT_COMMAND_READ_BIT) + ACCESS_FLAG_BIT_TO_STRING(VK_ACCESS_INDEX_READ_BIT) + ACCESS_FLAG_BIT_TO_STRING(VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT) + ACCESS_FLAG_BIT_TO_STRING(VK_ACCESS_UNIFORM_READ_BIT) + ACCESS_FLAG_BIT_TO_STRING(VK_ACCESS_INPUT_ATTACHMENT_READ_BIT) + ACCESS_FLAG_BIT_TO_STRING(VK_ACCESS_SHADER_READ_BIT) + ACCESS_FLAG_BIT_TO_STRING(VK_ACCESS_SHADER_WRITE_BIT) + ACCESS_FLAG_BIT_TO_STRING(VK_ACCESS_COLOR_ATTACHMENT_READ_BIT) + ACCESS_FLAG_BIT_TO_STRING(VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT) + ACCESS_FLAG_BIT_TO_STRING(VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT) + ACCESS_FLAG_BIT_TO_STRING(VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT) + ACCESS_FLAG_BIT_TO_STRING(VK_ACCESS_TRANSFER_READ_BIT) + ACCESS_FLAG_BIT_TO_STRING(VK_ACCESS_TRANSFER_WRITE_BIT) + ACCESS_FLAG_BIT_TO_STRING(VK_ACCESS_HOST_READ_BIT) + ACCESS_FLAG_BIT_TO_STRING(VK_ACCESS_HOST_WRITE_BIT) + ACCESS_FLAG_BIT_TO_STRING(VK_ACCESS_MEMORY_READ_BIT) + ACCESS_FLAG_BIT_TO_STRING(VK_ACCESS_MEMORY_WRITE_BIT) +#undef ACCESS_FLAG_BIT_TO_STRING + default: UNEXPECTED("Unexpected bit"); return ""; + } + } + + const char* VkImageLayoutToString(VkImageLayout Layout) + { + switch(Layout) + { +#define IMAGE_LAYOUT_TO_STRING(LAYOUT)case LAYOUT: return #LAYOUT; + IMAGE_LAYOUT_TO_STRING(VK_IMAGE_LAYOUT_UNDEFINED) + IMAGE_LAYOUT_TO_STRING(VK_IMAGE_LAYOUT_GENERAL) + IMAGE_LAYOUT_TO_STRING(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) + IMAGE_LAYOUT_TO_STRING(VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) + IMAGE_LAYOUT_TO_STRING(VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL) + IMAGE_LAYOUT_TO_STRING(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) + IMAGE_LAYOUT_TO_STRING(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) + IMAGE_LAYOUT_TO_STRING(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) + IMAGE_LAYOUT_TO_STRING(VK_IMAGE_LAYOUT_PREINITIALIZED) + IMAGE_LAYOUT_TO_STRING(VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL) + IMAGE_LAYOUT_TO_STRING(VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL) + IMAGE_LAYOUT_TO_STRING(VK_IMAGE_LAYOUT_PRESENT_SRC_KHR) + IMAGE_LAYOUT_TO_STRING(VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR) +#undef IMAGE_LAYOUT_TO_STRING + default: UNEXPECTED("Unknown layout"); return ""; + } + } + + std::string VkAccessFlagsToString(VkAccessFlags Flags) + { + std::string FlagsString; + while(Flags != 0) + { + auto Bit = Flags & ~(Flags - 1); + if (!FlagsString.empty()) + FlagsString += ", "; + FlagsString += VkAccessFlagBitToString( static_cast<VkAccessFlagBits>(Bit) ); + Flags = Flags & (Flags - 1); + } + return std::move(FlagsString); + } } |
