Commit d2a67b96 by Olli Etuaho Committed by Jamie Madill

Fix precision tracking for built-in function return values

Previously, the type of the return value of all function calls was set to the type of the return value in the function signature. This did not carry precision information. This patch changes this so that the return value precision is set correctly for built-in functions. For single-argument math functions, it mostly depends on that addUnaryMath sets the type of the return value to be the same as the type of the operand. The type is replaced but the precision information from the operand type is retained when needed. For multi-argument math functions, precision is determined based on all the nodes in the aggregate after the type has been set. For texture functions, the precision is set according the sampler type as per ESSL 1.0 spec. For textureSize, the precision is always highp as per ESSL 3.0 spec. BUG=angle:787 Change-Id: I48448e3ffe38656b91177dee9b60dd07a03cd095 Reviewed-on: https://chromium-review.googlesource.com/224951Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarNicolas Capens <capn@chromium.org> Tested-by: 's avatarOlli Etuaho <oetuaho@nvidia.com>
parent e2fecdd3
...@@ -133,6 +133,14 @@ bool CompareStructure(const TType &leftNodeType, ...@@ -133,6 +133,14 @@ bool CompareStructure(const TType &leftNodeType,
// //
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
void TIntermTyped::setTypePreservePrecision(const TType &t)
{
TPrecision precision = getPrecision();
mType = t;
ASSERT(mType.getBasicType() != EbtBool || precision == EbpUndefined);
mType.setPrecision(precision);
}
#define REPLACE_IF_IS(node, type, original, replacement) \ #define REPLACE_IF_IS(node, type, original, replacement) \
if (node == original) { \ if (node == original) { \
node = static_cast<type *>(replacement); \ node = static_cast<type *>(replacement); \
...@@ -237,6 +245,52 @@ void TIntermAggregate::enqueueChildren(std::queue<TIntermNode *> *nodeQueue) con ...@@ -237,6 +245,52 @@ void TIntermAggregate::enqueueChildren(std::queue<TIntermNode *> *nodeQueue) con
} }
} }
void TIntermAggregate::setPrecisionFromChildren()
{
if (getBasicType() == EbtBool)
{
mType.setPrecision(EbpUndefined);
return;
}
TPrecision precision = EbpUndefined;
TIntermSequence::iterator childIter = mSequence.begin();
while (childIter != mSequence.end())
{
TIntermTyped *typed = (*childIter)->getAsTyped();
if (typed)
precision = GetHigherPrecision(typed->getPrecision(), precision);
++childIter;
}
mType.setPrecision(precision);
}
void TIntermAggregate::setBuiltInFunctionPrecision()
{
// All built-ins returning bool should be handled as ops, not functions.
ASSERT(getBasicType() != EbtBool);
TPrecision precision = EbpUndefined;
TIntermSequence::iterator childIter = mSequence.begin();
while (childIter != mSequence.end())
{
TIntermTyped *typed = (*childIter)->getAsTyped();
// ESSL spec section 8: texture functions get their precision from the sampler.
if (typed && IsSampler(typed->getBasicType()))
{
precision = typed->getPrecision();
break;
}
++childIter;
}
// ESSL 3.0 spec section 8: textureSize always gets highp precision.
// All other functions that take a sampler are assumed to be texture functions.
if (mName.find("textureSize") == 0)
mType.setPrecision(EbpHigh);
else
mType.setPrecision(precision);
}
bool TIntermSelection::replaceChildNode( bool TIntermSelection::replaceChildNode(
TIntermNode *original, TIntermNode *replacement) TIntermNode *original, TIntermNode *replacement)
{ {
......
...@@ -266,6 +266,7 @@ class TIntermTyped : public TIntermNode ...@@ -266,6 +266,7 @@ class TIntermTyped : public TIntermNode
virtual bool hasSideEffects() const = 0; virtual bool hasSideEffects() const = 0;
void setType(const TType &t) { mType = t; } void setType(const TType &t) { mType = t; }
void setTypePreservePrecision(const TType &t);
const TType &getType() const { return mType; } const TType &getType() const { return mType; }
TType *getTypePointer() { return &mType; } TType *getTypePointer() { return &mType; }
...@@ -614,6 +615,9 @@ class TIntermAggregate : public TIntermOperator ...@@ -614,6 +615,9 @@ class TIntermAggregate : public TIntermOperator
virtual void enqueueChildren(std::queue<TIntermNode *> *nodeQueue) const; virtual void enqueueChildren(std::queue<TIntermNode *> *nodeQueue) const;
void setPrecisionFromChildren();
void setBuiltInFunctionPrecision();
protected: protected:
TIntermAggregate(const TIntermAggregate &); // disallow copy constructor TIntermAggregate(const TIntermAggregate &); // disallow copy constructor
TIntermAggregate &operator=(const TIntermAggregate &); // disallow assignment operator TIntermAggregate &operator=(const TIntermAggregate &); // disallow assignment operator
......
...@@ -354,6 +354,15 @@ function_call ...@@ -354,6 +354,15 @@ function_call
// Treat it like a built-in unary operator. // Treat it like a built-in unary operator.
// //
$$ = context->intermediate.addUnaryMath(op, $1.intermNode, @1); $$ = context->intermediate.addUnaryMath(op, $1.intermNode, @1);
const TType& returnType = fnCandidate->getReturnType();
if (returnType.getBasicType() == EbtBool) {
// Bool types should not have precision, so we'll override any precision
// that might have been set by addUnaryMath.
$$->setType(returnType);
} else {
// addUnaryMath has set the precision of the node based on the operand.
$$->setTypePreservePrecision(returnType);
}
if ($$ == 0) { if ($$ == 0) {
std::stringstream extraInfoStream; std::stringstream extraInfoStream;
extraInfoStream << "built in unary operator function. Type: " << static_cast<TIntermTyped*>($1.intermNode)->getCompleteString(); extraInfoStream << "built in unary operator function. Type: " << static_cast<TIntermTyped*>($1.intermNode)->getCompleteString();
...@@ -362,20 +371,29 @@ function_call ...@@ -362,20 +371,29 @@ function_call
YYERROR; YYERROR;
} }
} else { } else {
$$ = context->intermediate.setAggregateOperator($1.intermAggregate, op, @1); TIntermAggregate *aggregate = context->intermediate.setAggregateOperator($1.intermAggregate, op, @1);
aggregate->setType(fnCandidate->getReturnType());
aggregate->setPrecisionFromChildren();
$$ = aggregate;
} }
} else { } else {
// This is a real function call // This is a real function call
$$ = context->intermediate.setAggregateOperator($1.intermAggregate, EOpFunctionCall, @1); TIntermAggregate *aggregate = context->intermediate.setAggregateOperator($1.intermAggregate, EOpFunctionCall, @1);
$$->setType(fnCandidate->getReturnType()); aggregate->setType(fnCandidate->getReturnType());
// this is how we know whether the given function is a builtIn function or a user defined function // this is how we know whether the given function is a builtIn function or a user defined function
// if builtIn == false, it's a userDefined -> could be an overloaded builtIn function also // if builtIn == false, it's a userDefined -> could be an overloaded builtIn function also
// if builtIn == true, it's definitely a builtIn function with EOpNull // if builtIn == true, it's definitely a builtIn function with EOpNull
if (!builtIn) if (!builtIn)
$$->getAsAggregate()->setUserDefined(); aggregate->setUserDefined();
$$->getAsAggregate()->setName(fnCandidate->getMangledName()); aggregate->setName(fnCandidate->getMangledName());
// This needs to happen after the name is set
if (builtIn)
aggregate->setBuiltInFunctionPrecision();
$$ = aggregate;
TQualifier qual; TQualifier qual;
for (size_t i = 0; i < fnCandidate->getParamCount(); ++i) { for (size_t i = 0; i < fnCandidate->getParamCount(); ++i) {
...@@ -388,7 +406,6 @@ function_call ...@@ -388,7 +406,6 @@ function_call
} }
} }
} }
$$->setType(fnCandidate->getReturnType());
} else { } else {
// error message was put out by PaFindFunction() // error message was put out by PaFindFunction()
// Put on a dummy node for error recovery // Put on a dummy node for error recovery
......
/* A Bison parser, made by GNU Bison 2.7.1. */ /* A Bison parser, made by GNU Bison 2.7.12-4996. */
/* Bison implementation for Yacc-like parsers in C /* Bison implementation for Yacc-like parsers in C
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
#define YYBISON 1 #define YYBISON 1
/* Bison version. */ /* Bison version. */
#define YYBISON_VERSION "2.7.1" #define YYBISON_VERSION "2.7.12-4996"
/* Skeleton name. */ /* Skeleton name. */
#define YYSKELETON_NAME "yacc.c" #define YYSKELETON_NAME "yacc.c"
...@@ -796,31 +796,31 @@ static const yytype_int16 yyrhs[] = ...@@ -796,31 +796,31 @@ static const yytype_int16 yyrhs[] =
static const yytype_uint16 yyrline[] = static const yytype_uint16 yyrline[] =
{ {
0, 206, 206, 207, 210, 234, 237, 242, 247, 252, 0, 206, 206, 207, 210, 234, 237, 242, 247, 252,
257, 263, 266, 269, 272, 275, 285, 298, 306, 406, 257, 263, 266, 269, 272, 275, 285, 298, 306, 423,
409, 417, 420, 426, 430, 437, 443, 452, 460, 463, 426, 434, 437, 443, 447, 454, 460, 469, 477, 480,
473, 476, 486, 496, 517, 518, 519, 524, 525, 533, 490, 493, 503, 513, 535, 536, 537, 542, 543, 551,
544, 545, 553, 564, 568, 569, 579, 589, 599, 612, 562, 563, 571, 582, 586, 587, 597, 607, 617, 630,
613, 623, 636, 640, 644, 648, 649, 662, 663, 676, 631, 641, 654, 658, 662, 666, 667, 680, 681, 694,
677, 690, 691, 708, 709, 722, 723, 724, 725, 726, 695, 708, 709, 726, 727, 740, 741, 742, 743, 744,
730, 733, 744, 752, 760, 787, 793, 804, 808, 812, 748, 751, 762, 770, 778, 805, 811, 822, 826, 830,
816, 823, 879, 882, 889, 897, 918, 939, 949, 977, 834, 841, 897, 900, 907, 915, 936, 957, 967, 995,
982, 992, 997, 1007, 1010, 1013, 1016, 1022, 1029, 1032, 1000, 1010, 1015, 1025, 1028, 1031, 1034, 1040, 1047, 1050,
1036, 1040, 1044, 1051, 1055, 1059, 1066, 1070, 1074, 1081, 1054, 1058, 1062, 1069, 1073, 1077, 1084, 1088, 1092, 1099,
1090, 1096, 1099, 1105, 1111, 1118, 1127, 1136, 1144, 1147, 1108, 1114, 1117, 1123, 1129, 1136, 1145, 1154, 1162, 1165,
1154, 1158, 1165, 1168, 1172, 1176, 1185, 1194, 1202, 1212, 1172, 1176, 1183, 1186, 1190, 1194, 1203, 1212, 1220, 1230,
1224, 1227, 1230, 1236, 1243, 1246, 1252, 1255, 1258, 1264, 1242, 1245, 1248, 1254, 1261, 1264, 1270, 1273, 1276, 1282,
1267, 1282, 1286, 1290, 1294, 1298, 1302, 1307, 1312, 1317, 1285, 1300, 1304, 1308, 1312, 1316, 1320, 1325, 1330, 1335,
1322, 1327, 1332, 1337, 1342, 1347, 1352, 1357, 1362, 1367, 1340, 1345, 1350, 1355, 1360, 1365, 1370, 1375, 1380, 1385,
1372, 1377, 1382, 1387, 1392, 1397, 1402, 1407, 1411, 1415, 1390, 1395, 1400, 1405, 1410, 1415, 1420, 1425, 1429, 1433,
1419, 1423, 1427, 1431, 1435, 1439, 1443, 1447, 1451, 1455, 1437, 1441, 1445, 1449, 1453, 1457, 1461, 1465, 1469, 1473,
1459, 1463, 1467, 1475, 1483, 1487, 1500, 1500, 1503, 1503, 1477, 1481, 1485, 1493, 1501, 1505, 1518, 1518, 1521, 1521,
1509, 1512, 1528, 1531, 1540, 1544, 1550, 1557, 1572, 1576, 1527, 1530, 1546, 1549, 1558, 1562, 1568, 1575, 1590, 1594,
1580, 1581, 1587, 1588, 1589, 1590, 1591, 1595, 1596, 1596, 1598, 1599, 1605, 1606, 1607, 1608, 1609, 1613, 1614, 1614,
1596, 1606, 1607, 1611, 1611, 1612, 1612, 1617, 1620, 1630, 1614, 1624, 1625, 1629, 1629, 1630, 1630, 1635, 1638, 1648,
1633, 1639, 1640, 1644, 1652, 1656, 1666, 1671, 1688, 1688, 1651, 1657, 1658, 1662, 1670, 1674, 1684, 1689, 1706, 1706,
1693, 1693, 1700, 1700, 1708, 1711, 1717, 1720, 1726, 1730, 1711, 1711, 1718, 1718, 1726, 1729, 1735, 1738, 1744, 1748,
1737, 1744, 1751, 1758, 1769, 1778, 1782, 1789, 1792, 1798, 1755, 1762, 1769, 1776, 1787, 1796, 1800, 1807, 1810, 1816,
1798 1816
}; };
#endif #endif
...@@ -2736,6 +2736,15 @@ yyreduce: ...@@ -2736,6 +2736,15 @@ yyreduce:
// Treat it like a built-in unary operator. // Treat it like a built-in unary operator.
// //
(yyval.interm.intermTypedNode) = context->intermediate.addUnaryMath(op, (yyvsp[(1) - (1)].interm).intermNode, (yylsp[(1) - (1)])); (yyval.interm.intermTypedNode) = context->intermediate.addUnaryMath(op, (yyvsp[(1) - (1)].interm).intermNode, (yylsp[(1) - (1)]));
const TType& returnType = fnCandidate->getReturnType();
if (returnType.getBasicType() == EbtBool) {
// Bool types should not have precision, so we'll override any precision
// that might have been set by addUnaryMath.
(yyval.interm.intermTypedNode)->setType(returnType);
} else {
// addUnaryMath has set the precision of the node based on the operand.
(yyval.interm.intermTypedNode)->setTypePreservePrecision(returnType);
}
if ((yyval.interm.intermTypedNode) == 0) { if ((yyval.interm.intermTypedNode) == 0) {
std::stringstream extraInfoStream; std::stringstream extraInfoStream;
extraInfoStream << "built in unary operator function. Type: " << static_cast<TIntermTyped*>((yyvsp[(1) - (1)].interm).intermNode)->getCompleteString(); extraInfoStream << "built in unary operator function. Type: " << static_cast<TIntermTyped*>((yyvsp[(1) - (1)].interm).intermNode)->getCompleteString();
...@@ -2744,20 +2753,29 @@ yyreduce: ...@@ -2744,20 +2753,29 @@ yyreduce:
YYERROR; YYERROR;
} }
} else { } else {
(yyval.interm.intermTypedNode) = context->intermediate.setAggregateOperator((yyvsp[(1) - (1)].interm).intermAggregate, op, (yylsp[(1) - (1)])); TIntermAggregate *aggregate = context->intermediate.setAggregateOperator((yyvsp[(1) - (1)].interm).intermAggregate, op, (yylsp[(1) - (1)]));
aggregate->setType(fnCandidate->getReturnType());
aggregate->setPrecisionFromChildren();
(yyval.interm.intermTypedNode) = aggregate;
} }
} else { } else {
// This is a real function call // This is a real function call
(yyval.interm.intermTypedNode) = context->intermediate.setAggregateOperator((yyvsp[(1) - (1)].interm).intermAggregate, EOpFunctionCall, (yylsp[(1) - (1)])); TIntermAggregate *aggregate = context->intermediate.setAggregateOperator((yyvsp[(1) - (1)].interm).intermAggregate, EOpFunctionCall, (yylsp[(1) - (1)]));
(yyval.interm.intermTypedNode)->setType(fnCandidate->getReturnType()); aggregate->setType(fnCandidate->getReturnType());
// this is how we know whether the given function is a builtIn function or a user defined function // this is how we know whether the given function is a builtIn function or a user defined function
// if builtIn == false, it's a userDefined -> could be an overloaded builtIn function also // if builtIn == false, it's a userDefined -> could be an overloaded builtIn function also
// if builtIn == true, it's definitely a builtIn function with EOpNull // if builtIn == true, it's definitely a builtIn function with EOpNull
if (!builtIn) if (!builtIn)
(yyval.interm.intermTypedNode)->getAsAggregate()->setUserDefined(); aggregate->setUserDefined();
(yyval.interm.intermTypedNode)->getAsAggregate()->setName(fnCandidate->getMangledName()); aggregate->setName(fnCandidate->getMangledName());
// This needs to happen after the name is set
if (builtIn)
aggregate->setBuiltInFunctionPrecision();
(yyval.interm.intermTypedNode) = aggregate;
TQualifier qual; TQualifier qual;
for (size_t i = 0; i < fnCandidate->getParamCount(); ++i) { for (size_t i = 0; i < fnCandidate->getParamCount(); ++i) {
...@@ -2770,7 +2788,6 @@ yyreduce: ...@@ -2770,7 +2788,6 @@ yyreduce:
} }
} }
} }
(yyval.interm.intermTypedNode)->setType(fnCandidate->getReturnType());
} else { } else {
// error message was put out by PaFindFunction() // error message was put out by PaFindFunction()
// Put on a dummy node for error recovery // Put on a dummy node for error recovery
......
/* A Bison parser, made by GNU Bison 2.7.1. */ /* A Bison parser, made by GNU Bison 2.7.12-4996. */
/* Bison interface for Yacc-like parsers in C /* Bison interface for Yacc-like parsers in C
......
//
// Copyright (c) 2014 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.
//
// TypeTracking_test.cpp:
// Test for tracking types resulting from math operations, including their
// precision.
//
#include "angle_gl.h"
#include "gtest/gtest.h"
#include "GLSLANG/ShaderLang.h"
#include "compiler/translator/TranslatorESSL.h"
class TypeTrackingTest : public testing::Test
{
public:
TypeTrackingTest() {}
protected:
virtual void SetUp()
{
ShBuiltInResources resources;
ShInitBuiltInResources(&resources);
mTranslator = new TranslatorESSL(GL_FRAGMENT_SHADER, SH_GLES2_SPEC);
ASSERT_TRUE(mTranslator->Init(resources));
}
virtual void TearDown()
{
delete mTranslator;
}
void compile(const std::string& shaderString)
{
const char *shaderStrings[] = { shaderString.c_str() };
bool compilationSuccess = mTranslator->compile(shaderStrings, 1, SH_INTERMEDIATE_TREE);
TInfoSink &infoSink = mTranslator->getInfoSink();
mInfoLog = infoSink.info.c_str();
if (!compilationSuccess)
FAIL() << "Shader compilation failed " << mInfoLog;
}
bool foundInIntermediateTree(const char* stringToFind)
{
return mInfoLog.find(stringToFind) != std::string::npos;
}
private:
TranslatorESSL *mTranslator;
std::string mInfoLog;
};
TEST_F(TypeTrackingTest, BuiltInFunctionResultPrecision)
{
const std::string &shaderString =
"precision mediump float;\n"
"uniform float f;\n"
"void main() {\n"
" float ff = sin(f);\n"
" gl_FragColor = vec4(ff);\n"
"}\n";
compile(shaderString);
ASSERT_TRUE(foundInIntermediateTree("sine (mediump float)"));
};
TEST_F(TypeTrackingTest, BinaryMathResultPrecision)
{
const std::string &shaderString =
"precision mediump float;\n"
"uniform float f;\n"
"void main() {\n"
" float ff = f * 0.5;\n"
" gl_FragColor = vec4(ff);\n"
"}\n";
compile(shaderString);
ASSERT_TRUE(foundInIntermediateTree("multiply (mediump float)"));
};
TEST_F(TypeTrackingTest, BuiltInVecFunctionResultTypeAndPrecision)
{
const std::string &shaderString =
"precision mediump float;\n"
"uniform vec2 a;\n"
"void main() {\n"
" float b = length(a);\n"
" float c = dot(a, vec2(0.5));\n"
" float d = distance(vec2(0.5), a);\n"
" gl_FragColor = vec4(b, c, d, 1.0);\n"
"}\n";
compile(shaderString);
ASSERT_TRUE(foundInIntermediateTree("length (mediump float)"));
ASSERT_TRUE(foundInIntermediateTree("dot-product (mediump float)"));
ASSERT_TRUE(foundInIntermediateTree("distance (mediump float)"));
};
TEST_F(TypeTrackingTest, BuiltInFunctionChoosesHigherPrecision)
{
const std::string &shaderString =
"precision lowp float;\n"
"uniform mediump vec2 a;\n"
"uniform lowp vec2 b;\n"
"void main() {\n"
" float c = dot(a, b);\n"
" float d = distance(b, a);\n"
" gl_FragColor = vec4(c, d, 0.0, 1.0);\n"
"}\n";
compile(shaderString);
ASSERT_TRUE(foundInIntermediateTree("dot-product (mediump float)"));
ASSERT_TRUE(foundInIntermediateTree("distance (mediump float)"));
};
TEST_F(TypeTrackingTest, BuiltInBoolFunctionResultType)
{
const std::string &shaderString =
"uniform bvec4 bees;\n"
"void main() {\n"
" bool b = any(bees);\n"
" bool c = all(bees);\n"
" bvec4 d = not(bees);\n"
" gl_FragColor = vec4(b ? 1.0 : 0.0, c ? 1.0 : 0.0, d.x ? 1.0 : 0.0, 1.0);\n"
"}\n";
compile(shaderString);
ASSERT_TRUE(foundInIntermediateTree("any (bool)"));
ASSERT_TRUE(foundInIntermediateTree("all (bool)"));
ASSERT_TRUE(foundInIntermediateTree("Negate conditional (4-component vector of bool)"));
};
TEST_F(TypeTrackingTest, BuiltInVecToBoolFunctionResultType)
{
const std::string &shaderString =
"precision mediump float;\n"
"uniform vec2 apples;\n"
"uniform vec2 oranges;\n"
"uniform ivec2 foo;\n"
"uniform ivec2 bar;\n"
"void main() {\n"
" bvec2 a = lessThan(apples, oranges);\n"
" bvec2 b = greaterThan(foo, bar);\n"
" gl_FragColor = vec4(any(a) ? 1.0 : 0.0, any(b) ? 1.0 : 0.0, 0.0, 1.0);\n"
"}\n";
compile(shaderString);
ASSERT_TRUE(foundInIntermediateTree("Less Than (2-component vector of bool)"));
ASSERT_TRUE(foundInIntermediateTree("Greater Than (2-component vector of bool)"));
};
TEST_F(TypeTrackingTest, Texture2DResultTypeAndPrecision)
{
// ESSL spec section 4.5.3: sampler2D and samplerCube are lowp by default
// ESSL spec section 8: For the texture functions, the precision of the return type matches the precision of the sampler type.
const std::string &shaderString =
"precision mediump float;\n"
"uniform sampler2D s;\n"
"uniform vec2 a;\n"
"void main() {\n"
" vec4 c = texture2D(s, a);\n"
" gl_FragColor = c;\n"
"}\n";
compile(shaderString);
ASSERT_TRUE(foundInIntermediateTree("texture2D(s21;vf2; (lowp 4-component vector of float)"));
};
TEST_F(TypeTrackingTest, TextureCubeResultTypeAndPrecision)
{
// ESSL spec section 4.5.3: sampler2D and samplerCube are lowp by default
// ESSL spec section 8: For the texture functions, the precision of the return type matches the precision of the sampler type.
const std::string &shaderString =
"precision mediump float;\n"
"uniform samplerCube sc;\n"
"uniform vec3 a;\n"
"void main() {\n"
" vec4 c = textureCube(sc, a);\n"
" gl_FragColor = c;\n"
"}\n";
compile(shaderString);
ASSERT_TRUE(foundInIntermediateTree("textureCube(sC1;vf3; (lowp 4-component vector of float)"));
};
TEST_F(TypeTrackingTest, TextureSizeResultTypeAndPrecision)
{
// ESSL 3.0 spec section 8: textureSize has predefined precision highp
const std::string &shaderString =
"#version 300 es\n"
"precision mediump float;\n"
"out vec4 my_FragColor;\n"
"uniform sampler2D s;\n"
"void main() {\n"
" ivec2 size = textureSize(s, 0);\n"
" if (size.x > 100) {\n"
" my_FragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
" } else {\n"
" my_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
" }\n"
"}\n";
compile(shaderString);
ASSERT_TRUE(foundInIntermediateTree("textureSize(s21;i1; (highp 2-component vector of int)"));
};
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