diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /gfx/angle/checkout/src/libANGLE/ProgramLinkedResources.cpp | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/angle/checkout/src/libANGLE/ProgramLinkedResources.cpp')
-rw-r--r-- | gfx/angle/checkout/src/libANGLE/ProgramLinkedResources.cpp | 2377 |
1 files changed, 2377 insertions, 0 deletions
diff --git a/gfx/angle/checkout/src/libANGLE/ProgramLinkedResources.cpp b/gfx/angle/checkout/src/libANGLE/ProgramLinkedResources.cpp new file mode 100644 index 0000000000..11000fdabc --- /dev/null +++ b/gfx/angle/checkout/src/libANGLE/ProgramLinkedResources.cpp @@ -0,0 +1,2377 @@ +// +// Copyright 2017 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. +// + +// ProgramLinkedResources.cpp: implements link-time checks for default block uniforms, and generates +// uniform locations. Populates data structures related to uniforms so that they can be stored in +// program state. + +#include "libANGLE/ProgramLinkedResources.h" + +#include "common/string_utils.h" +#include "common/utilities.h" +#include "libANGLE/Caps.h" +#include "libANGLE/Context.h" +#include "libANGLE/Shader.h" +#include "libANGLE/features.h" + +namespace gl +{ +namespace +{ +LinkedUniform *FindUniform(std::vector<LinkedUniform> &list, const std::string &name) +{ + for (LinkedUniform &uniform : list) + { + if (uniform.name == name) + return &uniform; + } + + return nullptr; +} + +template <typename VarT> +void SetActive(std::vector<VarT> *list, const std::string &name, ShaderType shaderType, bool active) +{ + for (auto &variable : *list) + { + if (variable.name == name) + { + variable.setActive(shaderType, active); + return; + } + } +} + +// GLSL ES Spec 3.00.3, section 4.3.5. +LinkMismatchError LinkValidateUniforms(const sh::ShaderVariable &uniform1, + const sh::ShaderVariable &uniform2, + std::string *mismatchedStructFieldName) +{ +#if ANGLE_PROGRAM_LINK_VALIDATE_UNIFORM_PRECISION == ANGLE_ENABLED + const bool validatePrecisionFeature = true; +#else + const bool validatePrecisionFeature = false; +#endif + + // Validate precision match of uniforms iff they are statically used + bool validatePrecision = uniform1.staticUse && uniform2.staticUse && validatePrecisionFeature; + LinkMismatchError linkError = LinkValidateProgramVariables( + uniform1, uniform2, validatePrecision, false, false, mismatchedStructFieldName); + if (linkError != LinkMismatchError::NO_MISMATCH) + { + return linkError; + } + + // GLSL ES Spec 3.10.4, section 4.4.5. + if (uniform1.binding != -1 && uniform2.binding != -1 && uniform1.binding != uniform2.binding) + { + return LinkMismatchError::BINDING_MISMATCH; + } + + // GLSL ES Spec 3.10.4, section 9.2.1. + if (uniform1.location != -1 && uniform2.location != -1 && + uniform1.location != uniform2.location) + { + return LinkMismatchError::LOCATION_MISMATCH; + } + if (uniform1.offset != uniform2.offset) + { + return LinkMismatchError::OFFSET_MISMATCH; + } + + return LinkMismatchError::NO_MISMATCH; +} + +GLuint GetMaximumShaderUniformVectors(ShaderType shaderType, const Caps &caps) +{ + switch (shaderType) + { + case ShaderType::Vertex: + return static_cast<GLuint>(caps.maxVertexUniformVectors); + case ShaderType::Fragment: + return static_cast<GLuint>(caps.maxFragmentUniformVectors); + + case ShaderType::Compute: + case ShaderType::Geometry: + case ShaderType::TessControl: + case ShaderType::TessEvaluation: + return static_cast<GLuint>(caps.maxShaderUniformComponents[shaderType]) / 4; + + default: + UNREACHABLE(); + return 0u; + } +} + +enum class UniformType : uint8_t +{ + Variable = 0, + Sampler = 1, + Image = 2, + AtomicCounter = 3, + + InvalidEnum = 4, + EnumCount = 4, +}; + +const char *GetUniformResourceNameString(UniformType uniformType) +{ + switch (uniformType) + { + case UniformType::Variable: + return "uniform"; + case UniformType::Sampler: + return "texture image unit"; + case UniformType::Image: + return "image uniform"; + case UniformType::AtomicCounter: + return "atomic counter"; + default: + UNREACHABLE(); + return ""; + } +} + +std::string GetUniformResourceLimitName(ShaderType shaderType, UniformType uniformType) +{ + // Special case: MAX_TEXTURE_IMAGE_UNITS (no "MAX_FRAGMENT_TEXTURE_IMAGE_UNITS") + if (shaderType == ShaderType::Fragment && uniformType == UniformType::Sampler) + { + return "MAX_TEXTURE_IMAGE_UNITS"; + } + + std::ostringstream ostream; + ostream << "MAX_" << GetShaderTypeString(shaderType) << "_"; + + switch (uniformType) + { + case UniformType::Variable: + // For vertex and fragment shaders, ES 2.0 only defines MAX_VERTEX_UNIFORM_VECTORS and + // MAX_FRAGMENT_UNIFORM_VECTORS ([OpenGL ES 2.0] Table 6.20). + if (shaderType == ShaderType::Vertex || shaderType == ShaderType::Fragment) + { + ostream << "UNIFORM_VECTORS"; + break; + } + // For compute and geometry shaders, there are no definitions on + // "MAX_COMPUTE_UNIFORM_VECTORS" or "MAX_GEOMETRY_UNIFORM_VECTORS_EXT" + // ([OpenGL ES 3.1] Table 20.45, [EXT_geometry_shader] Table 20.43gs). + else + { + ostream << "UNIFORM_COMPONENTS"; + } + break; + case UniformType::Sampler: + ostream << "TEXTURE_IMAGE_UNITS"; + break; + case UniformType::Image: + ostream << "IMAGE_UNIFORMS"; + break; + case UniformType::AtomicCounter: + ostream << "ATOMIC_COUNTERS"; + break; + default: + UNREACHABLE(); + return ""; + } + + if (shaderType == ShaderType::Geometry) + { + ostream << "_EXT"; + } + + return ostream.str(); +} + +void LogUniformsExceedLimit(ShaderType shaderType, + UniformType uniformType, + GLuint limit, + InfoLog &infoLog) +{ + infoLog << GetShaderTypeString(shaderType) << " shader " + << GetUniformResourceNameString(uniformType) << "s count exceeds " + << GetUniformResourceLimitName(shaderType, uniformType) << "(" << limit << ")"; +} + +// The purpose of this visitor is to capture the uniforms in a uniform block. Each new uniform is +// added to "uniformsOut". +class UniformBlockEncodingVisitor : public sh::VariableNameVisitor +{ + public: + UniformBlockEncodingVisitor(const GetBlockMemberInfoFunc &getMemberInfo, + const std::string &namePrefix, + const std::string &mappedNamePrefix, + std::vector<LinkedUniform> *uniformsOut, + ShaderType shaderType, + int blockIndex) + : sh::VariableNameVisitor(namePrefix, mappedNamePrefix), + mGetMemberInfo(getMemberInfo), + mUniformsOut(uniformsOut), + mShaderType(shaderType), + mBlockIndex(blockIndex) + {} + + void visitNamedVariable(const sh::ShaderVariable &variable, + bool isRowMajor, + const std::string &name, + const std::string &mappedName, + const std::vector<unsigned int> &arraySizes) override + { + // If getBlockMemberInfo returns false, the variable is optimized out. + sh::BlockMemberInfo variableInfo; + if (!mGetMemberInfo(name, mappedName, &variableInfo)) + return; + + std::string nameWithArrayIndex = name; + std::string mappedNameWithArrayIndex = mappedName; + + if (variable.isArray()) + { + nameWithArrayIndex += "[0]"; + mappedNameWithArrayIndex += "[0]"; + } + + if (mBlockIndex == -1) + { + SetActive(mUniformsOut, nameWithArrayIndex, mShaderType, variable.active); + return; + } + + LinkedUniform newUniform(variable.type, variable.precision, nameWithArrayIndex, + variable.arraySizes, -1, -1, -1, mBlockIndex, variableInfo); + newUniform.mappedName = mappedNameWithArrayIndex; + newUniform.setActive(mShaderType, variable.active); + + // Since block uniforms have no location, we don't need to store them in the uniform + // locations list. + mUniformsOut->push_back(newUniform); + } + + private: + const GetBlockMemberInfoFunc &mGetMemberInfo; + std::vector<LinkedUniform> *mUniformsOut; + const ShaderType mShaderType; + const int mBlockIndex; +}; + +// The purpose of this visitor is to capture the buffer variables in a shader storage block. Each +// new buffer variable is stored in "bufferVariablesOut". +class ShaderStorageBlockVisitor : public sh::BlockEncoderVisitor +{ + public: + ShaderStorageBlockVisitor(const GetBlockMemberInfoFunc &getMemberInfo, + const std::string &namePrefix, + const std::string &mappedNamePrefix, + std::vector<BufferVariable> *bufferVariablesOut, + ShaderType shaderType, + int blockIndex) + : sh::BlockEncoderVisitor(namePrefix, mappedNamePrefix, &mStubEncoder), + mGetMemberInfo(getMemberInfo), + mBufferVariablesOut(bufferVariablesOut), + mShaderType(shaderType), + mBlockIndex(blockIndex) + {} + + void visitNamedVariable(const sh::ShaderVariable &variable, + bool isRowMajor, + const std::string &name, + const std::string &mappedName, + const std::vector<unsigned int> &arraySizes) override + { + if (mSkipEnabled) + return; + + // If getBlockMemberInfo returns false, the variable is optimized out. + sh::BlockMemberInfo variableInfo; + if (!mGetMemberInfo(name, mappedName, &variableInfo)) + return; + + std::string nameWithArrayIndex = name; + std::string mappedNameWithArrayIndex = mappedName; + + if (variable.isArray()) + { + nameWithArrayIndex += "[0]"; + mappedNameWithArrayIndex += "[0]"; + } + + if (mBlockIndex == -1) + { + SetActive(mBufferVariablesOut, nameWithArrayIndex, mShaderType, variable.active); + return; + } + + BufferVariable newBufferVariable(variable.type, variable.precision, nameWithArrayIndex, + variable.arraySizes, mBlockIndex, variableInfo); + newBufferVariable.mappedName = mappedNameWithArrayIndex; + newBufferVariable.setActive(mShaderType, variable.active); + + newBufferVariable.topLevelArraySize = mTopLevelArraySize; + + mBufferVariablesOut->push_back(newBufferVariable); + } + + private: + const GetBlockMemberInfoFunc &mGetMemberInfo; + std::vector<BufferVariable> *mBufferVariablesOut; + const ShaderType mShaderType; + const int mBlockIndex; + sh::StubBlockEncoder mStubEncoder; +}; + +struct ShaderUniformCount +{ + unsigned int vectorCount = 0; + unsigned int samplerCount = 0; + unsigned int imageCount = 0; + unsigned int atomicCounterCount = 0; + unsigned int fragmentInOutCount = 0; +}; + +ShaderUniformCount &operator+=(ShaderUniformCount &lhs, const ShaderUniformCount &rhs) +{ + lhs.vectorCount += rhs.vectorCount; + lhs.samplerCount += rhs.samplerCount; + lhs.imageCount += rhs.imageCount; + lhs.atomicCounterCount += rhs.atomicCounterCount; + lhs.fragmentInOutCount += rhs.fragmentInOutCount; + return lhs; +} + +// The purpose of this visitor is to flatten struct and array uniforms into a list of singleton +// uniforms. They are stored in separate lists by uniform type so they can be sorted in order. +// Counts for each uniform category are stored and can be queried with "getCounts". +class FlattenUniformVisitor : public sh::VariableNameVisitor +{ + public: + FlattenUniformVisitor(ShaderType shaderType, + const sh::ShaderVariable &uniform, + std::vector<LinkedUniform> *uniforms, + std::vector<LinkedUniform> *samplerUniforms, + std::vector<LinkedUniform> *imageUniforms, + std::vector<LinkedUniform> *atomicCounterUniforms, + std::vector<LinkedUniform> *inputAttachmentUniforms, + std::vector<UnusedUniform> *unusedUniforms) + : sh::VariableNameVisitor("", ""), + mShaderType(shaderType), + mMarkActive(uniform.active), + mMarkStaticUse(uniform.staticUse), + mBinding(uniform.binding), + mOffset(uniform.offset), + mLocation(uniform.location), + mUniforms(uniforms), + mSamplerUniforms(samplerUniforms), + mImageUniforms(imageUniforms), + mAtomicCounterUniforms(atomicCounterUniforms), + mInputAttachmentUniforms(inputAttachmentUniforms), + mUnusedUniforms(unusedUniforms) + {} + + void visitNamedOpaqueObject(const sh::ShaderVariable &variable, + const std::string &name, + const std::string &mappedName, + const std::vector<unsigned int> &arraySizes) override + { + visitNamedVariable(variable, false, name, mappedName, arraySizes); + } + + void visitNamedVariable(const sh::ShaderVariable &variable, + bool isRowMajor, + const std::string &name, + const std::string &mappedName, + const std::vector<unsigned int> &arraySizes) override + { + bool isSampler = IsSamplerType(variable.type); + bool isImage = IsImageType(variable.type); + bool isAtomicCounter = IsAtomicCounterType(variable.type); + bool isFragmentInOut = variable.isFragmentInOut; + std::vector<LinkedUniform> *uniformList = mUniforms; + if (isSampler) + { + uniformList = mSamplerUniforms; + } + else if (isImage) + { + uniformList = mImageUniforms; + } + else if (isAtomicCounter) + { + uniformList = mAtomicCounterUniforms; + } + else if (isFragmentInOut) + { + uniformList = mInputAttachmentUniforms; + } + + std::string fullNameWithArrayIndex(name); + std::string fullMappedNameWithArrayIndex(mappedName); + + if (variable.isArray()) + { + // We're following the GLES 3.1 November 2016 spec section 7.3.1.1 Naming Active + // Resources and including [0] at the end of array variable names. + fullNameWithArrayIndex += "[0]"; + fullMappedNameWithArrayIndex += "[0]"; + } + + LinkedUniform *existingUniform = FindUniform(*uniformList, fullNameWithArrayIndex); + if (existingUniform) + { + if (getBinding() != -1) + { + existingUniform->binding = getBinding(); + } + if (getOffset() != -1) + { + existingUniform->offset = getOffset(); + } + if (mLocation != -1) + { + existingUniform->location = mLocation; + } + if (mMarkActive) + { + existingUniform->active = true; + existingUniform->setActive(mShaderType, true); + } + if (mMarkStaticUse) + { + existingUniform->staticUse = true; + } + } + else + { + LinkedUniform linkedUniform(variable.type, variable.precision, fullNameWithArrayIndex, + variable.arraySizes, getBinding(), getOffset(), mLocation, + -1, sh::kDefaultBlockMemberInfo); + linkedUniform.mappedName = fullMappedNameWithArrayIndex; + linkedUniform.active = mMarkActive; + linkedUniform.staticUse = mMarkStaticUse; + linkedUniform.outerArraySizes = arraySizes; + linkedUniform.texelFetchStaticUse = variable.texelFetchStaticUse; + linkedUniform.imageUnitFormat = variable.imageUnitFormat; + linkedUniform.isFragmentInOut = variable.isFragmentInOut; + if (variable.hasParentArrayIndex()) + { + linkedUniform.setParentArrayIndex(variable.parentArrayIndex()); + } + + std::vector<unsigned int> arrayDims = arraySizes; + ASSERT(variable.arraySizes.size() == 1 || variable.arraySizes.size() == 0); + arrayDims.push_back(variable.arraySizes.empty() ? 1 : variable.arraySizes[0]); + + size_t numDimensions = arraySizes.size(); + uint32_t arrayStride = 1; + for (size_t dimension = numDimensions; dimension > 0;) + { + --dimension; + arrayStride *= arrayDims[dimension + 1]; + linkedUniform.outerArrayOffset += arrayStride * mArrayElementStack[dimension]; + } + + if (mMarkActive) + { + linkedUniform.setActive(mShaderType, true); + } + else + { + mUnusedUniforms->emplace_back( + linkedUniform.name, linkedUniform.isSampler(), linkedUniform.isImage(), + linkedUniform.isAtomicCounter(), linkedUniform.isFragmentInOut); + } + + uniformList->push_back(linkedUniform); + } + + unsigned int elementCount = variable.getBasicTypeElementCount(); + + // Samplers and images aren't "real" uniforms, so they don't count towards register usage. + // Likewise, don't count "real" uniforms towards opaque count. + + if (!IsOpaqueType(variable.type) && !isFragmentInOut) + { + mUniformCount.vectorCount += VariableRegisterCount(variable.type) * elementCount; + } + + mUniformCount.samplerCount += (isSampler ? elementCount : 0); + mUniformCount.imageCount += (isImage ? elementCount : 0); + mUniformCount.atomicCounterCount += (isAtomicCounter ? elementCount : 0); + mUniformCount.fragmentInOutCount += (isFragmentInOut ? elementCount : 0); + + if (mLocation != -1) + { + mLocation += elementCount; + } + } + + void enterStructAccess(const sh::ShaderVariable &structVar, bool isRowMajor) override + { + mStructStackSize++; + sh::VariableNameVisitor::enterStructAccess(structVar, isRowMajor); + } + + void exitStructAccess(const sh::ShaderVariable &structVar, bool isRowMajor) override + { + mStructStackSize--; + sh::VariableNameVisitor::exitStructAccess(structVar, isRowMajor); + } + + void enterArrayElement(const sh::ShaderVariable &arrayVar, unsigned int arrayElement) override + { + mArrayElementStack.push_back(arrayElement); + sh::VariableNameVisitor::enterArrayElement(arrayVar, arrayElement); + } + + void exitArrayElement(const sh::ShaderVariable &arrayVar, unsigned int arrayElement) override + { + mArrayElementStack.pop_back(); + sh::VariableNameVisitor::exitArrayElement(arrayVar, arrayElement); + } + + ShaderUniformCount getCounts() const { return mUniformCount; } + + private: + int getBinding() const { return mStructStackSize == 0 ? mBinding : -1; } + int getOffset() const { return mStructStackSize == 0 ? mOffset : -1; } + + ShaderType mShaderType; + + // Active and StaticUse are given separately because they are tracked at struct granularity. + bool mMarkActive; + bool mMarkStaticUse; + int mBinding; + int mOffset; + int mLocation; + std::vector<LinkedUniform> *mUniforms; + std::vector<LinkedUniform> *mSamplerUniforms; + std::vector<LinkedUniform> *mImageUniforms; + std::vector<LinkedUniform> *mAtomicCounterUniforms; + std::vector<LinkedUniform> *mInputAttachmentUniforms; + std::vector<UnusedUniform> *mUnusedUniforms; + std::vector<unsigned int> mArrayElementStack; + ShaderUniformCount mUniformCount; + unsigned int mStructStackSize = 0; +}; + +class InterfaceBlockInfo final : angle::NonCopyable +{ + public: + InterfaceBlockInfo(CustomBlockLayoutEncoderFactory *customEncoderFactory) + : mCustomEncoderFactory(customEncoderFactory) + {} + + void getShaderBlockInfo(const std::vector<sh::InterfaceBlock> &interfaceBlocks); + + bool getBlockSize(const std::string &name, const std::string &mappedName, size_t *sizeOut); + bool getBlockMemberInfo(const std::string &name, + const std::string &mappedName, + sh::BlockMemberInfo *infoOut); + + private: + size_t getBlockInfo(const sh::InterfaceBlock &interfaceBlock); + + std::map<std::string, size_t> mBlockSizes; + sh::BlockLayoutMap mBlockLayout; + // Based on the interface block layout, the std140 or std430 encoders are used. On some + // platforms (currently only D3D), there could be another non-standard encoder used. + CustomBlockLayoutEncoderFactory *mCustomEncoderFactory; +}; + +void InterfaceBlockInfo::getShaderBlockInfo(const std::vector<sh::InterfaceBlock> &interfaceBlocks) +{ + for (const sh::InterfaceBlock &interfaceBlock : interfaceBlocks) + { + if (!IsActiveInterfaceBlock(interfaceBlock)) + continue; + + if (mBlockSizes.count(interfaceBlock.name) > 0) + continue; + + size_t dataSize = getBlockInfo(interfaceBlock); + mBlockSizes[interfaceBlock.name] = dataSize; + } +} + +size_t InterfaceBlockInfo::getBlockInfo(const sh::InterfaceBlock &interfaceBlock) +{ + ASSERT(IsActiveInterfaceBlock(interfaceBlock)); + + // define member uniforms + sh::Std140BlockEncoder std140Encoder; + sh::Std430BlockEncoder std430Encoder; + sh::BlockLayoutEncoder *customEncoder = nullptr; + sh::BlockLayoutEncoder *encoder = nullptr; + + if (interfaceBlock.layout == sh::BLOCKLAYOUT_STD140) + { + encoder = &std140Encoder; + } + else if (interfaceBlock.layout == sh::BLOCKLAYOUT_STD430) + { + encoder = &std430Encoder; + } + else if (mCustomEncoderFactory) + { + encoder = customEncoder = mCustomEncoderFactory->makeEncoder(); + } + else + { + UNREACHABLE(); + return 0; + } + + sh::GetInterfaceBlockInfo(interfaceBlock.fields, interfaceBlock.fieldPrefix(), encoder, + &mBlockLayout); + + size_t offset = encoder->getCurrentOffset(); + + SafeDelete(customEncoder); + + return offset; +} + +bool InterfaceBlockInfo::getBlockSize(const std::string &name, + const std::string &mappedName, + size_t *sizeOut) +{ + size_t nameLengthWithoutArrayIndex; + ParseArrayIndex(name, &nameLengthWithoutArrayIndex); + std::string baseName = name.substr(0u, nameLengthWithoutArrayIndex); + auto sizeIter = mBlockSizes.find(baseName); + if (sizeIter == mBlockSizes.end()) + { + *sizeOut = 0; + return false; + } + + *sizeOut = sizeIter->second; + return true; +} + +bool InterfaceBlockInfo::getBlockMemberInfo(const std::string &name, + const std::string &mappedName, + sh::BlockMemberInfo *infoOut) +{ + auto infoIter = mBlockLayout.find(name); + if (infoIter == mBlockLayout.end()) + { + *infoOut = sh::kDefaultBlockMemberInfo; + return false; + } + + *infoOut = infoIter->second; + return true; +} + +void GetFilteredVaryings(const std::vector<sh::ShaderVariable> &varyings, + std::vector<const sh::ShaderVariable *> *filteredVaryingsOut) +{ + for (const sh::ShaderVariable &varying : varyings) + { + // Built-in varyings obey special rules + if (varying.isBuiltIn()) + { + continue; + } + + filteredVaryingsOut->push_back(&varying); + } +} + +LinkMismatchError LinkValidateVaryings(const sh::ShaderVariable &outputVarying, + const sh::ShaderVariable &inputVarying, + int shaderVersion, + ShaderType frontShaderType, + ShaderType backShaderType, + bool isSeparable, + std::string *mismatchedStructFieldName) +{ + // [ES 3.2 spec] 7.4.1 Shader Interface Matching: + // Tessellation control shader per-vertex output variables and blocks and tessellation control, + // tessellation evaluation, and geometry shader per-vertex input variables and blocks are + // required to be declared as arrays, with each element representing input or output values for + // a single vertex of a multi-vertex primitive. For the purposes of interface matching, such + // variables and blocks are treated as though they were not declared as arrays. + bool treatOutputAsNonArray = + (frontShaderType == ShaderType::TessControl && !outputVarying.isPatch); + bool treatInputAsNonArray = + ((backShaderType == ShaderType::TessControl || + backShaderType == ShaderType::TessEvaluation || backShaderType == ShaderType::Geometry) && + !inputVarying.isPatch); + + // Skip the validation on the array sizes between a vertex output varying and a geometry input + // varying as it has been done before. + bool validatePrecision = isSeparable && (shaderVersion > 100); + LinkMismatchError linkError = LinkValidateProgramVariables( + outputVarying, inputVarying, validatePrecision, treatOutputAsNonArray, treatInputAsNonArray, + mismatchedStructFieldName); + if (linkError != LinkMismatchError::NO_MISMATCH) + { + return linkError; + } + + // Explicit locations must match if the names match. + if (outputVarying.isSameNameAtLinkTime(inputVarying) && + outputVarying.location != inputVarying.location) + { + return LinkMismatchError::LOCATION_MISMATCH; + } + + if (!sh::InterpolationTypesMatch(outputVarying.interpolation, inputVarying.interpolation)) + { + return LinkMismatchError::INTERPOLATION_TYPE_MISMATCH; + } + + if (shaderVersion == 100 && outputVarying.isInvariant != inputVarying.isInvariant) + { + return LinkMismatchError::INVARIANCE_MISMATCH; + } + + return LinkMismatchError::NO_MISMATCH; +} + +bool DoShaderVariablesMatch(int frontShaderVersion, + ShaderType frontShaderType, + ShaderType backShaderType, + const sh::ShaderVariable &input, + const sh::ShaderVariable &output, + bool isSeparable, + gl::InfoLog &infoLog) +{ + bool namesMatch = input.isSameNameAtLinkTime(output); + bool locationsMatch = input.location != -1 && input.location == output.location; + + // An output block is considered to match an input block in the subsequent + // shader if the two blocks have the same block name, and the members of the + // block match exactly in name, type, qualification, and declaration order. + // + // - For the purposes of shader interface matching, the gl_PointSize + // member of the intrinsically declared gl_PerVertex shader interface + // block is ignored. + // - Output blocks that do not match in name, but have a location and match + // in every other way listed above may be considered to match by some + // implementations, but not all - so this behaviour should not be relied + // upon. + + // An output variable is considered to match an input variable in the subsequent + // shader if: + // + // - the two variables match in name, type, and qualification; or + // - the two variables are declared with the same location qualifier and + // match in type and qualification. + + if (namesMatch || locationsMatch) + { + std::string mismatchedStructFieldName; + LinkMismatchError linkError = + LinkValidateVaryings(output, input, frontShaderVersion, frontShaderType, backShaderType, + isSeparable, &mismatchedStructFieldName); + if (linkError != LinkMismatchError::NO_MISMATCH) + { + LogLinkMismatch(infoLog, input.name, "varying", linkError, mismatchedStructFieldName, + frontShaderType, backShaderType); + return false; + } + + return true; + } + + return false; +} + +const char *GetInterfaceBlockTypeString(sh::BlockType blockType) +{ + switch (blockType) + { + case sh::BlockType::BLOCK_UNIFORM: + return "uniform block"; + case sh::BlockType::BLOCK_BUFFER: + return "shader storage block"; + default: + UNREACHABLE(); + return ""; + } +} + +std::string GetInterfaceBlockLimitName(ShaderType shaderType, sh::BlockType blockType) +{ + std::ostringstream stream; + stream << "GL_MAX_" << GetShaderTypeString(shaderType) << "_"; + + switch (blockType) + { + case sh::BlockType::BLOCK_UNIFORM: + stream << "UNIFORM_BUFFERS"; + break; + case sh::BlockType::BLOCK_BUFFER: + stream << "SHADER_STORAGE_BLOCKS"; + break; + default: + UNREACHABLE(); + return ""; + } + + if (shaderType == ShaderType::Geometry) + { + stream << "_EXT"; + } + + return stream.str(); +} + +void LogInterfaceBlocksExceedLimit(InfoLog &infoLog, + ShaderType shaderType, + sh::BlockType blockType, + GLuint limit) +{ + infoLog << GetShaderTypeString(shaderType) << " shader " + << GetInterfaceBlockTypeString(blockType) << " count exceeds " + << GetInterfaceBlockLimitName(shaderType, blockType) << " (" << limit << ")"; +} + +bool ValidateInterfaceBlocksCount(GLuint maxInterfaceBlocks, + const std::vector<sh::InterfaceBlock> &interfaceBlocks, + ShaderType shaderType, + sh::BlockType blockType, + GLuint *combinedInterfaceBlocksCount, + InfoLog &infoLog) +{ + GLuint blockCount = 0; + for (const sh::InterfaceBlock &block : interfaceBlocks) + { + if (IsActiveInterfaceBlock(block)) + { + blockCount += std::max(block.arraySize, 1u); + if (blockCount > maxInterfaceBlocks) + { + LogInterfaceBlocksExceedLimit(infoLog, shaderType, blockType, maxInterfaceBlocks); + return false; + } + } + } + + // [OpenGL ES 3.1] Chapter 7.6.2 Page 105: + // If a uniform block is used by multiple shader stages, each such use counts separately + // against this combined limit. + // [OpenGL ES 3.1] Chapter 7.8 Page 111: + // If a shader storage block in a program is referenced by multiple shaders, each such + // reference counts separately against this combined limit. + if (combinedInterfaceBlocksCount) + { + *combinedInterfaceBlocksCount += blockCount; + } + + return true; +} +} // anonymous namespace + +UniformLinker::UniformLinker(const ShaderBitSet &activeShaderStages, + const ShaderMap<std::vector<sh::ShaderVariable>> &shaderUniforms) + : mActiveShaderStages(activeShaderStages), mShaderUniforms(shaderUniforms) +{} + +UniformLinker::~UniformLinker() = default; + +void UniformLinker::getResults(std::vector<LinkedUniform> *uniforms, + std::vector<UnusedUniform> *unusedUniformsOutOrNull, + std::vector<VariableLocation> *uniformLocationsOutOrNull) +{ + uniforms->swap(mUniforms); + + if (unusedUniformsOutOrNull) + { + unusedUniformsOutOrNull->swap(mUnusedUniforms); + } + + if (uniformLocationsOutOrNull) + { + uniformLocationsOutOrNull->swap(mUniformLocations); + } +} + +bool UniformLinker::link(const Caps &caps, + InfoLog &infoLog, + const ProgramAliasedBindings &uniformLocationBindings) +{ + if (mActiveShaderStages[ShaderType::Vertex] && mActiveShaderStages[ShaderType::Fragment]) + { + if (!validateGraphicsUniforms(infoLog)) + { + return false; + } + } + + // Flatten the uniforms list (nested fields) into a simple list (no nesting). + // Also check the maximum uniform vector and sampler counts. + if (!flattenUniformsAndCheckCaps(caps, infoLog)) + { + return false; + } + + if (!checkMaxCombinedAtomicCounters(caps, infoLog)) + { + return false; + } + + if (!indexUniforms(infoLog, uniformLocationBindings)) + { + return false; + } + + return true; +} + +bool UniformLinker::validateGraphicsUniforms(InfoLog &infoLog) const +{ + // Check that uniforms defined in the graphics shaders are identical + std::map<std::string, ShaderUniform> linkedUniforms; + + for (const ShaderType shaderType : mActiveShaderStages) + { + if (shaderType == ShaderType::Vertex) + { + for (const sh::ShaderVariable &vertexUniform : mShaderUniforms[ShaderType::Vertex]) + { + linkedUniforms[vertexUniform.name] = + std::make_pair(ShaderType::Vertex, &vertexUniform); + } + } + else + { + bool isLastShader = (shaderType == ShaderType::Fragment); + if (!validateGraphicsUniformsPerShader(shaderType, !isLastShader, &linkedUniforms, + infoLog)) + { + return false; + } + } + } + + return true; +} + +bool UniformLinker::validateGraphicsUniformsPerShader( + ShaderType shaderToLink, + bool extendLinkedUniforms, + std::map<std::string, ShaderUniform> *linkedUniforms, + InfoLog &infoLog) const +{ + ASSERT(mActiveShaderStages[shaderToLink] && linkedUniforms); + + for (const sh::ShaderVariable &uniform : mShaderUniforms[shaderToLink]) + { + const auto &entry = linkedUniforms->find(uniform.name); + if (entry != linkedUniforms->end()) + { + const sh::ShaderVariable &linkedUniform = *(entry->second.second); + std::string mismatchedStructFieldName; + LinkMismatchError linkError = + LinkValidateUniforms(uniform, linkedUniform, &mismatchedStructFieldName); + if (linkError != LinkMismatchError::NO_MISMATCH) + { + LogLinkMismatch(infoLog, uniform.name, "uniform", linkError, + mismatchedStructFieldName, entry->second.first, shaderToLink); + return false; + } + } + else if (extendLinkedUniforms) + { + (*linkedUniforms)[uniform.name] = std::make_pair(shaderToLink, &uniform); + } + } + + return true; +} + +bool UniformLinker::indexUniforms(InfoLog &infoLog, + const ProgramAliasedBindings &uniformLocationBindings) +{ + // Locations which have been allocated for an unused uniform. + std::set<GLuint> ignoredLocations; + + int maxUniformLocation = -1; + + // Gather uniform locations that have been set either using the bindUniformLocationCHROMIUM API + // or by using a location layout qualifier and check conflicts between them. + if (!gatherUniformLocationsAndCheckConflicts(infoLog, uniformLocationBindings, + &ignoredLocations, &maxUniformLocation)) + { + return false; + } + + // Conflicts have been checked, now we can prune non-statically used uniforms. Code further down + // the line relies on only having statically used uniforms in mUniforms. + pruneUnusedUniforms(); + + // Gather uniforms that have their location pre-set and uniforms that don't yet have a location. + std::vector<VariableLocation> unlocatedUniforms; + std::map<GLuint, VariableLocation> preLocatedUniforms; + + for (size_t uniformIndex = 0; uniformIndex < mUniforms.size(); uniformIndex++) + { + const LinkedUniform &uniform = mUniforms[uniformIndex]; + + if ((uniform.isBuiltIn() && !uniform.isEmulatedBuiltIn()) || + IsAtomicCounterType(uniform.type) || uniform.isFragmentInOut) + { + continue; + } + + int preSetLocation = uniformLocationBindings.getBinding(uniform); + int shaderLocation = uniform.location; + + if (shaderLocation != -1) + { + preSetLocation = shaderLocation; + } + + unsigned int elementCount = uniform.getBasicTypeElementCount(); + for (unsigned int arrayIndex = 0; arrayIndex < elementCount; arrayIndex++) + { + VariableLocation location(arrayIndex, static_cast<unsigned int>(uniformIndex)); + + if ((arrayIndex == 0 && preSetLocation != -1) || shaderLocation != -1) + { + int elementLocation = preSetLocation + arrayIndex; + preLocatedUniforms[elementLocation] = location; + } + else + { + unlocatedUniforms.push_back(location); + } + } + } + + // Make enough space for all uniforms, with pre-set locations or not. + mUniformLocations.resize( + std::max(unlocatedUniforms.size() + preLocatedUniforms.size() + ignoredLocations.size(), + static_cast<size_t>(maxUniformLocation + 1))); + + // Assign uniforms with pre-set locations + for (const auto &uniform : preLocatedUniforms) + { + mUniformLocations[uniform.first] = uniform.second; + } + + // Assign ignored uniforms + for (const auto &ignoredLocation : ignoredLocations) + { + mUniformLocations[ignoredLocation].markIgnored(); + } + + // Automatically assign locations for the rest of the uniforms + size_t nextUniformLocation = 0; + for (const auto &unlocatedUniform : unlocatedUniforms) + { + while (mUniformLocations[nextUniformLocation].used() || + mUniformLocations[nextUniformLocation].ignored) + { + nextUniformLocation++; + } + + ASSERT(nextUniformLocation < mUniformLocations.size()); + mUniformLocations[nextUniformLocation] = unlocatedUniform; + nextUniformLocation++; + } + + return true; +} + +bool UniformLinker::gatherUniformLocationsAndCheckConflicts( + InfoLog &infoLog, + const ProgramAliasedBindings &uniformLocationBindings, + std::set<GLuint> *ignoredLocations, + int *maxUniformLocation) +{ + // All the locations where another uniform can't be located. + std::set<GLuint> reservedLocations; + + for (const LinkedUniform &uniform : mUniforms) + { + if ((uniform.isBuiltIn() && !uniform.isEmulatedBuiltIn()) || uniform.isFragmentInOut) + { + // The uniform of the fragment inout is not a normal uniform type. So, in the case of + // the fragment inout, this routine should be skipped. + continue; + } + + int apiBoundLocation = uniformLocationBindings.getBinding(uniform); + int shaderLocation = uniform.location; + + if (shaderLocation != -1) + { + unsigned int elementCount = uniform.getBasicTypeElementCount(); + + for (unsigned int arrayIndex = 0; arrayIndex < elementCount; arrayIndex++) + { + // GLSL ES 3.10 section 4.4.3 + int elementLocation = shaderLocation + arrayIndex; + *maxUniformLocation = std::max(*maxUniformLocation, elementLocation); + if (reservedLocations.find(elementLocation) != reservedLocations.end()) + { + infoLog << "Multiple uniforms bound to location " << elementLocation << "."; + return false; + } + reservedLocations.insert(elementLocation); + if (!uniform.active) + { + ignoredLocations->insert(elementLocation); + } + } + } + else if (apiBoundLocation != -1 && uniform.staticUse) + { + // Only the first location is reserved even if the uniform is an array. + *maxUniformLocation = std::max(*maxUniformLocation, apiBoundLocation); + if (reservedLocations.find(apiBoundLocation) != reservedLocations.end()) + { + infoLog << "Multiple uniforms bound to location " << apiBoundLocation << "."; + return false; + } + reservedLocations.insert(apiBoundLocation); + if (!uniform.active) + { + ignoredLocations->insert(apiBoundLocation); + } + } + } + + // Record the uniform locations that were bound using the API for uniforms that were not found + // from the shader. Other uniforms should not be assigned to those locations. + for (const auto &locationBinding : uniformLocationBindings) + { + GLuint location = locationBinding.second.location; + if (reservedLocations.find(location) == reservedLocations.end()) + { + ignoredLocations->insert(location); + *maxUniformLocation = std::max(*maxUniformLocation, static_cast<int>(location)); + } + } + + return true; +} + +void UniformLinker::pruneUnusedUniforms() +{ + auto uniformIter = mUniforms.begin(); + while (uniformIter != mUniforms.end()) + { + if (uniformIter->active) + { + ++uniformIter; + } + else + { + mUnusedUniforms.emplace_back(uniformIter->name, uniformIter->isSampler(), + uniformIter->isImage(), uniformIter->isAtomicCounter(), + uniformIter->isFragmentInOut); + uniformIter = mUniforms.erase(uniformIter); + } + } +} + +bool UniformLinker::flattenUniformsAndCheckCapsForShader( + ShaderType shaderType, + const Caps &caps, + std::vector<LinkedUniform> &samplerUniforms, + std::vector<LinkedUniform> &imageUniforms, + std::vector<LinkedUniform> &atomicCounterUniforms, + std::vector<LinkedUniform> &inputAttachmentUniforms, + std::vector<UnusedUniform> &unusedUniforms, + InfoLog &infoLog) +{ + ShaderUniformCount shaderUniformCount; + for (const sh::ShaderVariable &uniform : mShaderUniforms[shaderType]) + { + FlattenUniformVisitor flattener(shaderType, uniform, &mUniforms, &samplerUniforms, + &imageUniforms, &atomicCounterUniforms, + &inputAttachmentUniforms, &unusedUniforms); + sh::TraverseShaderVariable(uniform, false, &flattener); + + if (uniform.active) + { + shaderUniformCount += flattener.getCounts(); + } + else + { + unusedUniforms.emplace_back(uniform.name, IsSamplerType(uniform.type), + IsImageType(uniform.type), + IsAtomicCounterType(uniform.type), uniform.isFragmentInOut); + } + } + + // This code does not do fine-grained component counting. + GLuint maxUniformVectorsCount = GetMaximumShaderUniformVectors(shaderType, caps); + if (shaderUniformCount.vectorCount > maxUniformVectorsCount) + { + GLuint maxUniforms = 0u; + + // See comments in GetUniformResourceLimitName() + if (shaderType == ShaderType::Vertex || shaderType == ShaderType::Fragment) + { + maxUniforms = maxUniformVectorsCount; + } + else + { + maxUniforms = maxUniformVectorsCount * 4; + } + + LogUniformsExceedLimit(shaderType, UniformType::Variable, maxUniforms, infoLog); + return false; + } + + if (shaderUniformCount.samplerCount > + static_cast<GLuint>(caps.maxShaderTextureImageUnits[shaderType])) + { + LogUniformsExceedLimit(shaderType, UniformType::Sampler, + caps.maxShaderTextureImageUnits[shaderType], infoLog); + return false; + } + + if (shaderUniformCount.imageCount > + static_cast<GLuint>(caps.maxShaderImageUniforms[shaderType])) + { + LogUniformsExceedLimit(shaderType, UniformType::Image, + caps.maxShaderImageUniforms[shaderType], infoLog); + return false; + } + + if (shaderUniformCount.atomicCounterCount > + static_cast<GLuint>(caps.maxShaderAtomicCounters[shaderType])) + { + LogUniformsExceedLimit(shaderType, UniformType::AtomicCounter, + caps.maxShaderAtomicCounters[shaderType], infoLog); + return false; + } + + return true; +} + +bool UniformLinker::flattenUniformsAndCheckCaps(const Caps &caps, InfoLog &infoLog) +{ + std::vector<LinkedUniform> samplerUniforms; + std::vector<LinkedUniform> imageUniforms; + std::vector<LinkedUniform> atomicCounterUniforms; + std::vector<LinkedUniform> inputAttachmentUniforms; + std::vector<UnusedUniform> unusedUniforms; + + for (const ShaderType shaderType : mActiveShaderStages) + { + if (!flattenUniformsAndCheckCapsForShader(shaderType, caps, samplerUniforms, imageUniforms, + atomicCounterUniforms, inputAttachmentUniforms, + unusedUniforms, infoLog)) + { + return false; + } + } + + mUniforms.insert(mUniforms.end(), samplerUniforms.begin(), samplerUniforms.end()); + mUniforms.insert(mUniforms.end(), imageUniforms.begin(), imageUniforms.end()); + mUniforms.insert(mUniforms.end(), atomicCounterUniforms.begin(), atomicCounterUniforms.end()); + mUniforms.insert(mUniforms.end(), inputAttachmentUniforms.begin(), + inputAttachmentUniforms.end()); + mUnusedUniforms.insert(mUnusedUniforms.end(), unusedUniforms.begin(), unusedUniforms.end()); + return true; +} + +bool UniformLinker::checkMaxCombinedAtomicCounters(const Caps &caps, InfoLog &infoLog) +{ + unsigned int atomicCounterCount = 0; + for (const auto &uniform : mUniforms) + { + if (IsAtomicCounterType(uniform.type) && uniform.active) + { + atomicCounterCount += uniform.getBasicTypeElementCount(); + if (atomicCounterCount > static_cast<GLuint>(caps.maxCombinedAtomicCounters)) + { + infoLog << "atomic counter count exceeds MAX_COMBINED_ATOMIC_COUNTERS" + << caps.maxCombinedAtomicCounters << ")."; + return false; + } + } + } + return true; +} + +// InterfaceBlockLinker implementation. +InterfaceBlockLinker::InterfaceBlockLinker() = default; + +InterfaceBlockLinker::~InterfaceBlockLinker() = default; + +void InterfaceBlockLinker::init(std::vector<InterfaceBlock> *blocksOut, + std::vector<std::string> *unusedInterfaceBlocksOut) +{ + mBlocksOut = blocksOut; + mUnusedInterfaceBlocksOut = unusedInterfaceBlocksOut; +} + +void InterfaceBlockLinker::addShaderBlocks(ShaderType shaderType, + const std::vector<sh::InterfaceBlock> *blocks) +{ + mShaderBlocks[shaderType] = blocks; +} + +void InterfaceBlockLinker::linkBlocks(const GetBlockSizeFunc &getBlockSize, + const GetBlockMemberInfoFunc &getMemberInfo) const +{ + ASSERT(mBlocksOut->empty()); + + std::set<std::string> visitedList; + + for (const ShaderType shaderType : AllShaderTypes()) + { + if (!mShaderBlocks[shaderType]) + { + continue; + } + + for (const sh::InterfaceBlock &block : *mShaderBlocks[shaderType]) + { + if (!IsActiveInterfaceBlock(block)) + { + mUnusedInterfaceBlocksOut->push_back(block.name); + continue; + } + + if (visitedList.count(block.name) == 0) + { + defineInterfaceBlock(getBlockSize, getMemberInfo, block, shaderType); + visitedList.insert(block.name); + continue; + } + + if (!block.active) + { + mUnusedInterfaceBlocksOut->push_back(block.name); + continue; + } + + for (InterfaceBlock &priorBlock : *mBlocksOut) + { + if (block.name == priorBlock.name) + { + priorBlock.setActive(shaderType, true); + + std::unique_ptr<sh::ShaderVariableVisitor> visitor( + getVisitor(getMemberInfo, block.fieldPrefix(), block.fieldMappedPrefix(), + shaderType, -1)); + + sh::TraverseShaderVariables(block.fields, false, visitor.get()); + } + } + } + } +} + +void InterfaceBlockLinker::defineInterfaceBlock(const GetBlockSizeFunc &getBlockSize, + const GetBlockMemberInfoFunc &getMemberInfo, + const sh::InterfaceBlock &interfaceBlock, + ShaderType shaderType) const +{ + size_t blockSize = 0; + std::vector<unsigned int> blockIndexes; + + int blockIndex = static_cast<int>(mBlocksOut->size()); + // Track the first and last block member index to determine the range of active block members in + // the block. + size_t firstBlockMemberIndex = getCurrentBlockMemberIndex(); + + std::unique_ptr<sh::ShaderVariableVisitor> visitor( + getVisitor(getMemberInfo, interfaceBlock.fieldPrefix(), interfaceBlock.fieldMappedPrefix(), + shaderType, blockIndex)); + sh::TraverseShaderVariables(interfaceBlock.fields, false, visitor.get()); + + size_t lastBlockMemberIndex = getCurrentBlockMemberIndex(); + + for (size_t blockMemberIndex = firstBlockMemberIndex; blockMemberIndex < lastBlockMemberIndex; + ++blockMemberIndex) + { + blockIndexes.push_back(static_cast<unsigned int>(blockMemberIndex)); + } + + unsigned int firstFieldArraySize = interfaceBlock.fields[0].getArraySizeProduct(); + + for (unsigned int arrayElement = 0; arrayElement < interfaceBlock.elementCount(); + ++arrayElement) + { + std::string blockArrayName = interfaceBlock.name; + std::string blockMappedArrayName = interfaceBlock.mappedName; + if (interfaceBlock.isArray()) + { + blockArrayName += ArrayString(arrayElement); + blockMappedArrayName += ArrayString(arrayElement); + } + + // Don't define this block at all if it's not active in the implementation. + if (!getBlockSize(blockArrayName, blockMappedArrayName, &blockSize)) + { + continue; + } + + // ESSL 3.10 section 4.4.4 page 58: + // Any uniform or shader storage block declared without a binding qualifier is initially + // assigned to block binding point zero. + int blockBinding = + (interfaceBlock.binding == -1 ? 0 : interfaceBlock.binding + arrayElement); + InterfaceBlock block(interfaceBlock.name, interfaceBlock.mappedName, + interfaceBlock.isArray(), arrayElement, firstFieldArraySize, + blockBinding); + block.memberIndexes = blockIndexes; + block.setActive(shaderType, interfaceBlock.active); + + // Since all block elements in an array share the same active interface blocks, they + // will all be active once any block member is used. So, since interfaceBlock.name[0] + // was active, here we will add every block element in the array. + block.dataSize = static_cast<unsigned int>(blockSize); + mBlocksOut->push_back(block); + } +} + +// UniformBlockLinker implementation. +UniformBlockLinker::UniformBlockLinker() = default; + +UniformBlockLinker::~UniformBlockLinker() {} + +void UniformBlockLinker::init(std::vector<InterfaceBlock> *blocksOut, + std::vector<LinkedUniform> *uniformsOut, + std::vector<std::string> *unusedInterfaceBlocksOut) +{ + InterfaceBlockLinker::init(blocksOut, unusedInterfaceBlocksOut); + mUniformsOut = uniformsOut; +} + +size_t UniformBlockLinker::getCurrentBlockMemberIndex() const +{ + return mUniformsOut->size(); +} + +sh::ShaderVariableVisitor *UniformBlockLinker::getVisitor( + const GetBlockMemberInfoFunc &getMemberInfo, + const std::string &namePrefix, + const std::string &mappedNamePrefix, + ShaderType shaderType, + int blockIndex) const +{ + return new UniformBlockEncodingVisitor(getMemberInfo, namePrefix, mappedNamePrefix, + mUniformsOut, shaderType, blockIndex); +} + +// ShaderStorageBlockLinker implementation. +ShaderStorageBlockLinker::ShaderStorageBlockLinker() = default; + +ShaderStorageBlockLinker::~ShaderStorageBlockLinker() = default; + +void ShaderStorageBlockLinker::init(std::vector<InterfaceBlock> *blocksOut, + std::vector<BufferVariable> *bufferVariablesOut, + std::vector<std::string> *unusedInterfaceBlocksOut) +{ + InterfaceBlockLinker::init(blocksOut, unusedInterfaceBlocksOut); + mBufferVariablesOut = bufferVariablesOut; +} + +size_t ShaderStorageBlockLinker::getCurrentBlockMemberIndex() const +{ + return mBufferVariablesOut->size(); +} + +sh::ShaderVariableVisitor *ShaderStorageBlockLinker::getVisitor( + const GetBlockMemberInfoFunc &getMemberInfo, + const std::string &namePrefix, + const std::string &mappedNamePrefix, + ShaderType shaderType, + int blockIndex) const +{ + return new ShaderStorageBlockVisitor(getMemberInfo, namePrefix, mappedNamePrefix, + mBufferVariablesOut, shaderType, blockIndex); +} + +// AtomicCounterBufferLinker implementation. +AtomicCounterBufferLinker::AtomicCounterBufferLinker() = default; + +AtomicCounterBufferLinker::~AtomicCounterBufferLinker() = default; + +void AtomicCounterBufferLinker::init(std::vector<AtomicCounterBuffer> *atomicCounterBuffersOut) +{ + mAtomicCounterBuffersOut = atomicCounterBuffersOut; +} + +void AtomicCounterBufferLinker::link(const std::map<int, unsigned int> &sizeMap) const +{ + for (auto &atomicCounterBuffer : *mAtomicCounterBuffersOut) + { + auto bufferSize = sizeMap.find(atomicCounterBuffer.binding); + ASSERT(bufferSize != sizeMap.end()); + atomicCounterBuffer.dataSize = bufferSize->second; + } +} + +ProgramLinkedResources::ProgramLinkedResources() = default; + +ProgramLinkedResources::~ProgramLinkedResources() = default; + +LinkingVariables::LinkingVariables(const Context *context, const ProgramState &state) +{ + for (ShaderType shaderType : kAllGraphicsShaderTypes) + { + Shader *shader = state.getAttachedShader(shaderType); + if (shader) + { + outputVaryings[shaderType] = shader->getOutputVaryings(context); + inputVaryings[shaderType] = shader->getInputVaryings(context); + uniforms[shaderType] = shader->getUniforms(context); + uniformBlocks[shaderType] = shader->getUniformBlocks(context); + isShaderStageUsedBitset.set(shaderType); + } + } +} + +LinkingVariables::LinkingVariables(const ProgramPipelineState &state) +{ + for (ShaderType shaderType : state.getExecutable().getLinkedShaderStages()) + { + const Program *program = state.getShaderProgram(shaderType); + ASSERT(program); + outputVaryings[shaderType] = program->getExecutable().getLinkedOutputVaryings(shaderType); + inputVaryings[shaderType] = program->getExecutable().getLinkedInputVaryings(shaderType); + uniforms[shaderType] = program->getState().getExecutable().getLinkedUniforms(shaderType); + uniformBlocks[shaderType] = + program->getState().getExecutable().getLinkedUniformBlocks(shaderType); + isShaderStageUsedBitset.set(shaderType); + } +} + +LinkingVariables::~LinkingVariables() = default; + +void ProgramLinkedResources::init(std::vector<InterfaceBlock> *uniformBlocksOut, + std::vector<LinkedUniform> *uniformsOut, + std::vector<InterfaceBlock> *shaderStorageBlocksOut, + std::vector<BufferVariable> *bufferVariablesOut, + std::vector<AtomicCounterBuffer> *atomicCounterBuffersOut) +{ + uniformBlockLinker.init(uniformBlocksOut, uniformsOut, &unusedInterfaceBlocks); + shaderStorageBlockLinker.init(shaderStorageBlocksOut, bufferVariablesOut, + &unusedInterfaceBlocks); + atomicCounterBufferLinker.init(atomicCounterBuffersOut); +} + +void ProgramLinkedResourcesLinker::linkResources(const Context *context, + const ProgramState &programState, + const ProgramLinkedResources &resources) const +{ + // Gather uniform interface block info. + InterfaceBlockInfo uniformBlockInfo(mCustomEncoderFactory); + for (const ShaderType shaderType : AllShaderTypes()) + { + Shader *shader = programState.getAttachedShader(shaderType); + if (shader) + { + uniformBlockInfo.getShaderBlockInfo(shader->getUniformBlocks(context)); + } + } + + auto getUniformBlockSize = [&uniformBlockInfo](const std::string &name, + const std::string &mappedName, size_t *sizeOut) { + return uniformBlockInfo.getBlockSize(name, mappedName, sizeOut); + }; + + auto getUniformBlockMemberInfo = [&uniformBlockInfo](const std::string &name, + const std::string &mappedName, + sh::BlockMemberInfo *infoOut) { + return uniformBlockInfo.getBlockMemberInfo(name, mappedName, infoOut); + }; + + // Link uniform interface blocks. + resources.uniformBlockLinker.linkBlocks(getUniformBlockSize, getUniformBlockMemberInfo); + + // Gather storage buffer interface block info. + InterfaceBlockInfo shaderStorageBlockInfo(mCustomEncoderFactory); + for (const ShaderType shaderType : AllShaderTypes()) + { + Shader *shader = programState.getAttachedShader(shaderType); + if (shader) + { + shaderStorageBlockInfo.getShaderBlockInfo(shader->getShaderStorageBlocks(context)); + } + } + auto getShaderStorageBlockSize = [&shaderStorageBlockInfo](const std::string &name, + const std::string &mappedName, + size_t *sizeOut) { + return shaderStorageBlockInfo.getBlockSize(name, mappedName, sizeOut); + }; + + auto getShaderStorageBlockMemberInfo = [&shaderStorageBlockInfo](const std::string &name, + const std::string &mappedName, + sh::BlockMemberInfo *infoOut) { + return shaderStorageBlockInfo.getBlockMemberInfo(name, mappedName, infoOut); + }; + + // Link storage buffer interface blocks. + resources.shaderStorageBlockLinker.linkBlocks(getShaderStorageBlockSize, + getShaderStorageBlockMemberInfo); + + // Gather and link atomic counter buffer interface blocks. + std::map<int, unsigned int> sizeMap; + getAtomicCounterBufferSizeMap(programState, sizeMap); + resources.atomicCounterBufferLinker.link(sizeMap); +} + +void ProgramLinkedResourcesLinker::getAtomicCounterBufferSizeMap( + const ProgramState &programState, + std::map<int, unsigned int> &sizeMapOut) const +{ + for (unsigned int index : programState.getAtomicCounterUniformRange()) + { + const LinkedUniform &glUniform = programState.getUniforms()[index]; + + auto &bufferDataSize = sizeMapOut[glUniform.binding]; + + // Calculate the size of the buffer by finding the end of the last uniform with the same + // binding. The end of the uniform is calculated by finding the initial offset of the + // uniform and adding size of the uniform. For arrays, the size is the number of elements + // times the element size (should always by 4 for atomic_units). + unsigned dataOffset = + glUniform.offset + static_cast<unsigned int>(glUniform.getBasicTypeElementCount() * + glUniform.getElementSize()); + if (dataOffset > bufferDataSize) + { + bufferDataSize = dataOffset; + } + } +} + +bool LinkValidateProgramGlobalNames(InfoLog &infoLog, + const ProgramExecutable &executable, + const LinkingVariables &linkingVariables) +{ + angle::HashMap<std::string, const sh::ShaderVariable *> uniformMap; + using BlockAndFieldPair = std::pair<const sh::InterfaceBlock *, const sh::ShaderVariable *>; + angle::HashMap<std::string, std::vector<BlockAndFieldPair>> uniformBlockFieldMap; + + for (ShaderType shaderType : kAllGraphicsShaderTypes) + { + if (!linkingVariables.isShaderStageUsedBitset[shaderType]) + { + continue; + } + + // Build a map of Uniforms + const std::vector<sh::ShaderVariable> &uniforms = linkingVariables.uniforms[shaderType]; + for (const auto &uniform : uniforms) + { + uniformMap[uniform.name] = &uniform; + } + + // Build a map of Uniform Blocks + // This will also detect any field name conflicts between Uniform Blocks without instance + // names + const std::vector<sh::InterfaceBlock> &uniformBlocks = + linkingVariables.uniformBlocks[shaderType]; + + for (const auto &uniformBlock : uniformBlocks) + { + // Only uniform blocks without an instance name can create a conflict with their field + // names + if (!uniformBlock.instanceName.empty()) + { + continue; + } + + for (const auto &field : uniformBlock.fields) + { + if (!uniformBlockFieldMap.count(field.name)) + { + // First time we've seen this uniform block field name, so add the + // (Uniform Block, Field) pair immediately since there can't be a conflict yet + BlockAndFieldPair blockAndFieldPair(&uniformBlock, &field); + std::vector<BlockAndFieldPair> newUniformBlockList; + newUniformBlockList.push_back(blockAndFieldPair); + uniformBlockFieldMap[field.name] = newUniformBlockList; + continue; + } + + // We've seen this name before. + // We need to check each of the uniform blocks that contain a field with this name + // to see if there's a conflict or not. + std::vector<BlockAndFieldPair> prevBlockFieldPairs = + uniformBlockFieldMap[field.name]; + for (const auto &prevBlockFieldPair : prevBlockFieldPairs) + { + const sh::InterfaceBlock *prevUniformBlock = prevBlockFieldPair.first; + const sh::ShaderVariable *prevUniformBlockField = prevBlockFieldPair.second; + + if (uniformBlock.isSameInterfaceBlockAtLinkTime(*prevUniformBlock)) + { + // The same uniform block should, by definition, contain the same field name + continue; + } + + // The uniform blocks don't match, so check if the necessary field properties + // also match + if ((field.name == prevUniformBlockField->name) && + (field.type == prevUniformBlockField->type) && + (field.precision == prevUniformBlockField->precision)) + { + infoLog << "Name conflicts between uniform block field names: " + << field.name; + return false; + } + } + + // No conflict, so record this pair + BlockAndFieldPair blockAndFieldPair(&uniformBlock, &field); + uniformBlockFieldMap[field.name].push_back(blockAndFieldPair); + } + } + } + + // Validate no uniform names conflict with attribute names + if (linkingVariables.isShaderStageUsedBitset[ShaderType::Vertex]) + { + // ESSL 3.00.6 section 4.3.5: + // If a uniform variable name is declared in one stage (e.g., a vertex shader) + // but not in another (e.g., a fragment shader), then that name is still + // available in the other stage for a different use. + std::unordered_set<std::string> uniforms; + for (const sh::ShaderVariable &uniform : linkingVariables.uniforms[ShaderType::Vertex]) + { + uniforms.insert(uniform.name); + } + for (const auto &attrib : executable.getProgramInputs()) + { + if (uniforms.count(attrib.name)) + { + infoLog << "Name conflicts between a uniform and an attribute: " << attrib.name; + return false; + } + } + } + + // Validate no Uniform Block fields conflict with other Uniforms + for (const auto &uniformBlockField : uniformBlockFieldMap) + { + const std::string &fieldName = uniformBlockField.first; + if (uniformMap.count(fieldName)) + { + infoLog << "Name conflicts between a uniform and a uniform block field: " << fieldName; + return false; + } + } + + return true; +} + +// [OpenGL ES 3.2] Chapter 7.4.1 "Shader Interface Matching" +bool LinkValidateShaderInterfaceMatching(const std::vector<sh::ShaderVariable> &outputVaryings, + const std::vector<sh::ShaderVariable> &inputVaryings, + ShaderType frontShaderType, + ShaderType backShaderType, + int frontShaderVersion, + int backShaderVersion, + bool isSeparable, + gl::InfoLog &infoLog) +{ + ASSERT(frontShaderVersion == backShaderVersion); + + std::vector<const sh::ShaderVariable *> filteredInputVaryings; + std::vector<const sh::ShaderVariable *> filteredOutputVaryings; + + GetFilteredVaryings(inputVaryings, &filteredInputVaryings); + GetFilteredVaryings(outputVaryings, &filteredOutputVaryings); + + // Separable programs require the number of inputs and outputs match + if (isSeparable && filteredInputVaryings.size() < filteredOutputVaryings.size()) + { + infoLog << GetShaderTypeString(backShaderType) + << " does not consume all varyings generated by " + << GetShaderTypeString(frontShaderType); + return false; + } + if (isSeparable && filteredInputVaryings.size() > filteredOutputVaryings.size()) + { + infoLog << GetShaderTypeString(frontShaderType) + << " does not generate all varyings consumed by " + << GetShaderTypeString(backShaderType); + return false; + } + + // All inputs must match all outputs + for (const sh::ShaderVariable *input : filteredInputVaryings) + { + bool match = false; + for (const sh::ShaderVariable *output : filteredOutputVaryings) + { + if (DoShaderVariablesMatch(frontShaderVersion, frontShaderType, backShaderType, *input, + *output, isSeparable, infoLog)) + { + match = true; + break; + } + } + + // We permit unmatched, unreferenced varyings. Note that this specifically depends on + // whether the input is statically used - a statically used input should fail this test even + // if it is not active. GLSL ES 3.00.6 section 4.3.10. + if (!match && input->staticUse) + { + const std::string &name = + input->isShaderIOBlock ? input->structOrBlockName : input->name; + infoLog << GetShaderTypeString(backShaderType) << " varying " << name + << " does not match any " << GetShaderTypeString(frontShaderType) << " varying"; + return false; + } + } + + return true; +} + +LinkMismatchError LinkValidateProgramVariables(const sh::ShaderVariable &variable1, + const sh::ShaderVariable &variable2, + bool validatePrecision, + bool treatVariable1AsNonArray, + bool treatVariable2AsNonArray, + std::string *mismatchedStructOrBlockMemberName) +{ + if (variable1.type != variable2.type) + { + return LinkMismatchError::TYPE_MISMATCH; + } + + bool variable1IsArray = variable1.isArray(); + bool variable2IsArray = variable2.isArray(); + if (treatVariable1AsNonArray) + { + ASSERT(variable1IsArray); + variable1IsArray = false; + } + if (treatVariable2AsNonArray) + { + ASSERT(variable2IsArray); + variable2IsArray = false; + } + // TODO(anglebug.com/5557): Investigate interactions with arrays-of-arrays. + if (variable1IsArray != variable2IsArray) + { + return LinkMismatchError::ARRAYNESS_MISMATCH; + } + if (!treatVariable1AsNonArray && !treatVariable2AsNonArray && + variable1.arraySizes != variable2.arraySizes) + { + return LinkMismatchError::ARRAY_SIZE_MISMATCH; + } + if (validatePrecision && variable1.precision != variable2.precision) + { + return LinkMismatchError::PRECISION_MISMATCH; + } + if (!variable1.isShaderIOBlock && !variable2.isShaderIOBlock && + variable1.structOrBlockName != variable2.structOrBlockName) + { + return LinkMismatchError::STRUCT_NAME_MISMATCH; + } + if (variable1.imageUnitFormat != variable2.imageUnitFormat) + { + return LinkMismatchError::FORMAT_MISMATCH; + } + + if (variable1.fields.size() != variable2.fields.size()) + { + return LinkMismatchError::FIELD_NUMBER_MISMATCH; + } + const unsigned int numMembers = static_cast<unsigned int>(variable1.fields.size()); + for (unsigned int memberIndex = 0; memberIndex < numMembers; memberIndex++) + { + const sh::ShaderVariable &member1 = variable1.fields[memberIndex]; + const sh::ShaderVariable &member2 = variable2.fields[memberIndex]; + + if (member1.name != member2.name) + { + return LinkMismatchError::FIELD_NAME_MISMATCH; + } + + if (member1.interpolation != member2.interpolation) + { + return LinkMismatchError::INTERPOLATION_TYPE_MISMATCH; + } + + if (variable1.isShaderIOBlock && variable2.isShaderIOBlock) + { + if (member1.location != member2.location) + { + return LinkMismatchError::FIELD_LOCATION_MISMATCH; + } + + if (member1.structOrBlockName != member2.structOrBlockName) + { + return LinkMismatchError::FIELD_STRUCT_NAME_MISMATCH; + } + } + + LinkMismatchError linkErrorOnField = LinkValidateProgramVariables( + member1, member2, validatePrecision, false, false, mismatchedStructOrBlockMemberName); + if (linkErrorOnField != LinkMismatchError::NO_MISMATCH) + { + AddProgramVariableParentPrefix(member1.name, mismatchedStructOrBlockMemberName); + return linkErrorOnField; + } + } + + return LinkMismatchError::NO_MISMATCH; +} + +void AddProgramVariableParentPrefix(const std::string &parentName, std::string *mismatchedFieldName) +{ + ASSERT(mismatchedFieldName); + if (mismatchedFieldName->empty()) + { + *mismatchedFieldName = parentName; + } + else + { + std::ostringstream stream; + stream << parentName << "." << *mismatchedFieldName; + *mismatchedFieldName = stream.str(); + } +} + +bool LinkValidateBuiltInVaryingsInvariant(const std::vector<sh::ShaderVariable> &vertexVaryings, + const std::vector<sh::ShaderVariable> &fragmentVaryings, + int vertexShaderVersion, + InfoLog &infoLog) +{ + bool glPositionIsInvariant = false; + bool glPointSizeIsInvariant = false; + bool glFragCoordIsInvariant = false; + bool glPointCoordIsInvariant = false; + + for (const sh::ShaderVariable &varying : vertexVaryings) + { + if (!varying.isBuiltIn()) + { + continue; + } + if (varying.name.compare("gl_Position") == 0) + { + glPositionIsInvariant = varying.isInvariant; + } + else if (varying.name.compare("gl_PointSize") == 0) + { + glPointSizeIsInvariant = varying.isInvariant; + } + } + + for (const sh::ShaderVariable &varying : fragmentVaryings) + { + if (!varying.isBuiltIn()) + { + continue; + } + if (varying.name.compare("gl_FragCoord") == 0) + { + glFragCoordIsInvariant = varying.isInvariant; + } + else if (varying.name.compare("gl_PointCoord") == 0) + { + glPointCoordIsInvariant = varying.isInvariant; + } + } + + // There is some ambiguity in ESSL 1.00.17 paragraph 4.6.4 interpretation, + // for example, https://cvs.khronos.org/bugzilla/show_bug.cgi?id=13842. + // Not requiring invariance to match is supported by: + // dEQP, WebGL CTS, Nexus 5X GLES + if (glFragCoordIsInvariant && !glPositionIsInvariant) + { + infoLog << "gl_FragCoord can only be declared invariant if and only if gl_Position is " + "declared invariant."; + return false; + } + if (glPointCoordIsInvariant && !glPointSizeIsInvariant) + { + infoLog << "gl_PointCoord can only be declared invariant if and only if gl_PointSize is " + "declared invariant."; + return false; + } + + return true; +} + +bool LinkValidateBuiltInVaryings(const std::vector<sh::ShaderVariable> &outputVaryings, + const std::vector<sh::ShaderVariable> &inputVaryings, + ShaderType outputShaderType, + ShaderType inputShaderType, + int outputShaderVersion, + int inputShaderVersion, + InfoLog &infoLog) +{ + ASSERT(outputShaderVersion == inputShaderVersion); + + // Only ESSL 1.0 has restrictions on matching input and output invariance + if (inputShaderVersion == 100 && outputShaderType == ShaderType::Vertex && + inputShaderType == ShaderType::Fragment) + { + return LinkValidateBuiltInVaryingsInvariant(outputVaryings, inputVaryings, + outputShaderVersion, infoLog); + } + + uint32_t sizeClipDistance = 0; + uint32_t sizeCullDistance = 0; + + for (const sh::ShaderVariable &varying : outputVaryings) + { + if (!varying.isBuiltIn()) + { + continue; + } + if (varying.name.compare("gl_ClipDistance") == 0) + { + sizeClipDistance = varying.getOutermostArraySize(); + } + else if (varying.name.compare("gl_CullDistance") == 0) + { + sizeCullDistance = varying.getOutermostArraySize(); + } + } + + for (const sh::ShaderVariable &varying : inputVaryings) + { + if (!varying.isBuiltIn()) + { + continue; + } + if (varying.name.compare("gl_ClipDistance") == 0) + { + if (sizeClipDistance != varying.getOutermostArraySize()) + { + infoLog << "If either shader redeclares the built-in arrays gl_ClipDistance[] the " + "array must have the same size in both shaders."; + return false; + } + } + else if (varying.name.compare("gl_CullDistance") == 0) + { + if (sizeCullDistance != varying.getOutermostArraySize()) + { + infoLog << "If either shader redeclares the built-in arrays gl_CullDistance[] the " + "array must have the same size in both shaders."; + return false; + } + } + } + return true; +} + +void LogAmbiguousFieldLinkMismatch(InfoLog &infoLog, + const std::string &blockName1, + const std::string &blockName2, + const std::string &fieldName, + ShaderType shaderType1, + ShaderType shaderType2) +{ + infoLog << "Ambiguous field '" << fieldName << "' in blocks '" << blockName1 << "' (" + << GetShaderTypeString(shaderType1) << " shader) and '" << blockName2 << "' (" + << GetShaderTypeString(shaderType2) << " shader) which don't have instance names."; +} + +bool ValidateInstancelessGraphicsInterfaceBlocksPerShader( + const std::vector<sh::InterfaceBlock> &interfaceBlocks, + ShaderType shaderType, + InterfaceBlockMap *instancelessBlocksFields, + InfoLog &infoLog) +{ + ASSERT(instancelessBlocksFields); + + for (const sh::InterfaceBlock &block : interfaceBlocks) + { + if (!block.instanceName.empty()) + { + continue; + } + + for (const sh::ShaderVariable &field : block.fields) + { + const auto &entry = instancelessBlocksFields->find(field.name); + if (entry != instancelessBlocksFields->end()) + { + const sh::InterfaceBlock &linkedBlock = *(entry->second.second); + if (block.name != linkedBlock.name) + { + LogAmbiguousFieldLinkMismatch(infoLog, block.name, linkedBlock.name, field.name, + entry->second.first, shaderType); + return false; + } + } + else + { + (*instancelessBlocksFields)[field.name] = std::make_pair(shaderType, &block); + } + } + } + + return true; +} + +LinkMismatchError LinkValidateInterfaceBlockFields(const sh::ShaderVariable &blockField1, + const sh::ShaderVariable &blockField2, + bool webglCompatibility, + std::string *mismatchedBlockFieldName) +{ + if (blockField1.name != blockField2.name) + { + return LinkMismatchError::FIELD_NAME_MISMATCH; + } + + // If webgl, validate precision of UBO fields, otherwise don't. See Khronos bug 10287. + LinkMismatchError linkError = LinkValidateProgramVariables( + blockField1, blockField2, webglCompatibility, false, false, mismatchedBlockFieldName); + if (linkError != LinkMismatchError::NO_MISMATCH) + { + AddProgramVariableParentPrefix(blockField1.name, mismatchedBlockFieldName); + return linkError; + } + + if (blockField1.isRowMajorLayout != blockField2.isRowMajorLayout) + { + AddProgramVariableParentPrefix(blockField1.name, mismatchedBlockFieldName); + return LinkMismatchError::MATRIX_PACKING_MISMATCH; + } + + return LinkMismatchError::NO_MISMATCH; +} + +LinkMismatchError AreMatchingInterfaceBlocks(const sh::InterfaceBlock &interfaceBlock1, + const sh::InterfaceBlock &interfaceBlock2, + bool webglCompatibility, + std::string *mismatchedBlockFieldName) +{ + // validate blocks for the same member types + if (interfaceBlock1.fields.size() != interfaceBlock2.fields.size()) + { + return LinkMismatchError::FIELD_NUMBER_MISMATCH; + } + if (interfaceBlock1.arraySize != interfaceBlock2.arraySize) + { + return LinkMismatchError::ARRAY_SIZE_MISMATCH; + } + if (interfaceBlock1.layout != interfaceBlock2.layout || + interfaceBlock1.binding != interfaceBlock2.binding) + { + return LinkMismatchError::LAYOUT_QUALIFIER_MISMATCH; + } + if (interfaceBlock1.instanceName.empty() != interfaceBlock2.instanceName.empty()) + { + return LinkMismatchError::INSTANCE_NAME_MISMATCH; + } + const unsigned int numBlockMembers = static_cast<unsigned int>(interfaceBlock1.fields.size()); + for (unsigned int blockMemberIndex = 0; blockMemberIndex < numBlockMembers; blockMemberIndex++) + { + const sh::ShaderVariable &member1 = interfaceBlock1.fields[blockMemberIndex]; + const sh::ShaderVariable &member2 = interfaceBlock2.fields[blockMemberIndex]; + + LinkMismatchError linkError = LinkValidateInterfaceBlockFields( + member1, member2, webglCompatibility, mismatchedBlockFieldName); + if (linkError != LinkMismatchError::NO_MISMATCH) + { + return linkError; + } + } + return LinkMismatchError::NO_MISMATCH; +} + +void InitializeInterfaceBlockMap(const std::vector<sh::InterfaceBlock> &interfaceBlocks, + ShaderType shaderType, + InterfaceBlockMap *linkedInterfaceBlocks) +{ + ASSERT(linkedInterfaceBlocks); + + for (const sh::InterfaceBlock &interfaceBlock : interfaceBlocks) + { + (*linkedInterfaceBlocks)[interfaceBlock.name] = std::make_pair(shaderType, &interfaceBlock); + } +} + +bool ValidateGraphicsInterfaceBlocksPerShader( + const std::vector<sh::InterfaceBlock> &interfaceBlocksToLink, + ShaderType shaderType, + bool webglCompatibility, + InterfaceBlockMap *linkedBlocks, + InfoLog &infoLog) +{ + ASSERT(linkedBlocks); + + for (const sh::InterfaceBlock &block : interfaceBlocksToLink) + { + const auto &entry = linkedBlocks->find(block.name); + if (entry != linkedBlocks->end()) + { + const sh::InterfaceBlock &linkedBlock = *(entry->second.second); + std::string mismatchedStructFieldName; + LinkMismatchError linkError = AreMatchingInterfaceBlocks( + block, linkedBlock, webglCompatibility, &mismatchedStructFieldName); + if (linkError != LinkMismatchError::NO_MISMATCH) + { + LogLinkMismatch(infoLog, block.name, GetInterfaceBlockTypeString(block.blockType), + linkError, mismatchedStructFieldName, entry->second.first, + shaderType); + return false; + } + } + else + { + (*linkedBlocks)[block.name] = std::make_pair(shaderType, &block); + } + } + + return true; +} + +bool ValidateInterfaceBlocksMatch( + GLuint numShadersHasInterfaceBlocks, + const ShaderMap<const std::vector<sh::InterfaceBlock> *> &shaderInterfaceBlocks, + InfoLog &infoLog, + bool webglCompatibility, + InterfaceBlockMap *instancelessInterfaceBlocksFields) +{ + for (ShaderType shaderType : kAllGraphicsShaderTypes) + { + // Validate that instanceless blocks of different names don't have fields of the same name. + if (shaderInterfaceBlocks[shaderType] && + !ValidateInstancelessGraphicsInterfaceBlocksPerShader( + *shaderInterfaceBlocks[shaderType], shaderType, instancelessInterfaceBlocksFields, + infoLog)) + { + return false; + } + } + + if (numShadersHasInterfaceBlocks < 2u) + { + return true; + } + + ASSERT(!shaderInterfaceBlocks[ShaderType::Compute]); + + // Check that interface blocks defined in the graphics shaders are identical + + InterfaceBlockMap linkedInterfaceBlocks; + + bool interfaceBlockMapInitialized = false; + for (ShaderType shaderType : kAllGraphicsShaderTypes) + { + if (!shaderInterfaceBlocks[shaderType]) + { + continue; + } + + if (!interfaceBlockMapInitialized) + { + InitializeInterfaceBlockMap(*shaderInterfaceBlocks[shaderType], shaderType, + &linkedInterfaceBlocks); + interfaceBlockMapInitialized = true; + } + else if (!ValidateGraphicsInterfaceBlocksPerShader(*shaderInterfaceBlocks[shaderType], + shaderType, webglCompatibility, + &linkedInterfaceBlocks, infoLog)) + { + return false; + } + } + + return true; +} + +bool LinkValidateProgramInterfaceBlocks(const Context *context, + ShaderBitSet activeProgramStages, + const ProgramLinkedResources &resources, + InfoLog &infoLog, + GLuint *combinedShaderStorageBlocksCountOut) +{ + ASSERT(combinedShaderStorageBlocksCountOut); + + const Caps &caps = context->getCaps(); + const bool webglCompatibility = context->isWebGL(); + const Version &version = context->getClientVersion(); + + GLuint combinedUniformBlocksCount = 0u; + GLuint numShadersHasUniformBlocks = 0u; + ShaderMap<const std::vector<sh::InterfaceBlock> *> allShaderUniformBlocks = {}; + InterfaceBlockMap instancelessInterfaceBlocksFields; + + for (ShaderType shaderType : activeProgramStages) + { + const std::vector<sh::InterfaceBlock> &uniformBlocks = + resources.uniformBlockLinker.getShaderBlocks(shaderType); + if (!uniformBlocks.empty()) + { + if (!ValidateInterfaceBlocksCount( + static_cast<GLuint>(caps.maxShaderUniformBlocks[shaderType]), uniformBlocks, + shaderType, sh::BlockType::BLOCK_UNIFORM, &combinedUniformBlocksCount, infoLog)) + { + return false; + } + + allShaderUniformBlocks[shaderType] = &uniformBlocks; + ++numShadersHasUniformBlocks; + } + } + + if (combinedUniformBlocksCount > static_cast<GLuint>(caps.maxCombinedUniformBlocks)) + { + infoLog << "The sum of the number of active uniform blocks exceeds " + "MAX_COMBINED_UNIFORM_BLOCKS (" + << caps.maxCombinedUniformBlocks << ")."; + return false; + } + + if (!ValidateInterfaceBlocksMatch(numShadersHasUniformBlocks, allShaderUniformBlocks, infoLog, + webglCompatibility, &instancelessInterfaceBlocksFields)) + { + return false; + } + + if (version >= Version(3, 1)) + { + *combinedShaderStorageBlocksCountOut = 0u; + GLuint numShadersHasShaderStorageBlocks = 0u; + ShaderMap<const std::vector<sh::InterfaceBlock> *> allShaderStorageBlocks = {}; + for (ShaderType shaderType : activeProgramStages) + { + const std::vector<sh::InterfaceBlock> &shaderStorageBlocks = + resources.shaderStorageBlockLinker.getShaderBlocks(shaderType); + if (!shaderStorageBlocks.empty()) + { + if (!ValidateInterfaceBlocksCount( + static_cast<GLuint>(caps.maxShaderStorageBlocks[shaderType]), + shaderStorageBlocks, shaderType, sh::BlockType::BLOCK_BUFFER, + combinedShaderStorageBlocksCountOut, infoLog)) + { + return false; + } + + allShaderStorageBlocks[shaderType] = &shaderStorageBlocks; + ++numShadersHasShaderStorageBlocks; + } + } + + if (*combinedShaderStorageBlocksCountOut > + static_cast<GLuint>(caps.maxCombinedShaderStorageBlocks)) + { + infoLog << "The sum of the number of active shader storage blocks exceeds " + "MAX_COMBINED_SHADER_STORAGE_BLOCKS (" + << caps.maxCombinedShaderStorageBlocks << ")."; + return false; + } + + if (!ValidateInterfaceBlocksMatch(numShadersHasShaderStorageBlocks, allShaderStorageBlocks, + infoLog, webglCompatibility, + &instancelessInterfaceBlocksFields)) + { + return false; + } + } + + return true; +} + +} // namespace gl |