From 2cf86454bb3b4494a653fa1afc865df45742ff15 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Tue, 3 Oct 2023 10:49:45 -0700 Subject: [PATCH] vk: refactor VulkanProgram (#7221) --- filament/backend/src/vulkan/VulkanBlitter.cpp | 9 +- filament/backend/src/vulkan/VulkanDriver.cpp | 101 +++++++------- filament/backend/src/vulkan/VulkanHandles.cpp | 130 ++++++++++-------- filament/backend/src/vulkan/VulkanHandles.h | 58 +++++++- .../src/vulkan/VulkanPipelineCache.cpp | 17 ++- .../backend/src/vulkan/VulkanPipelineCache.h | 5 +- 6 files changed, 195 insertions(+), 125 deletions(-) diff --git a/filament/backend/src/vulkan/VulkanBlitter.cpp b/filament/backend/src/vulkan/VulkanBlitter.cpp index 9f20b11ac8c..bfda73fa326 100644 --- a/filament/backend/src/vulkan/VulkanBlitter.cpp +++ b/filament/backend/src/vulkan/VulkanBlitter.cpp @@ -237,11 +237,12 @@ void VulkanBlitter::lazyInit() noexcept { VkShaderModule vertexShader = decode(VKSHADERS_BLITDEPTHVS_DATA, VKSHADERS_BLITDEPTHVS_SIZE); VkShaderModule fragmentShader = decode(VKSHADERS_BLITDEPTHFS_DATA, VKSHADERS_BLITDEPTHFS_SIZE); - mDepthResolveProgram = new VulkanProgram(mDevice, vertexShader, fragmentShader); // Allocate one anonymous sampler at slot 0. - mDepthResolveProgram->samplerGroupInfo[0].samplers.reserve(1); - mDepthResolveProgram->samplerGroupInfo[0].samplers.resize(1); + VulkanProgram::CustomSamplerInfoList samplers = { + {0, 0, ShaderStageFlags::FRAGMENT}, + }; + mDepthResolveProgram = new VulkanProgram(mDevice, vertexShader, fragmentShader, samplers); #if FVK_ENABLED(FVK_DEBUG_BLITTER) utils::slog.d << "Created Shader Module for VulkanBlitter " @@ -359,7 +360,7 @@ void VulkanBlitter::blitSlowDepth(VkFilter filter, const VkExtent2D srcExtent, V // DRAW THE TRIANGLE // ----------------- - mPipelineCache.bindProgram(*mDepthResolveProgram); + mPipelineCache.bindProgram(mDepthResolveProgram); mPipelineCache.bindPrimitiveTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP); auto vkraster = mPipelineCache.getCurrentRasterState(); diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index 217aab56905..b8381330e44 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -1548,7 +1548,7 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle r VkDeviceSize const* offsets = prim.vertexBuffer->getOffsets(); // Push state changes to the VulkanPipelineCache instance. This is fast and does not make VK calls. - mPipelineCache.bindProgram(*program); + mPipelineCache.bindProgram(program); mPipelineCache.bindRasterState(mPipelineCache.getCurrentRasterState()); mPipelineCache.bindPrimitiveTopology(prim.primitiveTopology); mPipelineCache.bindVertexArray(attribDesc, bufferDesc, bufferCount); @@ -1560,68 +1560,69 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle r VkDescriptorImageInfo samplerInfo[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {}; VulkanTexture* samplerTextures[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {nullptr}; - VulkanPipelineCache::UsageFlags usage; + auto const& bindingToSamplerIndex = program->getBindingToSamplerIndex(); + VulkanPipelineCache::UsageFlags usage = program->getUsage(); UTILS_NOUNROLL - for (uint8_t samplerGroupIdx = 0; samplerGroupIdx < Program::SAMPLER_BINDING_COUNT; samplerGroupIdx++) { - const auto& samplerGroup = program->samplerGroupInfo[samplerGroupIdx]; - const auto& samplers = samplerGroup.samplers; - if (samplers.empty()) { + for (uint8_t binding = 0; binding < VulkanPipelineCache::SAMPLER_BINDING_COUNT; binding++) { + uint16_t const indexPair = bindingToSamplerIndex[binding]; + + if (indexPair == 0xffff) { + usage = VulkanPipelineCache::disableUsageFlags(binding, usage); continue; } - VulkanSamplerGroup* vksb = mSamplerBindings[samplerGroupIdx]; + + uint16_t const samplerGroupInd = (indexPair >> 8) & 0xff; + uint16_t const samplerInd = (indexPair & 0xff); + + VulkanSamplerGroup* vksb = mSamplerBindings[samplerGroupInd]; if (!vksb) { + usage = VulkanPipelineCache::disableUsageFlags(binding, usage); continue; } - SamplerGroup* sb = vksb->sb.get(); - assert_invariant(sb->getSize() == samplers.size()); - size_t samplerIdx = 0; - for (auto& sampler : samplers) { - const SamplerDescriptor* boundSampler = sb->data() + samplerIdx; - samplerIdx++; + SamplerDescriptor const* boundSampler = ((SamplerDescriptor*) vksb->sb->data()) + samplerInd; - if (UTILS_LIKELY(boundSampler->t)) { - VulkanTexture* texture = mResourceAllocator.handle_cast(boundSampler->t); - VkImageViewType const expectedType = texture->getViewType(); + if (UTILS_UNLIKELY(!boundSampler->t)) { + usage = VulkanPipelineCache::disableUsageFlags(binding, usage); + continue; + } + + VulkanTexture* texture = mResourceAllocator.handle_cast(boundSampler->t); + VkImageViewType const expectedType = texture->getViewType(); - // TODO: can this uninitialized check be checked in a higher layer? - // This fallback path is very flaky because the dummy texture might not have - // matching characteristics. (e.g. if the missing texture is a 3D texture) - if (UTILS_UNLIKELY(texture->getPrimaryImageLayout() == VulkanLayout::UNDEFINED)) { + // TODO: can this uninitialized check be checked in a higher layer? + // This fallback path is very flaky because the dummy texture might not have + // matching characteristics. (e.g. if the missing texture is a 3D texture) + if (UTILS_UNLIKELY(texture->getPrimaryImageLayout() == VulkanLayout::UNDEFINED)) { #if FVK_ENABLED(FVK_DEBUG_TEXTURE) - utils::slog.w << "Uninitialized texture bound to '" << sampler.name.c_str() << "'"; - utils::slog.w << " in material '" << program->name.c_str() << "'"; - utils::slog.w << " at binding point " << +sampler.binding << utils::io::endl; + utils::slog.w << "Uninitialized texture bound to '" << sampler.name.c_str() << "'"; + utils::slog.w << " in material '" << program->name.c_str() << "'"; + utils::slog.w << " at binding point " << +sampler.binding << utils::io::endl; #endif - texture = mEmptyTexture.get(); - } - - const SamplerParams& samplerParams = boundSampler->s; - VkSampler vksampler = mSamplerCache.getSampler(samplerParams); - - usage = VulkanPipelineCache::getUsageFlags(sampler.binding, samplerGroup.stageFlags, usage); - - VkImageView imageView = VK_NULL_HANDLE; - VkImageSubresourceRange const range = texture->getPrimaryViewRange(); - if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) - && expectedType == VK_IMAGE_VIEW_TYPE_2D) { - // If the sampler is part of a mipmapped depth texture, where one of the level - // *can* be an attachment, then the sampler for this texture has the same view - // properties as a view for an attachment. Therefore, we can use - // getAttachmentView to get a corresponding VkImageView. - imageView = texture->getAttachmentView(range); - } else { - imageView = texture->getViewForType(range, expectedType); - } + texture = mEmptyTexture.get(); + } - samplerInfo[sampler.binding] = { - .sampler = vksampler, - .imageView = imageView, - .imageLayout = ImgUtil::getVkLayout(texture->getPrimaryImageLayout()) - }; - samplerTextures[sampler.binding] = texture; - } + SamplerParams const& samplerParams = boundSampler->s; + VkSampler const vksampler = mSamplerCache.getSampler(samplerParams); + VkImageView imageView = VK_NULL_HANDLE; + VkImageSubresourceRange const range = texture->getPrimaryViewRange(); + if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) && + expectedType == VK_IMAGE_VIEW_TYPE_2D) { + // If the sampler is part of a mipmapped depth texture, where one of the level *can* be + // an attachment, then the sampler for this texture has the same view properties as a + // view for an attachment. Therefore, we can use getAttachmentView to get a + // corresponding VkImageView. + imageView = texture->getAttachmentView(range); + } else { + imageView = texture->getViewForType(range, expectedType); } + + samplerInfo[binding] = { + .sampler = vksampler, + .imageView = imageView, + .imageLayout = ImgUtil::getVkLayout(texture->getPrimaryImageLayout()) + }; + samplerTextures[binding] = texture; } mPipelineCache.bindSamplers(samplerInfo, samplerTextures, usage); diff --git a/filament/backend/src/vulkan/VulkanHandles.cpp b/filament/backend/src/vulkan/VulkanHandles.cpp index 963ca70f0f1..ac6b4ccea17 100644 --- a/filament/backend/src/vulkan/VulkanHandles.cpp +++ b/filament/backend/src/vulkan/VulkanHandles.cpp @@ -49,88 +49,100 @@ static void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeig VulkanProgram::VulkanProgram(VkDevice device, const Program& builder) noexcept : HwProgram(builder.getName()), VulkanResource(VulkanResourceType::PROGRAM), + mInfo(new PipelineInfo(builder.getSpecializationConstants().size())), mDevice(device) { - auto const& blobs = builder.getShadersSource(); - VkShaderModule* modules[2] = {&bundle.vertex, &bundle.fragment}; - // TODO: handle compute shaders. - for (size_t i = 0; i < 2; i++) { + auto& blobs = builder.getShadersSource(); + auto& modules = mInfo->shaders; + for (size_t i = 0; i < MAX_SHADER_MODULES; i++) { const auto& blob = blobs[i]; - VkShaderModule* module = modules[i]; - VkShaderModuleCreateInfo moduleInfo = {}; - moduleInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - moduleInfo.codeSize = blob.size(); - moduleInfo.pCode = (uint32_t*) blob.data(); - VkResult result = vkCreateShaderModule(mDevice, &moduleInfo, VKALLOC, module); + uint32_t* data = (uint32_t*)blob.data(); + VkShaderModule& module = modules[i]; + VkShaderModuleCreateInfo moduleInfo = { + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .codeSize = blob.size(), + .pCode = data, + }; + VkResult result = vkCreateShaderModule(mDevice, &moduleInfo, VKALLOC, &module); ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to create shader module."); } + // Note that bools are 4-bytes in Vulkan + // https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkBool32.html + constexpr uint32_t const CONSTANT_SIZE = 4; + // populate the specialization constants requirements right now auto const& specializationConstants = builder.getSpecializationConstants(); - if (!specializationConstants.empty()) { - // Allocate a single heap block to store all the specialization constants structures - // our supported types are int32, float and bool, so we use 4 bytes per data. bool will - // just use the first byte. - char* pStorage = (char*)malloc( - sizeof(VkSpecializationInfo) + - specializationConstants.size() * sizeof(VkSpecializationMapEntry) + - specializationConstants.size() * 4); - - VkSpecializationInfo* const pInfo = (VkSpecializationInfo*)pStorage; - VkSpecializationMapEntry* const pEntries = - (VkSpecializationMapEntry*)(pStorage + sizeof(VkSpecializationInfo)); - void* pData = pStorage + sizeof(VkSpecializationInfo) + - specializationConstants.size() * sizeof(VkSpecializationMapEntry); - - *pInfo = { - .mapEntryCount = specializationConstants.size(), - .pMapEntries = pEntries, - .dataSize = specializationConstants.size() * 4, - .pData = pData, + uint32_t const specConstCount = static_cast(specializationConstants.size()); + char* specData = mInfo->specConstData.get(); + if (specConstCount > 0) { + mInfo->specializationInfo = { + .mapEntryCount = specConstCount, + .pMapEntries = mInfo->specConsts.data(), + .dataSize = specConstCount * CONSTANT_SIZE, + .pData = specData, }; + } + for (uint32_t i = 0; i < specConstCount; ++i) { + uint32_t const offset = i * CONSTANT_SIZE; + mInfo->specConsts[i] = { + .constantID = specializationConstants[i].id, + .offset = offset, + .size = CONSTANT_SIZE, + }; + using SpecConstant = Program::SpecializationConstant::Type; + char const* addr = (char*)specData + offset; + SpecConstant const& arg = specializationConstants[i].value; + if (std::holds_alternative(arg)) { + *((VkBool32*)addr) = std::get(arg) ? VK_TRUE : VK_FALSE; + } else if (std::holds_alternative(arg)) { + *((float*)addr) = std::get(arg); + } else { + *((int32_t*)addr) = std::get(arg); + } + } - for (size_t i = 0; i < specializationConstants.size(); i++) { - uint32_t const offset = uint32_t(i) * 4; - pEntries[i] = { - .constantID = specializationConstants[i].id, - .offset = offset, - // Note that bools are 4-bytes in Vulkan - // https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkBool32.html - .size = 4, - }; - - using SpecConstant = Program::SpecializationConstant::Type; - char const* addr = (char*)pData + offset; - SpecConstant const& arg = specializationConstants[i].value; - if (std::holds_alternative(arg)) { - *((VkBool32*)addr) = std::get(arg) ? VK_TRUE : VK_FALSE; - } else if (std::holds_alternative(arg)) { - *((float*)addr) = std::get(arg); - } else { - *((int32_t*)addr) = std::get(arg); - } + auto& groupInfo = builder.getSamplerGroupInfo(); + auto& bindingToSamplerIndex = mInfo->bindingToSamplerIndex; + auto& usage = mInfo->usage; + for (uint8_t groupInd = 0; groupInd < Program::SAMPLER_BINDING_COUNT; groupInd++) { + auto const& group = groupInfo[groupInd]; + auto const& samplers = group.samplers; + 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); } - bundle.specializationInfos = pInfo; } - // Make a copy of the binding map - samplerGroupInfo = builder.getSamplerGroupInfo(); #if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE) utils::slog.d << "Created VulkanProgram " << builder << ", shaders = (" << bundle.vertex << ", " << bundle.fragment << ")" << utils::io::endl; #endif } -VulkanProgram::VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs) noexcept +VulkanProgram::VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs, + CustomSamplerInfoList const& samplerInfo) noexcept : VulkanResource(VulkanResourceType::PROGRAM), + mInfo(new PipelineInfo(0)), mDevice(device) { - bundle.vertex = vs; - bundle.fragment = fs; + 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() { - vkDestroyShaderModule(mDevice, bundle.vertex, VKALLOC); - vkDestroyShaderModule(mDevice, bundle.fragment, VKALLOC); - free(bundle.specializationInfos); + for (auto shader: mInfo->shaders) { + vkDestroyShaderModule(mDevice, shader, VKALLOC); + } + delete mInfo; } // Creates a special "default" render target (i.e. associated with the swap chain) diff --git a/filament/backend/src/vulkan/VulkanHandles.h b/filament/backend/src/vulkan/VulkanHandles.h index 223a13dbd60..f60d73ca550 100644 --- a/filament/backend/src/vulkan/VulkanHandles.h +++ b/filament/backend/src/vulkan/VulkanHandles.h @@ -37,14 +37,64 @@ namespace filament::backend { class VulkanTimestamps; struct VulkanProgram : public HwProgram, VulkanResource { + VulkanProgram(VkDevice device, const Program& builder) noexcept; - VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs) 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(); - VulkanPipelineCache::ProgramBundle bundle; - Program::SamplerGroupInfo samplerGroupInfo; + + inline VkShaderModule getVertexShader() const { + return mInfo->shaders[0]; + } + + 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 VkSpecializationInfo const& getSpecConstInfo() const { + return mInfo->specializationInfo; + } private: - VkDevice mDevice; + // TODO: handle compute shaders. + // The expected order of shaders - from frontend to backend - is vertex, fragment, compute. + static constexpr uint8_t MAX_SHADER_MODULES = 2; + + struct PipelineInfo { + PipelineInfo(size_t specConstsCount) : + bindingToSamplerIndex(MAX_SAMPLER_COUNT, 0xffff), + specConsts(specConstsCount, VkSpecializationMapEntry{}), + specConstData(new char[specConstsCount * 4]) + {} + + // 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; + + // 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}; + VkSpecializationInfo specializationInfo = {}; + utils::FixedCapacityVector specConsts; + std::unique_ptr specConstData; + }; + + PipelineInfo* mInfo; + VkDevice mDevice = VK_NULL_HANDLE; }; // The render target bundles together a set of attachments, each of which can have one of the diff --git a/filament/backend/src/vulkan/VulkanPipelineCache.cpp b/filament/backend/src/vulkan/VulkanPipelineCache.cpp index fb0ef037c4c..000f8c755c3 100644 --- a/filament/backend/src/vulkan/VulkanPipelineCache.cpp +++ b/filament/backend/src/vulkan/VulkanPipelineCache.cpp @@ -65,6 +65,13 @@ VulkanPipelineCache::getUsageFlags(uint16_t binding, ShaderStageFlags flags, Usa return src; } +VulkanPipelineCache::UsageFlags VulkanPipelineCache::disableUsageFlags(uint16_t binding, + UsageFlags src) { + src.unset(binding); + src.unset(MAX_SAMPLER_COUNT + binding); + return src; +} + VulkanPipelineCache::VulkanPipelineCache(VulkanResourceAllocator* allocator) : mCurrentRasterState(createDefaultRasterState()), mResourceAllocator(allocator), @@ -558,12 +565,10 @@ VulkanPipelineCache::PipelineLayoutCacheEntry* VulkanPipelineCache::getOrCreateP return &mPipelineLayouts.emplace(mPipelineRequirements.layout, cacheEntry).first.value(); } -void VulkanPipelineCache::bindProgram(const VulkanProgram& program) noexcept { - const VkShaderModule shaders[2] = { program.bundle.vertex, program.bundle.fragment }; - for (uint32_t ssi = 0; ssi < SHADER_MODULE_COUNT; ssi++) { - mPipelineRequirements.shaders[ssi] = shaders[ssi]; - } - mSpecializationRequirements = program.bundle.specializationInfos; +void VulkanPipelineCache::bindProgram(VulkanProgram* program) noexcept { + mPipelineRequirements.shaders[0] = program->getVertexShader(); + mPipelineRequirements.shaders[1] = program->getFragmentShader(); + mSpecializationRequirements = &program->getSpecConstInfo(); } void VulkanPipelineCache::bindRasterState(const RasterState& rasterState) noexcept { diff --git a/filament/backend/src/vulkan/VulkanPipelineCache.h b/filament/backend/src/vulkan/VulkanPipelineCache.h index b7248420f8e..0e092e9555f 100644 --- a/filament/backend/src/vulkan/VulkanPipelineCache.h +++ b/filament/backend/src/vulkan/VulkanPipelineCache.h @@ -91,6 +91,7 @@ class VulkanPipelineCache : public CommandBufferObserver { 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 #pragma clang diagnostic warning "-Wpadded" @@ -150,7 +151,7 @@ class VulkanPipelineCache : public CommandBufferObserver { void bindScissor(VkCommandBuffer cmdbuffer, VkRect2D scissor) noexcept; // Each of the following methods are fast and do not make Vulkan calls. - void bindProgram(const VulkanProgram& program) noexcept; + void bindProgram(VulkanProgram* program) noexcept; void bindRasterState(const RasterState& rasterState) noexcept; void bindRenderPass(VkRenderPass renderPass, int subpassIndex) noexcept; void bindPrimitiveTopology(VkPrimitiveTopology topology) noexcept; @@ -415,7 +416,7 @@ class VulkanPipelineCache : public CommandBufferObserver { RasterState mCurrentRasterState; PipelineKey mPipelineRequirements = {}; DescriptorKey mDescriptorRequirements = {}; - VkSpecializationInfo* mSpecializationRequirements = {}; + VkSpecializationInfo const* mSpecializationRequirements = nullptr; // Current bindings for the pipeline and descriptor sets. PipelineKey mBoundPipeline = {};