glslang/SPIRV/GlslangToSpv.cpp
John Kessenich ebb505355d SPV: Don't add clip/cull distance capabilities unless used.
These capabalities were added on declaration of the members, but
that is considered too aggressive, as those members are automatically
declared in some shaders that don't use them.  Now, actual access
is needed to make the capabalities be declared.
2016-05-16 19:32:59 -06:00

4328 lines
172 KiB
C++
Executable File

//
//Copyright (C) 2014-2015 LunarG, Inc.
//Copyright (C) 2015-2016 Google, Inc.
//
//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"
}
// Glslang includes
#include "../glslang/MachineIndependent/localintermediate.h"
#include "../glslang/MachineIndependent/SymbolTable.h"
#include "../glslang/Include/Common.h"
#include <fstream>
#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 = 1;
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);
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 dumpSpv(std::vector<unsigned int>& out);
protected:
spv::Decoration TranslateInterpolationDecoration(const glslang::TQualifier& qualifier);
spv::BuiltIn TranslateBuiltInDecoration(glslang::TBuiltInVariable, bool member);
spv::ImageFormat TranslateImageFormat(const glslang::TType& type);
spv::Id createSpvVariable(const glslang::TIntermSymbol*);
spv::Id getSampledType(const glslang::TSampler&);
spv::Id convertGlslangToSpvType(const glslang::TType& type);
spv::Id convertGlslangToSpvType(const glslang::TType& type, glslang::TLayoutPacking, const glslang::TQualifier&);
spv::Id makeArraySizeId(const glslang::TArraySizes&, int dim);
spv::Id accessChainLoad(const glslang::TType& type);
void accessChainStore(const glslang::TType& type, 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 declareClipCullCapability(const glslang::TTypeList& members, int member);
bool isShaderEntrypoint(const glslang::TIntermAggregate* node);
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, 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 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, spv::Id typeId, spv::Id operand);
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::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::Function* shaderEntry;
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 inMain;
bool mainTerminated;
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<int, spv::Id> symbolValues;
std::unordered_set<int> constReadOnlyParameters; // set of formal function parameters that have glslang qualifier constReadOnly, so we know they are not local function "const" that are write-once
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 type to SPIR-V storage class.
spv::StorageClass TranslateStorageClass(const glslang::TType& type)
{
if (type.getQualifier().isPipeInput())
return spv::StorageClassInput;
else if (type.getQualifier().isPipeOutput())
return spv::StorageClassOutput;
else if (type.getQualifier().isUniformOrBuffer()) {
if (type.getQualifier().layoutPushConstant)
return spv::StorageClassPushConstant;
if (type.getBasicType() == glslang::EbtBlock)
return spv::StorageClassUniform;
else if (type.getBasicType() == glslang::EbtAtomicUint)
return spv::StorageClassAtomicCounter;
else
return spv::StorageClassUniformConstant;
// TODO: how are we distuingishing between default and non-default non-writable uniforms? Do default uniforms even exist?
} else {
switch (type.getQualifier().storage) {
case glslang::EvqShared: return spv::StorageClassWorkgroup; break;
case glslang::EvqGlobal: return spv::StorageClassPrivate;
case glslang::EvqConstReadOnly: return spv::StorageClassFunction;
case glslang::EvqTemporary: return spv::StorageClassFunction;
default:
assert(0);
return spv::StorageClassFunction;
}
}
}
// 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 type to SPIR-V precision decorations.
spv::Decoration TranslatePrecisionDecoration(const glslang::TType& type)
{
switch (type.getQualifier().precision) {
case glslang::EpqLow: return spv::DecorationRelaxedPrecision;
case glslang::EpqMedium: return spv::DecorationRelaxedPrecision;
default:
return spv::NoPrecision;
}
}
// Translate glslang type to SPIR-V block decorations.
spv::Decoration TranslateBlockDecoration(const glslang::TType& type)
{
if (type.getBasicType() == glslang::EbtBlock) {
switch (type.getQualifier().storage) {
case glslang::EvqUniform: return spv::DecorationBlock;
case glslang::EvqBuffer: return spv::DecorationBufferBlock;
case glslang::EvqVaryingIn: return spv::DecorationBlock;
case glslang::EvqVaryingOut: return spv::DecorationBlock;
default:
assert(0);
break;
}
}
return (spv::Decoration)spv::BadValue;
}
// 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::Decoration)spv::BadValue;
}
} else {
switch (type.getBasicType()) {
default:
return (spv::Decoration)spv::BadValue;
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::Decoration)spv::BadValue;
}
case glslang::EvqVaryingIn:
case glslang::EvqVaryingOut:
assert(type.getQualifier().layoutPacking == glslang::ElpNone);
return (spv::Decoration)spv::BadValue;
default:
assert(0);
return (spv::Decoration)spv::BadValue;
}
}
}
}
// Translate glslang type to SPIR-V interpolation decorations.
// Returns spv::Decoration(spv::BadValue) 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::Decoration)spv::BadValue;
}
if (qualifier.nopersp)
return spv::DecorationNoPerspective;
else if (qualifier.patch)
return spv::DecorationPatch;
else if (qualifier.flat)
return spv::DecorationFlat;
else if (qualifier.centroid)
return spv::DecorationCentroid;
else if (qualifier.sample) {
builder.addCapability(spv::CapabilitySampleRateShading);
return spv::DecorationSample;
} else
return (spv::Decoration)spv::BadValue;
}
// 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::Decoration)spv::BadValue;
}
// 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::Decoration)spv::BadValue;
}
// Translate glslang built-in variable to SPIR-V built in decoration.
spv::BuiltIn TGlslangToSpvTraverser::TranslateBuiltInDecoration(glslang::TBuiltInVariable builtIn, bool member)
{
switch (builtIn) {
case glslang::EbvPointSize:
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 (! member)
builder.addCapability(spv::CapabilityClipDistance);
return spv::BuiltInClipDistance;
case glslang::EbvCullDistance:
if (! member)
builder.addCapability(spv::CapabilityCullDistance);
return spv::BuiltInCullDistance;
case glslang::EbvViewportIndex:
builder.addCapability(spv::CapabilityMultiViewport);
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::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:
case glslang::EbvBaseInstance:
case glslang::EbvDrawId:
// TODO: Add SPIR-V builtin ID.
logger->missingFunctionality("shader draw parameters");
return (spv::BuiltIn)spv::BadValue;
case glslang::EbvPrimitiveId: return spv::BuiltInPrimitiveId;
case glslang::EbvInvocationId: return spv::BuiltInInvocationId;
case glslang::EbvLayer: return spv::BuiltInLayer;
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:
case glslang::EbvSubGroupInvocation:
case glslang::EbvSubGroupEqMask:
case glslang::EbvSubGroupGeMask:
case glslang::EbvSubGroupGtMask:
case glslang::EbvSubGroupLeMask:
case glslang::EbvSubGroupLtMask:
// TODO: Add SPIR-V builtin ID.
logger->missingFunctionality("shader ballot");
return (spv::BuiltIn)spv::BadValue;
default: return (spv::BuiltIn)spv::BadValue;
}
}
// 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::ImageFormat)spv::BadValue;
}
}
// 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;
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::TQualifier& qualifier)
{
// This should list qualifiers that simultaneous satisfy:
// - struct members can inherit from a struct declaration
// - effect 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.nopersp || qualifier.flat || qualifier.centroid || qualifier.patch || qualifier.sample || qualifier.hasLocation();
}
//
// Implement the TGlslangToSpvTraverser class.
//
TGlslangToSpvTraverser::TGlslangToSpvTraverser(const glslang::TIntermediate* glslangIntermediate, spv::SpvBuildLogger* buildLogger)
: TIntermTraverser(true, false, true), shaderEntry(0), sequenceDepth(0), logger(buildLogger),
builder((glslang::GetKhronosToolId() << 16) | GeneratorVersion, logger),
inMain(false), mainTerminated(false), linkageOnly(false),
glslangIntermediate(glslangIntermediate)
{
spv::ExecutionModel executionModel = TranslateExecutionModel(glslangIntermediate->getStage());
builder.clearAccessChain();
builder.setSource(TranslateSourceLanguage(glslangIntermediate->getSource(), glslangIntermediate->getProfile()), glslangIntermediate->getVersion());
stdBuiltins = builder.import("GLSL.std.450");
builder.setMemoryModel(spv::AddressingModelLogical, spv::MemoryModelGLSL450);
shaderEntry = builder.makeEntrypoint(glslangIntermediate->getEntryPoint().c_str());
entryPoint = builder.addEntryPoint(executionModel, shaderEntry, glslangIntermediate->getEntryPoint().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 EShLangTessControl:
builder.addCapability(spv::CapabilityTessellation);
builder.addExecutionMode(shaderEntry, spv::ExecutionModeOutputVertices, glslangIntermediate->getVertices());
break;
case EShLangTessEvaluation:
builder.addCapability(spv::CapabilityTessellation);
switch (glslangIntermediate->getInputPrimitive()) {
case glslang::ElgTriangles: mode = spv::ExecutionModeTriangles; break;
case glslang::ElgQuads: mode = spv::ExecutionModeQuads; break;
case glslang::ElgIsolines: mode = spv::ExecutionModeIsolines; break;
default: mode = spv::BadValue; break;
}
if (mode != spv::BadValue)
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::BadValue; break;
}
if (mode != spv::BadValue)
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::BadValue; break;
}
if (mode != spv::BadValue)
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::BadValue; break;
}
if (mode != spv::BadValue)
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::BadValue; break;
}
if (mode != spv::BadValue)
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);
switch(glslangIntermediate->getDepth()) {
case glslang::EldGreater: mode = spv::ExecutionModeDepthGreater; break;
case glslang::EldLess: mode = spv::ExecutionModeDepthLess; break;
default: mode = spv::BadValue; break;
}
if (mode != spv::BadValue)
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 everything and dump
void TGlslangToSpvTraverser::dumpSpv(std::vector<unsigned int>& out)
{
// 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();
builder.dump(out);
}
TGlslangToSpvTraverser::~TGlslangToSpvTraverser()
{
if (! mainTerminated) {
spv::Block* lastMainBlock = shaderEntry->getLastBlock();
builder.setBuildPoint(lastMainBlock);
builder.leaveFunction();
}
}
//
// 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)
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) "const in" arguments to a function, which are an intermediate object.
// See comments in handleUserFunctionCall().
// B) Specialization constants (normal constant don't even come in as a variable),
// These are also pure R-values.
glslang::TQualifier qualifier = symbol->getQualifier();
if ((qualifier.storage == glslang::EvqConstReadOnly && constReadOnlyParameters.find(symbol->getId()) != constReadOnlyParameters.end()) ||
qualifier.isSpecConstant())
builder.setAccessChainRValue(id);
else
builder.setAccessChainLValue(id);
}
}
bool TGlslangToSpvTraverser::visitBinary(glslang::TVisit /* visit */, glslang::TIntermBinary* node)
{
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->getType()),
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);
accessChainStore(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
int index = node->getRight()->getAsConstantUnion()->getConstArray()[0].getIConst();
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);
index = remapper[index];
}
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(node->getRight()->getAsConstantUnion()->getConstArray()[0].getIConst());
builder.accessChainPushSwizzle(swizzle, convertGlslangToSpvType(node->getLeft()->getType()));
} else {
// normal case for indexing array or structure or block
builder.accessChainPush(builder.makeIntConstant(index));
// Add capabilities here for accessing clip/cull distance
if (node->getLeft()->getType().isStruct() && ! node->getLeft()->getType().isArray())
declareClipCullCapability(*node->getLeft()->getType().getStruct(), index);
}
}
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);
glslang::TIntermSequence& swizzleSequence = node->getRight()->getAsAggregate()->getSequence();
std::vector<unsigned> swizzle;
for (int i = 0; i < (int)swizzleSequence.size(); ++i)
swizzle.push_back(swizzleSequence[i]->getAsConstantUnion()->getConstArray()[0].getIConst());
builder.accessChainPushSwizzle(swizzle, convertGlslangToSpvType(node->getLeft()->getType()));
}
return false;
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->getType()),
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)
{
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
builder.clearAccessChain();
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->getType());
spv::Decoration noContraction = TranslateNoContractionDecoration(node->getType().getQualifier());
// it could be a conversion
if (! result)
result = createConversion(node->getOp(), precision, noContraction, convertGlslangToSpvType(node->getType()), operand, node->getOperand()->getBasicType());
// if not, then possibly an operation
if (! result)
result = createUnaryOperation(node->getOp(), precision, noContraction, convertGlslangToSpvType(node->getType()), operand, node->getOperand()->getBasicType());
if (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::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, TranslatePrecisionDecoration(node->getType()),
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;
// try texturing
result = createImageTextureFunctionCall(node);
if (result != spv::NoResult) {
builder.clearAccessChain();
builder.setAccessChainRValue(result);
return false;
} else if (node->getOp() == glslang::EOpImageStore) {
// "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->getType());
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 entry of main(), 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 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)) {
inMain = true;
builder.setBuildPoint(shaderEntry->getLastBlock());
} else {
handleFunctionEntry(node);
}
} else {
if (inMain)
mainTerminated = true;
builder.leaveFunction();
inMain = 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:
{
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:
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::EOpConstructBool:
case glslang::EOpConstructBVec2:
case glslang::EOpConstructBVec3:
case glslang::EOpConstructBVec4:
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:
{
std::vector<spv::Id> arguments;
translateArguments(*node, arguments);
spv::Id resultTypeId = convertGlslangToSpvType(node->getType());
spv::Id constructed;
if (node->getOp() == glslang::EOpConstructTextureSampler)
constructed = builder.createOp(spv::OpSampledImage, resultTypeId, 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(resultTypeId, constituents);
} else if (isMatrix)
constructed = builder.createMatrixConstructor(precision, arguments, resultTypeId);
else
constructed = builder.createConstructor(precision, arguments, resultTypeId);
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:
// compontent-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()->isVector())
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:
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;
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());
result = createBinaryOperation(binOp, precision, TranslateNoContractionDecoration(node->getType().getQualifier()),
convertGlslangToSpvType(node->getType()), 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) {
builder.clearAccessChain();
glslangOperands[arg]->traverse(this);
// 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:
if (arg == 0)
lvalue = true;
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:
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;
}
if (lvalue)
operands.push_back(builder.accessChainGetLValue());
else
operands.push_back(accessChainLoad(glslangOperands[arg]->getAsTyped()->getType()));
}
if (atomic) {
// Handle all atomics
result = createAtomicOperation(node->getOp(), precision, convertGlslangToSpvType(node->getType()), operands, node->getBasicType());
} else {
// Pass through to generic operations.
switch (glslangOperands.size()) {
case 0:
result = createNoArgOperation(node->getOp());
break;
case 1:
result = createUnaryOperation(
node->getOp(), precision,
TranslateNoContractionDecoration(node->getType().getQualifier()),
convertGlslangToSpvType(node->getType()), operands.front(),
glslangOperands[0]->getAsTyped()->getBasicType());
break;
default:
result = createMiscOperation(node->getOp(), precision, convertGlslangToSpvType(node->getType()), operands, node->getBasicType());
break;
}
}
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;
}
}
bool TGlslangToSpvTraverser::visitSelection(glslang::TVisit /* visit */, glslang::TIntermSelection* node)
{
// This path handles both if-then-else and ?:
// The if-then-else has a node type of void, while
// ?: has a non-void node type
spv::Id result = 0;
if (node->getBasicType() != glslang::EbtVoid) {
// don't handle this as just on-the-fly temporaries, because there will be two names
// and better to leave SSA to later passes
result = builder.createVariable(spv::StorageClassFunction, convertGlslangToSpvType(node->getType()));
}
// emit the condition before doing anything with selection
node->getCondition()->traverse(this);
// make an "if" based on the value created by the condition
spv::Builder::If ifBuilder(accessChainLoad(node->getCondition()->getType()), builder);
if (node->getTrueBlock()) {
// emit the "then" statement
node->getTrueBlock()->traverse(this);
if (result)
builder.createStore(accessChainLoad(node->getTrueBlock()->getAsTyped()->getType()), result);
}
if (node->getFalseBlock()) {
ifBuilder.makeBeginElse();
// emit the "else" statement
node->getFalseBlock()->traverse(this);
if (result)
builder.createStore(accessChainLoad(node->getFalseBlock()->getAsTyped()->getType()), result);
}
ifBuilder.makeEndIf();
if (result) {
// 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());
// 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, (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);
// 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.setBuildPoint(&blocks.head);
builder.createLoopMerge(&blocks.merge, &blocks.continue_target, spv::LoopControlMaskNone);
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.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);
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())
builder.makeReturn(false, accessChainLoad(node->getExpression()->getType()));
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 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);
}
}
// 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.
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::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::EbtInt:
spvType = builder.makeIntType(32);
break;
case glslang::EbtUint:
spvType = builder.makeUintType(32);
break;
case glslang::EbtInt64:
builder.addCapability(spv::CapabilityInt64);
spvType = builder.makeIntType(64);
break;
case glslang::EbtUint64:
builder.addCapability(spv::CapabilityInt64);
spvType = builder.makeUintType(64);
break;
case glslang::EbtAtomicUint:
logger->tbdFunctionality("Is atomic_uint an opaque handle in the uniform storage class, or an addresses in the atomic storage class?");
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* glslangStruct = type.getStruct();
std::vector<spv::Id> structFields;
// Try to share structs for different layouts, but not yet for other
// kinds of qualification (primarily not yet including interpolant qualification).
if (! HasNonLayoutQualifiers(qualifier))
spvType = structMap[explicitLayout][qualifier.layoutMatrix][glslangStruct];
if (spvType != spv::NoResult)
break;
// else, we haven't seen it...
// Create a vector of struct types for SPIR-V to consume
int memberDelta = 0; // how much the member's index changes from glslang to SPIR-V, normally 0, except sometimes for blocks
if (type.getBasicType() == glslang::EbtBlock)
memberRemapper[glslangStruct].resize(glslangStruct->size());
int locationOffset = 0; // for use across struct members, when they are called recursively
for (int i = 0; i < (int)glslangStruct->size(); i++) {
glslang::TType& glslangType = *(*glslangStruct)[i].type;
if (glslangType.hiddenMember()) {
++memberDelta;
if (type.getBasicType() == glslang::EbtBlock)
memberRemapper[glslangStruct][i] = -1;
} else {
if (type.getBasicType() == glslang::EbtBlock)
memberRemapper[glslangStruct][i] = i - memberDelta;
// modify just this child's view of the qualifier
glslang::TQualifier subQualifier = glslangType.getQualifier();
InheritQualifiers(subQualifier, qualifier);
// manually inherit location; it's more complex
if (! subQualifier.hasLocation() && qualifier.hasLocation())
subQualifier.layoutLocation = qualifier.layoutLocation + locationOffset;
if (qualifier.hasLocation())
locationOffset += glslangIntermediate->computeTypeLocationSize(glslangType);
// recurse
structFields.push_back(convertGlslangToSpvType(glslangType, explicitLayout, subQualifier));
}
}
// Make the SPIR-V type
spvType = builder.makeStructType(structFields, type.getTypeName().c_str());
if (! HasNonLayoutQualifiers(qualifier))
structMap[explicitLayout][qualifier.layoutMatrix][glslangStruct] = spvType;
// Name and decorate the non-hidden members
int offset = -1;
locationOffset = 0; // for use within the members of this struct, right now
for (int i = 0; i < (int)glslangStruct->size(); i++) {
glslang::TType& glslangType = *(*glslangStruct)[i].type;
int member = i;
if (type.getBasicType() == glslang::EbtBlock)
member = memberRemapper[glslangStruct][i];
// modify just this child's view of the qualifier
glslang::TQualifier subQualifier = glslangType.getQualifier();
InheritQualifiers(subQualifier, qualifier);
// using -1 above to indicate a hidden member
if (member >= 0) {
builder.addMemberName(spvType, member, glslangType.getFieldName().c_str());
addMemberDecoration(spvType, member, TranslateLayoutDecoration(glslangType, subQualifier.layoutMatrix));
addMemberDecoration(spvType, member, TranslatePrecisionDecoration(glslangType));
addMemberDecoration(spvType, member, TranslateInterpolationDecoration(subQualifier));
addMemberDecoration(spvType, member, TranslateInvariantDecoration(subQualifier));
if (qualifier.storage == glslang::EvqBuffer) {
std::vector<spv::Decoration> memory;
TranslateMemoryDecoration(subQualifier, memory);
for (unsigned int i = 0; i < memory.size(); ++i)
addMemberDecoration(spvType, member, memory[i]);
}
// compute location decoration; tricky based on whether inheritance is at play
// TODO: This algorithm (and it's cousin above doing almost the same thing) should
// probably move to the linker stage of the front end proper, and just have the
// answer sitting already distributed throughout the individual member locations.
int location = -1; // will only decorate if present or inherited
if (subQualifier.hasLocation()) // no inheritance, or override of inheritance
location = subQualifier.layoutLocation;
else if (qualifier.hasLocation()) // inheritance
location = qualifier.layoutLocation + locationOffset;
if (qualifier.hasLocation()) // track for upcoming inheritance
locationOffset += glslangIntermediate->computeTypeLocationSize(glslangType);
if (location >= 0)
builder.addMemberDecoration(spvType, member, spv::DecorationLocation, location);
// component, XFB, others
if (glslangType.getQualifier().hasComponent())
builder.addMemberDecoration(spvType, member, spv::DecorationComponent, glslangType.getQualifier().layoutComponent);
if (glslangType.getQualifier().hasXfbOffset())
builder.addMemberDecoration(spvType, member, spv::DecorationOffset, glslangType.getQualifier().layoutXfbOffset);
else if (explicitLayout != glslang::ElpNone) {
// figure out what to do with offset, which is accumulating
int nextOffset;
updateMemberOffset(type, glslangType, offset, nextOffset, explicitLayout, subQualifier.layoutMatrix);
if (offset >= 0)
builder.addMemberDecoration(spvType, member, spv::DecorationOffset, offset);
offset = nextOffset;
}
if (glslangType.isMatrix() && explicitLayout != glslang::ElpNone)
builder.addMemberDecoration(spvType, member, spv::DecorationMatrixStride, getMatrixStride(glslangType, explicitLayout, subQualifier.layoutMatrix));
// built-in variable decorations
spv::BuiltIn builtIn = TranslateBuiltInDecoration(glslangType.getQualifier().builtIn, true);
if (builtIn != spv::BadValue)
addMemberDecoration(spvType, member, spv::DecorationBuiltIn, (int)builtIn);
}
}
// Decorate the structure
addDecoration(spvType, TranslateLayoutDecoration(type, qualifier.layoutMatrix));
addDecoration(spvType, TranslateBlockDecoration(type));
if (type.getQualifier().hasStream() && glslangIntermediate->isMultiStream()) {
builder.addCapability(spv::CapabilityGeometryStreams);
builder.addDecoration(spvType, spv::DecorationStream, type.getQualifier().layoutStream);
}
if (glslangIntermediate->getXfbMode()) {
builder.addCapability(spv::CapabilityTransformFeedback);
if (type.getQualifier().hasXfbStride())
builder.addDecoration(spvType, spv::DecorationXfbStride, type.getQualifier().layoutXfbStride);
if (type.getQualifier().hasXfbBuffer())
builder.addDecoration(spvType, spv::DecorationXfbBuffer, type.getQualifier().layoutXfbBuffer);
}
}
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;
}
// Turn the expression forming the array size into an id.
// This is not quite trivial, because of specialization constants.
// Sometimes, a raw constant is turned into an Id, and sometimes
// a specialization constant expression is.
spv::Id TGlslangToSpvTraverser::makeArraySizeId(const glslang::TArraySizes& arraySizes, int dim)
{
// First, see if this is sized with a node, meaning a specialization constant:
glslang::TIntermTyped* specNode = arraySizes.getDimNode(dim);
if (specNode != nullptr) {
builder.clearAccessChain();
specNode->traverse(this);
return accessChainLoad(specNode->getAsTyped()->getType());
}
// Otherwise, need a compile-time (front end) size, get it:
int size = arraySizes.getDimSize(dim);
assert(size > 0);
return builder.makeUintConstant(size);
}
// Wrap the builder's accessChainLoad to:
// - localize handling of RelaxedPrecision
// - use the SPIR-V inferred type instead of another conversion of the glslang type
// (avoids unnecessary work and possible type punning for structures)
// - do conversion of concrete to abstract type
spv::Id TGlslangToSpvTraverser::accessChainLoad(const glslang::TType& type)
{
spv::Id nominalTypeId = builder.accessChainGetInferredType();
spv::Id loadedId = builder.accessChainLoad(TranslatePrecisionDecoration(type), nominalTypeId);
// Need to convert to abstract types when necessary
if (type.getBasicType() == glslang::EbtBool) {
if (builder.isScalarType(nominalTypeId)) {
// Conversion for bool
spv::Id boolType = builder.makeBoolType();
if (nominalTypeId != boolType)
loadedId = builder.createBinOp(spv::OpINotEqual, boolType, loadedId, builder.makeUintConstant(0));
} else if (builder.isVectorType(nominalTypeId)) {
// Conversion for bvec
int vecSize = builder.getNumTypeComponents(nominalTypeId);
spv::Id bvecType = builder.makeVectorType(builder.makeBoolType(), vecSize);
if (nominalTypeId != bvecType)
loadedId = builder.createBinOp(spv::OpINotEqual, bvecType, loadedId, makeSmearedConstant(builder.makeUintConstant(0), vecSize));
}
}
return loadedId;
}
// Wrap the builder's accessChainStore to:
// - do conversion of concrete to abstract type
void TGlslangToSpvTraverser::accessChainStore(const glslang::TType& type, spv::Id rvalue)
{
// Need to convert to abstract types when necessary
if (type.getBasicType() == glslang::EbtBool) {
spv::Id nominalTypeId = builder.accessChainGetInferredType();
if (builder.isScalarType(nominalTypeId)) {
// Conversion for bool
spv::Id boolType = builder.makeBoolType();
if (nominalTypeId != boolType) {
spv::Id zero = builder.makeUintConstant(0);
spv::Id one = builder.makeUintConstant(1);
rvalue = builder.createTriOp(spv::OpSelect, nominalTypeId, rvalue, one, zero);
}
} else if (builder.isVectorType(nominalTypeId)) {
// Conversion for bvec
int vecSize = builder.getNumTypeComponents(nominalTypeId);
spv::Id bvecType = builder.makeVectorType(builder.makeBoolType(), vecSize);
if (nominalTypeId != bvecType) {
spv::Id zero = makeSmearedConstant(builder.makeUintConstant(0), vecSize);
spv::Id one = makeSmearedConstant(builder.makeUintConstant(1), vecSize);
rvalue = builder.createTriOp(spv::OpSelect, nominalTypeId, rvalue, one, zero);
}
}
}
builder.accessChainStore(rvalue);
}
// Decide whether or not this type should be
// decorated with offsets and strides, and if so
// whether std140 or std430 rules should be applied.
glslang::TLayoutPacking TGlslangToSpvTraverser::getExplicitLayout(const glslang::TType& type) const
{
// has to be a block
if (type.getBasicType() != glslang::EbtBlock)
return glslang::ElpNone;
// has to be a uniform or buffer block
if (type.getQualifier().storage != glslang::EvqUniform &&
type.getQualifier().storage != glslang::EvqBuffer)
return glslang::ElpNone;
// return the layout to use
switch (type.getQualifier().layoutPacking) {
case glslang::ElpStd140:
case glslang::ElpStd430:
return type.getQualifier().layoutPacking;
default:
return glslang::ElpNone;
}
}
// Given an array type, returns the integer stride required for that array
int TGlslangToSpvTraverser::getArrayStride(const glslang::TType& arrayType, glslang::TLayoutPacking explicitLayout, glslang::TLayoutMatrix matrixLayout)
{
int size;
int stride;
glslangIntermediate->getBaseAlignment(arrayType, size, stride, explicitLayout == glslang::ElpStd140, matrixLayout == glslang::ElmRowMajor);
return stride;
}
// Given a matrix type, or array (of array) of matrixes type, returns the integer stride required for that matrix
// when used as a member of an interface block
int TGlslangToSpvTraverser::getMatrixStride(const glslang::TType& matrixType, glslang::TLayoutPacking explicitLayout, glslang::TLayoutMatrix matrixLayout)
{
glslang::TType elementType;
elementType.shallowCopy(matrixType);
elementType.clearArraySizes();
int size;
int stride;
glslangIntermediate->getBaseAlignment(elementType, size, stride, explicitLayout == glslang::ElpStd140, matrixLayout == glslang::ElmRowMajor);
return stride;
}
// Given a member type of a struct, realign the current offset for it, and compute
// the next (not yet aligned) offset for the next member, which will get aligned
// on the next call.
// 'currentOffset' should be passed in already initialized, ready to modify, and reflecting
// the migration of data from nextOffset -> currentOffset. It should be -1 on the first call.
// -1 means a non-forced member offset (no decoration needed).
void TGlslangToSpvTraverser::updateMemberOffset(const glslang::TType& /*structType*/, const glslang::TType& memberType, int& currentOffset, int& nextOffset,
glslang::TLayoutPacking explicitLayout, glslang::TLayoutMatrix matrixLayout)
{
// this will get a positive value when deemed necessary
nextOffset = -1;
// override anything in currentOffset with user-set offset
if (memberType.getQualifier().hasOffset())
currentOffset = memberType.getQualifier().layoutOffset;
// It could be that current linker usage in glslang updated all the layoutOffset,
// in which case the following code does not matter. But, that's not quite right
// once cross-compilation unit GLSL validation is done, as the original user
// settings are needed in layoutOffset, and then the following will come into play.
if (explicitLayout == glslang::ElpNone) {
if (! memberType.getQualifier().hasOffset())
currentOffset = -1;
return;
}
// Getting this far means we need explicit offsets
if (currentOffset < 0)
currentOffset = 0;
// Now, currentOffset is valid (either 0, or from a previous nextOffset),
// but possibly not yet correctly aligned.
int memberSize;
int dummyStride;
int memberAlignment = glslangIntermediate->getBaseAlignment(memberType, memberSize, dummyStride, explicitLayout == glslang::ElpStd140, matrixLayout == glslang::ElmRowMajor);
glslang::RoundToPow2(currentOffset, memberAlignment);
nextOffset = currentOffset + memberSize;
}
void TGlslangToSpvTraverser::declareClipCullCapability(const glslang::TTypeList& members, int member)
{
if (members[member].type->getQualifier().builtIn == glslang::EbvClipDistance)
builder.addCapability(spv::CapabilityClipDistance);
if (members[member].type->getQualifier().builtIn == glslang::EbvCullDistance)
builder.addCapability(spv::CapabilityCullDistance);
}
bool TGlslangToSpvTraverser::isShaderEntrypoint(const glslang::TIntermAggregate* node)
{
// have to ignore mangling and just look at the base name
size_t firstOpen = node->getName().find('(');
return node->getName().compare(0, firstOpen, glslangIntermediate->getEntryPoint().c_str()) == 0;
}
// Make all the functions, skeletally, without actually visiting their bodies.
void TGlslangToSpvTraverser::makeFunctions(const glslang::TIntermSequence& glslFunctions)
{
for (int f = 0; f < (int)glslFunctions.size(); ++f) {
glslang::TIntermAggregate* glslFunction = glslFunctions[f]->getAsAggregate();
if (! glslFunction || glslFunction->getOp() != glslang::EOpFunction || isShaderEntrypoint(glslFunction))
continue;
// We're on a user function. Set up the basic interface for the function now,
// so that it's available to call.
// Translating the body will happen later.
//
// Typically (except for a "const in" parameter), an address will be passed to the
// function. What it is an address of varies:
//
// - "in" parameters not marked as "const" can be written to without modifying the argument,
// so that write needs to be to a copy, hence the address of a copy works.
//
// - "const in" parameters can just be the r-value, as no writes need occur.
//
// - "out" and "inout" arguments can't be done as direct pointers, because GLSL has
// copy-in/copy-out semantics. They can be handled though with a pointer to a copy.
std::vector<spv::Id> paramTypes;
std::vector<spv::Decoration> paramPrecisions;
glslang::TIntermSequence& parameters = glslFunction->getSequence()[0]->getAsAggregate()->getSequence();
for (int p = 0; p < (int)parameters.size(); ++p) {
const glslang::TType& paramType = parameters[p]->getAsTyped()->getType();
spv::Id typeId = convertGlslangToSpvType(paramType);
if (paramType.getQualifier().storage != glslang::EvqConstReadOnly)
typeId = builder.makePointer(spv::StorageClassFunction, typeId);
else
constReadOnlyParameters.insert(parameters[p]->getAsSymbolNode()->getId());
paramPrecisions.push_back(TranslatePrecisionDecoration(paramType));
paramTypes.push_back(typeId);
}
spv::Block* functionBlock;
spv::Function *function = builder.makeFunctionEntry(TranslatePrecisionDecoration(glslFunction->getType()),
convertGlslangToSpvType(glslFunction->getType()),
glslFunction->getName().c_str(), paramTypes, paramPrecisions, &functionBlock);
// Track function to emit/call later
functionMap[glslFunction->getName().c_str()] = function;
// Set the parameter id's
for (int p = 0; p < (int)parameters.size(); ++p) {
symbolValues[parameters[p]->getAsSymbolNode()->getId()] = function->getParamId(p);
// give a name too
builder.addName(function->getParamId(p), parameters[p]->getAsSymbolNode()->getName().c_str());
}
}
}
// Process all the initializers, while skipping the functions and link objects
void TGlslangToSpvTraverser::makeGlobalInitializers(const glslang::TIntermSequence& initializers)
{
builder.setBuildPoint(shaderEntry->getLastBlock());
for (int i = 0; i < (int)initializers.size(); ++i) {
glslang::TIntermAggregate* initializer = initializers[i]->getAsAggregate();
if (initializer && initializer->getOp() != glslang::EOpFunction && initializer->getOp() != glslang::EOpLinkerObjects) {
// We're on a top-level node that's not a function. Treat as an initializer, whose
// code goes into the beginning of main.
initializer->traverse(this);
}
}
}
// Process all the functions, while skipping initializers.
void TGlslangToSpvTraverser::visitFunctions(const glslang::TIntermSequence& glslFunctions)
{
for (int f = 0; f < (int)glslFunctions.size(); ++f) {
glslang::TIntermAggregate* node = glslFunctions[f]->getAsAggregate();
if (node && (node->getOp() == glslang::EOpFunction || node->getOp() == glslang ::EOpLinkerObjects))
node->traverse(this);
}
}
void TGlslangToSpvTraverser::handleFunctionEntry(const glslang::TIntermAggregate* node)
{
// SPIR-V functions should already be in the functionMap from the prepass
// that called makeFunctions().
spv::Function* function = functionMap[node->getName().c_str()];
spv::Block* functionBlock = function->getEntryBlock();
builder.setBuildPoint(functionBlock);
}
void TGlslangToSpvTraverser::translateArguments(const glslang::TIntermAggregate& node, std::vector<spv::Id>& arguments)
{
const glslang::TIntermSequence& glslangArguments = node.getSequence();
glslang::TSampler sampler = {};
bool cubeCompare = false;
if (node.isTexture() || node.isImage()) {
sampler = glslangArguments[0]->getAsTyped()->getType().getSampler();
cubeCompare = sampler.dim == glslang::EsdCube && sampler.arrayed && sampler.shadow;
}
for (int i = 0; i < (int)glslangArguments.size(); ++i) {
builder.clearAccessChain();
glslangArguments[i]->traverse(this);
// Special case l-value operands
bool lvalue = false;
switch (node.getOp()) {
case glslang::EOpImageAtomicAdd:
case glslang::EOpImageAtomicMin:
case glslang::EOpImageAtomicMax:
case glslang::EOpImageAtomicAnd:
case glslang::EOpImageAtomicOr:
case glslang::EOpImageAtomicXor:
case glslang::EOpImageAtomicExchange:
case glslang::EOpImageAtomicCompSwap:
if (i == 0)
lvalue = true;
break;
case glslang::EOpSparseImageLoad:
if ((sampler.ms && i == 3) || (! sampler.ms && i == 2))
lvalue = true;
break;
case glslang::EOpSparseTexture:
if ((cubeCompare && i == 3) || (! cubeCompare && i == 2))
lvalue = true;
break;
case glslang::EOpSparseTextureClamp:
if ((cubeCompare && i == 4) || (! cubeCompare && i == 3))
lvalue = true;
break;
case glslang::EOpSparseTextureLod:
case glslang::EOpSparseTextureOffset:
if (i == 3)
lvalue = true;
break;
case glslang::EOpSparseTextureFetch:
if ((sampler.dim != glslang::EsdRect && i == 3) || (sampler.dim == glslang::EsdRect && i == 2))
lvalue = true;
break;
case glslang::EOpSparseTextureFetchOffset:
if ((sampler.dim != glslang::EsdRect && i == 4) || (sampler.dim == glslang::EsdRect && i == 3))
lvalue = true;
break;
case glslang::EOpSparseTextureLodOffset:
case glslang::EOpSparseTextureGrad:
case glslang::EOpSparseTextureOffsetClamp:
if (i == 4)
lvalue = true;
break;
case glslang::EOpSparseTextureGradOffset:
case glslang::EOpSparseTextureGradClamp:
if (i == 5)
lvalue = true;
break;
case glslang::EOpSparseTextureGradOffsetClamp:
if (i == 6)
lvalue = true;
break;
case glslang::EOpSparseTextureGather:
if ((sampler.shadow && i == 3) || (! sampler.shadow && i == 2))
lvalue = true;
break;
case glslang::EOpSparseTextureGatherOffset:
case glslang::EOpSparseTextureGatherOffsets:
if ((sampler.shadow && i == 4) || (! sampler.shadow && i == 3))
lvalue = true;
break;
default:
break;
}
if (lvalue)
arguments.push_back(builder.accessChainGetLValue());
else
arguments.push_back(accessChainLoad(glslangArguments[i]->getAsTyped()->getType()));
}
}
void TGlslangToSpvTraverser::translateArguments(glslang::TIntermUnary& node, std::vector<spv::Id>& arguments)
{
builder.clearAccessChain();
node.getOperand()->traverse(this);
arguments.push_back(accessChainLoad(node.getOperand()->getType()));
}
spv::Id TGlslangToSpvTraverser::createImageTextureFunctionCall(glslang::TIntermOperator* node)
{
if (! node->isImage() && ! node->isTexture()) {
return spv::NoResult;
}
// Process a GLSL texturing op (will be SPV image)
const glslang::TSampler sampler = node->getAsAggregate() ? node->getAsAggregate()->getSequence()[0]->getAsTyped()->getType().getSampler()
: node->getAsUnaryNode()->getOperand()->getAsTyped()->getType().getSampler();
std::vector<spv::Id> arguments;
if (node->getAsAggregate())
translateArguments(*node->getAsAggregate(), arguments);
else
translateArguments(*node->getAsUnaryNode(), arguments);
spv::Decoration precision = TranslatePrecisionDecoration(node->getType());
spv::Builder::TextureParameters params = { };
params.sampler = arguments[0];
glslang::TCrackedTextureOp cracked;
node->crackTexture(sampler, cracked);
// Check for queries
if (cracked.query) {
// a sampled image needs to have the image extracted first
if (builder.isSampledImage(params.sampler))
params.sampler = builder.createUnaryOp(spv::OpImage, builder.getImageType(params.sampler), params.sampler);
switch (node->getOp()) {
case glslang::EOpImageQuerySize:
case glslang::EOpTextureQuerySize:
if (arguments.size() > 1) {
params.lod = arguments[1];
return builder.createTextureQueryCall(spv::OpImageQuerySizeLod, params);
} else
return builder.createTextureQueryCall(spv::OpImageQuerySize, params);
case glslang::EOpImageQuerySamples:
case glslang::EOpTextureQuerySamples:
return builder.createTextureQueryCall(spv::OpImageQuerySamples, params);
case glslang::EOpTextureQueryLod:
params.coords = arguments[1];
return builder.createTextureQueryCall(spv::OpImageQueryLod, params);
case glslang::EOpTextureQueryLevels:
return builder.createTextureQueryCall(spv::OpImageQueryLevels, params);
case glslang::EOpSparseTexelsResident:
return builder.createUnaryOp(spv::OpImageSparseTexelsResident, builder.makeBoolType(), arguments[0]);
default:
assert(0);
break;
}
}
// Check for image functions other than queries
if (node->isImage()) {
std::vector<spv::Id> operands;
auto opIt = arguments.begin();
operands.push_back(*(opIt++));
// Handle subpass operations
// TODO: GLSL should change to have the "MS" only on the type rather than the
// built-in function.
if (cracked.subpass) {
// add on the (0,0) coordinate
spv::Id zero = builder.makeIntConstant(0);
std::vector<spv::Id> comps;
comps.push_back(zero);
comps.push_back(zero);
operands.push_back(builder.makeCompositeConstant(builder.makeVectorType(builder.makeIntType(32), 2), comps));
if (sampler.ms) {
operands.push_back(spv::ImageOperandsSampleMask);
operands.push_back(*(opIt++));
}
return builder.createOp(spv::OpImageRead, convertGlslangToSpvType(node->getType()), operands);
}
operands.push_back(*(opIt++));
if (node->getOp() == glslang::EOpImageLoad) {
if (sampler.ms) {
operands.push_back(spv::ImageOperandsSampleMask);
operands.push_back(*opIt);
}
if (builder.getImageTypeFormat(builder.getImageType(operands.front())) == spv::ImageFormatUnknown)
builder.addCapability(spv::CapabilityStorageImageReadWithoutFormat);
return builder.createOp(spv::OpImageRead, convertGlslangToSpvType(node->getType()), operands);
} else if (node->getOp() == glslang::EOpImageStore) {
if (sampler.ms) {
operands.push_back(*(opIt + 1));
operands.push_back(spv::ImageOperandsSampleMask);
operands.push_back(*opIt);
} else
operands.push_back(*opIt);
builder.createNoResultOp(spv::OpImageWrite, operands);
if (builder.getImageTypeFormat(builder.getImageType(operands.front())) == spv::ImageFormatUnknown)
builder.addCapability(spv::CapabilityStorageImageWriteWithoutFormat);
return spv::NoResult;
} else if (node->getOp() == glslang::EOpSparseImageLoad) {
builder.addCapability(spv::CapabilitySparseResidency);
if (builder.getImageTypeFormat(builder.getImageType(operands.front())) == spv::ImageFormatUnknown)
builder.addCapability(spv::CapabilityStorageImageReadWithoutFormat);
if (sampler.ms) {
operands.push_back(spv::ImageOperandsSampleMask);
operands.push_back(*opIt++);
}
// Create the return type that was a special structure
spv::Id texelOut = *opIt;
spv::Id typeId0 = convertGlslangToSpvType(node->getType());
spv::Id typeId1 = builder.getDerefTypeId(texelOut);
spv::Id resultTypeId = builder.makeStructResultType(typeId0, typeId1);
spv::Id resultId = builder.createOp(spv::OpImageSparseRead, resultTypeId, operands);
// Decode the return type
builder.createStore(builder.createCompositeExtract(resultId, typeId1, 1), texelOut);
return builder.createCompositeExtract(resultId, typeId0, 0);
} else {
// Process image atomic operations
// GLSL "IMAGE_PARAMS" will involve in constructing an image texel pointer and this pointer,
// as the first source operand, is required by SPIR-V atomic operations.
operands.push_back(sampler.ms ? *(opIt++) : builder.makeUintConstant(0)); // For non-MS, the value should be 0
spv::Id resultTypeId = builder.makePointer(spv::StorageClassImage, convertGlslangToSpvType(node->getType()));
spv::Id pointer = builder.createOp(spv::OpImageTexelPointer, resultTypeId, operands);
std::vector<spv::Id> operands;
operands.push_back(pointer);
for (; opIt != arguments.end(); ++opIt)
operands.push_back(*opIt);
return createAtomicOperation(node->getOp(), precision, convertGlslangToSpvType(node->getType()), operands, node->getBasicType());
}
}
// Check for texture functions other than queries
bool sparse = node->isSparseTexture();
bool cubeCompare = sampler.dim == glslang::EsdCube && sampler.arrayed && sampler.shadow;
// check for bias argument
bool bias = false;
if (! cracked.lod && ! cracked.gather && ! cracked.grad && ! cracked.fetch && ! cubeCompare) {
int nonBiasArgCount = 2;
if (cracked.offset)
++nonBiasArgCount;
if (cracked.grad)
nonBiasArgCount += 2;
if (cracked.lodClamp)
++nonBiasArgCount;
if (sparse)
++nonBiasArgCount;
if ((int)arguments.size() > nonBiasArgCount)
bias = true;
}
// set the rest of the arguments
params.coords = arguments[1];
int extraArgs = 0;
bool noImplicitLod = false;
// sort out where Dref is coming from
if (cubeCompare) {
params.Dref = arguments[2];
++extraArgs;
} else if (sampler.shadow && cracked.gather) {
params.Dref = arguments[2];
++extraArgs;
} else if (sampler.shadow) {
std::vector<spv::Id> indexes;
int comp;
if (cracked.proj)
comp = 2; // "The resulting 3rd component of P in the shadow forms is used as Dref"
else
comp = builder.getNumComponents(params.coords) - 1;
indexes.push_back(comp);
params.Dref = builder.createCompositeExtract(params.coords, builder.getScalarTypeId(builder.getTypeId(params.coords)), indexes);
}
if (cracked.lod) {
params.lod = arguments[2];
++extraArgs;
} else if (glslangIntermediate->getStage() != EShLangFragment) {
// we need to invent the default lod for an explicit lod instruction for a non-fragment stage
noImplicitLod = true;
}
if (sampler.ms) {
params.sample = arguments[2]; // For MS, "sample" should be specified
++extraArgs;
}
if (cracked.grad) {
params.gradX = arguments[2 + extraArgs];
params.gradY = arguments[3 + extraArgs];
extraArgs += 2;
}
if (cracked.offset) {
params.offset = arguments[2 + extraArgs];
++extraArgs;
} else if (cracked.offsets) {
params.offsets = arguments[2 + extraArgs];
++extraArgs;
}
if (cracked.lodClamp) {
params.lodClamp = arguments[2 + extraArgs];
++extraArgs;
}
if (sparse) {
params.texelOut = arguments[2 + extraArgs];
++extraArgs;
}
if (bias) {
params.bias = arguments[2 + extraArgs];
++extraArgs;
}
if (cracked.gather && ! sampler.shadow) {
// default component is 0, if missing, otherwise an argument
if (2 + extraArgs < (int)arguments.size()) {
params.comp = arguments[2 + extraArgs];
++extraArgs;
} else {
params.comp = builder.makeIntConstant(0);
}
}
return builder.createTextureCall(precision, convertGlslangToSpvType(node->getType()), sparse, cracked.fetch, cracked.proj, cracked.gather, noImplicitLod, params);
}
spv::Id TGlslangToSpvTraverser::handleUserFunctionCall(const glslang::TIntermAggregate* node)
{
// Grab the function's pointer from the previously created function
spv::Function* function = functionMap[node->getName().c_str()];
if (! function)
return 0;
const glslang::TIntermSequence& glslangArgs = node->getSequence();
const glslang::TQualifierList& qualifiers = node->getQualifierList();
// See comments in makeFunctions() for details about the semantics for parameter passing.
//
// These imply we need a four step process:
// 1. Evaluate the arguments
// 2. Allocate and make copies of in, out, and inout arguments
// 3. Make the call
// 4. Copy back the results
// 1. Evaluate the arguments
std::vector<spv::Builder::AccessChain> lValues;
std::vector<spv::Id> rValues;
std::vector<const glslang::TType*> argTypes;
for (int a = 0; a < (int)glslangArgs.size(); ++a) {
// build l-value
builder.clearAccessChain();
glslangArgs[a]->traverse(this);
argTypes.push_back(&glslangArgs[a]->getAsTyped()->getType());
// keep outputs as l-values, evaluate input-only as r-values
if (qualifiers[a] != glslang::EvqConstReadOnly) {
// save l-value
lValues.push_back(builder.getAccessChain());
} else {
// process r-value
rValues.push_back(accessChainLoad(*argTypes.back()));
}
}
// 2. Allocate space for anything needing a copy, and if it's "in" or "inout"
// copy the original into that space.
//
// Also, build up the list of actual arguments to pass in for the call
int lValueCount = 0;
int rValueCount = 0;
std::vector<spv::Id> spvArgs;
for (int a = 0; a < (int)glslangArgs.size(); ++a) {
spv::Id arg;
if (qualifiers[a] != glslang::EvqConstReadOnly) {
// need space to hold the copy
const glslang::TType& paramType = glslangArgs[a]->getAsTyped()->getType();
arg = builder.createVariable(spv::StorageClassFunction, convertGlslangToSpvType(paramType), "param");
if (qualifiers[a] == glslang::EvqIn || qualifiers[a] == glslang::EvqInOut) {
// need to copy the input into output space
builder.setAccessChain(lValues[lValueCount]);
spv::Id copy = accessChainLoad(*argTypes[a]);
builder.createStore(copy, arg);
}
++lValueCount;
} else {
arg = rValues[rValueCount];
++rValueCount;
}
spvArgs.push_back(arg);
}
// 3. Make the call.
spv::Id result = builder.createFunctionCall(function, spvArgs);
builder.setPrecision(result, TranslatePrecisionDecoration(node->getType()));
// 4. Copy back out an "out" arguments.
lValueCount = 0;
for (int a = 0; a < (int)glslangArgs.size(); ++a) {
if (qualifiers[a] != glslang::EvqConstReadOnly) {
if (qualifiers[a] == glslang::EvqOut || qualifiers[a] == glslang::EvqInOut) {
spv::Id copy = builder.createLoad(spvArgs[a]);
builder.setAccessChain(lValues[lValueCount]);
accessChainStore(glslangArgs[a]->getAsTyped()->getType(), copy);
}
++lValueCount;
}
}
return result;
}
// Translate AST operation to SPV operation, already having SPV-based operands/types.
spv::Id TGlslangToSpvTraverser::createBinaryOperation(glslang::TOperator op, spv::Decoration precision,
spv::Decoration noContraction,
spv::Id typeId, spv::Id left, spv::Id right,
glslang::TBasicType typeProxy, bool reduceComparison)
{
bool isUnsigned = typeProxy == glslang::EbtUint || typeProxy == glslang::EbtUint64;
bool isFloat = typeProxy == glslang::EbtFloat || typeProxy == glslang::EbtDouble;
bool isBool = typeProxy == glslang::EbtBool;
spv::Op binOp = spv::OpNop;
bool needMatchingVectors = true; // for non-matrix ops, would a scalar need to smear to match a vector?
bool comparison = false;
switch (op) {
case glslang::EOpAdd:
case glslang::EOpAddAssign:
if (isFloat)
binOp = spv::OpFAdd;
else
binOp = spv::OpIAdd;
break;
case glslang::EOpSub:
case glslang::EOpSubAssign:
if (isFloat)
binOp = spv::OpFSub;
else
binOp = spv::OpISub;
break;
case glslang::EOpMul:
case glslang::EOpMulAssign:
if (isFloat)
binOp = spv::OpFMul;
else
binOp = spv::OpIMul;
break;
case glslang::EOpVectorTimesScalar:
case glslang::EOpVectorTimesScalarAssign:
if (isFloat) {
if (builder.isVector(right))
std::swap(left, right);
assert(builder.isScalar(right));
needMatchingVectors = false;
binOp = spv::OpVectorTimesScalar;
} else
binOp = spv::OpIMul;
break;
case glslang::EOpVectorTimesMatrix:
case glslang::EOpVectorTimesMatrixAssign:
binOp = spv::OpVectorTimesMatrix;
break;
case glslang::EOpMatrixTimesVector:
binOp = spv::OpMatrixTimesVector;
break;
case glslang::EOpMatrixTimesScalar:
case glslang::EOpMatrixTimesScalarAssign:
binOp = spv::OpMatrixTimesScalar;
break;
case glslang::EOpMatrixTimesMatrix:
case glslang::EOpMatrixTimesMatrixAssign:
binOp = spv::OpMatrixTimesMatrix;
break;
case glslang::EOpOuterProduct:
binOp = spv::OpOuterProduct;
needMatchingVectors = false;
break;
case glslang::EOpDiv:
case glslang::EOpDivAssign:
if (isFloat)
binOp = spv::OpFDiv;
else if (isUnsigned)
binOp = spv::OpUDiv;
else
binOp = spv::OpSDiv;
break;
case glslang::EOpMod:
case glslang::EOpModAssign:
if (isFloat)
binOp = spv::OpFMod;
else if (isUnsigned)
binOp = spv::OpUMod;
else
binOp = spv::OpSMod;
break;
case glslang::EOpRightShift:
case glslang::EOpRightShiftAssign:
if (isUnsigned)
binOp = spv::OpShiftRightLogical;
else
binOp = spv::OpShiftRightArithmetic;
break;
case glslang::EOpLeftShift:
case glslang::EOpLeftShiftAssign:
binOp = spv::OpShiftLeftLogical;
break;
case glslang::EOpAnd:
case glslang::EOpAndAssign:
binOp = spv::OpBitwiseAnd;
break;
case glslang::EOpLogicalAnd:
needMatchingVectors = false;
binOp = spv::OpLogicalAnd;
break;
case glslang::EOpInclusiveOr:
case glslang::EOpInclusiveOrAssign:
binOp = spv::OpBitwiseOr;
break;
case glslang::EOpLogicalOr:
needMatchingVectors = false;
binOp = spv::OpLogicalOr;
break;
case glslang::EOpExclusiveOr:
case glslang::EOpExclusiveOrAssign:
binOp = spv::OpBitwiseXor;
break;
case glslang::EOpLogicalXor:
needMatchingVectors = false;
binOp = spv::OpLogicalNotEqual;
break;
case glslang::EOpLessThan:
case glslang::EOpGreaterThan:
case glslang::EOpLessThanEqual:
case glslang::EOpGreaterThanEqual:
case glslang::EOpEqual:
case glslang::EOpNotEqual:
case glslang::EOpVectorEqual:
case glslang::EOpVectorNotEqual:
comparison = true;
break;
default:
break;
}
// handle mapped binary operations (should be non-comparison)
if (binOp != spv::OpNop) {
assert(comparison == false);
if (builder.isMatrix(left) || builder.isMatrix(right))
return createBinaryMatrixOperation(binOp, precision, noContraction, typeId, left, right);
// No matrix involved; make both operands be the same number of components, if needed
if (needMatchingVectors)
builder.promoteScalar(precision, left, right);
spv::Id result = builder.createBinOp(binOp, typeId, left, right);
addDecoration(result, noContraction);
return builder.setPrecision(result, precision);
}
if (! comparison)
return 0;
// Handle comparison instructions
if (reduceComparison && (builder.isVector(left) || builder.isMatrix(left) || builder.isAggregate(left))) {
assert(op == glslang::EOpEqual || op == glslang::EOpNotEqual);
return builder.createCompositeCompare(precision, left, right, op == glslang::EOpEqual);
}
switch (op) {
case glslang::EOpLessThan:
if (isFloat)
binOp = spv::OpFOrdLessThan;
else if (isUnsigned)
binOp = spv::OpULessThan;
else
binOp = spv::OpSLessThan;
break;
case glslang::EOpGreaterThan:
if (isFloat)
binOp = spv::OpFOrdGreaterThan;
else if (isUnsigned)
binOp = spv::OpUGreaterThan;
else
binOp = spv::OpSGreaterThan;
break;
case glslang::EOpLessThanEqual:
if (isFloat)
binOp = spv::OpFOrdLessThanEqual;
else if (isUnsigned)
binOp = spv::OpULessThanEqual;
else
binOp = spv::OpSLessThanEqual;
break;
case glslang::EOpGreaterThanEqual:
if (isFloat)
binOp = spv::OpFOrdGreaterThanEqual;
else if (isUnsigned)
binOp = spv::OpUGreaterThanEqual;
else
binOp = spv::OpSGreaterThanEqual;
break;
case glslang::EOpEqual:
case glslang::EOpVectorEqual:
if (isFloat)
binOp = spv::OpFOrdEqual;
else if (isBool)
binOp = spv::OpLogicalEqual;
else
binOp = spv::OpIEqual;
break;
case glslang::EOpNotEqual:
case glslang::EOpVectorNotEqual:
if (isFloat)
binOp = spv::OpFOrdNotEqual;
else if (isBool)
binOp = spv::OpLogicalNotEqual;
else
binOp = spv::OpINotEqual;
break;
default:
break;
}
if (binOp != spv::OpNop) {
spv::Id result = builder.createBinOp(binOp, typeId, left, right);
addDecoration(result, noContraction);
return builder.setPrecision(result, precision);
}
return 0;
}
//
// Translate AST matrix operation to SPV operation, already having SPV-based operands/types.
// These can be any of:
//
// matrix * scalar
// scalar * matrix
// matrix * matrix linear algebraic
// matrix * vector
// vector * matrix
// matrix * matrix componentwise
// matrix op matrix op in {+, -, /}
// matrix op scalar op in {+, -, /}
// scalar op matrix op in {+, -, /}
//
spv::Id TGlslangToSpvTraverser::createBinaryMatrixOperation(spv::Op op, spv::Decoration precision, spv::Decoration noContraction, spv::Id typeId, spv::Id left, spv::Id right)
{
bool firstClass = true;
// First, handle first-class matrix operations (* and matrix/scalar)
switch (op) {
case spv::OpFDiv:
if (builder.isMatrix(left) && builder.isScalar(right)) {
// turn matrix / scalar into a multiply...
right = builder.createBinOp(spv::OpFDiv, builder.getTypeId(right), builder.makeFloatConstant(1.0F), right);
op = spv::OpMatrixTimesScalar;
} else
firstClass = false;
break;
case spv::OpMatrixTimesScalar:
if (builder.isMatrix(right))
std::swap(left, right);
assert(builder.isScalar(right));
break;
case spv::OpVectorTimesMatrix:
assert(builder.isVector(left));
assert(builder.isMatrix(right));
break;
case spv::OpMatrixTimesVector:
assert(builder.isMatrix(left));
assert(builder.isVector(right));
break;
case spv::OpMatrixTimesMatrix:
assert(builder.isMatrix(left));
assert(builder.isMatrix(right));
break;
default:
firstClass = false;
break;
}
if (firstClass) {
spv::Id result = builder.createBinOp(op, typeId, left, right);
addDecoration(result, noContraction);
return builder.setPrecision(result, precision);
}
// Handle component-wise +, -, *, and / for all combinations of type.
// The result type of all of them is the same type as the (a) matrix operand.
// The algorithm is to:
// - break the matrix(es) into vectors
// - smear any scalar to a vector
// - do vector operations
// - make a matrix out the vector results
switch (op) {
case spv::OpFAdd:
case spv::OpFSub:
case spv::OpFDiv:
case spv::OpFMul:
{
// one time set up...
bool leftMat = builder.isMatrix(left);
bool rightMat = builder.isMatrix(right);
unsigned int numCols = leftMat ? builder.getNumColumns(left) : builder.getNumColumns(right);
int numRows = leftMat ? builder.getNumRows(left) : builder.getNumRows(right);
spv::Id scalarType = builder.getScalarTypeId(typeId);
spv::Id vecType = builder.makeVectorType(scalarType, numRows);
std::vector<spv::Id> results;
spv::Id smearVec = spv::NoResult;
if (builder.isScalar(left))
smearVec = builder.smearScalar(precision, left, vecType);
else if (builder.isScalar(right))
smearVec = builder.smearScalar(precision, right, vecType);
// do each vector op
for (unsigned int c = 0; c < numCols; ++c) {
std::vector<unsigned int> indexes;
indexes.push_back(c);
spv::Id leftVec = leftMat ? builder.createCompositeExtract( left, vecType, indexes) : smearVec;
spv::Id rightVec = rightMat ? builder.createCompositeExtract(right, vecType, indexes) : smearVec;
spv::Id result = builder.createBinOp(op, vecType, leftVec, rightVec);
addDecoration(result, noContraction);
results.push_back(builder.setPrecision(result, precision));
}
// put the pieces together
return builder.setPrecision(builder.createCompositeConstruct(typeId, results), precision);
}
default:
assert(0);
return spv::NoResult;
}
}
spv::Id TGlslangToSpvTraverser::createUnaryOperation(glslang::TOperator op, spv::Decoration precision, spv::Decoration noContraction, spv::Id typeId, spv::Id operand, glslang::TBasicType typeProxy)
{
spv::Op unaryOp = spv::OpNop;
int libCall = -1;
bool isUnsigned = typeProxy == glslang::EbtUint || typeProxy == glslang::EbtUint64;
bool isFloat = typeProxy == glslang::EbtFloat || typeProxy == glslang::EbtDouble;
switch (op) {
case glslang::EOpNegative:
if (isFloat) {
unaryOp = spv::OpFNegate;
if (builder.isMatrixType(typeId))
return createUnaryMatrixOperation(unaryOp, precision, noContraction, typeId, operand, typeProxy);
} else
unaryOp = spv::OpSNegate;
break;
case glslang::EOpLogicalNot:
case glslang::EOpVectorLogicalNot:
unaryOp = spv::OpLogicalNot;
break;
case glslang::EOpBitwiseNot:
unaryOp = spv::OpNot;
break;
case glslang::EOpDeterminant:
libCall = spv::GLSLstd450Determinant;
break;
case glslang::EOpMatrixInverse:
libCall = spv::GLSLstd450MatrixInverse;
break;
case glslang::EOpTranspose:
unaryOp = spv::OpTranspose;
break;
case glslang::EOpRadians:
libCall = spv::GLSLstd450Radians;
break;
case glslang::EOpDegrees:
libCall = spv::GLSLstd450Degrees;
break;
case glslang::EOpSin:
libCall = spv::GLSLstd450Sin;
break;
case glslang::EOpCos:
libCall = spv::GLSLstd450Cos;
break;
case glslang::EOpTan:
libCall = spv::GLSLstd450Tan;
break;
case glslang::EOpAcos:
libCall = spv::GLSLstd450Acos;
break;
case glslang::EOpAsin:
libCall = spv::GLSLstd450Asin;
break;
case glslang::EOpAtan:
libCall = spv::GLSLstd450Atan;
break;
case glslang::EOpAcosh:
libCall = spv::GLSLstd450Acosh;
break;
case glslang::EOpAsinh:
libCall = spv::GLSLstd450Asinh;
break;
case glslang::EOpAtanh:
libCall = spv::GLSLstd450Atanh;
break;
case glslang::EOpTanh:
libCall = spv::GLSLstd450Tanh;
break;
case glslang::EOpCosh:
libCall = spv::GLSLstd450Cosh;
break;
case glslang::EOpSinh:
libCall = spv::GLSLstd450Sinh;
break;
case glslang::EOpLength:
libCall = spv::GLSLstd450Length;
break;
case glslang::EOpNormalize:
libCall = spv::GLSLstd450Normalize;
break;
case glslang::EOpExp:
libCall = spv::GLSLstd450Exp;
break;
case glslang::EOpLog:
libCall = spv::GLSLstd450Log;
break;
case glslang::EOpExp2:
libCall = spv::GLSLstd450Exp2;
break;
case glslang::EOpLog2:
libCall = spv::GLSLstd450Log2;
break;
case glslang::EOpSqrt:
libCall = spv::GLSLstd450Sqrt;
break;
case glslang::EOpInverseSqrt:
libCall = spv::GLSLstd450InverseSqrt;
break;
case glslang::EOpFloor:
libCall = spv::GLSLstd450Floor;
break;
case glslang::EOpTrunc:
libCall = spv::GLSLstd450Trunc;
break;
case glslang::EOpRound:
libCall = spv::GLSLstd450Round;
break;
case glslang::EOpRoundEven:
libCall = spv::GLSLstd450RoundEven;
break;
case glslang::EOpCeil:
libCall = spv::GLSLstd450Ceil;
break;
case glslang::EOpFract:
libCall = spv::GLSLstd450Fract;
break;
case glslang::EOpIsNan:
unaryOp = spv::OpIsNan;
break;
case glslang::EOpIsInf:
unaryOp = spv::OpIsInf;
break;
case glslang::EOpFloatBitsToInt:
case glslang::EOpFloatBitsToUint:
case glslang::EOpIntBitsToFloat:
case glslang::EOpUintBitsToFloat:
case glslang::EOpDoubleBitsToInt64:
case glslang::EOpDoubleBitsToUint64:
case glslang::EOpInt64BitsToDouble:
case glslang::EOpUint64BitsToDouble:
unaryOp = spv::OpBitcast;
break;
case glslang::EOpPackSnorm2x16:
libCall = spv::GLSLstd450PackSnorm2x16;
break;
case glslang::EOpUnpackSnorm2x16:
libCall = spv::GLSLstd450UnpackSnorm2x16;
break;
case glslang::EOpPackUnorm2x16:
libCall = spv::GLSLstd450PackUnorm2x16;
break;
case glslang::EOpUnpackUnorm2x16:
libCall = spv::GLSLstd450UnpackUnorm2x16;
break;
case glslang::EOpPackHalf2x16:
libCall = spv::GLSLstd450PackHalf2x16;
break;
case glslang::EOpUnpackHalf2x16:
libCall = spv::GLSLstd450UnpackHalf2x16;
break;
case glslang::EOpPackSnorm4x8:
libCall = spv::GLSLstd450PackSnorm4x8;
break;
case glslang::EOpUnpackSnorm4x8:
libCall = spv::GLSLstd450UnpackSnorm4x8;
break;
case glslang::EOpPackUnorm4x8:
libCall = spv::GLSLstd450PackUnorm4x8;
break;
case glslang::EOpUnpackUnorm4x8:
libCall = spv::GLSLstd450UnpackUnorm4x8;
break;
case glslang::EOpPackDouble2x32:
libCall = spv::GLSLstd450PackDouble2x32;
break;
case glslang::EOpUnpackDouble2x32:
libCall = spv::GLSLstd450UnpackDouble2x32;
break;
case glslang::EOpPackInt2x32:
case glslang::EOpUnpackInt2x32:
case glslang::EOpPackUint2x32:
case glslang::EOpUnpackUint2x32:
logger->missingFunctionality("shader int64");
libCall = spv::GLSLstd450Bad; // TODO: This is a placeholder.
break;
case glslang::EOpDPdx:
unaryOp = spv::OpDPdx;
break;
case glslang::EOpDPdy:
unaryOp = spv::OpDPdy;
break;
case glslang::EOpFwidth:
unaryOp = spv::OpFwidth;
break;
case glslang::EOpDPdxFine:
builder.addCapability(spv::CapabilityDerivativeControl);
unaryOp = spv::OpDPdxFine;
break;
case glslang::EOpDPdyFine:
builder.addCapability(spv::CapabilityDerivativeControl);
unaryOp = spv::OpDPdyFine;
break;
case glslang::EOpFwidthFine:
builder.addCapability(spv::CapabilityDerivativeControl);
unaryOp = spv::OpFwidthFine;
break;
case glslang::EOpDPdxCoarse:
builder.addCapability(spv::CapabilityDerivativeControl);
unaryOp = spv::OpDPdxCoarse;
break;
case glslang::EOpDPdyCoarse:
builder.addCapability(spv::CapabilityDerivativeControl);
unaryOp = spv::OpDPdyCoarse;
break;
case glslang::EOpFwidthCoarse:
builder.addCapability(spv::CapabilityDerivativeControl);
unaryOp = spv::OpFwidthCoarse;
break;
case glslang::EOpInterpolateAtCentroid:
builder.addCapability(spv::CapabilityInterpolationFunction);
libCall = spv::GLSLstd450InterpolateAtCentroid;
break;
case glslang::EOpAny:
unaryOp = spv::OpAny;
break;
case glslang::EOpAll:
unaryOp = spv::OpAll;
break;
case glslang::EOpAbs:
if (isFloat)
libCall = spv::GLSLstd450FAbs;
else
libCall = spv::GLSLstd450SAbs;
break;
case glslang::EOpSign:
if (isFloat)
libCall = spv::GLSLstd450FSign;
else
libCall = spv::GLSLstd450SSign;
break;
case glslang::EOpAtomicCounterIncrement:
case glslang::EOpAtomicCounterDecrement:
case glslang::EOpAtomicCounter:
{
// Handle all of the atomics in one place, in createAtomicOperation()
std::vector<spv::Id> operands;
operands.push_back(operand);
return createAtomicOperation(op, precision, typeId, operands, typeProxy);
}
case glslang::EOpBitFieldReverse:
unaryOp = spv::OpBitReverse;
break;
case glslang::EOpBitCount:
unaryOp = spv::OpBitCount;
break;
case glslang::EOpFindLSB:
libCall = spv::GLSLstd450FindILsb;
break;
case glslang::EOpFindMSB:
if (isUnsigned)
libCall = spv::GLSLstd450FindUMsb;
else
libCall = spv::GLSLstd450FindSMsb;
break;
case glslang::EOpBallot:
case glslang::EOpReadFirstInvocation:
logger->missingFunctionality("shader ballot");
libCall = spv::GLSLstd450Bad;
break;
case glslang::EOpAnyInvocation:
case glslang::EOpAllInvocations:
case glslang::EOpAllInvocationsEqual:
return createInvocationsOperation(op, typeId, operand);
default:
return 0;
}
spv::Id id;
if (libCall >= 0) {
std::vector<spv::Id> args;
args.push_back(operand);
id = builder.createBuiltinCall(typeId, stdBuiltins, libCall, args);
} else {
id = builder.createUnaryOp(unaryOp, typeId, operand);
}
addDecoration(id, noContraction);
return builder.setPrecision(id, precision);
}
// Create a unary operation on a matrix
spv::Id TGlslangToSpvTraverser::createUnaryMatrixOperation(spv::Op op, spv::Decoration precision, spv::Decoration noContraction, spv::Id typeId, spv::Id operand, glslang::TBasicType /* typeProxy */)
{
// Handle unary operations vector by vector.
// The result type is the same type as the original type.
// The algorithm is to:
// - break the matrix into vectors
// - apply the operation to each vector
// - make a matrix out the vector results
// get the types sorted out
int numCols = builder.getNumColumns(operand);
int numRows = builder.getNumRows(operand);
spv::Id scalarType = builder.getScalarTypeId(typeId);
spv::Id vecType = builder.makeVectorType(scalarType, numRows);
std::vector<spv::Id> results;
// do each vector op
for (int c = 0; c < numCols; ++c) {
std::vector<unsigned int> indexes;
indexes.push_back(c);
spv::Id vec = builder.createCompositeExtract(operand, vecType, indexes);
spv::Id vec_result = builder.createUnaryOp(op, vecType, vec);
addDecoration(vec_result, noContraction);
results.push_back(builder.setPrecision(vec_result, precision));
}
// put the pieces together
return builder.setPrecision(builder.createCompositeConstruct(typeId, results), precision);
}
spv::Id TGlslangToSpvTraverser::createConversion(glslang::TOperator op, spv::Decoration precision, spv::Decoration noContraction, spv::Id destType, spv::Id operand, glslang::TBasicType typeProxy)
{
spv::Op convOp = spv::OpNop;
spv::Id zero = 0;
spv::Id one = 0;
spv::Id type = 0;
int vectorSize = builder.isVectorType(destType) ? builder.getNumTypeComponents(destType) : 0;
switch (op) {
case glslang::EOpConvIntToBool:
case glslang::EOpConvUintToBool:
case glslang::EOpConvInt64ToBool:
case glslang::EOpConvUint64ToBool:
zero = (op == glslang::EOpConvInt64ToBool ||
op == glslang::EOpConvUint64ToBool) ? builder.makeUint64Constant(0) : builder.makeUintConstant(0);
zero = makeSmearedConstant(zero, vectorSize);
return builder.createBinOp(spv::OpINotEqual, destType, operand, zero);
case glslang::EOpConvFloatToBool:
zero = builder.makeFloatConstant(0.0F);
zero = makeSmearedConstant(zero, vectorSize);
return builder.createBinOp(spv::OpFOrdNotEqual, destType, operand, zero);
case glslang::EOpConvDoubleToBool:
zero = builder.makeDoubleConstant(0.0);
zero = makeSmearedConstant(zero, vectorSize);
return builder.createBinOp(spv::OpFOrdNotEqual, destType, operand, zero);
case glslang::EOpConvBoolToFloat:
convOp = spv::OpSelect;
zero = builder.makeFloatConstant(0.0);
one = builder.makeFloatConstant(1.0);
break;
case glslang::EOpConvBoolToDouble:
convOp = spv::OpSelect;
zero = builder.makeDoubleConstant(0.0);
one = builder.makeDoubleConstant(1.0);
break;
case glslang::EOpConvBoolToInt:
case glslang::EOpConvBoolToInt64:
zero = (op == glslang::EOpConvBoolToInt64) ? builder.makeInt64Constant(0) : builder.makeIntConstant(0);
one = (op == glslang::EOpConvBoolToInt64) ? builder.makeInt64Constant(1) : builder.makeIntConstant(1);
convOp = spv::OpSelect;
break;
case glslang::EOpConvBoolToUint:
case glslang::EOpConvBoolToUint64:
zero = (op == glslang::EOpConvBoolToUint64) ? builder.makeUint64Constant(0) : builder.makeUintConstant(0);
one = (op == glslang::EOpConvBoolToUint64) ? builder.makeUint64Constant(1) : builder.makeUintConstant(1);
convOp = spv::OpSelect;
break;
case glslang::EOpConvIntToFloat:
case glslang::EOpConvIntToDouble:
case glslang::EOpConvInt64ToFloat:
case glslang::EOpConvInt64ToDouble:
convOp = spv::OpConvertSToF;
break;
case glslang::EOpConvUintToFloat:
case glslang::EOpConvUintToDouble:
case glslang::EOpConvUint64ToFloat:
case glslang::EOpConvUint64ToDouble:
convOp = spv::OpConvertUToF;
break;
case glslang::EOpConvDoubleToFloat:
case glslang::EOpConvFloatToDouble:
convOp = spv::OpFConvert;
if (builder.isMatrixType(destType))
return createUnaryMatrixOperation(convOp, precision, noContraction, destType, operand, typeProxy);
break;
case glslang::EOpConvFloatToInt:
case glslang::EOpConvDoubleToInt:
case glslang::EOpConvFloatToInt64:
case glslang::EOpConvDoubleToInt64:
convOp = spv::OpConvertFToS;
break;
case glslang::EOpConvUintToInt:
case glslang::EOpConvIntToUint:
case glslang::EOpConvUint64ToInt64:
case glslang::EOpConvInt64ToUint64:
if (builder.isInSpecConstCodeGenMode()) {
// Build zero scalar or vector for OpIAdd.
zero = (op == glslang::EOpConvUintToInt64 ||
op == glslang::EOpConvIntToUint64) ? builder.makeUint64Constant(0) : builder.makeUintConstant(0);
zero = makeSmearedConstant(zero, vectorSize);
// Use OpIAdd, instead of OpBitcast to do the conversion when
// generating for OpSpecConstantOp instruction.
return builder.createBinOp(spv::OpIAdd, destType, operand, zero);
}
// For normal run-time conversion instruction, use OpBitcast.
convOp = spv::OpBitcast;
break;
case glslang::EOpConvFloatToUint:
case glslang::EOpConvDoubleToUint:
case glslang::EOpConvFloatToUint64:
case glslang::EOpConvDoubleToUint64:
convOp = spv::OpConvertFToU;
break;
case glslang::EOpConvIntToInt64:
case glslang::EOpConvInt64ToInt:
convOp = spv::OpSConvert;
break;
case glslang::EOpConvUintToUint64:
case glslang::EOpConvUint64ToUint:
convOp = spv::OpUConvert;
break;
case glslang::EOpConvIntToUint64:
case glslang::EOpConvInt64ToUint:
case glslang::EOpConvUint64ToInt:
case glslang::EOpConvUintToInt64:
// OpSConvert/OpUConvert + OpBitCast
switch (op) {
case glslang::EOpConvIntToUint64:
convOp = spv::OpSConvert;
type = builder.makeIntType(64);
break;
case glslang::EOpConvInt64ToUint:
convOp = spv::OpSConvert;
type = builder.makeIntType(32);
break;
case glslang::EOpConvUint64ToInt:
convOp = spv::OpUConvert;
type = builder.makeUintType(32);
break;
case glslang::EOpConvUintToInt64:
convOp = spv::OpUConvert;
type = builder.makeUintType(64);
break;
default:
assert(0);
break;
}
if (vectorSize > 0)
type = builder.makeVectorType(type, vectorSize);
operand = builder.createUnaryOp(convOp, type, operand);
if (builder.isInSpecConstCodeGenMode()) {
// Build zero scalar or vector for OpIAdd.
zero = (op == glslang::EOpConvIntToUint64 ||
op == glslang::EOpConvUintToInt64) ? builder.makeUint64Constant(0) : builder.makeUintConstant(0);
zero = makeSmearedConstant(zero, vectorSize);
// Use OpIAdd, instead of OpBitcast to do the conversion when
// generating for OpSpecConstantOp instruction.
return builder.createBinOp(spv::OpIAdd, destType, operand, zero);
}
// For normal run-time conversion instruction, use OpBitcast.
convOp = spv::OpBitcast;
break;
default:
break;
}
spv::Id result = 0;
if (convOp == spv::OpNop)
return result;
if (convOp == spv::OpSelect) {
zero = makeSmearedConstant(zero, vectorSize);
one = makeSmearedConstant(one, vectorSize);
result = builder.createTriOp(convOp, destType, operand, one, zero);
} else
result = builder.createUnaryOp(convOp, destType, operand);
return builder.setPrecision(result, precision);
}
spv::Id TGlslangToSpvTraverser::makeSmearedConstant(spv::Id constant, int vectorSize)
{
if (vectorSize == 0)
return constant;
spv::Id vectorTypeId = builder.makeVectorType(builder.getTypeId(constant), vectorSize);
std::vector<spv::Id> components;
for (int c = 0; c < vectorSize; ++c)
components.push_back(constant);
return builder.makeCompositeConstant(vectorTypeId, components);
}
// For glslang ops that map to SPV atomic opCodes
spv::Id TGlslangToSpvTraverser::createAtomicOperation(glslang::TOperator op, spv::Decoration /*precision*/, spv::Id typeId, std::vector<spv::Id>& operands, glslang::TBasicType typeProxy)
{
spv::Op opCode = spv::OpNop;
switch (op) {
case glslang::EOpAtomicAdd:
case glslang::EOpImageAtomicAdd:
opCode = spv::OpAtomicIAdd;
break;
case glslang::EOpAtomicMin:
case glslang::EOpImageAtomicMin:
opCode = typeProxy == glslang::EbtUint ? spv::OpAtomicUMin : spv::OpAtomicSMin;
break;
case glslang::EOpAtomicMax:
case glslang::EOpImageAtomicMax:
opCode = typeProxy == glslang::EbtUint ? spv::OpAtomicUMax : spv::OpAtomicSMax;
break;
case glslang::EOpAtomicAnd:
case glslang::EOpImageAtomicAnd:
opCode = spv::OpAtomicAnd;
break;
case glslang::EOpAtomicOr:
case glslang::EOpImageAtomicOr:
opCode = spv::OpAtomicOr;
break;
case glslang::EOpAtomicXor:
case glslang::EOpImageAtomicXor:
opCode = spv::OpAtomicXor;
break;
case glslang::EOpAtomicExchange:
case glslang::EOpImageAtomicExchange:
opCode = spv::OpAtomicExchange;
break;
case glslang::EOpAtomicCompSwap:
case glslang::EOpImageAtomicCompSwap:
opCode = spv::OpAtomicCompareExchange;
break;
case glslang::EOpAtomicCounterIncrement:
opCode = spv::OpAtomicIIncrement;
break;
case glslang::EOpAtomicCounterDecrement:
opCode = spv::OpAtomicIDecrement;
break;
case glslang::EOpAtomicCounter:
opCode = spv::OpAtomicLoad;
break;
default:
assert(0);
break;
}
// Sort out the operands
// - mapping from glslang -> SPV
// - there are extra SPV operands with no glslang source
// - compare-exchange swaps the value and comparator
// - compare-exchange has an extra memory semantics
std::vector<spv::Id> spvAtomicOperands; // hold the spv operands
auto opIt = operands.begin(); // walk the glslang operands
spvAtomicOperands.push_back(*(opIt++));
spvAtomicOperands.push_back(builder.makeUintConstant(spv::ScopeDevice)); // TBD: what is the correct scope?
spvAtomicOperands.push_back(builder.makeUintConstant(spv::MemorySemanticsMaskNone)); // TBD: what are the correct memory semantics?
if (opCode == spv::OpAtomicCompareExchange) {
// There are 2 memory semantics for compare-exchange. And the operand order of "comparator" and "new value" in GLSL
// differs from that in SPIR-V. Hence, special processing is required.
spvAtomicOperands.push_back(builder.makeUintConstant(spv::MemorySemanticsMaskNone));
spvAtomicOperands.push_back(*(opIt + 1));
spvAtomicOperands.push_back(*opIt);
opIt += 2;
}
// Add the rest of the operands, skipping any that were dealt with above.
for (; opIt != operands.end(); ++opIt)
spvAtomicOperands.push_back(*opIt);
return builder.createOp(opCode, typeId, spvAtomicOperands);
}
// Create group invocation operations.
spv::Id TGlslangToSpvTraverser::createInvocationsOperation(glslang::TOperator op, spv::Id typeId, spv::Id operand)
{
builder.addCapability(spv::CapabilityGroups);
std::vector<spv::Id> operands;
operands.push_back(builder.makeUintConstant(spv::ScopeSubgroup));
operands.push_back(operand);
switch (op) {
case glslang::EOpAnyInvocation:
case glslang::EOpAllInvocations:
return builder.createOp(op == glslang::EOpAnyInvocation ? spv::OpGroupAny : spv::OpGroupAll, typeId, operands);
case glslang::EOpAllInvocationsEqual:
{
spv::Id groupAll = builder.createOp(spv::OpGroupAll, typeId, operands);
spv::Id groupAny = builder.createOp(spv::OpGroupAny, typeId, operands);
return builder.createBinOp(spv::OpLogicalOr, typeId, groupAll,
builder.createUnaryOp(spv::OpLogicalNot, typeId, groupAny));
}
default:
logger->missingFunctionality("invocation operation");
return spv::NoResult;
}
}
spv::Id TGlslangToSpvTraverser::createMiscOperation(glslang::TOperator op, spv::Decoration precision, spv::Id typeId, std::vector<spv::Id>& operands, glslang::TBasicType typeProxy)
{
bool isUnsigned = typeProxy == glslang::EbtUint || typeProxy == glslang::EbtUint64;
bool isFloat = typeProxy == glslang::EbtFloat || typeProxy == glslang::EbtDouble;
spv::Op opCode = spv::OpNop;
int libCall = -1;
size_t consumedOperands = operands.size();
spv::Id typeId0 = 0;
if (consumedOperands > 0)
typeId0 = builder.getTypeId(operands[0]);
spv::Id frexpIntType = 0;
switch (op) {
case glslang::EOpMin:
if (isFloat)
libCall = spv::GLSLstd450FMin;
else if (isUnsigned)
libCall = spv::GLSLstd450UMin;
else
libCall = spv::GLSLstd450SMin;
builder.promoteScalar(precision, operands.front(), operands.back());
break;
case glslang::EOpModf:
libCall = spv::GLSLstd450Modf;
break;
case glslang::EOpMax:
if (isFloat)
libCall = spv::GLSLstd450FMax;
else if (isUnsigned)
libCall = spv::GLSLstd450UMax;
else
libCall = spv::GLSLstd450SMax;
builder.promoteScalar(precision, operands.front(), operands.back());
break;
case glslang::EOpPow:
libCall = spv::GLSLstd450Pow;
break;
case glslang::EOpDot:
opCode = spv::OpDot;
break;
case glslang::EOpAtan:
libCall = spv::GLSLstd450Atan2;
break;
case glslang::EOpClamp:
if (isFloat)
libCall = spv::GLSLstd450FClamp;
else if (isUnsigned)
libCall = spv::GLSLstd450UClamp;
else
libCall = spv::GLSLstd450SClamp;
builder.promoteScalar(precision, operands.front(), operands[1]);
builder.promoteScalar(precision, operands.front(), operands[2]);
break;
case glslang::EOpMix:
if (! builder.isBoolType(builder.getScalarTypeId(builder.getTypeId(operands.back())))) {
assert(isFloat);
libCall = spv::GLSLstd450FMix;
} else {
opCode = spv::OpSelect;
std::swap(operands.front(), operands.back());
}
builder.promoteScalar(precision, operands.front(), operands.back());
break;
case glslang::EOpStep:
libCall = spv::GLSLstd450Step;
builder.promoteScalar(precision, operands.front(), operands.back());
break;
case glslang::EOpSmoothStep:
libCall = spv::GLSLstd450SmoothStep;
builder.promoteScalar(precision, operands[0], operands[2]);
builder.promoteScalar(precision, operands[1], operands[2]);
break;
case glslang::EOpDistance:
libCall = spv::GLSLstd450Distance;
break;
case glslang::EOpCross:
libCall = spv::GLSLstd450Cross;
break;
case glslang::EOpFaceForward:
libCall = spv::GLSLstd450FaceForward;
break;
case glslang::EOpReflect:
libCall = spv::GLSLstd450Reflect;
break;
case glslang::EOpRefract:
libCall = spv::GLSLstd450Refract;
break;
case glslang::EOpInterpolateAtSample:
builder.addCapability(spv::CapabilityInterpolationFunction);
libCall = spv::GLSLstd450InterpolateAtSample;
break;
case glslang::EOpInterpolateAtOffset:
builder.addCapability(spv::CapabilityInterpolationFunction);
libCall = spv::GLSLstd450InterpolateAtOffset;
break;
case glslang::EOpAddCarry:
opCode = spv::OpIAddCarry;
typeId = builder.makeStructResultType(typeId0, typeId0);
consumedOperands = 2;
break;
case glslang::EOpSubBorrow:
opCode = spv::OpISubBorrow;
typeId = builder.makeStructResultType(typeId0, typeId0);
consumedOperands = 2;
break;
case glslang::EOpUMulExtended:
opCode = spv::OpUMulExtended;
typeId = builder.makeStructResultType(typeId0, typeId0);
consumedOperands = 2;
break;
case glslang::EOpIMulExtended:
opCode = spv::OpSMulExtended;
typeId = builder.makeStructResultType(typeId0, typeId0);
consumedOperands = 2;
break;
case glslang::EOpBitfieldExtract:
if (isUnsigned)
opCode = spv::OpBitFieldUExtract;
else
opCode = spv::OpBitFieldSExtract;
break;
case glslang::EOpBitfieldInsert:
opCode = spv::OpBitFieldInsert;
break;
case glslang::EOpFma:
libCall = spv::GLSLstd450Fma;
break;
case glslang::EOpFrexp:
libCall = spv::GLSLstd450FrexpStruct;
if (builder.getNumComponents(operands[0]) == 1)
frexpIntType = builder.makeIntegerType(32, true);
else
frexpIntType = builder.makeVectorType(builder.makeIntegerType(32, true), builder.getNumComponents(operands[0]));
typeId = builder.makeStructResultType(typeId0, frexpIntType);
consumedOperands = 1;
break;
case glslang::EOpLdexp:
libCall = spv::GLSLstd450Ldexp;
break;
case glslang::EOpReadInvocation:
logger->missingFunctionality("shader ballot");
libCall = spv::GLSLstd450Bad;
break;
default:
return 0;
}
spv::Id id = 0;
if (libCall >= 0) {
// Use an extended instruction from the standard library.
// Construct the call arguments, without modifying the original operands vector.
// We might need the remaining arguments, e.g. in the EOpFrexp case.
std::vector<spv::Id> callArguments(operands.begin(), operands.begin() + consumedOperands);
id = builder.createBuiltinCall(typeId, stdBuiltins, libCall, callArguments);
} else {
switch (consumedOperands) {
case 0:
// should all be handled by visitAggregate and createNoArgOperation
assert(0);
return 0;
case 1:
// should all be handled by createUnaryOperation
assert(0);
return 0;
case 2:
id = builder.createBinOp(opCode, typeId, operands[0], operands[1]);
break;
default:
// anything 3 or over doesn't have l-value operands, so all should be consumed
assert(consumedOperands == operands.size());
id = builder.createOp(opCode, typeId, operands);
break;
}
}
// Decode the return types that were structures
switch (op) {
case glslang::EOpAddCarry:
case glslang::EOpSubBorrow:
builder.createStore(builder.createCompositeExtract(id, typeId0, 1), operands[2]);
id = builder.createCompositeExtract(id, typeId0, 0);
break;
case glslang::EOpUMulExtended:
case glslang::EOpIMulExtended:
builder.createStore(builder.createCompositeExtract(id, typeId0, 0), operands[3]);
builder.createStore(builder.createCompositeExtract(id, typeId0, 1), operands[2]);
break;
case glslang::EOpFrexp:
assert(operands.size() == 2);
builder.createStore(builder.createCompositeExtract(id, frexpIntType, 1), operands[1]);
id = builder.createCompositeExtract(id, typeId0, 0);
break;
default:
break;
}
return builder.setPrecision(id, precision);
}
// Intrinsics with no arguments, no return value, and no precision.
spv::Id TGlslangToSpvTraverser::createNoArgOperation(glslang::TOperator op)
{
// TODO: get the barrier operands correct
switch (op) {
case glslang::EOpEmitVertex:
builder.createNoResultOp(spv::OpEmitVertex);
return 0;
case glslang::EOpEndPrimitive:
builder.createNoResultOp(spv::OpEndPrimitive);
return 0;
case glslang::EOpBarrier:
builder.createMemoryBarrier(spv::ScopeDevice, spv::MemorySemanticsAllMemory);
builder.createControlBarrier(spv::ScopeDevice, spv::ScopeDevice, spv::MemorySemanticsMaskNone);
return 0;
case glslang::EOpMemoryBarrier:
builder.createMemoryBarrier(spv::ScopeDevice, spv::MemorySemanticsAllMemory);
return 0;
case glslang::EOpMemoryBarrierAtomicCounter:
builder.createMemoryBarrier(spv::ScopeDevice, spv::MemorySemanticsAtomicCounterMemoryMask);
return 0;
case glslang::EOpMemoryBarrierBuffer:
builder.createMemoryBarrier(spv::ScopeDevice, spv::MemorySemanticsUniformMemoryMask);
return 0;
case glslang::EOpMemoryBarrierImage:
builder.createMemoryBarrier(spv::ScopeDevice, spv::MemorySemanticsImageMemoryMask);
return 0;
case glslang::EOpMemoryBarrierShared:
builder.createMemoryBarrier(spv::ScopeDevice, spv::MemorySemanticsWorkgroupMemoryMask);
return 0;
case glslang::EOpGroupMemoryBarrier:
builder.createMemoryBarrier(spv::ScopeDevice, spv::MemorySemanticsCrossWorkgroupMemoryMask);
return 0;
default:
logger->missingFunctionality("unknown operation with no arguments");
return 0;
}
}
spv::Id TGlslangToSpvTraverser::getSymbolId(const glslang::TIntermSymbol* symbol)
{
auto iter = symbolValues.find(symbol->getId());
spv::Id id;
if (symbolValues.end() != iter) {
id = iter->second;
return id;
}
// it was not found, create it
id = createSpvVariable(symbol);
symbolValues[symbol->getId()] = id;
if (! symbol->getType().isStruct()) {
addDecoration(id, TranslatePrecisionDecoration(symbol->getType()));
addDecoration(id, TranslateInterpolationDecoration(symbol->getType().getQualifier()));
if (symbol->getType().getQualifier().hasSpecConstantId())
addDecoration(id, spv::DecorationSpecId, symbol->getType().getQualifier().layoutSpecConstantId);
if (symbol->getQualifier().hasLocation())
builder.addDecoration(id, spv::DecorationLocation, symbol->getQualifier().layoutLocation);
if (symbol->getQualifier().hasIndex())
builder.addDecoration(id, spv::DecorationIndex, symbol->getQualifier().layoutIndex);
if (symbol->getQualifier().hasComponent())
builder.addDecoration(id, spv::DecorationComponent, symbol->getQualifier().layoutComponent);
if (glslangIntermediate->getXfbMode()) {
builder.addCapability(spv::CapabilityTransformFeedback);
if (symbol->getQualifier().hasXfbStride())
builder.addDecoration(id, spv::DecorationXfbStride, symbol->getQualifier().layoutXfbStride);
if (symbol->getQualifier().hasXfbBuffer())
builder.addDecoration(id, spv::DecorationXfbBuffer, symbol->getQualifier().layoutXfbBuffer);
if (symbol->getQualifier().hasXfbOffset())
builder.addDecoration(id, spv::DecorationOffset, symbol->getQualifier().layoutXfbOffset);
}
}
addDecoration(id, TranslateInvariantDecoration(symbol->getType().getQualifier()));
if (symbol->getQualifier().hasStream() && glslangIntermediate->isMultiStream()) {
builder.addCapability(spv::CapabilityGeometryStreams);
builder.addDecoration(id, spv::DecorationStream, symbol->getQualifier().layoutStream);
}
if (symbol->getQualifier().hasSet())
builder.addDecoration(id, spv::DecorationDescriptorSet, symbol->getQualifier().layoutSet);
else if (IsDescriptorResource(symbol->getType())) {
// default to 0
builder.addDecoration(id, spv::DecorationDescriptorSet, 0);
}
if (symbol->getQualifier().hasBinding())
builder.addDecoration(id, spv::DecorationBinding, symbol->getQualifier().layoutBinding);
if (symbol->getQualifier().hasAttachment())
builder.addDecoration(id, spv::DecorationInputAttachmentIndex, symbol->getQualifier().layoutAttachment);
if (glslangIntermediate->getXfbMode()) {
builder.addCapability(spv::CapabilityTransformFeedback);
if (symbol->getQualifier().hasXfbStride())
builder.addDecoration(id, spv::DecorationXfbStride, symbol->getQualifier().layoutXfbStride);
if (symbol->getQualifier().hasXfbBuffer())
builder.addDecoration(id, spv::DecorationXfbBuffer, symbol->getQualifier().layoutXfbBuffer);
}
if (symbol->getType().isImage()) {
std::vector<spv::Decoration> memory;
TranslateMemoryDecoration(symbol->getType().getQualifier(), memory);
for (unsigned int i = 0; i < memory.size(); ++i)
addDecoration(id, memory[i]);
}
// built-in variable decorations
spv::BuiltIn builtIn = TranslateBuiltInDecoration(symbol->getQualifier().builtIn, false);
if (builtIn != spv::BadValue)
addDecoration(id, spv::DecorationBuiltIn, (int)builtIn);
return id;
}
// If 'dec' is valid, add no-operand decoration to an object
void TGlslangToSpvTraverser::addDecoration(spv::Id id, spv::Decoration dec)
{
if (dec != spv::BadValue)
builder.addDecoration(id, dec);
}
// If 'dec' is valid, add a one-operand decoration to an object
void TGlslangToSpvTraverser::addDecoration(spv::Id id, spv::Decoration dec, unsigned value)
{
if (dec != spv::BadValue)
builder.addDecoration(id, dec, value);
}
// If 'dec' is valid, add a no-operand decoration to a struct member
void TGlslangToSpvTraverser::addMemberDecoration(spv::Id id, int member, spv::Decoration dec)
{
if (dec != spv::BadValue)
builder.addMemberDecoration(id, (unsigned)member, dec);
}
// If 'dec' is valid, add a one-operand decoration to a struct member
void TGlslangToSpvTraverser::addMemberDecoration(spv::Id id, int member, spv::Decoration dec, unsigned value)
{
if (dec != spv::BadValue)
builder.addMemberDecoration(id, (unsigned)member, dec, value);
}
// Make a full tree of instructions to build a SPIR-V specialization constant,
// or regular constant if possible.
//
// TBD: this is not yet done, nor verified to be the best design, it does do the leaf symbols though
//
// Recursively walk the nodes. The nodes form a tree whose leaves are
// regular constants, which themselves are trees that createSpvConstant()
// recursively walks. So, this function walks the "top" of the tree:
// - emit specialization constant-building instructions for specConstant
// - when running into a non-spec-constant, switch to createSpvConstant()
spv::Id TGlslangToSpvTraverser::createSpvConstant(const glslang::TIntermTyped& node)
{
assert(node.getQualifier().isConstant());
// Handle front-end constants first (non-specialization constants).
if (! node.getQualifier().specConstant) {
// hand off to the non-spec-constant path
assert(node.getAsConstantUnion() != nullptr || node.getAsSymbolNode() != nullptr);
int nextConst = 0;
return createSpvConstantFromConstUnionArray(node.getType(), node.getAsConstantUnion() ? node.getAsConstantUnion()->getConstArray() : node.getAsSymbolNode()->getConstArray(),
nextConst, false);
}
// We now know we have a specialization constant to build
// gl_WorkgroupSize is a special case until the front-end handles hierarchical specialization constants,
// even then, it's specialization ids are handled by special case syntax in GLSL: layout(local_size_x = ...
if (node.getType().getQualifier().builtIn == glslang::EbvWorkGroupSize) {
std::vector<spv::Id> dimConstId;
for (int dim = 0; dim < 3; ++dim) {
bool specConst = (glslangIntermediate->getLocalSizeSpecId(dim) != glslang::TQualifier::layoutNotSet);
dimConstId.push_back(builder.makeUintConstant(glslangIntermediate->getLocalSize(dim), specConst));
if (specConst)
addDecoration(dimConstId.back(), spv::DecorationSpecId, glslangIntermediate->getLocalSizeSpecId(dim));
}
return builder.makeCompositeConstant(builder.makeVectorType(builder.makeUintType(32), 3), dimConstId, true);
}
// An AST node labelled as specialization constant should be a symbol node.
// Its initializer should either be a sub tree with constant nodes, or a constant union array.
if (auto* sn = node.getAsSymbolNode()) {
if (auto* sub_tree = sn->getConstSubtree()) {
// Traverse the constant constructor sub tree like generating normal run-time instructions.
// During the AST traversal, if the node is marked as 'specConstant', SpecConstantOpModeGuard
// will set the builder into spec constant op instruction generating mode.
sub_tree->traverse(this);
return accessChainLoad(sub_tree->getType());
} else if (auto* const_union_array = &sn->getConstArray()){
int nextConst = 0;
return createSpvConstantFromConstUnionArray(sn->getType(), *const_union_array, nextConst, true);
}
}
// Neither a front-end constant node, nor a specialization constant node with constant union array or
// constant sub tree as initializer.
logger->missingFunctionality("Neither a front-end constant nor a spec constant.");
exit(1);
return spv::NoResult;
}
// Use 'consts' as the flattened glslang source of scalar constants to recursively
// build the aggregate SPIR-V constant.
//
// If there are not enough elements present in 'consts', 0 will be substituted;
// an empty 'consts' can be used to create a fully zeroed SPIR-V constant.
//
spv::Id TGlslangToSpvTraverser::createSpvConstantFromConstUnionArray(const glslang::TType& glslangType, const glslang::TConstUnionArray& consts, int& nextConst, bool specConstant)
{
// vector of constants for SPIR-V
std::vector<spv::Id> spvConsts;
// Type is used for struct and array constants
spv::Id typeId = convertGlslangToSpvType(glslangType);
if (glslangType.isArray()) {
glslang::TType elementType(glslangType, 0);
for (int i = 0; i < glslangType.getOuterArraySize(); ++i)
spvConsts.push_back(createSpvConstantFromConstUnionArray(elementType, consts, nextConst, false));
} else if (glslangType.isMatrix()) {
glslang::TType vectorType(glslangType, 0);
for (int col = 0; col < glslangType.getMatrixCols(); ++col)
spvConsts.push_back(createSpvConstantFromConstUnionArray(vectorType, consts, nextConst, false));
} else if (glslangType.getStruct()) {
glslang::TVector<glslang::TTypeLoc>::const_iterator iter;
for (iter = glslangType.getStruct()->begin(); iter != glslangType.getStruct()->end(); ++iter)
spvConsts.push_back(createSpvConstantFromConstUnionArray(*iter->type, consts, nextConst, false));
} else if (glslangType.isVector()) {
for (unsigned int i = 0; i < (unsigned int)glslangType.getVectorSize(); ++i) {
bool zero = nextConst >= consts.size();
switch (glslangType.getBasicType()) {
case glslang::EbtInt:
spvConsts.push_back(builder.makeIntConstant(zero ? 0 : consts[nextConst].getIConst()));
break;
case glslang::EbtUint:
spvConsts.push_back(builder.makeUintConstant(zero ? 0 : consts[nextConst].getUConst()));
break;
case glslang::EbtInt64:
spvConsts.push_back(builder.makeInt64Constant(zero ? 0 : consts[nextConst].getI64Const()));
break;
case glslang::EbtUint64:
spvConsts.push_back(builder.makeUint64Constant(zero ? 0 : consts[nextConst].getU64Const()));
break;
case glslang::EbtFloat:
spvConsts.push_back(builder.makeFloatConstant(zero ? 0.0F : (float)consts[nextConst].getDConst()));
break;
case glslang::EbtDouble:
spvConsts.push_back(builder.makeDoubleConstant(zero ? 0.0 : consts[nextConst].getDConst()));
break;
case glslang::EbtBool:
spvConsts.push_back(builder.makeBoolConstant(zero ? false : consts[nextConst].getBConst()));
break;
default:
assert(0);
break;
}
++nextConst;
}
} else {
// we have a non-aggregate (scalar) constant
bool zero = nextConst >= consts.size();
spv::Id scalar = 0;
switch (glslangType.getBasicType()) {
case glslang::EbtInt:
scalar = builder.makeIntConstant(zero ? 0 : consts[nextConst].getIConst(), specConstant);
break;
case glslang::EbtUint:
scalar = builder.makeUintConstant(zero ? 0 : consts[nextConst].getUConst(), specConstant);
break;
case glslang::EbtInt64:
scalar = builder.makeInt64Constant(zero ? 0 : consts[nextConst].getI64Const(), specConstant);
break;
case glslang::EbtUint64:
scalar = builder.makeUint64Constant(zero ? 0 : consts[nextConst].getU64Const(), specConstant);
break;
case glslang::EbtFloat:
scalar = builder.makeFloatConstant(zero ? 0.0F : (float)consts[nextConst].getDConst(), specConstant);
break;
case glslang::EbtDouble:
scalar = builder.makeDoubleConstant(zero ? 0.0 : consts[nextConst].getDConst(), specConstant);
break;
case glslang::EbtBool:
scalar = builder.makeBoolConstant(zero ? false : consts[nextConst].getBConst(), specConstant);
break;
default:
assert(0);
break;
}
++nextConst;
return scalar;
}
return builder.makeCompositeConstant(typeId, spvConsts);
}
// Return true if the node is a constant or symbol whose reading has no
// non-trivial observable cost or effect.
bool TGlslangToSpvTraverser::isTrivialLeaf(const glslang::TIntermTyped* node)
{
// don't know what this is
if (node == nullptr)
return false;
// a constant is safe
if (node->getAsConstantUnion() != nullptr)
return true;
// not a symbol means non-trivial
if (node->getAsSymbolNode() == nullptr)
return false;
// a symbol, depends on what's being read
switch (node->getType().getQualifier().storage) {
case glslang::EvqTemporary:
case glslang::EvqGlobal:
case glslang::EvqIn:
case glslang::EvqInOut:
case glslang::EvqConst:
case glslang::EvqConstReadOnly:
case glslang::EvqUniform:
return true;
default:
return false;
}
}
// A node is trivial if it is a single operation with no side effects.
// Error on the side of saying non-trivial.
// Return true if trivial.
bool TGlslangToSpvTraverser::isTrivial(const glslang::TIntermTyped* node)
{
if (node == nullptr)
return false;
// symbols and constants are trivial
if (isTrivialLeaf(node))
return true;
// otherwise, it needs to be a simple operation or one or two leaf nodes
// not a simple operation
const glslang::TIntermBinary* binaryNode = node->getAsBinaryNode();
const glslang::TIntermUnary* unaryNode = node->getAsUnaryNode();
if (binaryNode == nullptr && unaryNode == nullptr)
return false;
// not on leaf nodes
if (binaryNode && (! isTrivialLeaf(binaryNode->getLeft()) || ! isTrivialLeaf(binaryNode->getRight())))
return false;
if (unaryNode && ! isTrivialLeaf(unaryNode->getOperand())) {
return false;
}
switch (node->getAsOperator()->getOp()) {
case glslang::EOpLogicalNot:
case glslang::EOpConvIntToBool:
case glslang::EOpConvUintToBool:
case glslang::EOpConvFloatToBool:
case glslang::EOpConvDoubleToBool:
case glslang::EOpEqual:
case glslang::EOpNotEqual:
case glslang::EOpLessThan:
case glslang::EOpGreaterThan:
case glslang::EOpLessThanEqual:
case glslang::EOpGreaterThanEqual:
case glslang::EOpIndexDirect:
case glslang::EOpIndexDirectStruct:
case glslang::EOpLogicalXor:
case glslang::EOpAny:
case glslang::EOpAll:
return true;
default:
return false;
}
}
// Emit short-circuiting code, where 'right' is never evaluated unless
// the left side is true (for &&) or false (for ||).
spv::Id TGlslangToSpvTraverser::createShortCircuit(glslang::TOperator op, glslang::TIntermTyped& left, glslang::TIntermTyped& right)
{
spv::Id boolTypeId = builder.makeBoolType();
// emit left operand
builder.clearAccessChain();
left.traverse(this);
spv::Id leftId = accessChainLoad(left.getType());
// Operands to accumulate OpPhi operands
std::vector<spv::Id> phiOperands;
// accumulate left operand's phi information
phiOperands.push_back(leftId);
phiOperands.push_back(builder.getBuildPoint()->getId());
// Make the two kinds of operation symmetric with a "!"
// || => emit "if (! left) result = right"
// && => emit "if ( left) result = right"
//
// TODO: this runtime "not" for || could be avoided by adding functionality
// to 'builder' to have an "else" without an "then"
if (op == glslang::EOpLogicalOr)
leftId = builder.createUnaryOp(spv::OpLogicalNot, boolTypeId, leftId);
// make an "if" based on the left value
spv::Builder::If ifBuilder(leftId, builder);
// emit right operand as the "then" part of the "if"
builder.clearAccessChain();
right.traverse(this);
spv::Id rightId = accessChainLoad(right.getType());
// accumulate left operand's phi information
phiOperands.push_back(rightId);
phiOperands.push_back(builder.getBuildPoint()->getId());
// finish the "if"
ifBuilder.makeEndIf();
// phi together the two results
return builder.createOp(spv::OpPhi, boolTypeId, phiOperands);
}
}; // end anonymous namespace
namespace glslang {
void GetSpirvVersion(std::string& version)
{
const int bufSize = 100;
char buf[bufSize];
snprintf(buf, bufSize, "0x%08x, Revision %d", spv::Version, spv::Revision);
version = buf;
}
// Write SPIR-V out to a binary file
void OutputSpv(const std::vector<unsigned int>& spirv, const char* baseName)
{
std::ofstream out;
out.open(baseName, std::ios::binary | std::ios::out);
for (int i = 0; i < (int)spirv.size(); ++i) {
unsigned int word = spirv[i];
out.write((const char*)&word, 4);
}
out.close();
}
//
// Set up the glslang traversal
//
void GlslangToSpv(const glslang::TIntermediate& intermediate, std::vector<unsigned int>& spirv)
{
spv::SpvBuildLogger logger;
GlslangToSpv(intermediate, spirv, &logger);
}
void GlslangToSpv(const glslang::TIntermediate& intermediate, std::vector<unsigned int>& spirv, spv::SpvBuildLogger* logger)
{
TIntermNode* root = intermediate.getTreeRoot();
if (root == 0)
return;
glslang::GetThreadPoolAllocator().push();
TGlslangToSpvTraverser it(&intermediate, logger);
root->traverse(&it);
it.dumpSpv(spirv);
glslang::GetThreadPoolAllocator().pop();
}
}; // end namespace glslang