| // |
| // Copyright (c) 2002-2016 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. |
| // |
| |
| #include "compiler/translator/QualifierTypes.h" |
| |
| #include "compiler/translator/Diagnostics.h" |
| |
| #include <algorithm> |
| |
| namespace sh |
| { |
| |
| namespace |
| { |
| |
| // GLSL ES 3.10 does not impose a strict order on type qualifiers and allows multiple layout |
| // declarations. |
| // GLSL ES 3.10 Revision 4, 4.10 Order of Qualification |
| bool AreTypeQualifierChecksRelaxed(int shaderVersion) |
| { |
| return shaderVersion >= 310; |
| } |
| |
| bool IsScopeQualifier(TQualifier qualifier) |
| { |
| return qualifier == EvqGlobal || qualifier == EvqTemporary; |
| } |
| |
| bool IsScopeQualifierWrapper(const TQualifierWrapperBase *qualifier) |
| { |
| if (qualifier->getType() != QtStorage) |
| return false; |
| const TStorageQualifierWrapper *storageQualifier = |
| static_cast<const TStorageQualifierWrapper *>(qualifier); |
| TQualifier q = storageQualifier->getQualifier(); |
| return IsScopeQualifier(q); |
| } |
| |
| // Returns true if the invariant for the qualifier sequence holds |
| bool IsInvariantCorrect(const TTypeQualifierBuilder::QualifierSequence &qualifiers) |
| { |
| // We should have at least one qualifier. |
| // The first qualifier always tells the scope. |
| return qualifiers.size() >= 1 && IsScopeQualifierWrapper(qualifiers[0]); |
| } |
| |
| // Returns true if there are qualifiers which have been specified multiple times |
| // If areQualifierChecksRelaxed is set to true, then layout qualifier repetition is allowed. |
| bool HasRepeatingQualifiers(const TTypeQualifierBuilder::QualifierSequence &qualifiers, |
| bool areQualifierChecksRelaxed, |
| std::string *errorMessage) |
| { |
| bool invariantFound = false; |
| bool precisionFound = false; |
| bool layoutFound = false; |
| bool interpolationFound = false; |
| |
| unsigned int locationsSpecified = 0; |
| bool isOut = false; |
| |
| // The iteration starts from one since the first qualifier only reveals the scope of the |
| // expression. It is inserted first whenever the sequence gets created. |
| for (size_t i = 1; i < qualifiers.size(); ++i) |
| { |
| switch (qualifiers[i]->getType()) |
| { |
| case QtInvariant: |
| { |
| if (invariantFound) |
| { |
| *errorMessage = "The invariant qualifier specified multiple times."; |
| return true; |
| } |
| invariantFound = true; |
| break; |
| } |
| case QtPrecision: |
| { |
| if (precisionFound) |
| { |
| *errorMessage = "The precision qualifier specified multiple times."; |
| return true; |
| } |
| precisionFound = true; |
| break; |
| } |
| case QtLayout: |
| { |
| if (layoutFound && !areQualifierChecksRelaxed) |
| { |
| *errorMessage = "The layout qualifier specified multiple times."; |
| return true; |
| } |
| if (invariantFound && !areQualifierChecksRelaxed) |
| { |
| // This combination is not correct according to the syntax specified in the |
| // formal grammar in the ESSL 3.00 spec. In ESSL 3.10 the grammar does not have |
| // a similar restriction. |
| *errorMessage = |
| "The layout qualifier and invariant qualifier cannot coexist in the same " |
| "declaration according to the grammar."; |
| return true; |
| } |
| layoutFound = true; |
| const TLayoutQualifier ¤tQualifier = |
| static_cast<const TLayoutQualifierWrapper *>(qualifiers[i])->getQualifier(); |
| locationsSpecified += currentQualifier.locationsSpecified; |
| break; |
| } |
| case QtInterpolation: |
| { |
| // 'centroid' is treated as a storage qualifier |
| // 'flat centroid' will be squashed to 'flat' |
| // 'smooth centroid' will be squashed to 'centroid' |
| if (interpolationFound) |
| { |
| *errorMessage = "The interpolation qualifier specified multiple times."; |
| return true; |
| } |
| interpolationFound = true; |
| break; |
| } |
| case QtStorage: |
| { |
| // Go over all of the storage qualifiers up until the current one and check for |
| // repetitions. |
| TQualifier currentQualifier = |
| static_cast<const TStorageQualifierWrapper *>(qualifiers[i])->getQualifier(); |
| if (currentQualifier == EvqVertexOut || currentQualifier == EvqFragmentOut) |
| { |
| isOut = true; |
| } |
| for (size_t j = 1; j < i; ++j) |
| { |
| if (qualifiers[j]->getType() == QtStorage) |
| { |
| const TStorageQualifierWrapper *previousQualifierWrapper = |
| static_cast<const TStorageQualifierWrapper *>(qualifiers[j]); |
| TQualifier previousQualifier = previousQualifierWrapper->getQualifier(); |
| if (currentQualifier == previousQualifier) |
| { |
| *errorMessage = previousQualifierWrapper->getQualifierString().c_str(); |
| *errorMessage += " specified multiple times"; |
| return true; |
| } |
| } |
| } |
| break; |
| } |
| case QtMemory: |
| { |
| // Go over all of the memory qualifiers up until the current one and check for |
| // repetitions. |
| // Having both readonly and writeonly in a sequence is valid. |
| // GLSL ES 3.10 Revision 4, 4.9 Memory Access Qualifiers |
| TQualifier currentQualifier = |
| static_cast<const TMemoryQualifierWrapper *>(qualifiers[i])->getQualifier(); |
| for (size_t j = 1; j < i; ++j) |
| { |
| if (qualifiers[j]->getType() == QtMemory) |
| { |
| const TMemoryQualifierWrapper *previousQualifierWrapper = |
| static_cast<const TMemoryQualifierWrapper *>(qualifiers[j]); |
| TQualifier previousQualifier = previousQualifierWrapper->getQualifier(); |
| if (currentQualifier == previousQualifier) |
| { |
| *errorMessage = previousQualifierWrapper->getQualifierString().c_str(); |
| *errorMessage += " specified multiple times"; |
| return true; |
| } |
| } |
| } |
| break; |
| } |
| default: |
| UNREACHABLE(); |
| } |
| } |
| |
| if (locationsSpecified > 1 && isOut) |
| { |
| // GLSL ES 3.00.6 section 4.3.8.2 Output Layout Qualifiers |
| // GLSL ES 3.10 section 4.4.2 Output Layout Qualifiers |
| // "The qualifier may appear at most once within a declaration." |
| *errorMessage = "Output layout location specified multiple times."; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| // GLSL ES 3.00_6, 4.7 Order of Qualification |
| // The correct order of qualifiers is: |
| // invariant-qualifier interpolation-qualifier storage-qualifier precision-qualifier |
| // layout-qualifier has to be before storage-qualifier. |
| bool AreQualifiersInOrder(const TTypeQualifierBuilder::QualifierSequence &qualifiers, |
| std::string *errorMessage) |
| { |
| bool foundInterpolation = false; |
| bool foundStorage = false; |
| bool foundPrecision = false; |
| for (size_t i = 1; i < qualifiers.size(); ++i) |
| { |
| switch (qualifiers[i]->getType()) |
| { |
| case QtInvariant: |
| if (foundInterpolation || foundStorage || foundPrecision) |
| { |
| *errorMessage = "The invariant qualifier has to be first in the expression."; |
| return false; |
| } |
| break; |
| case QtInterpolation: |
| if (foundStorage) |
| { |
| *errorMessage = "Storage qualifiers have to be after interpolation qualifiers."; |
| return false; |
| } |
| else if (foundPrecision) |
| { |
| *errorMessage = |
| "Precision qualifiers have to be after interpolation qualifiers."; |
| return false; |
| } |
| foundInterpolation = true; |
| break; |
| case QtLayout: |
| if (foundStorage) |
| { |
| *errorMessage = "Storage qualifiers have to be after layout qualifiers."; |
| return false; |
| } |
| else if (foundPrecision) |
| { |
| *errorMessage = "Precision qualifiers have to be after layout qualifiers."; |
| return false; |
| } |
| break; |
| case QtStorage: |
| if (foundPrecision) |
| { |
| *errorMessage = "Precision qualifiers have to be after storage qualifiers."; |
| return false; |
| } |
| foundStorage = true; |
| break; |
| case QtMemory: |
| if (foundPrecision) |
| { |
| *errorMessage = "Precision qualifiers have to be after memory qualifiers."; |
| return false; |
| } |
| break; |
| case QtPrecision: |
| foundPrecision = true; |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| } |
| return true; |
| } |
| |
| struct QualifierComparator |
| { |
| bool operator()(const TQualifierWrapperBase *q1, const TQualifierWrapperBase *q2) |
| { |
| return q1->getRank() < q2->getRank(); |
| } |
| }; |
| |
| void SortSequence(TTypeQualifierBuilder::QualifierSequence &qualifiers) |
| { |
| // We need a stable sorting algorithm since the order of layout-qualifier declarations matter. |
| // The sorting starts from index 1, instead of 0, since the element at index 0 tells the scope |
| // and we always want it to be first. |
| std::stable_sort(qualifiers.begin() + 1, qualifiers.end(), QualifierComparator()); |
| } |
| |
| // Handles the joining of storage qualifiers for variables. |
| bool JoinVariableStorageQualifier(TQualifier *joinedQualifier, TQualifier storageQualifier) |
| { |
| switch (*joinedQualifier) |
| { |
| case EvqGlobal: |
| *joinedQualifier = storageQualifier; |
| break; |
| case EvqTemporary: |
| { |
| switch (storageQualifier) |
| { |
| case EvqConst: |
| *joinedQualifier = storageQualifier; |
| break; |
| default: |
| return false; |
| } |
| break; |
| } |
| case EvqSmooth: |
| { |
| switch (storageQualifier) |
| { |
| case EvqCentroid: |
| *joinedQualifier = EvqCentroid; |
| break; |
| case EvqVertexOut: |
| *joinedQualifier = EvqSmoothOut; |
| break; |
| case EvqFragmentIn: |
| *joinedQualifier = EvqSmoothIn; |
| break; |
| default: |
| return false; |
| } |
| break; |
| } |
| case EvqFlat: |
| { |
| switch (storageQualifier) |
| { |
| case EvqCentroid: |
| *joinedQualifier = EvqFlat; |
| break; |
| case EvqVertexOut: |
| *joinedQualifier = EvqFlatOut; |
| break; |
| case EvqFragmentIn: |
| *joinedQualifier = EvqFlatIn; |
| break; |
| default: |
| return false; |
| } |
| break; |
| } |
| case EvqCentroid: |
| { |
| switch (storageQualifier) |
| { |
| case EvqVertexOut: |
| *joinedQualifier = EvqCentroidOut; |
| break; |
| case EvqFragmentIn: |
| *joinedQualifier = EvqCentroidIn; |
| break; |
| default: |
| return false; |
| } |
| break; |
| } |
| default: |
| return false; |
| } |
| return true; |
| } |
| |
| // Handles the joining of storage qualifiers for a parameter in a function. |
| bool JoinParameterStorageQualifier(TQualifier *joinedQualifier, TQualifier storageQualifier) |
| { |
| switch (*joinedQualifier) |
| { |
| case EvqTemporary: |
| *joinedQualifier = storageQualifier; |
| break; |
| case EvqConst: |
| { |
| switch (storageQualifier) |
| { |
| case EvqIn: |
| *joinedQualifier = EvqConstReadOnly; |
| break; |
| default: |
| return false; |
| } |
| break; |
| } |
| default: |
| return false; |
| } |
| return true; |
| } |
| |
| bool JoinMemoryQualifier(TMemoryQualifier *joinedMemoryQualifier, TQualifier memoryQualifier) |
| { |
| switch (memoryQualifier) |
| { |
| case EvqReadOnly: |
| joinedMemoryQualifier->readonly = true; |
| break; |
| case EvqWriteOnly: |
| joinedMemoryQualifier->writeonly = true; |
| break; |
| case EvqCoherent: |
| joinedMemoryQualifier->coherent = true; |
| break; |
| case EvqRestrict: |
| joinedMemoryQualifier->restrictQualifier = true; |
| break; |
| case EvqVolatile: |
| // Variables having the volatile qualifier are automatcally treated as coherent as well. |
| // GLSL ES 3.10, Revision 4, 4.9 Memory Access Qualifiers |
| joinedMemoryQualifier->volatileQualifier = true; |
| joinedMemoryQualifier->coherent = true; |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| return true; |
| } |
| |
| TTypeQualifier GetVariableTypeQualifierFromSortedSequence( |
| const TTypeQualifierBuilder::QualifierSequence &sortedSequence, |
| TDiagnostics *diagnostics) |
| { |
| TTypeQualifier typeQualifier( |
| static_cast<const TStorageQualifierWrapper *>(sortedSequence[0])->getQualifier(), |
| sortedSequence[0]->getLine()); |
| for (size_t i = 1; i < sortedSequence.size(); ++i) |
| { |
| const TQualifierWrapperBase *qualifier = sortedSequence[i]; |
| bool isQualifierValid = false; |
| switch (qualifier->getType()) |
| { |
| case QtInvariant: |
| isQualifierValid = true; |
| typeQualifier.invariant = true; |
| break; |
| case QtInterpolation: |
| { |
| switch (typeQualifier.qualifier) |
| { |
| case EvqGlobal: |
| isQualifierValid = true; |
| typeQualifier.qualifier = |
| static_cast<const TInterpolationQualifierWrapper *>(qualifier) |
| ->getQualifier(); |
| break; |
| default: |
| isQualifierValid = false; |
| } |
| break; |
| } |
| case QtLayout: |
| { |
| const TLayoutQualifierWrapper *layoutQualifierWrapper = |
| static_cast<const TLayoutQualifierWrapper *>(qualifier); |
| isQualifierValid = true; |
| typeQualifier.layoutQualifier = sh::JoinLayoutQualifiers( |
| typeQualifier.layoutQualifier, layoutQualifierWrapper->getQualifier(), |
| layoutQualifierWrapper->getLine(), diagnostics); |
| break; |
| } |
| case QtStorage: |
| isQualifierValid = JoinVariableStorageQualifier( |
| &typeQualifier.qualifier, |
| static_cast<const TStorageQualifierWrapper *>(qualifier)->getQualifier()); |
| break; |
| case QtPrecision: |
| isQualifierValid = true; |
| typeQualifier.precision = |
| static_cast<const TPrecisionQualifierWrapper *>(qualifier)->getQualifier(); |
| ASSERT(typeQualifier.precision != EbpUndefined); |
| break; |
| case QtMemory: |
| isQualifierValid = JoinMemoryQualifier( |
| &typeQualifier.memoryQualifier, |
| static_cast<const TMemoryQualifierWrapper *>(qualifier)->getQualifier()); |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| if (!isQualifierValid) |
| { |
| const TString &qualifierString = qualifier->getQualifierString(); |
| diagnostics->error(qualifier->getLine(), "invalid qualifier combination", |
| qualifierString.c_str()); |
| break; |
| } |
| } |
| return typeQualifier; |
| } |
| |
| TTypeQualifier GetParameterTypeQualifierFromSortedSequence( |
| const TTypeQualifierBuilder::QualifierSequence &sortedSequence, |
| TDiagnostics *diagnostics) |
| { |
| TTypeQualifier typeQualifier(EvqTemporary, sortedSequence[0]->getLine()); |
| for (size_t i = 1; i < sortedSequence.size(); ++i) |
| { |
| const TQualifierWrapperBase *qualifier = sortedSequence[i]; |
| bool isQualifierValid = false; |
| switch (qualifier->getType()) |
| { |
| case QtInvariant: |
| case QtInterpolation: |
| case QtLayout: |
| break; |
| case QtMemory: |
| isQualifierValid = JoinMemoryQualifier( |
| &typeQualifier.memoryQualifier, |
| static_cast<const TMemoryQualifierWrapper *>(qualifier)->getQualifier()); |
| break; |
| case QtStorage: |
| isQualifierValid = JoinParameterStorageQualifier( |
| &typeQualifier.qualifier, |
| static_cast<const TStorageQualifierWrapper *>(qualifier)->getQualifier()); |
| break; |
| case QtPrecision: |
| isQualifierValid = true; |
| typeQualifier.precision = |
| static_cast<const TPrecisionQualifierWrapper *>(qualifier)->getQualifier(); |
| ASSERT(typeQualifier.precision != EbpUndefined); |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| if (!isQualifierValid) |
| { |
| const TString &qualifierString = qualifier->getQualifierString(); |
| diagnostics->error(qualifier->getLine(), "invalid parameter qualifier", |
| qualifierString.c_str()); |
| break; |
| } |
| } |
| |
| switch (typeQualifier.qualifier) |
| { |
| case EvqIn: |
| case EvqConstReadOnly: // const in |
| case EvqOut: |
| case EvqInOut: |
| break; |
| case EvqConst: |
| typeQualifier.qualifier = EvqConstReadOnly; |
| break; |
| case EvqTemporary: |
| // no qualifier has been specified, set it to EvqIn which is the default |
| typeQualifier.qualifier = EvqIn; |
| break; |
| default: |
| diagnostics->error(sortedSequence[0]->getLine(), "Invalid parameter qualifier ", |
| getQualifierString(typeQualifier.qualifier)); |
| } |
| return typeQualifier; |
| } |
| } // namespace |
| |
| TLayoutQualifier JoinLayoutQualifiers(TLayoutQualifier leftQualifier, |
| TLayoutQualifier rightQualifier, |
| const TSourceLoc &rightQualifierLocation, |
| TDiagnostics *diagnostics) |
| { |
| TLayoutQualifier joinedQualifier = leftQualifier; |
| |
| if (rightQualifier.location != -1) |
| { |
| joinedQualifier.location = rightQualifier.location; |
| ++joinedQualifier.locationsSpecified; |
| } |
| if (rightQualifier.yuv != false) |
| { |
| joinedQualifier.yuv = rightQualifier.yuv; |
| } |
| if (rightQualifier.binding != -1) |
| { |
| joinedQualifier.binding = rightQualifier.binding; |
| } |
| if (rightQualifier.matrixPacking != EmpUnspecified) |
| { |
| joinedQualifier.matrixPacking = rightQualifier.matrixPacking; |
| } |
| if (rightQualifier.blockStorage != EbsUnspecified) |
| { |
| joinedQualifier.blockStorage = rightQualifier.blockStorage; |
| } |
| |
| for (size_t i = 0u; i < rightQualifier.localSize.size(); ++i) |
| { |
| if (rightQualifier.localSize[i] != -1) |
| { |
| if (joinedQualifier.localSize[i] != -1 && |
| joinedQualifier.localSize[i] != rightQualifier.localSize[i]) |
| { |
| diagnostics->error(rightQualifierLocation, |
| "Cannot have multiple different work group size specifiers", |
| getWorkGroupSizeString(i)); |
| } |
| joinedQualifier.localSize[i] = rightQualifier.localSize[i]; |
| } |
| } |
| |
| if (rightQualifier.numViews != -1) |
| { |
| joinedQualifier.numViews = rightQualifier.numViews; |
| } |
| |
| if (rightQualifier.imageInternalFormat != EiifUnspecified) |
| { |
| joinedQualifier.imageInternalFormat = rightQualifier.imageInternalFormat; |
| } |
| |
| return joinedQualifier; |
| } |
| |
| unsigned int TInvariantQualifierWrapper::getRank() const |
| { |
| return 0u; |
| } |
| |
| unsigned int TInterpolationQualifierWrapper::getRank() const |
| { |
| return 1u; |
| } |
| |
| unsigned int TLayoutQualifierWrapper::getRank() const |
| { |
| return 2u; |
| } |
| |
| unsigned int TStorageQualifierWrapper::getRank() const |
| { |
| // Force the 'centroid' auxilary storage qualifier to be always first among all storage |
| // qualifiers. |
| if (mStorageQualifier == EvqCentroid) |
| { |
| return 3u; |
| } |
| else |
| { |
| return 4u; |
| } |
| } |
| |
| unsigned int TMemoryQualifierWrapper::getRank() const |
| { |
| return 4u; |
| } |
| |
| unsigned int TPrecisionQualifierWrapper::getRank() const |
| { |
| return 5u; |
| } |
| |
| TTypeQualifier::TTypeQualifier(TQualifier scope, const TSourceLoc &loc) |
| : layoutQualifier(TLayoutQualifier::create()), |
| memoryQualifier(TMemoryQualifier::create()), |
| precision(EbpUndefined), |
| qualifier(scope), |
| invariant(false), |
| line(loc) |
| { |
| ASSERT(IsScopeQualifier(qualifier)); |
| } |
| |
| TTypeQualifierBuilder::TTypeQualifierBuilder(const TStorageQualifierWrapper *scope, |
| int shaderVersion) |
| : mShaderVersion(shaderVersion) |
| { |
| ASSERT(IsScopeQualifier(scope->getQualifier())); |
| mQualifiers.push_back(scope); |
| } |
| |
| void TTypeQualifierBuilder::appendQualifier(const TQualifierWrapperBase *qualifier) |
| { |
| mQualifiers.push_back(qualifier); |
| } |
| |
| bool TTypeQualifierBuilder::checkSequenceIsValid(TDiagnostics *diagnostics) const |
| { |
| bool areQualifierChecksRelaxed = AreTypeQualifierChecksRelaxed(mShaderVersion); |
| std::string errorMessage; |
| if (HasRepeatingQualifiers(mQualifiers, areQualifierChecksRelaxed, &errorMessage)) |
| { |
| diagnostics->error(mQualifiers[0]->getLine(), errorMessage.c_str(), "qualifier sequence"); |
| return false; |
| } |
| |
| if (!areQualifierChecksRelaxed && !AreQualifiersInOrder(mQualifiers, &errorMessage)) |
| { |
| diagnostics->error(mQualifiers[0]->getLine(), errorMessage.c_str(), "qualifier sequence"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| TTypeQualifier TTypeQualifierBuilder::getParameterTypeQualifier(TDiagnostics *diagnostics) const |
| { |
| ASSERT(IsInvariantCorrect(mQualifiers)); |
| ASSERT(static_cast<const TStorageQualifierWrapper *>(mQualifiers[0])->getQualifier() == |
| EvqTemporary); |
| |
| if (!checkSequenceIsValid(diagnostics)) |
| { |
| return TTypeQualifier(EvqTemporary, mQualifiers[0]->getLine()); |
| } |
| |
| // If the qualifier checks are relaxed, then it is easier to sort the qualifiers so |
| // that the order imposed by the GLSL ES 3.00 spec is kept. Then we can use the same code to |
| // combine the qualifiers. |
| if (AreTypeQualifierChecksRelaxed(mShaderVersion)) |
| { |
| // Copy the qualifier sequence so that we can sort them. |
| QualifierSequence sortedQualifierSequence = mQualifiers; |
| SortSequence(sortedQualifierSequence); |
| return GetParameterTypeQualifierFromSortedSequence(sortedQualifierSequence, diagnostics); |
| } |
| return GetParameterTypeQualifierFromSortedSequence(mQualifiers, diagnostics); |
| } |
| |
| TTypeQualifier TTypeQualifierBuilder::getVariableTypeQualifier(TDiagnostics *diagnostics) const |
| { |
| ASSERT(IsInvariantCorrect(mQualifiers)); |
| |
| if (!checkSequenceIsValid(diagnostics)) |
| { |
| return TTypeQualifier( |
| static_cast<const TStorageQualifierWrapper *>(mQualifiers[0])->getQualifier(), |
| mQualifiers[0]->getLine()); |
| } |
| |
| // If the qualifier checks are relaxed, then it is easier to sort the qualifiers so |
| // that the order imposed by the GLSL ES 3.00 spec is kept. Then we can use the same code to |
| // combine the qualifiers. |
| if (AreTypeQualifierChecksRelaxed(mShaderVersion)) |
| { |
| // Copy the qualifier sequence so that we can sort them. |
| QualifierSequence sortedQualifierSequence = mQualifiers; |
| SortSequence(sortedQualifierSequence); |
| return GetVariableTypeQualifierFromSortedSequence(sortedQualifierSequence, diagnostics); |
| } |
| return GetVariableTypeQualifierFromSortedSequence(mQualifiers, diagnostics); |
| } |
| |
| } // namespace sh |