From 7eb08f11ea24d8fe86b8b99b23eb03d4ec16184e Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Tue, 2 Apr 2024 13:59:24 -0700 Subject: [PATCH] vk: add descriptor set/layout types to handles (#7728) - Move VulkanDescriptorSet to VulkanHandles.h - Add VulkanDescriptorSetLayout to VulkanHandles.h - Add "input" descriptor set types to VulkanUtility.h. These are structs that will be defined in the backend API (shared across all backends) and eventually passed from the front-end to the vk backend. - Logic to parse descriptor set layout from the spirv-v shaders. - Move UsageFlags type to VulkanUtility.h - Just prep work. No effect to current implementations. --- filament/backend/src/vulkan/VulkanDriver.cpp | 5 +- filament/backend/src/vulkan/VulkanHandles.cpp | 167 +++++++++++++-- filament/backend/src/vulkan/VulkanHandles.h | 193 ++++++++++++++++-- .../src/vulkan/VulkanPipelineCache.cpp | 21 +- .../backend/src/vulkan/VulkanPipelineCache.h | 2 - .../backend/src/vulkan/VulkanResources.cpp | 5 +- filament/backend/src/vulkan/VulkanResources.h | 31 +-- filament/backend/src/vulkan/VulkanUtility.cpp | 2 + filament/backend/src/vulkan/VulkanUtility.h | 129 +++++++++++- .../vulkan/caching/VulkanDescriptorSet.cpp | 79 ++----- .../src/vulkan/caching/VulkanDescriptorSet.h | 25 --- .../src/vulkan/spirv/VulkanSpirvUtils.cpp | 60 +++++- .../src/vulkan/spirv/VulkanSpirvUtils.h | 6 +- 13 files changed, 554 insertions(+), 171 deletions(-) diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index d91eeb84b4..5b8d6de9f8 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -1771,14 +1771,13 @@ void VulkanDriver::bindPipeline(PipelineState pipelineState) { VulkanTexture* samplerTextures[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {nullptr}; auto const& bindingToSamplerIndex = program->getBindingToSamplerIndex(); - VulkanPipelineCache::UsageFlags usage = program->getUsage(); + UsageFlags usage = program->getUsage(); #if FVK_ENABLED_DEBUG_SAMPLER_NAME auto const& bindingToName = program->getBindingToName(); #endif - UTILS_NOUNROLL - for (uint8_t binding = 0; binding < VulkanPipelineCache::SAMPLER_BINDING_COUNT; binding++) { + for (auto binding: program->getBindings()) { uint16_t const indexPair = bindingToSamplerIndex[binding]; if (indexPair == 0xffff) { diff --git a/filament/backend/src/vulkan/VulkanHandles.cpp b/filament/backend/src/vulkan/VulkanHandles.cpp index 48dc56a2b1..ae012d77bc 100644 --- a/filament/backend/src/vulkan/VulkanHandles.cpp +++ b/filament/backend/src/vulkan/VulkanHandles.cpp @@ -20,6 +20,7 @@ #include "VulkanConstants.h" #include "VulkanDriver.h" #include "VulkanMemory.h" +#include "VulkanUtility.h" #include "spirv/VulkanSpirvUtils.h" #include @@ -30,15 +31,17 @@ using namespace bluevk; namespace filament::backend { -static void flipVertically(VkRect2D* rect, uint32_t framebufferHeight) { +namespace { + +void flipVertically(VkRect2D* rect, uint32_t framebufferHeight) { rect->offset.y = framebufferHeight - rect->offset.y - rect->extent.height; } -static void flipVertically(VkViewport* rect, uint32_t framebufferHeight) { +void flipVertically(VkViewport* rect, uint32_t framebufferHeight) { rect->y = framebufferHeight - rect->y - rect->height; } -static void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeight) { +void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeight) { int32_t x = std::max(rect->offset.x, 0); int32_t y = std::max(rect->offset.y, 0); int32_t right = std::min(rect->offset.x + (int32_t) rect->extent.width, (int32_t) fbWidth); @@ -49,17 +52,103 @@ static void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeig rect->extent.height = std::max(top - y, 0); } +template +static constexpr Bitmask fromStageFlags(ShaderStageFlags2 flags, uint8_t binding) { + Bitmask ret = 0; + if (flags & ShaderStageFlags2::VERTEX) { + ret |= (getVertexStage() << binding); + } + if (flags & ShaderStageFlags2::FRAGMENT) { + ret |= (getFragmentStage() << binding); + } + return ret; +} + +UsageFlags getUsageFlags(uint16_t binding, ShaderStageFlags flags, UsageFlags src) { + // NOTE: if you modify this function, you also need to modify getShaderStageFlags. + assert_invariant(binding < MAX_SAMPLER_COUNT); + if (any(flags & ShaderStageFlags::VERTEX)) { + src.set(binding); + } + if (any(flags & ShaderStageFlags::FRAGMENT)) { + src.set(MAX_SAMPLER_COUNT + binding); + } + // TODO: add support for compute by extending SHADER_MODULE_COUNT and ensuring UsageFlags + // has 186 bits (MAX_SAMPLER_COUNT * 3) + // assert_invariant(!any(flags & ~(ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT))); + return src; +} + +constexpr decltype(VulkanProgram::MAX_SHADER_MODULES) MAX_SHADER_MODULES = + VulkanProgram::MAX_SHADER_MODULES; + +using LayoutDescriptionList = VulkanProgram::LayoutDescriptionList; + +template +void addDescriptors(Bitmask mask, + utils::FixedCapacityVector& outputList) { + constexpr uint8_t MODULE_OFFSET = (sizeof(Bitmask) * 8) / MAX_SHADER_MODULES; + for (uint8_t i = 0; i < MODULE_OFFSET; ++i) { + bool const hasVertex = (mask & (1ULL << i)) != 0; + bool const hasFragment = (mask & (1ULL << (MODULE_OFFSET + i))) != 0; + if (!hasVertex && !hasFragment) { + continue; + } + + DescriptorSetLayoutBinding binding{ + .binding = i, + .flags = DescriptorFlags::NONE, + .count = 0,// This is always 0 for now as we pass the size of the UBOs in the Driver API + // instead. + }; + if (hasVertex) { + binding.stageFlags = ShaderStageFlags2::VERTEX; + } + if (hasFragment) { + binding.stageFlags = static_cast( + binding.stageFlags | ShaderStageFlags2::FRAGMENT); + } + if constexpr (std::is_same_v) { + binding.type = DescriptorType::UNIFORM_BUFFER; + } else if constexpr (std::is_same_v) { + binding.type = DescriptorType::SAMPLER; + } else if constexpr (std::is_same_v) { + binding.type = DescriptorType::INPUT_ATTACHMENT; + } + outputList.push_back(binding); + } +} + +} // anonymous namespace + VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept : HwProgram(builder.getName()), VulkanResource(VulkanResourceType::PROGRAM), mInfo(new PipelineInfo()), mDevice(device) { + + constexpr uint8_t UBO_MODULE_OFFSET = (sizeof(UniformBufferBitmask) * 8) / MAX_SHADER_MODULES; + constexpr uint8_t SAMPLER_MODULE_OFFSET = (sizeof(SamplerBitmask) * 8) / MAX_SHADER_MODULES; + constexpr uint8_t INPUT_ATTACHMENT_MODULE_OFFSET = + (sizeof(InputAttachmentBitmask) * 8) / MAX_SHADER_MODULES; + Program::ShaderSource const& blobs = builder.getShadersSource(); auto& modules = mInfo->shaders; auto const& specializationConstants = builder.getSpecializationConstants(); std::vector shader; + + // TODO: this will be moved out of the shader as the descriptor set layout will be provided by + // Filament instead of parsed from the shaders. + UniformBufferBitmask uboMask = 0; + SamplerBitmask samplerMask = 0; + InputAttachmentBitmask inputAttachmentMask = 0; + + static_assert(static_cast(0) == ShaderStage::VERTEX && + static_cast(1) == ShaderStage::FRAGMENT && + MAX_SHADER_MODULES == 2); + for (size_t i = 0; i < MAX_SHADER_MODULES; i++) { Program::ShaderBlob const& blob = blobs[i]; @@ -72,6 +161,12 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept dataSize = shader.size() * 4; } + auto const [ubo, sampler, inputAttachment] = getProgramBindings(blob); + uboMask |= (static_cast(ubo) << (UBO_MODULE_OFFSET * i)); + samplerMask |= (static_cast(sampler) << (SAMPLER_MODULE_OFFSET * i)); + inputAttachmentMask |= (static_cast(inputAttachment) + << (INPUT_ATTACHMENT_MODULE_OFFSET * i)); + VkShaderModule& module = modules[i]; VkShaderModuleCreateInfo moduleInfo = { .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, @@ -99,12 +194,25 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept #endif } + LayoutDescriptionList& layouts = mInfo->layouts; + layouts[0].bindings = utils::FixedCapacityVector::with_capacity( + countBits(collapseStages(uboMask))); + layouts[1].bindings = utils::FixedCapacityVector::with_capacity( + countBits(collapseStages(samplerMask))); + layouts[2].bindings = utils::FixedCapacityVector::with_capacity( + countBits(collapseStages(inputAttachmentMask))); + + addDescriptors(uboMask, layouts[0].bindings); + addDescriptors(samplerMask, layouts[1].bindings); + addDescriptors(inputAttachmentMask, layouts[2].bindings); + #if FVK_ENABLED_DEBUG_SAMPLER_NAME auto& bindingToName = mInfo->bindingToName; #endif auto& groupInfo = builder.getSamplerGroupInfo(); auto& bindingToSamplerIndex = mInfo->bindingToSamplerIndex; + auto& bindings = mInfo->bindings; auto& usage = mInfo->usage; for (uint8_t groupInd = 0; groupInd < Program::SAMPLER_BINDING_COUNT; groupInd++) { auto const& group = groupInfo[groupInd]; @@ -112,7 +220,9 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept for (size_t i = 0; i < samplers.size(); ++i) { uint32_t const binding = samplers[i].binding; bindingToSamplerIndex[binding] = (groupInd << 8) | (0xff & i); - usage = VulkanPipelineCache::getUsageFlags(binding, group.stageFlags, usage); + assert_invariant(bindings.find(binding) == bindings.end()); + bindings.insert(binding); + usage = getUsageFlags(binding, group.stageFlags, usage); #if FVK_ENABLED_DEBUG_SAMPLER_NAME bindingToName[binding] = samplers[i].name.c_str(); @@ -126,24 +236,6 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept #endif } -VulkanProgram::VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs, - CustomSamplerInfoList const& samplerInfo) noexcept - : VulkanResource(VulkanResourceType::PROGRAM), - mInfo(new PipelineInfo()), - mDevice(device) { - mInfo->shaders[0] = vs; - mInfo->shaders[1] = fs; - auto& bindingToSamplerIndex = mInfo->bindingToSamplerIndex; - auto& usage = mInfo->usage; - bindingToSamplerIndex.resize(samplerInfo.size()); - for (uint16_t binding = 0; binding < samplerInfo.size(); ++binding) { - auto const& sampler = samplerInfo[binding]; - bindingToSamplerIndex[binding] - = (sampler.groupIndex << 8) | (0xff & sampler.samplerIndex); - usage = VulkanPipelineCache::getUsageFlags(binding, sampler.flags, usage); - } -} - VulkanProgram::~VulkanProgram() { for (auto shader: mInfo->shaders) { vkDestroyShaderModule(mDevice, shader, VKALLOC); @@ -405,4 +497,35 @@ VulkanRenderPrimitive::VulkanRenderPrimitive(VulkanResourceAllocator* resourceAl mResources.acquire(indexBuffer); } +using Bitmask = VulkanDescriptorSetLayout::Bitmask; + +Bitmask Bitmask::fromBackendLayout(descset::DescriptorSetLayout const& layout) { + Bitmask mask; + for (auto const& binding: layout.bindings) { + switch (binding.type) { + case descset::DescriptorType::UNIFORM_BUFFER: { + if (binding.flags == descset::DescriptorFlags::DYNAMIC_OFFSET) { + mask.dynamicUbo |= fromStageFlags(binding.stageFlags, + binding.binding); + } else { + mask.ubo |= fromStageFlags(binding.stageFlags, + binding.binding); + } + break; + } + case descset::DescriptorType::SAMPLER: { + mask.sampler |= fromStageFlags(binding.stageFlags, binding.binding); + break; + } + case descset::DescriptorType::INPUT_ATTACHMENT: { + mask.inputAttachment |= + fromStageFlags(binding.stageFlags, binding.binding); + break; + } + } + } + return mask; +} + + } // namespace filament::backend diff --git a/filament/backend/src/vulkan/VulkanHandles.h b/filament/backend/src/vulkan/VulkanHandles.h index f6158c77ae..e41112c125 100644 --- a/filament/backend/src/vulkan/VulkanHandles.h +++ b/filament/backend/src/vulkan/VulkanHandles.h @@ -21,35 +21,172 @@ #include "DriverBase.h" #include "VulkanBuffer.h" -#include "VulkanPipelineCache.h" #include "VulkanResources.h" #include "VulkanSwapChain.h" #include "VulkanTexture.h" +#include "VulkanUtility.h" #include "private/backend/SamplerGroup.h" +#include "utils/FixedCapacityVector.h" +#include "vulkan/vulkan_core.h" #include #include namespace filament::backend { +using namespace descset; + class VulkanTimestamps; +struct VulkanDescriptorSetLayout : public VulkanResource { + static constexpr uint8_t UNIQUE_DESCRIPTOR_SET_COUNT = 3; + + // The bitmask representation of a set layout. + struct Bitmask { + UniformBufferBitmask ubo = 0; // 4 bytes + UniformBufferBitmask dynamicUbo = 0; // 4 bytes + SamplerBitmask sampler = 0; // 8 bytes + InputAttachmentBitmask inputAttachment = 0; // 1 bytes + + // Because we're using this struct as hash key, must make it's 8-bytes aligned, with no + // unaccounted bytes. + uint8_t padding0 = 0; // 1 bytes + uint16_t padding1 = 0;// 2 bytes + uint32_t padding2 = 0;// 4 bytes + + bool operator==(Bitmask const& right) const { + return ubo == right.ubo && dynamicUbo == right.dynamicUbo && sampler == right.sampler && + inputAttachment == right.inputAttachment; + } + + static Bitmask fromBackendLayout(descset::DescriptorSetLayout const& layout); + }; + + // This is a convenience struct to quickly check layout compatibility in terms of descriptor set + // pools. + struct Count { + uint32_t ubo = 0; + uint32_t dynamicUbo = 0; + uint32_t sampler = 0; + uint32_t inputAttachment = 0; + + bool operator==(Count const& right) const noexcept { + return ubo == right.ubo && dynamicUbo == right.dynamicUbo && sampler == right.sampler && + inputAttachment == right.inputAttachment; + } + + static inline Count fromLayoutBitmask(Bitmask const& mask) { + return { + .ubo = countBits(collapseStages(mask.ubo)), + .dynamicUbo = countBits(collapseStages(mask.dynamicUbo)), + .sampler = countBits(collapseStages(mask.sampler)), + .inputAttachment = countBits(collapseStages(mask.inputAttachment)), + }; + } + + Count operator*(uint16_t mult) const noexcept { + // TODO: check for overflow. + + Count ret; + ret.ubo = ubo * mult; + ret.dynamicUbo = dynamicUbo * mult; + ret.sampler = sampler * mult; + ret.inputAttachment = inputAttachment * mult; + return ret; + } + }; + + static_assert(sizeof(Bitmask) % 8 == 0); + + explicit VulkanDescriptorSetLayout(VkDescriptorSetLayout layout, Bitmask const& bitmask) + : VulkanResource(VulkanResourceType::DESCRIPTOR_SET_LAYOUT), + vklayout(layout), + bitmask(bitmask), + bindings(getBindings(bitmask)), + count(Count::fromLayoutBitmask(bitmask)) {} + + VkDescriptorSetLayout const vklayout; + Bitmask const bitmask; + + // This is a convenience struct so that we don't have to iterate through all the bits of the + // bitmask (which correspondings to binding indices). + struct _Bindings { + utils::FixedCapacityVector const ubo; + utils::FixedCapacityVector const dynamicUbo; + utils::FixedCapacityVector const sampler; + utils::FixedCapacityVector const inputAttachment; + } bindings; + + Count const count; + +private: + + template + utils::FixedCapacityVector bits(MaskType mask) { + utils::FixedCapacityVector ret = + utils::FixedCapacityVector::with_capacity(countBits(mask)); + for (uint8_t i = 0; i < sizeof(mask) * 4; ++i) { + if (mask & (1 << i)) { + ret.push_back(i); + } + } + return ret; + } + + _Bindings getBindings(Bitmask const& bitmask) { + auto const uboCollapsed = collapseStages(bitmask.ubo); + auto const dynamicUboCollapsed = collapseStages(bitmask.dynamicUbo); + auto const samplerCollapsed = collapseStages(bitmask.sampler); + auto const inputAttachmentCollapsed = collapseStages(bitmask.inputAttachment); + return { + bits(uboCollapsed), + bits(dynamicUboCollapsed), + bits(samplerCollapsed), + bits(inputAttachmentCollapsed), + }; + } +}; + +using VulkanDescriptorSetLayoutList = std::array, + VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>; + +struct VulkanDescriptorSet : public VulkanResource { +public: + // Because we need to recycle descriptor sets not used, we allow for a callback that the "Pool" + // can use to repackage the vk handle. + using OnRecycle = std::function; + + VulkanDescriptorSet(VulkanResourceAllocator* allocator, + VkDescriptorSet rawSet, OnRecycle&& onRecycleFn) + : VulkanResource(VulkanResourceType::DESCRIPTOR_SET), + resources(allocator), + vkSet(rawSet), + mOnRecycleFn(std::move(onRecycleFn)) {} + + ~VulkanDescriptorSet() { + if (mOnRecycleFn) { + mOnRecycleFn(); + } + } + + // TODO: maybe change to fixed size for performance. + VulkanAcquireOnlyResourceManager resources; + VkDescriptorSet const vkSet; + +private: + OnRecycle mOnRecycleFn; +}; + +using VulkanDescriptorSetList = std::array, + VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>; + struct VulkanProgram : public HwProgram, VulkanResource { + using BindingList = CappedArray; + VulkanProgram(VkDevice device, Program const& builder) noexcept; - struct CustomSamplerInfo { - uint8_t groupIndex; - uint8_t samplerIndex; - ShaderStageFlags flags; - }; - using CustomSamplerInfoList = utils::FixedCapacityVector; - - // We allow custom descriptor of the samplers within shaders. This is needed if we want to use - // a program that exists only in the backend - for example, for shader-based bliting. - VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs, - CustomSamplerInfoList const& samplerInfo) noexcept; ~VulkanProgram(); inline VkShaderModule getVertexShader() const { @@ -58,39 +195,61 @@ struct VulkanProgram : public HwProgram, VulkanResource { inline VkShaderModule getFragmentShader() const { return mInfo->shaders[1]; } - inline VulkanPipelineCache::UsageFlags getUsage() const { return mInfo->usage; } - inline utils::FixedCapacityVector const& getBindingToSamplerIndex() const { return mInfo->bindingToSamplerIndex; } + inline UsageFlags getUsage() const { + return mInfo->usage; + } + + // Get a list of the sampler binding indices so that we don't have to loop through all possible + // samplers. + inline BindingList const& getBindings() const { return mInfo->bindings; } + + // TODO: this is currently not used. This will replace getLayoutDescriptionList below. + // inline descset::DescriptorSetLayout const& getLayoutDescription() const { + // return mInfo->layout; + // } + // In the usual case, we would have just one layout per program. But in the current setup, we + // have a set/layout for each descriptor type. This will be changed in the future. + using LayoutDescriptionList = std::array; + inline LayoutDescriptionList const& getLayoutDescriptionList() const { return mInfo->layouts; } + #if FVK_ENABLED_DEBUG_SAMPLER_NAME inline utils::FixedCapacityVector const& getBindingToName() const { return mInfo->bindingToName; } #endif -private: // TODO: handle compute shaders. // The expected order of shaders - from frontend to backend - is vertex, fragment, compute. static constexpr uint8_t const MAX_SHADER_MODULES = 2; +private: struct PipelineInfo { PipelineInfo() : bindingToSamplerIndex(MAX_SAMPLER_COUNT, 0xffff) #if FVK_ENABLED_DEBUG_SAMPLER_NAME , bindingToName(MAX_SAMPLER_COUNT, "") -#endif +#endif {} // This bitset maps to each of the sampler in the sampler groups associated with this // program, and whether each sampler is used in which shader (i.e. vert, frag, compute). - VulkanPipelineCache::UsageFlags usage; + UsageFlags usage; + + BindingList bindings; // We store the samplerGroupIndex as the top 8-bit and the index within each group as the lower 8-bit. utils::FixedCapacityVector bindingToSamplerIndex; VkShaderModule shaders[MAX_SHADER_MODULES] = { VK_NULL_HANDLE }; + // TODO: Use this instead of `layouts` after Filament-side Descriptor Set API is in place. + // descset::DescriptorSetLayout layout; + LayoutDescriptionList layouts; + #if FVK_ENABLED_DEBUG_SAMPLER_NAME // We store the sampler name mapped from binding index (only for debug purposes). utils::FixedCapacityVector bindingToName; diff --git a/filament/backend/src/vulkan/VulkanPipelineCache.cpp b/filament/backend/src/vulkan/VulkanPipelineCache.cpp index 2cb474d7bb..581d6e9bee 100644 --- a/filament/backend/src/vulkan/VulkanPipelineCache.cpp +++ b/filament/backend/src/vulkan/VulkanPipelineCache.cpp @@ -34,7 +34,7 @@ using namespace bluevk; namespace filament::backend { -static VkShaderStageFlags getShaderStageFlags(VulkanPipelineCache::UsageFlags key, uint16_t binding) { +static VkShaderStageFlags getShaderStageFlags(UsageFlags key, uint16_t binding) { // NOTE: if you modify this function, you also need to modify getUsageFlags. assert_invariant(binding < MAX_SAMPLER_COUNT); VkShaderStageFlags flags = 0; @@ -47,24 +47,7 @@ static VkShaderStageFlags getShaderStageFlags(VulkanPipelineCache::UsageFlags ke return flags; } -VulkanPipelineCache::UsageFlags -VulkanPipelineCache::getUsageFlags(uint16_t binding, ShaderStageFlags flags, UsageFlags src) { - // NOTE: if you modify this function, you also need to modify getShaderStageFlags. - assert_invariant(binding < MAX_SAMPLER_COUNT); - if (any(flags & ShaderStageFlags::VERTEX)) { - src.set(binding); - } - if (any(flags & ShaderStageFlags::FRAGMENT)) { - src.set(MAX_SAMPLER_COUNT + binding); - } - // TODO: add support for compute by extending SHADER_MODULE_COUNT and ensuring UsageFlags - // has 186 bits (MAX_SAMPLER_COUNT * 3) - // assert_invariant(!any(flags & ~(ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT))); - return src; -} - -VulkanPipelineCache::UsageFlags VulkanPipelineCache::disableUsageFlags(uint16_t binding, - UsageFlags src) { +UsageFlags VulkanPipelineCache::disableUsageFlags(uint16_t binding, UsageFlags src) { src.unset(binding); src.unset(MAX_SAMPLER_COUNT + binding); return src; diff --git a/filament/backend/src/vulkan/VulkanPipelineCache.h b/filament/backend/src/vulkan/VulkanPipelineCache.h index 9888c59fb8..10bc845eb3 100644 --- a/filament/backend/src/vulkan/VulkanPipelineCache.h +++ b/filament/backend/src/vulkan/VulkanPipelineCache.h @@ -89,8 +89,6 @@ public: VkSpecializationInfo* specializationInfos = nullptr; }; - using UsageFlags = utils::bitset128; - static UsageFlags getUsageFlags(uint16_t binding, ShaderStageFlags stages, UsageFlags src = {}); static UsageFlags disableUsageFlags(uint16_t binding, UsageFlags src); #pragma clang diagnostic push diff --git a/filament/backend/src/vulkan/VulkanResources.cpp b/filament/backend/src/vulkan/VulkanResources.cpp index a0bfd79435..deb3a3168e 100644 --- a/filament/backend/src/vulkan/VulkanResources.cpp +++ b/filament/backend/src/vulkan/VulkanResources.cpp @@ -17,7 +17,7 @@ #include "VulkanResources.h" #include "VulkanHandles.h" #include "VulkanResourceAllocator.h" -#include "caching/VulkanDescriptorSet.h" +#include "VulkanPipelineCache.h" namespace filament::backend { @@ -62,6 +62,9 @@ void deallocateResource(VulkanResourceAllocator* allocator, VulkanResourceType t case VulkanResourceType::RENDER_PRIMITIVE: allocator->destruct(Handle(id)); break; + case VulkanResourceType::DESCRIPTOR_SET_LAYOUT: + allocator->destruct(Handle(id)); + break; case VulkanResourceType::DESCRIPTOR_SET: allocator->destruct(Handle(id)); break; diff --git a/filament/backend/src/vulkan/VulkanResources.h b/filament/backend/src/vulkan/VulkanResources.h index dd40cbb3ab..cbfb70ab60 100644 --- a/filament/backend/src/vulkan/VulkanResources.h +++ b/filament/backend/src/vulkan/VulkanResources.h @@ -35,24 +35,25 @@ struct VulkanThreadSafeResource; // Subclasses of VulkanResource must provide this enum in their construction. enum class VulkanResourceType : uint8_t { - BUFFER_OBJECT, - INDEX_BUFFER, - PROGRAM, - RENDER_TARGET, - SAMPLER_GROUP, - SWAP_CHAIN, - RENDER_PRIMITIVE, - TEXTURE, - TIMER_QUERY, - VERTEX_BUFFER, - VERTEX_BUFFER_INFO, - DESCRIPTOR_SET, + BUFFER_OBJECT = 0, + INDEX_BUFFER = 1, + PROGRAM = 2, + RENDER_TARGET = 3, + SAMPLER_GROUP = 4, + SWAP_CHAIN = 5, + RENDER_PRIMITIVE = 6, + TEXTURE = 7, + TIMER_QUERY = 8, + VERTEX_BUFFER = 9, + VERTEX_BUFFER_INFO = 10, + DESCRIPTOR_SET_LAYOUT = 11, + DESCRIPTOR_SET = 12, // Below are resources that are managed manually (i.e. not ref counted). - FENCE, - HEAP_ALLOCATED, + FENCE = 13, + HEAP_ALLOCATED = 14, - END_TYPE, // A placeholder + END_TYPE = 15, // A placeholder }; #define IS_HEAP_ALLOC_TYPE(f) \ diff --git a/filament/backend/src/vulkan/VulkanUtility.cpp b/filament/backend/src/vulkan/VulkanUtility.cpp index 612acb20e5..4b60b92f4d 100644 --- a/filament/backend/src/vulkan/VulkanUtility.cpp +++ b/filament/backend/src/vulkan/VulkanUtility.cpp @@ -655,4 +655,6 @@ uint8_t reduceSampleCount(uint8_t sampleCount, VkSampleCountFlags mask) { return mostSignificantBit((sampleCount - 1) & mask); } +_BitCountHelper _BitCountHelper::BitCounter = {}; + } // namespace filament::backend diff --git a/filament/backend/src/vulkan/VulkanUtility.h b/filament/backend/src/vulkan/VulkanUtility.h index 1b2ddc2e26..a4b0c62c1f 100644 --- a/filament/backend/src/vulkan/VulkanUtility.h +++ b/filament/backend/src/vulkan/VulkanUtility.h @@ -90,6 +90,9 @@ utils::FixedCapacityVector enumerate( #undef EXPAND_ENUM_NO_ARGS #undef EXPAND_ENUM_ARGS +// Used across pipeline related classes. +using UsageFlags = utils::bitset128; + // Useful shorthands using VkFormatList = utils::FixedCapacityVector; @@ -122,14 +125,14 @@ public: clear(); } - inline const_iterator begin() { + inline const_iterator begin() const { if (mInd == 0) { return mArray.cend(); } return mArray.cbegin(); } - inline const_iterator end() { + inline const_iterator end() const { if (mInd > 0 && mInd < CAPACITY) { return mArray.begin() + mInd; } @@ -170,7 +173,7 @@ public: return mArray[ind]; } - inline size_t size() { + inline uint32_t size() const { return mInd; } @@ -181,9 +184,127 @@ private: } FixedSizeArray mArray; - size_t mInd = 0; + uint32_t mInd = 0; }; +// TODO: ok to remove once Filament-side API is complete +namespace descset { + +// Used to describe the descriptor binding in shader stages. We assume that the binding index does +// not exceed 31. We also assume that we have two shader stages - vertex and fragment. The below +// types and struct are used across VulkanDescriptorSet and VulkanProgram. +using UniformBufferBitmask = uint32_t; +using SamplerBitmask = uint64_t; + +// We only have at most one input attachment, so this bitmask exists only to make the code more +// general. +using InputAttachmentBitmask = uint8_t; + +constexpr UniformBufferBitmask UBO_VERTEX_STAGE = 0x1; +constexpr UniformBufferBitmask UBO_FRAGMENT_STAGE = (0x1ULL << (sizeof(UniformBufferBitmask) * 4)); +constexpr SamplerBitmask SAMPLER_VERTEX_STAGE = 0x1; +constexpr SamplerBitmask SAMPLER_FRAGMENT_STAGE = (0x1ULL << (sizeof(SamplerBitmask) * 4)); +constexpr InputAttachmentBitmask INPUT_ATTACHMENT_VERTEX_STAGE = 0x1; +constexpr InputAttachmentBitmask INPUT_ATTACHMENT_FRAGMENT_STAGE = + (0x1ULL << (sizeof(InputAttachmentBitmask) * 4)); + +template +static constexpr Bitmask getVertexStage() noexcept { + if constexpr (std::is_same_v) { + return UBO_VERTEX_STAGE; + } + if constexpr (std::is_same_v) { + return SAMPLER_VERTEX_STAGE; + } + if constexpr (std::is_same_v) { + return INPUT_ATTACHMENT_VERTEX_STAGE; + } +} + +template +static constexpr Bitmask getFragmentStage() noexcept { + if constexpr (std::is_same_v) { + return UBO_FRAGMENT_STAGE; + } + if constexpr (std::is_same_v) { + return SAMPLER_FRAGMENT_STAGE; + } + if constexpr (std::is_same_v) { + return INPUT_ATTACHMENT_FRAGMENT_STAGE; + } +} + +typedef enum ShaderStageFlags2 : uint8_t { + NONE = 0, + VERTEX = 0x1, + FRAGMENT = 0x2, +} ShaderStageFlags2; + +enum class DescriptorType : uint8_t { + UNIFORM_BUFFER, + SAMPLER, + INPUT_ATTACHMENT, +}; + +enum class DescriptorFlags : uint8_t { + NONE = 0x00, + DYNAMIC_OFFSET = 0x01 +}; + +struct DescriptorSetLayoutBinding { + DescriptorType type; + ShaderStageFlags2 stageFlags; + uint8_t binding; + DescriptorFlags flags; + uint16_t count; +}; + +struct DescriptorSetLayout { + utils::FixedCapacityVector bindings; +}; + +} // namespace descset + +// Use constexpr to statically generate a bit count table for 8-bit numbers. +struct _BitCountHelper { + constexpr _BitCountHelper() : data{} { + for (uint16_t i = 0; i < 256; ++i) { + data[i] = 0; + for (auto j = i; j > 0; j /= 2) { + if (j & 1) { + data[i]++; + } + } + } + } + + template + constexpr uint8_t count(MaskType num) { + uint8_t count = 0; + for (uint8_t i = 0; i < sizeof(MaskType) * 8; i+=8) { + count += data[(num >> i) & 0xFF]; + } + return count; + } + + static _BitCountHelper BitCounter; +private: + uint8_t data[256]; +}; + +template +inline uint8_t countBits(MaskType num) { + return _BitCountHelper::BitCounter.count(num); +} + +// This is useful for counting the total number of descriptors for both vertex and fragment stages. +template +inline MaskType collapseStages(MaskType mask) { + constexpr uint8_t NBITS_DIV_2 = sizeof(MaskType) * 4; + // First zero out the top-half and then or the bottom-half against the original top-half. + return ((mask << NBITS_DIV_2) >> NBITS_DIV_2) | (mask >> NBITS_DIV_2); +} + } // namespace filament::backend #endif // TNT_FILAMENT_BACKEND_VULKANUTILITY_H diff --git a/filament/backend/src/vulkan/caching/VulkanDescriptorSet.cpp b/filament/backend/src/vulkan/caching/VulkanDescriptorSet.cpp index 78ed4ae46e..21e8406395 100644 --- a/filament/backend/src/vulkan/caching/VulkanDescriptorSet.cpp +++ b/filament/backend/src/vulkan/caching/VulkanDescriptorSet.cpp @@ -44,27 +44,6 @@ PORT_CONSTANT(Program, SAMPLER_BINDING_COUNT) #undef PORT_CONSTANT -// Use constexpr to statically generate a bit count table for 8-bit numbers. -struct BitCountHelper { - constexpr BitCountHelper() : data{} { - for (uint16_t i = 0; i < 256; ++i) { - data[i] = 0; - for (auto j = i; j > 0; j /= 2) { - if (j & 1) { - data[i]++; - } - } - } - } - uint8_t data[256]; -} BitCountImpl; - -inline uint8_t countBits(uint32_t num) { - static constexpr uint8_t const* BIT_COUNT = BitCountImpl.data; - return BIT_COUNT[num & 0xFF] + BIT_COUNT[(num >> 8) & 0xFF] + BIT_COUNT[(num >> 16) & 0xFF] + - BIT_COUNT[(num >> 24) & 0xFF]; -} - struct SamplerSet { using Mask = UniformBufferBitmask; struct Key { @@ -202,26 +181,26 @@ public: private: inline VulkanDescriptorSet* createSet(VkDescriptorSetLayout layout, VkDescriptorSet vkSet) { - return VulkanDescriptorSet::create(mResourceAllocator, vkSet, layout, - [this](VulkanDescriptorSet* set) { - auto const layout = set->layout; - auto const vkSet = set->vkSet; - auto listIter = mUnused.find(layout); - assert_invariant(listIter != mUnused.end()); - auto& unusedList = listIter->second; + auto handle = mResourceAllocator->initHandle( + mResourceAllocator, vkSet, []() { + // auto const vkSet = set->vkSet; + // auto listIter = mUnused.find(layout); + // assert_invariant(listIter != mUnused.end()); + // auto& unusedList = listIter->second; - // At this point, we know that the pointer will be stale, and we need to remove - // any non-ref-counted references to it. - // TODO: this will be inefficient - auto iter = std::find(unusedList.begin(), unusedList.end(), set); - unusedList.erase(iter); + // // At this point, we know that the pointer will be stale, and we need to remove + // // any non-ref-counted references to it. + // // TODO: this will be inefficient + // auto iter = std::find(unusedList.begin(), unusedList.end(), set); + // unusedList.erase(iter); - // We are recycling - release the vk resource back into the pool. Note that the - // vk handle has not changed, but we need to change the backend handle to allow - // for proper refcounting of resources referenced in this set. - unusedList.push_back(createSet(layout, vkSet)); - mCount--; - }); + // // We are recycling - release the vk resource back into the pool. Note that the + // // vk handle has not changed, but we need to change the backend handle to allow + // // for proper refcounting of resources referenced in this set. + // unusedList.push_back(createSet(layout, vkSet)); + // mCount--; + }); + return mResourceAllocator->handle_cast(handle); } VkDevice mDevice; @@ -412,28 +391,6 @@ using SamplerSetLayoutCache = LayoutCache; } // anonymous namespace -VulkanDescriptorSet::VulkanDescriptorSet(VulkanResourceAllocator* allocator, VkDescriptorSet rawSet, - VkDescriptorSetLayout layout, OnRecycle&& onRecycleFn) - : VulkanResource(VulkanResourceType::DESCRIPTOR_SET), - resources(allocator), - vkSet(rawSet), - layout(layout), - mOnRecycleFn(std::move(onRecycleFn)) {} - -VulkanDescriptorSet::~VulkanDescriptorSet() { - if (mOnRecycleFn) { - mOnRecycleFn(this); - } -} - -VulkanDescriptorSet* VulkanDescriptorSet::create(VulkanResourceAllocator* allocator, - VkDescriptorSet rawSet, VkDescriptorSetLayout layout, OnRecycle&& onRecycleFn) { - auto handle = allocator->allocHandle(); - auto set = allocator->construct(handle, allocator, rawSet, layout, - std::move(onRecycleFn)); - return set; -} - class VulkanDescriptorSetManager::Impl { public: Impl(VkDevice device, VulkanResourceAllocator* allocator) diff --git a/filament/backend/src/vulkan/caching/VulkanDescriptorSet.h b/filament/backend/src/vulkan/caching/VulkanDescriptorSet.h index f19812ad7b..e787cb429f 100644 --- a/filament/backend/src/vulkan/caching/VulkanDescriptorSet.h +++ b/filament/backend/src/vulkan/caching/VulkanDescriptorSet.h @@ -32,31 +32,6 @@ namespace filament::backend { -// We need to make this class public to enable allocation on the HandleAllocator. -struct VulkanDescriptorSet : public VulkanResourceBase { -public: - // Because we need to recycle descriptor set not used, we allow for a callback that the "Pool" - // can use to repackage the vk handle. - using OnRecycle = std::function; - - VulkanDescriptorSet(VulkanResourceAllocator* allocator, VkDescriptorSet rawSet, - VkDescriptorSetLayout layout, OnRecycle&& onRecycleFn); - - ~VulkanDescriptorSet(); - - static VulkanDescriptorSet* create(VulkanResourceAllocator* allocator, VkDescriptorSet rawSet, - VkDescriptorSetLayout layout, OnRecycle&& onRecycleFn); - - // TODO: maybe change to fixed size for performance. - VulkanAcquireOnlyResourceManager resources; - - VkDescriptorSet const vkSet; - VkDescriptorSetLayout const layout; - -private: - OnRecycle mOnRecycleFn; -}; - // Abstraction over the pool and the layout cache. class VulkanDescriptorSetManager { public: diff --git a/filament/backend/src/vulkan/spirv/VulkanSpirvUtils.cpp b/filament/backend/src/vulkan/spirv/VulkanSpirvUtils.cpp index 3a84d1b639..893bf3439c 100644 --- a/filament/backend/src/vulkan/spirv/VulkanSpirvUtils.cpp +++ b/filament/backend/src/vulkan/spirv/VulkanSpirvUtils.cpp @@ -24,6 +24,7 @@ #include #include +#include namespace filament::backend { @@ -72,7 +73,7 @@ void workaroundSpecConstant(Program::ShaderBlob const& blob, std::vector& output) { using WordMap = std::unordered_map; using SpecValueMap = std::unordered_map; - constexpr size_t const HEADER_SIZE = 5; + constexpr size_t HEADER_SIZE = 5; WordMap varToIdMap; SpecValueMap idToValue; @@ -145,4 +146,61 @@ void workaroundSpecConstant(Program::ShaderBlob const& blob, output.resize(outputCursor); } +std::tuple getProgramBindings(Program::ShaderBlob const& blob) { + std::unordered_map targetToSet, targetToBinding; + + constexpr size_t HEADER_SIZE = 5; + + size_t const dataSize = blob.size() / 4; + uint32_t const* data = (uint32_t*) blob.data(); + + for (uint32_t cursor = HEADER_SIZE, cursorEnd = dataSize; cursor < cursorEnd;) { + uint32_t const firstWord = data[cursor]; + uint32_t const wordCount = firstWord >> 16; + uint32_t const op = firstWord & 0x0000FFFF; + + switch(op) { + case spv::Op::OpDecorate: { + if (data[cursor + 2] == spv::Decoration::DecorationDescriptorSet) { + uint32_t const targetVar = data[cursor + 1]; + uint32_t const setId = data[cursor + 3]; + targetToSet[targetVar] = setId; + } else if (data[cursor + 2] == spv::Decoration::DecorationBinding) { + uint32_t const targetVar = data[cursor + 1]; + uint32_t const binding = data[cursor + 3]; + targetToBinding[targetVar] = binding; + } + break; + } + default: + break; + } + cursor += wordCount; + } + + constexpr uint32_t UBO = 0; + constexpr uint32_t SAMPLER = 1; + constexpr uint32_t IATTACHMENT = 2; + uint32_t ubo = 0, sampler = 0, inputAttachment = 0; + + for (auto const [target, setId]: targetToSet) { + uint32_t const binding = targetToBinding[target]; + assert_invariant(binding < 32); + switch(setId) { + case UBO: + ubo |= (1 << binding); + break; + case SAMPLER: + sampler |= (1 << binding); + break; + case IATTACHMENT: + inputAttachment |= (1 << binding); + break; + default: + PANIC_POSTCONDITION("unexpected %d", (int) setId); + } + } + return {ubo, sampler, inputAttachment}; +} + } // namespace filament::backend diff --git a/filament/backend/src/vulkan/spirv/VulkanSpirvUtils.h b/filament/backend/src/vulkan/spirv/VulkanSpirvUtils.h index ebbb11af66..2a9f79a611 100644 --- a/filament/backend/src/vulkan/spirv/VulkanSpirvUtils.h +++ b/filament/backend/src/vulkan/spirv/VulkanSpirvUtils.h @@ -17,10 +17,11 @@ #ifndef TNT_FILAMENT_BACKEND_VULKANSPIRVUTILS_H #define TNT_FILAMENT_BACKEND_VULKANSPIRVUTILS_H -#include "backend/Program.h" +#include #include +#include #include namespace filament::backend { @@ -42,6 +43,9 @@ void workaroundSpecConstant(Program::ShaderBlob const& blob, utils::FixedCapacityVector const& specConstants, std::vector& output); +// bindings for UBO, samplers, input attachment +std::tuple getProgramBindings(Program::ShaderBlob const& blob); + } // namespace filament::backend #endif // TNT_FILAMENT_BACKEND_VULKANSPIRVUTILS_H