Commit 98493ddc by Jamie Madill Committed by Shannon Woods

Simplified TType class by carving out TStructure and TField.

R=kbr@chromium.org Review URL: https://codereview.appspot.com/9866043 git-svn-id: https://angleproject.googlecode.com/svn/trunk@2423 736b8ea6-26fd-11df-bfd4-992fa37f6226 TRAC #23415 Authored-by: alokp@chromium.org Signed-off-by: Shannon Woods Signed-off-by Nicolas Capens Merged-by: Jamie Madill
parent f386bf76
...@@ -55,11 +55,11 @@ bool FlagStd140Structs::isInStd140InterfaceBlock(TIntermTyped *node) const ...@@ -55,11 +55,11 @@ bool FlagStd140Structs::isInStd140InterfaceBlock(TIntermTyped *node) const
const TType &type = node->getType(); const TType &type = node->getType();
if (type.isInterfaceBlockMember() || type.getBasicType() == EbtInterfaceBlock)
{
// determine if we are in the standard layout // determine if we are in the standard layout
const TType &interfaceBlockType = (type.isInterfaceBlockMember() ? *type.getInterfaceBlockType() : type); const TInterfaceBlock *interfaceBlock = type.getInterfaceBlock();
return (interfaceBlockType.getLayoutQualifier().blockStorage == EbsStd140); if (interfaceBlock)
{
return (interfaceBlock->blockStorage() == EbsStd140);
} }
return false; return false;
......
...@@ -447,19 +447,18 @@ void InsertBuiltInFunctions(ShShaderType type, ShShaderSpec spec, const ShBuiltI ...@@ -447,19 +447,18 @@ void InsertBuiltInFunctions(ShShaderType type, ShShaderSpec spec, const ShBuiltI
// //
// Depth range in window coordinates // Depth range in window coordinates
// //
TTypeList *members = NewPoolTTypeList(); TFieldList *fields = NewPoolTFieldList();
TTypeLine near = {new TType(EbtFloat, EbpHigh, EvqGlobal, 1), 0}; TSourceLoc zeroSourceLoc = {0};
TTypeLine far = {new TType(EbtFloat, EbpHigh, EvqGlobal, 1), 0}; TField *near = new TField(new TType(EbtFloat, EbpHigh, EvqGlobal, 1), NewPoolTString("near"), zeroSourceLoc);
TTypeLine diff = {new TType(EbtFloat, EbpHigh, EvqGlobal, 1), 0}; TField *far = new TField(new TType(EbtFloat, EbpHigh, EvqGlobal, 1), NewPoolTString("far"), zeroSourceLoc);
near.type->setFieldName("near"); TField *diff = new TField(new TType(EbtFloat, EbpHigh, EvqGlobal, 1), NewPoolTString("diff"), zeroSourceLoc);
far.type->setFieldName("far"); fields->push_back(near);
diff.type->setFieldName("diff"); fields->push_back(far);
members->push_back(near); fields->push_back(diff);
members->push_back(far); TStructure *depthRangeStruct = new TStructure(NewPoolTString("gl_DepthRangeParameters"), fields);
members->push_back(diff); TVariable *depthRangeParameters = new TVariable(&depthRangeStruct->name(), depthRangeStruct, true);
TVariable *depthRangeParameters = new TVariable(NewPoolTString("gl_DepthRangeParameters"), TType(members, "gl_DepthRangeParameters"), true);
symbolTable.insert(COMMON_BUILTINS, *depthRangeParameters); symbolTable.insert(COMMON_BUILTINS, *depthRangeParameters);
TVariable *depthRange = new TVariable(NewPoolTString("gl_DepthRange"), TType(members, "gl_DepthRangeParameters")); TVariable *depthRange = new TVariable(NewPoolTString("gl_DepthRange"), TType(depthRangeStruct));
depthRange->setQualifier(EvqUniform); depthRange->setQualifier(EvqUniform);
symbolTable.insert(COMMON_BUILTINS, *depthRange); symbolTable.insert(COMMON_BUILTINS, *depthRange);
......
...@@ -1142,16 +1142,16 @@ bool TIntermBinary::promote(TInfoSink& infoSink) ...@@ -1142,16 +1142,16 @@ bool TIntermBinary::promote(TInfoSink& infoSink)
bool CompareStruct(const TType& leftNodeType, ConstantUnion* rightUnionArray, ConstantUnion* leftUnionArray) bool CompareStruct(const TType& leftNodeType, ConstantUnion* rightUnionArray, ConstantUnion* leftUnionArray)
{ {
const TTypeList* fields = leftNodeType.getStruct(); const TFieldList& fields = leftNodeType.getStruct()->fields();
size_t structSize = fields->size(); size_t structSize = fields.size();
size_t index = 0; size_t index = 0;
for (size_t j = 0; j < structSize; j++) { for (size_t j = 0; j < structSize; j++) {
size_t size = (*fields)[j].type->getObjectSize(); size_t size = fields[j]->type()->getObjectSize();
for (size_t i = 0; i < size; i++) { for (size_t i = 0; i < size; i++) {
if ((*fields)[j].type->getBasicType() == EbtStruct) { if (fields[j]->type()->getBasicType() == EbtStruct) {
if (!CompareStructure(*(*fields)[j].type, &rightUnionArray[index], &leftUnionArray[index])) if (!CompareStructure(*fields[j]->type(), &rightUnionArray[index], &leftUnionArray[index]))
return false; return false;
} else { } else {
if (leftUnionArray[index] != rightUnionArray[index]) if (leftUnionArray[index] != rightUnionArray[index])
......
...@@ -79,25 +79,9 @@ void TOutputGLSLBase::writeVariableType(const TType& type) ...@@ -79,25 +79,9 @@ void TOutputGLSLBase::writeVariableType(const TType& type)
if ((qualifier != EvqTemporary) && (qualifier != EvqGlobal)) if ((qualifier != EvqTemporary) && (qualifier != EvqGlobal))
out << type.getQualifierString() << " "; out << type.getQualifierString() << " ";
// Declare the struct if we have not done so already. // Declare the struct if we have not done so already.
if ((type.getBasicType() == EbtStruct) && if ((type.getBasicType() == EbtStruct) && !structDeclared(type.getStruct()))
(mDeclaredStructs.find(type.getTypeName()) == mDeclaredStructs.end()))
{ {
out << "struct " << hashName(type.getTypeName()) << "{\n"; declareStruct(type.getStruct());
const TTypeList* structure = type.getStruct();
ASSERT(structure != NULL);
for (size_t i = 0; i < structure->size(); ++i)
{
const TType* fieldType = (*structure)[i].type;
ASSERT(fieldType != NULL);
if (writeVariablePrecision(fieldType->getPrecision()))
out << " ";
out << getTypeName(*fieldType) << " " << hashName(fieldType->getFieldName());
if (fieldType->isArray())
out << arrayBrackets(*fieldType);
out << ";\n";
}
out << "}";
mDeclaredStructs.insert(type.getTypeName());
} }
else else
{ {
...@@ -138,15 +122,16 @@ const ConstantUnion* TOutputGLSLBase::writeConstantUnion(const TType& type, ...@@ -138,15 +122,16 @@ const ConstantUnion* TOutputGLSLBase::writeConstantUnion(const TType& type,
if (type.getBasicType() == EbtStruct) if (type.getBasicType() == EbtStruct)
{ {
out << hashName(type.getTypeName()) << "("; const TStructure* structure = type.getStruct();
const TTypeList* structure = type.getStruct(); out << hashName(structure->name()) << "(";
ASSERT(structure != NULL);
for (size_t i = 0; i < structure->size(); ++i) const TFieldList& fields = structure->fields();
for (size_t i = 0; i < fields.size(); ++i)
{ {
const TType* fieldType = (*structure)[i].type; const TType* fieldType = fields[i]->type();
ASSERT(fieldType != NULL); ASSERT(fieldType != NULL);
pConstUnion = writeConstantUnion(*fieldType, pConstUnion); pConstUnion = writeConstantUnion(*fieldType, pConstUnion);
if (i != structure->size() - 1) out << ", "; if (i != fields.size() - 1) out << ", ";
} }
out << ")"; out << ")";
} }
...@@ -260,12 +245,18 @@ bool TOutputGLSLBase::visitBinary(Visit visit, TIntermBinary* node) ...@@ -260,12 +245,18 @@ bool TOutputGLSLBase::visitBinary(Visit visit, TIntermBinary* node)
case EOpIndexDirectStruct: case EOpIndexDirectStruct:
if (visit == InVisit) 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 << "."; out << ".";
// TODO(alokp): ASSERT const TStructure* structure = node->getLeft()->getType().getStruct();
TString fieldName = node->getType().getFieldName(); const TIntermConstantUnion* index = node->getRight()->getAsConstantUnion();
const TField* field = structure->fields()[index->getIConst(0)];
const TType& structType = node->getLeft()->getType(); TString fieldName = field->name();
if (!mSymbolTable.findBuiltIn(structType.getTypeName())) if (!mSymbolTable.findBuiltIn(structure->name()))
fieldName = hashName(fieldName); fieldName = hashName(fieldName);
out << fieldName; out << fieldName;
...@@ -596,7 +587,7 @@ bool TOutputGLSLBase::visitAggregate(Visit visit, TIntermAggregate* node) ...@@ -596,7 +587,7 @@ bool TOutputGLSLBase::visitAggregate(Visit visit, TIntermAggregate* node)
{ {
const TType& type = node->getType(); const TType& type = node->getType();
ASSERT(type.getBasicType() == EbtStruct); ASSERT(type.getBasicType() == EbtStruct);
out << hashName(type.getTypeName()) << "("; out << hashName(type.getStruct()->name()) << "(";
} }
else if (visit == InVisit) else if (visit == InVisit)
{ {
...@@ -765,7 +756,7 @@ TString TOutputGLSLBase::getTypeName(const TType& type) ...@@ -765,7 +756,7 @@ TString TOutputGLSLBase::getTypeName(const TType& type)
else else
{ {
if (type.getBasicType() == EbtStruct) if (type.getBasicType() == EbtStruct)
out << hashName(type.getTypeName()); out << hashName(type.getStruct()->name());
else else
out << type.getBasicString(); out << type.getBasicString();
} }
...@@ -798,3 +789,29 @@ TString TOutputGLSLBase::hashFunctionName(const TString& mangled_name) ...@@ -798,3 +789,29 @@ TString TOutputGLSLBase::hashFunctionName(const TString& mangled_name)
return name; return name;
return hashName(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());
}
...@@ -52,6 +52,9 @@ protected: ...@@ -52,6 +52,9 @@ protected:
TString hashFunctionName(const TString& mangled_name); TString hashFunctionName(const TString& mangled_name);
private: private:
bool structDeclared(const TStructure* structure) const;
void declareStruct(const TStructure* structure);
TInfoSinkBase& mObjSink; TInfoSinkBase& mObjSink;
bool mDeclaringVariables; bool mDeclaringVariables;
......
...@@ -40,14 +40,14 @@ class OutputHLSL : public TIntermTraverser ...@@ -40,14 +40,14 @@ class OutputHLSL : public TIntermTraverser
TString typeString(const TType &type); TString typeString(const TType &type);
TString textureString(const TType &type); TString textureString(const TType &type);
TString interpolationString(TQualifier qualifier); TString interpolationString(TQualifier qualifier);
TString structureString(const TType &structType, bool useHLSLRowMajorPacking, bool useStd140Packing); TString structureString(const TStructure &structure, bool useHLSLRowMajorPacking, bool useStd140Packing);
TString structureTypeName(const TType &structType, bool useHLSLRowMajorPacking, bool useStd140Packing); TString structureTypeName(const TStructure &structure, bool useHLSLRowMajorPacking, bool useStd140Packing);
static TString qualifierString(TQualifier qualifier); static TString qualifierString(TQualifier qualifier);
static TString arrayString(const TType &type); static TString arrayString(const TType &type);
static TString initializer(const TType &type); static TString initializer(const TType &type);
static TString decorate(const TString &string); // Prepends an underscore to avoid naming clashes static TString decorate(const TString &string); // Prepends an underscore to avoid naming clashes
static TString decorateUniform(const TString &string, const TType &type); static TString decorateUniform(const TString &string, const TType &type);
static TString decorateField(const TString &string, const TType &structure); static TString decorateField(const TString &string, const TStructure &structure);
protected: protected:
void header(); void header();
...@@ -187,20 +187,20 @@ class OutputHLSL : public TIntermTraverser ...@@ -187,20 +187,20 @@ class OutputHLSL : public TIntermTraverser
TString registerString(TIntermSymbol *operand); TString registerString(TIntermSymbol *operand);
int samplerRegister(TIntermSymbol *sampler); int samplerRegister(TIntermSymbol *sampler);
int uniformRegister(TIntermSymbol *uniform); int uniformRegister(TIntermSymbol *uniform);
void declareUniformToList(const TType &type, const TString &name, int index, ActiveUniforms& output); void declareUniformToList(const TType &type, const TString &name, int registerIndex, ActiveUniforms& output);
void declareUniform(const TType &type, const TString &name, int index); void declareUniform(const TType &type, const TString &name, int index);
TString interfaceBlockUniformName(const TType &interfaceBlockType, const TType &uniformType); TString interfaceBlockFieldString(const TInterfaceBlock &interfaceBlock, const TField &field);
TString decoratePrivate(const TString &privateText); TString decoratePrivate(const TString &privateText);
TString interfaceBlockStructName(const TType &interfaceBlockType); TString interfaceBlockStructNameString(const TInterfaceBlock &interfaceBlockType);
TString interfaceBlockInstanceString(const TType& interfaceBlockType, unsigned int arrayIndex); TString interfaceBlockInstanceString(const TInterfaceBlock& interfaceBlock, unsigned int arrayIndex);
TString interfaceBlockMemberTypeString(const TType &memberType, TLayoutBlockStorage blockStorage); TString interfaceBlockFieldTypeString(const TField &field, TLayoutBlockStorage blockStorage);
TString interfaceBlockMemberString(const TTypeList &typeList, TLayoutBlockStorage blockStorage); TString interfaceBlockFieldString(const TInterfaceBlock &interfaceBlock, TLayoutBlockStorage blockStorage);
TString interfaceBlockStructString(const TType &interfaceBlockType); TString interfaceBlockStructString(const TInterfaceBlock &interfaceBlock);
TString interfaceBlockString(const TType &interfaceBlockType, unsigned int registerIndex, unsigned int arrayIndex); TString interfaceBlockString(const TInterfaceBlock &interfaceBlock, unsigned int registerIndex, unsigned int arrayIndex);
TString std140PrePaddingString(const TType &type, int *elementIndex); TString std140PrePaddingString(const TType &type, int *elementIndex);
TString std140PostPaddingString(const TType &type, bool useHLSLRowMajorPacking); TString std140PostPaddingString(const TType &type, bool useHLSLRowMajorPacking);
TString structInitializerString(int indent, const TTypeList &structMembers, const TString &structName); TString structInitializerString(int indent, const TStructure &structure, const TString &rhsStructName);
static GLenum glVariableType(const TType &type); static GLenum glVariableType(const TType &type);
static GLenum glVariablePrecision(const TType &type); static GLenum glVariablePrecision(const TType &type);
......
...@@ -142,11 +142,11 @@ struct TParseContext { ...@@ -142,11 +142,11 @@ struct TParseContext {
TIntermTyped* addIndexExpression(TIntermTyped *baseExpression, const TSourceLoc& location, TIntermTyped *indexExpression); TIntermTyped* addIndexExpression(TIntermTyped *baseExpression, const TSourceLoc& location, TIntermTyped *indexExpression);
TIntermTyped* addFieldSelectionExpression(TIntermTyped *baseExpression, const TSourceLoc& dotLocation, const TString &fieldString, const TSourceLoc& fieldLocation); TIntermTyped* addFieldSelectionExpression(TIntermTyped *baseExpression, const TSourceLoc& dotLocation, const TString &fieldString, const TSourceLoc& fieldLocation);
TTypeList *addStructDeclaratorList(const TPublicType& typeSpecifier, TTypeList *typeList); TFieldList *addStructDeclaratorList(const TPublicType& typeSpecifier, TFieldList *fieldList);
TPublicType addStructure(const TSourceLoc& structLine, const TSourceLoc& nameLine, const TString &structName, TTypeList* typeList); TPublicType addStructure(const TSourceLoc& structLine, const TSourceLoc& nameLine, const TString *structName, TFieldList* fieldList);
TIntermAggregate* addInterfaceBlock(const TPublicType& typeQualifier, const TSourceLoc& nameLine, const TString& blockName, TTypeList* typeList, TIntermAggregate* addInterfaceBlock(const TPublicType& typeQualifier, const TSourceLoc& nameLine, const TString& blockName, TFieldList* fieldList,
const TString& instanceName, const TSourceLoc& instanceLine, TIntermTyped* arrayIndex, const TSourceLoc& arrayIndexLine); const TString* instanceName, const TSourceLoc& instanceLine, TIntermTyped* arrayIndex, const TSourceLoc& arrayIndexLine);
TLayoutQualifier parseLayoutQualifier(const TString &qualifierType, const TSourceLoc& qualifierTypeLine); TLayoutQualifier parseLayoutQualifier(const TString &qualifierType, const TSourceLoc& qualifierTypeLine);
TLayoutQualifier parseLayoutQualifier(const TString &qualifierType, const TSourceLoc& qualifierTypeLine, const TString &intValueString, int intValue, const TSourceLoc& intValueLine); TLayoutQualifier parseLayoutQualifier(const TString &qualifierType, const TSourceLoc& qualifierTypeLine, const TString &intValueString, int intValue, const TSourceLoc& intValueLine);
...@@ -158,7 +158,7 @@ struct TParseContext { ...@@ -158,7 +158,7 @@ struct TParseContext {
bool enterStructDeclaration(const TSourceLoc& line, const TString& identifier); bool enterStructDeclaration(const TSourceLoc& line, const TString& identifier);
void exitStructDeclaration(); void exitStructDeclaration();
bool structNestingErrorCheck(const TSourceLoc& line, const TType& fieldType); bool structNestingErrorCheck(const TSourceLoc& line, const TField& field);
}; };
int PaParseStrings(size_t count, const char* const string[], const int length[], int PaParseStrings(size_t count, const char* const string[], const int length[],
......
...@@ -23,20 +23,19 @@ int TSymbolTableLevel::uniqueId = 0; ...@@ -23,20 +23,19 @@ int TSymbolTableLevel::uniqueId = 0;
TType::TType(const TPublicType &p) : TType::TType(const TPublicType &p) :
type(p.type), precision(p.precision), qualifier(p.qualifier), primarySize(p.primarySize), secondarySize(p.secondarySize), array(p.array), layoutQualifier(p.layoutQualifier), arraySize(p.arraySize), type(p.type), precision(p.precision), qualifier(p.qualifier), primarySize(p.primarySize), secondarySize(p.secondarySize), array(p.array), layoutQualifier(p.layoutQualifier), arraySize(p.arraySize),
interfaceBlockType(0), structure(0), structureSize(0), deepestStructNesting(0), fieldName(0), mangled(0), typeName(0) interfaceBlock(0), structure(0)
{ {
if (p.userDef) { if (p.userDef) {
structure = p.userDef->getStruct(); structure = p.userDef->getStruct();
typeName = NewPoolTString(p.userDef->getTypeName().c_str());
computeDeepestStructNesting();
} }
} }
// //
// Recursively generate mangled names. // Recursively generate mangled names.
// //
void TType::buildMangledName(TString& mangledName) TString TType::buildMangledName() const
{ {
TString mangledName;
if (isMatrix()) if (isMatrix())
mangledName += 'm'; mangledName += 'm';
else if (isVector()) else if (isVector())
...@@ -45,7 +44,6 @@ void TType::buildMangledName(TString& mangledName) ...@@ -45,7 +44,6 @@ void TType::buildMangledName(TString& mangledName)
switch (type) { switch (type) {
case EbtFloat: mangledName += 'f'; break; case EbtFloat: mangledName += 'f'; break;
case EbtInt: mangledName += 'i'; break; case EbtInt: mangledName += 'i'; break;
case EbtUInt: mangledName += 'u'; break;
case EbtBool: mangledName += 'b'; break; case EbtBool: mangledName += 'b'; break;
case EbtSampler2D: mangledName += "s2"; break; case EbtSampler2D: mangledName += "s2"; break;
case EbtSampler3D: mangledName += "s3"; break; case EbtSampler3D: mangledName += "s3"; break;
...@@ -59,33 +57,9 @@ void TType::buildMangledName(TString& mangledName) ...@@ -59,33 +57,9 @@ void TType::buildMangledName(TString& mangledName)
case EbtUSampler3D: mangledName += "us3"; break; case EbtUSampler3D: mangledName += "us3"; break;
case EbtUSamplerCube: mangledName += "usC"; break; case EbtUSamplerCube: mangledName += "usC"; break;
case EbtUSampler2DArray: mangledName += "us2a"; break; case EbtUSampler2DArray: mangledName += "us2a"; break;
case EbtStruct: case EbtStruct: mangledName += structure->mangledName(); break;
mangledName += "struct-"; case EbtInterfaceBlock: mangledName += interfaceBlock->mangledName(); break;
if (typeName) default: break;
mangledName += *typeName;
{// support MSVC++6.0
for (unsigned int i = 0; i < structure->size(); ++i) {
mangledName += '-';
(*structure)[i].type->buildMangledName(mangledName);
}
}
break;
case EbtInterfaceBlock:
{
mangledName += "interface-block-";
if (typeName)
{
mangledName += *typeName;
}
for (unsigned int i = 0; i < structure->size(); ++i)
{
mangledName += '-';
(*structure)[i].type->buildMangledName(mangledName);
}
}
break;
default:
break;
} }
if (isMatrix()) if (isMatrix())
...@@ -98,6 +72,7 @@ void TType::buildMangledName(TString& mangledName) ...@@ -98,6 +72,7 @@ void TType::buildMangledName(TString& mangledName)
{ {
mangledName += static_cast<char>('0' + getNominalSize()); mangledName += static_cast<char>('0' + getNominalSize());
} }
if (isArray()) { if (isArray()) {
char buf[20]; char buf[20];
snprintf(buf, sizeof(buf), "%d", arraySize); snprintf(buf, sizeof(buf), "%d", arraySize);
...@@ -105,6 +80,7 @@ void TType::buildMangledName(TString& mangledName) ...@@ -105,6 +80,7 @@ void TType::buildMangledName(TString& mangledName)
mangledName += buf; mangledName += buf;
mangledName += ']'; mangledName += ']';
} }
return mangledName;
} }
size_t TType::getObjectSize() const size_t TType::getObjectSize() const
...@@ -112,7 +88,7 @@ size_t TType::getObjectSize() const ...@@ -112,7 +88,7 @@ size_t TType::getObjectSize() const
size_t totalSize; size_t totalSize;
if (getBasicType() == EbtStruct) if (getBasicType() == EbtStruct)
totalSize = getStructSize(); totalSize = structure->objectSize();
else else
totalSize = primarySize * secondarySize; totalSize = primarySize * secondarySize;
...@@ -127,57 +103,47 @@ size_t TType::getObjectSize() const ...@@ -127,57 +103,47 @@ size_t TType::getObjectSize() const
return totalSize; return totalSize;
} }
size_t TType::getStructSize() const bool TStructure::containsArrays() const
{ {
if (!getStruct()) { for (size_t i = 0; i < mFields->size(); ++i) {
assert(false && "Not a struct"); const TType* fieldType = (*mFields)[i]->type();
return 0; if (fieldType->isArray() || fieldType->isStructureContainingArrays())
} return true;
if (structureSize == 0) {
for (TTypeList::const_iterator tl = getStruct()->begin(); tl != getStruct()->end(); tl++) {
size_t fieldSize = ((*tl).type)->getObjectSize();
if (fieldSize > INT_MAX - structureSize)
structureSize = INT_MAX;
else
structureSize += fieldSize;
}
} }
return false;
return structureSize;
} }
void TType::computeDeepestStructNesting() TString TFieldListCollection::buildMangledName() const
{ {
if (!getStruct()) { TString mangledName(mangledNamePrefix());
return; mangledName += *mName;
} for (size_t i = 0; i < mFields->size(); ++i) {
mangledName += '-';
int maxNesting = 0; mangledName += (*mFields)[i]->type()->getMangledName();
for (TTypeList::const_iterator tl = getStruct()->begin(); tl != getStruct()->end(); ++tl) {
maxNesting = std::max(maxNesting, ((*tl).type)->getDeepestStructNesting());
} }
return mangledName;
deepestStructNesting = 1 + maxNesting;
} }
bool TType::isStructureContainingArrays() const size_t TFieldListCollection::calculateObjectSize() const
{ {
if (!structure) size_t size = 0;
{ for (size_t i = 0; i < mFields->size(); ++i) {
return false; size_t fieldSize = (*mFields)[i]->type()->getObjectSize();
if (fieldSize > INT_MAX - size)
size = INT_MAX;
else
size += fieldSize;
} }
return size;
}
for (TTypeList::const_iterator member = structure->begin(); member != structure->end(); member++) int TStructure::calculateDeepestNesting() const
{ {
if (member->type->isArray() || int maxNesting = 0;
member->type->isStructureContainingArrays()) for (size_t i = 0; i < mFields->size(); ++i) {
{ maxNesting = std::max(maxNesting, (*mFields)[i]->type()->getDeepestStructNesting());
return true;
}
} }
return 1 + maxNesting;
return false;
} }
// //
......
...@@ -173,14 +173,15 @@ void getUserDefinedVariableInfo(const TType& type, ...@@ -173,14 +173,15 @@ void getUserDefinedVariableInfo(const TType& type,
TVariableInfoList& infoList, TVariableInfoList& infoList,
ShHashFunction64 hashFunction) ShHashFunction64 hashFunction)
{ {
ASSERT(type.getBasicType() == EbtStruct); ASSERT(type.getBasicType() == EbtStruct || type.isInterfaceBlock());
const TTypeList* structure = type.getStruct(); const TFieldList& fields = type.getStruct()->fields();
for (size_t i = 0; i < structure->size(); ++i) { for (size_t i = 0; i < fields.size(); ++i) {
const TType* fieldType = (*structure)[i].type; const TType& fieldType = *(fields[i]->type());
getVariableInfo(*fieldType, const TString& fieldName = fields[i]->name();
name + "." + fieldType->getFieldName(), getVariableInfo(fieldType,
mappedName + "." + TIntermTraverser::hash(fieldType->getFieldName(), hashFunction), name + "." + fieldName,
mappedName + "." + TIntermTraverser::hash(fieldName, hashFunction),
infoList, infoList,
hashFunction); hashFunction);
} }
......
...@@ -79,8 +79,8 @@ WHICH GENERATES THE GLSL ES PARSER (glslang_tab.cpp AND glslang_tab.h). ...@@ -79,8 +79,8 @@ WHICH GENERATES THE GLSL ES PARSER (glslang_tab.cpp AND glslang_tab.h).
TQualifier qualifier; TQualifier qualifier;
TFunction* function; TFunction* function;
TParameter param; TParameter param;
TTypeLine typeLine; TField* field;
TTypeList* typeList; TFieldList* fieldList;
}; };
} interm; } interm;
} }
...@@ -189,8 +189,8 @@ extern void yyerror(YYLTYPE* yylloc, TParseContext* context, const char* reason) ...@@ -189,8 +189,8 @@ extern void yyerror(YYLTYPE* yylloc, TParseContext* context, const char* reason)
%type <interm.type> type_qualifier fully_specified_type type_specifier storage_qualifier interpolation_qualifier %type <interm.type> type_qualifier fully_specified_type type_specifier storage_qualifier interpolation_qualifier
%type <interm.type> type_specifier_no_prec type_specifier_nonarray %type <interm.type> type_specifier_no_prec type_specifier_nonarray
%type <interm.type> struct_specifier %type <interm.type> struct_specifier
%type <interm.typeLine> struct_declarator %type <interm.field> struct_declarator
%type <interm.typeList> struct_declarator_list struct_declaration struct_declaration_list %type <interm.fieldList> struct_declarator_list struct_declaration struct_declaration_list
%type <interm.function> function_header function_declarator function_identifier %type <interm.function> function_header function_declarator function_identifier
%type <interm.function> function_header_with_parameters function_call_header %type <interm.function> function_header_with_parameters function_call_header
%type <interm> function_call_header_with_parameters function_call_header_no_parameters function_call_generic function_prototype %type <interm> function_call_header_with_parameters function_call_header_no_parameters function_call_generic function_prototype
...@@ -818,15 +818,15 @@ declaration ...@@ -818,15 +818,15 @@ declaration
} }
| type_qualifier enter_struct struct_declaration_list RIGHT_BRACE SEMICOLON { | type_qualifier enter_struct struct_declaration_list RIGHT_BRACE SEMICOLON {
ES3_ONLY(getQualifierString($1.qualifier), @1, "interface blocks"); ES3_ONLY(getQualifierString($1.qualifier), @1, "interface blocks");
$$ = context->addInterfaceBlock($1, @2, *$2.string, $3, "", @$, NULL, @$); $$ = context->addInterfaceBlock($1, @2, *$2.string, $3, NULL, @$, NULL, @$);
} }
| type_qualifier enter_struct struct_declaration_list RIGHT_BRACE IDENTIFIER SEMICOLON { | type_qualifier enter_struct struct_declaration_list RIGHT_BRACE IDENTIFIER SEMICOLON {
ES3_ONLY(getQualifierString($1.qualifier), @1, "interface blocks"); ES3_ONLY(getQualifierString($1.qualifier), @1, "interface blocks");
$$ = context->addInterfaceBlock($1, @2, *$2.string, $3, *$5.string, @5, NULL, @$); $$ = context->addInterfaceBlock($1, @2, *$2.string, $3, $5.string, @5, NULL, @$);
} }
| type_qualifier enter_struct struct_declaration_list RIGHT_BRACE IDENTIFIER LEFT_BRACKET constant_expression RIGHT_BRACKET SEMICOLON { | type_qualifier enter_struct struct_declaration_list RIGHT_BRACE IDENTIFIER LEFT_BRACKET constant_expression RIGHT_BRACKET SEMICOLON {
ES3_ONLY(getQualifierString($1.qualifier), @1, "interface blocks"); ES3_ONLY(getQualifierString($1.qualifier), @1, "interface blocks");
$$ = context->addInterfaceBlock($1, @2, *$2.string, $3, *$5.string, @5, $7, @6); $$ = context->addInterfaceBlock($1, @2, *$2.string, $3, $5.string, @5, $7, @6);
} }
| type_qualifier SEMICOLON { | type_qualifier SEMICOLON {
context->parseGlobalLayoutQualifier($1); context->parseGlobalLayoutQualifier($1);
...@@ -1519,10 +1519,10 @@ type_specifier_nonarray ...@@ -1519,10 +1519,10 @@ type_specifier_nonarray
struct_specifier struct_specifier
: STRUCT identifier LEFT_BRACE { if (context->enterStructDeclaration(@2, *$2.string)) context->recover(); } struct_declaration_list RIGHT_BRACE { : STRUCT identifier LEFT_BRACE { if (context->enterStructDeclaration(@2, *$2.string)) context->recover(); } struct_declaration_list RIGHT_BRACE {
$$ = context->addStructure(@1, @2, *$2.string, $5); $$ = context->addStructure(@1, @2, $2.string, $5);
} }
| STRUCT LEFT_BRACE { if (context->enterStructDeclaration(@2, *$2.string)) context->recover(); } struct_declaration_list RIGHT_BRACE { | STRUCT LEFT_BRACE { if (context->enterStructDeclaration(@2, *$2.string)) context->recover(); } struct_declaration_list RIGHT_BRACE {
$$ = context->addStructure(@1, @$, "", $4); $$ = context->addStructure(@1, @$, NewPoolTString(""), $4);
} }
; ;
...@@ -1532,14 +1532,15 @@ struct_declaration_list ...@@ -1532,14 +1532,15 @@ struct_declaration_list
} }
| struct_declaration_list struct_declaration { | struct_declaration_list struct_declaration {
$$ = $1; $$ = $1;
for (unsigned int i = 0; i < $2->size(); ++i) { for (size_t i = 0; i < $2->size(); ++i) {
for (unsigned int j = 0; j < $$->size(); ++j) { TField* field = (*$2)[i];
if ((*$$)[j].type->getFieldName() == (*$2)[i].type->getFieldName()) { for (size_t j = 0; j < $$->size(); ++j) {
context->error((*$2)[i].line, "duplicate field name in structure:", "struct", (*$2)[i].type->getFieldName().c_str()); if ((*$$)[j]->name() == field->name()) {
context->error(@2, "duplicate field name in structure:", "struct", field->name().c_str());
context->recover(); context->recover();
} }
} }
$$->push_back((*$2)[i]); $$->push_back(field);
} }
} }
; ;
...@@ -1558,7 +1559,7 @@ struct_declaration ...@@ -1558,7 +1559,7 @@ struct_declaration
struct_declarator_list struct_declarator_list
: struct_declarator { : struct_declarator {
$$ = NewPoolTTypeList(); $$ = NewPoolTFieldList();
$$->push_back($1); $$->push_back($1);
} }
| struct_declarator_list COMMA struct_declarator { | struct_declarator_list COMMA struct_declarator {
...@@ -1571,20 +1572,20 @@ struct_declarator ...@@ -1571,20 +1572,20 @@ struct_declarator
if (context->reservedErrorCheck(@1, *$1.string)) if (context->reservedErrorCheck(@1, *$1.string))
context->recover(); context->recover();
$$.type = new TType(EbtVoid, EbpUndefined); TType* type = new TType(EbtVoid, EbpUndefined);
$$.type->setFieldName(*$1.string); $$ = new TField(type, $1.string, @1);
} }
| identifier LEFT_BRACKET constant_expression RIGHT_BRACKET { | identifier LEFT_BRACKET constant_expression RIGHT_BRACKET {
if (context->reservedErrorCheck(@1, *$1.string)) if (context->reservedErrorCheck(@1, *$1.string))
context->recover(); context->recover();
$$.type = new TType(EbtVoid, EbpUndefined); TType* type = new TType(EbtVoid, EbpUndefined);
$$.type->setFieldName(*$1.string);
int size; int size;
if (context->arraySizeErrorCheck(@2, $3, size)) if (context->arraySizeErrorCheck(@3, $3, size))
context->recover(); context->recover();
$$.type->setArraySize(size); type->setArraySize(size);
$$ = new TField(type, $1.string, @1);
} }
; ;
......
...@@ -294,8 +294,8 @@ typedef union YYSTYPE ...@@ -294,8 +294,8 @@ typedef union YYSTYPE
TQualifier qualifier; TQualifier qualifier;
TFunction* function; TFunction* function;
TParameter param; TParameter param;
TTypeLine typeLine; TField* field;
TTypeList* typeList; TFieldList* fieldList;
}; };
} interm; } interm;
...@@ -808,13 +808,13 @@ static const yytype_uint16 yyrline[] = ...@@ -808,13 +808,13 @@ static const yytype_uint16 yyrline[] =
1355, 1360, 1365, 1370, 1375, 1380, 1385, 1390, 1395, 1400, 1355, 1360, 1365, 1370, 1375, 1380, 1385, 1390, 1395, 1400,
1405, 1410, 1415, 1420, 1425, 1430, 1435, 1440, 1444, 1448, 1405, 1410, 1415, 1420, 1425, 1430, 1435, 1440, 1444, 1448,
1452, 1456, 1460, 1464, 1468, 1472, 1476, 1480, 1484, 1488, 1452, 1456, 1460, 1464, 1468, 1472, 1476, 1480, 1484, 1488,
1496, 1504, 1508, 1521, 1521, 1524, 1524, 1530, 1533, 1548, 1496, 1504, 1508, 1521, 1521, 1524, 1524, 1530, 1533, 1549,
1551, 1560, 1564, 1570, 1577, 1592, 1596, 1600, 1601, 1607, 1552, 1561, 1565, 1571, 1578, 1593, 1597, 1601, 1602, 1608,
1608, 1609, 1610, 1611, 1615, 1616, 1616, 1616, 1626, 1627, 1609, 1610, 1611, 1612, 1616, 1617, 1617, 1617, 1627, 1628,
1631, 1631, 1632, 1632, 1637, 1640, 1650, 1653, 1659, 1660, 1632, 1632, 1633, 1633, 1638, 1641, 1651, 1654, 1660, 1661,
1664, 1672, 1676, 1686, 1691, 1708, 1708, 1713, 1713, 1720, 1665, 1673, 1677, 1687, 1692, 1709, 1709, 1714, 1714, 1721,
1720, 1728, 1731, 1737, 1740, 1746, 1750, 1757, 1764, 1771, 1721, 1729, 1732, 1738, 1741, 1747, 1751, 1758, 1765, 1772,
1778, 1789, 1798, 1802, 1809, 1812, 1818, 1818 1779, 1790, 1799, 1803, 1810, 1813, 1819, 1819
}; };
#endif #endif
...@@ -3333,7 +3333,7 @@ yyreduce: ...@@ -3333,7 +3333,7 @@ yyreduce:
{ {
ES3_ONLY(getQualifierString((yyvsp[(1) - (5)].interm.type).qualifier), (yylsp[(1) - (5)]), "interface blocks"); ES3_ONLY(getQualifierString((yyvsp[(1) - (5)].interm.type).qualifier), (yylsp[(1) - (5)]), "interface blocks");
(yyval.interm.intermNode) = context->addInterfaceBlock((yyvsp[(1) - (5)].interm.type), (yylsp[(2) - (5)]), *(yyvsp[(2) - (5)].lex).string, (yyvsp[(3) - (5)].interm.typeList), "", (yyloc), NULL, (yyloc)); (yyval.interm.intermNode) = context->addInterfaceBlock((yyvsp[(1) - (5)].interm.type), (yylsp[(2) - (5)]), *(yyvsp[(2) - (5)].lex).string, (yyvsp[(3) - (5)].interm.fieldList), NULL, (yyloc), NULL, (yyloc));
} }
break; break;
...@@ -3341,7 +3341,7 @@ yyreduce: ...@@ -3341,7 +3341,7 @@ yyreduce:
{ {
ES3_ONLY(getQualifierString((yyvsp[(1) - (6)].interm.type).qualifier), (yylsp[(1) - (6)]), "interface blocks"); ES3_ONLY(getQualifierString((yyvsp[(1) - (6)].interm.type).qualifier), (yylsp[(1) - (6)]), "interface blocks");
(yyval.interm.intermNode) = context->addInterfaceBlock((yyvsp[(1) - (6)].interm.type), (yylsp[(2) - (6)]), *(yyvsp[(2) - (6)].lex).string, (yyvsp[(3) - (6)].interm.typeList), *(yyvsp[(5) - (6)].lex).string, (yylsp[(5) - (6)]), NULL, (yyloc)); (yyval.interm.intermNode) = context->addInterfaceBlock((yyvsp[(1) - (6)].interm.type), (yylsp[(2) - (6)]), *(yyvsp[(2) - (6)].lex).string, (yyvsp[(3) - (6)].interm.fieldList), (yyvsp[(5) - (6)].lex).string, (yylsp[(5) - (6)]), NULL, (yyloc));
} }
break; break;
...@@ -3349,7 +3349,7 @@ yyreduce: ...@@ -3349,7 +3349,7 @@ yyreduce:
{ {
ES3_ONLY(getQualifierString((yyvsp[(1) - (9)].interm.type).qualifier), (yylsp[(1) - (9)]), "interface blocks"); ES3_ONLY(getQualifierString((yyvsp[(1) - (9)].interm.type).qualifier), (yylsp[(1) - (9)]), "interface blocks");
(yyval.interm.intermNode) = context->addInterfaceBlock((yyvsp[(1) - (9)].interm.type), (yylsp[(2) - (9)]), *(yyvsp[(2) - (9)].lex).string, (yyvsp[(3) - (9)].interm.typeList), *(yyvsp[(5) - (9)].lex).string, (yylsp[(5) - (9)]), (yyvsp[(7) - (9)].interm.intermTypedNode), (yylsp[(6) - (9)])); (yyval.interm.intermNode) = context->addInterfaceBlock((yyvsp[(1) - (9)].interm.type), (yylsp[(2) - (9)]), *(yyvsp[(2) - (9)].lex).string, (yyvsp[(3) - (9)].interm.fieldList), (yyvsp[(5) - (9)].lex).string, (yylsp[(5) - (9)]), (yyvsp[(7) - (9)].interm.intermTypedNode), (yylsp[(6) - (9)]));
} }
break; break;
...@@ -4381,7 +4381,7 @@ yyreduce: ...@@ -4381,7 +4381,7 @@ yyreduce:
case 184: case 184:
{ {
(yyval.interm.type) = context->addStructure((yylsp[(1) - (6)]), (yylsp[(2) - (6)]), *(yyvsp[(2) - (6)].lex).string, (yyvsp[(5) - (6)].interm.typeList)); (yyval.interm.type) = context->addStructure((yylsp[(1) - (6)]), (yylsp[(2) - (6)]), (yyvsp[(2) - (6)].lex).string, (yyvsp[(5) - (6)].interm.fieldList));
} }
break; break;
...@@ -4393,29 +4393,30 @@ yyreduce: ...@@ -4393,29 +4393,30 @@ yyreduce:
case 186: case 186:
{ {
(yyval.interm.type) = context->addStructure((yylsp[(1) - (5)]), (yyloc), "", (yyvsp[(4) - (5)].interm.typeList)); (yyval.interm.type) = context->addStructure((yylsp[(1) - (5)]), (yyloc), NewPoolTString(""), (yyvsp[(4) - (5)].interm.fieldList));
} }
break; break;
case 187: case 187:
{ {
(yyval.interm.typeList) = (yyvsp[(1) - (1)].interm.typeList); (yyval.interm.fieldList) = (yyvsp[(1) - (1)].interm.fieldList);
} }
break; break;
case 188: case 188:
{ {
(yyval.interm.typeList) = (yyvsp[(1) - (2)].interm.typeList); (yyval.interm.fieldList) = (yyvsp[(1) - (2)].interm.fieldList);
for (unsigned int i = 0; i < (yyvsp[(2) - (2)].interm.typeList)->size(); ++i) { for (size_t i = 0; i < (yyvsp[(2) - (2)].interm.fieldList)->size(); ++i) {
for (unsigned int j = 0; j < (yyval.interm.typeList)->size(); ++j) { TField* field = (*(yyvsp[(2) - (2)].interm.fieldList))[i];
if ((*(yyval.interm.typeList))[j].type->getFieldName() == (*(yyvsp[(2) - (2)].interm.typeList))[i].type->getFieldName()) { for (size_t j = 0; j < (yyval.interm.fieldList)->size(); ++j) {
context->error((*(yyvsp[(2) - (2)].interm.typeList))[i].line, "duplicate field name in structure:", "struct", (*(yyvsp[(2) - (2)].interm.typeList))[i].type->getFieldName().c_str()); if ((*(yyval.interm.fieldList))[j]->name() == field->name()) {
context->error((yylsp[(2) - (2)]), "duplicate field name in structure:", "struct", field->name().c_str());
context->recover(); context->recover();
} }
} }
(yyval.interm.typeList)->push_back((*(yyvsp[(2) - (2)].interm.typeList))[i]); (yyval.interm.fieldList)->push_back(field);
} }
} }
break; break;
...@@ -4423,7 +4424,7 @@ yyreduce: ...@@ -4423,7 +4424,7 @@ yyreduce:
case 189: case 189:
{ {
(yyval.interm.typeList) = context->addStructDeclaratorList((yyvsp[(1) - (3)].interm.type), (yyvsp[(2) - (3)].interm.typeList)); (yyval.interm.fieldList) = context->addStructDeclaratorList((yyvsp[(1) - (3)].interm.type), (yyvsp[(2) - (3)].interm.fieldList));
} }
break; break;
...@@ -4433,22 +4434,22 @@ yyreduce: ...@@ -4433,22 +4434,22 @@ yyreduce:
// ES3 Only, but errors should be handled elsewhere // ES3 Only, but errors should be handled elsewhere
(yyvsp[(2) - (4)].interm.type).qualifier = (yyvsp[(1) - (4)].interm.type).qualifier; (yyvsp[(2) - (4)].interm.type).qualifier = (yyvsp[(1) - (4)].interm.type).qualifier;
(yyvsp[(2) - (4)].interm.type).layoutQualifier = (yyvsp[(1) - (4)].interm.type).layoutQualifier; (yyvsp[(2) - (4)].interm.type).layoutQualifier = (yyvsp[(1) - (4)].interm.type).layoutQualifier;
(yyval.interm.typeList) = context->addStructDeclaratorList((yyvsp[(2) - (4)].interm.type), (yyvsp[(3) - (4)].interm.typeList)); (yyval.interm.fieldList) = context->addStructDeclaratorList((yyvsp[(2) - (4)].interm.type), (yyvsp[(3) - (4)].interm.fieldList));
} }
break; break;
case 191: case 191:
{ {
(yyval.interm.typeList) = NewPoolTTypeList(); (yyval.interm.fieldList) = NewPoolTFieldList();
(yyval.interm.typeList)->push_back((yyvsp[(1) - (1)].interm.typeLine)); (yyval.interm.fieldList)->push_back((yyvsp[(1) - (1)].interm.field));
} }
break; break;
case 192: case 192:
{ {
(yyval.interm.typeList)->push_back((yyvsp[(3) - (3)].interm.typeLine)); (yyval.interm.fieldList)->push_back((yyvsp[(3) - (3)].interm.field));
} }
break; break;
...@@ -4458,8 +4459,8 @@ yyreduce: ...@@ -4458,8 +4459,8 @@ yyreduce:
if (context->reservedErrorCheck((yylsp[(1) - (1)]), *(yyvsp[(1) - (1)].lex).string)) if (context->reservedErrorCheck((yylsp[(1) - (1)]), *(yyvsp[(1) - (1)].lex).string))
context->recover(); context->recover();
(yyval.interm.typeLine).type = new TType(EbtVoid, EbpUndefined); TType* type = new TType(EbtVoid, EbpUndefined);
(yyval.interm.typeLine).type->setFieldName(*(yyvsp[(1) - (1)].lex).string); (yyval.interm.field) = new TField(type, (yyvsp[(1) - (1)].lex).string, (yylsp[(1) - (1)]));
} }
break; break;
...@@ -4469,13 +4470,13 @@ yyreduce: ...@@ -4469,13 +4470,13 @@ yyreduce:
if (context->reservedErrorCheck((yylsp[(1) - (4)]), *(yyvsp[(1) - (4)].lex).string)) if (context->reservedErrorCheck((yylsp[(1) - (4)]), *(yyvsp[(1) - (4)].lex).string))
context->recover(); context->recover();
(yyval.interm.typeLine).type = new TType(EbtVoid, EbpUndefined); TType* type = new TType(EbtVoid, EbpUndefined);
(yyval.interm.typeLine).type->setFieldName(*(yyvsp[(1) - (4)].lex).string);
int size; int size;
if (context->arraySizeErrorCheck((yylsp[(2) - (4)]), (yyvsp[(3) - (4)].interm.intermTypedNode), size)) if (context->arraySizeErrorCheck((yylsp[(3) - (4)]), (yyvsp[(3) - (4)].interm.intermTypedNode), size))
context->recover(); context->recover();
(yyval.interm.typeLine).type->setArraySize(size); type->setArraySize(size);
(yyval.interm.field) = new TField(type, (yyvsp[(1) - (4)].lex).string, (yylsp[(1) - (4)]));
} }
break; break;
......
...@@ -211,8 +211,8 @@ typedef union YYSTYPE ...@@ -211,8 +211,8 @@ typedef union YYSTYPE
TQualifier qualifier; TQualifier qualifier;
TFunction* function; TFunction* function;
TParameter param; TParameter param;
TTypeLine typeLine; TField* field;
TTypeList* typeList; TFieldList* fieldList;
}; };
} interm; } interm;
......
...@@ -271,6 +271,7 @@ public: ...@@ -271,6 +271,7 @@ public:
int getNominalSize() const { return type.getNominalSize(); } int getNominalSize() const { return type.getNominalSize(); }
int getSecondarySize() const { return type.getSecondarySize(); } int getSecondarySize() const { return type.getSecondarySize(); }
bool isInterfaceBlock() const { return type.isInterfaceBlock(); }
bool isMatrix() const { return type.isMatrix(); } bool isMatrix() const { return type.isMatrix(); }
bool isArray() const { return type.isArray(); } bool isArray() const { return type.isArray(); }
bool isVector() const { return type.isVector(); } bool isVector() const { return type.isVector(); }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment