// // Copyright 2020 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // ProgramExecutableVk.h: Collects the information and interfaces common to both ProgramVks and // ProgramPipelineVks in order to execute/draw with either. #ifndef LIBANGLE_RENDERER_VULKAN_PROGRAMEXECUTABLEVK_H_ #define LIBANGLE_RENDERER_VULKAN_PROGRAMEXECUTABLEVK_H_ #include "common/bitset_utils.h" #include "common/mathutil.h" #include "common/utilities.h" #include "libANGLE/Context.h" #include "libANGLE/InfoLog.h" #include "libANGLE/ProgramExecutable.h" #include "libANGLE/renderer/ProgramExecutableImpl.h" #include "libANGLE/renderer/vulkan/ContextVk.h" #include "libANGLE/renderer/vulkan/ShaderInterfaceVariableInfoMap.h" #include "libANGLE/renderer/vulkan/spv_utils.h" #include "libANGLE/renderer/vulkan/vk_cache_utils.h" #include "libANGLE/renderer/vulkan/vk_helpers.h" namespace rx { class ShaderInfo final : angle::NonCopyable { public: ShaderInfo(); ~ShaderInfo(); angle::Result initShaders(vk::Context *context, const gl::ShaderBitSet &linkedShaderStages, const gl::ShaderMap &spirvBlobs, const ShaderInterfaceVariableInfoMap &variableInfoMap, bool isGLES1); void initShaderFromProgram(gl::ShaderType shaderType, const ShaderInfo &programShaderInfo); void clear(); ANGLE_INLINE bool valid() const { return mIsInitialized; } const gl::ShaderMap &getSpirvBlobs() const { return mSpirvBlobs; } // Save and load implementation for GLES Program Binary support. void load(gl::BinaryInputStream *stream); void save(gl::BinaryOutputStream *stream); private: gl::ShaderMap mSpirvBlobs; bool mIsInitialized = false; }; union ProgramTransformOptions final { struct { uint8_t surfaceRotation : 1; uint8_t removeTransformFeedbackEmulation : 1; uint8_t multiSampleFramebufferFetch : 1; uint8_t enableSampleShading : 1; uint8_t reserved : 4; // must initialize to zero }; uint8_t permutationIndex; static constexpr uint32_t kPermutationCount = 0x1 << 4; }; static_assert(sizeof(ProgramTransformOptions) == 1, "Size check failed"); static_assert(static_cast(SurfaceRotation::EnumCount) <= 8, "Size check failed"); class ProgramInfo final : angle::NonCopyable { public: ProgramInfo(); ~ProgramInfo(); angle::Result initProgram(vk::Context *context, gl::ShaderType shaderType, bool isLastPreFragmentStage, bool isTransformFeedbackProgram, const ShaderInfo &shaderInfo, ProgramTransformOptions optionBits, const ShaderInterfaceVariableInfoMap &variableInfoMap); void release(ContextVk *contextVk); ANGLE_INLINE bool valid(gl::ShaderType shaderType) const { return mProgramHelper.valid(shaderType); } vk::ShaderProgramHelper &getShaderProgram() { return mProgramHelper; } private: vk::ShaderProgramHelper mProgramHelper; vk::ShaderModuleMap mShaders; }; using ImmutableSamplerIndexMap = angle::HashMap; class ProgramExecutableVk : public ProgramExecutableImpl { public: ProgramExecutableVk(const gl::ProgramExecutable *executable); ~ProgramExecutableVk() override; void destroy(const gl::Context *context) override; void save(ContextVk *contextVk, bool isSeparable, gl::BinaryOutputStream *stream); angle::Result load(ContextVk *contextVk, bool isSeparable, gl::BinaryInputStream *stream, egl::CacheGetResult *resultOut); void setUniform1fv(GLint location, GLsizei count, const GLfloat *v) override; void setUniform2fv(GLint location, GLsizei count, const GLfloat *v) override; void setUniform3fv(GLint location, GLsizei count, const GLfloat *v) override; void setUniform4fv(GLint location, GLsizei count, const GLfloat *v) override; void setUniform1iv(GLint location, GLsizei count, const GLint *v) override; void setUniform2iv(GLint location, GLsizei count, const GLint *v) override; void setUniform3iv(GLint location, GLsizei count, const GLint *v) override; void setUniform4iv(GLint location, GLsizei count, const GLint *v) override; void setUniform1uiv(GLint location, GLsizei count, const GLuint *v) override; void setUniform2uiv(GLint location, GLsizei count, const GLuint *v) override; void setUniform3uiv(GLint location, GLsizei count, const GLuint *v) override; void setUniform4uiv(GLint location, GLsizei count, const GLuint *v) override; void setUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) override; void setUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) override; void setUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) override; void setUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) override; void setUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) override; void setUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) override; void setUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) override; void setUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) override; void setUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) override; void getUniformfv(const gl::Context *context, GLint location, GLfloat *params) const override; void getUniformiv(const gl::Context *context, GLint location, GLint *params) const override; void getUniformuiv(const gl::Context *context, GLint location, GLuint *params) const override; void clearVariableInfoMap(); vk::BufferSerial getCurrentDefaultUniformBufferSerial() const { return mCurrentDefaultUniformBufferSerial; } // Get the graphics pipeline if already created. angle::Result getGraphicsPipeline(ContextVk *contextVk, vk::GraphicsPipelineSubset pipelineSubset, const vk::GraphicsPipelineDesc &desc, const vk::GraphicsPipelineDesc **descPtrOut, vk::PipelineHelper **pipelineOut); angle::Result createGraphicsPipeline(ContextVk *contextVk, vk::GraphicsPipelineSubset pipelineSubset, vk::PipelineCacheAccess *pipelineCache, PipelineSource source, const vk::GraphicsPipelineDesc &desc, const vk::GraphicsPipelineDesc **descPtrOut, vk::PipelineHelper **pipelineOut); angle::Result linkGraphicsPipelineLibraries(ContextVk *contextVk, vk::PipelineCacheAccess *pipelineCache, const vk::GraphicsPipelineDesc &desc, vk::PipelineHelper *vertexInputPipeline, vk::PipelineHelper *shadersPipeline, vk::PipelineHelper *fragmentOutputPipeline, const vk::GraphicsPipelineDesc **descPtrOut, vk::PipelineHelper **pipelineOut); angle::Result getOrCreateComputePipeline(vk::Context *context, vk::PipelineCacheAccess *pipelineCache, PipelineSource source, vk::PipelineRobustness pipelineRobustness, vk::PipelineProtectedAccess pipelineProtectedAccess, vk::PipelineHelper **pipelineOut); const vk::PipelineLayout &getPipelineLayout() const { return *mPipelineLayout; } void resetLayout(ContextVk *contextVk); angle::Result createPipelineLayout(vk::Context *context, PipelineLayoutCache *pipelineLayoutCache, DescriptorSetLayoutCache *descriptorSetLayoutCache, gl::ActiveTextureArray *activeTextures); angle::Result initializeDescriptorPools( vk::Context *context, DescriptorSetLayoutCache *descriptorSetLayoutCache, vk::DescriptorSetArray *metaDescriptorPools); angle::Result updateTexturesDescriptorSet(vk::Context *context, uint32_t currentFrame, const gl::ActiveTextureArray &textures, const gl::SamplerBindingVector &samplers, PipelineType pipelineType, UpdateDescriptorSetsBuilder *updateBuilder); angle::Result updateShaderResourcesDescriptorSet( vk::Context *context, uint32_t currentFrame, UpdateDescriptorSetsBuilder *updateBuilder, const vk::WriteDescriptorDescs &writeDescriptorDescs, const vk::DescriptorSetDescBuilder &shaderResourcesDesc, vk::SharedDescriptorSetCacheKey *newSharedCacheKeyOut); angle::Result updateUniformsAndXfbDescriptorSet( vk::Context *context, uint32_t currentFrame, UpdateDescriptorSetsBuilder *updateBuilder, const vk::WriteDescriptorDescs &writeDescriptorDescs, vk::BufferHelper *defaultUniformBuffer, vk::DescriptorSetDescBuilder *uniformsAndXfbDesc, vk::SharedDescriptorSetCacheKey *sharedCacheKeyOut); template angle::Result bindDescriptorSets(vk::Context *context, uint32_t currentFrame, vk::CommandBufferHelperCommon *commandBufferHelper, CommandBufferT *commandBuffer, PipelineType pipelineType); bool usesDynamicUniformBufferDescriptors() const { return mUniformBufferDescriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; } VkDescriptorType getUniformBufferDescriptorType() const { return mUniformBufferDescriptorType; } bool usesDynamicShaderStorageBufferDescriptors() const { return false; } VkDescriptorType getStorageBufferDescriptorType() const { return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; } VkDescriptorType getAtomicCounterBufferDescriptorType() const { return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; } bool usesDynamicAtomicCounterBufferDescriptors() const { return false; } bool areImmutableSamplersCompatible( const ImmutableSamplerIndexMap &immutableSamplerIndexMap) const { return (mImmutableSamplerIndexMap == immutableSamplerIndexMap); } size_t getDefaultUniformAlignedSize(vk::Context *context, gl::ShaderType shaderType) const { vk::Renderer *renderer = context->getRenderer(); size_t alignment = static_cast( renderer->getPhysicalDeviceProperties().limits.minUniformBufferOffsetAlignment); return roundUp(mDefaultUniformBlocks[shaderType]->uniformData.size(), alignment); } std::shared_ptr &getSharedDefaultUniformBlock(gl::ShaderType shaderType) { return mDefaultUniformBlocks[shaderType]; } bool updateAndCheckDirtyUniforms() { if (ANGLE_LIKELY(!mExecutable->IsPPO())) { return mDefaultUniformBlocksDirty.any(); } const auto &ppoExecutables = mExecutable->getPPOProgramExecutables(); for (gl::ShaderType shaderType : mExecutable->getLinkedShaderStages()) { ProgramExecutableVk *executableVk = vk::GetImpl(ppoExecutables[shaderType].get()); if (executableVk->mDefaultUniformBlocksDirty.test(shaderType)) { mDefaultUniformBlocksDirty.set(shaderType); // Note: this relies on onProgramBind marking everything as dirty executableVk->mDefaultUniformBlocksDirty.reset(shaderType); } } return mDefaultUniformBlocksDirty.any(); } void setAllDefaultUniformsDirty(); angle::Result updateUniforms(vk::Context *context, uint32_t currentFrame, UpdateDescriptorSetsBuilder *updateBuilder, vk::BufferHelper *emptyBuffer, vk::DynamicBuffer *defaultUniformStorage, bool isTransformFeedbackActiveUnpaused, TransformFeedbackVk *transformFeedbackVk); void onProgramBind(); const ShaderInterfaceVariableInfoMap &getVariableInfoMap() const { return mVariableInfoMap; } angle::Result warmUpPipelineCache(vk::Renderer *renderer, vk::PipelineRobustness pipelineRobustness, vk::PipelineProtectedAccess pipelineProtectedAccess) { return getPipelineCacheWarmUpTasks(renderer, pipelineRobustness, pipelineProtectedAccess, nullptr); } angle::Result getPipelineCacheWarmUpTasks( vk::Renderer *renderer, vk::PipelineRobustness pipelineRobustness, vk::PipelineProtectedAccess pipelineProtectedAccess, std::vector> *postLinkSubTasksOut); void waitForPostLinkTasks(const gl::Context *context) override { ContextVk *contextVk = vk::GetImpl(context); waitForPostLinkTasksImpl(contextVk); } void waitForComputePostLinkTasks(ContextVk *contextVk) { ASSERT(mExecutable->hasLinkedShaderStage(gl::ShaderType::Compute)); waitForPostLinkTasksImpl(contextVk); } void waitForGraphicsPostLinkTasks(ContextVk *contextVk, const vk::GraphicsPipelineDesc ¤tGraphicsPipelineDesc); angle::Result mergePipelineCacheToRenderer(vk::Context *context) const; const vk::WriteDescriptorDescs &getShaderResourceWriteDescriptorDescs() const { return mShaderResourceWriteDescriptorDescs; } const vk::WriteDescriptorDescs &getDefaultUniformWriteDescriptorDescs( TransformFeedbackVk *transformFeedbackVk) const { return transformFeedbackVk == nullptr ? mDefaultUniformWriteDescriptorDescs : mDefaultUniformAndXfbWriteDescriptorDescs; } const vk::WriteDescriptorDescs &getTextureWriteDescriptorDescs() const { return mTextureWriteDescriptorDescs; } // The following functions are for internal use of programs, including from a threaded link job: angle::Result resizeUniformBlockMemory(vk::Context *context, const gl::ShaderMap &requiredBufferSize); void resolvePrecisionMismatch(const gl::ProgramMergedVaryings &mergedVaryings); angle::Result initShaders(vk::Context *context, const gl::ShaderBitSet &linkedShaderStages, const gl::ShaderMap &spirvBlobs, bool isGLES1) { return mOriginalShaderInfo.initShaders(context, linkedShaderStages, spirvBlobs, mVariableInfoMap, isGLES1); } void assignAllSpvLocations(vk::Context *context, const gl::ProgramState &programState, const gl::ProgramLinkedResources &resources) { SpvSourceOptions options = SpvCreateSourceOptions( context->getFeatures(), context->getRenderer()->getMaxColorInputAttachmentCount()); SpvAssignAllLocations(options, programState, resources, &mVariableInfoMap); } private: class WarmUpTaskCommon; class WarmUpComputeTask; class WarmUpGraphicsTask; friend class ProgramVk; friend class ProgramPipelineVk; void reset(ContextVk *contextVk); void addInterfaceBlockDescriptorSetDesc(const std::vector &blocks, gl::ShaderBitSet shaderTypes, VkDescriptorType descType, vk::DescriptorSetLayoutDesc *descOut); void addAtomicCounterBufferDescriptorSetDesc( const std::vector &atomicCounterBuffers, vk::DescriptorSetLayoutDesc *descOut); void addImageDescriptorSetDesc(vk::DescriptorSetLayoutDesc *descOut); void addInputAttachmentDescriptorSetDesc(vk::Context *context, vk::DescriptorSetLayoutDesc *descOut); angle::Result addTextureDescriptorSetDesc( vk::Context *context, const gl::ActiveTextureArray *activeTextures, vk::DescriptorSetLayoutDesc *descOut); size_t calcUniformUpdateRequiredSpace(vk::Context *context, gl::ShaderMap *uniformOffsets) const; ANGLE_INLINE angle::Result initProgram(vk::Context *context, gl::ShaderType shaderType, bool isLastPreFragmentStage, bool isTransformFeedbackProgram, ProgramTransformOptions optionBits, ProgramInfo *programInfo, const ShaderInterfaceVariableInfoMap &variableInfoMap) { ASSERT(mOriginalShaderInfo.valid()); // Create the program pipeline. This is done lazily and once per combination of // specialization constants. if (!programInfo->valid(shaderType)) { ANGLE_TRY(programInfo->initProgram(context, shaderType, isLastPreFragmentStage, isTransformFeedbackProgram, mOriginalShaderInfo, optionBits, variableInfoMap)); } ASSERT(programInfo->valid(shaderType)); return angle::Result::Continue; } ANGLE_INLINE angle::Result initGraphicsShaderProgram( vk::Context *context, gl::ShaderType shaderType, bool isLastPreFragmentStage, bool isTransformFeedbackProgram, ProgramTransformOptions optionBits, ProgramInfo *programInfo, const ShaderInterfaceVariableInfoMap &variableInfoMap) { mValidGraphicsPermutations.set(optionBits.permutationIndex); return initProgram(context, shaderType, isLastPreFragmentStage, isTransformFeedbackProgram, optionBits, programInfo, variableInfoMap); } ANGLE_INLINE angle::Result initComputeProgram( vk::Context *context, ProgramInfo *programInfo, const ShaderInterfaceVariableInfoMap &variableInfoMap, const vk::ComputePipelineOptions &pipelineOptions) { mValidComputePermutations.set(pipelineOptions.permutationIndex); ProgramTransformOptions optionBits = {}; return initProgram(context, gl::ShaderType::Compute, false, false, optionBits, programInfo, variableInfoMap); } ProgramTransformOptions getTransformOptions(ContextVk *contextVk, const vk::GraphicsPipelineDesc &desc); angle::Result initGraphicsShaderPrograms(vk::Context *context, ProgramTransformOptions transformOptions); angle::Result initProgramThenCreateGraphicsPipeline(vk::Context *context, ProgramTransformOptions transformOptions, vk::GraphicsPipelineSubset pipelineSubset, vk::PipelineCacheAccess *pipelineCache, PipelineSource source, const vk::GraphicsPipelineDesc &desc, const vk::RenderPass &compatibleRenderPass, const vk::GraphicsPipelineDesc **descPtrOut, vk::PipelineHelper **pipelineOut); angle::Result createGraphicsPipelineImpl(vk::Context *context, ProgramTransformOptions transformOptions, vk::GraphicsPipelineSubset pipelineSubset, vk::PipelineCacheAccess *pipelineCache, PipelineSource source, const vk::GraphicsPipelineDesc &desc, const vk::RenderPass &compatibleRenderPass, const vk::GraphicsPipelineDesc **descPtrOut, vk::PipelineHelper **pipelineOut); angle::Result prepareForWarmUpPipelineCache( vk::Context *context, vk::PipelineRobustness pipelineRobustness, vk::PipelineProtectedAccess pipelineProtectedAccess, vk::GraphicsPipelineSubset subset, bool *isComputeOut, angle::FixedVector *surfaceRotationVariationsOut, vk::GraphicsPipelineDesc **graphicsPipelineDescOut, vk::RenderPass *renderPassOut); angle::Result warmUpComputePipelineCache(vk::Context *context, vk::PipelineRobustness pipelineRobustness, vk::PipelineProtectedAccess pipelineProtectedAccess); angle::Result warmUpGraphicsPipelineCache(vk::Context *context, vk::PipelineRobustness pipelineRobustness, vk::PipelineProtectedAccess pipelineProtectedAccess, vk::GraphicsPipelineSubset subset, const bool isSurfaceRotated, const vk::GraphicsPipelineDesc &graphicsPipelineDesc, const vk::RenderPass &renderPass, vk::PipelineHelper *placeholderPipelineHelper); void waitForPostLinkTasksImpl(ContextVk *contextVk); angle::Result getOrAllocateDescriptorSet(vk::Context *context, uint32_t currentFrame, UpdateDescriptorSetsBuilder *updateBuilder, const vk::DescriptorSetDescBuilder &descriptorSetDesc, const vk::WriteDescriptorDescs &writeDescriptorDescs, DescriptorSetIndex setIndex, vk::SharedDescriptorSetCacheKey *newSharedCacheKeyOut); // When loading from cache / binary, initialize the pipeline cache with given data. Otherwise // the cache is lazily created as needed. angle::Result initializePipelineCache(vk::Context *context, bool compressed, const std::vector &pipelineData); angle::Result ensurePipelineCacheInitialized(vk::Context *context); void initializeWriteDescriptorDesc(vk::Context *context); // Descriptor sets and pools for shader resources for this program. vk::DescriptorSetArray mDescriptorSets; vk::DescriptorSetArray mDynamicDescriptorPools; vk::BufferSerial mCurrentDefaultUniformBufferSerial; // We keep a reference to the pipeline and descriptor set layouts. This ensures they don't get // deleted while this program is in use. uint32_t mImmutableSamplersMaxDescriptorCount; ImmutableSamplerIndexMap mImmutableSamplerIndexMap; vk::PipelineLayoutPtr mPipelineLayout; vk::DescriptorSetLayoutPointerArray mDescriptorSetLayouts; // A set of dynamic offsets used with vkCmdBindDescriptorSets for the default uniform buffers. VkDescriptorType mUniformBufferDescriptorType; gl::ShaderVector mDynamicUniformDescriptorOffsets; std::vector mDynamicShaderResourceDescriptorOffsets; ShaderInterfaceVariableInfoMap mVariableInfoMap; static_assert((ProgramTransformOptions::kPermutationCount == 16), "ProgramTransformOptions::kPermutationCount must be 16."); angle::BitSet16 mValidGraphicsPermutations; static_assert((vk::ComputePipelineOptions::kPermutationCount == 4), "ComputePipelineOptions::kPermutationCount must be 4."); angle::BitSet8 mValidComputePermutations; // We store all permutations of surface rotation and transformed SPIR-V programs here. We may // need some LRU algorithm to free least used programs to reduce the number of programs. ProgramInfo mGraphicsProgramInfos[ProgramTransformOptions::kPermutationCount]; ProgramInfo mComputeProgramInfo; // Pipeline caches. The pipelines are tightly coupled with the shaders they are created for, so // they live in the program executable. With VK_EXT_graphics_pipeline_library, the pipeline is // divided in subsets; the "shaders" subset is created based on the shaders, so its cache lives // in the program executable. The "vertex input" and "fragment output" pipelines are // independent, and live in the context. CompleteGraphicsPipelineCache mCompleteGraphicsPipelines[ProgramTransformOptions::kPermutationCount]; ShadersGraphicsPipelineCache mShadersGraphicsPipelines[ProgramTransformOptions::kPermutationCount]; vk::ComputePipelineCache mComputePipelines; DefaultUniformBlockMap mDefaultUniformBlocks; gl::ShaderBitSet mDefaultUniformBlocksDirty; ShaderInfo mOriginalShaderInfo; // The pipeline cache specific to this program executable. Currently: // // - This is used during warm up (at link time) // - The contents are merged to Renderer's pipeline cache immediately after warm up // - The contents are returned as part of program binary // - Draw-time pipeline creation uses Renderer's cache // // Without VK_EXT_graphics_pipeline_library, this cache is not used for draw-time pipeline // creations to allow reuse of other blobs that are independent of the actual shaders; vertex // input fetch, fragment output and blend. // // With VK_EXT_graphics_pipeline_library, this cache is used for the "shaders" subset of the // pipeline. vk::PipelineCache mPipelineCache; vk::GraphicsPipelineDesc mWarmUpGraphicsPipelineDesc; // The "layout" information for descriptorSets vk::WriteDescriptorDescs mShaderResourceWriteDescriptorDescs; vk::WriteDescriptorDescs mTextureWriteDescriptorDescs; vk::WriteDescriptorDescs mDefaultUniformWriteDescriptorDescs; vk::WriteDescriptorDescs mDefaultUniformAndXfbWriteDescriptorDescs; vk::DescriptorSetLayoutDesc mShaderResourceSetDesc; vk::DescriptorSetLayoutDesc mTextureSetDesc; vk::DescriptorSetLayoutDesc mDefaultUniformAndXfbSetDesc; }; } // namespace rx #endif // LIBANGLE_RENDERER_VULKAN_PROGRAMEXECUTABLEVK_H_