| // |
| // Copyright 2002 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/Compiler.h" |
| |
| #include <sstream> |
| |
| #include "angle_gl.h" |
| |
| #include "common/BinaryStream.h" |
| #include "common/CompiledShaderState.h" |
| #include "common/PackedEnums.h" |
| #include "common/angle_version_info.h" |
| |
| #include "compiler/translator/CallDAG.h" |
| #include "compiler/translator/CollectVariables.h" |
| #include "compiler/translator/Initialize.h" |
| #include "compiler/translator/IsASTDepthBelowLimit.h" |
| #include "compiler/translator/OutputTree.h" |
| #include "compiler/translator/ParseContext.h" |
| #include "compiler/translator/ValidateBarrierFunctionCall.h" |
| #include "compiler/translator/ValidateClipCullDistance.h" |
| #include "compiler/translator/ValidateLimitations.h" |
| #include "compiler/translator/ValidateMaxParameters.h" |
| #include "compiler/translator/ValidateOutputs.h" |
| #include "compiler/translator/ValidateTypeSizeLimitations.h" |
| #include "compiler/translator/ValidateVaryingLocations.h" |
| #include "compiler/translator/VariablePacker.h" |
| #include "compiler/translator/tree_ops/ClampFragDepth.h" |
| #include "compiler/translator/tree_ops/ClampIndirectIndices.h" |
| #include "compiler/translator/tree_ops/ClampPointSize.h" |
| #include "compiler/translator/tree_ops/DeclareAndInitBuiltinsForInstancedMultiview.h" |
| #include "compiler/translator/tree_ops/DeferGlobalInitializers.h" |
| #include "compiler/translator/tree_ops/EmulateGLFragColorBroadcast.h" |
| #include "compiler/translator/tree_ops/EmulateMultiDrawShaderBuiltins.h" |
| #include "compiler/translator/tree_ops/FoldExpressions.h" |
| #include "compiler/translator/tree_ops/ForcePrecisionQualifier.h" |
| #include "compiler/translator/tree_ops/InitializeVariables.h" |
| #include "compiler/translator/tree_ops/MonomorphizeUnsupportedFunctions.h" |
| #include "compiler/translator/tree_ops/PruneEmptyCases.h" |
| #include "compiler/translator/tree_ops/PruneNoOps.h" |
| #include "compiler/translator/tree_ops/RemoveArrayLengthMethod.h" |
| #include "compiler/translator/tree_ops/RemoveDynamicIndexing.h" |
| #include "compiler/translator/tree_ops/RemoveInvariantDeclaration.h" |
| #include "compiler/translator/tree_ops/RemoveUnreferencedVariables.h" |
| #include "compiler/translator/tree_ops/RescopeGlobalVariables.h" |
| #include "compiler/translator/tree_ops/RewritePixelLocalStorage.h" |
| #include "compiler/translator/tree_ops/SeparateDeclarations.h" |
| #include "compiler/translator/tree_ops/SeparateStructFromFunctionDeclarations.h" |
| #include "compiler/translator/tree_ops/SimplifyLoopConditions.h" |
| #include "compiler/translator/tree_ops/SplitSequenceOperator.h" |
| #include "compiler/translator/tree_ops/glsl/RegenerateStructNames.h" |
| #include "compiler/translator/tree_ops/glsl/RewriteRepeatedAssignToSwizzled.h" |
| #include "compiler/translator/tree_ops/glsl/ScalarizeVecAndMatConstructorArgs.h" |
| #include "compiler/translator/tree_ops/glsl/UseInterfaceBlockFields.h" |
| #include "compiler/translator/tree_ops/glsl/apple/AddAndTrueToLoopCondition.h" |
| #include "compiler/translator/tree_ops/glsl/apple/RewriteDoWhile.h" |
| #include "compiler/translator/tree_ops/glsl/apple/UnfoldShortCircuitAST.h" |
| #include "compiler/translator/tree_util/BuiltIn.h" |
| #include "compiler/translator/tree_util/IntermNodePatternMatcher.h" |
| #include "compiler/translator/tree_util/ReplaceShadowingVariables.h" |
| #include "compiler/translator/tree_util/ReplaceVariable.h" |
| #include "compiler/translator/util.h" |
| |
| // #define ANGLE_FUZZER_CORPUS_OUTPUT_DIR "corpus/" |
| |
| #if defined(ANGLE_FUZZER_CORPUS_OUTPUT_DIR) |
| # include "common/hash_utils.h" |
| # include "common/mathutil.h" |
| #endif |
| |
| namespace sh |
| { |
| |
| namespace |
| { |
| // Helper that returns if a top-level node is unused. If it's a function, the function prototype is |
| // returned as well. |
| bool IsTopLevelNodeUnusedFunction(const CallDAG &callDag, |
| const std::vector<TFunctionMetadata> &metadata, |
| TIntermNode *node, |
| const TFunction **functionOut) |
| { |
| const TIntermFunctionPrototype *asFunctionPrototype = node->getAsFunctionPrototypeNode(); |
| const TIntermFunctionDefinition *asFunctionDefinition = node->getAsFunctionDefinition(); |
| |
| *functionOut = nullptr; |
| |
| if (asFunctionDefinition) |
| { |
| *functionOut = asFunctionDefinition->getFunction(); |
| } |
| else if (asFunctionPrototype) |
| { |
| *functionOut = asFunctionPrototype->getFunction(); |
| } |
| if (*functionOut == nullptr) |
| { |
| return false; |
| } |
| |
| size_t callDagIndex = callDag.findIndex((*functionOut)->uniqueId()); |
| if (callDagIndex == CallDAG::InvalidIndex) |
| { |
| // This happens only for unimplemented prototypes which are thus unused |
| ASSERT(asFunctionPrototype); |
| return true; |
| } |
| |
| ASSERT(callDagIndex < metadata.size()); |
| return !metadata[callDagIndex].used; |
| } |
| |
| #if defined(ANGLE_FUZZER_CORPUS_OUTPUT_DIR) |
| void DumpFuzzerCase(char const *const *shaderStrings, |
| size_t numStrings, |
| uint32_t type, |
| uint32_t spec, |
| uint32_t output, |
| const ShCompileOptions &options) |
| { |
| ShaderDumpHeader header{}; |
| header.type = type; |
| header.spec = spec; |
| header.output = output; |
| memcpy(&header.basicCompileOptions, &options, offsetof(ShCompileOptions, metal)); |
| static_assert(offsetof(ShCompileOptions, metal) <= sizeof(header.basicCompileOptions)); |
| memcpy(&header.metalCompileOptions, &options.metal, sizeof(options.metal)); |
| static_assert(sizeof(options.metal) <= sizeof(header.metalCompileOptions)); |
| memcpy(&header.plsCompileOptions, &options.pls, sizeof(options.pls)); |
| static_assert(sizeof(options.pls) <= sizeof(header.plsCompileOptions)); |
| size_t contentsLength = sizeof(header) + 1; // Extra: header + nul terminator. |
| for (size_t i = 0; i < numStrings; i++) |
| { |
| contentsLength += strlen(shaderStrings[i]); |
| } |
| std::vector<uint8_t> contents(rx::roundUp<size_t>(contentsLength, 4), 0); |
| memcpy(&contents[0], &header, sizeof(header)); |
| uint8_t *data = &contents[sizeof(header)]; |
| for (size_t i = 0; i < numStrings; i++) |
| { |
| auto length = strlen(shaderStrings[i]); |
| memcpy(data, shaderStrings[i], length); |
| data += length; |
| } |
| auto hash = angle::ComputeGenericHash(contents.data(), contents.size()); |
| |
| std::ostringstream o = sh::InitializeStream<std::ostringstream>(); |
| o << ANGLE_FUZZER_CORPUS_OUTPUT_DIR << std::hex << std::setw(16) << std::setfill('0') << hash |
| << ".sample"; |
| std::string s = o.str(); |
| |
| // Must match the input format of the fuzzer |
| FILE *f = fopen(s.c_str(), "w"); |
| fwrite(contents.data(), sizeof(char), contentsLength, f); |
| fclose(f); |
| } |
| #endif // defined(ANGLE_FUZZER_CORPUS_OUTPUT_DIR) |
| } // anonymous namespace |
| |
| bool IsGLSL130OrNewer(ShShaderOutput output) |
| { |
| return (output == SH_GLSL_130_OUTPUT || output == SH_GLSL_140_OUTPUT || |
| output == SH_GLSL_150_CORE_OUTPUT || output == SH_GLSL_330_CORE_OUTPUT || |
| output == SH_GLSL_400_CORE_OUTPUT || output == SH_GLSL_410_CORE_OUTPUT || |
| output == SH_GLSL_420_CORE_OUTPUT || output == SH_GLSL_430_CORE_OUTPUT || |
| output == SH_GLSL_440_CORE_OUTPUT || output == SH_GLSL_450_CORE_OUTPUT); |
| } |
| |
| bool IsGLSL420OrNewer(ShShaderOutput output) |
| { |
| return (output == SH_GLSL_420_CORE_OUTPUT || output == SH_GLSL_430_CORE_OUTPUT || |
| output == SH_GLSL_440_CORE_OUTPUT || output == SH_GLSL_450_CORE_OUTPUT); |
| } |
| |
| bool IsGLSL410OrOlder(ShShaderOutput output) |
| { |
| return (output == SH_GLSL_130_OUTPUT || output == SH_GLSL_140_OUTPUT || |
| output == SH_GLSL_150_CORE_OUTPUT || output == SH_GLSL_330_CORE_OUTPUT || |
| output == SH_GLSL_400_CORE_OUTPUT || output == SH_GLSL_410_CORE_OUTPUT); |
| } |
| |
| bool RemoveInvariant(sh::GLenum shaderType, |
| int shaderVersion, |
| ShShaderOutput outputType, |
| const ShCompileOptions &compileOptions) |
| { |
| if (shaderType == GL_FRAGMENT_SHADER && |
| (IsGLSL420OrNewer(outputType) || IsOutputSPIRV(outputType))) |
| return true; |
| |
| if (compileOptions.removeInvariantAndCentroidForESSL3 && shaderVersion >= 300 && |
| shaderType == GL_VERTEX_SHADER) |
| return true; |
| |
| return false; |
| } |
| |
| size_t GetGlobalMaxTokenSize(ShShaderSpec spec) |
| { |
| // WebGL defines a max token length of 256, while ES2 leaves max token |
| // size undefined. ES3 defines a max size of 1024 characters. |
| switch (spec) |
| { |
| case SH_WEBGL_SPEC: |
| return 256; |
| default: |
| return 1024; |
| } |
| } |
| |
| int GetMaxUniformVectorsForShaderType(GLenum shaderType, const ShBuiltInResources &resources) |
| { |
| switch (shaderType) |
| { |
| case GL_VERTEX_SHADER: |
| return resources.MaxVertexUniformVectors; |
| case GL_FRAGMENT_SHADER: |
| return resources.MaxFragmentUniformVectors; |
| |
| // TODO (jiawei.shao@intel.com): check if we need finer-grained component counting |
| case GL_COMPUTE_SHADER: |
| return resources.MaxComputeUniformComponents / 4; |
| case GL_GEOMETRY_SHADER_EXT: |
| return resources.MaxGeometryUniformComponents / 4; |
| default: |
| UNREACHABLE(); |
| return -1; |
| } |
| } |
| |
| namespace |
| { |
| |
| class [[nodiscard]] TScopedPoolAllocator |
| { |
| public: |
| TScopedPoolAllocator(angle::PoolAllocator *allocator) : mAllocator(allocator) |
| { |
| mAllocator->push(); |
| SetGlobalPoolAllocator(mAllocator); |
| } |
| ~TScopedPoolAllocator() |
| { |
| SetGlobalPoolAllocator(nullptr); |
| mAllocator->pop(); |
| } |
| |
| private: |
| angle::PoolAllocator *mAllocator; |
| }; |
| |
| class [[nodiscard]] TScopedSymbolTableLevel |
| { |
| public: |
| TScopedSymbolTableLevel(TSymbolTable *table) : mTable(table) |
| { |
| ASSERT(mTable->isEmpty()); |
| mTable->push(); |
| } |
| ~TScopedSymbolTableLevel() |
| { |
| while (!mTable->isEmpty()) |
| mTable->pop(); |
| } |
| |
| private: |
| TSymbolTable *mTable; |
| }; |
| |
| int GetMaxShaderVersionForSpec(ShShaderSpec spec) |
| { |
| switch (spec) |
| { |
| case SH_GLES2_SPEC: |
| case SH_WEBGL_SPEC: |
| return 100; |
| case SH_GLES3_SPEC: |
| case SH_WEBGL2_SPEC: |
| return 300; |
| case SH_GLES3_1_SPEC: |
| case SH_WEBGL3_SPEC: |
| return 310; |
| case SH_GLES3_2_SPEC: |
| return 320; |
| case SH_GL_CORE_SPEC: |
| case SH_GL_COMPATIBILITY_SPEC: |
| return 460; |
| default: |
| UNREACHABLE(); |
| return 0; |
| } |
| } |
| |
| bool ValidateFragColorAndFragData(GLenum shaderType, |
| int shaderVersion, |
| const TSymbolTable &symbolTable, |
| TDiagnostics *diagnostics) |
| { |
| if (shaderVersion > 100 || shaderType != GL_FRAGMENT_SHADER) |
| { |
| return true; |
| } |
| |
| bool usesFragColor = false; |
| bool usesFragData = false; |
| // This validation is a bit stricter than the spec - it's only an error to write to |
| // both FragData and FragColor. But because it's better not to have reads from undefined |
| // variables, we always return an error if they are both referenced, rather than only if they |
| // are written. |
| if (symbolTable.isStaticallyUsed(*BuiltInVariable::gl_FragColor()) || |
| symbolTable.isStaticallyUsed(*BuiltInVariable::gl_SecondaryFragColorEXT())) |
| { |
| usesFragColor = true; |
| } |
| // Extension variables may not always be initialized (saves some time at symbol table init). |
| bool secondaryFragDataUsed = |
| symbolTable.gl_SecondaryFragDataEXT() != nullptr && |
| symbolTable.isStaticallyUsed(*symbolTable.gl_SecondaryFragDataEXT()); |
| if (symbolTable.isStaticallyUsed(*symbolTable.gl_FragData()) || secondaryFragDataUsed) |
| { |
| usesFragData = true; |
| } |
| if (usesFragColor && usesFragData) |
| { |
| const char *errorMessage = "cannot use both gl_FragData and gl_FragColor"; |
| if (symbolTable.isStaticallyUsed(*BuiltInVariable::gl_SecondaryFragColorEXT()) || |
| secondaryFragDataUsed) |
| { |
| errorMessage = |
| "cannot use both output variable sets (gl_FragData, gl_SecondaryFragDataEXT)" |
| " and (gl_FragColor, gl_SecondaryFragColorEXT)"; |
| } |
| diagnostics->globalError(errorMessage); |
| return false; |
| } |
| return true; |
| } |
| |
| } // namespace |
| |
| TShHandleBase::TShHandleBase() |
| { |
| allocator.push(); |
| SetGlobalPoolAllocator(&allocator); |
| } |
| |
| TShHandleBase::~TShHandleBase() |
| { |
| SetGlobalPoolAllocator(nullptr); |
| allocator.popAll(); |
| } |
| |
| TCompiler::TCompiler(sh::GLenum type, ShShaderSpec spec, ShShaderOutput output) |
| : mVariablesCollected(false), |
| mGLPositionInitialized(false), |
| mShaderType(type), |
| mShaderSpec(spec), |
| mOutputType(output), |
| mBuiltInFunctionEmulator(), |
| mDiagnostics(mInfoSink.info), |
| mSourcePath(nullptr), |
| mComputeShaderLocalSizeDeclared(false), |
| mComputeShaderLocalSize(1), |
| mGeometryShaderMaxVertices(-1), |
| mGeometryShaderInvocations(0), |
| mGeometryShaderInputPrimitiveType(EptUndefined), |
| mGeometryShaderOutputPrimitiveType(EptUndefined), |
| mTessControlShaderOutputVertices(0), |
| mTessEvaluationShaderInputPrimitiveType(EtetUndefined), |
| mTessEvaluationShaderInputVertexSpacingType(EtetUndefined), |
| mTessEvaluationShaderInputOrderingType(EtetUndefined), |
| mTessEvaluationShaderInputPointType(EtetUndefined), |
| mHasAnyPreciseType(false), |
| mAdvancedBlendEquations(0), |
| mHasPixelLocalStorageUniforms(false), |
| mUsesDerivatives(false), |
| mCompileOptions{} |
| {} |
| |
| TCompiler::~TCompiler() {} |
| |
| bool TCompiler::isHighPrecisionSupported() const |
| { |
| return mShaderVersion > 100 || mShaderType != GL_FRAGMENT_SHADER || |
| mResources.FragmentPrecisionHigh == 1; |
| } |
| |
| bool TCompiler::shouldRunLoopAndIndexingValidation(const ShCompileOptions &compileOptions) const |
| { |
| // If compiling an ESSL 1.00 shader for WebGL, or if its been requested through the API, |
| // validate loop and indexing as well (to verify that the shader only uses minimal functionality |
| // of ESSL 1.00 as in Appendix A of the spec). |
| return (IsWebGLBasedSpec(mShaderSpec) && mShaderVersion == 100) || |
| compileOptions.validateLoopIndexing; |
| } |
| |
| bool TCompiler::shouldLimitTypeSizes() const |
| { |
| // Prevent unrealistically large variable sizes in shaders. This works around driver bugs |
| // around int-size limits (such as 2GB). The limits are generously large enough that no real |
| // shader should ever hit it. |
| return true; |
| } |
| |
| bool TCompiler::Init(const ShBuiltInResources &resources) |
| { |
| SetGlobalPoolAllocator(&allocator); |
| |
| // Generate built-in symbol table. |
| if (!initBuiltInSymbolTable(resources)) |
| return false; |
| |
| mResources = resources; |
| setResourceString(); |
| |
| InitExtensionBehavior(resources, mExtensionBehavior); |
| return true; |
| } |
| |
| TIntermBlock *TCompiler::compileTreeForTesting(const char *const shaderStrings[], |
| size_t numStrings, |
| const ShCompileOptions &compileOptions) |
| { |
| return compileTreeImpl(shaderStrings, numStrings, compileOptions); |
| } |
| |
| TIntermBlock *TCompiler::compileTreeImpl(const char *const shaderStrings[], |
| size_t numStrings, |
| const ShCompileOptions &compileOptions) |
| { |
| // Remember the compile options for helper functions such as validateAST. |
| mCompileOptions = compileOptions; |
| |
| clearResults(); |
| |
| ASSERT(numStrings > 0); |
| ASSERT(GetGlobalPoolAllocator()); |
| |
| // Reset the extension behavior for each compilation unit. |
| ResetExtensionBehavior(mResources, mExtensionBehavior, compileOptions); |
| |
| // If gl_DrawID is not supported, remove it from the available extensions |
| // Currently we only allow emulation of gl_DrawID |
| const bool glDrawIDSupported = compileOptions.emulateGLDrawID; |
| if (!glDrawIDSupported) |
| { |
| auto it = mExtensionBehavior.find(TExtension::ANGLE_multi_draw); |
| if (it != mExtensionBehavior.end()) |
| { |
| mExtensionBehavior.erase(it); |
| } |
| } |
| |
| const bool glBaseVertexBaseInstanceSupported = compileOptions.emulateGLBaseVertexBaseInstance; |
| if (!glBaseVertexBaseInstanceSupported) |
| { |
| auto it = |
| mExtensionBehavior.find(TExtension::ANGLE_base_vertex_base_instance_shader_builtin); |
| if (it != mExtensionBehavior.end()) |
| { |
| mExtensionBehavior.erase(it); |
| } |
| } |
| |
| // First string is path of source file if flag is set. The actual source follows. |
| size_t firstSource = 0; |
| if (compileOptions.sourcePath) |
| { |
| mSourcePath = shaderStrings[0]; |
| ++firstSource; |
| } |
| |
| TParseContext parseContext(mSymbolTable, mExtensionBehavior, mShaderType, mShaderSpec, |
| compileOptions, !IsDesktopGLSpec(mShaderSpec), &mDiagnostics, |
| getResources(), getOutputType()); |
| |
| parseContext.setFragmentPrecisionHighOnESSL1(mResources.FragmentPrecisionHigh == 1); |
| |
| // We preserve symbols at the built-in level from compile-to-compile. |
| // Start pushing the user-defined symbols at global level. |
| TScopedSymbolTableLevel globalLevel(&mSymbolTable); |
| ASSERT(mSymbolTable.atGlobalLevel()); |
| |
| // Parse shader. |
| if (PaParseStrings(numStrings - firstSource, &shaderStrings[firstSource], nullptr, |
| &parseContext) != 0) |
| { |
| return nullptr; |
| } |
| |
| if (!postParseChecks(parseContext)) |
| { |
| return nullptr; |
| } |
| |
| setASTMetadata(parseContext); |
| |
| if (!checkShaderVersion(&parseContext)) |
| { |
| return nullptr; |
| } |
| |
| TIntermBlock *root = parseContext.getTreeRoot(); |
| if (!checkAndSimplifyAST(root, parseContext, compileOptions)) |
| { |
| return nullptr; |
| } |
| |
| return root; |
| } |
| |
| bool TCompiler::checkShaderVersion(TParseContext *parseContext) |
| { |
| if (GetMaxShaderVersionForSpec(mShaderSpec) < mShaderVersion) |
| { |
| mDiagnostics.globalError("unsupported shader version"); |
| return false; |
| } |
| |
| ASSERT(parseContext); |
| switch (mShaderType) |
| { |
| case GL_COMPUTE_SHADER: |
| if (mShaderVersion < 310) |
| { |
| mDiagnostics.globalError("Compute shader is not supported in this shader version."); |
| return false; |
| } |
| break; |
| |
| case GL_GEOMETRY_SHADER_EXT: |
| if (mShaderVersion < 310) |
| { |
| mDiagnostics.globalError( |
| "Geometry shader is not supported in this shader version."); |
| return false; |
| } |
| else if (mShaderVersion == 310) |
| { |
| if (!parseContext->checkCanUseOneOfExtensions( |
| sh::TSourceLoc(), |
| std::array<TExtension, 2u>{ |
| {TExtension::EXT_geometry_shader, TExtension::OES_geometry_shader}})) |
| { |
| return false; |
| } |
| } |
| break; |
| |
| case GL_TESS_CONTROL_SHADER_EXT: |
| case GL_TESS_EVALUATION_SHADER_EXT: |
| if (mShaderVersion < 310) |
| { |
| mDiagnostics.globalError( |
| "Tessellation shaders are not supported in this shader version."); |
| return false; |
| } |
| else if (mShaderVersion == 310) |
| { |
| if (!parseContext->checkCanUseExtension(sh::TSourceLoc(), |
| TExtension::EXT_tessellation_shader)) |
| { |
| return false; |
| } |
| } |
| break; |
| |
| default: |
| break; |
| } |
| |
| return true; |
| } |
| |
| void TCompiler::setASTMetadata(const TParseContext &parseContext) |
| { |
| mShaderVersion = parseContext.getShaderVersion(); |
| |
| mPragma = parseContext.pragma(); |
| mSymbolTable.setGlobalInvariant(mPragma.stdgl.invariantAll); |
| |
| mEarlyFragmentTestsSpecified = parseContext.isEarlyFragmentTestsSpecified(); |
| |
| mMetadataFlags[MetadataFlags::HasDiscard] = parseContext.hasDiscard(); |
| mMetadataFlags[MetadataFlags::EnablesPerSampleShading] = |
| parseContext.isSampleQualifierSpecified(); |
| |
| mComputeShaderLocalSizeDeclared = parseContext.isComputeShaderLocalSizeDeclared(); |
| mComputeShaderLocalSize = parseContext.getComputeShaderLocalSize(); |
| |
| mNumViews = parseContext.getNumViews(); |
| |
| mHasAnyPreciseType = parseContext.hasAnyPreciseType(); |
| |
| mUsesDerivatives = parseContext.usesDerivatives(); |
| |
| if (mShaderType == GL_FRAGMENT_SHADER) |
| { |
| mAdvancedBlendEquations = parseContext.getAdvancedBlendEquations(); |
| mHasPixelLocalStorageUniforms = !parseContext.pixelLocalStorageBindings().empty(); |
| } |
| if (mShaderType == GL_GEOMETRY_SHADER_EXT) |
| { |
| mGeometryShaderInputPrimitiveType = parseContext.getGeometryShaderInputPrimitiveType(); |
| mGeometryShaderOutputPrimitiveType = parseContext.getGeometryShaderOutputPrimitiveType(); |
| mGeometryShaderMaxVertices = parseContext.getGeometryShaderMaxVertices(); |
| mGeometryShaderInvocations = parseContext.getGeometryShaderInvocations(); |
| |
| mMetadataFlags[MetadataFlags::HasValidGeometryShaderInputPrimitiveType] = |
| mGeometryShaderInputPrimitiveType != EptUndefined; |
| mMetadataFlags[MetadataFlags::HasValidGeometryShaderOutputPrimitiveType] = |
| mGeometryShaderOutputPrimitiveType != EptUndefined; |
| mMetadataFlags[MetadataFlags::HasValidGeometryShaderMaxVertices] = |
| mGeometryShaderMaxVertices >= 0; |
| } |
| if (mShaderType == GL_TESS_CONTROL_SHADER_EXT) |
| { |
| mTessControlShaderOutputVertices = parseContext.getTessControlShaderOutputVertices(); |
| } |
| if (mShaderType == GL_TESS_EVALUATION_SHADER_EXT) |
| { |
| mTessEvaluationShaderInputPrimitiveType = |
| parseContext.getTessEvaluationShaderInputPrimitiveType(); |
| mTessEvaluationShaderInputVertexSpacingType = |
| parseContext.getTessEvaluationShaderInputVertexSpacingType(); |
| mTessEvaluationShaderInputOrderingType = |
| parseContext.getTessEvaluationShaderInputOrderingType(); |
| mTessEvaluationShaderInputPointType = parseContext.getTessEvaluationShaderInputPointType(); |
| |
| mMetadataFlags[MetadataFlags::HasValidTessGenMode] = |
| mTessEvaluationShaderInputPrimitiveType != EtetUndefined; |
| mMetadataFlags[MetadataFlags::HasValidTessGenSpacing] = |
| mTessEvaluationShaderInputVertexSpacingType != EtetUndefined; |
| mMetadataFlags[MetadataFlags::HasValidTessGenVertexOrder] = |
| mTessEvaluationShaderInputOrderingType != EtetUndefined; |
| mMetadataFlags[MetadataFlags::HasValidTessGenPointMode] = |
| mTessEvaluationShaderInputPointType != EtetUndefined; |
| } |
| } |
| |
| unsigned int TCompiler::getSharedMemorySize() const |
| { |
| unsigned int sharedMemSize = 0; |
| for (const sh::ShaderVariable &var : mSharedVariables) |
| { |
| sharedMemSize += var.getExternalSize(); |
| } |
| |
| return sharedMemSize; |
| } |
| |
| bool TCompiler::getShaderBinary(const ShHandle compilerHandle, |
| const char *const shaderStrings[], |
| size_t numStrings, |
| const ShCompileOptions &compileOptions, |
| ShaderBinaryBlob *const binaryOut) |
| { |
| if (!compile(shaderStrings, numStrings, compileOptions)) |
| { |
| return false; |
| } |
| |
| gl::BinaryOutputStream stream; |
| gl::ShaderType shaderType = gl::FromGLenum<gl::ShaderType>(mShaderType); |
| gl::CompiledShaderState state(shaderType); |
| state.buildCompiledShaderState(compilerHandle, IsOutputSPIRV(mOutputType)); |
| |
| stream.writeBytes( |
| reinterpret_cast<const unsigned char *>(angle::GetANGLEShaderProgramVersion()), |
| angle::GetANGLEShaderProgramVersionHashSize()); |
| stream.writeEnum(shaderType); |
| stream.writeEnum(mOutputType); |
| |
| // Serialize the full source string for the shader. Ignore the source path if it is provided. |
| std::string sourceString; |
| size_t startingIndex = compileOptions.sourcePath ? 1 : 0; |
| for (size_t i = startingIndex; i < numStrings; ++i) |
| { |
| sourceString.append(shaderStrings[i]); |
| } |
| stream.writeString(sourceString); |
| |
| stream.writeBytes(reinterpret_cast<const uint8_t *>(&compileOptions), sizeof(compileOptions)); |
| stream.writeBytes(reinterpret_cast<const uint8_t *>(&mResources), sizeof(mResources)); |
| |
| state.serialize(stream); |
| |
| ASSERT(binaryOut); |
| *binaryOut = std::move(stream.getData()); |
| return true; |
| } |
| |
| bool TCompiler::validateAST(TIntermNode *root) |
| { |
| if (mCompileOptions.validateAST) |
| { |
| bool valid = ValidateAST(root, &mDiagnostics, mValidateASTOptions); |
| |
| #if defined(ANGLE_ENABLE_ASSERTS) |
| if (!valid) |
| { |
| OutputTree(root, mInfoSink.info); |
| fprintf(stderr, "AST validation error(s):\n%s\n", mInfoSink.info.c_str()); |
| } |
| #endif |
| // In debug, assert validation. In release, validation errors will be returned back to the |
| // application as internal ANGLE errors. |
| ASSERT(valid); |
| |
| return valid; |
| } |
| return true; |
| } |
| |
| bool TCompiler::disableValidateFunctionCall() |
| { |
| bool wasEnabled = mValidateASTOptions.validateFunctionCall; |
| mValidateASTOptions.validateFunctionCall = false; |
| return wasEnabled; |
| } |
| |
| void TCompiler::restoreValidateFunctionCall(bool enable) |
| { |
| ASSERT(!mValidateASTOptions.validateFunctionCall); |
| mValidateASTOptions.validateFunctionCall = enable; |
| } |
| |
| bool TCompiler::disableValidateVariableReferences() |
| { |
| bool wasEnabled = mValidateASTOptions.validateVariableReferences; |
| mValidateASTOptions.validateVariableReferences = false; |
| return wasEnabled; |
| } |
| |
| void TCompiler::restoreValidateVariableReferences(bool enable) |
| { |
| ASSERT(!mValidateASTOptions.validateVariableReferences); |
| mValidateASTOptions.validateVariableReferences = enable; |
| } |
| |
| void TCompiler::enableValidateNoMoreTransformations() |
| { |
| mValidateASTOptions.validateNoMoreTransformations = true; |
| } |
| |
| bool TCompiler::checkAndSimplifyAST(TIntermBlock *root, |
| const TParseContext &parseContext, |
| const ShCompileOptions &compileOptions) |
| { |
| mValidateASTOptions = {}; |
| |
| // Desktop GLSL shaders don't have precision, so don't expect them to be specified. |
| mValidateASTOptions.validatePrecision = !IsDesktopGLSpec(mShaderSpec); |
| |
| if (!validateAST(root)) |
| { |
| return false; |
| } |
| |
| // For now, rewrite pixel local storage before collecting variables or any operations on images. |
| // |
| // TODO(anglebug.com/7279): |
| // Should this actually run after collecting variables? |
| // Do we need more introspection? |
| // Do we want to hide rewritten shader image uniforms from glGetActiveUniform? |
| if (hasPixelLocalStorageUniforms()) |
| { |
| ASSERT( |
| IsExtensionEnabled(mExtensionBehavior, TExtension::ANGLE_shader_pixel_local_storage)); |
| if (!RewritePixelLocalStorage(this, root, getSymbolTable(), compileOptions, |
| getShaderVersion())) |
| { |
| mDiagnostics.globalError("internal compiler error translating pixel local storage"); |
| return false; |
| } |
| } |
| |
| // Disallow expressions deemed too complex. |
| if (compileOptions.limitExpressionComplexity && !limitExpressionComplexity(root)) |
| { |
| return false; |
| } |
| |
| if (shouldRunLoopAndIndexingValidation(compileOptions) && |
| !ValidateLimitations(root, mShaderType, &mSymbolTable, &mDiagnostics)) |
| { |
| return false; |
| } |
| |
| if (!ValidateFragColorAndFragData(mShaderType, mShaderVersion, mSymbolTable, &mDiagnostics)) |
| { |
| return false; |
| } |
| |
| // Fold expressions that could not be folded before validation that was done as a part of |
| // parsing. |
| if (!FoldExpressions(this, root, &mDiagnostics)) |
| { |
| return false; |
| } |
| // Folding should only be able to generate warnings. |
| ASSERT(mDiagnostics.numErrors() == 0); |
| |
| // gl_ClipDistance and gl_CullDistance built-in arrays have unique semantics. |
| // They are pre-declared as unsized and must be sized by the shader either |
| // redeclaring them or indexing them only with integral constant expressions. |
| // The translator treats them as having the maximum allowed size and this pass |
| // detects the actual sizes resizing the variables if needed. |
| if (parseContext.isExtensionEnabled(TExtension::ANGLE_clip_cull_distance) || |
| parseContext.isExtensionEnabled(TExtension::EXT_clip_cull_distance) || |
| parseContext.isExtensionEnabled(TExtension::APPLE_clip_distance)) |
| { |
| bool isClipDistanceUsed = false; |
| if (!ValidateClipCullDistance(this, root, &mDiagnostics, |
| mResources.MaxCombinedClipAndCullDistances, |
| &mClipDistanceSize, &mCullDistanceSize, &isClipDistanceUsed)) |
| { |
| return false; |
| } |
| mMetadataFlags[MetadataFlags::HasClipDistance] = isClipDistanceUsed; |
| } |
| |
| // Validate no barrier() after return before prunning it in |PruneNoOps()| below. |
| if (mShaderType == GL_TESS_CONTROL_SHADER && !ValidateBarrierFunctionCall(root, &mDiagnostics)) |
| { |
| return false; |
| } |
| |
| // We prune no-ops to work around driver bugs and to keep AST processing and output simple. |
| // The following kinds of no-ops are pruned: |
| // 1. Empty declarations "int;". |
| // 2. Literal statements: "1.0;". The ESSL output doesn't define a default precision |
| // for float, so float literal statements would end up with no precision which is |
| // invalid ESSL. |
| // 3. Any unreachable statement after a discard, return, break or continue. |
| // After this empty declarations are not allowed in the AST. |
| if (!PruneNoOps(this, root, &mSymbolTable)) |
| { |
| return false; |
| } |
| mValidateASTOptions.validateNoStatementsAfterBranch = true; |
| |
| // We need to generate globals early if we have non constant initializers enabled |
| bool initializeLocalsAndGlobals = |
| compileOptions.initializeUninitializedLocals && !IsOutputHLSL(getOutputType()); |
| bool canUseLoopsToInitialize = !compileOptions.dontUseLoopsToInitializeVariables; |
| bool highPrecisionSupported = isHighPrecisionSupported(); |
| bool enableNonConstantInitializers = IsExtensionEnabled( |
| mExtensionBehavior, TExtension::EXT_shader_non_constant_global_initializers); |
| // forceDeferNonConstGlobalInitializers is needed for MSL |
| // to convert a non-const global. For example: |
| // |
| // int someGlobal = 123; |
| // |
| // to |
| // |
| // int someGlobal; |
| // void main() { |
| // someGlobal = 123; |
| // |
| // This is because MSL doesn't allow statically initialized non-const globals. |
| bool forceDeferNonConstGlobalInitializers = getOutputType() == SH_MSL_METAL_OUTPUT; |
| |
| if (enableNonConstantInitializers && |
| !DeferGlobalInitializers(this, root, initializeLocalsAndGlobals, canUseLoopsToInitialize, |
| highPrecisionSupported, forceDeferNonConstGlobalInitializers, |
| &mSymbolTable)) |
| { |
| return false; |
| } |
| |
| if (!SeparateStructFromFunctionDeclarations(*this, *root)) |
| { |
| return false; |
| } |
| |
| // Create the function DAG and check there is no recursion |
| if (!initCallDag(root)) |
| { |
| return false; |
| } |
| |
| if (compileOptions.limitCallStackDepth && !checkCallDepth()) |
| { |
| return false; |
| } |
| |
| // Checks which functions are used and if "main" exists |
| mFunctionMetadata.clear(); |
| mFunctionMetadata.resize(mCallDag.size()); |
| if (!tagUsedFunctions()) |
| { |
| return false; |
| } |
| |
| if (!pruneUnusedFunctions(root)) |
| { |
| return false; |
| } |
| |
| if (IsSpecWithFunctionBodyNewScope(mShaderSpec, mShaderVersion)) |
| { |
| if (!ReplaceShadowingVariables(this, root, &mSymbolTable)) |
| { |
| return false; |
| } |
| } |
| |
| if (mShaderVersion >= 310 && !ValidateVaryingLocations(root, &mDiagnostics, mShaderType)) |
| { |
| return false; |
| } |
| |
| // anglebug.com/7484: The ESSL spec has a bug with images as function arguments. The recommended |
| // workaround is to inline functions that accept image arguments. |
| if (mShaderVersion >= 310 && !MonomorphizeUnsupportedFunctions( |
| this, root, &mSymbolTable, compileOptions, |
| UnsupportedFunctionArgsBitSet{UnsupportedFunctionArgs::Image})) |
| { |
| return false; |
| } |
| |
| if (mShaderVersion >= 300 && mShaderType == GL_FRAGMENT_SHADER && |
| !ValidateOutputs(root, getExtensionBehavior(), mResources, hasPixelLocalStorageUniforms(), |
| IsWebGLBasedSpec(mShaderSpec), &mDiagnostics)) |
| { |
| return false; |
| } |
| |
| // Clamping uniform array bounds needs to happen after validateLimitations pass. |
| if (compileOptions.clampIndirectArrayBounds) |
| { |
| if (!ClampIndirectIndices(this, root, &mSymbolTable)) |
| { |
| return false; |
| } |
| } |
| |
| if (compileOptions.initializeBuiltinsForInstancedMultiview && |
| (parseContext.isExtensionEnabled(TExtension::OVR_multiview2) || |
| parseContext.isExtensionEnabled(TExtension::OVR_multiview)) && |
| getShaderType() != GL_COMPUTE_SHADER) |
| { |
| if (!DeclareAndInitBuiltinsForInstancedMultiview( |
| this, root, mNumViews, mShaderType, compileOptions, mOutputType, &mSymbolTable)) |
| { |
| return false; |
| } |
| } |
| |
| // This pass might emit short circuits so keep it before the short circuit unfolding |
| if (compileOptions.rewriteDoWhileLoops) |
| { |
| if (!RewriteDoWhile(this, root, &mSymbolTable)) |
| { |
| return false; |
| } |
| } |
| |
| if (compileOptions.addAndTrueToLoopCondition) |
| { |
| if (!AddAndTrueToLoopCondition(this, root)) |
| { |
| return false; |
| } |
| } |
| |
| if (compileOptions.unfoldShortCircuit) |
| { |
| if (!UnfoldShortCircuitAST(this, root)) |
| { |
| return false; |
| } |
| } |
| |
| if (compileOptions.regenerateStructNames) |
| { |
| if (!RegenerateStructNames(this, root, &mSymbolTable)) |
| { |
| return false; |
| } |
| } |
| |
| if (mShaderType == GL_VERTEX_SHADER && |
| IsExtensionEnabled(mExtensionBehavior, TExtension::ANGLE_multi_draw)) |
| { |
| if (compileOptions.emulateGLDrawID) |
| { |
| if (!EmulateGLDrawID(this, root, &mSymbolTable, &mUniforms)) |
| { |
| return false; |
| } |
| } |
| } |
| |
| if (mShaderType == GL_VERTEX_SHADER && |
| IsExtensionEnabled(mExtensionBehavior, |
| TExtension::ANGLE_base_vertex_base_instance_shader_builtin)) |
| { |
| if (compileOptions.emulateGLBaseVertexBaseInstance) |
| { |
| if (!EmulateGLBaseVertexBaseInstance(this, root, &mSymbolTable, &mUniforms, |
| compileOptions.addBaseVertexToVertexID)) |
| { |
| return false; |
| } |
| } |
| } |
| |
| if (mShaderType == GL_FRAGMENT_SHADER && mShaderVersion == 100 && mResources.EXT_draw_buffers && |
| mResources.MaxDrawBuffers > 1 && |
| IsExtensionEnabled(mExtensionBehavior, TExtension::EXT_draw_buffers)) |
| { |
| if (!EmulateGLFragColorBroadcast(this, root, mResources.MaxDrawBuffers, |
| mResources.MaxDualSourceDrawBuffers, &mOutputVariables, |
| &mSymbolTable, mShaderVersion)) |
| { |
| return false; |
| } |
| } |
| |
| // Split multi declarations and remove calls to array length(). |
| // Note that SimplifyLoopConditions needs to be run before any other AST transformations |
| // that may need to generate new statements from loop conditions or loop expressions. |
| if (!SimplifyLoopConditions(this, root, |
| IntermNodePatternMatcher::kMultiDeclaration | |
| IntermNodePatternMatcher::kArrayLengthMethod, |
| &getSymbolTable())) |
| { |
| return false; |
| } |
| |
| // Note that separate declarations need to be run before other AST transformations that |
| // generate new statements from expressions. |
| if (!SeparateDeclarations(*this, *root)) |
| { |
| return false; |
| } |
| |
| if (compileOptions.rescopeGlobalVariables) |
| { |
| if (!RescopeGlobalVariables(*this, *root)) |
| { |
| return false; |
| } |
| } |
| |
| mValidateASTOptions.validateMultiDeclarations = true; |
| |
| if (!SplitSequenceOperator(this, root, IntermNodePatternMatcher::kArrayLengthMethod, |
| &getSymbolTable())) |
| { |
| return false; |
| } |
| |
| if (!RemoveArrayLengthMethod(this, root)) |
| { |
| return false; |
| } |
| // Fold the expressions again, because |RemoveArrayLengthMethod| can introduce new constants. |
| if (!FoldExpressions(this, root, &mDiagnostics)) |
| { |
| return false; |
| } |
| |
| if (!RemoveUnreferencedVariables(this, root, &mSymbolTable)) |
| { |
| return false; |
| } |
| |
| // In case the last case inside a switch statement is a certain type of no-op, GLSL compilers in |
| // drivers may not accept it. In this case we clean up the dead code from the end of switch |
| // statements. This is also required because PruneNoOps or RemoveUnreferencedVariables may have |
| // left switch statements that only contained an empty declaration inside the final case in an |
| // invalid state. Relies on that PruneNoOps and RemoveUnreferencedVariables have already been |
| // run. |
| if (!PruneEmptyCases(this, root)) |
| { |
| return false; |
| } |
| |
| // Run after RemoveUnreferencedVariables, validate that the shader does not have excessively |
| // large variables. |
| if (shouldLimitTypeSizes() && !ValidateTypeSizeLimitations(root, &mSymbolTable, &mDiagnostics)) |
| { |
| return false; |
| } |
| |
| // Built-in function emulation needs to happen after validateLimitations pass. |
| GetGlobalPoolAllocator()->lock(); |
| initBuiltInFunctionEmulator(&mBuiltInFunctionEmulator, compileOptions); |
| GetGlobalPoolAllocator()->unlock(); |
| mBuiltInFunctionEmulator.markBuiltInFunctionsForEmulation(root); |
| |
| if (compileOptions.scalarizeVecAndMatConstructorArgs) |
| { |
| if (!ScalarizeVecAndMatConstructorArgs(this, root, &mSymbolTable)) |
| { |
| return false; |
| } |
| } |
| |
| if (compileOptions.forceShaderPrecisionHighpToMediump) |
| { |
| if (!ForceShaderPrecisionToMediump(root, &mSymbolTable, mShaderType)) |
| { |
| return false; |
| } |
| } |
| |
| ASSERT(!mVariablesCollected); |
| CollectVariables(root, &mAttributes, &mOutputVariables, &mUniforms, &mInputVaryings, |
| &mOutputVaryings, &mSharedVariables, &mUniformBlocks, &mShaderStorageBlocks, |
| mResources.HashFunction, &mSymbolTable, mShaderType, mExtensionBehavior, |
| mResources, mTessControlShaderOutputVertices); |
| collectInterfaceBlocks(); |
| mVariablesCollected = true; |
| if (compileOptions.useUnusedStandardSharedBlocks) |
| { |
| if (!useAllMembersInUnusedStandardAndSharedBlocks(root)) |
| { |
| return false; |
| } |
| } |
| if (compileOptions.enforcePackingRestrictions) |
| { |
| int maxUniformVectors = GetMaxUniformVectorsForShaderType(mShaderType, mResources); |
| if (mShaderType == GL_VERTEX_SHADER && compileOptions.emulateClipOrigin) |
| { |
| --maxUniformVectors; |
| } |
| // Returns true if, after applying the packing rules in the GLSL ES 1.00.17 spec |
| // Appendix A, section 7, the shader does not use too many uniforms. |
| if (!CheckVariablesInPackingLimits(maxUniformVectors, mUniforms)) |
| { |
| mDiagnostics.globalError("too many uniforms"); |
| return false; |
| } |
| } |
| bool needInitializeOutputVariables = |
| compileOptions.initOutputVariables && mShaderType != GL_COMPUTE_SHADER; |
| needInitializeOutputVariables |= |
| compileOptions.initFragmentOutputVariables && mShaderType == GL_FRAGMENT_SHADER; |
| if (needInitializeOutputVariables) |
| { |
| if (!initializeOutputVariables(root)) |
| { |
| return false; |
| } |
| } |
| |
| // Removing invariant declarations must be done after collecting variables. |
| // Otherwise, built-in invariant declarations don't apply. |
| if (RemoveInvariant(mShaderType, mShaderVersion, mOutputType, compileOptions)) |
| { |
| if (!RemoveInvariantDeclaration(this, root)) |
| { |
| return false; |
| } |
| } |
| |
| // gl_Position is always written in compatibility output mode. |
| // It may have been already initialized among other output variables, in that case we don't |
| // need to initialize it twice. |
| if (mShaderType == GL_VERTEX_SHADER && !mGLPositionInitialized && |
| (compileOptions.initGLPosition || mOutputType == SH_GLSL_COMPATIBILITY_OUTPUT)) |
| { |
| if (!initializeGLPosition(root)) |
| { |
| return false; |
| } |
| mGLPositionInitialized = true; |
| } |
| |
| // DeferGlobalInitializers needs to be run before other AST transformations that generate new |
| // statements from expressions. But it's fine to run DeferGlobalInitializers after the above |
| // SplitSequenceOperator and RemoveArrayLengthMethod since they only have an effect on the AST |
| // on ESSL >= 3.00, and the initializers that need to be deferred can only exist in ESSL < 3.00. |
| // Exception: if EXT_shader_non_constant_global_initializers is enabled, we must generate global |
| // initializers before we generate the DAG, since initializers may call functions which must not |
| // be optimized out |
| if (!enableNonConstantInitializers && |
| !DeferGlobalInitializers(this, root, initializeLocalsAndGlobals, canUseLoopsToInitialize, |
| highPrecisionSupported, forceDeferNonConstGlobalInitializers, |
| &mSymbolTable)) |
| { |
| return false; |
| } |
| |
| if (initializeLocalsAndGlobals) |
| { |
| // Initialize uninitialized local variables. |
| // In some cases initializing can generate extra statements in the parent block, such as |
| // when initializing nameless structs or initializing arrays in ESSL 1.00. In that case |
| // we need to first simplify loop conditions. We've already separated declarations |
| // earlier, which is also required. If we don't follow the Appendix A limitations, loop |
| // init statements can declare arrays or nameless structs and have multiple |
| // declarations. |
| |
| if (!shouldRunLoopAndIndexingValidation(compileOptions)) |
| { |
| if (!SimplifyLoopConditions(this, root, |
| IntermNodePatternMatcher::kArrayDeclaration | |
| IntermNodePatternMatcher::kNamelessStructDeclaration, |
| &getSymbolTable())) |
| { |
| return false; |
| } |
| } |
| |
| if (!InitializeUninitializedLocals(this, root, getShaderVersion(), canUseLoopsToInitialize, |
| highPrecisionSupported, &getSymbolTable())) |
| { |
| return false; |
| } |
| } |
| |
| if (getShaderType() == GL_VERTEX_SHADER && compileOptions.clampPointSize) |
| { |
| if (!ClampPointSize(this, root, mResources.MinPointSize, mResources.MaxPointSize, |
| &getSymbolTable())) |
| { |
| return false; |
| } |
| } |
| |
| if (getShaderType() == GL_FRAGMENT_SHADER && compileOptions.clampFragDepth) |
| { |
| if (!ClampFragDepth(this, root, &getSymbolTable())) |
| { |
| return false; |
| } |
| } |
| |
| if (compileOptions.rewriteRepeatedAssignToSwizzled) |
| { |
| if (!sh::RewriteRepeatedAssignToSwizzled(this, root)) |
| { |
| return false; |
| } |
| } |
| |
| if (compileOptions.removeDynamicIndexingOfSwizzledVector) |
| { |
| if (!sh::RemoveDynamicIndexingOfSwizzledVector(this, root, &getSymbolTable(), nullptr)) |
| { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool TCompiler::postParseChecks(const TParseContext &parseContext) |
| { |
| std::stringstream errorMessage; |
| |
| if (parseContext.getTreeRoot() == nullptr) |
| { |
| errorMessage << "Shader parsing failed (mTreeRoot == nullptr)"; |
| } |
| |
| for (TType *type : parseContext.getDeferredArrayTypesToSize()) |
| { |
| errorMessage << "Unsized global array type: " << type->getBasicString(); |
| } |
| |
| if (!errorMessage.str().empty()) |
| { |
| mDiagnostics.globalError(errorMessage.str().c_str()); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool TCompiler::compile(const char *const shaderStrings[], |
| size_t numStrings, |
| const ShCompileOptions &compileOptionsIn) |
| { |
| #if defined(ANGLE_FUZZER_CORPUS_OUTPUT_DIR) |
| DumpFuzzerCase(shaderStrings, numStrings, mShaderType, mShaderSpec, mOutputType, |
| compileOptionsIn); |
| #endif // defined(ANGLE_FUZZER_CORPUS_OUTPUT_DIR) |
| |
| if (numStrings == 0) |
| return true; |
| |
| ShCompileOptions compileOptions = compileOptionsIn; |
| |
| // Apply key workarounds. |
| if (shouldFlattenPragmaStdglInvariantAll()) |
| { |
| // This should be harmless to do in all cases, but for the moment, do it only conditionally. |
| compileOptions.flattenPragmaSTDGLInvariantAll = true; |
| } |
| |
| TScopedPoolAllocator scopedAlloc(&allocator); |
| TIntermBlock *root = compileTreeImpl(shaderStrings, numStrings, compileOptions); |
| |
| if (root) |
| { |
| if (compileOptions.intermediateTree) |
| { |
| OutputTree(root, mInfoSink.info); |
| } |
| |
| if (compileOptions.objectCode) |
| { |
| PerformanceDiagnostics perfDiagnostics(&mDiagnostics); |
| if (!translate(root, compileOptions, &perfDiagnostics)) |
| { |
| return false; |
| } |
| } |
| |
| if (mShaderType == GL_VERTEX_SHADER) |
| { |
| bool lookForDrawID = |
| IsExtensionEnabled(mExtensionBehavior, TExtension::ANGLE_multi_draw) && |
| compileOptions.emulateGLDrawID; |
| bool lookForBaseVertexBaseInstance = |
| IsExtensionEnabled(mExtensionBehavior, |
| TExtension::ANGLE_base_vertex_base_instance_shader_builtin) && |
| compileOptions.emulateGLBaseVertexBaseInstance; |
| |
| if (lookForDrawID || lookForBaseVertexBaseInstance) |
| { |
| for (auto &uniform : mUniforms) |
| { |
| if (lookForDrawID && uniform.name == "angle_DrawID" && |
| uniform.mappedName == "angle_DrawID") |
| { |
| uniform.name = "gl_DrawID"; |
| } |
| else if (lookForBaseVertexBaseInstance && uniform.name == "angle_BaseVertex" && |
| uniform.mappedName == "angle_BaseVertex") |
| { |
| uniform.name = "gl_BaseVertex"; |
| } |
| else if (lookForBaseVertexBaseInstance && |
| uniform.name == "angle_BaseInstance" && |
| uniform.mappedName == "angle_BaseInstance") |
| { |
| uniform.name = "gl_BaseInstance"; |
| } |
| } |
| } |
| } |
| |
| // The IntermNode tree doesn't need to be deleted here, since the |
| // memory will be freed in a big chunk by the PoolAllocator. |
| return true; |
| } |
| return false; |
| } |
| |
| bool TCompiler::initBuiltInSymbolTable(const ShBuiltInResources &resources) |
| { |
| if (resources.MaxDrawBuffers < 1) |
| { |
| return false; |
| } |
| if (resources.EXT_blend_func_extended && resources.MaxDualSourceDrawBuffers < 1) |
| { |
| return false; |
| } |
| |
| mSymbolTable.initializeBuiltIns(mShaderType, mShaderSpec, resources); |
| |
| return true; |
| } |
| |
| void TCompiler::setResourceString() |
| { |
| std::ostringstream strstream = sh::InitializeStream<std::ostringstream>(); |
| |
| // clang-format off |
| strstream << ":MaxVertexAttribs:" << mResources.MaxVertexAttribs |
| << ":MaxVertexUniformVectors:" << mResources.MaxVertexUniformVectors |
| << ":MaxVaryingVectors:" << mResources.MaxVaryingVectors |
| << ":MaxVertexTextureImageUnits:" << mResources.MaxVertexTextureImageUnits |
| << ":MaxCombinedTextureImageUnits:" << mResources.MaxCombinedTextureImageUnits |
| << ":MaxTextureImageUnits:" << mResources.MaxTextureImageUnits |
| << ":MaxFragmentUniformVectors:" << mResources.MaxFragmentUniformVectors |
| << ":MaxDrawBuffers:" << mResources.MaxDrawBuffers |
| << ":OES_standard_derivatives:" << mResources.OES_standard_derivatives |
| << ":OES_EGL_image_external:" << mResources.OES_EGL_image_external |
| << ":OES_EGL_image_external_essl3:" << mResources.OES_EGL_image_external_essl3 |
| << ":NV_EGL_stream_consumer_external:" << mResources.NV_EGL_stream_consumer_external |
| << ":ARB_texture_rectangle:" << mResources.ARB_texture_rectangle |
| << ":EXT_draw_buffers:" << mResources.EXT_draw_buffers |
| << ":FragmentPrecisionHigh:" << mResources.FragmentPrecisionHigh |
| << ":MaxExpressionComplexity:" << mResources.MaxExpressionComplexity |
| << ":MaxCallStackDepth:" << mResources.MaxCallStackDepth |
| << ":MaxFunctionParameters:" << mResources.MaxFunctionParameters |
| << ":EXT_blend_func_extended:" << mResources.EXT_blend_func_extended |
| << ":EXT_conservative_depth:" << mResources.EXT_conservative_depth |
| << ":EXT_frag_depth:" << mResources.EXT_frag_depth |
| << ":EXT_primitive_bounding_box:" << mResources.EXT_primitive_bounding_box |
| << ":OES_primitive_bounding_box:" << mResources.OES_primitive_bounding_box |
| << ":EXT_separate_shader_objects:" << mResources.EXT_separate_shader_objects |
| << ":EXT_shader_texture_lod:" << mResources.EXT_shader_texture_lod |
| << ":EXT_shader_framebuffer_fetch:" << mResources.EXT_shader_framebuffer_fetch |
| << ":EXT_shader_framebuffer_fetch_non_coherent:" << mResources.EXT_shader_framebuffer_fetch_non_coherent |
| << ":NV_shader_framebuffer_fetch:" << mResources.NV_shader_framebuffer_fetch |
| << ":ARM_shader_framebuffer_fetch:" << mResources.ARM_shader_framebuffer_fetch |
| << ":OVR_multiview2:" << mResources.OVR_multiview2 |
| << ":OVR_multiview:" << mResources.OVR_multiview |
| << ":EXT_YUV_target:" << mResources.EXT_YUV_target |
| << ":EXT_geometry_shader:" << mResources.EXT_geometry_shader |
| << ":OES_geometry_shader:" << mResources.OES_geometry_shader |
| << ":OES_shader_io_blocks:" << mResources.OES_shader_io_blocks |
| << ":EXT_shader_io_blocks:" << mResources.EXT_shader_io_blocks |
| << ":EXT_gpu_shader5:" << mResources.EXT_gpu_shader5 |
| << ":OES_texture_3D:" << mResources.OES_texture_3D |
| << ":MaxVertexOutputVectors:" << mResources.MaxVertexOutputVectors |
| << ":MaxFragmentInputVectors:" << mResources.MaxFragmentInputVectors |
| << ":MinProgramTexelOffset:" << mResources.MinProgramTexelOffset |
| << ":MaxProgramTexelOffset:" << mResources.MaxProgramTexelOffset |
| << ":MaxDualSourceDrawBuffers:" << mResources.MaxDualSourceDrawBuffers |
| << ":MaxViewsOVR:" << mResources.MaxViewsOVR |
| << ":NV_draw_buffers:" << mResources.NV_draw_buffers |
| << ":ANGLE_multi_draw:" << mResources.ANGLE_multi_draw |
| << ":ANGLE_base_vertex_base_instance_shader_builtin:" << mResources.ANGLE_base_vertex_base_instance_shader_builtin |
| << ":APPLE_clip_distance:" << mResources.APPLE_clip_distance |
| << ":OES_texture_cube_map_array:" << mResources.OES_texture_cube_map_array |
| << ":EXT_texture_cube_map_array:" << mResources.EXT_texture_cube_map_array |
| << ":EXT_shadow_samplers:" << mResources.EXT_shadow_samplers |
| << ":OES_shader_multisample_interpolation:" << mResources.OES_shader_multisample_interpolation |
| << ":OES_shader_image_atomic:" << mResources.OES_shader_image_atomic |
| << ":EXT_tessellation_shader:" << mResources.EXT_tessellation_shader |
| << ":OES_texture_buffer:" << mResources.OES_texture_buffer |
| << ":EXT_texture_buffer:" << mResources.EXT_texture_buffer |
| << ":OES_sample_variables:" << mResources.OES_sample_variables |
| << ":EXT_clip_cull_distance:" << mResources.EXT_clip_cull_distance |
| << ":ANGLE_clip_cull_distance:" << mResources.ANGLE_clip_cull_distance |
| << ":MinProgramTextureGatherOffset:" << mResources.MinProgramTextureGatherOffset |
| << ":MaxProgramTextureGatherOffset:" << mResources.MaxProgramTextureGatherOffset |
| << ":MaxImageUnits:" << mResources.MaxImageUnits |
| << ":MaxSamples:" << mResources.MaxSamples |
| << ":MaxVertexImageUniforms:" << mResources.MaxVertexImageUniforms |
| << ":MaxFragmentImageUniforms:" << mResources.MaxFragmentImageUniforms |
| << ":MaxComputeImageUniforms:" << mResources.MaxComputeImageUniforms |
| << ":MaxCombinedImageUniforms:" << mResources.MaxCombinedImageUniforms |
| << ":MaxCombinedShaderOutputResources:" << mResources.MaxCombinedShaderOutputResources |
| << ":MaxComputeWorkGroupCountX:" << mResources.MaxComputeWorkGroupCount[0] |
| << ":MaxComputeWorkGroupCountY:" << mResources.MaxComputeWorkGroupCount[1] |
| << ":MaxComputeWorkGroupCountZ:" << mResources.MaxComputeWorkGroupCount[2] |
| << ":MaxComputeWorkGroupSizeX:" << mResources.MaxComputeWorkGroupSize[0] |
| << ":MaxComputeWorkGroupSizeY:" << mResources.MaxComputeWorkGroupSize[1] |
| << ":MaxComputeWorkGroupSizeZ:" << mResources.MaxComputeWorkGroupSize[2] |
| << ":MaxComputeUniformComponents:" << mResources.MaxComputeUniformComponents |
| << ":MaxComputeTextureImageUnits:" << mResources.MaxComputeTextureImageUnits |
| << ":MaxComputeAtomicCounters:" << mResources.MaxComputeAtomicCounters |
| << ":MaxComputeAtomicCounterBuffers:" << mResources.MaxComputeAtomicCounterBuffers |
| << ":MaxVertexAtomicCounters:" << mResources.MaxVertexAtomicCounters |
| << ":MaxFragmentAtomicCounters:" << mResources.MaxFragmentAtomicCounters |
| << ":MaxCombinedAtomicCounters:" << mResources.MaxCombinedAtomicCounters |
| << ":MaxAtomicCounterBindings:" << mResources.MaxAtomicCounterBindings |
| << ":MaxVertexAtomicCounterBuffers:" << mResources.MaxVertexAtomicCounterBuffers |
| << ":MaxFragmentAtomicCounterBuffers:" << mResources.MaxFragmentAtomicCounterBuffers |
| << ":MaxCombinedAtomicCounterBuffers:" << mResources.MaxCombinedAtomicCounterBuffers |
| << ":MaxAtomicCounterBufferSize:" << mResources.MaxAtomicCounterBufferSize |
| << ":MaxGeometryUniformComponents:" << mResources.MaxGeometryUniformComponents |
| << ":MaxGeometryUniformBlocks:" << mResources.MaxGeometryUniformBlocks |
| << ":MaxGeometryInputComponents:" << mResources.MaxGeometryInputComponents |
| << ":MaxGeometryOutputComponents:" << mResources.MaxGeometryOutputComponents |
| << ":MaxGeometryOutputVertices:" << mResources.MaxGeometryOutputVertices |
| << ":MaxGeometryTotalOutputComponents:" << mResources.MaxGeometryTotalOutputComponents |
| << ":MaxGeometryTextureImageUnits:" << mResources.MaxGeometryTextureImageUnits |
| << ":MaxGeometryAtomicCounterBuffers:" << mResources.MaxGeometryAtomicCounterBuffers |
| << ":MaxGeometryAtomicCounters:" << mResources.MaxGeometryAtomicCounters |
| << ":MaxGeometryShaderStorageBlocks:" << mResources.MaxGeometryShaderStorageBlocks |
| << ":MaxGeometryShaderInvocations:" << mResources.MaxGeometryShaderInvocations |
| << ":MaxGeometryImageUniforms:" << mResources.MaxGeometryImageUniforms |
| << ":MaxClipDistances" << mResources.MaxClipDistances |
| << ":MaxCullDistances" << mResources.MaxCullDistances |
| << ":MaxCombinedClipAndCullDistances" << mResources.MaxCombinedClipAndCullDistances |
| << ":MaxTessControlInputComponents:" << mResources.MaxTessControlInputComponents |
| << ":MaxTessControlOutputComponents:" << mResources.MaxTessControlOutputComponents |
| << ":MaxTessControlTextureImageUnits:" << mResources.MaxTessControlTextureImageUnits |
| << ":MaxTessControlUniformComponents:" << mResources.MaxTessControlUniformComponents |
| << ":MaxTessControlTotalOutputComponents:" << mResources.MaxTessControlTotalOutputComponents |
| << ":MaxTessControlImageUniforms:" << mResources.MaxTessControlImageUniforms |
| << ":MaxTessControlAtomicCounters:" << mResources.MaxTessControlAtomicCounters |
| << ":MaxTessControlAtomicCounterBuffers:" << mResources.MaxTessControlAtomicCounterBuffers |
| << ":MaxTessPatchComponents:" << mResources.MaxTessPatchComponents |
| << ":MaxPatchVertices:" << mResources.MaxPatchVertices |
| << ":MaxTessGenLevel:" << mResources.MaxTessGenLevel |
| << ":MaxTessEvaluationInputComponents:" << mResources.MaxTessEvaluationInputComponents |
| << ":MaxTessEvaluationOutputComponents:" << mResources.MaxTessEvaluationOutputComponents |
| << ":MaxTessEvaluationTextureImageUnits:" << mResources.MaxTessEvaluationTextureImageUnits |
| << ":MaxTessEvaluationUniformComponents:" << mResources.MaxTessEvaluationUniformComponents |
| << ":MaxTessEvaluationImageUniforms:" << mResources.MaxTessEvaluationImageUniforms |
| << ":MaxTessEvaluationAtomicCounters:" << mResources.MaxTessEvaluationAtomicCounters |
| << ":MaxTessEvaluationAtomicCounterBuffers:" << mResources.MaxTessEvaluationAtomicCounterBuffers; |
| // clang-format on |
| |
| mBuiltInResourcesString = strstream.str(); |
| } |
| |
| void TCompiler::collectInterfaceBlocks() |
| { |
| ASSERT(mInterfaceBlocks.empty()); |
| mInterfaceBlocks.reserve(mUniformBlocks.size() + mShaderStorageBlocks.size()); |
| mInterfaceBlocks.insert(mInterfaceBlocks.end(), mUniformBlocks.begin(), mUniformBlocks.end()); |
| mInterfaceBlocks.insert(mInterfaceBlocks.end(), mShaderStorageBlocks.begin(), |
| mShaderStorageBlocks.end()); |
| } |
| |
| void TCompiler::clearResults() |
| { |
| mInfoSink.info.erase(); |
| mInfoSink.obj.erase(); |
| mInfoSink.debug.erase(); |
| mDiagnostics.resetErrorCount(); |
| |
| mMetadataFlags.reset(); |
| mSpecConstUsageBits.reset(); |
| |
| mAttributes.clear(); |
| mOutputVariables.clear(); |
| mUniforms.clear(); |
| mInputVaryings.clear(); |
| mOutputVaryings.clear(); |
| mSharedVariables.clear(); |
| mInterfaceBlocks.clear(); |
| mUniformBlocks.clear(); |
| mShaderStorageBlocks.clear(); |
| mVariablesCollected = false; |
| mGLPositionInitialized = false; |
| |
| mNumViews = -1; |
| |
| mClipDistanceSize = 0; |
| mCullDistanceSize = 0; |
| |
| mGeometryShaderInputPrimitiveType = EptUndefined; |
| mGeometryShaderOutputPrimitiveType = EptUndefined; |
| mGeometryShaderInvocations = 0; |
| mGeometryShaderMaxVertices = -1; |
| |
| mTessControlShaderOutputVertices = 0; |
| mTessEvaluationShaderInputPrimitiveType = EtetUndefined; |
| mTessEvaluationShaderInputVertexSpacingType = EtetUndefined; |
| mTessEvaluationShaderInputOrderingType = EtetUndefined; |
| mTessEvaluationShaderInputPointType = EtetUndefined; |
| |
| mBuiltInFunctionEmulator.cleanup(); |
| |
| mNameMap.clear(); |
| |
| mSourcePath = nullptr; |
| |
| mSymbolTable.clearCompilationResults(); |
| } |
| |
| bool TCompiler::initCallDag(TIntermNode *root) |
| { |
| mCallDag.clear(); |
| |
| switch (mCallDag.init(root, &mDiagnostics)) |
| { |
| case CallDAG::INITDAG_SUCCESS: |
| return true; |
| case CallDAG::INITDAG_RECURSION: |
| case CallDAG::INITDAG_UNDEFINED: |
| // Error message has already been written out. |
| ASSERT(mDiagnostics.numErrors() > 0); |
| return false; |
| } |
| |
| UNREACHABLE(); |
| return true; |
| } |
| |
| bool TCompiler::checkCallDepth() |
| { |
| std::vector<int> depths(mCallDag.size()); |
| |
| for (size_t i = 0; i < mCallDag.size(); i++) |
| { |
| int depth = 0; |
| const CallDAG::Record &record = mCallDag.getRecordFromIndex(i); |
| |
| for (int calleeIndex : record.callees) |
| { |
| depth = std::max(depth, depths[calleeIndex] + 1); |
| } |
| |
| depths[i] = depth; |
| |
| if (depth >= mResources.MaxCallStackDepth) |
| { |
| // Trace back the function chain to have a meaningful info log. |
| std::stringstream errorStream = sh::InitializeStream<std::stringstream>(); |
| errorStream << "Call stack too deep (larger than " << mResources.MaxCallStackDepth |
| << ") with the following call chain: " |
| << record.node->getFunction()->name(); |
| |
| int currentFunction = static_cast<int>(i); |
| int currentDepth = depth; |
| |
| while (currentFunction != -1) |
| { |
| errorStream |
| << " -> " |
| << mCallDag.getRecordFromIndex(currentFunction).node->getFunction()->name(); |
| |
| int nextFunction = -1; |
| for (const int &calleeIndex : mCallDag.getRecordFromIndex(currentFunction).callees) |
| { |
| if (depths[calleeIndex] == currentDepth - 1) |
| { |
| currentDepth--; |
| nextFunction = calleeIndex; |
| } |
| } |
| |
| currentFunction = nextFunction; |
| } |
| |
| std::string errorStr = errorStream.str(); |
| mDiagnostics.globalError(errorStr.c_str()); |
| |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool TCompiler::tagUsedFunctions() |
| { |
| // Search from main, starting from the end of the DAG as it usually is the root. |
| for (size_t i = mCallDag.size(); i-- > 0;) |
| { |
| if (mCallDag.getRecordFromIndex(i).node->getFunction()->isMain()) |
| { |
| internalTagUsedFunction(i); |
| return true; |
| } |
| } |
| |
| mDiagnostics.globalError("Missing main()"); |
| return false; |
| } |
| |
| void TCompiler::internalTagUsedFunction(size_t index) |
| { |
| if (mFunctionMetadata[index].used) |
| { |
| return; |
| } |
| |
| mFunctionMetadata[index].used = true; |
| |
| for (int calleeIndex : mCallDag.getRecordFromIndex(index).callees) |
| { |
| internalTagUsedFunction(calleeIndex); |
| } |
| } |
| |
| bool TCompiler::pruneUnusedFunctions(TIntermBlock *root) |
| { |
| TIntermSequence *sequence = root->getSequence(); |
| |
| size_t writeIndex = 0; |
| for (size_t readIndex = 0; readIndex < sequence->size(); ++readIndex) |
| { |
| TIntermNode *node = sequence->at(readIndex); |
| |
| // Keep anything that's not unused. |
| const TFunction *function = nullptr; |
| const bool shouldPrune = |
| IsTopLevelNodeUnusedFunction(mCallDag, mFunctionMetadata, node, &function); |
| if (!shouldPrune) |
| { |
| (*sequence)[writeIndex++] = node; |
| continue; |
| } |
| |
| // If a function is unused, it may have a struct declaration in its return value which |
| // shouldn't be pruned. In that case, replace the function definition with the struct |
| // definition. |
| ASSERT(function != nullptr); |
| const TType &returnType = function->getReturnType(); |
| if (!returnType.isStructSpecifier()) |
| { |
| continue; |
| } |
| |
| TVariable *structVariable = |
| new TVariable(&mSymbolTable, kEmptyImmutableString, &returnType, SymbolType::Empty); |
| TIntermSymbol *structSymbol = new TIntermSymbol(structVariable); |
| TIntermDeclaration *structDeclaration = new TIntermDeclaration; |
| structDeclaration->appendDeclarator(structSymbol); |
| |
| structSymbol->setLine(node->getLine()); |
| structDeclaration->setLine(node->getLine()); |
| |
| (*sequence)[writeIndex++] = structDeclaration; |
| } |
| |
| sequence->resize(writeIndex); |
| |
| return validateAST(root); |
| } |
| |
| bool TCompiler::limitExpressionComplexity(TIntermBlock *root) |
| { |
| if (!IsASTDepthBelowLimit(root, mResources.MaxExpressionComplexity)) |
| { |
| mDiagnostics.globalError("Expression too complex."); |
| return false; |
| } |
| |
| if (!ValidateMaxParameters(root, mResources.MaxFunctionParameters)) |
| { |
| mDiagnostics.globalError("Function has too many parameters."); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool TCompiler::initializeGLPosition(TIntermBlock *root) |
| { |
| sh::ShaderVariable var(GL_FLOAT_VEC4); |
| var.name = "gl_Position"; |
| return InitializeVariables(this, root, {var}, &mSymbolTable, mShaderVersion, mExtensionBehavior, |
| false, false); |
| } |
| |
| bool TCompiler::useAllMembersInUnusedStandardAndSharedBlocks(TIntermBlock *root) |
| { |
| sh::InterfaceBlockList list; |
| |
| for (const sh::InterfaceBlock &block : mUniformBlocks) |
| { |
| if (!block.staticUse && |
| (block.layout == sh::BLOCKLAYOUT_STD140 || block.layout == sh::BLOCKLAYOUT_SHARED)) |
| { |
| list.push_back(block); |
| } |
| } |
| |
| return sh::UseInterfaceBlockFields(this, root, list, mSymbolTable); |
| } |
| |
| bool TCompiler::initializeOutputVariables(TIntermBlock *root) |
| { |
| InitVariableList list; |
| list.reserve(mOutputVaryings.size()); |
| if (mShaderType == GL_VERTEX_SHADER || mShaderType == GL_GEOMETRY_SHADER_EXT || |
| mShaderType == GL_TESS_CONTROL_SHADER_EXT || mShaderType == GL_TESS_EVALUATION_SHADER_EXT) |
| { |
| for (const sh::ShaderVariable &var : mOutputVaryings) |
| { |
| list.push_back(var); |
| if (var.name == "gl_Position") |
| { |
| ASSERT(!mGLPositionInitialized); |
| mGLPositionInitialized = true; |
| } |
| } |
| } |
| else |
| { |
| ASSERT(mShaderType == GL_FRAGMENT_SHADER); |
| for (const sh::ShaderVariable &var : mOutputVariables) |
| { |
| // in-out variables represent the context of the framebuffer |
| // when the draw call starts, so they have to be considered |
| // as already initialized. |
| if (!var.isFragmentInOut) |
| { |
| list.push_back(var); |
| } |
| } |
| } |
| return InitializeVariables(this, root, list, &mSymbolTable, mShaderVersion, mExtensionBehavior, |
| false, false); |
| } |
| |
| const TExtensionBehavior &TCompiler::getExtensionBehavior() const |
| { |
| return mExtensionBehavior; |
| } |
| |
| const char *TCompiler::getSourcePath() const |
| { |
| return mSourcePath; |
| } |
| |
| const ShBuiltInResources &TCompiler::getResources() const |
| { |
| return mResources; |
| } |
| |
| const BuiltInFunctionEmulator &TCompiler::getBuiltInFunctionEmulator() const |
| { |
| return mBuiltInFunctionEmulator; |
| } |
| |
| bool TCompiler::isVaryingDefined(const char *varyingName) |
| { |
| ASSERT(mVariablesCollected); |
| for (size_t ii = 0; ii < mInputVaryings.size(); ++ii) |
| { |
| if (mInputVaryings[ii].name == varyingName) |
| { |
| return true; |
| } |
| } |
| for (size_t ii = 0; ii < mOutputVaryings.size(); ++ii) |
| { |
| if (mOutputVaryings[ii].name == varyingName) |
| { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| } // namespace sh |