blob: d677c756338a51dbb803b4258d3fb78e01877ca6 [file] [log] [blame]
//
// Copyright (c) 2002-2011 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/OutputGLSLBase.h"
#include "compiler/debug.h"
#include <cfloat>
namespace
{
TString arrayBrackets(const TType& type)
{
ASSERT(type.isArray());
TInfoSinkBase out;
out << "[" << type.getArraySize() << "]";
return TString(out.c_str());
}
bool isSingleStatement(TIntermNode* node) {
if (const TIntermAggregate* aggregate = node->getAsAggregate())
{
return (aggregate->getOp() != EOpFunction) &&
(aggregate->getOp() != EOpSequence);
}
else if (const TIntermSelection* selection = node->getAsSelectionNode())
{
// Ternary operators are usually part of an assignment operator.
// This handles those rare cases in which they are all by themselves.
return selection->usesTernaryOperator();
}
else if (node->getAsLoopNode())
{
return false;
}
return true;
}
} // namespace
TOutputGLSLBase::TOutputGLSLBase(TInfoSinkBase& objSink,
ShArrayIndexClampingStrategy clampingStrategy,
ShHashFunction64 hashFunction,
NameMap& nameMap,
TSymbolTable& symbolTable)
: TIntermTraverser(true, true, true),
mObjSink(objSink),
mDeclaringVariables(false),
mClampingStrategy(clampingStrategy),
mHashFunction(hashFunction),
mNameMap(nameMap),
mSymbolTable(symbolTable)
{
}
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::writeVariableType(const TType& type)
{
TInfoSinkBase& out = objSink();
TQualifier qualifier = type.getQualifier();
// TODO(alokp): Validate qualifier for variable declarations.
if ((qualifier != EvqTemporary) && (qualifier != EvqGlobal))
out << type.getQualifierString() << " ";
// Declare the struct if we have not done so already.
if ((type.getBasicType() == EbtStruct) && !structDeclared(type.getStruct()))
{
declareStruct(type.getStruct());
}
else
{
if (writeVariablePrecision(type.getPrecision()))
out << " ";
out << getTypeName(type);
}
}
void TOutputGLSLBase::writeFunctionParameters(const TIntermSequence& args)
{
TInfoSinkBase& out = objSink();
for (TIntermSequence::const_iterator iter = args.begin();
iter != args.end(); ++iter)
{
const TIntermSymbol* arg = (*iter)->getAsSymbolNode();
ASSERT(arg != NULL);
const TType& type = arg->getType();
writeVariableType(type);
const TString& name = arg->getSymbol();
if (!name.empty())
out << " " << hashName(name);
if (type.isArray())
out << arrayBrackets(type);
// Put a comma if this is not the last argument.
if (iter != args.end() - 1)
out << ", ";
}
}
const ConstantUnion* TOutputGLSLBase::writeConstantUnion(const TType& type,
const ConstantUnion* pConstUnion)
{
TInfoSinkBase& out = objSink();
if (type.getBasicType() == EbtStruct)
{
const TStructure* structure = type.getStruct();
out << hashName(structure->name()) << "(";
const TFieldList& fields = structure->fields();
for (size_t i = 0; i < fields.size(); ++i)
{
const TType* fieldType = fields[i]->type();
ASSERT(fieldType != NULL);
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: out << std::min(FLT_MAX, std::max(-FLT_MAX, pConstUnion->getFConst())); break;
case EbtInt: out << pConstUnion->getIConst(); break;
case EbtBool: out << pConstUnion->getBConst(); break;
default: UNREACHABLE();
}
if (i != size - 1) out << ", ";
}
if (writeType) out << ")";
}
return pConstUnion;
}
void TOutputGLSLBase::visitSymbol(TIntermSymbol* node)
{
TInfoSinkBase& out = objSink();
if (mLoopUnroll.NeedsToReplaceSymbolWithValue(node))
out << mLoopUnroll.GetLoopIndexValue(node);
else
out << hashVariableName(node->getSymbol());
if (mDeclaringVariables && node->getType().isArray())
out << arrayBrackets(node->getType());
}
void TOutputGLSLBase::visitConstantUnion(TIntermConstantUnion* node)
{
writeConstantUnion(node->getType(), node->getUnionArrayPointer());
}
bool TOutputGLSLBase::visitBinary(Visit visit, TIntermBinary* node)
{
bool visitChildren = true;
TInfoSinkBase& out = objSink();
switch (node->getOp())
{
case EOpInitialize:
if (visit == InVisit)
{
out << " = ";
// RHS of initialize is not being declared.
mDeclaringVariables = false;
}
break;
case EOpAssign: writeTriplet(visit, "(", " = ", ")"); break;
case EOpAddAssign: writeTriplet(visit, "(", " += ", ")"); break;
case EOpSubAssign: writeTriplet(visit, "(", " -= ", ")"); break;
case EOpDivAssign: writeTriplet(visit, "(", " /= ", ")"); break;
// Notice the fall-through.
case EOpMulAssign:
case EOpVectorTimesMatrixAssign:
case EOpVectorTimesScalarAssign:
case EOpMatrixTimesScalarAssign:
case EOpMatrixTimesMatrixAssign:
writeTriplet(visit, "(", " *= ", ")");
break;
case EOpIndexDirect:
writeTriplet(visit, NULL, "[", "]");
break;
case EOpIndexIndirect:
if (node->getAddIndexClamp())
{
if (visit == InVisit)
{
if (mClampingStrategy == SH_CLAMP_WITH_CLAMP_INTRINSIC) {
out << "[int(clamp(float(";
} else {
out << "[webgl_int_clamp(";
}
}
else if (visit == PostVisit)
{
int maxSize;
TIntermTyped *left = node->getLeft();
TType leftType = left->getType();
if (left->isArray())
{
// The shader will fail validation if the array length is not > 0.
maxSize = leftType.getArraySize() - 1;
}
else
{
maxSize = leftType.getNominalSize() - 1;
}
if (mClampingStrategy == SH_CLAMP_WITH_CLAMP_INTRINSIC) {
out << "), 0.0, float(" << maxSize << ")))]";
} else {
out << ", 0, " << maxSize << ")]";
}
}
}
else
{
writeTriplet(visit, NULL, "[", "]");
}
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)];
TString fieldName = field->name();
if (!mSymbolTable.findBuiltIn(structure->name()))
fieldName = hashName(fieldName);
out << fieldName;
visitChildren = false;
}
break;
case EOpVectorSwizzle:
if (visit == InVisit)
{
out << ".";
TIntermAggregate* rightChild = node->getRight()->getAsAggregate();
TIntermSequence& sequence = rightChild->getSequence();
for (TIntermSequence::iterator sit = sequence.begin(); sit != sequence.end(); ++sit)
{
TIntermConstantUnion* element = (*sit)->getAsConstantUnion();
ASSERT(element->getBasicType() == EbtInt);
ASSERT(element->getNominalSize() == 1);
const ConstantUnion& data = element->getUnionArrayPointer()[0];
ASSERT(data.getType() == EbtInt);
switch (data.getIConst())
{
case 0: out << "x"; break;
case 1: out << "y"; break;
case 2: out << "z"; break;
case 3: out << "w"; break;
default: UNREACHABLE(); break;
}
}
visitChildren = false;
}
break;
case EOpAdd: writeTriplet(visit, "(", " + ", ")"); break;
case EOpSub: writeTriplet(visit, "(", " - ", ")"); break;
case EOpMul: writeTriplet(visit, "(", " * ", ")"); break;
case EOpDiv: writeTriplet(visit, "(", " / ", ")"); break;
case EOpMod: UNIMPLEMENTED(); 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(); break;
}
return visitChildren;
}
bool TOutputGLSLBase::visitUnary(Visit visit, TIntermUnary* node)
{
TString preString;
TString postString = ")";
switch (node->getOp())
{
case EOpNegative: preString = "(-"; break;
case EOpVectorLogicalNot: preString = "not("; break;
case EOpLogicalNot: preString = "(!"; break;
case EOpPostIncrement: preString = "("; postString = "++)"; break;
case EOpPostDecrement: preString = "("; postString = "--)"; break;
case EOpPreIncrement: preString = "(++"; break;
case EOpPreDecrement: preString = "(--"; break;
case EOpConvIntToBool:
case EOpConvFloatToBool:
switch (node->getOperand()->getType().getNominalSize())
{
case 1: preString = "bool("; break;
case 2: preString = "bvec2("; break;
case 3: preString = "bvec3("; break;
case 4: preString = "bvec4("; break;
default: UNREACHABLE();
}
break;
case EOpConvBoolToFloat:
case EOpConvIntToFloat:
switch (node->getOperand()->getType().getNominalSize())
{
case 1: preString = "float("; break;
case 2: preString = "vec2("; break;
case 3: preString = "vec3("; break;
case 4: preString = "vec4("; break;
default: UNREACHABLE();
}
break;
case EOpConvFloatToInt:
case EOpConvBoolToInt:
switch (node->getOperand()->getType().getNominalSize())
{
case 1: preString = "int("; break;
case 2: preString = "ivec2("; break;
case 3: preString = "ivec3("; break;
case 4: preString = "ivec4("; break;
default: UNREACHABLE();
}
break;
case EOpRadians: preString = "radians("; break;
case EOpDegrees: preString = "degrees("; break;
case EOpSin: preString = "sin("; break;
case EOpCos: preString = "cos("; break;
case EOpTan: preString = "tan("; break;
case EOpAsin: preString = "asin("; break;
case EOpAcos: preString = "acos("; break;
case EOpAtan: preString = "atan("; break;
case EOpExp: preString = "exp("; break;
case EOpLog: preString = "log("; break;
case EOpExp2: preString = "exp2("; break;
case EOpLog2: preString = "log2("; break;
case EOpSqrt: preString = "sqrt("; break;
case EOpInverseSqrt: preString = "inversesqrt("; break;
case EOpAbs: preString = "abs("; break;
case EOpSign: preString = "sign("; break;
case EOpFloor: preString = "floor("; break;
case EOpCeil: preString = "ceil("; break;
case EOpFract: preString = "fract("; break;
case EOpLength: preString = "length("; break;
case EOpNormalize: preString = "normalize("; break;
case EOpDFdx: preString = "dFdx("; break;
case EOpDFdy: preString = "dFdy("; break;
case EOpFwidth: preString = "fwidth("; break;
case EOpAny: preString = "any("; break;
case EOpAll: preString = "all("; break;
default: UNREACHABLE(); break;
}
if (visit == PreVisit && node->getUseEmulatedFunction())
preString = BuiltInFunctionEmulator::GetEmulatedFunctionName(preString);
writeTriplet(visit, preString.c_str(), NULL, postString.c_str());
return true;
}
bool TOutputGLSLBase::visitSelection(Visit visit, TIntermSelection* node)
{
TInfoSinkBase& out = objSink();
if (node->usesTernaryOperator())
{
// 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->getTrueBlock()->traverse(this);
out << ") : (";
node->getFalseBlock()->traverse(this);
out << "))";
}
else
{
out << "if (";
node->getCondition()->traverse(this);
out << ")\n";
incrementDepth();
visitCodeBlock(node->getTrueBlock());
if (node->getFalseBlock())
{
out << "else\n";
visitCodeBlock(node->getFalseBlock());
}
decrementDepth();
}
return false;
}
bool TOutputGLSLBase::visitAggregate(Visit visit, TIntermAggregate* node)
{
bool visitChildren = true;
TInfoSinkBase& out = objSink();
TString preString;
bool delayedWrite = false;
switch (node->getOp())
{
case EOpSequence: {
// Scope the sequences except when at the global scope.
if (depth > 0) out << "{\n";
incrementDepth();
const TIntermSequence& sequence = node->getSequence();
for (TIntermSequence::const_iterator iter = sequence.begin();
iter != sequence.end(); ++iter)
{
TIntermNode* node = *iter;
ASSERT(node != NULL);
node->traverse(this);
if (isSingleStatement(node))
out << ";\n";
}
decrementDepth();
// Scope the sequences except when at the global scope.
if (depth > 0) out << "}\n";
visitChildren = false;
break;
}
case EOpPrototype: {
// Function declaration.
ASSERT(visit == PreVisit);
writeVariableType(node->getType());
out << " " << hashName(node->getName());
out << "(";
writeFunctionParameters(node->getSequence());
out << ")";
visitChildren = false;
break;
}
case EOpFunction: {
// Function definition.
ASSERT(visit == PreVisit);
writeVariableType(node->getType());
out << " " << hashFunctionName(node->getName());
incrementDepth();
// Function definition node contains one or two children nodes
// representing function parameters and function body. The latter
// is not present in case of empty function bodies.
const TIntermSequence& sequence = node->getSequence();
ASSERT((sequence.size() == 1) || (sequence.size() == 2));
TIntermSequence::const_iterator seqIter = sequence.begin();
// Traverse function parameters.
TIntermAggregate* params = (*seqIter)->getAsAggregate();
ASSERT(params != NULL);
ASSERT(params->getOp() == EOpParameters);
params->traverse(this);
// Traverse function body.
TIntermAggregate* body = ++seqIter != sequence.end() ?
(*seqIter)->getAsAggregate() : NULL;
visitCodeBlock(body);
decrementDepth();
// Fully processed; no need to visit children.
visitChildren = false;
break;
}
case EOpFunctionCall:
// Function call.
if (visit == PreVisit)
{
out << hashFunctionName(node->getName()) << "(";
}
else if (visit == InVisit)
{
out << ", ";
}
else
{
out << ")";
}
break;
case EOpParameters: {
// Function parameters.
ASSERT(visit == PreVisit);
out << "(";
writeFunctionParameters(node->getSequence());
out << ")";
visitChildren = false;
break;
}
case EOpDeclaration: {
// Variable declaration.
if (visit == PreVisit)
{
const TIntermSequence& sequence = node->getSequence();
const TIntermTyped* variable = sequence.front()->getAsTyped();
writeVariableType(variable->getType());
out << " ";
mDeclaringVariables = true;
}
else if (visit == InVisit)
{
out << ", ";
mDeclaringVariables = true;
}
else
{
mDeclaringVariables = false;
}
break;
}
case EOpConstructFloat: writeTriplet(visit, "float(", NULL, ")"); break;
case EOpConstructVec2: writeTriplet(visit, "vec2(", ", ", ")"); break;
case EOpConstructVec3: writeTriplet(visit, "vec3(", ", ", ")"); break;
case EOpConstructVec4: writeTriplet(visit, "vec4(", ", ", ")"); break;
case EOpConstructBool: writeTriplet(visit, "bool(", NULL, ")"); break;
case EOpConstructBVec2: writeTriplet(visit, "bvec2(", ", ", ")"); break;
case EOpConstructBVec3: writeTriplet(visit, "bvec3(", ", ", ")"); break;
case EOpConstructBVec4: writeTriplet(visit, "bvec4(", ", ", ")"); break;
case EOpConstructInt: writeTriplet(visit, "int(", NULL, ")"); break;
case EOpConstructIVec2: writeTriplet(visit, "ivec2(", ", ", ")"); break;
case EOpConstructIVec3: writeTriplet(visit, "ivec3(", ", ", ")"); break;
case EOpConstructIVec4: writeTriplet(visit, "ivec4(", ", ", ")"); break;
case EOpConstructMat2: writeTriplet(visit, "mat2(", ", ", ")"); break;
case EOpConstructMat3: writeTriplet(visit, "mat3(", ", ", ")"); break;
case EOpConstructMat4: writeTriplet(visit, "mat4(", ", ", ")"); break;
case EOpConstructStruct:
if (visit == PreVisit)
{
const TType& type = node->getType();
ASSERT(type.getBasicType() == EbtStruct);
out << hashName(type.getStruct()->name()) << "(";
}
else if (visit == InVisit)
{
out << ", ";
}
else
{
out << ")";
}
break;
case EOpLessThan: preString = "lessThan("; delayedWrite = true; break;
case EOpGreaterThan: preString = "greaterThan("; delayedWrite = true; break;
case EOpLessThanEqual: preString = "lessThanEqual("; delayedWrite = true; break;
case EOpGreaterThanEqual: preString = "greaterThanEqual("; delayedWrite = true; break;
case EOpVectorEqual: preString = "equal("; delayedWrite = true; break;
case EOpVectorNotEqual: preString = "notEqual("; delayedWrite = true; break;
case EOpComma: writeTriplet(visit, NULL, ", ", NULL); break;
case EOpMod: preString = "mod("; delayedWrite = true; break;
case EOpPow: preString = "pow("; delayedWrite = true; break;
case EOpAtan: preString = "atan("; delayedWrite = true; break;
case EOpMin: preString = "min("; delayedWrite = true; break;
case EOpMax: preString = "max("; delayedWrite = true; break;
case EOpClamp: preString = "clamp("; delayedWrite = true; break;
case EOpMix: preString = "mix("; delayedWrite = true; break;
case EOpStep: preString = "step("; delayedWrite = true; break;
case EOpSmoothStep: preString = "smoothstep("; delayedWrite = true; break;
case EOpDistance: preString = "distance("; delayedWrite = true; break;
case EOpDot: preString = "dot("; delayedWrite = true; break;
case EOpCross: preString = "cross("; delayedWrite = true; break;
case EOpFaceForward: preString = "faceforward("; delayedWrite = true; break;
case EOpReflect: preString = "reflect("; delayedWrite = true; break;
case EOpRefract: preString = "refract("; delayedWrite = true; break;
case EOpMul: preString = "matrixCompMult("; delayedWrite = true; break;
default: UNREACHABLE(); break;
}
if (delayedWrite && visit == PreVisit && node->getUseEmulatedFunction())
preString = BuiltInFunctionEmulator::GetEmulatedFunctionName(preString);
if (delayedWrite)
writeTriplet(visit, preString.c_str(), ", ", ")");
return visitChildren;
}
bool TOutputGLSLBase::visitLoop(Visit visit, TIntermLoop* node)
{
TInfoSinkBase& out = objSink();
incrementDepth();
// Loop header.
TLoopType loopType = node->getType();
if (loopType == ELoopFor) // for loop
{
if (!node->getUnrollFlag()) {
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";
}
}
else if (loopType == ELoopWhile) // while loop
{
out << "while (";
ASSERT(node->getCondition() != NULL);
node->getCondition()->traverse(this);
out << ")\n";
}
else // do-while loop
{
ASSERT(loopType == ELoopDoWhile);
out << "do\n";
}
// Loop body.
if (node->getUnrollFlag())
{
TLoopIndexInfo indexInfo;
mLoopUnroll.FillLoopIndexInfo(node, indexInfo);
mLoopUnroll.Push(indexInfo);
while (mLoopUnroll.SatisfiesLoopCondition())
{
visitCodeBlock(node->getBody());
mLoopUnroll.Step();
}
mLoopUnroll.Pop();
}
else
{
visitCodeBlock(node->getBody());
}
// Loop footer.
if (loopType == ELoopDoWhile) // do-while loop
{
out << "while (";
ASSERT(node->getCondition() != NULL);
node->getCondition()->traverse(this);
out << ");\n";
}
decrementDepth();
// 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", NULL, NULL); break;
case EOpBreak: writeTriplet(visit, "break", NULL, NULL); break;
case EOpContinue: writeTriplet(visit, "continue", NULL, NULL); break;
case EOpReturn: writeTriplet(visit, "return ", NULL, NULL); break;
default: UNREACHABLE(); break;
}
return true;
}
void TOutputGLSLBase::visitCodeBlock(TIntermNode* node) {
TInfoSinkBase &out = objSink();
if (node != NULL)
{
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.
}
}
TString TOutputGLSLBase::getTypeName(const TType& type)
{
TInfoSinkBase out;
if (type.isMatrix())
{
out << "mat";
out << type.getNominalSize();
}
else if (type.isVector())
{
switch (type.getBasicType())
{
case EbtFloat: out << "vec"; break;
case EbtInt: out << "ivec"; break;
case EbtBool: out << "bvec"; break;
default: UNREACHABLE(); break;
}
out << type.getNominalSize();
}
else
{
if (type.getBasicType() == EbtStruct)
out << hashName(type.getStruct()->name());
else
out << type.getBasicString();
}
return TString(out.c_str());
}
TString TOutputGLSLBase::hashName(const TString& name)
{
if (mHashFunction == NULL || name.empty())
return name;
NameMap::const_iterator it = mNameMap.find(name.c_str());
if (it != mNameMap.end())
return it->second.c_str();
TString hashedName = TIntermTraverser::hash(name, mHashFunction);
mNameMap[name.c_str()] = hashedName.c_str();
return hashedName;
}
TString TOutputGLSLBase::hashVariableName(const TString& name)
{
if (mSymbolTable.findBuiltIn(name) != NULL)
return name;
return hashName(name);
}
TString TOutputGLSLBase::hashFunctionName(const TString& mangled_name)
{
TString name = TFunction::unmangleName(mangled_name);
if (mSymbolTable.findBuiltIn(mangled_name) != NULL || name == "main")
return name;
return hashName(name);
}
bool TOutputGLSLBase::structDeclared(const TStructure* structure) const
{
return mDeclaredStructs.find(structure->name()) != mDeclaredStructs.end();
}
void TOutputGLSLBase::declareStruct(const TStructure* structure)
{
TInfoSinkBase& out = objSink();
out << "struct " << hashName(structure->name()) << "{\n";
const TFieldList& fields = structure->fields();
for (size_t i = 0; i < fields.size(); ++i)
{
const TField* field = fields[i];
if (writeVariablePrecision(field->type()->getPrecision()))
out << " ";
out << getTypeName(*field->type()) << " " << hashName(field->name());
if (field->type()->isArray())
out << arrayBrackets(*field->type());
out << ";\n";
}
out << "}";
mDeclaredStructs.insert(structure->name());
}