Source code
Revision control
Copy as Markdown
Other Tools
//
// Copyright 2002 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
#include "compiler/translator/OutputGLSLBase.h"
#include "angle_gl.h"
#include "common/debug.h"
#include "common/mathutil.h"
#include "compiler/translator/Compiler.h"
#include "compiler/translator/util.h"
#include <cfloat>
namespace sh
{
namespace
{
bool isSingleStatement(TIntermNode *node)
{
if (node->getAsFunctionDefinition())
{
return false;
}
else if (node->getAsBlock())
{
return false;
}
else if (node->getAsIfElseNode())
{
return false;
}
else if (node->getAsLoopNode())
{
return false;
}
else if (node->getAsSwitchNode())
{
return false;
}
else if (node->getAsCaseNode())
{
return false;
}
else if (node->getAsPreprocessorDirective())
{
return false;
}
return true;
}
class CommaSeparatedListItemPrefixGenerator
{
public:
CommaSeparatedListItemPrefixGenerator() : mFirst(true) {}
private:
bool mFirst;
template <typename Stream>
friend Stream &operator<<(Stream &out, CommaSeparatedListItemPrefixGenerator &gen);
};
template <typename Stream>
Stream &operator<<(Stream &out, CommaSeparatedListItemPrefixGenerator &gen)
{
if (gen.mFirst)
{
gen.mFirst = false;
}
else
{
out << ", ";
}
return out;
}
} // namespace
TOutputGLSLBase::TOutputGLSLBase(TCompiler *compiler,
TInfoSinkBase &objSink,
const ShCompileOptions &compileOptions)
: TIntermTraverser(true, true, true, &compiler->getSymbolTable()),
mObjSink(objSink),
mDeclaringVariable(false),
mHashFunction(compiler->getHashFunction()),
mNameMap(compiler->getNameMap()),
mShaderType(compiler->getShaderType()),
mShaderVersion(compiler->getShaderVersion()),
mOutput(compiler->getOutputType()),
mHighPrecisionSupported(compiler->isHighPrecisionSupported()),
// If pixel local storage introduces new fragment outputs, we are now required to specify a
// location for _all_ fragment outputs, including previously valid outputs that had an
// implicit location of zero.
mAlwaysSpecifyFragOutLocation(compiler->hasPixelLocalStorageUniforms() &&
compileOptions.pls.type ==
ShPixelLocalStorageType::FramebufferFetch),
mCompileOptions(compileOptions)
{}
void TOutputGLSLBase::writeInvariantQualifier(const TType &type)
{
if (!sh::RemoveInvariant(mShaderType, mShaderVersion, mOutput, mCompileOptions))
{
TInfoSinkBase &out = objSink();
out << "invariant ";
}
}
void TOutputGLSLBase::writePreciseQualifier(const TType &type)
{
TInfoSinkBase &out = objSink();
out << "precise ";
}
void TOutputGLSLBase::writeFloat(TInfoSinkBase &out, float f)
{
if ((gl::isInf(f) || gl::isNaN(f)) && mShaderVersion >= 300)
{
out << "uintBitsToFloat(" << gl::bitCast<uint32_t>(f) << "u)";
}
else
{
out << std::min(FLT_MAX, std::max(-FLT_MAX, f));
}
}
void TOutputGLSLBase::writeTriplet(Visit visit,
const char *preStr,
const char *inStr,
const char *postStr)
{
TInfoSinkBase &out = objSink();
if (visit == PreVisit && preStr)
out << preStr;
else if (visit == InVisit && inStr)
out << inStr;
else if (visit == PostVisit && postStr)
out << postStr;
}
void TOutputGLSLBase::writeFunctionTriplet(Visit visit,
const ImmutableString &functionName,
bool useEmulatedFunction)
{
TInfoSinkBase &out = objSink();
if (visit == PreVisit)
{
if (useEmulatedFunction)
{
BuiltInFunctionEmulator::WriteEmulatedFunctionName(out, functionName.data());
}
else
{
out << functionName;
}
out << "(";
}
else
{
writeTriplet(visit, nullptr, ", ", ")");
}
}
// Outputs what goes inside layout(), except for location and binding qualifiers, as they are
// handled differently between GL GLSL and Vulkan GLSL.
std::string TOutputGLSLBase::getCommonLayoutQualifiers(TIntermSymbol *variable)
{
std::ostringstream out;
CommaSeparatedListItemPrefixGenerator listItemPrefix;
const TType &type = variable->getType();
const TLayoutQualifier &layoutQualifier = type.getLayoutQualifier();
if (type.getQualifier() == EvqFragmentOut || type.getQualifier() == EvqFragmentInOut)
{
if (layoutQualifier.index >= 0)
{
out << listItemPrefix << "index = " << layoutQualifier.index;
}
if (layoutQualifier.yuv)
{
out << listItemPrefix << "yuv";
}
}
if (type.getQualifier() == EvqFragmentInOut && layoutQualifier.noncoherent)
{
out << listItemPrefix << "noncoherent";
}
if (IsImage(type.getBasicType()))
{
if (layoutQualifier.imageInternalFormat != EiifUnspecified)
{
ASSERT(type.getQualifier() == EvqTemporary || type.getQualifier() == EvqUniform);
out << listItemPrefix
<< getImageInternalFormatString(layoutQualifier.imageInternalFormat);
}
}
if (IsAtomicCounter(type.getBasicType()))
{
out << listItemPrefix << "offset = " << layoutQualifier.offset;
}
return out.str();
}
// Outputs memory qualifiers applied to images, buffers and its fields, as well as image function
// arguments.
std::string TOutputGLSLBase::getMemoryQualifiers(const TType &type)
{
std::ostringstream out;
const TMemoryQualifier &memoryQualifier = type.getMemoryQualifier();
if (memoryQualifier.readonly)
{
out << "readonly ";
}
if (memoryQualifier.writeonly)
{
out << "writeonly ";
}
if (memoryQualifier.coherent)
{
out << "coherent ";
}
if (memoryQualifier.restrictQualifier)
{
out << "restrict ";
}
if (memoryQualifier.volatileQualifier)
{
out << "volatile ";
}
return out.str();
}
void TOutputGLSLBase::writeLayoutQualifier(TIntermSymbol *variable)
{
const TType &type = variable->getType();
if (!needsToWriteLayoutQualifier(type))
{
return;
}
if (type.getBasicType() == EbtInterfaceBlock)
{
declareInterfaceBlockLayout(type);
return;
}
TInfoSinkBase &out = objSink();
const TLayoutQualifier &layoutQualifier = type.getLayoutQualifier();
out << "layout(";
CommaSeparatedListItemPrefixGenerator listItemPrefix;
if (IsFragmentOutput(type.getQualifier()) || type.getQualifier() == EvqVertexIn ||
IsVarying(type.getQualifier()))
{
if (layoutQualifier.location >= 0 ||
(mAlwaysSpecifyFragOutLocation && IsFragmentOutput(type.getQualifier())))
{
out << listItemPrefix << "location = " << std::max(layoutQualifier.location, 0);
}
}
if (IsOpaqueType(type.getBasicType()))
{
if (layoutQualifier.binding >= 0)
{
out << listItemPrefix << "binding = " << layoutQualifier.binding;
}
}
std::string otherQualifiers = getCommonLayoutQualifiers(variable);
if (!otherQualifiers.empty())
{
out << listItemPrefix << otherQualifiers;
}
out << ") ";
}
void TOutputGLSLBase::writeFieldLayoutQualifier(const TField *field)
{
if (!field->type()->isMatrix() && !field->type()->isStructureContainingMatrices())
{
return;
}
TInfoSinkBase &out = objSink();
out << "layout(";
switch (field->type()->getLayoutQualifier().matrixPacking)
{
case EmpUnspecified:
case EmpColumnMajor:
// Default matrix packing is column major.
out << "column_major";
break;
case EmpRowMajor:
out << "row_major";
break;
default:
UNREACHABLE();
break;
}
out << ") ";
}
void TOutputGLSLBase::writeQualifier(TQualifier qualifier, const TType &type, const TSymbol *symbol)
{
const char *result = mapQualifierToString(qualifier);
if (result && result[0] != '\0')
{
objSink() << result << " ";
}
objSink() << getMemoryQualifiers(type);
}
const char *TOutputGLSLBase::mapQualifierToString(TQualifier qualifier)
{
if (sh::IsGLSL410OrOlder(mOutput) && mShaderVersion >= 300 &&
mCompileOptions.removeInvariantAndCentroidForESSL3)
{
switch (qualifier)
{
// The return string is consistent with sh::getQualifierString() from
// BaseTypes.h minus the "centroid" keyword.
case EvqCentroid:
return "";
case EvqCentroidIn:
return "smooth in";
case EvqCentroidOut:
return "smooth out";
default:
break;
}
}
if (sh::IsGLSL130OrNewer(mOutput))
{
switch (qualifier)
{
case EvqAttribute:
return "in";
case EvqVaryingIn:
return "in";
case EvqVaryingOut:
return "out";
default:
break;
}
}
switch (qualifier)
{
// gl_ClipDistance / gl_CullDistance require different qualifiers based on shader type.
case EvqClipDistance:
case EvqCullDistance:
return mShaderType == GL_FRAGMENT_SHADER ? "in" : "out";
// gl_LastFragColor / gl_LastFragData have no qualifiers.
case EvqLastFragData:
case EvqLastFragColor:
return nullptr;
default:
return sh::getQualifierString(qualifier);
}
}
namespace
{
constexpr char kIndent[] = " "; // 10x2 spaces
constexpr int kIndentWidth = 2;
constexpr int kMaxIndentLevel = sizeof(kIndent) / kIndentWidth;
} // namespace
const char *TOutputGLSLBase::getIndentPrefix(int extraIndentation)
{
int indentDepth = std::min(kMaxIndentLevel, getCurrentBlockDepth() + extraIndentation);
ASSERT(indentDepth >= 0);
return kIndent + (kMaxIndentLevel - indentDepth) * kIndentWidth;
}
void TOutputGLSLBase::writeVariableType(const TType &type,
const TSymbol *symbol,
bool isFunctionArgument)
{
TQualifier qualifier = type.getQualifier();
TInfoSinkBase &out = objSink();
if (type.isInvariant())
{
writeInvariantQualifier(type);
}
if (type.isPrecise())
{
writePreciseQualifier(type);
}
if (qualifier != EvqTemporary && qualifier != EvqGlobal)
{
writeQualifier(qualifier, type, symbol);
}
if (isFunctionArgument)
{
// Function arguments are the only place (other than image/SSBO/field declaration) where
// memory qualifiers can appear.
out << getMemoryQualifiers(type);
}
// Declare the struct.
if (type.isStructSpecifier())
{
const TStructure *structure = type.getStruct();
declareStruct(structure);
}
else if (type.getBasicType() == EbtInterfaceBlock)
{
declareInterfaceBlock(type);
}
else
{
if (writeVariablePrecision(type.getPrecision()))
out << " ";
out << getTypeName(type);
}
}
void TOutputGLSLBase::writeFunctionParameters(const TFunction *func)
{
TInfoSinkBase &out = objSink();
size_t paramCount = func->getParamCount();
for (size_t i = 0; i < paramCount; ++i)
{
const TVariable *param = func->getParam(i);
const TType &type = param->getType();
writeVariableType(type, param, true);
if (param->symbolType() != SymbolType::Empty)
{
out << " " << hashName(param);
}
if (type.isArray())
{
out << ArrayString(type);
}
// Put a comma if this is not the last argument.
if (i != paramCount - 1)
out << ", ";
}
}
const TConstantUnion *TOutputGLSLBase::writeConstantUnion(const TType &type,
const TConstantUnion *pConstUnion)
{
TInfoSinkBase &out = objSink();
if (type.getBasicType() == EbtStruct)
{
const TStructure *structure = type.getStruct();
out << hashName(structure) << "(";
const TFieldList &fields = structure->fields();
for (size_t i = 0; i < fields.size(); ++i)
{
const TType *fieldType = fields[i]->type();
ASSERT(fieldType != nullptr);
pConstUnion = writeConstantUnion(*fieldType, pConstUnion);
if (i != fields.size() - 1)
out << ", ";
}
out << ")";
}
else
{
size_t size = type.getObjectSize();
bool writeType = size > 1;
if (writeType)
out << getTypeName(type) << "(";
for (size_t i = 0; i < size; ++i, ++pConstUnion)
{
switch (pConstUnion->getType())
{
case EbtFloat:
writeFloat(out, pConstUnion->getFConst());
break;
case EbtInt:
out << pConstUnion->getIConst();
break;
case EbtUInt:
out << pConstUnion->getUConst() << "u";
break;
case EbtBool:
out << pConstUnion->getBConst();
break;
case EbtYuvCscStandardEXT:
out << getYuvCscStandardEXTString(pConstUnion->getYuvCscStandardEXTConst());
break;
default:
UNREACHABLE();
}
if (i != size - 1)
out << ", ";
}
if (writeType)
out << ")";
}
return pConstUnion;
}
void TOutputGLSLBase::writeConstructorTriplet(Visit visit, const TType &type)
{
TInfoSinkBase &out = objSink();
if (visit == PreVisit)
{
if (type.isArray())
{
out << getTypeName(type);
out << ArrayString(type);
out << "(";
}
else
{
out << getTypeName(type) << "(";
}
}
else
{
writeTriplet(visit, nullptr, ", ", ")");
}
}
void TOutputGLSLBase::visitSymbol(TIntermSymbol *node)
{
TInfoSinkBase &out = objSink();
out << hashName(&node->variable());
if (mDeclaringVariable && node->getType().isArray())
out << ArrayString(node->getType());
}
void TOutputGLSLBase::visitConstantUnion(TIntermConstantUnion *node)
{
writeConstantUnion(node->getType(), node->getConstantValue());
}
bool TOutputGLSLBase::visitSwizzle(Visit visit, TIntermSwizzle *node)
{
TInfoSinkBase &out = objSink();
if (visit == PostVisit)
{
out << ".";
node->writeOffsetsAsXYZW(&out);
}
return true;
}
bool TOutputGLSLBase::visitBinary(Visit visit, TIntermBinary *node)
{
bool visitChildren = true;
TInfoSinkBase &out = objSink();
switch (node->getOp())
{
case EOpComma:
writeTriplet(visit, "(", ", ", ")");
break;
case EOpInitialize:
if (visit == InVisit)
{
out << " = ";
// RHS of initialize is not being declared.
mDeclaringVariable = false;
}
break;
case EOpAssign:
writeTriplet(visit, "(", " = ", ")");
break;
case EOpAddAssign:
writeTriplet(visit, "(", " += ", ")");
break;
case EOpSubAssign:
writeTriplet(visit, "(", " -= ", ")");
break;
case EOpDivAssign:
writeTriplet(visit, "(", " /= ", ")");
break;
case EOpIModAssign:
writeTriplet(visit, "(", " %= ", ")");
break;
// Notice the fall-through.
case EOpMulAssign:
case EOpVectorTimesMatrixAssign:
case EOpVectorTimesScalarAssign:
case EOpMatrixTimesScalarAssign:
case EOpMatrixTimesMatrixAssign:
writeTriplet(visit, "(", " *= ", ")");
break;
case EOpBitShiftLeftAssign:
writeTriplet(visit, "(", " <<= ", ")");
break;
case EOpBitShiftRightAssign:
writeTriplet(visit, "(", " >>= ", ")");
break;
case EOpBitwiseAndAssign:
writeTriplet(visit, "(", " &= ", ")");
break;
case EOpBitwiseXorAssign:
writeTriplet(visit, "(", " ^= ", ")");
break;
case EOpBitwiseOrAssign:
writeTriplet(visit, "(", " |= ", ")");
break;
case EOpIndexDirect:
case EOpIndexIndirect:
writeTriplet(visit, nullptr, "[", "]");
break;
case EOpIndexDirectStruct:
if (visit == InVisit)
{
// Here we are writing out "foo.bar", where "foo" is struct
// and "bar" is field. In AST, it is represented as a binary
// node, where left child represents "foo" and right child "bar".
// The node itself represents ".". The struct field "bar" is
// actually stored as an index into TStructure::fields.
out << ".";
const TStructure *structure = node->getLeft()->getType().getStruct();
const TIntermConstantUnion *index = node->getRight()->getAsConstantUnion();
const TField *field = structure->fields()[index->getIConst(0)];
out << hashFieldName(field);
visitChildren = false;
}
break;
case EOpIndexDirectInterfaceBlock:
if (visit == InVisit)
{
out << ".";
const TInterfaceBlock *interfaceBlock =
node->getLeft()->getType().getInterfaceBlock();
const TIntermConstantUnion *index = node->getRight()->getAsConstantUnion();
const TField *field = interfaceBlock->fields()[index->getIConst(0)];
out << hashFieldName(field);
visitChildren = false;
}
break;
case EOpAdd:
writeTriplet(visit, "(", " + ", ")");
break;
case EOpSub:
writeTriplet(visit, "(", " - ", ")");
break;
case EOpMul:
writeTriplet(visit, "(", " * ", ")");
break;
case EOpDiv:
writeTriplet(visit, "(", " / ", ")");
break;
case EOpIMod:
writeTriplet(visit, "(", " % ", ")");
break;
case EOpBitShiftLeft:
writeTriplet(visit, "(", " << ", ")");
break;
case EOpBitShiftRight:
writeTriplet(visit, "(", " >> ", ")");
break;
case EOpBitwiseAnd:
writeTriplet(visit, "(", " & ", ")");
break;
case EOpBitwiseXor:
writeTriplet(visit, "(", " ^ ", ")");
break;
case EOpBitwiseOr:
writeTriplet(visit, "(", " | ", ")");
break;
case EOpEqual:
writeTriplet(visit, "(", " == ", ")");
break;
case EOpNotEqual:
writeTriplet(visit, "(", " != ", ")");
break;
case EOpLessThan:
writeTriplet(visit, "(", " < ", ")");
break;
case EOpGreaterThan:
writeTriplet(visit, "(", " > ", ")");
break;
case EOpLessThanEqual:
writeTriplet(visit, "(", " <= ", ")");
break;
case EOpGreaterThanEqual:
writeTriplet(visit, "(", " >= ", ")");
break;
// Notice the fall-through.
case EOpVectorTimesScalar:
case EOpVectorTimesMatrix:
case EOpMatrixTimesVector:
case EOpMatrixTimesScalar:
case EOpMatrixTimesMatrix:
writeTriplet(visit, "(", " * ", ")");
break;
case EOpLogicalOr:
writeTriplet(visit, "(", " || ", ")");
break;
case EOpLogicalXor:
writeTriplet(visit, "(", " ^^ ", ")");
break;
case EOpLogicalAnd:
writeTriplet(visit, "(", " && ", ")");
break;
default:
UNREACHABLE();
}
return visitChildren;
}
bool TOutputGLSLBase::visitUnary(Visit visit, TIntermUnary *node)
{
const char *preString = "";
const char *postString = ")";
switch (node->getOp())
{
case EOpNegative:
preString = "(-";
break;
case EOpPositive:
preString = "(+";
break;
case EOpLogicalNot:
preString = "(!";
break;
case EOpBitwiseNot:
preString = "(~";
break;
case EOpPostIncrement:
preString = "(";
postString = "++)";
break;
case EOpPostDecrement:
preString = "(";
postString = "--)";
break;
case EOpPreIncrement:
preString = "(++";
break;
case EOpPreDecrement:
preString = "(--";
break;
case EOpArrayLength:
preString = "((";
postString = ").length())";
break;
default:
writeFunctionTriplet(visit, node->getFunction()->name(),
node->getUseEmulatedFunction());
return true;
}
writeTriplet(visit, preString, nullptr, postString);
return true;
}
bool TOutputGLSLBase::visitTernary(Visit visit, TIntermTernary *node)
{
TInfoSinkBase &out = objSink();
// Notice two brackets at the beginning and end. The outer ones
// encapsulate the whole ternary expression. This preserves the
// order of precedence when ternary expressions are used in a
// compound expression, i.e., c = 2 * (a < b ? 1 : 2).
out << "((";
node->getCondition()->traverse(this);
out << ") ? (";
node->getTrueExpression()->traverse(this);
out << ") : (";
node->getFalseExpression()->traverse(this);
out << "))";
return false;
}
bool TOutputGLSLBase::visitIfElse(Visit visit, TIntermIfElse *node)
{
TInfoSinkBase &out = objSink();
out << "if (";
node->getCondition()->traverse(this);
out << ")\n";
visitCodeBlock(node->getTrueBlock());
if (node->getFalseBlock())
{
out << getIndentPrefix() << "else\n";
visitCodeBlock(node->getFalseBlock());
}
return false;
}
bool TOutputGLSLBase::visitSwitch(Visit visit, TIntermSwitch *node)
{
ASSERT(node->getStatementList());
writeTriplet(visit, "switch (", ") ", nullptr);
// The curly braces get written when visiting the statementList aggregate
return true;
}
bool TOutputGLSLBase::visitCase(Visit visit, TIntermCase *node)
{
if (node->hasCondition())
{
writeTriplet(visit, "case (", nullptr, "):\n");
return true;
}
else
{
TInfoSinkBase &out = objSink();
out << "default:\n";
return false;
}
}
bool TOutputGLSLBase::visitBlock(Visit visit, TIntermBlock *node)
{
TInfoSinkBase &out = objSink();
// Scope the blocks except when at the global scope.
if (getCurrentTraversalDepth() > 0)
{
out << "{\n";
}
for (TIntermSequence::const_iterator iter = node->getSequence()->begin();
iter != node->getSequence()->end(); ++iter)
{
TIntermNode *curNode = *iter;
ASSERT(curNode != nullptr);
out << getIndentPrefix(curNode->getAsCaseNode() ? -1 : 0);
curNode->traverse(this);
if (isSingleStatement(curNode))
out << ";\n";
}
// Scope the blocks except when at the global scope.
if (getCurrentTraversalDepth() > 0)
{
out << getIndentPrefix(-1) << "}\n";
}
return false;
}
bool TOutputGLSLBase::visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node)
{
TIntermFunctionPrototype *prototype = node->getFunctionPrototype();
prototype->traverse(this);
visitCodeBlock(node->getBody());
// Fully processed; no need to visit children.
return false;
}
bool TOutputGLSLBase::visitGlobalQualifierDeclaration(Visit visit,
TIntermGlobalQualifierDeclaration *node)
{
TInfoSinkBase &out = objSink();
ASSERT(visit == PreVisit);
const TIntermSymbol *symbol = node->getSymbol();
out << (node->isPrecise() ? "precise " : "invariant ") << hashName(&symbol->variable());
return false;
}
void TOutputGLSLBase::visitFunctionPrototype(TIntermFunctionPrototype *node)
{
TInfoSinkBase &out = objSink();
const TType &type = node->getType();
writeVariableType(type, node->getFunction(), false);
if (type.isArray())
out << ArrayString(type);
out << " " << hashFunctionNameIfNeeded(node->getFunction());
out << "(";
writeFunctionParameters(node->getFunction());
out << ")";
}
bool TOutputGLSLBase::visitAggregate(Visit visit, TIntermAggregate *node)
{
bool visitChildren = true;
if (node->getOp() == EOpConstruct)
{
writeConstructorTriplet(visit, node->getType());
}
else
{
// Function call.
ImmutableString functionName = node->getFunction()->name();
if (visit == PreVisit)
{
// No raw function is expected.
ASSERT(node->getOp() != EOpCallInternalRawFunction);
if (node->getOp() == EOpCallFunctionInAST)
{
functionName = hashFunctionNameIfNeeded(node->getFunction());
}
else
{
functionName =
translateTextureFunction(node->getFunction()->name(), mCompileOptions);
}
}
writeFunctionTriplet(visit, functionName, node->getUseEmulatedFunction());
}
return visitChildren;
}
bool TOutputGLSLBase::visitDeclaration(Visit visit, TIntermDeclaration *node)
{
TInfoSinkBase &out = objSink();
// Variable declaration.
if (visit == PreVisit)
{
const TIntermSequence &sequence = *(node->getSequence());
TIntermTyped *decl = sequence.front()->getAsTyped();
TIntermSymbol *symbolNode = decl->getAsSymbolNode();
if (symbolNode == nullptr)
{
ASSERT(decl->getAsBinaryNode() && decl->getAsBinaryNode()->getOp() == EOpInitialize);
symbolNode = decl->getAsBinaryNode()->getLeft()->getAsSymbolNode();
}
ASSERT(symbolNode);
if (symbolNode->getName() != "gl_ClipDistance" &&
symbolNode->getName() != "gl_CullDistance")
{
// gl_Clip/CullDistance re-declaration doesn't need layout.
writeLayoutQualifier(symbolNode);
}
writeVariableType(symbolNode->getType(), &symbolNode->variable(), false);
if (symbolNode->variable().symbolType() != SymbolType::Empty)
{
out << " ";
}
mDeclaringVariable = true;
}
else if (visit == InVisit)
{
UNREACHABLE();
}
else
{
mDeclaringVariable = false;
}
return true;
}
bool TOutputGLSLBase::visitLoop(Visit visit, TIntermLoop *node)
{
TInfoSinkBase &out = objSink();
TLoopType loopType = node->getType();
if (loopType == ELoopFor) // for loop
{
out << "for (";
if (node->getInit())
node->getInit()->traverse(this);
out << "; ";
if (node->getCondition())
node->getCondition()->traverse(this);
out << "; ";
if (node->getExpression())
node->getExpression()->traverse(this);
out << ")\n";
visitCodeBlock(node->getBody());
}
else if (loopType == ELoopWhile) // while loop
{
out << "while (";
ASSERT(node->getCondition() != nullptr);
node->getCondition()->traverse(this);
out << ")\n";
visitCodeBlock(node->getBody());
}
else // do-while loop
{
ASSERT(loopType == ELoopDoWhile);
out << "do\n";
visitCodeBlock(node->getBody());
out << "while (";
ASSERT(node->getCondition() != nullptr);
node->getCondition()->traverse(this);
out << ");\n";
}
// No need to visit children. They have been already processed in
// this function.
return false;
}
bool TOutputGLSLBase::visitBranch(Visit visit, TIntermBranch *node)
{
switch (node->getFlowOp())
{
case EOpKill:
writeTriplet(visit, "discard", nullptr, nullptr);
break;
case EOpBreak:
writeTriplet(visit, "break", nullptr, nullptr);
break;
case EOpContinue:
writeTriplet(visit, "continue", nullptr, nullptr);
break;
case EOpReturn:
writeTriplet(visit, "return ", nullptr, nullptr);
break;
default:
UNREACHABLE();
}
return true;
}
void TOutputGLSLBase::visitCodeBlock(TIntermBlock *node)
{
TInfoSinkBase &out = objSink();
if (node != nullptr)
{
out << getIndentPrefix();
node->traverse(this);
// Single statements not part of a sequence need to be terminated
// with semi-colon.
if (isSingleStatement(node))
out << ";\n";
}
else
{
out << "{\n}\n"; // Empty code block.
}
}
void TOutputGLSLBase::visitPreprocessorDirective(TIntermPreprocessorDirective *node)
{
TInfoSinkBase &out = objSink();
out << "\n";
switch (node->getDirective())
{
case PreprocessorDirective::Define:
out << "#define";
break;
case PreprocessorDirective::Endif:
out << "#endif";
break;
case PreprocessorDirective::If:
out << "#if";
break;
case PreprocessorDirective::Ifdef:
out << "#ifdef";
break;
default:
UNREACHABLE();
break;
}
if (!node->getCommand().empty())
{
out << " " << node->getCommand();
}
out << "\n";
}
ImmutableString TOutputGLSLBase::getTypeName(const TType &type)
{
if (type.getBasicType() == EbtSamplerVideoWEBGL)
{
// when necessary (e.g. on Android devices)
return ImmutableString("sampler2D");
}
return GetTypeName(type, mHashFunction, &mNameMap);
}
ImmutableString TOutputGLSLBase::hashName(const TSymbol *symbol)
{
return HashName(symbol, mHashFunction, &mNameMap);
}
ImmutableString TOutputGLSLBase::hashFieldName(const TField *field)
{
ASSERT(field->symbolType() != SymbolType::Empty);
if (field->symbolType() == SymbolType::UserDefined)
{
return HashName(field->name(), mHashFunction, &mNameMap);
}
return field->name();
}
ImmutableString TOutputGLSLBase::hashFunctionNameIfNeeded(const TFunction *func)
{
if (func->isMain())
{
return func->name();
}
else
{
return hashName(func);
}
}
void TOutputGLSLBase::declareStruct(const TStructure *structure)
{
TInfoSinkBase &out = objSink();
out << "struct ";
if (structure->symbolType() != SymbolType::Empty)
{
out << hashName(structure) << " ";
}
out << "{\n";
const TFieldList &fields = structure->fields();
for (size_t i = 0; i < fields.size(); ++i)
{
out << getIndentPrefix(1);
const TField *field = fields[i];
const TType &fieldType = *field->type();
if (writeVariablePrecision(fieldType.getPrecision()))
{
out << " ";
}
if (fieldType.isPrecise())
{
writePreciseQualifier(fieldType);
}
out << getTypeName(fieldType) << " " << hashFieldName(field);
if (fieldType.isArray())
{
out << ArrayString(fieldType);
}
out << ";\n";
}
out << getIndentPrefix() << "}";
}
void TOutputGLSLBase::declareInterfaceBlockLayout(const TType &type)
{
// 4.4.5 Uniform and Shader Storage Block Layout Qualifiers in GLSL 4.5 spec.
// Layout qualifiers can be used for uniform and shader storage blocks,
// but not for non-block uniform declarations.
if (IsShaderIoBlock(type.getQualifier()))
{
return;
}
const TInterfaceBlock *interfaceBlock = type.getInterfaceBlock();
TInfoSinkBase &out = objSink();
out << "layout(";
switch (interfaceBlock->blockStorage())
{
case EbsUnspecified:
case EbsShared:
// Default block storage is shared.
out << "shared";
break;
case EbsPacked:
out << "packed";
break;
case EbsStd140:
out << "std140";
break;
case EbsStd430:
out << "std430";
break;
default:
UNREACHABLE();
break;
}
if (interfaceBlock->blockBinding() >= 0)
{
out << ", ";
out << "binding = " << interfaceBlock->blockBinding();
}
out << ") ";
}
const char *getVariableInterpolation(TQualifier qualifier)
{
switch (qualifier)
{
case EvqSmoothOut:
return "smooth out ";
case EvqFlatOut:
return "flat out ";
case EvqNoPerspectiveOut:
return "noperspective out ";
case EvqCentroidOut:
return "centroid out ";
case EvqSmoothIn:
return "smooth in ";
case EvqFlatIn:
return "flat in ";
case EvqNoPerspectiveIn:
return "noperspective in ";
case EvqCentroidIn:
return "centroid in ";
default:
break;
}
return nullptr;
}
void TOutputGLSLBase::declareInterfaceBlock(const TType &type)
{
const TInterfaceBlock *interfaceBlock = type.getInterfaceBlock();
TInfoSinkBase &out = objSink();
out << hashName(interfaceBlock) << "{\n";
const TFieldList &fields = interfaceBlock->fields();
for (const TField *field : fields)
{
out << getIndentPrefix(1);
if (!IsShaderIoBlock(type.getQualifier()) && type.getQualifier() != EvqPatchIn &&
type.getQualifier() != EvqPatchOut)
{
writeFieldLayoutQualifier(field);
}
const TType &fieldType = *field->type();
out << getMemoryQualifiers(fieldType);
if (writeVariablePrecision(fieldType.getPrecision()))
out << " ";
if (fieldType.isInvariant())
{
writeInvariantQualifier(fieldType);
}
if (fieldType.isPrecise())
{
writePreciseQualifier(fieldType);
}
const char *qualifier = getVariableInterpolation(fieldType.getQualifier());
if (qualifier != nullptr)
out << qualifier;
out << getTypeName(fieldType) << " " << hashFieldName(field);
if (fieldType.isArray())
out << ArrayString(fieldType);
out << ";\n";
}
out << "}";
}
void WritePragma(TInfoSinkBase &out, const ShCompileOptions &compileOptions, const TPragma &pragma)
{
if (!compileOptions.flattenPragmaSTDGLInvariantAll)
{
if (pragma.stdgl.invariantAll)
out << "#pragma STDGL invariant(all)\n";
}
}
void WriteGeometryShaderLayoutQualifiers(TInfoSinkBase &out,
sh::TLayoutPrimitiveType inputPrimitive,
int invocations,
sh::TLayoutPrimitiveType outputPrimitive,
int maxVertices)
{
// Omit 'invocations = 1'
if (inputPrimitive != EptUndefined || invocations > 1)
{
out << "layout (";
if (inputPrimitive != EptUndefined)
{
out << getGeometryShaderPrimitiveTypeString(inputPrimitive);
}
if (invocations > 1)
{
if (inputPrimitive != EptUndefined)
{
out << ", ";
}
out << "invocations = " << invocations;
}
out << ") in;\n";
}
if (outputPrimitive != EptUndefined || maxVertices != -1)
{
out << "layout (";
if (outputPrimitive != EptUndefined)
{
out << getGeometryShaderPrimitiveTypeString(outputPrimitive);
}
if (maxVertices != -1)
{
if (outputPrimitive != EptUndefined)
{
out << ", ";
}
out << "max_vertices = " << maxVertices;
}
out << ") out;\n";
}
}
void WriteTessControlShaderLayoutQualifiers(TInfoSinkBase &out, int inputVertices)
{
if (inputVertices != 0)
{
out << "layout (vertices = " << inputVertices << ") out;\n";
}
}
void WriteTessEvaluationShaderLayoutQualifiers(TInfoSinkBase &out,
sh::TLayoutTessEvaluationType inputPrimitive,
sh::TLayoutTessEvaluationType inputVertexSpacing,
sh::TLayoutTessEvaluationType inputOrdering,
sh::TLayoutTessEvaluationType inputPoint)
{
if (inputPrimitive != EtetUndefined)
{
out << "layout (";
out << getTessEvaluationShaderTypeString(inputPrimitive);
if (inputVertexSpacing != EtetUndefined)
{
out << ", " << getTessEvaluationShaderTypeString(inputVertexSpacing);
}
if (inputOrdering != EtetUndefined)
{
out << ", " << getTessEvaluationShaderTypeString(inputOrdering);
}
if (inputPoint != EtetUndefined)
{
out << ", " << getTessEvaluationShaderTypeString(inputPoint);
}
out << ") in;\n";
}
}
// If SH_SCALARIZE_VEC_AND_MAT_CONSTRUCTOR_ARGS is enabled, layout qualifiers are spilled whenever
// variables with specified layout qualifiers are copied. Additional checks are needed against the
// type and storage qualifier of the variable to verify that layout qualifiers have to be outputted.
// TODO (mradev): Fix layout qualifier spilling in ScalarizeVecAndMatConstructorArgs and remove
// NeedsToWriteLayoutQualifier.
bool TOutputGLSLBase::needsToWriteLayoutQualifier(const TType &type)
{
if (type.getBasicType() == EbtInterfaceBlock)
{
return true;
}
const TLayoutQualifier &layoutQualifier = type.getLayoutQualifier();
if (IsFragmentOutput(type.getQualifier()) || type.getQualifier() == EvqVertexIn ||
IsVarying(type.getQualifier()))
{
if (layoutQualifier.location >= 0 ||
(mAlwaysSpecifyFragOutLocation && IsFragmentOutput(type.getQualifier())))
{
return true;
}
}
if (type.getQualifier() == EvqFragmentOut || type.getQualifier() == EvqFragmentInOut)
{
if (layoutQualifier.index >= 0)
{
return true;
}
if (layoutQualifier.yuv)
{
return true;
}
}
if (type.getQualifier() == EvqFragmentInOut && layoutQualifier.noncoherent)
{
return true;
}
if (IsOpaqueType(type.getBasicType()) && layoutQualifier.binding != -1)
{
return true;
}
if (IsImage(type.getBasicType()) && layoutQualifier.imageInternalFormat != EiifUnspecified)
{
return true;
}
return false;
}
void EmitEarlyFragmentTestsGLSL(const TCompiler &compiler, TInfoSinkBase &sink)
{
if (compiler.isEarlyFragmentTestsSpecified())
{
sink << "layout (early_fragment_tests) in;\n";
}
}
void EmitWorkGroupSizeGLSL(const TCompiler &compiler, TInfoSinkBase &sink)
{
if (compiler.isComputeShaderLocalSizeDeclared())
{
const sh::WorkGroupSize &localSize = compiler.getComputeShaderLocalSize();
sink << "layout (local_size_x=" << localSize[0] << ", local_size_y=" << localSize[1]
<< ", local_size_z=" << localSize[2] << ") in;\n";
}
}
void EmitMultiviewGLSL(const TCompiler &compiler,
const ShCompileOptions &compileOptions,
const TExtension extension,
const TBehavior behavior,
TInfoSinkBase &sink)
{
ASSERT(behavior != EBhUndefined);
if (behavior == EBhDisable)
return;
const bool isVertexShader = (compiler.getShaderType() == GL_VERTEX_SHADER);
if (compileOptions.initializeBuiltinsForInstancedMultiview)
{
// Emit ARB_shader_viewport_layer_array/NV_viewport_array2 in a vertex shader if the
// SH_SELECT_VIEW_IN_NV_GLSL_VERTEX_SHADER option is set and the
// OVR_multiview(2) extension is requested.
if (isVertexShader && compileOptions.selectViewInNvGLSLVertexShader)
{
sink << "#if defined(GL_ARB_shader_viewport_layer_array)\n"
<< "#extension GL_ARB_shader_viewport_layer_array : require\n"
<< "#elif defined(GL_NV_viewport_array2)\n"
<< "#extension GL_NV_viewport_array2 : require\n"
<< "#endif\n";
}
}
else
{
sink << "#extension GL_OVR_multiview";
if (extension == TExtension::OVR_multiview2)
{
sink << "2";
}
sink << " : " << GetBehaviorString(behavior) << "\n";
const auto &numViews = compiler.getNumViews();
if (isVertexShader && numViews != -1)
{
sink << "layout(num_views=" << numViews << ") in;\n";
}
}
}
} // namespace sh