Commit 40f48ab8 by Alexis Hetu Committed by Alexis Hétu

Unsigned int GLSL parsing fixed

Unsigned integers in GLSL were being parsed using the regular integer parser, so it was limited to INT_MAX. All values from INT_MAX + 1 to UINT_MAX could not be parsed properly. Also, added constant folding for the 4 bit conversion glsl functions. Fixes shader compilation issue in the Epic Zen Garden example: https://s3.amazonaws.com/mozilla-games/ZenGarden/EpicZenGarden.html (unfortunately, the screen is still black, so there are other issues left) Fixes WebGL 2 test: conformance2/glsl3/float-parsing.html Change-Id: Iae52b2c8e083f0e1a22599e5a583297b9850444d Reviewed-on: https://swiftshader-review.googlesource.com/16648Tested-by: 's avatarAlexis Hétu <sugoi@google.com> Reviewed-by: 's avatarNicolas Capens <nicolascapens@google.com>
parent 26a86758
...@@ -74,6 +74,18 @@ namespace sw ...@@ -74,6 +74,18 @@ namespace sw
b = t; b = t;
} }
template <typename destType, typename sourceType>
destType bitCast(const sourceType &source)
{
union
{
sourceType s;
destType d;
} sd;
sd.s = source;
return sd.d;
}
inline int iround(float x) inline int iround(float x)
{ {
return (int)floor(x + 0.5f); return (int)floor(x + 0.5f);
......
...@@ -1778,10 +1778,12 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod ...@@ -1778,10 +1778,12 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod
// //
TIntermConstantUnion *newNode = 0; TIntermConstantUnion *newNode = 0;
ConstantUnion* tempConstArray = new ConstantUnion[objectSize]; ConstantUnion* tempConstArray = new ConstantUnion[objectSize];
TType type = getType();
TBasicType basicType = type.getBasicType();
for (size_t i = 0; i < objectSize; i++) { for (size_t i = 0; i < objectSize; i++) {
switch(op) { switch(op) {
case EOpNegative: case EOpNegative:
switch (getType().getBasicType()) { switch (basicType) {
case EbtFloat: tempConstArray[i].setFConst(-unionArray[i].getFConst()); break; case EbtFloat: tempConstArray[i].setFConst(-unionArray[i].getFConst()); break;
case EbtInt: tempConstArray[i].setIConst(-unionArray[i].getIConst()); break; case EbtInt: tempConstArray[i].setIConst(-unionArray[i].getIConst()); break;
default: default:
...@@ -1790,7 +1792,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod ...@@ -1790,7 +1792,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod
} }
break; break;
case EOpLogicalNot: // this code is written for possible future use, will not get executed currently case EOpLogicalNot: // this code is written for possible future use, will not get executed currently
switch (getType().getBasicType()) { switch (basicType) {
case EbtBool: tempConstArray[i].setBConst(!unionArray[i].getBConst()); break; case EbtBool: tempConstArray[i].setBConst(!unionArray[i].getBConst()); break;
default: default:
infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine()); infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine());
...@@ -1798,7 +1800,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod ...@@ -1798,7 +1800,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod
} }
break; break;
case EOpBitwiseNot: case EOpBitwiseNot:
switch(getType().getBasicType()) { switch(basicType) {
case EbtInt: tempConstArray[i].setIConst(~unionArray[i].getIConst()); break; case EbtInt: tempConstArray[i].setIConst(~unionArray[i].getIConst()); break;
case EbtUInt: tempConstArray[i].setUConst(~unionArray[i].getUConst()); break; case EbtUInt: tempConstArray[i].setUConst(~unionArray[i].getUConst()); break;
default: default:
...@@ -1807,7 +1809,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod ...@@ -1807,7 +1809,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod
} }
break; break;
case EOpRadians: case EOpRadians:
switch(getType().getBasicType()) { switch(basicType) {
case EbtFloat: tempConstArray[i].setFConst(unionArray[i].getFConst() * 1.74532925e-2f); break; case EbtFloat: tempConstArray[i].setFConst(unionArray[i].getFConst() * 1.74532925e-2f); break;
default: default:
infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine()); infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine());
...@@ -1815,7 +1817,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod ...@@ -1815,7 +1817,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod
} }
break; break;
case EOpDegrees: case EOpDegrees:
switch(getType().getBasicType()) { switch(basicType) {
case EbtFloat: tempConstArray[i].setFConst(unionArray[i].getFConst() * 5.72957795e+1f); break; case EbtFloat: tempConstArray[i].setFConst(unionArray[i].getFConst() * 5.72957795e+1f); break;
default: default:
infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine()); infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine());
...@@ -1823,7 +1825,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod ...@@ -1823,7 +1825,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod
} }
break; break;
case EOpSin: case EOpSin:
switch(getType().getBasicType()) { switch(basicType) {
case EbtFloat: tempConstArray[i].setFConst(sinf(unionArray[i].getFConst())); break; case EbtFloat: tempConstArray[i].setFConst(sinf(unionArray[i].getFConst())); break;
default: default:
infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine()); infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine());
...@@ -1831,7 +1833,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod ...@@ -1831,7 +1833,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod
} }
break; break;
case EOpCos: case EOpCos:
switch(getType().getBasicType()) { switch(basicType) {
case EbtFloat: tempConstArray[i].setFConst(cosf(unionArray[i].getFConst())); break; case EbtFloat: tempConstArray[i].setFConst(cosf(unionArray[i].getFConst())); break;
default: default:
infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine()); infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine());
...@@ -1839,7 +1841,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod ...@@ -1839,7 +1841,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod
} }
break; break;
case EOpTan: case EOpTan:
switch(getType().getBasicType()) { switch(basicType) {
case EbtFloat: tempConstArray[i].setFConst(tanf(unionArray[i].getFConst())); break; case EbtFloat: tempConstArray[i].setFConst(tanf(unionArray[i].getFConst())); break;
default: default:
infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine()); infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine());
...@@ -1847,7 +1849,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod ...@@ -1847,7 +1849,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod
} }
break; break;
case EOpAsin: case EOpAsin:
switch(getType().getBasicType()) { switch(basicType) {
case EbtFloat: tempConstArray[i].setFConst(asinf(unionArray[i].getFConst())); break; case EbtFloat: tempConstArray[i].setFConst(asinf(unionArray[i].getFConst())); break;
default: default:
infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine()); infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine());
...@@ -1855,7 +1857,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod ...@@ -1855,7 +1857,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod
} }
break; break;
case EOpAcos: case EOpAcos:
switch(getType().getBasicType()) { switch(basicType) {
case EbtFloat: tempConstArray[i].setFConst(acosf(unionArray[i].getFConst())); break; case EbtFloat: tempConstArray[i].setFConst(acosf(unionArray[i].getFConst())); break;
default: default:
infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine()); infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine());
...@@ -1863,7 +1865,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod ...@@ -1863,7 +1865,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod
} }
break; break;
case EOpAtan: case EOpAtan:
switch(getType().getBasicType()) { switch(basicType) {
case EbtFloat: tempConstArray[i].setFConst(atanf(unionArray[i].getFConst())); break; case EbtFloat: tempConstArray[i].setFConst(atanf(unionArray[i].getFConst())); break;
default: default:
infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine()); infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine());
...@@ -1871,7 +1873,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod ...@@ -1871,7 +1873,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod
} }
break; break;
case EOpSinh: case EOpSinh:
switch(getType().getBasicType()) { switch(basicType) {
case EbtFloat: tempConstArray[i].setFConst(sinhf(unionArray[i].getFConst())); break; case EbtFloat: tempConstArray[i].setFConst(sinhf(unionArray[i].getFConst())); break;
default: default:
infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine()); infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine());
...@@ -1879,7 +1881,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod ...@@ -1879,7 +1881,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod
} }
break; break;
case EOpCosh: case EOpCosh:
switch(getType().getBasicType()) { switch(basicType) {
case EbtFloat: tempConstArray[i].setFConst(coshf(unionArray[i].getFConst())); break; case EbtFloat: tempConstArray[i].setFConst(coshf(unionArray[i].getFConst())); break;
default: default:
infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine()); infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine());
...@@ -1887,7 +1889,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod ...@@ -1887,7 +1889,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod
} }
break; break;
case EOpTanh: case EOpTanh:
switch(getType().getBasicType()) { switch(basicType) {
case EbtFloat: tempConstArray[i].setFConst(tanhf(unionArray[i].getFConst())); break; case EbtFloat: tempConstArray[i].setFConst(tanhf(unionArray[i].getFConst())); break;
default: default:
infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine()); infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine());
...@@ -1895,7 +1897,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod ...@@ -1895,7 +1897,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod
} }
break; break;
case EOpAsinh: case EOpAsinh:
switch(getType().getBasicType()) { switch(basicType) {
case EbtFloat: tempConstArray[i].setFConst(asinhf(unionArray[i].getFConst())); break; case EbtFloat: tempConstArray[i].setFConst(asinhf(unionArray[i].getFConst())); break;
default: default:
infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine()); infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine());
...@@ -1903,7 +1905,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod ...@@ -1903,7 +1905,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod
} }
break; break;
case EOpAcosh: case EOpAcosh:
switch(getType().getBasicType()) { switch(basicType) {
case EbtFloat: tempConstArray[i].setFConst(acoshf(unionArray[i].getFConst())); break; case EbtFloat: tempConstArray[i].setFConst(acoshf(unionArray[i].getFConst())); break;
default: default:
infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine()); infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine());
...@@ -1911,7 +1913,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod ...@@ -1911,7 +1913,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod
} }
break; break;
case EOpAtanh: case EOpAtanh:
switch(getType().getBasicType()) { switch(basicType) {
case EbtFloat: tempConstArray[i].setFConst(atanhf(unionArray[i].getFConst())); break; case EbtFloat: tempConstArray[i].setFConst(atanhf(unionArray[i].getFConst())); break;
default: default:
infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine()); infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine());
...@@ -1919,7 +1921,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod ...@@ -1919,7 +1921,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod
} }
break; break;
case EOpLog: case EOpLog:
switch(getType().getBasicType()) { switch(basicType) {
case EbtFloat: tempConstArray[i].setFConst(logf(unionArray[i].getFConst())); break; case EbtFloat: tempConstArray[i].setFConst(logf(unionArray[i].getFConst())); break;
default: default:
infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine()); infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine());
...@@ -1927,7 +1929,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod ...@@ -1927,7 +1929,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod
} }
break; break;
case EOpLog2: case EOpLog2:
switch(getType().getBasicType()) { switch(basicType) {
case EbtFloat: tempConstArray[i].setFConst(sw::log2(unionArray[i].getFConst())); break; case EbtFloat: tempConstArray[i].setFConst(sw::log2(unionArray[i].getFConst())); break;
default: default:
infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine()); infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine());
...@@ -1935,7 +1937,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod ...@@ -1935,7 +1937,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod
} }
break; break;
case EOpExp: case EOpExp:
switch(getType().getBasicType()) { switch(basicType) {
case EbtFloat: tempConstArray[i].setFConst(expf(unionArray[i].getFConst())); break; case EbtFloat: tempConstArray[i].setFConst(expf(unionArray[i].getFConst())); break;
default: default:
infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine()); infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine());
...@@ -1943,7 +1945,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod ...@@ -1943,7 +1945,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod
} }
break; break;
case EOpExp2: case EOpExp2:
switch(getType().getBasicType()) { switch(basicType) {
case EbtFloat: tempConstArray[i].setFConst(exp2f(unionArray[i].getFConst())); break; case EbtFloat: tempConstArray[i].setFConst(exp2f(unionArray[i].getFConst())); break;
default: default:
infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine()); infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine());
...@@ -1951,7 +1953,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod ...@@ -1951,7 +1953,7 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod
} }
break; break;
case EOpSqrt: case EOpSqrt:
switch(getType().getBasicType()) { switch(basicType) {
case EbtFloat: tempConstArray[i].setFConst(sqrtf(unionArray[i].getFConst())); break; case EbtFloat: tempConstArray[i].setFConst(sqrtf(unionArray[i].getFConst())); break;
default: default:
infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine()); infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine());
...@@ -1959,18 +1961,63 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod ...@@ -1959,18 +1961,63 @@ TIntermTyped* TIntermConstantUnion::fold(TOperator op, TIntermTyped* constantNod
} }
break; break;
case EOpInverseSqrt: case EOpInverseSqrt:
switch(getType().getBasicType()) { switch(basicType) {
case EbtFloat: tempConstArray[i].setFConst(1.0f / sqrtf(unionArray[i].getFConst())); break; case EbtFloat: tempConstArray[i].setFConst(1.0f / sqrtf(unionArray[i].getFConst())); break;
default: default:
infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine()); infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine());
return 0; return 0;
} }
break; break;
case EOpFloatBitsToInt:
switch(basicType) {
case EbtFloat:
tempConstArray[i].setIConst(sw::bitCast<int>(unionArray[i].getFConst()));
type.setBasicType(EbtInt);
break;
default:
infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine());
return 0;
}
break;
break;
case EOpFloatBitsToUint:
switch(basicType) {
case EbtFloat:
tempConstArray[i].setUConst(sw::bitCast<unsigned int>(unionArray[i].getFConst()));
type.setBasicType(EbtUInt);
break;
default:
infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine());
return 0;
}
break;
case EOpIntBitsToFloat:
switch(basicType) {
case EbtInt:
tempConstArray[i].setFConst(sw::bitCast<float>(unionArray[i].getIConst()));
type.setBasicType(EbtFloat);
break;
default:
infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine());
return 0;
}
break;
case EOpUintBitsToFloat:
switch(basicType) {
case EbtUInt:
tempConstArray[i].setFConst(sw::bitCast<float>(unionArray[i].getUConst()));
type.setBasicType(EbtFloat);
break;
default:
infoSink.info.message(EPrefixInternalError, "Unary operation not folded into constant", getLine());
return 0;
}
break;
default: default:
return 0; return 0;
} }
} }
newNode = new TIntermConstantUnion(tempConstArray, getType()); newNode = new TIntermConstantUnion(tempConstArray, type);
newNode->setLine(getLine()); newNode->setLine(getLine());
return newNode; return newNode;
} }
......
...@@ -492,7 +492,7 @@ int uint_constant(TParseContext *context) ...@@ -492,7 +492,7 @@ int uint_constant(TParseContext *context)
return 0; return 0;
} }
if (!atoi_clamp(yytext, &(yylval->lex.i))) if (!atou_clamp(yytext, &(yylval->lex.u)))
yyextra->warning(*yylloc, "Integer overflow", yytext, ""); yyextra->warning(*yylloc, "Integer overflow", yytext, "");
return UINTCONSTANT; return UINTCONSTANT;
......
...@@ -3724,7 +3724,7 @@ int uint_constant(TParseContext *context) ...@@ -3724,7 +3724,7 @@ int uint_constant(TParseContext *context)
return 0; return 0;
} }
if (!atoi_clamp(yytext, &(yylval->lex.i))) if (!atou_clamp(yytext, &(yylval->lex.u)))
yyextra->warning(*yylloc, "Integer overflow", yytext, ""); yyextra->warning(*yylloc, "Integer overflow", yytext, "");
return UINTCONSTANT; return UINTCONSTANT;
......
...@@ -32,3 +32,11 @@ bool atoi_clamp(const char *str, int *value) ...@@ -32,3 +32,11 @@ bool atoi_clamp(const char *str, int *value)
*value = std::numeric_limits<int>::max(); *value = std::numeric_limits<int>::max();
return success; return success;
} }
bool atou_clamp(const char *str, unsigned int *value)
{
bool success = pp::numeric_lex_int(str, value);
if(!success)
*value = std::numeric_limits<unsigned int>::max();
return success;
}
...@@ -21,14 +21,18 @@ extern "C" { ...@@ -21,14 +21,18 @@ extern "C" {
// atof_clamp is like atof but // atof_clamp is like atof but
// 1. it forces C locale, i.e. forcing '.' as decimal point. // 1. it forces C locale, i.e. forcing '.' as decimal point.
// 2. it clamps the value to -FLT_MAX or FLT_MAX if overflow happens. // 2. it sets the value to FLT_MAX if overflow happens.
// Return false if overflow happens. // Return false if overflow happens.
bool atof_clamp(const char *str, float *value); bool atof_clamp(const char *str, float *value);
// If overflow happens, clamp the value to INT_MIN or INT_MAX. // If overflow happens, value is set to INT_MAX.
// Return false if overflow happens. // Return false if overflow happens.
bool atoi_clamp(const char *str, int *value); bool atoi_clamp(const char *str, int *value);
// If overflow happens, value is set to UINT_MAX.
// Return false if overflow happens.
bool atou_clamp(const char *str, unsigned int *value);
#ifdef __cplusplus #ifdef __cplusplus
} // end extern "C" } // end extern "C"
#endif #endif
......
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