Home | History | Annotate | Download | only in functional
      1 /*-------------------------------------------------------------------------
      2  * drawElements Quality Program OpenGL ES 2.0 Module
      3  * -------------------------------------------------
      4  *
      5  * Copyright 2014 The Android Open Source Project
      6  *
      7  * Licensed under the Apache License, Version 2.0 (the "License");
      8  * you may not use this file except in compliance with the License.
      9  * You may obtain a copy of the License at
     10  *
     11  *      http://www.apache.org/licenses/LICENSE-2.0
     12  *
     13  * Unless required by applicable law or agreed to in writing, software
     14  * distributed under the License is distributed on an "AS IS" BASIS,
     15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     16  * See the License for the specific language governing permissions and
     17  * limitations under the License.
     18  *
     19  *//*!
     20  * \file
     21  * \brief Texture unit usage tests.
     22  *
     23  * \todo [2012-07-12 nuutti] Come up with a good way to make these tests faster.
     24  *//*--------------------------------------------------------------------*/
     25 
     26 #include "es2fTextureUnitTests.hpp"
     27 #include "glsTextureTestUtil.hpp"
     28 #include "gluTextureUtil.hpp"
     29 #include "gluContextInfo.hpp"
     30 #include "tcuTextureUtil.hpp"
     31 #include "tcuImageCompare.hpp"
     32 #include "tcuMatrix.hpp"
     33 #include "tcuRenderTarget.hpp"
     34 #include "sglrContextUtil.hpp"
     35 #include "sglrReferenceContext.hpp"
     36 #include "sglrGLContext.hpp"
     37 #include "deStringUtil.hpp"
     38 #include "deRandom.hpp"
     39 
     40 #include "glwEnums.hpp"
     41 #include "glwFunctions.hpp"
     42 
     43 using tcu::Vec2;
     44 using tcu::Vec3;
     45 using tcu::Vec4;
     46 using tcu::IVec2;
     47 using tcu::Mat3;
     48 using std::vector;
     49 using std::string;
     50 using namespace glw; // GL types
     51 
     52 namespace deqp
     53 {
     54 
     55 using namespace gls::TextureTestUtil;
     56 
     57 namespace gles2
     58 {
     59 namespace Functional
     60 {
     61 
     62 static const int VIEWPORT_WIDTH			= 128;
     63 static const int VIEWPORT_HEIGHT		= 128;
     64 
     65 static const int TEXTURE_WIDTH_2D		= 128;
     66 static const int TEXTURE_HEIGHT_2D		= 128;
     67 
     68 // \note Cube map texture size is larger in order to make minifications possible - otherwise would need to display different faces at same time.
     69 static const int TEXTURE_WIDTH_CUBE		= 256;
     70 static const int TEXTURE_HEIGHT_CUBE	= 256;
     71 
     72 static const int GRID_CELL_SIZE			= 8;
     73 
     74 static const GLenum s_testFormats[] =
     75 {
     76 	GL_RGB,
     77 	GL_RGBA,
     78 	GL_ALPHA,
     79 	GL_LUMINANCE,
     80 	GL_LUMINANCE_ALPHA
     81 };
     82 
     83 static const GLenum s_testDataTypes[] =
     84 {
     85 	GL_UNSIGNED_BYTE,
     86 	GL_UNSIGNED_SHORT_5_6_5,
     87 	GL_UNSIGNED_SHORT_4_4_4_4,
     88 	GL_UNSIGNED_SHORT_5_5_5_1,
     89 };
     90 
     91 static const GLenum s_testWrapModes[] =
     92 {
     93 	GL_CLAMP_TO_EDGE,
     94 	GL_REPEAT,
     95 	GL_MIRRORED_REPEAT,
     96 };
     97 
     98 static const GLenum s_testMinFilters[] =
     99 {
    100 	GL_NEAREST,
    101 	GL_LINEAR,
    102 	GL_NEAREST_MIPMAP_NEAREST,
    103 	GL_LINEAR_MIPMAP_NEAREST,
    104 	GL_NEAREST_MIPMAP_LINEAR,
    105 	GL_LINEAR_MIPMAP_LINEAR
    106 };
    107 
    108 static const GLenum s_testNonMipmapMinFilters[] =
    109 {
    110 	GL_NEAREST,
    111 	GL_LINEAR
    112 };
    113 
    114 static const GLenum s_testMagFilters[] =
    115 {
    116 	GL_NEAREST,
    117 	GL_LINEAR
    118 };
    119 
    120 static const GLenum s_cubeFaceTargets[] =
    121 {
    122 	GL_TEXTURE_CUBE_MAP_POSITIVE_X,
    123 	GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
    124 	GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
    125 	GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
    126 	GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
    127 	GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
    128 };
    129 
    130 static string generateMultiTexFragmentShader(int numUnits, const GLenum* unitTypes)
    131 {
    132 	// The fragment shader calculates the average of a set of textures.
    133 
    134 	string samplersStr;
    135 	string matricesStr;
    136 	string lookupsStr;
    137 
    138 	string colorMultiplier = "(1.0/" + de::toString(numUnits) + ".0)";
    139 
    140 	for (int ndx = 0; ndx < numUnits; ndx++)
    141 	{
    142 		string			ndxStr				= de::toString(ndx);
    143 		string			samplerName			= "u_sampler" + ndxStr;
    144 		string			transformationName	= "u_trans" + ndxStr;
    145 		const char*		samplerType			= unitTypes[ndx] == GL_TEXTURE_2D ? "sampler2D" : "samplerCube";
    146 		const char*		lookupFunc			= unitTypes[ndx] == GL_TEXTURE_2D ? "texture2D" : "textureCube";
    147 
    148 		samplersStr += string("") + "uniform mediump " + samplerType + " " + samplerName + ";\n";
    149 		matricesStr += "uniform mediump mat3 " + transformationName + ";\n";
    150 
    151 		string lookupCoord = transformationName + "*vec3(v_coord, 1.0)";
    152 
    153 		if (unitTypes[ndx] == GL_TEXTURE_2D)
    154 			lookupCoord = "vec2(" + lookupCoord + ")";
    155 
    156 		lookupsStr += "\tcolor += " + colorMultiplier + "*" + lookupFunc + "(" + samplerName + ", " + lookupCoord + ");\n";
    157 	}
    158 
    159 	return
    160 		samplersStr +
    161 		matricesStr +
    162 		"varying mediump vec2 v_coord;\n"
    163 		"\n"
    164 		"void main (void)\n"
    165 		"{\n"
    166 		"	mediump vec4 color = vec4(0.0);\n" +
    167 		lookupsStr +
    168 		"	gl_FragColor = color;\n"
    169 		"}\n";
    170 }
    171 
    172 static sglr::pdec::ShaderProgramDeclaration generateShaderProgramDeclaration (int numUnits, const GLenum* unitTypes)
    173 {
    174 	sglr::pdec::ShaderProgramDeclaration decl;
    175 
    176 	decl << sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT);
    177 	decl << sglr::pdec::VertexAttribute("a_coord", rr::GENERICVECTYPE_FLOAT);
    178 	decl << sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT);
    179 	decl << sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT);
    180 
    181 	for (int ndx = 0; ndx < numUnits; ++ndx)
    182 	{
    183 		string	samplerName			= "u_sampler" + de::toString(ndx);
    184 		string	transformationName	= "u_trans" + de::toString(ndx);
    185 
    186 		decl << sglr::pdec::Uniform(samplerName, (unitTypes[ndx] == GL_TEXTURE_2D) ? (glu::TYPE_SAMPLER_2D) : (glu::TYPE_SAMPLER_CUBE));
    187 		decl << sglr::pdec::Uniform(transformationName, glu::TYPE_FLOAT_MAT3);
    188 	}
    189 
    190 	decl << sglr::pdec::VertexSource("attribute highp vec4 a_position;\n"
    191 									 "attribute mediump vec2 a_coord;\n"
    192 									 "varying mediump vec2 v_coord;\n"
    193 									 "\n"
    194 									 "void main (void)\n"
    195 									 "{\n"
    196 									 "	gl_Position = a_position;\n"
    197 									 "	v_coord = a_coord;\n"
    198 									 "}\n");
    199 	decl << sglr::pdec::FragmentSource(generateMultiTexFragmentShader(numUnits, unitTypes));
    200 
    201 	return decl;
    202 }
    203 
    204 // Calculates values to be used in calculateLod().
    205 static Vec4 calculateLodDerivateParts(const Mat3& transformation)
    206 {
    207 	// Calculate transformed coordinates of three corners.
    208 	Vec2 trans00 = (transformation * Vec3(0.0f, 0.0f, 1.0f)).xy();
    209 	Vec2 trans01 = (transformation * Vec3(0.0f, 1.0f, 1.0f)).xy();
    210 	Vec2 trans10 = (transformation * Vec3(1.0f, 0.0f, 1.0f)).xy();
    211 
    212 	return Vec4(trans10.x() - trans00.x(),
    213 				trans01.x() - trans00.x(),
    214 				trans10.y() - trans00.y(),
    215 				trans01.y() - trans00.y());
    216 }
    217 
    218 // Calculates the maximum allowed lod from derivates
    219 static float calculateLodMax(const Vec4& derivateParts, const tcu::IVec2& textureSize, const Vec2& screenDerivate)
    220 {
    221 	float dudx = derivateParts.x() * (float)textureSize.x() * screenDerivate.x();
    222 	float dudy = derivateParts.y() * (float)textureSize.x() * screenDerivate.y();
    223 	float dvdx = derivateParts.z() * (float)textureSize.y() * screenDerivate.x();
    224 	float dvdy = derivateParts.w() * (float)textureSize.y() * screenDerivate.y();
    225 
    226 	return deFloatLog2(de::max(de::abs(dudx), de::abs(dudy)) + de::max(de::abs(dvdx), de::abs(dvdy)));
    227 }
    228 
    229 // Calculates the minimum allowed lod from derivates
    230 static float calculateLodMin(const Vec4& derivateParts, const tcu::IVec2& textureSize, const Vec2& screenDerivate)
    231 {
    232 	float dudx = derivateParts.x() * (float)textureSize.x() * screenDerivate.x();
    233 	float dudy = derivateParts.y() * (float)textureSize.x() * screenDerivate.y();
    234 	float dvdx = derivateParts.z() * (float)textureSize.y() * screenDerivate.x();
    235 	float dvdy = derivateParts.w() * (float)textureSize.y() * screenDerivate.y();
    236 
    237 	return deFloatLog2(de::max(de::max(de::abs(dudx), de::abs(dudy)), de::max(de::abs(dvdx), de::abs(dvdy))));
    238 }
    239 
    240 class MultiTexShader : public sglr::ShaderProgram
    241 {
    242 public:
    243 							MultiTexShader	(deUint32 randSeed, int numUnits, const vector<GLenum>& unitTypes);
    244 
    245 	void					setUniforms		(sglr::Context& context, deUint32 program) const;
    246 	void					makeSafeLods	(const vector<IVec2>& textureSizes, const IVec2& viewportSize); // Modifies texture coordinates so that LODs aren't too close to x.5 or 0.0 .
    247 
    248 private:
    249 	void					shadeVertices	(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
    250 	void					shadeFragments	(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
    251 
    252 	int						m_numUnits;
    253 	vector<GLenum>			m_unitTypes;		// 2d or cube map.
    254 	vector<Mat3>			m_transformations;
    255 	vector<Vec4>			m_lodDerivateParts;	// Parts of lod derivates; computed in init(), used in eval().
    256 };
    257 
    258 MultiTexShader::MultiTexShader (deUint32 randSeed, int numUnits, const vector<GLenum>& unitTypes)
    259 	: sglr::ShaderProgram	(generateShaderProgramDeclaration(numUnits, &unitTypes[0]))
    260 	, m_numUnits			(numUnits)
    261 	, m_unitTypes			(unitTypes)
    262 {
    263 	// 2d-to-cube-face transformations.
    264 	// \note 2d coordinates range from 0 to 1 and cube face coordinates from -1 to 1, so scaling is done as well.
    265 	static const float s_cubeTransforms[][3*3] =
    266 	{
    267 		// Face -X: (x, y, 1) -> (-1, -(2*y-1), +(2*x-1))
    268 		{  0.0f,  0.0f, -1.0f,
    269 		   0.0f, -2.0f,  1.0f,
    270 		   2.0f,  0.0f, -1.0f },
    271 		// Face +X: (x, y, 1) -> (+1, -(2*y-1), -(2*x-1))
    272 		{  0.0f,  0.0f,  1.0f,
    273 		   0.0f, -2.0f,  1.0f,
    274 		  -2.0f,  0.0f,  1.0f },
    275 		// Face -Y: (x, y, 1) -> (+(2*x-1), -1, -(2*y-1))
    276 		{  2.0f,  0.0f, -1.0f,
    277 		   0.0f,  0.0f, -1.0f,
    278 		   0.0f, -2.0f,  1.0f },
    279 		// Face +Y: (x, y, 1) -> (+(2*x-1), +1, +(2*y-1))
    280 		{  2.0f,  0.0f, -1.0f,
    281 		   0.0f,  0.0f,  1.0f,
    282 		   0.0f,  2.0f, -1.0f },
    283 		// Face -Z: (x, y, 1) -> (-(2*x-1), -(2*y-1), -1)
    284 		{ -2.0f,  0.0f,  1.0f,
    285 		   0.0f, -2.0f,  1.0f,
    286 		   0.0f,  0.0f, -1.0f },
    287 		// Face +Z: (x, y, 1) -> (+(2*x-1), -(2*y-1), +1)
    288 		{  2.0f,  0.0f, -1.0f,
    289 		   0.0f, -2.0f,  1.0f,
    290 		   0.0f,  0.0f,  1.0f }
    291 	};
    292 
    293 	// Generate transformation matrices.
    294 
    295 	de::Random rnd(randSeed);
    296 
    297 	m_transformations.reserve(m_numUnits);
    298 	m_lodDerivateParts.reserve(m_numUnits);
    299 
    300 	DE_ASSERT((int)m_unitTypes.size() == m_numUnits);
    301 
    302 	for (int unitNdx = 0; unitNdx < m_numUnits; unitNdx++)
    303 	{
    304 		if (m_unitTypes[unitNdx] == GL_TEXTURE_2D)
    305 		{
    306 			float rotAngle				= rnd.getFloat(0.0f, 2.0f*DE_PI);
    307 			float xScaleFactor			= rnd.getFloat(0.7f, 1.5f);
    308 			float yScaleFactor			= rnd.getFloat(0.7f, 1.5f);
    309 			float xShearAmount			= rnd.getFloat(0.0f, 0.5f);
    310 			float yShearAmount			= rnd.getFloat(0.0f, 0.5f);
    311 			float xTranslationAmount	= rnd.getFloat(-0.5f, 0.5f);
    312 			float yTranslationAmount	= rnd.getFloat(-0.5f, 0.5f);
    313 
    314 			float tempOffsetData[3*3] = // For temporarily centering the coordinates to get nicer transformations.
    315 			{
    316 				1.0f,  0.0f, -0.5f,
    317 				0.0f,  1.0f, -0.5f,
    318 				0.0f,  0.0f,  1.0f
    319 			};
    320 			float rotTransfData[3*3] =
    321 			{
    322 				deFloatCos(rotAngle),	-deFloatSin(rotAngle),	0.0f,
    323 				deFloatSin(rotAngle),	deFloatCos(rotAngle),	0.0f,
    324 				0.0f,					0.0f,					1.0f
    325 			};
    326 			float scaleTransfData[3*3] =
    327 			{
    328 				xScaleFactor,	0.0f,			0.0f,
    329 				0.0f,			yScaleFactor,	0.0f,
    330 				0.0f,			0.0f,			1.0f
    331 			};
    332 			float xShearTransfData[3*3] =
    333 			{
    334 				1.0f,			xShearAmount,	0.0f,
    335 				0.0f,			1.0f,			0.0f,
    336 				0.0f,			0.0f,			1.0f
    337 			};
    338 			float yShearTransfData[3*3] =
    339 			{
    340 				1.0f,			0.0f,			0.0f,
    341 				yShearAmount,	1.0f,			0.0f,
    342 				0.0f,			0.0f,			1.0f
    343 			};
    344 			float translationTransfData[3*3] =
    345 			{
    346 				1.0f,	0.0f,	xTranslationAmount,
    347 				0.0f,	1.0f,	yTranslationAmount,
    348 				0.0f,	0.0f,	1.0f
    349 			};
    350 
    351 			Mat3 transformation =
    352 				Mat3(tempOffsetData) *
    353 				Mat3(translationTransfData) *
    354 				Mat3(rotTransfData) *
    355 				Mat3(scaleTransfData) *
    356 				Mat3(xShearTransfData) *
    357 				Mat3(yShearTransfData) *
    358 				(Mat3(tempOffsetData) * (-1.0f));
    359 
    360 			// Calculate parts of lod derivates.
    361 			m_lodDerivateParts.push_back(calculateLodDerivateParts(transformation));
    362 
    363 			m_transformations.push_back(transformation);
    364 		}
    365 		else
    366 		{
    367 			DE_ASSERT(m_unitTypes[unitNdx] == GL_TEXTURE_CUBE_MAP);
    368 			DE_STATIC_ASSERT((int)tcu::CUBEFACE_LAST == DE_LENGTH_OF_ARRAY(s_cubeTransforms));
    369 
    370 			float planarTransData[3*3];
    371 
    372 			// In case of a cube map, we only want to render one face, so the transformation needs to be restricted - only enlarging scaling is done.
    373 
    374 			for (int i = 0; i < DE_LENGTH_OF_ARRAY(planarTransData); i++)
    375 			{
    376 				if (i == 0 || i == 4)
    377 					planarTransData[i] = rnd.getFloat(0.1f, 0.9f); // Two first diagonal cells control the scaling.
    378 				else if (i == 8)
    379 					planarTransData[i] = 1.0f;
    380 				else
    381 					planarTransData[i] = 0.0f;
    382 			}
    383 
    384 			int		faceNdx			= rnd.getInt(0, (int)tcu::CUBEFACE_LAST - 1);
    385 			Mat3	planarTrans		(planarTransData);									// Planar, face-agnostic transformation.
    386 			Mat3	finalTrans		= Mat3(s_cubeTransforms[faceNdx]) * planarTrans;	// Final transformation from planar to cube map coordinates, including the transformation just generated.
    387 
    388 			// Calculate parts of lod derivates.
    389 			m_lodDerivateParts.push_back(calculateLodDerivateParts(planarTrans));
    390 
    391 			m_transformations.push_back(finalTrans);
    392 		}
    393 	}
    394 }
    395 
    396 void MultiTexShader::setUniforms (sglr::Context& ctx, deUint32 program) const
    397 {
    398 	ctx.useProgram(program);
    399 
    400 	// Sampler and matrix uniforms.
    401 
    402 	for (int ndx = 0; ndx < m_numUnits; ndx++)
    403 	{
    404 		string			ndxStr		= de::toString(ndx);
    405 
    406 		ctx.uniform1i(ctx.getUniformLocation(program, ("u_sampler" + ndxStr).c_str()), ndx);
    407 		ctx.uniformMatrix3fv(ctx.getUniformLocation(program, ("u_trans" + ndxStr).c_str()), 1, GL_FALSE, (GLfloat*)&m_transformations[ndx].getColumnMajorData()[0]);
    408 	}
    409 }
    410 
    411 void MultiTexShader::makeSafeLods (const vector<IVec2>& textureSizes, const IVec2& viewportSize)
    412 {
    413 	DE_ASSERT((int)textureSizes.size() == m_numUnits);
    414 
    415 	static const float shrinkScaleMatData[3*3] =
    416 	{
    417 		0.95f,	0.0f,	0.0f,
    418 		0.0f,	0.95f,	0.0f,
    419 		0.0f,	0.0f,	1.0f
    420 	};
    421 	Mat3 shrinkScaleMat(shrinkScaleMatData);
    422 
    423 	Vec2 screenDerivate(1.0f / (float)viewportSize.x(), 1.0f / (float)viewportSize.y());
    424 
    425 	for (int unitNdx = 0; unitNdx < m_numUnits; unitNdx++)
    426 	{
    427 		// As long as LOD is too close to 0.0 or is positive and too close to a something-and-a-half (0.5, 1.5, 2.5 etc) or allowed lod range could round to different levels, zoom in a little to get a safer LOD.
    428 		for (;;)
    429 		{
    430 			const float threshold = 0.1f;
    431 			const float epsilon	= 0.01f;
    432 
    433 			const float lodMax = calculateLodMax(m_lodDerivateParts[unitNdx], textureSizes[unitNdx], screenDerivate);
    434 			const float lodMin = calculateLodMin(m_lodDerivateParts[unitNdx], textureSizes[unitNdx], screenDerivate);
    435 
    436 			const deInt32 maxLevel = (lodMax + epsilon < 0.5f) ? (0) : (deCeilFloatToInt32(lodMax + epsilon + 0.5f) - 1);
    437 			const deInt32 minLevel = (lodMin - epsilon < 0.5f) ? (0) : (deCeilFloatToInt32(lodMin - epsilon + 0.5f) - 1);
    438 
    439 			if (de::abs(lodMax) < threshold || (lodMax > 0.0f && de::abs(deFloatFrac(lodMax) - 0.5f) < threshold) ||
    440 				de::abs(lodMin) < threshold || (lodMin > 0.0f && de::abs(deFloatFrac(lodMin) - 0.5f) < threshold) ||
    441 				maxLevel != minLevel)
    442 			{
    443 				m_transformations[unitNdx] = shrinkScaleMat * m_transformations[unitNdx];
    444 				m_lodDerivateParts[unitNdx] = calculateLodDerivateParts(m_transformations[unitNdx]);
    445 			}
    446 			else
    447 				break;
    448 		}
    449 	}
    450 }
    451 
    452 void MultiTexShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
    453 {
    454 	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
    455 	{
    456 		rr::VertexPacket& packet = *(packets[packetNdx]);
    457 
    458 		packet.position		= rr::readVertexAttribFloat(inputs[0], packet.instanceNdx, packet.vertexNdx);
    459 		packet.outputs[0]	= rr::readVertexAttribFloat(inputs[1], packet.instanceNdx, packet.vertexNdx);
    460 	}
    461 }
    462 
    463 void MultiTexShader::shadeFragments	(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
    464 {
    465 	DE_ASSERT((int)m_unitTypes.size() == m_numUnits);
    466 	DE_ASSERT((int)m_transformations.size() == m_numUnits);
    467 	DE_ASSERT((int)m_lodDerivateParts.size() == m_numUnits);
    468 
    469 	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
    470 	{
    471 		rr::FragmentPacket& packet				= packets[packetNdx];
    472 		const float			colorMultiplier		= 1.0f / (float)m_numUnits;
    473 		Vec4				outColors[4]		= { Vec4(0.0f), Vec4(0.0f), Vec4(0.0f), Vec4(0.0f) };
    474 
    475 		for (int unitNdx = 0; unitNdx < m_numUnits; unitNdx++)
    476 		{
    477 			tcu::Vec4 texSamples[4];
    478 
    479 			// Read tex coords
    480 			const tcu::Vec2 texCoords[4] =
    481 			{
    482 				rr::readTriangleVarying<float>(packet, context, 0, 0).xy(),
    483 				rr::readTriangleVarying<float>(packet, context, 0, 1).xy(),
    484 				rr::readTriangleVarying<float>(packet, context, 0, 2).xy(),
    485 				rr::readTriangleVarying<float>(packet, context, 0, 3).xy(),
    486 			};
    487 
    488 			if (m_unitTypes[unitNdx] == GL_TEXTURE_2D)
    489 			{
    490 				// Transform
    491 				const tcu::Vec2 transformedTexCoords[4] =
    492 				{
    493 					(m_transformations[unitNdx] * Vec3(texCoords[0].x(), texCoords[0].y(), 1.0f)).xy(),
    494 					(m_transformations[unitNdx] * Vec3(texCoords[1].x(), texCoords[1].y(), 1.0f)).xy(),
    495 					(m_transformations[unitNdx] * Vec3(texCoords[2].x(), texCoords[2].y(), 1.0f)).xy(),
    496 					(m_transformations[unitNdx] * Vec3(texCoords[3].x(), texCoords[3].y(), 1.0f)).xy(),
    497 				};
    498 
    499 				// Sample
    500 				m_uniforms[2*unitNdx].sampler.tex2D->sample4(texSamples, transformedTexCoords);
    501 			}
    502 			else
    503 			{
    504 				DE_ASSERT(m_unitTypes[unitNdx] == GL_TEXTURE_CUBE_MAP);
    505 
    506 				// Transform
    507 				const tcu::Vec3 transformedTexCoords[4] =
    508 				{
    509 					m_transformations[unitNdx] * Vec3(texCoords[0].x(), texCoords[0].y(), 1.0f),
    510 					m_transformations[unitNdx] * Vec3(texCoords[1].x(), texCoords[1].y(), 1.0f),
    511 					m_transformations[unitNdx] * Vec3(texCoords[2].x(), texCoords[2].y(), 1.0f),
    512 					m_transformations[unitNdx] * Vec3(texCoords[3].x(), texCoords[3].y(), 1.0f),
    513 				};
    514 
    515 				// Sample
    516 				m_uniforms[2*unitNdx].sampler.texCube->sample4(texSamples, transformedTexCoords);
    517 			}
    518 
    519 			// Add to sum
    520 			for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
    521 				outColors[fragNdx] += colorMultiplier * texSamples[fragNdx];
    522 		}
    523 
    524 		// output
    525 		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
    526 			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, outColors[fragNdx]);
    527 	}
    528 }
    529 
    530 class TextureUnitCase : public TestCase
    531 {
    532 public:
    533 	enum CaseType
    534 	{
    535 		CASE_ONLY_2D = 0,
    536 		CASE_ONLY_CUBE,
    537 		CASE_MIXED,
    538 
    539 		CASE_LAST
    540 	};
    541 								TextureUnitCase		(Context& context, const char* name, const char* desc, int numUnits /* \note If non-positive, use all units */, CaseType caseType, deUint32 randSeed);
    542 								~TextureUnitCase	(void);
    543 
    544 	void						init				(void);
    545 	void						deinit				(void);
    546 	IterateResult				iterate				(void);
    547 
    548 private:
    549 	struct TextureParameters
    550 	{
    551 		GLenum format;
    552 		GLenum dataType;
    553 		GLenum wrapModeS;
    554 		GLenum wrapModeT;
    555 		GLenum minFilter;
    556 		GLenum magFilter;
    557 	};
    558 
    559 								TextureUnitCase		(const TextureUnitCase& other);
    560 	TextureUnitCase&			operator=			(const TextureUnitCase& other);
    561 
    562 	void						render				(sglr::Context& context);
    563 
    564 	const int					m_numUnitsParam;
    565 	const CaseType				m_caseType;
    566 	const deUint32				m_randSeed;
    567 
    568 	int							m_numTextures;	//!< \note Needed in addition to m_numUnits since same texture may be bound to many texture units.
    569 	int							m_numUnits;		//!< = m_numUnitsParam > 0 ? m_numUnitsParam : implementationDefinedMaximum
    570 
    571 	vector<GLenum>				m_textureTypes;
    572 	vector<TextureParameters>	m_textureParams;
    573 	vector<tcu::Texture2D*>		m_textures2d;
    574 	vector<tcu::TextureCube*>	m_texturesCube;
    575 	vector<int>					m_unitTextures;	//!< Which texture is used in a particular unit.
    576 	vector<int>					m_ndx2dOrCube;	//!< Index of a texture in either m_textures2d or m_texturesCube, depending on texture type.
    577 	MultiTexShader*				m_shader;
    578 };
    579 
    580 TextureUnitCase::TextureUnitCase (Context& context, const char* name, const char* desc, int numUnits, CaseType caseType, deUint32 randSeed)
    581 	: TestCase			(context, tcu::NODETYPE_SELF_VALIDATE, name, desc)
    582 	, m_numUnitsParam	(numUnits)
    583 	, m_caseType		(caseType)
    584 	, m_randSeed		(randSeed)
    585 	, m_shader			(DE_NULL)
    586 {
    587 }
    588 
    589 TextureUnitCase::~TextureUnitCase (void)
    590 {
    591 	TextureUnitCase::deinit();
    592 }
    593 
    594 void TextureUnitCase::deinit (void)
    595 {
    596 	for (vector<tcu::Texture2D*>::iterator i = m_textures2d.begin(); i != m_textures2d.end(); i++)
    597 		delete *i;
    598 	m_textures2d.clear();
    599 
    600 	for (vector<tcu::TextureCube*>::iterator i = m_texturesCube.begin(); i != m_texturesCube.end(); i++)
    601 		delete *i;
    602 	m_texturesCube.clear();
    603 
    604 	delete m_shader;
    605 	m_shader = DE_NULL;
    606 }
    607 
    608 void TextureUnitCase::init (void)
    609 {
    610 	m_numUnits = m_numUnitsParam > 0 ? m_numUnitsParam : m_context.getContextInfo().getInt(GL_MAX_TEXTURE_IMAGE_UNITS);
    611 
    612 	// Make the textures.
    613 
    614 	try
    615 	{
    616 		tcu::TestLog&	log	= m_testCtx.getLog();
    617 		de::Random		rnd	(m_randSeed);
    618 
    619 		if (rnd.getFloat() < 0.7f)
    620 			m_numTextures = m_numUnits;											// In most cases use one unit per texture.
    621 		else
    622 			m_numTextures = rnd.getInt(deMax32(1, m_numUnits - 2), m_numUnits);	// Sometimes assign same texture to multiple units.
    623 
    624 		log << tcu::TestLog::Message << ("Using " + de::toString(m_numUnits) + " texture unit(s) and " + de::toString(m_numTextures) + " texture(s)").c_str() << tcu::TestLog::EndMessage;
    625 
    626 		m_textureTypes.reserve(m_numTextures);
    627 		m_textureParams.reserve(m_numTextures);
    628 		m_ndx2dOrCube.reserve(m_numTextures);
    629 
    630 		// Generate textures.
    631 
    632 		for (int texNdx = 0; texNdx < m_numTextures; texNdx++)
    633 		{
    634 			// Either fixed or randomized target types (2d or cube), and randomized parameters for every texture.
    635 
    636 			TextureParameters	params;
    637 			bool				is2d		= m_caseType == CASE_ONLY_2D	? true :
    638 											  m_caseType == CASE_ONLY_CUBE	? false :
    639 																			  rnd.getBool();
    640 
    641 			GLenum				type		= is2d ? GL_TEXTURE_2D : GL_TEXTURE_CUBE_MAP;
    642 			const int			texWidth	= is2d ? TEXTURE_WIDTH_2D : TEXTURE_WIDTH_CUBE;
    643 			const int			texHeight	= is2d ? TEXTURE_HEIGHT_2D : TEXTURE_HEIGHT_CUBE;
    644 			bool				mipmaps		= (deIsPowerOfTwo32(texWidth) && deIsPowerOfTwo32(texHeight));
    645 			int					numLevels	= mipmaps ? deLog2Floor32(de::max(texWidth, texHeight))+1 : 1;
    646 
    647 			params.wrapModeS	= s_testWrapModes	[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_testWrapModes) - 1)];
    648 			params.wrapModeT	= s_testWrapModes	[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_testWrapModes) - 1)];
    649 			params.magFilter	= s_testMagFilters	[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_testMagFilters) - 1)];
    650 			params.dataType		= s_testDataTypes	[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_testDataTypes) - 1)];
    651 
    652 			// Certain minification filters are only used when using mipmaps.
    653 			if (mipmaps)
    654 				params.minFilter = s_testMinFilters[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_testMinFilters) - 1)];
    655 			else
    656 				params.minFilter = s_testNonMipmapMinFilters[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_testNonMipmapMinFilters) - 1)];
    657 
    658 			// Format may depend on data type.
    659 			if (params.dataType == GL_UNSIGNED_SHORT_5_6_5)
    660 				params.format = GL_RGB;
    661 			else if (params.dataType == GL_UNSIGNED_SHORT_4_4_4_4 || params.dataType == GL_UNSIGNED_SHORT_5_5_5_1)
    662 				params.format = GL_RGBA;
    663 			else
    664 				params.format = s_testFormats[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_testFormats) - 1)];
    665 
    666 			m_textureTypes.push_back(type);
    667 			m_textureParams.push_back(params);
    668 
    669 			// Create new texture.
    670 
    671 			if (is2d)
    672 			{
    673 				m_ndx2dOrCube.push_back((int)m_textures2d.size()); // Remember the index this texture has in the 2d array.
    674 				m_textures2d.push_back(new tcu::Texture2D(glu::mapGLTransferFormat(params.format, params.dataType), texWidth, texHeight));
    675 			}
    676 			else
    677 			{
    678 				m_ndx2dOrCube.push_back((int)m_texturesCube.size()); // Remember the index this texture has in the cube array.
    679 				DE_ASSERT(texWidth == texHeight);
    680 				m_texturesCube.push_back(new tcu::TextureCube(glu::mapGLTransferFormat(params.format, params.dataType), texWidth));
    681 			}
    682 
    683 			tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(is2d ? m_textures2d.back()->getFormat() : m_texturesCube.back()->getFormat());
    684 			Vec4					cBias		= fmtInfo.valueMin;
    685 			Vec4					cScale		= fmtInfo.valueMax-fmtInfo.valueMin;
    686 
    687 			// Fill with grid texture.
    688 
    689 			int numFaces = is2d ? 1 : (int)tcu::CUBEFACE_LAST;
    690 
    691 			for (int face = 0; face < numFaces; face++)
    692 			{
    693 				deUint32 rgb	= rnd.getUint32() & 0x00ffffff;
    694 				deUint32 alpha0	= 0xff000000;
    695 				deUint32 alpha1	= 0xff000000;
    696 
    697 				if (params.format == GL_ALPHA) // \note This needs alpha to be visible.
    698 				{
    699 					alpha0 &= rnd.getUint32();
    700 					alpha1 = ~alpha0;
    701 				}
    702 
    703 				deUint32 colorA = alpha0 | rgb;
    704 				deUint32 colorB = alpha1 | ~rgb;
    705 
    706 				for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
    707 				{
    708 					if (is2d)
    709 						m_textures2d.back()->allocLevel(levelNdx);
    710 					else
    711 						m_texturesCube.back()->allocLevel((tcu::CubeFace)face, levelNdx);
    712 
    713 					int curCellSize = deMax32(1, GRID_CELL_SIZE >> levelNdx); // \note Scale grid cell size for mipmaps.
    714 
    715 					tcu::PixelBufferAccess access = is2d ? m_textures2d.back()->getLevel(levelNdx) : m_texturesCube.back()->getLevelFace(levelNdx, (tcu::CubeFace)face);
    716 					tcu::fillWithGrid(access, curCellSize, toVec4(tcu::RGBA(colorA))*cScale + cBias, toVec4(tcu::RGBA(colorB))*cScale + cBias);
    717 				}
    718 			}
    719 		}
    720 
    721 		// Assign a texture index to each unit.
    722 
    723 		m_unitTextures.reserve(m_numUnits);
    724 
    725 		// \note Every texture is used at least once.
    726 		for (int i = 0; i < m_numTextures; i++)
    727 			m_unitTextures.push_back(i);
    728 
    729 		// Assign a random texture to remaining units.
    730 		while ((int)m_unitTextures.size() < m_numUnits)
    731 			m_unitTextures.push_back(rnd.getInt(0, m_numTextures - 1));
    732 
    733 		rnd.shuffle(m_unitTextures.begin(), m_unitTextures.end());
    734 
    735 		// Create shader.
    736 
    737 		vector<GLenum> unitTypes;
    738 		unitTypes.reserve(m_numUnits);
    739 		for (int i = 0; i < m_numUnits; i++)
    740 			unitTypes.push_back(m_textureTypes[m_unitTextures[i]]);
    741 
    742 		DE_ASSERT(m_shader == DE_NULL);
    743 		m_shader = new MultiTexShader(rnd.getUint32(), m_numUnits, unitTypes);
    744 	}
    745 	catch (const std::exception&)
    746 	{
    747 		// Clean up to save memory.
    748 		TextureUnitCase::deinit();
    749 		throw;
    750 	}
    751 }
    752 
    753 TextureUnitCase::IterateResult TextureUnitCase::iterate (void)
    754 {
    755 	glu::RenderContext&			renderCtx			= m_context.getRenderContext();
    756 	const tcu::RenderTarget&	renderTarget		= renderCtx.getRenderTarget();
    757 	tcu::TestLog&				log					= m_testCtx.getLog();
    758 	de::Random					rnd					(m_randSeed);
    759 
    760 	int							viewportWidth		= deMin32(VIEWPORT_WIDTH, renderTarget.getWidth());
    761 	int							viewportHeight		= deMin32(VIEWPORT_HEIGHT, renderTarget.getHeight());
    762 	int							viewportX			= rnd.getInt(0, renderTarget.getWidth() - viewportWidth);
    763 	int							viewportY			= rnd.getInt(0, renderTarget.getHeight() - viewportHeight);
    764 
    765 	tcu::Surface				gles2Frame			(viewportWidth, viewportHeight);
    766 	tcu::Surface				refFrame			(viewportWidth, viewportHeight);
    767 
    768 	{
    769 		// First we do some tricks to make the LODs safer wrt. precision issues. See MultiTexShader::makeSafeLods().
    770 
    771 		vector<IVec2> texSizes;
    772 		texSizes.reserve(m_numUnits);
    773 
    774 		for (int i = 0; i < m_numUnits; i++)
    775 		{
    776 			int		texNdx			= m_unitTextures[i];
    777 			int		texNdxInType	= m_ndx2dOrCube[texNdx];
    778 			GLenum	type			= m_textureTypes[texNdx];
    779 
    780 			switch (type)
    781 			{
    782 				case GL_TEXTURE_2D:			texSizes.push_back(IVec2(m_textures2d[texNdxInType]->getWidth(),	m_textures2d[texNdxInType]->getHeight()));	break;
    783 				case GL_TEXTURE_CUBE_MAP:	texSizes.push_back(IVec2(m_texturesCube[texNdxInType]->getSize(),	m_texturesCube[texNdxInType]->getSize()));	break;
    784 				default:
    785 					DE_ASSERT(DE_FALSE);
    786 			}
    787 		}
    788 
    789 		m_shader->makeSafeLods(texSizes, IVec2(viewportWidth, viewportHeight));
    790 	}
    791 
    792 	// Render using GLES2.
    793 	{
    794 		sglr::GLContext context(renderCtx, log, sglr::GLCONTEXT_LOG_CALLS|sglr::GLCONTEXT_LOG_PROGRAMS, tcu::IVec4(viewportX, viewportY, viewportWidth, viewportHeight));
    795 
    796 		render(context);
    797 
    798 		context.readPixels(gles2Frame, 0, 0, viewportWidth, viewportHeight);
    799 	}
    800 
    801 	// Render reference image.
    802 	{
    803 		sglr::ReferenceContextBuffers	buffers	(tcu::PixelFormat(8,8,8,renderTarget.getPixelFormat().alphaBits?8:0), 0 /* depth */, 0 /* stencil */, viewportWidth, viewportHeight);
    804 		sglr::ReferenceContext			context	(sglr::ReferenceContextLimits(renderCtx), buffers.getColorbuffer(), buffers.getDepthbuffer(), buffers.getStencilbuffer());
    805 
    806 		render(context);
    807 
    808 		context.readPixels(refFrame, 0, 0, viewportWidth, viewportHeight);
    809 	}
    810 
    811 	// Compare images.
    812 	const float		threshold	= 0.001f;
    813 	bool			isOk		= tcu::fuzzyCompare(log, "ComparisonResult", "Image comparison result", refFrame, gles2Frame, threshold, tcu::COMPARE_LOG_RESULT);
    814 
    815 	// Store test result.
    816 	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
    817 							isOk ? "Pass"				: "Image comparison failed");
    818 
    819 	return STOP;
    820 }
    821 
    822 void TextureUnitCase::render (sglr::Context& context)
    823 {
    824 	// Setup textures.
    825 
    826 	vector<deUint32>	textureGLNames;
    827 	vector<bool>		isTextureSetUp(m_numTextures, false); // \note Same texture may be bound to multiple units, but we only want to set up parameters and data once per texture.
    828 
    829 	textureGLNames.resize(m_numTextures);
    830 	context.genTextures(m_numTextures, &textureGLNames[0]);
    831 
    832 	for (int unitNdx = 0; unitNdx < m_numUnits; unitNdx++)
    833 	{
    834 		int texNdx = m_unitTextures[unitNdx];
    835 
    836 		// Bind texture to unit.
    837 		context.activeTexture(GL_TEXTURE0 + unitNdx);
    838 		context.bindTexture(m_textureTypes[texNdx], textureGLNames[texNdx]);
    839 
    840 		if (!isTextureSetUp[texNdx])
    841 		{
    842 			// Binding this texture for first time, so set parameters and data.
    843 
    844 			context.texParameteri(m_textureTypes[texNdx], GL_TEXTURE_WRAP_S, m_textureParams[texNdx].wrapModeS);
    845 			context.texParameteri(m_textureTypes[texNdx], GL_TEXTURE_WRAP_T, m_textureParams[texNdx].wrapModeT);
    846 			context.texParameteri(m_textureTypes[texNdx], GL_TEXTURE_MIN_FILTER, m_textureParams[texNdx].minFilter);
    847 			context.texParameteri(m_textureTypes[texNdx], GL_TEXTURE_MAG_FILTER, m_textureParams[texNdx].magFilter);
    848 
    849 			if (m_textureTypes[texNdx] == GL_TEXTURE_2D)
    850 			{
    851 				int						ndx2d		= m_ndx2dOrCube[texNdx];
    852 				const tcu::Texture2D*	texture		= m_textures2d[ndx2d];
    853 				bool					mipmaps		= (deIsPowerOfTwo32(texture->getWidth()) && deIsPowerOfTwo32(texture->getHeight()));
    854 				int						numLevels	= mipmaps ? deLog2Floor32(de::max(texture->getWidth(), texture->getHeight()))+1 : 1;
    855 
    856 				context.pixelStorei(GL_UNPACK_ALIGNMENT, 1);
    857 
    858 				for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
    859 				{
    860 					tcu::ConstPixelBufferAccess		access	= texture->getLevel(levelNdx);
    861 					int								width	= access.getWidth();
    862 					int								height	= access.getHeight();
    863 
    864 					DE_ASSERT(access.getRowPitch() == access.getFormat().getPixelSize()*width);
    865 
    866 					context.texImage2D(GL_TEXTURE_2D, levelNdx, m_textureParams[texNdx].format, width, height, 0, m_textureParams[texNdx].format, m_textureParams[texNdx].dataType, access.getDataPtr());
    867 				}
    868 			}
    869 			else
    870 			{
    871 				DE_ASSERT(m_textureTypes[texNdx] == GL_TEXTURE_CUBE_MAP);
    872 
    873 				int							ndxCube		= m_ndx2dOrCube[texNdx];
    874 				const tcu::TextureCube*		texture		= m_texturesCube[ndxCube];
    875 				bool						mipmaps		= deIsPowerOfTwo32(texture->getSize()) != DE_FALSE;
    876 				int							numLevels	= mipmaps ? deLog2Floor32(texture->getSize())+1 : 1;
    877 
    878 				context.pixelStorei(GL_UNPACK_ALIGNMENT, 1);
    879 
    880 				for (int face = 0; face < (int)tcu::CUBEFACE_LAST; face++)
    881 				{
    882 					for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
    883 					{
    884 						tcu::ConstPixelBufferAccess		access	= texture->getLevelFace(levelNdx, (tcu::CubeFace)face);
    885 						int								width	= access.getWidth();
    886 						int								height	= access.getHeight();
    887 
    888 						DE_ASSERT(access.getRowPitch() == access.getFormat().getPixelSize()*width);
    889 
    890 						context.texImage2D(s_cubeFaceTargets[face], levelNdx, m_textureParams[texNdx].format, width, height, 0, m_textureParams[texNdx].format, m_textureParams[texNdx].dataType, access.getDataPtr());
    891 					}
    892 				}
    893 			}
    894 
    895 			isTextureSetUp[texNdx] = true; // Don't set up this texture's parameters and data again later.
    896 		}
    897 	}
    898 
    899 	GLU_EXPECT_NO_ERROR(context.getError(), "Set textures");
    900 
    901 	// Setup shader
    902 
    903 	deUint32 shaderID = context.createProgram(m_shader);
    904 
    905 	// Draw.
    906 
    907 	context.clearColor(0.125f, 0.25f, 0.5f, 1.0f);
    908 	context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
    909 	m_shader->setUniforms(context, shaderID);
    910 	sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
    911 	GLU_EXPECT_NO_ERROR(context.getError(), "Draw");
    912 
    913 	// Delete previously generated texture names.
    914 
    915 	context.deleteTextures(m_numTextures, &textureGLNames[0]);
    916 	GLU_EXPECT_NO_ERROR(context.getError(), "Delete textures");
    917 }
    918 
    919 TextureUnitTests::TextureUnitTests (Context& context)
    920 	: TestCaseGroup(context, "units", "Texture Unit Usage Tests")
    921 {
    922 }
    923 
    924 TextureUnitTests::~TextureUnitTests (void)
    925 {
    926 }
    927 
    928 void TextureUnitTests::init (void)
    929 {
    930 	const int numTestsPerGroup = 10;
    931 
    932 	static const int unitCounts[] =
    933 	{
    934 		2,
    935 		4,
    936 		8,
    937 		-1 // \note Negative stands for the implementation-specified maximum.
    938 	};
    939 
    940 	for (int unitCountNdx = 0; unitCountNdx < DE_LENGTH_OF_ARRAY(unitCounts); unitCountNdx++)
    941 	{
    942 		int numUnits = unitCounts[unitCountNdx];
    943 
    944 		string countGroupName = (unitCounts[unitCountNdx] < 0 ? "all" : de::toString(numUnits)) + "_units";
    945 
    946 		tcu::TestCaseGroup* countGroup = new tcu::TestCaseGroup(m_testCtx, countGroupName.c_str(), "");
    947 		addChild(countGroup);
    948 
    949 		DE_STATIC_ASSERT((int)TextureUnitCase::CASE_ONLY_2D == 0);
    950 
    951 		for (int caseType = (int)TextureUnitCase::CASE_ONLY_2D; caseType < (int)TextureUnitCase::CASE_LAST; caseType++)
    952 		{
    953 			const char* caseTypeGroupName = (TextureUnitCase::CaseType)caseType == TextureUnitCase::CASE_ONLY_2D	? "only_2d" :
    954 											(TextureUnitCase::CaseType)caseType == TextureUnitCase::CASE_ONLY_CUBE	? "only_cube" :
    955 											(TextureUnitCase::CaseType)caseType == TextureUnitCase::CASE_MIXED		? "mixed" :
    956 																													  DE_NULL;
    957 			DE_ASSERT(caseTypeGroupName != DE_NULL);
    958 
    959 			tcu::TestCaseGroup* caseTypeGroup = new tcu::TestCaseGroup(m_testCtx, caseTypeGroupName, "");
    960 			countGroup->addChild(caseTypeGroup);
    961 
    962 			for (int testNdx = 0; testNdx < numTestsPerGroup; testNdx++)
    963 				caseTypeGroup->addChild(new TextureUnitCase(m_context, de::toString(testNdx).c_str(), "", numUnits, (TextureUnitCase::CaseType)caseType, (deUint32)deInt32Hash(testNdx)));
    964 		}
    965 	}
    966 }
    967 
    968 } // Functional
    969 } // gles2
    970 } // deqp
    971