| // |
| // Copyright (C) 2014-2016 LunarG, Inc. |
| // Copyright (C) 2015-2016 Google, Inc. |
| // Copyright (C) 2017 ARM Limited. |
| // |
| // All rights reserved. |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions |
| // are met: |
| // |
| // Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // |
| // Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following |
| // disclaimer in the documentation and/or other materials provided |
| // with the distribution. |
| // |
| // Neither the name of 3Dlabs Inc. Ltd. nor the names of its |
| // contributors may be used to endorse or promote products derived |
| // from this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
| // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
| // COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
| // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
| // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
| // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| // POSSIBILITY OF SUCH DAMAGE. |
| |
| // |
| // Visit the nodes in the glslang intermediate tree representation to |
| // translate them to SPIR-V. |
| // |
| |
| #include "spirv.hpp" |
| #include "GlslangToSpv.h" |
| #include "SpvBuilder.h" |
| namespace spv { |
| #include "GLSL.std.450.h" |
| #include "GLSL.ext.KHR.h" |
| #ifdef AMD_EXTENSIONS |
| #include "GLSL.ext.AMD.h" |
| #endif |
| #ifdef NV_EXTENSIONS |
| #include "GLSL.ext.NV.h" |
| #endif |
| } |
| |
| #ifdef ENABLE_OPT |
| #include "spirv-tools/optimizer.hpp" |
| #include "message.h" |
| #include "SPVRemapper.h" |
| #endif |
| |
| #ifdef ENABLE_OPT |
| using namespace spvtools; |
| #endif |
| |
| // Glslang includes |
| #include "../glslang/MachineIndependent/localintermediate.h" |
| #include "../glslang/MachineIndependent/SymbolTable.h" |
| #include "../glslang/Include/Common.h" |
| #include "../glslang/Include/revision.h" |
| |
| #include <fstream> |
| #include <iomanip> |
| #include <list> |
| #include <map> |
| #include <stack> |
| #include <string> |
| #include <vector> |
| |
| namespace { |
| |
| // For low-order part of the generator's magic number. Bump up |
| // when there is a change in the style (e.g., if SSA form changes, |
| // or a different instruction sequence to do something gets used). |
| const int GeneratorVersion = 2; |
| |
| namespace { |
| class SpecConstantOpModeGuard { |
| public: |
| SpecConstantOpModeGuard(spv::Builder* builder) |
| : builder_(builder) { |
| previous_flag_ = builder->isInSpecConstCodeGenMode(); |
| } |
| ~SpecConstantOpModeGuard() { |
| previous_flag_ ? builder_->setToSpecConstCodeGenMode() |
| : builder_->setToNormalCodeGenMode(); |
| } |
| void turnOnSpecConstantOpMode() { |
| builder_->setToSpecConstCodeGenMode(); |
| } |
| |
| private: |
| spv::Builder* builder_; |
| bool previous_flag_; |
| }; |
| } |
| |
| // |
| // The main holder of information for translating glslang to SPIR-V. |
| // |
| // Derives from the AST walking base class. |
| // |
| class TGlslangToSpvTraverser : public glslang::TIntermTraverser { |
| public: |
| TGlslangToSpvTraverser(const glslang::TIntermediate*, spv::SpvBuildLogger* logger, glslang::SpvOptions& options); |
| virtual ~TGlslangToSpvTraverser() { } |
| |
| bool visitAggregate(glslang::TVisit, glslang::TIntermAggregate*); |
| bool visitBinary(glslang::TVisit, glslang::TIntermBinary*); |
| void visitConstantUnion(glslang::TIntermConstantUnion*); |
| bool visitSelection(glslang::TVisit, glslang::TIntermSelection*); |
| bool visitSwitch(glslang::TVisit, glslang::TIntermSwitch*); |
| void visitSymbol(glslang::TIntermSymbol* symbol); |
| bool visitUnary(glslang::TVisit, glslang::TIntermUnary*); |
| bool visitLoop(glslang::TVisit, glslang::TIntermLoop*); |
| bool visitBranch(glslang::TVisit visit, glslang::TIntermBranch*); |
| |
| void finishSpv(); |
| void dumpSpv(std::vector<unsigned int>& out); |
| |
| protected: |
| spv::Decoration TranslateInterpolationDecoration(const glslang::TQualifier& qualifier); |
| spv::Decoration TranslateAuxiliaryStorageDecoration(const glslang::TQualifier& qualifier); |
| spv::BuiltIn TranslateBuiltInDecoration(glslang::TBuiltInVariable, bool memberDeclaration); |
| spv::ImageFormat TranslateImageFormat(const glslang::TType& type); |
| spv::SelectionControlMask TranslateSelectionControl(glslang::TSelectionControl) const; |
| spv::LoopControlMask TranslateLoopControl(glslang::TLoopControl) const; |
| spv::StorageClass TranslateStorageClass(const glslang::TType&); |
| spv::Id createSpvVariable(const glslang::TIntermSymbol*); |
| spv::Id getSampledType(const glslang::TSampler&); |
| spv::Id getInvertedSwizzleType(const glslang::TIntermTyped&); |
| spv::Id createInvertedSwizzle(spv::Decoration precision, const glslang::TIntermTyped&, spv::Id parentResult); |
| void convertSwizzle(const glslang::TIntermAggregate&, std::vector<unsigned>& swizzle); |
| spv::Id convertGlslangToSpvType(const glslang::TType& type); |
| spv::Id convertGlslangToSpvType(const glslang::TType& type, glslang::TLayoutPacking, const glslang::TQualifier&); |
| bool filterMember(const glslang::TType& member); |
| spv::Id convertGlslangStructToSpvType(const glslang::TType&, const glslang::TTypeList* glslangStruct, |
| glslang::TLayoutPacking, const glslang::TQualifier&); |
| void decorateStructType(const glslang::TType&, const glslang::TTypeList* glslangStruct, glslang::TLayoutPacking, |
| const glslang::TQualifier&, spv::Id); |
| spv::Id makeArraySizeId(const glslang::TArraySizes&, int dim); |
| spv::Id accessChainLoad(const glslang::TType& type); |
| void accessChainStore(const glslang::TType& type, spv::Id rvalue); |
| void multiTypeStore(const glslang::TType&, spv::Id rValue); |
| glslang::TLayoutPacking getExplicitLayout(const glslang::TType& type) const; |
| int getArrayStride(const glslang::TType& arrayType, glslang::TLayoutPacking, glslang::TLayoutMatrix); |
| int getMatrixStride(const glslang::TType& matrixType, glslang::TLayoutPacking, glslang::TLayoutMatrix); |
| void updateMemberOffset(const glslang::TType& structType, const glslang::TType& memberType, int& currentOffset, int& nextOffset, glslang::TLayoutPacking, glslang::TLayoutMatrix); |
| void declareUseOfStructMember(const glslang::TTypeList& members, int glslangMember); |
| |
| bool isShaderEntryPoint(const glslang::TIntermAggregate* node); |
| bool writableParam(glslang::TStorageQualifier); |
| bool originalParam(glslang::TStorageQualifier, const glslang::TType&, bool implicitThisParam); |
| void makeFunctions(const glslang::TIntermSequence&); |
| void makeGlobalInitializers(const glslang::TIntermSequence&); |
| void visitFunctions(const glslang::TIntermSequence&); |
| void handleFunctionEntry(const glslang::TIntermAggregate* node); |
| void translateArguments(const glslang::TIntermAggregate& node, std::vector<spv::Id>& arguments); |
| void translateArguments(glslang::TIntermUnary& node, std::vector<spv::Id>& arguments); |
| spv::Id createImageTextureFunctionCall(glslang::TIntermOperator* node); |
| spv::Id handleUserFunctionCall(const glslang::TIntermAggregate*); |
| |
| spv::Id createBinaryOperation(glslang::TOperator op, spv::Decoration precision, spv::Decoration noContraction, spv::Id typeId, spv::Id left, spv::Id right, glslang::TBasicType typeProxy, bool reduceComparison = true); |
| spv::Id createBinaryMatrixOperation(spv::Op, spv::Decoration precision, spv::Decoration noContraction, spv::Id typeId, spv::Id left, spv::Id right); |
| spv::Id createUnaryOperation(glslang::TOperator op, spv::Decoration precision, spv::Decoration noContraction, spv::Id typeId, spv::Id operand,glslang::TBasicType typeProxy); |
| spv::Id createUnaryMatrixOperation(spv::Op op, spv::Decoration precision, spv::Decoration noContraction, spv::Id typeId, spv::Id operand,glslang::TBasicType typeProxy); |
| spv::Id createConversion(glslang::TOperator op, spv::Decoration precision, spv::Decoration noContraction, spv::Id destTypeId, spv::Id operand, glslang::TBasicType typeProxy); |
| spv::Id createConversionOperation(glslang::TOperator op, spv::Id operand, int vectorSize); |
| spv::Id makeSmearedConstant(spv::Id constant, int vectorSize); |
| spv::Id createAtomicOperation(glslang::TOperator op, spv::Decoration precision, spv::Id typeId, std::vector<spv::Id>& operands, glslang::TBasicType typeProxy); |
| spv::Id createInvocationsOperation(glslang::TOperator op, spv::Id typeId, std::vector<spv::Id>& operands, glslang::TBasicType typeProxy); |
| spv::Id CreateInvocationsVectorOperation(spv::Op op, spv::GroupOperation groupOperation, spv::Id typeId, std::vector<spv::Id>& operands); |
| spv::Id createSubgroupOperation(glslang::TOperator op, spv::Id typeId, std::vector<spv::Id>& operands, glslang::TBasicType typeProxy); |
| spv::Id createMiscOperation(glslang::TOperator op, spv::Decoration precision, spv::Id typeId, std::vector<spv::Id>& operands, glslang::TBasicType typeProxy); |
| spv::Id createNoArgOperation(glslang::TOperator op, spv::Decoration precision, spv::Id typeId); |
| spv::Id getSymbolId(const glslang::TIntermSymbol* node); |
| void addDecoration(spv::Id id, spv::Decoration dec); |
| void addDecoration(spv::Id id, spv::Decoration dec, unsigned value); |
| void addMemberDecoration(spv::Id id, int member, spv::Decoration dec); |
| void addMemberDecoration(spv::Id id, int member, spv::Decoration dec, unsigned value); |
| spv::Id createSpvConstant(const glslang::TIntermTyped&); |
| spv::Id createSpvConstantFromConstUnionArray(const glslang::TType& type, const glslang::TConstUnionArray&, int& nextConst, bool specConstant); |
| bool isTrivialLeaf(const glslang::TIntermTyped* node); |
| bool isTrivial(const glslang::TIntermTyped* node); |
| spv::Id createShortCircuit(glslang::TOperator, glslang::TIntermTyped& left, glslang::TIntermTyped& right); |
| spv::Id getExtBuiltins(const char* name); |
| |
| glslang::SpvOptions& options; |
| spv::Function* shaderEntry; |
| spv::Function* currentFunction; |
| spv::Instruction* entryPoint; |
| int sequenceDepth; |
| |
| spv::SpvBuildLogger* logger; |
| |
| // There is a 1:1 mapping between a spv builder and a module; this is thread safe |
| spv::Builder builder; |
| bool inEntryPoint; |
| bool entryPointTerminated; |
| bool linkageOnly; // true when visiting the set of objects in the AST present only for establishing interface, whether or not they were statically used |
| std::set<spv::Id> iOSet; // all input/output variables from either static use or declaration of interface |
| const glslang::TIntermediate* glslangIntermediate; |
| spv::Id stdBuiltins; |
| std::unordered_map<const char*, spv::Id> extBuiltinMap; |
| |
| std::unordered_map<int, spv::Id> symbolValues; |
| std::unordered_set<int> rValueParameters; // set of formal function parameters passed as rValues, rather than a pointer |
| std::unordered_map<std::string, spv::Function*> functionMap; |
| std::unordered_map<const glslang::TTypeList*, spv::Id> structMap[glslang::ElpCount][glslang::ElmCount]; |
| std::unordered_map<const glslang::TTypeList*, std::vector<int> > memberRemapper; // for mapping glslang block indices to spv indices (e.g., due to hidden members) |
| std::stack<bool> breakForLoop; // false means break for switch |
| }; |
| |
| // |
| // Helper functions for translating glslang representations to SPIR-V enumerants. |
| // |
| |
| // Translate glslang profile to SPIR-V source language. |
| spv::SourceLanguage TranslateSourceLanguage(glslang::EShSource source, EProfile profile) |
| { |
| switch (source) { |
| case glslang::EShSourceGlsl: |
| switch (profile) { |
| case ENoProfile: |
| case ECoreProfile: |
| case ECompatibilityProfile: |
| return spv::SourceLanguageGLSL; |
| case EEsProfile: |
| return spv::SourceLanguageESSL; |
| default: |
| return spv::SourceLanguageUnknown; |
| } |
| case glslang::EShSourceHlsl: |
| return spv::SourceLanguageHLSL; |
| default: |
| return spv::SourceLanguageUnknown; |
| } |
| } |
| |
| // Translate glslang language (stage) to SPIR-V execution model. |
| spv::ExecutionModel TranslateExecutionModel(EShLanguage stage) |
| { |
| switch (stage) { |
| case EShLangVertex: return spv::ExecutionModelVertex; |
| case EShLangTessControl: return spv::ExecutionModelTessellationControl; |
| case EShLangTessEvaluation: return spv::ExecutionModelTessellationEvaluation; |
| case EShLangGeometry: return spv::ExecutionModelGeometry; |
| case EShLangFragment: return spv::ExecutionModelFragment; |
| case EShLangCompute: return spv::ExecutionModelGLCompute; |
| default: |
| assert(0); |
| return spv::ExecutionModelFragment; |
| } |
| } |
| |
| // Translate glslang sampler type to SPIR-V dimensionality. |
| spv::Dim TranslateDimensionality(const glslang::TSampler& sampler) |
| { |
| switch (sampler.dim) { |
| case glslang::Esd1D: return spv::Dim1D; |
| case glslang::Esd2D: return spv::Dim2D; |
| case glslang::Esd3D: return spv::Dim3D; |
| case glslang::EsdCube: return spv::DimCube; |
| case glslang::EsdRect: return spv::DimRect; |
| case glslang::EsdBuffer: return spv::DimBuffer; |
| case glslang::EsdSubpass: return spv::DimSubpassData; |
| default: |
| assert(0); |
| return spv::Dim2D; |
| } |
| } |
| |
| // Translate glslang precision to SPIR-V precision decorations. |
| spv::Decoration TranslatePrecisionDecoration(glslang::TPrecisionQualifier glslangPrecision) |
| { |
| switch (glslangPrecision) { |
| case glslang::EpqLow: return spv::DecorationRelaxedPrecision; |
| case glslang::EpqMedium: return spv::DecorationRelaxedPrecision; |
| default: |
| return spv::NoPrecision; |
| } |
| } |
| |
| // Translate glslang type to SPIR-V precision decorations. |
| spv::Decoration TranslatePrecisionDecoration(const glslang::TType& type) |
| { |
| return TranslatePrecisionDecoration(type.getQualifier().precision); |
| } |
| |
| // Translate glslang type to SPIR-V block decorations. |
| spv::Decoration TranslateBlockDecoration(const glslang::TType& type, bool useStorageBuffer) |
| { |
| if (type.getBasicType() == glslang::EbtBlock) { |
| switch (type.getQualifier().storage) { |
| case glslang::EvqUniform: return spv::DecorationBlock; |
| case glslang::EvqBuffer: return useStorageBuffer ? spv::DecorationBlock : spv::DecorationBufferBlock; |
| case glslang::EvqVaryingIn: return spv::DecorationBlock; |
| case glslang::EvqVaryingOut: return spv::DecorationBlock; |
| default: |
| assert(0); |
| break; |
| } |
| } |
| |
| return spv::DecorationMax; |
| } |
| |
| // Translate glslang type to SPIR-V memory decorations. |
| void TranslateMemoryDecoration(const glslang::TQualifier& qualifier, std::vector<spv::Decoration>& memory) |
| { |
| if (qualifier.coherent) |
| memory.push_back(spv::DecorationCoherent); |
| if (qualifier.volatil) |
| memory.push_back(spv::DecorationVolatile); |
| if (qualifier.restrict) |
| memory.push_back(spv::DecorationRestrict); |
| if (qualifier.readonly) |
| memory.push_back(spv::DecorationNonWritable); |
| if (qualifier.writeonly) |
| memory.push_back(spv::DecorationNonReadable); |
| } |
| |
| // Translate glslang type to SPIR-V layout decorations. |
| spv::Decoration TranslateLayoutDecoration(const glslang::TType& type, glslang::TLayoutMatrix matrixLayout) |
| { |
| if (type.isMatrix()) { |
| switch (matrixLayout) { |
| case glslang::ElmRowMajor: |
| return spv::DecorationRowMajor; |
| case glslang::ElmColumnMajor: |
| return spv::DecorationColMajor; |
| default: |
| // opaque layouts don't need a majorness |
| return spv::DecorationMax; |
| } |
| } else { |
| switch (type.getBasicType()) { |
| default: |
| return spv::DecorationMax; |
| break; |
| case glslang::EbtBlock: |
| switch (type.getQualifier().storage) { |
| case glslang::EvqUniform: |
| case glslang::EvqBuffer: |
| switch (type.getQualifier().layoutPacking) { |
| case glslang::ElpShared: return spv::DecorationGLSLShared; |
| case glslang::ElpPacked: return spv::DecorationGLSLPacked; |
| default: |
| return spv::DecorationMax; |
| } |
| case glslang::EvqVaryingIn: |
| case glslang::EvqVaryingOut: |
| assert(type.getQualifier().layoutPacking == glslang::ElpNone); |
| return spv::DecorationMax; |
| default: |
| assert(0); |
| return spv::DecorationMax; |
| } |
| } |
| } |
| } |
| |
| // Translate glslang type to SPIR-V interpolation decorations. |
| // Returns spv::DecorationMax when no decoration |
| // should be applied. |
| spv::Decoration TGlslangToSpvTraverser::TranslateInterpolationDecoration(const glslang::TQualifier& qualifier) |
| { |
| if (qualifier.smooth) |
| // Smooth decoration doesn't exist in SPIR-V 1.0 |
| return spv::DecorationMax; |
| else if (qualifier.nopersp) |
| return spv::DecorationNoPerspective; |
| else if (qualifier.flat) |
| return spv::DecorationFlat; |
| #ifdef AMD_EXTENSIONS |
| else if (qualifier.explicitInterp) { |
| builder.addExtension(spv::E_SPV_AMD_shader_explicit_vertex_parameter); |
| return spv::DecorationExplicitInterpAMD; |
| } |
| #endif |
| else |
| return spv::DecorationMax; |
| } |
| |
| // Translate glslang type to SPIR-V auxiliary storage decorations. |
| // Returns spv::DecorationMax when no decoration |
| // should be applied. |
| spv::Decoration TGlslangToSpvTraverser::TranslateAuxiliaryStorageDecoration(const glslang::TQualifier& qualifier) |
| { |
| if (qualifier.patch) |
| return spv::DecorationPatch; |
| else if (qualifier.centroid) |
| return spv::DecorationCentroid; |
| else if (qualifier.sample) { |
| builder.addCapability(spv::CapabilitySampleRateShading); |
| return spv::DecorationSample; |
| } else |
| return spv::DecorationMax; |
| } |
| |
| // If glslang type is invariant, return SPIR-V invariant decoration. |
| spv::Decoration TranslateInvariantDecoration(const glslang::TQualifier& qualifier) |
| { |
| if (qualifier.invariant) |
| return spv::DecorationInvariant; |
| else |
| return spv::DecorationMax; |
| } |
| |
| // If glslang type is noContraction, return SPIR-V NoContraction decoration. |
| spv::Decoration TranslateNoContractionDecoration(const glslang::TQualifier& qualifier) |
| { |
| if (qualifier.noContraction) |
| return spv::DecorationNoContraction; |
| else |
| return spv::DecorationMax; |
| } |
| |
| // Translate a glslang built-in variable to a SPIR-V built in decoration. Also generate |
| // associated capabilities when required. For some built-in variables, a capability |
| // is generated only when using the variable in an executable instruction, but not when |
| // just declaring a struct member variable with it. This is true for PointSize, |
| // ClipDistance, and CullDistance. |
| spv::BuiltIn TGlslangToSpvTraverser::TranslateBuiltInDecoration(glslang::TBuiltInVariable builtIn, bool memberDeclaration) |
| { |
| switch (builtIn) { |
| case glslang::EbvPointSize: |
| // Defer adding the capability until the built-in is actually used. |
| if (! memberDeclaration) { |
| switch (glslangIntermediate->getStage()) { |
| case EShLangGeometry: |
| builder.addCapability(spv::CapabilityGeometryPointSize); |
| break; |
| case EShLangTessControl: |
| case EShLangTessEvaluation: |
| builder.addCapability(spv::CapabilityTessellationPointSize); |
| break; |
| default: |
| break; |
| } |
| } |
| return spv::BuiltInPointSize; |
| |
| // These *Distance capabilities logically belong here, but if the member is declared and |
| // then never used, consumers of SPIR-V prefer the capability not be declared. |
| // They are now generated when used, rather than here when declared. |
| // Potentially, the specification should be more clear what the minimum |
| // use needed is to trigger the capability. |
| // |
| case glslang::EbvClipDistance: |
| if (!memberDeclaration) |
| builder.addCapability(spv::CapabilityClipDistance); |
| return spv::BuiltInClipDistance; |
| |
| case glslang::EbvCullDistance: |
| if (!memberDeclaration) |
| builder.addCapability(spv::CapabilityCullDistance); |
| return spv::BuiltInCullDistance; |
| |
| case glslang::EbvViewportIndex: |
| builder.addCapability(spv::CapabilityMultiViewport); |
| if (glslangIntermediate->getStage() == EShLangVertex || |
| glslangIntermediate->getStage() == EShLangTessControl || |
| glslangIntermediate->getStage() == EShLangTessEvaluation) { |
| |
| builder.addExtension(spv::E_SPV_EXT_shader_viewport_index_layer); |
| builder.addCapability(spv::CapabilityShaderViewportIndexLayerEXT); |
| } |
| return spv::BuiltInViewportIndex; |
| |
| case glslang::EbvSampleId: |
| builder.addCapability(spv::CapabilitySampleRateShading); |
| return spv::BuiltInSampleId; |
| |
| case glslang::EbvSamplePosition: |
| builder.addCapability(spv::CapabilitySampleRateShading); |
| return spv::BuiltInSamplePosition; |
| |
| case glslang::EbvSampleMask: |
| builder.addCapability(spv::CapabilitySampleRateShading); |
| return spv::BuiltInSampleMask; |
| |
| case glslang::EbvLayer: |
| builder.addCapability(spv::CapabilityGeometry); |
| if (glslangIntermediate->getStage() == EShLangVertex || |
| glslangIntermediate->getStage() == EShLangTessControl || |
| glslangIntermediate->getStage() == EShLangTessEvaluation) { |
| |
| builder.addExtension(spv::E_SPV_EXT_shader_viewport_index_layer); |
| builder.addCapability(spv::CapabilityShaderViewportIndexLayerEXT); |
| } |
| return spv::BuiltInLayer; |
| |
| case glslang::EbvPosition: return spv::BuiltInPosition; |
| case glslang::EbvVertexId: return spv::BuiltInVertexId; |
| case glslang::EbvInstanceId: return spv::BuiltInInstanceId; |
| case glslang::EbvVertexIndex: return spv::BuiltInVertexIndex; |
| case glslang::EbvInstanceIndex: return spv::BuiltInInstanceIndex; |
| |
| case glslang::EbvBaseVertex: |
| builder.addExtension(spv::E_SPV_KHR_shader_draw_parameters); |
| builder.addCapability(spv::CapabilityDrawParameters); |
| return spv::BuiltInBaseVertex; |
| |
| case glslang::EbvBaseInstance: |
| builder.addExtension(spv::E_SPV_KHR_shader_draw_parameters); |
| builder.addCapability(spv::CapabilityDrawParameters); |
| return spv::BuiltInBaseInstance; |
| |
| case glslang::EbvDrawId: |
| builder.addExtension(spv::E_SPV_KHR_shader_draw_parameters); |
| builder.addCapability(spv::CapabilityDrawParameters); |
| return spv::BuiltInDrawIndex; |
| |
| case glslang::EbvPrimitiveId: |
| if (glslangIntermediate->getStage() == EShLangFragment) |
| builder.addCapability(spv::CapabilityGeometry); |
| return spv::BuiltInPrimitiveId; |
| |
| case glslang::EbvFragStencilRef: |
| builder.addExtension(spv::E_SPV_EXT_shader_stencil_export); |
| builder.addCapability(spv::CapabilityStencilExportEXT); |
| return spv::BuiltInFragStencilRefEXT; |
| |
| case glslang::EbvInvocationId: return spv::BuiltInInvocationId; |
| case glslang::EbvTessLevelInner: return spv::BuiltInTessLevelInner; |
| case glslang::EbvTessLevelOuter: return spv::BuiltInTessLevelOuter; |
| case glslang::EbvTessCoord: return spv::BuiltInTessCoord; |
| case glslang::EbvPatchVertices: return spv::BuiltInPatchVertices; |
| case glslang::EbvFragCoord: return spv::BuiltInFragCoord; |
| case glslang::EbvPointCoord: return spv::BuiltInPointCoord; |
| case glslang::EbvFace: return spv::BuiltInFrontFacing; |
| case glslang::EbvFragDepth: return spv::BuiltInFragDepth; |
| case glslang::EbvHelperInvocation: return spv::BuiltInHelperInvocation; |
| case glslang::EbvNumWorkGroups: return spv::BuiltInNumWorkgroups; |
| case glslang::EbvWorkGroupSize: return spv::BuiltInWorkgroupSize; |
| case glslang::EbvWorkGroupId: return spv::BuiltInWorkgroupId; |
| case glslang::EbvLocalInvocationId: return spv::BuiltInLocalInvocationId; |
| case glslang::EbvLocalInvocationIndex: return spv::BuiltInLocalInvocationIndex; |
| case glslang::EbvGlobalInvocationId: return spv::BuiltInGlobalInvocationId; |
| |
| case glslang::EbvSubGroupSize: |
| builder.addExtension(spv::E_SPV_KHR_shader_ballot); |
| builder.addCapability(spv::CapabilitySubgroupBallotKHR); |
| return spv::BuiltInSubgroupSize; |
| |
| case glslang::EbvSubGroupInvocation: |
| builder.addExtension(spv::E_SPV_KHR_shader_ballot); |
| builder.addCapability(spv::CapabilitySubgroupBallotKHR); |
| return spv::BuiltInSubgroupLocalInvocationId; |
| |
| case glslang::EbvSubGroupEqMask: |
| builder.addExtension(spv::E_SPV_KHR_shader_ballot); |
| builder.addCapability(spv::CapabilitySubgroupBallotKHR); |
| return spv::BuiltInSubgroupEqMaskKHR; |
| |
| case glslang::EbvSubGroupGeMask: |
| builder.addExtension(spv::E_SPV_KHR_shader_ballot); |
| builder.addCapability(spv::CapabilitySubgroupBallotKHR); |
| return spv::BuiltInSubgroupGeMaskKHR; |
| |
| case glslang::EbvSubGroupGtMask: |
| builder.addExtension(spv::E_SPV_KHR_shader_ballot); |
| builder.addCapability(spv::CapabilitySubgroupBallotKHR); |
| return spv::BuiltInSubgroupGtMaskKHR; |
| |
| case glslang::EbvSubGroupLeMask: |
| builder.addExtension(spv::E_SPV_KHR_shader_ballot); |
| builder.addCapability(spv::CapabilitySubgroupBallotKHR); |
| return spv::BuiltInSubgroupLeMaskKHR; |
| |
| case glslang::EbvSubGroupLtMask: |
| builder.addExtension(spv::E_SPV_KHR_shader_ballot); |
| builder.addCapability(spv::CapabilitySubgroupBallotKHR); |
| return spv::BuiltInSubgroupLtMaskKHR; |
| |
| case glslang::EbvNumSubgroups: |
| builder.addCapability(spv::CapabilityGroupNonUniform); |
| return spv::BuiltInNumSubgroups; |
| |
| case glslang::EbvSubgroupID: |
| builder.addCapability(spv::CapabilityGroupNonUniform); |
| return spv::BuiltInSubgroupId; |
| |
| case glslang::EbvSubgroupSize2: |
| builder.addCapability(spv::CapabilityGroupNonUniform); |
| return spv::BuiltInSubgroupSize; |
| |
| case glslang::EbvSubgroupInvocation2: |
| builder.addCapability(spv::CapabilityGroupNonUniform); |
| return spv::BuiltInSubgroupLocalInvocationId; |
| |
| case glslang::EbvSubgroupEqMask2: |
| builder.addCapability(spv::CapabilityGroupNonUniform); |
| builder.addCapability(spv::CapabilityGroupNonUniformBallot); |
| return spv::BuiltInSubgroupEqMask; |
| |
| case glslang::EbvSubgroupGeMask2: |
| builder.addCapability(spv::CapabilityGroupNonUniform); |
| builder.addCapability(spv::CapabilityGroupNonUniformBallot); |
| return spv::BuiltInSubgroupGeMask; |
| |
| case glslang::EbvSubgroupGtMask2: |
| builder.addCapability(spv::CapabilityGroupNonUniform); |
| builder.addCapability(spv::CapabilityGroupNonUniformBallot); |
| return spv::BuiltInSubgroupGtMask; |
| |
| case glslang::EbvSubgroupLeMask2: |
| builder.addCapability(spv::CapabilityGroupNonUniform); |
| builder.addCapability(spv::CapabilityGroupNonUniformBallot); |
| return spv::BuiltInSubgroupLeMask; |
| |
| case glslang::EbvSubgroupLtMask2: |
| builder.addCapability(spv::CapabilityGroupNonUniform); |
| builder.addCapability(spv::CapabilityGroupNonUniformBallot); |
| return spv::BuiltInSubgroupLtMask; |
| #ifdef AMD_EXTENSIONS |
| case glslang::EbvBaryCoordNoPersp: |
| builder.addExtension(spv::E_SPV_AMD_shader_explicit_vertex_parameter); |
| return spv::BuiltInBaryCoordNoPerspAMD; |
| |
| case glslang::EbvBaryCoordNoPerspCentroid: |
| builder.addExtension(spv::E_SPV_AMD_shader_explicit_vertex_parameter); |
| return spv::BuiltInBaryCoordNoPerspCentroidAMD; |
| |
| case glslang::EbvBaryCoordNoPerspSample: |
| builder.addExtension(spv::E_SPV_AMD_shader_explicit_vertex_parameter); |
| return spv::BuiltInBaryCoordNoPerspSampleAMD; |
| |
| case glslang::EbvBaryCoordSmooth: |
| builder.addExtension(spv::E_SPV_AMD_shader_explicit_vertex_parameter); |
| return spv::BuiltInBaryCoordSmoothAMD; |
| |
| case glslang::EbvBaryCoordSmoothCentroid: |
| builder.addExtension(spv::E_SPV_AMD_shader_explicit_vertex_parameter); |
| return spv::BuiltInBaryCoordSmoothCentroidAMD; |
| |
| case glslang::EbvBaryCoordSmoothSample: |
| builder.addExtension(spv::E_SPV_AMD_shader_explicit_vertex_parameter); |
| return spv::BuiltInBaryCoordSmoothSampleAMD; |
| |
| case glslang::EbvBaryCoordPullModel: |
| builder.addExtension(spv::E_SPV_AMD_shader_explicit_vertex_parameter); |
| return spv::BuiltInBaryCoordPullModelAMD; |
| #endif |
| |
| case glslang::EbvDeviceIndex: |
| builder.addExtension(spv::E_SPV_KHR_device_group); |
| builder.addCapability(spv::CapabilityDeviceGroup); |
| return spv::BuiltInDeviceIndex; |
| |
| case glslang::EbvViewIndex: |
| builder.addExtension(spv::E_SPV_KHR_multiview); |
| builder.addCapability(spv::CapabilityMultiView); |
| return spv::BuiltInViewIndex; |
| |
| #ifdef NV_EXTENSIONS |
| case glslang::EbvViewportMaskNV: |
| if (!memberDeclaration) { |
| builder.addExtension(spv::E_SPV_NV_viewport_array2); |
| builder.addCapability(spv::CapabilityShaderViewportMaskNV); |
| } |
| return spv::BuiltInViewportMaskNV; |
| case glslang::EbvSecondaryPositionNV: |
| if (!memberDeclaration) { |
| builder.addExtension(spv::E_SPV_NV_stereo_view_rendering); |
| builder.addCapability(spv::CapabilityShaderStereoViewNV); |
| } |
| return spv::BuiltInSecondaryPositionNV; |
| case glslang::EbvSecondaryViewportMaskNV: |
| if (!memberDeclaration) { |
| builder.addExtension(spv::E_SPV_NV_stereo_view_rendering); |
| builder.addCapability(spv::CapabilityShaderStereoViewNV); |
| } |
| return spv::BuiltInSecondaryViewportMaskNV; |
| case glslang::EbvPositionPerViewNV: |
| if (!memberDeclaration) { |
| builder.addExtension(spv::E_SPV_NVX_multiview_per_view_attributes); |
| builder.addCapability(spv::CapabilityPerViewAttributesNV); |
| } |
| return spv::BuiltInPositionPerViewNV; |
| case glslang::EbvViewportMaskPerViewNV: |
| if (!memberDeclaration) { |
| builder.addExtension(spv::E_SPV_NVX_multiview_per_view_attributes); |
| builder.addCapability(spv::CapabilityPerViewAttributesNV); |
| } |
| return spv::BuiltInViewportMaskPerViewNV; |
| #endif |
| default: |
| return spv::BuiltInMax; |
| } |
| } |
| |
| // Translate glslang image layout format to SPIR-V image format. |
| spv::ImageFormat TGlslangToSpvTraverser::TranslateImageFormat(const glslang::TType& type) |
| { |
| assert(type.getBasicType() == glslang::EbtSampler); |
| |
| // Check for capabilities |
| switch (type.getQualifier().layoutFormat) { |
| case glslang::ElfRg32f: |
| case glslang::ElfRg16f: |
| case glslang::ElfR11fG11fB10f: |
| case glslang::ElfR16f: |
| case glslang::ElfRgba16: |
| case glslang::ElfRgb10A2: |
| case glslang::ElfRg16: |
| case glslang::ElfRg8: |
| case glslang::ElfR16: |
| case glslang::ElfR8: |
| case glslang::ElfRgba16Snorm: |
| case glslang::ElfRg16Snorm: |
| case glslang::ElfRg8Snorm: |
| case glslang::ElfR16Snorm: |
| case glslang::ElfR8Snorm: |
| |
| case glslang::ElfRg32i: |
| case glslang::ElfRg16i: |
| case glslang::ElfRg8i: |
| case glslang::ElfR16i: |
| case glslang::ElfR8i: |
| |
| case glslang::ElfRgb10a2ui: |
| case glslang::ElfRg32ui: |
| case glslang::ElfRg16ui: |
| case glslang::ElfRg8ui: |
| case glslang::ElfR16ui: |
| case glslang::ElfR8ui: |
| builder.addCapability(spv::CapabilityStorageImageExtendedFormats); |
| break; |
| |
| default: |
| break; |
| } |
| |
| // do the translation |
| switch (type.getQualifier().layoutFormat) { |
| case glslang::ElfNone: return spv::ImageFormatUnknown; |
| case glslang::ElfRgba32f: return spv::ImageFormatRgba32f; |
| case glslang::ElfRgba16f: return spv::ImageFormatRgba16f; |
| case glslang::ElfR32f: return spv::ImageFormatR32f; |
| case glslang::ElfRgba8: return spv::ImageFormatRgba8; |
| case glslang::ElfRgba8Snorm: return spv::ImageFormatRgba8Snorm; |
| case glslang::ElfRg32f: return spv::ImageFormatRg32f; |
| case glslang::ElfRg16f: return spv::ImageFormatRg16f; |
| case glslang::ElfR11fG11fB10f: return spv::ImageFormatR11fG11fB10f; |
| case glslang::ElfR16f: return spv::ImageFormatR16f; |
| case glslang::ElfRgba16: return spv::ImageFormatRgba16; |
| case glslang::ElfRgb10A2: return spv::ImageFormatRgb10A2; |
| case glslang::ElfRg16: return spv::ImageFormatRg16; |
| case glslang::ElfRg8: return spv::ImageFormatRg8; |
| case glslang::ElfR16: return spv::ImageFormatR16; |
| case glslang::ElfR8: return spv::ImageFormatR8; |
| case glslang::ElfRgba16Snorm: return spv::ImageFormatRgba16Snorm; |
| case glslang::ElfRg16Snorm: return spv::ImageFormatRg16Snorm; |
| case glslang::ElfRg8Snorm: return spv::ImageFormatRg8Snorm; |
| case glslang::ElfR16Snorm: return spv::ImageFormatR16Snorm; |
| case glslang::ElfR8Snorm: return spv::ImageFormatR8Snorm; |
| case glslang::ElfRgba32i: return spv::ImageFormatRgba32i; |
| case glslang::ElfRgba16i: return spv::ImageFormatRgba16i; |
| case glslang::ElfRgba8i: return spv::ImageFormatRgba8i; |
| case glslang::ElfR32i: return spv::ImageFormatR32i; |
| case glslang::ElfRg32i: return spv::ImageFormatRg32i; |
| case glslang::ElfRg16i: return spv::ImageFormatRg16i; |
| case glslang::ElfRg8i: return spv::ImageFormatRg8i; |
| case glslang::ElfR16i: return spv::ImageFormatR16i; |
| case glslang::ElfR8i: return spv::ImageFormatR8i; |
| case glslang::ElfRgba32ui: return spv::ImageFormatRgba32ui; |
| case glslang::ElfRgba16ui: return spv::ImageFormatRgba16ui; |
| case glslang::ElfRgba8ui: return spv::ImageFormatRgba8ui; |
| case glslang::ElfR32ui: return spv::ImageFormatR32ui; |
| case glslang::ElfRg32ui: return spv::ImageFormatRg32ui; |
| case glslang::ElfRg16ui: return spv::ImageFormatRg16ui; |
| case glslang::ElfRgb10a2ui: return spv::ImageFormatRgb10a2ui; |
| case glslang::ElfRg8ui: return spv::ImageFormatRg8ui; |
| case glslang::ElfR16ui: return spv::ImageFormatR16ui; |
| case glslang::ElfR8ui: return spv::ImageFormatR8ui; |
| default: return spv::ImageFormatMax; |
| } |
| } |
| |
| spv::SelectionControlMask TGlslangToSpvTraverser::TranslateSelectionControl(glslang::TSelectionControl selectionControl) const |
| { |
| switch (selectionControl) { |
| case glslang::ESelectionControlNone: return spv::SelectionControlMaskNone; |
| case glslang::ESelectionControlFlatten: return spv::SelectionControlFlattenMask; |
| case glslang::ESelectionControlDontFlatten: return spv::SelectionControlDontFlattenMask; |
| default: return spv::SelectionControlMaskNone; |
| } |
| } |
| |
| spv::LoopControlMask TGlslangToSpvTraverser::TranslateLoopControl(glslang::TLoopControl loopControl) const |
| { |
| switch (loopControl) { |
| case glslang::ELoopControlNone: return spv::LoopControlMaskNone; |
| case glslang::ELoopControlUnroll: return spv::LoopControlUnrollMask; |
| case glslang::ELoopControlDontUnroll: return spv::LoopControlDontUnrollMask; |
| // TODO: DependencyInfinite |
| // TODO: DependencyLength |
| default: return spv::LoopControlMaskNone; |
| } |
| } |
| |
| // Translate glslang type to SPIR-V storage class. |
| spv::StorageClass TGlslangToSpvTraverser::TranslateStorageClass(const glslang::TType& type) |
| { |
| if (type.getQualifier().isPipeInput()) |
| return spv::StorageClassInput; |
| if (type.getQualifier().isPipeOutput()) |
| return spv::StorageClassOutput; |
| |
| if (glslangIntermediate->getSource() != glslang::EShSourceHlsl || |
| type.getQualifier().storage == glslang::EvqUniform) { |
| if (type.getBasicType() == glslang::EbtAtomicUint) |
| return spv::StorageClassAtomicCounter; |
| if (type.containsOpaque()) |
| return spv::StorageClassUniformConstant; |
| } |
| |
| if (glslangIntermediate->usingStorageBuffer() && type.getQualifier().storage == glslang::EvqBuffer) { |
| builder.addExtension(spv::E_SPV_KHR_storage_buffer_storage_class); |
| return spv::StorageClassStorageBuffer; |
| } |
| |
| if (type.getQualifier().isUniformOrBuffer()) { |
| if (type.getQualifier().layoutPushConstant) |
| return spv::StorageClassPushConstant; |
| if (type.getBasicType() == glslang::EbtBlock) |
| return spv::StorageClassUniform; |
| return spv::StorageClassUniformConstant; |
| } |
| |
| switch (type.getQualifier().storage) { |
| case glslang::EvqShared: return spv::StorageClassWorkgroup; |
| case glslang::EvqGlobal: return spv::StorageClassPrivate; |
| case glslang::EvqConstReadOnly: return spv::StorageClassFunction; |
| case glslang::EvqTemporary: return spv::StorageClassFunction; |
| default: |
| assert(0); |
| break; |
| } |
| |
| return spv::StorageClassFunction; |
| } |
| |
| // Return whether or not the given type is something that should be tied to a |
| // descriptor set. |
| bool IsDescriptorResource(const glslang::TType& type) |
| { |
| // uniform and buffer blocks are included, unless it is a push_constant |
| if (type.getBasicType() == glslang::EbtBlock) |
| return type.getQualifier().isUniformOrBuffer() && ! type.getQualifier().layoutPushConstant; |
| |
| // non block... |
| // basically samplerXXX/subpass/sampler/texture are all included |
| // if they are the global-scope-class, not the function parameter |
| // (or local, if they ever exist) class. |
| if (type.getBasicType() == glslang::EbtSampler) |
| return type.getQualifier().isUniformOrBuffer(); |
| |
| // None of the above. |
| return false; |
| } |
| |
| void InheritQualifiers(glslang::TQualifier& child, const glslang::TQualifier& parent) |
| { |
| if (child.layoutMatrix == glslang::ElmNone) |
| child.layoutMatrix = parent.layoutMatrix; |
| |
| if (parent.invariant) |
| child.invariant = true; |
| if (parent.nopersp) |
| child.nopersp = true; |
| #ifdef AMD_EXTENSIONS |
| if (parent.explicitInterp) |
| child.explicitInterp = true; |
| #endif |
| if (parent.flat) |
| child.flat = true; |
| if (parent.centroid) |
| child.centroid = true; |
| if (parent.patch) |
| child.patch = true; |
| if (parent.sample) |
| child.sample = true; |
| if (parent.coherent) |
| child.coherent = true; |
| if (parent.volatil) |
| child.volatil = true; |
| if (parent.restrict) |
| child.restrict = true; |
| if (parent.readonly) |
| child.readonly = true; |
| if (parent.writeonly) |
| child.writeonly = true; |
| } |
| |
| bool HasNonLayoutQualifiers(const glslang::TType& type, const glslang::TQualifier& qualifier) |
| { |
| // This should list qualifiers that simultaneous satisfy: |
| // - struct members might inherit from a struct declaration |
| // (note that non-block structs don't explicitly inherit, |
| // only implicitly, meaning no decoration involved) |
| // - affect decorations on the struct members |
| // (note smooth does not, and expecting something like volatile |
| // to effect the whole object) |
| // - are not part of the offset/st430/etc or row/column-major layout |
| return qualifier.invariant || (qualifier.hasLocation() && type.getBasicType() == glslang::EbtBlock); |
| } |
| |
| // |
| // Implement the TGlslangToSpvTraverser class. |
| // |
| |
| TGlslangToSpvTraverser::TGlslangToSpvTraverser(const glslang::TIntermediate* glslangIntermediate, |
| spv::SpvBuildLogger* buildLogger, glslang::SpvOptions& options) |
| : TIntermTraverser(true, false, true), |
| options(options), |
| shaderEntry(nullptr), currentFunction(nullptr), |
| sequenceDepth(0), logger(buildLogger), |
| builder((glslang::GetKhronosToolId() << 16) | GeneratorVersion, logger), |
| inEntryPoint(false), entryPointTerminated(false), linkageOnly(false), |
| glslangIntermediate(glslangIntermediate) |
| { |
| builder.setTargetVersion(glslangIntermediate->getSpv().spv); |
| spv::ExecutionModel executionModel = TranslateExecutionModel(glslangIntermediate->getStage()); |
| |
| builder.clearAccessChain(); |
| builder.setSource(TranslateSourceLanguage(glslangIntermediate->getSource(), glslangIntermediate->getProfile()), |
| glslangIntermediate->getVersion()); |
| |
| if (options.generateDebugInfo) { |
| builder.setEmitOpLines(); |
| builder.setSourceFile(glslangIntermediate->getSourceFile()); |
| |
| // Set the source shader's text. If for SPV version 1.0, include |
| // a preamble in comments stating the OpModuleProcessed instructions. |
| // Otherwise, emit those as actual instructions. |
| std::string text; |
| const std::vector<std::string>& processes = glslangIntermediate->getProcesses(); |
| for (int p = 0; p < (int)processes.size(); ++p) { |
| if (glslangIntermediate->getSpv().spv < 0x00010100) { |
| text.append("// OpModuleProcessed "); |
| text.append(processes[p]); |
| text.append("\n"); |
| } else |
| builder.addModuleProcessed(processes[p]); |
| } |
| if (glslangIntermediate->getSpv().spv < 0x00010100 && (int)processes.size() > 0) |
| text.append("#line 1\n"); |
| text.append(glslangIntermediate->getSourceText()); |
| builder.setSourceText(text); |
| } |
| stdBuiltins = builder.import("GLSL.std.450"); |
| builder.setMemoryModel(spv::AddressingModelLogical, spv::MemoryModelGLSL450); |
| shaderEntry = builder.makeEntryPoint(glslangIntermediate->getEntryPointName().c_str()); |
| entryPoint = builder.addEntryPoint(executionModel, shaderEntry, glslangIntermediate->getEntryPointName().c_str()); |
| |
| // Add the source extensions |
| const auto& sourceExtensions = glslangIntermediate->getRequestedExtensions(); |
| for (auto it = sourceExtensions.begin(); it != sourceExtensions.end(); ++it) |
| builder.addSourceExtension(it->c_str()); |
| |
| // Add the top-level modes for this shader. |
| |
| if (glslangIntermediate->getXfbMode()) { |
| builder.addCapability(spv::CapabilityTransformFeedback); |
| builder.addExecutionMode(shaderEntry, spv::ExecutionModeXfb); |
| } |
| |
| unsigned int mode; |
| switch (glslangIntermediate->getStage()) { |
| case EShLangVertex: |
| builder.addCapability(spv::CapabilityShader); |
| break; |
| |
| case EShLangTessEvaluation: |
| case EShLangTessControl: |
| builder.addCapability(spv::CapabilityTessellation); |
| |
| glslang::TLayoutGeometry primitive; |
| |
| if (glslangIntermediate->getStage() == EShLangTessControl) { |
| builder.addExecutionMode(shaderEntry, spv::ExecutionModeOutputVertices, glslangIntermediate->getVertices()); |
| primitive = glslangIntermediate->getOutputPrimitive(); |
| } else { |
| primitive = glslangIntermediate->getInputPrimitive(); |
| } |
| |
| switch (primitive) { |
| case glslang::ElgTriangles: mode = spv::ExecutionModeTriangles; break; |
| case glslang::ElgQuads: mode = spv::ExecutionModeQuads; break; |
| case glslang::ElgIsolines: mode = spv::ExecutionModeIsolines; break; |
| default: mode = spv::ExecutionModeMax; break; |
| } |
| if (mode != spv::ExecutionModeMax) |
| builder.addExecutionMode(shaderEntry, (spv::ExecutionMode)mode); |
| |
| switch (glslangIntermediate->getVertexSpacing()) { |
| case glslang::EvsEqual: mode = spv::ExecutionModeSpacingEqual; break; |
| case glslang::EvsFractionalEven: mode = spv::ExecutionModeSpacingFractionalEven; break; |
| case glslang::EvsFractionalOdd: mode = spv::ExecutionModeSpacingFractionalOdd; break; |
| default: mode = spv::ExecutionModeMax; break; |
| } |
| if (mode != spv::ExecutionModeMax) |
| builder.addExecutionMode(shaderEntry, (spv::ExecutionMode)mode); |
| |
| switch (glslangIntermediate->getVertexOrder()) { |
| case glslang::EvoCw: mode = spv::ExecutionModeVertexOrderCw; break; |
| case glslang::EvoCcw: mode = spv::ExecutionModeVertexOrderCcw; break; |
| default: mode = spv::ExecutionModeMax; break; |
| } |
| if (mode != spv::ExecutionModeMax) |
| builder.addExecutionMode(shaderEntry, (spv::ExecutionMode)mode); |
| |
| if (glslangIntermediate->getPointMode()) |
| builder.addExecutionMode(shaderEntry, spv::ExecutionModePointMode); |
| break; |
| |
| case EShLangGeometry: |
| builder.addCapability(spv::CapabilityGeometry); |
| switch (glslangIntermediate->getInputPrimitive()) { |
| case glslang::ElgPoints: mode = spv::ExecutionModeInputPoints; break; |
| case glslang::ElgLines: mode = spv::ExecutionModeInputLines; break; |
| case glslang::ElgLinesAdjacency: mode = spv::ExecutionModeInputLinesAdjacency; break; |
| case glslang::ElgTriangles: mode = spv::ExecutionModeTriangles; break; |
| case glslang::ElgTrianglesAdjacency: mode = spv::ExecutionModeInputTrianglesAdjacency; break; |
| default: mode = spv::ExecutionModeMax; break; |
| } |
| if (mode != spv::ExecutionModeMax) |
| builder.addExecutionMode(shaderEntry, (spv::ExecutionMode)mode); |
| |
| builder.addExecutionMode(shaderEntry, spv::ExecutionModeInvocations, glslangIntermediate->getInvocations()); |
| |
| switch (glslangIntermediate->getOutputPrimitive()) { |
| case glslang::ElgPoints: mode = spv::ExecutionModeOutputPoints; break; |
| case glslang::ElgLineStrip: mode = spv::ExecutionModeOutputLineStrip; break; |
| case glslang::ElgTriangleStrip: mode = spv::ExecutionModeOutputTriangleStrip; break; |
| default: mode = spv::ExecutionModeMax; break; |
| } |
| if (mode != spv::ExecutionModeMax) |
| builder.addExecutionMode(shaderEntry, (spv::ExecutionMode)mode); |
| builder.addExecutionMode(shaderEntry, spv::ExecutionModeOutputVertices, glslangIntermediate->getVertices()); |
| break; |
| |
| case EShLangFragment: |
| builder.addCapability(spv::CapabilityShader); |
| if (glslangIntermediate->getPixelCenterInteger()) |
| builder.addExecutionMode(shaderEntry, spv::ExecutionModePixelCenterInteger); |
| |
| if (glslangIntermediate->getOriginUpperLeft()) |
| builder.addExecutionMode(shaderEntry, spv::ExecutionModeOriginUpperLeft); |
| else |
| builder.addExecutionMode(shaderEntry, spv::ExecutionModeOriginLowerLeft); |
| |
| if (glslangIntermediate->getEarlyFragmentTests()) |
| builder.addExecutionMode(shaderEntry, spv::ExecutionModeEarlyFragmentTests); |
| |
| if (glslangIntermediate->getPostDepthCoverage()) { |
| builder.addCapability(spv::CapabilitySampleMaskPostDepthCoverage); |
| builder.addExecutionMode(shaderEntry, spv::ExecutionModePostDepthCoverage); |
| builder.addExtension(spv::E_SPV_KHR_post_depth_coverage); |
| } |
| |
| switch(glslangIntermediate->getDepth()) { |
| case glslang::EldGreater: mode = spv::ExecutionModeDepthGreater; break; |
| case glslang::EldLess: mode = spv::ExecutionModeDepthLess; break; |
| default: mode = spv::ExecutionModeMax; break; |
| } |
| if (mode != spv::ExecutionModeMax) |
| builder.addExecutionMode(shaderEntry, (spv::ExecutionMode)mode); |
| |
| if (glslangIntermediate->getDepth() != glslang::EldUnchanged && glslangIntermediate->isDepthReplacing()) |
| builder.addExecutionMode(shaderEntry, spv::ExecutionModeDepthReplacing); |
| break; |
| |
| case EShLangCompute: |
| builder.addCapability(spv::CapabilityShader); |
| builder.addExecutionMode(shaderEntry, spv::ExecutionModeLocalSize, glslangIntermediate->getLocalSize(0), |
| glslangIntermediate->getLocalSize(1), |
| glslangIntermediate->getLocalSize(2)); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| // Finish creating SPV, after the traversal is complete. |
| void TGlslangToSpvTraverser::finishSpv() |
| { |
| if (! entryPointTerminated) { |
| builder.setBuildPoint(shaderEntry->getLastBlock()); |
| builder.leaveFunction(); |
| } |
| |
| // finish off the entry-point SPV instruction by adding the Input/Output <id> |
| for (auto it = iOSet.cbegin(); it != iOSet.cend(); ++it) |
| entryPoint->addIdOperand(*it); |
| |
| builder.eliminateDeadDecorations(); |
| } |
| |
| // Write the SPV into 'out'. |
| void TGlslangToSpvTraverser::dumpSpv(std::vector<unsigned int>& out) |
| { |
| builder.dump(out); |
| } |
| |
| // |
| // Implement the traversal functions. |
| // |
| // Return true from interior nodes to have the external traversal |
| // continue on to children. Return false if children were |
| // already processed. |
| // |
| |
| // |
| // Symbols can turn into |
| // - uniform/input reads |
| // - output writes |
| // - complex lvalue base setups: foo.bar[3].... , where we see foo and start up an access chain |
| // - something simple that degenerates into the last bullet |
| // |
| void TGlslangToSpvTraverser::visitSymbol(glslang::TIntermSymbol* symbol) |
| { |
| SpecConstantOpModeGuard spec_constant_op_mode_setter(&builder); |
| if (symbol->getType().getQualifier().isSpecConstant()) |
| spec_constant_op_mode_setter.turnOnSpecConstantOpMode(); |
| |
| // getSymbolId() will set up all the IO decorations on the first call. |
| // Formal function parameters were mapped during makeFunctions(). |
| spv::Id id = getSymbolId(symbol); |
| |
| // Include all "static use" and "linkage only" interface variables on the OpEntryPoint instruction |
| if (builder.isPointer(id)) { |
| spv::StorageClass sc = builder.getStorageClass(id); |
| if (sc == spv::StorageClassInput || sc == spv::StorageClassOutput) { |
| if (!symbol->getType().isStruct() || symbol->getType().getStruct()->size() > 0) |
| iOSet.insert(id); |
| } |
| } |
| |
| // Only process non-linkage-only nodes for generating actual static uses |
| if (! linkageOnly || symbol->getQualifier().isSpecConstant()) { |
| // Prepare to generate code for the access |
| |
| // L-value chains will be computed left to right. We're on the symbol now, |
| // which is the left-most part of the access chain, so now is "clear" time, |
| // followed by setting the base. |
| builder.clearAccessChain(); |
| |
| // For now, we consider all user variables as being in memory, so they are pointers, |
| // except for |
| // A) R-Value arguments to a function, which are an intermediate object. |
| // See comments in handleUserFunctionCall(). |
| // B) Specialization constants (normal constants don't even come in as a variable), |
| // These are also pure R-values. |
| glslang::TQualifier qualifier = symbol->getQualifier(); |
| if (qualifier.isSpecConstant() || rValueParameters.find(symbol->getId()) != rValueParameters.end()) |
| builder.setAccessChainRValue(id); |
| else |
| builder.setAccessChainLValue(id); |
| } |
| } |
| |
| bool TGlslangToSpvTraverser::visitBinary(glslang::TVisit /* visit */, glslang::TIntermBinary* node) |
| { |
| builder.setLine(node->getLoc().line); |
| |
| SpecConstantOpModeGuard spec_constant_op_mode_setter(&builder); |
| if (node->getType().getQualifier().isSpecConstant()) |
| spec_constant_op_mode_setter.turnOnSpecConstantOpMode(); |
| |
| // First, handle special cases |
| switch (node->getOp()) { |
| case glslang::EOpAssign: |
| case glslang::EOpAddAssign: |
| case glslang::EOpSubAssign: |
| case glslang::EOpMulAssign: |
| case glslang::EOpVectorTimesMatrixAssign: |
| case glslang::EOpVectorTimesScalarAssign: |
| case glslang::EOpMatrixTimesScalarAssign: |
| case glslang::EOpMatrixTimesMatrixAssign: |
| case glslang::EOpDivAssign: |
| case glslang::EOpModAssign: |
| case glslang::EOpAndAssign: |
| case glslang::EOpInclusiveOrAssign: |
| case glslang::EOpExclusiveOrAssign: |
| case glslang::EOpLeftShiftAssign: |
| case glslang::EOpRightShiftAssign: |
| // A bin-op assign "a += b" means the same thing as "a = a + b" |
| // where a is evaluated before b. For a simple assignment, GLSL |
| // says to evaluate the left before the right. So, always, left |
| // node then right node. |
| { |
| // get the left l-value, save it away |
| builder.clearAccessChain(); |
| node->getLeft()->traverse(this); |
| spv::Builder::AccessChain lValue = builder.getAccessChain(); |
| |
| // evaluate the right |
| builder.clearAccessChain(); |
| node->getRight()->traverse(this); |
| spv::Id rValue = accessChainLoad(node->getRight()->getType()); |
| |
| if (node->getOp() != glslang::EOpAssign) { |
| // the left is also an r-value |
| builder.setAccessChain(lValue); |
| spv::Id leftRValue = accessChainLoad(node->getLeft()->getType()); |
| |
| // do the operation |
| rValue = createBinaryOperation(node->getOp(), TranslatePrecisionDecoration(node->getOperationPrecision()), |
| TranslateNoContractionDecoration(node->getType().getQualifier()), |
| convertGlslangToSpvType(node->getType()), leftRValue, rValue, |
| node->getType().getBasicType()); |
| |
| // these all need their counterparts in createBinaryOperation() |
| assert(rValue != spv::NoResult); |
| } |
| |
| // store the result |
| builder.setAccessChain(lValue); |
| multiTypeStore(node->getType(), rValue); |
| |
| // assignments are expressions having an rValue after they are evaluated... |
| builder.clearAccessChain(); |
| builder.setAccessChainRValue(rValue); |
| } |
| return false; |
| case glslang::EOpIndexDirect: |
| case glslang::EOpIndexDirectStruct: |
| { |
| // Get the left part of the access chain. |
| node->getLeft()->traverse(this); |
| |
| // Add the next element in the chain |
| |
| const int glslangIndex = node->getRight()->getAsConstantUnion()->getConstArray()[0].getIConst(); |
| if (! node->getLeft()->getType().isArray() && |
| node->getLeft()->getType().isVector() && |
| node->getOp() == glslang::EOpIndexDirect) { |
| // This is essentially a hard-coded vector swizzle of size 1, |
| // so short circuit the access-chain stuff with a swizzle. |
| std::vector<unsigned> swizzle; |
| swizzle.push_back(glslangIndex); |
| builder.accessChainPushSwizzle(swizzle, convertGlslangToSpvType(node->getLeft()->getType())); |
| } else { |
| int spvIndex = glslangIndex; |
| if (node->getLeft()->getBasicType() == glslang::EbtBlock && |
| node->getOp() == glslang::EOpIndexDirectStruct) |
| { |
| // This may be, e.g., an anonymous block-member selection, which generally need |
| // index remapping due to hidden members in anonymous blocks. |
| std::vector<int>& remapper = memberRemapper[node->getLeft()->getType().getStruct()]; |
| assert(remapper.size() > 0); |
| spvIndex = remapper[glslangIndex]; |
| } |
| |
| // normal case for indexing array or structure or block |
| builder.accessChainPush(builder.makeIntConstant(spvIndex)); |
| |
| // Add capabilities here for accessing PointSize and clip/cull distance. |
| // We have deferred generation of associated capabilities until now. |
| if (node->getLeft()->getType().isStruct() && ! node->getLeft()->getType().isArray()) |
| declareUseOfStructMember(*(node->getLeft()->getType().getStruct()), glslangIndex); |
| } |
| } |
| return false; |
| case glslang::EOpIndexIndirect: |
| { |
| // Structure or array or vector indirection. |
| // Will use native SPIR-V access-chain for struct and array indirection; |
| // matrices are arrays of vectors, so will also work for a matrix. |
| // Will use the access chain's 'component' for variable index into a vector. |
| |
| // This adapter is building access chains left to right. |
| // Set up the access chain to the left. |
| node->getLeft()->traverse(this); |
| |
| // save it so that computing the right side doesn't trash it |
| spv::Builder::AccessChain partial = builder.getAccessChain(); |
| |
| // compute the next index in the chain |
| builder.clearAccessChain(); |
| node->getRight()->traverse(this); |
| spv::Id index = accessChainLoad(node->getRight()->getType()); |
| |
| // restore the saved access chain |
| builder.setAccessChain(partial); |
| |
| if (! node->getLeft()->getType().isArray() && node->getLeft()->getType().isVector()) |
| builder.accessChainPushComponent(index, convertGlslangToSpvType(node->getLeft()->getType())); |
| else |
| builder.accessChainPush(index); |
| } |
| return false; |
| case glslang::EOpVectorSwizzle: |
| { |
| node->getLeft()->traverse(this); |
| std::vector<unsigned> swizzle; |
| convertSwizzle(*node->getRight()->getAsAggregate(), swizzle); |
| builder.accessChainPushSwizzle(swizzle, convertGlslangToSpvType(node->getLeft()->getType())); |
| } |
| return false; |
| case glslang::EOpMatrixSwizzle: |
| logger->missingFunctionality("matrix swizzle"); |
| return true; |
| case glslang::EOpLogicalOr: |
| case glslang::EOpLogicalAnd: |
| { |
| |
| // These may require short circuiting, but can sometimes be done as straight |
| // binary operations. The right operand must be short circuited if it has |
| // side effects, and should probably be if it is complex. |
| if (isTrivial(node->getRight()->getAsTyped())) |
| break; // handle below as a normal binary operation |
| // otherwise, we need to do dynamic short circuiting on the right operand |
| spv::Id result = createShortCircuit(node->getOp(), *node->getLeft()->getAsTyped(), *node->getRight()->getAsTyped()); |
| builder.clearAccessChain(); |
| builder.setAccessChainRValue(result); |
| } |
| return false; |
| default: |
| break; |
| } |
| |
| // Assume generic binary op... |
| |
| // get right operand |
| builder.clearAccessChain(); |
| node->getLeft()->traverse(this); |
| spv::Id left = accessChainLoad(node->getLeft()->getType()); |
| |
| // get left operand |
| builder.clearAccessChain(); |
| node->getRight()->traverse(this); |
| spv::Id right = accessChainLoad(node->getRight()->getType()); |
| |
| // get result |
| spv::Id result = createBinaryOperation(node->getOp(), TranslatePrecisionDecoration(node->getOperationPrecision()), |
| TranslateNoContractionDecoration(node->getType().getQualifier()), |
| convertGlslangToSpvType(node->getType()), left, right, |
| node->getLeft()->getType().getBasicType()); |
| |
| builder.clearAccessChain(); |
| if (! result) { |
| logger->missingFunctionality("unknown glslang binary operation"); |
| return true; // pick up a child as the place-holder result |
| } else { |
| builder.setAccessChainRValue(result); |
| return false; |
| } |
| } |
| |
| bool TGlslangToSpvTraverser::visitUnary(glslang::TVisit /* visit */, glslang::TIntermUnary* node) |
| { |
| builder.setLine(node->getLoc().line); |
| |
| SpecConstantOpModeGuard spec_constant_op_mode_setter(&builder); |
| if (node->getType().getQualifier().isSpecConstant()) |
| spec_constant_op_mode_setter.turnOnSpecConstantOpMode(); |
| |
| spv::Id result = spv::NoResult; |
| |
| // try texturing first |
| result = createImageTextureFunctionCall(node); |
| if (result != spv::NoResult) { |
| builder.clearAccessChain(); |
| builder.setAccessChainRValue(result); |
| |
| return false; // done with this node |
| } |
| |
| // Non-texturing. |
| |
| if (node->getOp() == glslang::EOpArrayLength) { |
| // Quite special; won't want to evaluate the operand. |
| |
| // Normal .length() would have been constant folded by the front-end. |
| // So, this has to be block.lastMember.length(). |
| // SPV wants "block" and member number as the operands, go get them. |
| assert(node->getOperand()->getType().isRuntimeSizedArray()); |
| glslang::TIntermTyped* block = node->getOperand()->getAsBinaryNode()->getLeft(); |
| block->traverse(this); |
| unsigned int member = node->getOperand()->getAsBinaryNode()->getRight()->getAsConstantUnion()->getConstArray()[0].getUConst(); |
| spv::Id length = builder.createArrayLength(builder.accessChainGetLValue(), member); |
| |
| builder.clearAccessChain(); |
| builder.setAccessChainRValue(length); |
| |
| return false; |
| } |
| |
| // Start by evaluating the operand |
| |
| // Does it need a swizzle inversion? If so, evaluation is inverted; |
| // operate first on the swizzle base, then apply the swizzle. |
| spv::Id invertedType = spv::NoType; |
| auto resultType = [&invertedType, &node, this](){ return invertedType != spv::NoType ? invertedType : convertGlslangToSpvType(node->getType()); }; |
| if (node->getOp() == glslang::EOpInterpolateAtCentroid) |
| invertedType = getInvertedSwizzleType(*node->getOperand()); |
| |
| builder.clearAccessChain(); |
| if (invertedType != spv::NoType) |
| node->getOperand()->getAsBinaryNode()->getLeft()->traverse(this); |
| else |
| node->getOperand()->traverse(this); |
| |
| spv::Id operand = spv::NoResult; |
| |
| if (node->getOp() == glslang::EOpAtomicCounterIncrement || |
| node->getOp() == glslang::EOpAtomicCounterDecrement || |
| node->getOp() == glslang::EOpAtomicCounter || |
| node->getOp() == glslang::EOpInterpolateAtCentroid) |
| operand = builder.accessChainGetLValue(); // Special case l-value operands |
| else |
| operand = accessChainLoad(node->getOperand()->getType()); |
| |
| spv::Decoration precision = TranslatePrecisionDecoration(node->getOperationPrecision()); |
| spv::Decoration noContraction = TranslateNoContractionDecoration(node->getType().getQualifier()); |
| |
| // it could be a conversion |
| if (! result) |
| result = createConversion(node->getOp(), precision, noContraction, resultType(), operand, node->getOperand()->getBasicType()); |
| |
| // if not, then possibly an operation |
| if (! result) |
| result = createUnaryOperation(node->getOp(), precision, noContraction, resultType(), operand, node->getOperand()->getBasicType()); |
| |
| if (result) { |
| if (invertedType) |
| result = createInvertedSwizzle(precision, *node->getOperand(), result); |
| |
| builder.clearAccessChain(); |
| builder.setAccessChainRValue(result); |
| |
| return false; // done with this node |
| } |
| |
| // it must be a special case, check... |
| switch (node->getOp()) { |
| case glslang::EOpPostIncrement: |
| case glslang::EOpPostDecrement: |
| case glslang::EOpPreIncrement: |
| case glslang::EOpPreDecrement: |
| { |
| // we need the integer value "1" or the floating point "1.0" to add/subtract |
| spv::Id one = 0; |
| if (node->getBasicType() == glslang::EbtFloat) |
| one = builder.makeFloatConstant(1.0F); |
| else if (node->getBasicType() == glslang::EbtDouble) |
| one = builder.makeDoubleConstant(1.0); |
| else if (node->getBasicType() == glslang::EbtFloat16) |
| one = builder.makeFloat16Constant(1.0F); |
| else if (node->getBasicType() == glslang::EbtInt8 || node->getBasicType() == glslang::EbtUint8) |
| one = builder.makeInt8Constant(1); |
| else if (node->getBasicType() == glslang::EbtInt16 || node->getBasicType() == glslang::EbtUint16) |
| one = builder.makeInt16Constant(1); |
| else if (node->getBasicType() == glslang::EbtInt64 || node->getBasicType() == glslang::EbtUint64) |
| one = builder.makeInt64Constant(1); |
| else |
| one = builder.makeIntConstant(1); |
| glslang::TOperator op; |
| if (node->getOp() == glslang::EOpPreIncrement || |
| node->getOp() == glslang::EOpPostIncrement) |
| op = glslang::EOpAdd; |
| else |
| op = glslang::EOpSub; |
| |
| spv::Id result = createBinaryOperation(op, precision, |
| TranslateNoContractionDecoration(node->getType().getQualifier()), |
| convertGlslangToSpvType(node->getType()), operand, one, |
| node->getType().getBasicType()); |
| assert(result != spv::NoResult); |
| |
| // The result of operation is always stored, but conditionally the |
| // consumed result. The consumed result is always an r-value. |
| builder.accessChainStore(result); |
| builder.clearAccessChain(); |
| if (node->getOp() == glslang::EOpPreIncrement || |
| node->getOp() == glslang::EOpPreDecrement) |
| builder.setAccessChainRValue(result); |
| else |
| builder.setAccessChainRValue(operand); |
| } |
| |
| return false; |
| |
| case glslang::EOpEmitStreamVertex: |
| builder.createNoResultOp(spv::OpEmitStreamVertex, operand); |
| return false; |
| case glslang::EOpEndStreamPrimitive: |
| builder.createNoResultOp(spv::OpEndStreamPrimitive, operand); |
| return false; |
| |
| default: |
| logger->missingFunctionality("unknown glslang unary"); |
| return true; // pick up operand as placeholder result |
| } |
| } |
| |
| bool TGlslangToSpvTraverser::visitAggregate(glslang::TVisit visit, glslang::TIntermAggregate* node) |
| { |
| SpecConstantOpModeGuard spec_constant_op_mode_setter(&builder); |
| if (node->getType().getQualifier().isSpecConstant()) |
| spec_constant_op_mode_setter.turnOnSpecConstantOpMode(); |
| |
| spv::Id result = spv::NoResult; |
| spv::Id invertedType = spv::NoType; // to use to override the natural type of the node |
| auto resultType = [&invertedType, &node, this](){ return invertedType != spv::NoType ? invertedType : convertGlslangToSpvType(node->getType()); }; |
| |
| // try texturing |
| result = createImageTextureFunctionCall(node); |
| if (result != spv::NoResult) { |
| builder.clearAccessChain(); |
| builder.setAccessChainRValue(result); |
| |
| return false; |
| #ifdef AMD_EXTENSIONS |
| } else if (node->getOp() == glslang::EOpImageStore || node->getOp() == glslang::EOpImageStoreLod) { |
| #else |
| } else if (node->getOp() == glslang::EOpImageStore) { |
| #endif |
| // "imageStore" is a special case, which has no result |
| return false; |
| } |
| |
| glslang::TOperator binOp = glslang::EOpNull; |
| bool reduceComparison = true; |
| bool isMatrix = false; |
| bool noReturnValue = false; |
| bool atomic = false; |
| |
| assert(node->getOp()); |
| |
| spv::Decoration precision = TranslatePrecisionDecoration(node->getOperationPrecision()); |
| |
| switch (node->getOp()) { |
| case glslang::EOpSequence: |
| { |
| if (preVisit) |
| ++sequenceDepth; |
| else |
| --sequenceDepth; |
| |
| if (sequenceDepth == 1) { |
| // If this is the parent node of all the functions, we want to see them |
| // early, so all call points have actual SPIR-V functions to reference. |
| // In all cases, still let the traverser visit the children for us. |
| makeFunctions(node->getAsAggregate()->getSequence()); |
| |
| // Also, we want all globals initializers to go into the beginning of the entry point, before |
| // anything else gets there, so visit out of order, doing them all now. |
| makeGlobalInitializers(node->getAsAggregate()->getSequence()); |
| |
| // Initializers are done, don't want to visit again, but functions and link objects need to be processed, |
| // so do them manually. |
| visitFunctions(node->getAsAggregate()->getSequence()); |
| |
| return false; |
| } |
| |
| return true; |
| } |
| case glslang::EOpLinkerObjects: |
| { |
| if (visit == glslang::EvPreVisit) |
| linkageOnly = true; |
| else |
| linkageOnly = false; |
| |
| return true; |
| } |
| case glslang::EOpComma: |
| { |
| // processing from left to right naturally leaves the right-most |
| // lying around in the access chain |
| glslang::TIntermSequence& glslangOperands = node->getSequence(); |
| for (int i = 0; i < (int)glslangOperands.size(); ++i) |
| glslangOperands[i]->traverse(this); |
| |
| return false; |
| } |
| case glslang::EOpFunction: |
| if (visit == glslang::EvPreVisit) { |
| if (isShaderEntryPoint(node)) { |
| inEntryPoint = true; |
| builder.setBuildPoint(shaderEntry->getLastBlock()); |
| currentFunction = shaderEntry; |
| } else { |
| handleFunctionEntry(node); |
| } |
| } else { |
| if (inEntryPoint) |
| entryPointTerminated = true; |
| builder.leaveFunction(); |
| inEntryPoint = false; |
| } |
| |
| return true; |
| case glslang::EOpParameters: |
| // Parameters will have been consumed by EOpFunction processing, but not |
| // the body, so we still visited the function node's children, making this |
| // child redundant. |
| return false; |
| case glslang::EOpFunctionCall: |
| { |
| builder.setLine(node->getLoc().line); |
| if (node->isUserDefined()) |
| result = handleUserFunctionCall(node); |
| // assert(result); // this can happen for bad shaders because the call graph completeness checking is not yet done |
| if (result) { |
| builder.clearAccessChain(); |
| builder.setAccessChainRValue(result); |
| } else |
| logger->missingFunctionality("missing user function; linker needs to catch that"); |
| |
| return false; |
| } |
| case glslang::EOpConstructMat2x2: |
| case glslang::EOpConstructMat2x3: |
| case glslang::EOpConstructMat2x4: |
| case glslang::EOpConstructMat3x2: |
| case glslang::EOpConstructMat3x3: |
| case glslang::EOpConstructMat3x4: |
| case glslang::EOpConstructMat4x2: |
| case glslang::EOpConstructMat4x3: |
| case glslang::EOpConstructMat4x4: |
| case glslang::EOpConstructDMat2x2: |
| case glslang::EOpConstructDMat2x3: |
| case glslang::EOpConstructDMat2x4: |
| case glslang::EOpConstructDMat3x2: |
| case glslang::EOpConstructDMat3x3: |
| case glslang::EOpConstructDMat3x4: |
| case glslang::EOpConstructDMat4x2: |
| case glslang::EOpConstructDMat4x3: |
| case glslang::EOpConstructDMat4x4: |
| case glslang::EOpConstructIMat2x2: |
| case glslang::EOpConstructIMat2x3: |
| case glslang::EOpConstructIMat2x4: |
| case glslang::EOpConstructIMat3x2: |
| case glslang::EOpConstructIMat3x3: |
| case glslang::EOpConstructIMat3x4: |
| case glslang::EOpConstructIMat4x2: |
| case glslang::EOpConstructIMat4x3: |
| case glslang::EOpConstructIMat4x4: |
| case glslang::EOpConstructUMat2x2: |
| case glslang::EOpConstructUMat2x3: |
| case glslang::EOpConstructUMat2x4: |
| case glslang::EOpConstructUMat3x2: |
| case glslang::EOpConstructUMat3x3: |
| case glslang::EOpConstructUMat3x4: |
| case glslang::EOpConstructUMat4x2: |
| case glslang::EOpConstructUMat4x3: |
| case glslang::EOpConstructUMat4x4: |
| case glslang::EOpConstructBMat2x2: |
| case glslang::EOpConstructBMat2x3: |
| case glslang::EOpConstructBMat2x4: |
| case glslang::EOpConstructBMat3x2: |
| case glslang::EOpConstructBMat3x3: |
| case glslang::EOpConstructBMat3x4: |
| case glslang::EOpConstructBMat4x2: |
| case glslang::EOpConstructBMat4x3: |
| case glslang::EOpConstructBMat4x4: |
| case glslang::EOpConstructF16Mat2x2: |
| case glslang::EOpConstructF16Mat2x3: |
| case glslang::EOpConstructF16Mat2x4: |
| case glslang::EOpConstructF16Mat3x2: |
| case glslang::EOpConstructF16Mat3x3: |
| case glslang::EOpConstructF16Mat3x4: |
| case glslang::EOpConstructF16Mat4x2: |
| case glslang::EOpConstructF16Mat4x3: |
| case glslang::EOpConstructF16Mat4x4: |
| isMatrix = true; |
| // fall through |
| case glslang::EOpConstructFloat: |
| case glslang::EOpConstructVec2: |
| case glslang::EOpConstructVec3: |
| case glslang::EOpConstructVec4: |
| case glslang::EOpConstructDouble: |
| case glslang::EOpConstructDVec2: |
| case glslang::EOpConstructDVec3: |
| case glslang::EOpConstructDVec4: |
| case glslang::EOpConstructFloat16: |
| case glslang::EOpConstructF16Vec2: |
| case glslang::EOpConstructF16Vec3: |
| case glslang::EOpConstructF16Vec4: |
| case glslang::EOpConstructBool: |
| case glslang::EOpConstructBVec2: |
| case glslang::EOpConstructBVec3: |
| case glslang::EOpConstructBVec4: |
| case glslang::EOpConstructInt8: |
| case glslang::EOpConstructI8Vec2: |
| case glslang::EOpConstructI8Vec3: |
| case glslang::EOpConstructI8Vec4: |
| case glslang::EOpConstructUint8: |
| case glslang::EOpConstructU8Vec2: |
| case glslang::EOpConstructU8Vec3: |
| case glslang::EOpConstructU8Vec4: |
| case glslang::EOpConstructInt16: |
| case glslang::EOpConstructI16Vec2: |
| case glslang::EOpConstructI16Vec3: |
| case glslang::EOpConstructI16Vec4: |
| case glslang::EOpConstructUint16: |
| case glslang::EOpConstructU16Vec2: |
| case glslang::EOpConstructU16Vec3: |
| case glslang::EOpConstructU16Vec4: |
| case glslang::EOpConstructInt: |
| case glslang::EOpConstructIVec2: |
| case glslang::EOpConstructIVec3: |
| case glslang::EOpConstructIVec4: |
| case glslang::EOpConstructUint: |
| case glslang::EOpConstructUVec2: |
| case glslang::EOpConstructUVec3: |
| case glslang::EOpConstructUVec4: |
| case glslang::EOpConstructInt64: |
| case glslang::EOpConstructI64Vec2: |
| case glslang::EOpConstructI64Vec3: |
| case glslang::EOpConstructI64Vec4: |
| case glslang::EOpConstructUint64: |
| case glslang::EOpConstructU64Vec2: |
| case glslang::EOpConstructU64Vec3: |
| case glslang::EOpConstructU64Vec4: |
| case glslang::EOpConstructStruct: |
| case glslang::EOpConstructTextureSampler: |
| { |
| builder.setLine(node->getLoc().line); |
| std::vector<spv::Id> arguments; |
| translateArguments(*node, arguments); |
| spv::Id constructed; |
| if (node->getOp() == glslang::EOpConstructTextureSampler) |
| constructed = builder.createOp(spv::OpSampledImage, resultType(), arguments); |
| else if (node->getOp() == glslang::EOpConstructStruct || node->getType().isArray()) { |
| std::vector<spv::Id> constituents; |
| for (int c = 0; c < (int)arguments.size(); ++c) |
| constituents.push_back(arguments[c]); |
| constructed = builder.createCompositeConstruct(resultType(), constituents); |
| } else if (isMatrix) |
| constructed = builder.createMatrixConstructor(precision, arguments, resultType()); |
| else |
| constructed = builder.createConstructor(precision, arguments, resultType()); |
| |
| builder.clearAccessChain(); |
| builder.setAccessChainRValue(constructed); |
| |
| return false; |
| } |
| |
| // These six are component-wise compares with component-wise results. |
| // Forward on to createBinaryOperation(), requesting a vector result. |
| case glslang::EOpLessThan: |
| case glslang::EOpGreaterThan: |
| case glslang::EOpLessThanEqual: |
| case glslang::EOpGreaterThanEqual: |
| case glslang::EOpVectorEqual: |
| case glslang::EOpVectorNotEqual: |
| { |
| // Map the operation to a binary |
| binOp = node->getOp(); |
| reduceComparison = false; |
| switch (node->getOp()) { |
| case glslang::EOpVectorEqual: binOp = glslang::EOpVectorEqual; break; |
| case glslang::EOpVectorNotEqual: binOp = glslang::EOpVectorNotEqual; break; |
| default: binOp = node->getOp(); break; |
| } |
| |
| break; |
| } |
| case glslang::EOpMul: |
| // component-wise matrix multiply |
| binOp = glslang::EOpMul; |
| break; |
| case glslang::EOpOuterProduct: |
| // two vectors multiplied to make a matrix |
| binOp = glslang::EOpOuterProduct; |
| break; |
| case glslang::EOpDot: |
| { |
| // for scalar dot product, use multiply |
| glslang::TIntermSequence& glslangOperands = node->getSequence(); |
| if (glslangOperands[0]->getAsTyped()->getVectorSize() == 1) |
| binOp = glslang::EOpMul; |
| break; |
| } |
| case glslang::EOpMod: |
| // when an aggregate, this is the floating-point mod built-in function, |
| // which can be emitted by the one in createBinaryOperation() |
| binOp = glslang::EOpMod; |
| break; |
| case glslang::EOpEmitVertex: |
| case glslang::EOpEndPrimitive: |
| case glslang::EOpBarrier: |
| case glslang::EOpMemoryBarrier: |
| case glslang::EOpMemoryBarrierAtomicCounter: |
| case glslang::EOpMemoryBarrierBuffer: |
| case glslang::EOpMemoryBarrierImage: |
| case glslang::EOpMemoryBarrierShared: |
| case glslang::EOpGroupMemoryBarrier: |
| case glslang::EOpAllMemoryBarrierWithGroupSync: |
| case glslang::EOpGroupMemoryBarrierWithGroupSync: |
| case glslang::EOpWorkgroupMemoryBarrier: |
| case glslang::EOpWorkgroupMemoryBarrierWithGroupSync: |
| case glslang::EOpSubgroupBarrier: |
| case glslang::EOpSubgroupMemoryBarrier: |
| case glslang::EOpSubgroupMemoryBarrierBuffer: |
| case glslang::EOpSubgroupMemoryBarrierImage: |
| case glslang::EOpSubgroupMemoryBarrierShared: |
| noReturnValue = true; |
| // These all have 0 operands and will naturally finish up in the code below for 0 operands |
| break; |
| |
| case glslang::EOpAtomicAdd: |
| case glslang::EOpAtomicMin: |
| case glslang::EOpAtomicMax: |
| case glslang::EOpAtomicAnd: |
| case glslang::EOpAtomicOr: |
| case glslang::EOpAtomicXor: |
| case glslang::EOpAtomicExchange: |
| case glslang::EOpAtomicCompSwap: |
| atomic = true; |
| break; |
| |
| case glslang::EOpAtomicCounterAdd: |
| case glslang::EOpAtomicCounterSubtract: |
| case glslang::EOpAtomicCounterMin: |
| case glslang::EOpAtomicCounterMax: |
| case glslang::EOpAtomicCounterAnd: |
| case glslang::EOpAtomicCounterOr: |
| case glslang::EOpAtomicCounterXor: |
| case glslang::EOpAtomicCounterExchange: |
| case glslang::EOpAtomicCounterCompSwap: |
| builder.addExtension("SPV_KHR_shader_atomic_counter_ops"); |
| builder.addCapability(spv::CapabilityAtomicStorageOps); |
| atomic = true; |
| break; |
| |
| default: |
| break; |
| } |
| |
| // |
| // See if it maps to a regular operation. |
| // |
| if (binOp != glslang::EOpNull) { |
| glslang::TIntermTyped* left = node->getSequence()[0]->getAsTyped(); |
| glslang::TIntermTyped* right = node->getSequence()[1]->getAsTyped(); |
| assert(left && right); |
| |
| builder.clearAccessChain(); |
| left->traverse(this); |
| spv::Id leftId = accessChainLoad(left->getType()); |
| |
| builder.clearAccessChain(); |
| right->traverse(this); |
| spv::Id rightId = accessChainLoad(right->getType()); |
| |
| builder.setLine(node->getLoc().line); |
| result = createBinaryOperation(binOp, precision, TranslateNoContractionDecoration(node->getType().getQualifier()), |
| resultType(), leftId, rightId, |
| left->getType().getBasicType(), reduceComparison); |
| |
| // code above should only make binOp that exists in createBinaryOperation |
| assert(result != spv::NoResult); |
| builder.clearAccessChain(); |
| builder.setAccessChainRValue(result); |
| |
| return false; |
| } |
| |
| // |
| // Create the list of operands. |
| // |
| glslang::TIntermSequence& glslangOperands = node->getSequence(); |
| std::vector<spv::Id> operands; |
| for (int arg = 0; arg < (int)glslangOperands.size(); ++arg) { |
| // special case l-value operands; there are just a few |
| bool lvalue = false; |
| switch (node->getOp()) { |
| case glslang::EOpFrexp: |
| case glslang::EOpModf: |
| if (arg == 1) |
| lvalue = true; |
| break; |
| case glslang::EOpInterpolateAtSample: |
| case glslang::EOpInterpolateAtOffset: |
| #ifdef AMD_EXTENSIONS |
| case glslang::EOpInterpolateAtVertex: |
| #endif |
| if (arg == 0) { |
| lvalue = true; |
| |
| // Does it need a swizzle inversion? If so, evaluation is inverted; |
| // operate first on the swizzle base, then apply the swizzle. |
| if (glslangOperands[0]->getAsOperator() && |
| glslangOperands[0]->getAsOperator()->getOp() == glslang::EOpVectorSwizzle) |
| invertedType = convertGlslangToSpvType(glslangOperands[0]->getAsBinaryNode()->getLeft()->getType()); |
| } |
| break; |
| case glslang::EOpAtomicAdd: |
| case glslang::EOpAtomicMin: |
| case glslang::EOpAtomicMax: |
| case glslang::EOpAtomicAnd: |
| case glslang::EOpAtomicOr: |
| case glslang::EOpAtomicXor: |
| case glslang::EOpAtomicExchange: |
| case glslang::EOpAtomicCompSwap: |
| case glslang::EOpAtomicCounterAdd: |
| case glslang::EOpAtomicCounterSubtract: |
| case glslang::EOpAtomicCounterMin: |
| case glslang::EOpAtomicCounterMax: |
| case glslang::EOpAtomicCounterAnd: |
| case glslang::EOpAtomicCounterOr: |
| case glslang::EOpAtomicCounterXor: |
| case glslang::EOpAtomicCounterExchange: |
| case glslang::EOpAtomicCounterCompSwap: |
| if (arg == 0) |
| lvalue = true; |
| break; |
| case glslang::EOpAddCarry: |
| case glslang::EOpSubBorrow: |
| if (arg == 2) |
| lvalue = true; |
| break; |
| case glslang::EOpUMulExtended: |
| case glslang::EOpIMulExtended: |
| if (arg >= 2) |
| lvalue = true; |
| break; |
| default: |
| break; |
| } |
| builder.clearAccessChain(); |
| if (invertedType != spv::NoType && arg == 0) |
| glslangOperands[0]->getAsBinaryNode()->getLeft()->traverse(this); |
| else |
| glslangOperands[arg]->traverse(this); |
| if (lvalue) |
| operands.push_back(builder.accessChainGetLValue()); |
| else { |
| builder.setLine(node->getLoc().line); |
| operands.push_back(accessChainLoad(glslangOperands[arg]->getAsTyped()->getType())); |
| } |
| } |
| |
| builder.setLine(node->getLoc().line); |
| if (atomic) { |
| // Handle all atomics |
| result = createAtomicOperation(node->getOp(), precision, resultType(), operands, node->getBasicType()); |
| } else { |
| // Pass through to generic operations. |
| switch (glslangOperands.size()) { |
| case 0: |
| result = createNoArgOperation(node->getOp(), precision, resultType()); |
| break; |
| case 1: |
| result = createUnaryOperation( |
| node->getOp(), precision, |
| TranslateNoContractionDecoration(node->getType().getQualifier()), |
| resultType(), operands.front(), |
| glslangOperands[0]->getAsTyped()->getBasicType()); |
| break; |
| default: |
| result = createMiscOperation(node->getOp(), precision, resultType(), operands, node->getBasicType()); |
| break; |
| } |
| if (invertedType) |
| result = createInvertedSwizzle(precision, *glslangOperands[0]->getAsBinaryNode(), result); |
| } |
| |
| if (noReturnValue) |
| return false; |
| |
| if (! result) { |
| logger->missingFunctionality("unknown glslang aggregate"); |
| return true; // pick up a child as a placeholder operand |
| } else { |
| builder.clearAccessChain(); |
| builder.setAccessChainRValue(result); |
| return false; |
| } |
| } |
| |
| // This path handles both if-then-else and ?: |
| // The if-then-else has a node type of void, while |
| // ?: has either a void or a non-void node type |
| // |
| // Leaving the result, when not void: |
| // GLSL only has r-values as the result of a :?, but |
| // if we have an l-value, that can be more efficient if it will |
| // become the base of a complex r-value expression, because the |
| // next layer copies r-values into memory to use the access-chain mechanism |
| bool TGlslangToSpvTraverser::visitSelection(glslang::TVisit /* visit */, glslang::TIntermSelection* node) |
| { |
| // See if it simple and safe to generate OpSelect instead of using control flow. |
| // Crucially, side effects must be avoided, and there are performance trade-offs. |
| // Return true if good idea (and safe) for OpSelect, false otherwise. |
| const auto selectPolicy = [&]() -> bool { |
| if ((!node->getType().isScalar() && !node->getType().isVector()) || |
| node->getBasicType() == glslang::EbtVoid) |
| return false; |
| |
| if (node->getTrueBlock() == nullptr || |
| node->getFalseBlock() == nullptr) |
| return false; |
| |
| assert(node->getType() == node->getTrueBlock() ->getAsTyped()->getType() && |
| node->getType() == node->getFalseBlock()->getAsTyped()->getType()); |
| |
| // return true if a single operand to ? : is okay for OpSelect |
| const auto operandOkay = [](glslang::TIntermTyped* node) { |
| return node->getAsSymbolNode() || node->getType().getQualifier().isConstant(); |
| }; |
| |
| return operandOkay(node->getTrueBlock() ->getAsTyped()) && |
| operandOkay(node->getFalseBlock()->getAsTyped()); |
| }; |
| |
| // Emit OpSelect for this selection. |
| const auto handleAsOpSelect = [&]() { |
| node->getCondition()->traverse(this); |
| spv::Id condition = accessChainLoad(node->getCondition()->getType()); |
| node->getTrueBlock()->traverse(this); |
| spv::Id trueValue = accessChainLoad(node->getTrueBlock()->getAsTyped()->getType()); |
| node->getFalseBlock()->traverse(this); |
| spv::Id falseValue = accessChainLoad(node->getTrueBlock()->getAsTyped()->getType()); |
| |
| builder.setLine(node->getLoc().line); |
| |
| // smear condition to vector, if necessary (AST is always scalar) |
| if (builder.isVector(trueValue)) |
| condition = builder.smearScalar(spv::NoPrecision, condition, |
| builder.makeVectorType(builder.makeBoolType(), |
| builder.getNumComponents(trueValue))); |
| |
| spv::Id select = builder.createTriOp(spv::OpSelect, |
| convertGlslangToSpvType(node->getType()), condition, |
| trueValue, falseValue); |
| builder.clearAccessChain(); |
| builder.setAccessChainRValue(select); |
| }; |
| |
| // Try for OpSelect |
| |
| if (selectPolicy()) { |
| SpecConstantOpModeGuard spec_constant_op_mode_setter(&builder); |
| if (node->getType().getQualifier().isSpecConstant()) |
| spec_constant_op_mode_setter.turnOnSpecConstantOpMode(); |
| |
| handleAsOpSelect(); |
| return false; |
| } |
| |
| // Instead, emit control flow... |
| // Don't handle results as temporaries, because there will be two names |
| // and better to leave SSA to later passes. |
| spv::Id result = (node->getBasicType() == glslang::EbtVoid) |
| ? spv::NoResult |
| : builder.createVariable(spv::StorageClassFunction, convertGlslangToSpvType(node->getType())); |
| |
| // emit the condition before doing anything with selection |
| node->getCondition()->traverse(this); |
| |
| // Selection control: |
| const spv::SelectionControlMask control = TranslateSelectionControl(node->getSelectionControl()); |
| |
| // make an "if" based on the value created by the condition |
| spv::Builder::If ifBuilder(accessChainLoad(node->getCondition()->getType()), control, builder); |
| |
| // emit the "then" statement |
| if (node->getTrueBlock() != nullptr) { |
| node->getTrueBlock()->traverse(this); |
| if (result != spv::NoResult) |
| builder.createStore(accessChainLoad(node->getTrueBlock()->getAsTyped()->getType()), result); |
| } |
| |
| if (node->getFalseBlock() != nullptr) { |
| ifBuilder.makeBeginElse(); |
| // emit the "else" statement |
| node->getFalseBlock()->traverse(this); |
| if (result != spv::NoResult) |
| builder.createStore(accessChainLoad(node->getFalseBlock()->getAsTyped()->getType()), result); |
| } |
| |
| // finish off the control flow |
| ifBuilder.makeEndIf(); |
| |
| if (result != spv::NoResult) { |
| // GLSL only has r-values as the result of a :?, but |
| // if we have an l-value, that can be more efficient if it will |
| // become the base of a complex r-value expression, because the |
| // next layer copies r-values into memory to use the access-chain mechanism |
| builder.clearAccessChain(); |
| builder.setAccessChainLValue(result); |
| } |
| |
| return false; |
| } |
| |
| bool TGlslangToSpvTraverser::visitSwitch(glslang::TVisit /* visit */, glslang::TIntermSwitch* node) |
| { |
| // emit and get the condition before doing anything with switch |
| node->getCondition()->traverse(this); |
| spv::Id selector = accessChainLoad(node->getCondition()->getAsTyped()->getType()); |
| |
| // Selection control: |
| const spv::SelectionControlMask control = TranslateSelectionControl(node->getSelectionControl()); |
| |
| // browse the children to sort out code segments |
| int defaultSegment = -1; |
| std::vector<TIntermNode*> codeSegments; |
| glslang::TIntermSequence& sequence = node->getBody()->getSequence(); |
| std::vector<int> caseValues; |
| std::vector<int> valueIndexToSegment(sequence.size()); // note: probably not all are used, it is an overestimate |
| for (glslang::TIntermSequence::iterator c = sequence.begin(); c != sequence.end(); ++c) { |
| TIntermNode* child = *c; |
| if (child->getAsBranchNode() && child->getAsBranchNode()->getFlowOp() == glslang::EOpDefault) |
| defaultSegment = (int)codeSegments.size(); |
| else if (child->getAsBranchNode() && child->getAsBranchNode()->getFlowOp() == glslang::EOpCase) { |
| valueIndexToSegment[caseValues.size()] = (int)codeSegments.size(); |
| caseValues.push_back(child->getAsBranchNode()->getExpression()->getAsConstantUnion()->getConstArray()[0].getIConst()); |
| } else |
| codeSegments.push_back(child); |
| } |
| |
| // handle the case where the last code segment is missing, due to no code |
| // statements between the last case and the end of the switch statement |
| if ((caseValues.size() && (int)codeSegments.size() == valueIndexToSegment[caseValues.size() - 1]) || |
| (int)codeSegments.size() == defaultSegment) |
| codeSegments.push_back(nullptr); |
| |
| // make the switch statement |
| std::vector<spv::Block*> segmentBlocks; // returned, as the blocks allocated in the call |
| builder.makeSwitch(selector, control, (int)codeSegments.size(), caseValues, valueIndexToSegment, defaultSegment, segmentBlocks); |
| |
| // emit all the code in the segments |
| breakForLoop.push(false); |
| for (unsigned int s = 0; s < codeSegments.size(); ++s) { |
| builder.nextSwitchSegment(segmentBlocks, s); |
| if (codeSegments[s]) |
| codeSegments[s]->traverse(this); |
| else |
| builder.addSwitchBreak(); |
| } |
| breakForLoop.pop(); |
| |
| builder.endSwitch(segmentBlocks); |
| |
| return false; |
| } |
| |
| void TGlslangToSpvTraverser::visitConstantUnion(glslang::TIntermConstantUnion* node) |
| { |
| int nextConst = 0; |
| spv::Id constant = createSpvConstantFromConstUnionArray(node->getType(), node->getConstArray(), nextConst, false); |
| |
| builder.clearAccessChain(); |
| builder.setAccessChainRValue(constant); |
| } |
| |
| bool TGlslangToSpvTraverser::visitLoop(glslang::TVisit /* visit */, glslang::TIntermLoop* node) |
| { |
| auto blocks = builder.makeNewLoop(); |
| builder.createBranch(&blocks.head); |
| |
| // Loop control: |
| const spv::LoopControlMask control = TranslateLoopControl(node->getLoopControl()); |
| |
| // TODO: dependency length |
| |
| // Spec requires back edges to target header blocks, and every header block |
| // must dominate its merge block. Make a header block first to ensure these |
| // conditions are met. By definition, it will contain OpLoopMerge, followed |
| // by a block-ending branch. But we don't want to put any other body/test |
| // instructions in it, since the body/test may have arbitrary instructions, |
| // including merges of its own. |
| builder.setLine(node->getLoc().line); |
| builder.setBuildPoint(&blocks.head); |
| builder.createLoopMerge(&blocks.merge, &blocks.continue_target, control); |
| if (node->testFirst() && node->getTest()) { |
| spv::Block& test = builder.makeNewBlock(); |
| builder.createBranch(&test); |
| |
| builder.setBuildPoint(&test); |
| node->getTest()->traverse(this); |
| spv::Id condition = accessChainLoad(node->getTest()->getType()); |
| builder.createConditionalBranch(condition, &blocks.body, &blocks.merge); |
| |
| builder.setBuildPoint(&blocks.body); |
| breakForLoop.push(true); |
| if (node->getBody()) |
| node->getBody()->traverse(this); |
| builder.createBranch(&blocks.continue_target); |
| breakForLoop.pop(); |
| |
| builder.setBuildPoint(&blocks.continue_target); |
| if (node->getTerminal()) |
| node->getTerminal()->traverse(this); |
| builder.createBranch(&blocks.head); |
| } else { |
| builder.setLine(node->getLoc().line); |
| builder.createBranch(&blocks.body); |
| |
| breakForLoop.push(true); |
| builder.setBuildPoint(&blocks.body); |
| if (node->getBody()) |
| node->getBody()->traverse(this); |
| builder.createBranch(&blocks.continue_target); |
| breakForLoop.pop(); |
| |
| builder.setBuildPoint(&blocks.continue_target); |
| if (node->getTerminal()) |
| node->getTerminal()->traverse(this); |
| if (node->getTest()) { |
| node->getTest()->traverse(this); |
| spv::Id condition = |
| accessChainLoad(node->getTest()->getType()); |
| builder.createConditionalBranch(condition, &blocks.head, &blocks.merge); |
| } else { |
| // TODO: unless there was a break/return/discard instruction |
| // somewhere in the body, this is an infinite loop, so we should |
| // issue a warning. |
| builder.createBranch(&blocks.head); |
| } |
| } |
| builder.setBuildPoint(&blocks.merge); |
| builder.closeLoop(); |
| return false; |
| } |
| |
| bool TGlslangToSpvTraverser::visitBranch(glslang::TVisit /* visit */, glslang::TIntermBranch* node) |
| { |
| if (node->getExpression()) |
| node->getExpression()->traverse(this); |
| |
| builder.setLine(node->getLoc().line); |
| |
| switch (node->getFlowOp()) { |
| case glslang::EOpKill: |
| builder.makeDiscard(); |
| break; |
| case glslang::EOpBreak: |
| if (breakForLoop.top()) |
| builder.createLoopExit(); |
| else |
| builder.addSwitchBreak(); |
| break; |
| case glslang::EOpContinue: |
| builder.createLoopContinue(); |
| break; |
| case glslang::EOpReturn: |
| if (node->getExpression()) { |
| const glslang::TType& glslangReturnType = node->getExpression()->getType(); |
| spv::Id returnId = accessChainLoad(glslangReturnType); |
| if (builder.getTypeId(returnId) != currentFunction->getReturnType()) { |
| builder.clearAccessChain(); |
| spv::Id copyId = builder.createVariable(spv::StorageClassFunction, currentFunction->getReturnType()); |
| builder.setAccessChainLValue(copyId); |
| multiTypeStore(glslangReturnType, returnId); |
| returnId = builder.createLoad(copyId); |
| } |
| builder.makeReturn(false, returnId); |
| } else |
| builder.makeReturn(false); |
| |
| builder.clearAccessChain(); |
| break; |
| |
| default: |
| assert(0); |
| break; |
| } |
| |
| return false; |
| } |
| |
| spv::Id TGlslangToSpvTraverser::createSpvVariable(const glslang::TIntermSymbol* node) |
| { |
| // First, steer off constants, which are not SPIR-V variables, but |
| // can still have a mapping to a SPIR-V Id. |
| // This includes specialization constants. |
| if (node->getQualifier().isConstant()) { |
| return createSpvConstant(*node); |
| } |
| |
| // Now, handle actual variables |
| spv::StorageClass storageClass = TranslateStorageClass(node->getType()); |
| spv::Id spvType = convertGlslangToSpvType(node->getType()); |
| |
| const bool contains16BitType = node->getType().containsBasicType(glslang::EbtFloat16) || |
| node->getType().containsBasicType(glslang::EbtInt16) || |
| node->getType().containsBasicType(glslang::EbtUint16); |
| if (contains16BitType) { |
| if (storageClass == spv::StorageClassInput || storageClass == spv::StorageClassOutput) { |
| builder.addExtension(spv::E_SPV_KHR_16bit_storage); |
| builder.addCapability(spv::CapabilityStorageInputOutput16); |
| } else if (storageClass == spv::StorageClassPushConstant) { |
| builder.addExtension(spv::E_SPV_KHR_16bit_storage); |
| builder.addCapability(spv::CapabilityStoragePushConstant16); |
| } else if (storageClass == spv::StorageClassUniform) { |
| builder.addExtension(spv::E_SPV_KHR_16bit_storage); |
| builder.addCapability(spv::CapabilityStorageUniform16); |
| if (node->getType().getQualifier().storage == glslang::EvqBuffer) |
| builder.addCapability(spv::CapabilityStorageUniformBufferBlock16); |
| } |
| } |
| |
| const char* name = node->getName().c_str(); |
| if (glslang::IsAnonymous(name)) |
| name = ""; |
| |
| return builder.createVariable(storageClass, spvType, name); |
| } |
| |
| // Return type Id of the sampled type. |
| spv::Id TGlslangToSpvTraverser::getSampledType(const glslang::TSampler& sampler) |
| { |
| switch (sampler.type) { |
| case glslang::EbtFloat: return builder.makeFloatType(32); |
| case glslang::EbtInt: return builder.makeIntType(32); |
| case glslang::EbtUint: return builder.makeUintType(32); |
| default: |
| assert(0); |
| return builder.makeFloatType(32); |
| } |
| } |
| |
| // If node is a swizzle operation, return the type that should be used if |
| // the swizzle base is first consumed by another operation, before the swizzle |
| // is applied. |
| spv::Id TGlslangToSpvTraverser::getInvertedSwizzleType(const glslang::TIntermTyped& node) |
| { |
| if (node.getAsOperator() && |
| node.getAsOperator()->getOp() == glslang::EOpVectorSwizzle) |
| return convertGlslangToSpvType(node.getAsBinaryNode()->getLeft()->getType()); |
| else |
| return spv::NoType; |
| } |
| |
| // When inverting a swizzle with a parent op, this function |
| // will apply the swizzle operation to a completed parent operation. |
| spv::Id TGlslangToSpvTraverser::createInvertedSwizzle(spv::Decoration precision, const glslang::TIntermTyped& node, spv::Id parentResult) |
| { |
| std::vector<unsigned> swizzle; |
| convertSwizzle(*node.getAsBinaryNode()->getRight()->getAsAggregate(), swizzle); |
| return builder.createRvalueSwizzle(precision, convertGlslangToSpvType(node.getType()), parentResult, swizzle); |
| } |
| |
| // Convert a glslang AST swizzle node to a swizzle vector for building SPIR-V. |
| void TGlslangToSpvTraverser::convertSwizzle(const glslang::TIntermAggregate& node, std::vector<unsigned>& swizzle) |
| { |
| const glslang::TIntermSequence& swizzleSequence = node.getSequence(); |
| for (int i = 0; i < (int)swizzleSequence.size(); ++i) |
| swizzle.push_back(swizzleSequence[i]->getAsConstantUnion()->getConstArray()[0].getIConst()); |
| } |
| |
| // Convert from a glslang type to an SPV type, by calling into a |
| // recursive version of this function. This establishes the inherited |
| // layout state rooted from the top-level type. |
| spv::Id TGlslangToSpvTraverser::convertGlslangToSpvType(const glslang::TType& type) |
| { |
| return convertGlslangToSpvType(type, getExplicitLayout(type), type.getQualifier()); |
| } |
| |
| // Do full recursive conversion of an arbitrary glslang type to a SPIR-V Id. |
| // explicitLayout can be kept the same throughout the hierarchical recursive walk. |
| // Mutually recursive with convertGlslangStructToSpvType(). |
| spv::Id TGlslangToSpvTraverser::convertGlslangToSpvType(const glslang::TType& type, glslang::TLayoutPacking explicitLayout, const glslang::TQualifier& qualifier) |
| { |
| spv::Id spvType = spv::NoResult; |
| |
| switch (type.getBasicType()) { |
| case glslang::EbtVoid: |
| spvType = builder.makeVoidType(); |
| assert (! type.isArray()); |
| break; |
| case glslang::EbtFloat: |
| spvType = builder.makeFloatType(32); |
| break; |
| case glslang::EbtDouble: |
| spvType = builder.makeFloatType(64); |
| break; |
| case glslang::EbtFloat16: |
| builder.addCapability(spv::CapabilityFloat16); |
| #if AMD_EXTENSIONS |
| builder.addExtension(spv::E_SPV_AMD_gpu_shader_half_float); |
| #endif |
| spvType = builder.makeFloatType(16); |
| break; |
| case glslang::EbtBool: |
| // "transparent" bool doesn't exist in SPIR-V. The GLSL convention is |
| // a 32-bit int where non-0 means true. |
| if (explicitLayout != glslang::ElpNone) |
| spvType = builder.makeUintType(32); |
| else |
| spvType = builder.makeBoolType(); |
| break; |
| case glslang::EbtInt8: |
| builder.addCapability(spv::CapabilityInt8); |
| spvType = builder.makeIntType(8); |
| break; |
| case glslang::EbtUint8: |
| builder.addCapability(spv::CapabilityInt8); |
| spvType = builder.makeUintType(8); |
| case glslang::EbtInt16: |
| builder.addCapability(spv::CapabilityInt16); |
| #ifdef AMD_EXTENSIONS |
| builder.addExtension(spv::E_SPV_AMD_gpu_shader_int16); |
| #endif |
| spvType = builder.makeIntType(16); |
| break; |
| case glslang::EbtUint16: |
| builder.addCapability(spv::CapabilityInt16); |
| #ifdef AMD_EXTENSIONS |
| builder.addExtension(spv::E_SPV_AMD_gpu_shader_int16); |
| #endif |
| spvType = builder.makeUintType(16); |
| case glslang::EbtInt: |
| spvType = builder.makeIntType(32); |
| break; |
| case glslang::EbtUint: |
| spvType = builder.makeUintType(32); |
| break; |
| case glslang::EbtInt64: |
| spvType = builder.makeIntType(64); |
| break; |
| case glslang::EbtUint64: |
| spvType = builder.makeUintType(64); |
| break; |
| case glslang::EbtAtomicUint: |
| builder.addCapability(spv::CapabilityAtomicStorage); |
| spvType = builder.makeUintType(32); |
| break; |
| case glslang::EbtSampler: |
| { |
| const glslang::TSampler& sampler = type.getSampler(); |
| if (sampler.sampler) { |
| // pure sampler |
| spvType = builder.makeSamplerType(); |
| } else { |
| // an image is present, make its type |
| spvType = builder.makeImageType(getSampledType(sampler), TranslateDimensionality(sampler), sampler.shadow, sampler.arrayed, sampler.ms, |
| sampler.image ? 2 : 1, TranslateImageFormat(type)); |
| if (sampler.combined) { |
| // already has both image and sampler, make the combined type |
| spvType = builder.makeSampledImageType(spvType); |
| } |
| } |
| } |
| break; |
| case glslang::EbtStruct: |
| case glslang::EbtBlock: |
| { |
| // If we've seen this struct type, return it |
| const glslang::TTypeList* glslangMembers = type.getStruct(); |
| |
| // Try to share structs for different layouts, but not yet for other |
| // kinds of qualification (primarily not yet including interpolant qualification). |
| if (! HasNonLayoutQualifiers(type, qualifier)) |
| spvType = structMap[explicitLayout][qualifier.layoutMatrix][glslangMembers]; |
| if (spvType != spv::NoResult) |
| break; |
| |
| // else, we haven't seen it... |
| if (type.getBasicType() == glslang::EbtBlock) |
| memberRemapper[glslangMembers].resize(glslangMembers->size()); |
| spvType = convertGlslangStructToSpvType(type, glslangMembers, explicitLayout, qualifier); |
| } |
| break; |
| default: |
| assert(0); |
| break; |
| } |
| |
| if (type.isMatrix()) |
| spvType = builder.makeMatrixType(spvType, type.getMatrixCols(), type.getMatrixRows()); |
| else { |
| // If this variable has a vector element count greater than 1, create a SPIR-V vector |
| if (type.getVectorSize() > 1) |
| spvType = builder.makeVectorType(spvType, type.getVectorSize()); |
| } |
| |
| if (type.isArray()) { |
| int stride = 0; // keep this 0 unless doing an explicit layout; 0 will mean no decoration, no stride |
| |
| // Do all but the outer dimension |
| if (type.getArraySizes()->getNumDims() > 1) { |
| // We need to decorate array strides for types needing explicit layout, except blocks. |
| if (explicitLayout != glslang::ElpNone && type.getBasicType() != glslang::EbtBlock) { |
| // Use a dummy glslang type for querying internal strides of |
| // arrays of arrays, but using just a one-dimensional array. |
| glslang::TType simpleArrayType(type, 0); // deference type of the array |
| while (simpleArrayType.getArraySizes().getNumDims() > 1) |
| simpleArrayType.getArraySizes().dereference(); |
| |
| // Will compute the higher-order strides here, rather than making a whole |
| // pile of types and doing repetitive recursion on their contents. |
| stride = getArrayStride(simpleArrayType, explicitLayout, qualifier.layoutMatrix); |
| } |
| |
| // make the arrays |
| for (int dim = type.getArraySizes()->getNumDims() - 1; dim > 0; --dim) { |
| spvType = builder.makeArrayType(spvType, makeArraySizeId(*type.getArraySizes(), dim), stride); |
| if (stride > 0) |
| builder.addDecoration(spvType, spv::DecorationArrayStride, stride); |
| stride *= type.getArraySizes()->getDimSize(dim); |
| } |
| } else { |
| // single-dimensional array, and don't yet have stride |
| |
| // We need to decorate array strides for types needing explicit layout, except blocks. |
| if (explicitLayout != glslang::ElpNone && type.getBasicType() != glslang::EbtBlock) |
| stride = getArrayStride(type, explicitLayout, qualifier.layoutMatrix); |
| } |
| |
| // Do the outer dimension, which might not be known for a runtime-sized array |
| if (type.isRuntimeSizedArray()) { |
| spvType = builder.makeRuntimeArray(spvType); |
| } else { |
| assert(type.getOuterArraySize() > 0); |
| spvType = builder.makeArrayType(spvType, makeArraySizeId(*type.getArraySizes(), 0), stride); |
| } |
| if (stride > 0) |
| builder.addDecoration(spvType, spv::DecorationArrayStride, stride); |
| } |
| |
| return spvType; |
| } |
| |
| // TODO: this functionality should exist at a higher level, in creating the AST |
| // |
| // Identify interface members that don't have their required extension turned on. |
| // |
| bool TGlslangToSpvTraverser::filterMember(const glslang::TType& member) |
| { |
| auto& extensions = glslangIntermediate->getRequestedExtensions(); |
| |
| if (member.getFieldName() == "gl_ViewportMask" && |
| extensions.find("GL_NV_viewport_array2") == extensions.end()) |
| return true; |
| if (member.getFieldName() == "gl_SecondaryViewportMaskNV" && |
| extensions.find("GL_NV_stereo_view_rendering") == extensions.end()) |
| return true; |
| if (member.getFieldName() == "gl_SecondaryPositionNV" && |
| extensions.find("GL_NV_stereo_view_rendering") == extensions.end()) |
| return true; |
| if (member.getFieldName() == "gl_PositionPerViewNV" && |
| extensions.find("GL_NVX_multiview_per_view_attributes") == extensions.end()) |
| return true; |
| if (member.getFieldName() == "gl_ViewportMaskPerViewNV" && |
| extensions.find("GL_NVX_multiview_per_view_attributes") == extensions.end()) |
| return true; |
| |
| return false; |
| }; |
| |
| // Do full recursive conversion of a glslang structure (or block) type to a SPIR-V Id. |
| // explicitLayout can be kept the same throughout the hierarchical recursive walk. |
| // Mutually recursive with convertGlslangToSpvType(). |
| spv::Id TGlslangToSpvTraverser::convertGlslangStructToSpvType(const glslang::TType& type, |
| const glslang::TTypeList* glslangMembers, |
| glslang::TLayoutPacking explicitLayout, |
| const glslang::TQualifier& qualifier) |
| { |
| // Create a vector of struct types for SPIR-V to consume |
| std::vector<spv::Id> spvMembers; |
| int memberDelta = 0; // how much the member's index changes from glslang to SPIR-V, normally 0, except sometimes for blocks |
| for (int i = 0; i < (int)glslangMembers->size(); i++) { |
| glslang::TType& glslangMember = *(*glslangMembers)[i].type; |
| if (glslangMember.hiddenMember()) { |
| ++memberDelta; |
| if (type.getBasicType() == glslang::EbtBlock) |
| memberRemapper[glslangMembers][i] = -1; |
| } else { |
| if (type.getBasicType() == glslang::EbtBlock) { |
| memberRemapper[glslangMembers][i] = i - memberDelta; |
| if (filterMember(glslangMember)) |
| continue; |
| } |
| // modify just this child's view of the qualifier |
| glslang::TQualifier memberQualifier = glslangMember.getQualifier(); |
| InheritQualifiers(memberQualifier, qualifier); |
| |
| // manually inherit location |
| if (! memberQualifier.hasLocation() && qualifier.hasLocation()) |
| memberQualifier.layoutLocation = qualifier.layoutLocation; |
| |
| // recurse |
| spvMembers.push_back(convertGlslangToSpvType(glslangMember, explicitLayout, memberQualifier)); |
| } |
| } |
| |
| // Make the SPIR-V type |
| spv::Id spvType = builder.makeStructType(spvMembers, type.getTypeName().c_str()); |
| if (! HasNonLayoutQualifiers(type, qualifier)) |
| structMap[explicitLayout][qualifier.layoutMatrix][glslangMembers] = spvType; |
| |
| // Decorate it |
| decorateStructType(type, glslangMembers, explicitLayout, qualifier, spvType); |
| |
| return spvType; |
| } |
| |
| void TGlslangToSpvTraverser::decorateStructType(const glslang::TType& type, |
| const glslang::TTypeList* glslangMembers, |
| glslang::TLayoutPacking explicitLayout, |
| const glslang::TQualifier& qualifier, |
| spv::Id spvType) |
| { |
| // Name and decorate the non-hidden members |
| int offset = -1; |
| int locationOffset = 0; // for use within the members of this struct |
| for (int i = 0; i < (int)glslangMembers->size(); i++) { |
| glslang::TType& glslangMember = *(*glslangMembers)[i].type; |
| int member = i; |
| if (type.getBasicType() == glslang::EbtBlock) { |
| member = memberRemapper[glslangMembers][i]; |
| if (filterMember(glslangMember)) |
| continue; |
| } |
| |
| // modify just this child's view of the qualifier |
| glslang::TQualifier memberQualifier = glslangMember.getQualifier(); |
| InheritQualifiers(memberQualifier, qualifier); |
| |
| // using -1 above to indicate a hidden member |
| if (member >= 0) { |
| builder.addMemberName(spvType, member, glslangMember.getFieldName().c_str()); |
| addMemberDecoration(spvType, member, TranslateLayoutDecoration(glslangMember, memberQualifier.layoutMatrix)); |
| addMemberDecoration(spvType, member, TranslatePrecisionDecoration(glslangMember)); |
| // Add interpolation and auxiliary storage decorations only to top-level members of Input and Output storage classes |
| if (type.getQualifier().storage == glslang::EvqVaryingIn || |
| type.getQualifier().storage == glslang::EvqVaryingOut) { |
| if (type.getBasicType() == glslang::EbtBlock || |
| glslangIntermediate->getSource() == glslang::EShSourceHlsl) { |
| addMemberDecoration(spvType, member, TranslateInterpolationDecoration(memberQualifier)); |
| addMemberDecoration(spvType, member, TranslateAuxiliaryStorageDecoration(memberQualifier)); |
| } |
| } |
| addMemberDecoration(spvType, member, TranslateInvariantDecoration(memberQualifier)); |
| |
| if (type.getBasicType() == glslang::EbtBlock && |
| qualifier.storage == glslang::EvqBuffer) { |
| // Add memory decorations only to top-level members of shader storage block |
| std::vector<spv::Decoration> memory; |
| TranslateMemoryDecoration(memberQualifier, memory); |
| for (unsigned int i = 0; i < memory.size(); ++i) |
| addMemberDecoration(spvType, member, memory[i]); |
| } |
| |
| // Location assignment was already completed correctly by the front end, |
| // just track whether a member needs to be decorated. |
| // Ignore member locations if the container is an array, as that's |
| // ill-specified and decisions have been made to not allow this. |
| if (! type.isArray() && memberQualifier.hasLocation()) |
| builder.addMemberDecoration(spvType, member, spv::DecorationLocation, memberQualifier.layoutLocation); |
| |
| if (qualifier.hasLocation()) // track for upcoming inheritance |
| locationOffset += glslangIntermediate->computeTypeLocationSize(glslangMember); |
| |
| // component, XFB, others |
| if (glslangMember.getQualifier().hasComponent()) |
| builder.addMemberDecoration(spvType, member, spv::DecorationComponent, glslangMember.getQualifier().layoutComponent); |
| if (glslangMember.getQualifier().hasXfbOffset()) |
| builder.addMemberDecoration(spvType, member, spv::DecorationOffset, glslangMember.getQualifier().layoutXfbOffset); |
| else if (explicitLayout != glslang::ElpNone) { |
| // figure out what to do with offset, which is accumulating |
| int nextOffset; |
| updateMemberOffset(type, glslangMember, offset, nextOffset, explicitLayout, memberQualifier.layoutMatrix); |
| if (offset >= 0) |
| builder.addMemberDecoration(spvType, member, spv::DecorationOffset, offset); |
| offset = nextOffset; |
| } |
| |
| if (glslangMember.isMatrix() && explicitLayout != glslang::ElpNone) |
| builder.addMemberDecoration(spvType, member, spv::DecorationMatrixStride, getMatrixStride(glslangMember, explicitLayout, memberQualifier.layoutMatrix)); |
| |
| // built-in variable decorations |
| spv::BuiltIn builtIn = TranslateBuiltInDecoration(glslangMember.getQualifier().builtIn, |