1/*------------------------------------------------------------------------- 2 * drawElements Quality Program EGL 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 Rendering tests for different config and api combinations. 22 * \todo [2013-03-19 pyry] GLES1 and VG support. 23 *//*--------------------------------------------------------------------*/ 24 25#include "teglRenderTests.hpp" 26#include "teglRenderCase.hpp" 27 28#include "tcuRenderTarget.hpp" 29#include "tcuTestLog.hpp" 30#include "tcuImageCompare.hpp" 31#include "tcuTextureUtil.hpp" 32#include "tcuSurface.hpp" 33 34#include "egluDefs.hpp" 35#include "egluUtil.hpp" 36 37#include "eglwLibrary.hpp" 38#include "eglwEnums.hpp" 39 40#include "gluShaderProgram.hpp" 41 42#include "glwFunctions.hpp" 43#include "glwEnums.hpp" 44 45#include "deRandom.hpp" 46#include "deSharedPtr.hpp" 47#include "deSemaphore.hpp" 48#include "deThread.hpp" 49#include "deString.h" 50 51#include "rrRenderer.hpp" 52#include "rrFragmentOperations.hpp" 53 54#include <algorithm> 55#include <iterator> 56#include <memory> 57#include <set> 58 59namespace deqp 60{ 61namespace egl 62{ 63 64using std::string; 65using std::vector; 66using std::set; 67 68using tcu::Vec4; 69 70using tcu::TestLog; 71 72using namespace glw; 73using namespace eglw; 74 75static const tcu::Vec4 CLEAR_COLOR = tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f); 76static const float CLEAR_DEPTH = 1.0f; 77static const int CLEAR_STENCIL = 0; 78 79namespace 80{ 81 82enum PrimitiveType 83{ 84 PRIMITIVETYPE_TRIANGLE = 0, //!< Triangles, requires 3 coordinates per primitive 85// PRIMITIVETYPE_POINT, //!< Points, requires 1 coordinate per primitive (w is used as size) 86// PRIMITIVETYPE_LINE, //!< Lines, requires 2 coordinates per primitive 87 88 PRIMITIVETYPE_LAST 89}; 90 91enum BlendMode 92{ 93 BLENDMODE_NONE = 0, //!< No blending 94 BLENDMODE_ADDITIVE, //!< Blending with ONE, ONE 95 BLENDMODE_SRC_OVER, //!< Blending with SRC_ALPHA, ONE_MINUS_SRC_ALPHA 96 97 BLENDMODE_LAST 98}; 99 100enum DepthMode 101{ 102 DEPTHMODE_NONE = 0, //!< No depth test or depth writes 103 DEPTHMODE_LESS, //!< Depth test with less & depth write 104 105 DEPTHMODE_LAST 106}; 107 108enum StencilMode 109{ 110 STENCILMODE_NONE = 0, //!< No stencil test or write 111 STENCILMODE_LEQUAL_INC, //!< Stencil test with LEQUAL, increment on pass 112 113 STENCILMODE_LAST 114}; 115 116struct DrawPrimitiveOp 117{ 118 PrimitiveType type; 119 int count; 120 vector<Vec4> positions; 121 vector<Vec4> colors; 122 BlendMode blend; 123 DepthMode depth; 124 StencilMode stencil; 125 int stencilRef; 126}; 127 128static bool isANarrowScreenSpaceTriangle (const tcu::Vec4& p0, const tcu::Vec4& p1, const tcu::Vec4& p2) 129{ 130 // to clip space 131 const tcu::Vec2 csp0 = p0.swizzle(0, 1) / p0.w(); 132 const tcu::Vec2 csp1 = p1.swizzle(0, 1) / p1.w(); 133 const tcu::Vec2 csp2 = p2.swizzle(0, 1) / p2.w(); 134 135 const tcu::Vec2 e01 = (csp1 - csp0); 136 const tcu::Vec2 e02 = (csp2 - csp0); 137 138 const float minimumVisibleArea = 0.4f; // must cover at least 10% of the surface 139 const float visibleArea = de::abs(e01.x() * e02.y() - e02.x() * e01.y()) * 0.5f; 140 141 return visibleArea < minimumVisibleArea; 142} 143 144void randomizeDrawOp (de::Random& rnd, DrawPrimitiveOp& drawOp) 145{ 146 const int minStencilRef = 0; 147 const int maxStencilRef = 8; 148 const int minPrimitives = 2; 149 const int maxPrimitives = 4; 150 151 const float maxTriOffset = 1.0f; 152 const float minDepth = -1.0f; // \todo [pyry] Reference doesn't support Z clipping yet 153 const float maxDepth = 1.0f; 154 155 const float minRGB = 0.2f; 156 const float maxRGB = 0.9f; 157 const float minAlpha = 0.3f; 158 const float maxAlpha = 1.0f; 159 160 drawOp.type = (PrimitiveType)rnd.getInt(0, PRIMITIVETYPE_LAST-1); 161 drawOp.count = rnd.getInt(minPrimitives, maxPrimitives); 162 drawOp.blend = (BlendMode)rnd.getInt(0, BLENDMODE_LAST-1); 163 drawOp.depth = (DepthMode)rnd.getInt(0, DEPTHMODE_LAST-1); 164 drawOp.stencil = (StencilMode)rnd.getInt(0, STENCILMODE_LAST-1); 165 drawOp.stencilRef = rnd.getInt(minStencilRef, maxStencilRef); 166 167 if (drawOp.type == PRIMITIVETYPE_TRIANGLE) 168 { 169 drawOp.positions.resize(drawOp.count*3); 170 drawOp.colors.resize(drawOp.count*3); 171 172 for (int triNdx = 0; triNdx < drawOp.count; triNdx++) 173 { 174 const float cx = rnd.getFloat(-1.0f, 1.0f); 175 const float cy = rnd.getFloat(-1.0f, 1.0f); 176 177 for (int coordNdx = 0; coordNdx < 3; coordNdx++) 178 { 179 tcu::Vec4& position = drawOp.positions[triNdx*3 + coordNdx]; 180 tcu::Vec4& color = drawOp.colors[triNdx*3 + coordNdx]; 181 182 position.x() = cx + rnd.getFloat(-maxTriOffset, maxTriOffset); 183 position.y() = cy + rnd.getFloat(-maxTriOffset, maxTriOffset); 184 position.z() = rnd.getFloat(minDepth, maxDepth); 185 position.w() = 1.0f; 186 187 color.x() = rnd.getFloat(minRGB, maxRGB); 188 color.y() = rnd.getFloat(minRGB, maxRGB); 189 color.z() = rnd.getFloat(minRGB, maxRGB); 190 color.w() = rnd.getFloat(minAlpha, maxAlpha); 191 } 192 193 // avoid generating narrow triangles 194 { 195 const int maxAttempts = 40; 196 int numAttempts = 0; 197 tcu::Vec4& p0 = drawOp.positions[triNdx*3 + 0]; 198 tcu::Vec4& p1 = drawOp.positions[triNdx*3 + 1]; 199 tcu::Vec4& p2 = drawOp.positions[triNdx*3 + 2]; 200 201 while (isANarrowScreenSpaceTriangle(p0, p1, p2)) 202 { 203 p1.x() = cx + rnd.getFloat(-maxTriOffset, maxTriOffset); 204 p1.y() = cy + rnd.getFloat(-maxTriOffset, maxTriOffset); 205 p1.z() = rnd.getFloat(minDepth, maxDepth); 206 p1.w() = 1.0f; 207 208 p2.x() = cx + rnd.getFloat(-maxTriOffset, maxTriOffset); 209 p2.y() = cy + rnd.getFloat(-maxTriOffset, maxTriOffset); 210 p2.z() = rnd.getFloat(minDepth, maxDepth); 211 p2.w() = 1.0f; 212 213 if (++numAttempts > maxAttempts) 214 { 215 DE_ASSERT(false); 216 break; 217 } 218 } 219 } 220 } 221 } 222 else 223 DE_ASSERT(false); 224} 225 226// Reference rendering code 227 228class ReferenceShader : public rr::VertexShader, public rr::FragmentShader 229{ 230public: 231 enum 232 { 233 VaryingLoc_Color = 0 234 }; 235 236 ReferenceShader () 237 : rr::VertexShader (2, 1) // color and pos in => color out 238 , rr::FragmentShader(1, 1) // color in => color out 239 { 240 this->rr::VertexShader::m_inputs[0].type = rr::GENERICVECTYPE_FLOAT; 241 this->rr::VertexShader::m_inputs[1].type = rr::GENERICVECTYPE_FLOAT; 242 243 this->rr::VertexShader::m_outputs[0].type = rr::GENERICVECTYPE_FLOAT; 244 this->rr::VertexShader::m_outputs[0].flatshade = false; 245 246 this->rr::FragmentShader::m_inputs[0].type = rr::GENERICVECTYPE_FLOAT; 247 this->rr::FragmentShader::m_inputs[0].flatshade = false; 248 249 this->rr::FragmentShader::m_outputs[0].type = rr::GENERICVECTYPE_FLOAT; 250 } 251 252 void shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const 253 { 254 for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx) 255 { 256 const int positionAttrLoc = 0; 257 const int colorAttrLoc = 1; 258 259 rr::VertexPacket& packet = *packets[packetNdx]; 260 261 // Transform to position 262 packet.position = rr::readVertexAttribFloat(inputs[positionAttrLoc], packet.instanceNdx, packet.vertexNdx); 263 264 // Pass color to FS 265 packet.outputs[VaryingLoc_Color] = rr::readVertexAttribFloat(inputs[colorAttrLoc], packet.instanceNdx, packet.vertexNdx); 266 } 267 } 268 269 void shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const 270 { 271 for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx) 272 { 273 rr::FragmentPacket& packet = packets[packetNdx]; 274 275 for (int fragNdx = 0; fragNdx < 4; ++fragNdx) 276 rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, rr::readVarying<float>(packet, context, VaryingLoc_Color, fragNdx)); 277 } 278 } 279}; 280 281void toReferenceRenderState (rr::RenderState& state, const DrawPrimitiveOp& drawOp) 282{ 283 state.cullMode = rr::CULLMODE_NONE; 284 285 if (drawOp.blend != BLENDMODE_NONE) 286 { 287 state.fragOps.blendMode = rr::BLENDMODE_STANDARD; 288 289 switch (drawOp.blend) 290 { 291 case BLENDMODE_ADDITIVE: 292 state.fragOps.blendRGBState.srcFunc = rr::BLENDFUNC_ONE; 293 state.fragOps.blendRGBState.dstFunc = rr::BLENDFUNC_ONE; 294 state.fragOps.blendRGBState.equation = rr::BLENDEQUATION_ADD; 295 state.fragOps.blendAState = state.fragOps.blendRGBState; 296 break; 297 298 case BLENDMODE_SRC_OVER: 299 state.fragOps.blendRGBState.srcFunc = rr::BLENDFUNC_SRC_ALPHA; 300 state.fragOps.blendRGBState.dstFunc = rr::BLENDFUNC_ONE_MINUS_SRC_ALPHA; 301 state.fragOps.blendRGBState.equation = rr::BLENDEQUATION_ADD; 302 state.fragOps.blendAState = state.fragOps.blendRGBState; 303 break; 304 305 default: 306 DE_ASSERT(false); 307 } 308 } 309 310 if (drawOp.depth != DEPTHMODE_NONE) 311 { 312 state.fragOps.depthTestEnabled = true; 313 314 DE_ASSERT(drawOp.depth == DEPTHMODE_LESS); 315 state.fragOps.depthFunc = rr::TESTFUNC_LESS; 316 } 317 318 if (drawOp.stencil != STENCILMODE_NONE) 319 { 320 state.fragOps.stencilTestEnabled = true; 321 322 DE_ASSERT(drawOp.stencil == STENCILMODE_LEQUAL_INC); 323 state.fragOps.stencilStates[0].func = rr::TESTFUNC_LEQUAL; 324 state.fragOps.stencilStates[0].sFail = rr::STENCILOP_KEEP; 325 state.fragOps.stencilStates[0].dpFail = rr::STENCILOP_INCR; 326 state.fragOps.stencilStates[0].dpPass = rr::STENCILOP_INCR; 327 state.fragOps.stencilStates[0].ref = drawOp.stencilRef; 328 state.fragOps.stencilStates[1] = state.fragOps.stencilStates[0]; 329 } 330} 331 332tcu::TextureFormat getColorFormat (const tcu::PixelFormat& colorBits) 333{ 334 using tcu::TextureFormat; 335 336 DE_ASSERT(de::inBounds(colorBits.redBits, 0, 0xff) && 337 de::inBounds(colorBits.greenBits, 0, 0xff) && 338 de::inBounds(colorBits.blueBits, 0, 0xff) && 339 de::inBounds(colorBits.alphaBits, 0, 0xff)); 340 341#define PACK_FMT(R, G, B, A) (((R) << 24) | ((G) << 16) | ((B) << 8) | (A)) 342 343 // \note [pyry] This may not hold true on some implementations - best effort guess only. 344 switch (PACK_FMT(colorBits.redBits, colorBits.greenBits, colorBits.blueBits, colorBits.alphaBits)) 345 { 346 case PACK_FMT(8,8,8,8): return TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8); 347 case PACK_FMT(8,8,8,0): return TextureFormat(TextureFormat::RGB, TextureFormat::UNORM_INT8); 348 case PACK_FMT(4,4,4,4): return TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_SHORT_4444); 349 case PACK_FMT(5,5,5,1): return TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_SHORT_5551); 350 case PACK_FMT(5,6,5,0): return TextureFormat(TextureFormat::RGB, TextureFormat::UNORM_SHORT_565); 351 352 // \note Defaults to RGBA8 353 default: return TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8); 354 } 355 356#undef PACK_FMT 357} 358 359/* 360The getColorThreshold function is used to obtain a 361threshold usable for the fuzzyCompare function. 362 363For 8bit color depths a value of 0.02 should provide 364a good metric for rejecting images above this level. 365For other bit depths other thresholds should be selected. 366Ideally this function would take advantage of the 367getColorThreshold function provided by the PixelFormat class 368as this would also allow setting per channel thresholds. 369However using the PixelFormat provided function can result 370in too strict thresholds for 8bit bit depths (compared to 371the current default of 0.02) or too relaxed for lower bit 372depths if scaled proportionally to the 8bit default. 373*/ 374 375float getColorThreshold (const tcu::PixelFormat& colorBits) 376{ 377 if ((colorBits.redBits > 0 && colorBits.redBits < 8) || 378 (colorBits.greenBits > 0 && colorBits.greenBits < 8) || 379 (colorBits.blueBits > 0 && colorBits.blueBits < 8) || 380 (colorBits.alphaBits > 0 && colorBits.alphaBits < 8)) 381 { 382 return 0.05f; 383 } 384 else 385 { 386 return 0.02f; 387 } 388} 389 390tcu::TextureFormat getDepthFormat (const int depthBits) 391{ 392 switch (depthBits) 393 { 394 case 0: return tcu::TextureFormat(); 395 case 8: return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNORM_INT8); 396 case 16: return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNORM_INT16); 397 case 24: return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNORM_INT24); 398 case 32: 399 default: return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::FLOAT); 400 } 401} 402 403tcu::TextureFormat getStencilFormat (int stencilBits) 404{ 405 switch (stencilBits) 406 { 407 case 0: return tcu::TextureFormat(); 408 case 8: 409 default: return tcu::TextureFormat(tcu::TextureFormat::S, tcu::TextureFormat::UNSIGNED_INT8); 410 } 411} 412 413void renderReference (const tcu::PixelBufferAccess& dst, const vector<DrawPrimitiveOp>& drawOps, const tcu::PixelFormat& colorBits, const int depthBits, const int stencilBits, const int numSamples) 414{ 415 const int width = dst.getWidth(); 416 const int height = dst.getHeight(); 417 418 tcu::TextureLevel colorBuffer; 419 tcu::TextureLevel depthBuffer; 420 tcu::TextureLevel stencilBuffer; 421 422 rr::Renderer referenceRenderer; 423 rr::VertexAttrib attributes[2]; 424 const ReferenceShader shader; 425 426 attributes[0].type = rr::VERTEXATTRIBTYPE_FLOAT; 427 attributes[0].size = 4; 428 attributes[0].stride = 0; 429 attributes[0].instanceDivisor = 0; 430 431 attributes[1].type = rr::VERTEXATTRIBTYPE_FLOAT; 432 attributes[1].size = 4; 433 attributes[1].stride = 0; 434 attributes[1].instanceDivisor = 0; 435 436 // Initialize buffers. 437 colorBuffer.setStorage(getColorFormat(colorBits), numSamples, width, height); 438 rr::clearMultisampleColorBuffer(colorBuffer, CLEAR_COLOR, rr::WindowRectangle(0, 0, width, height)); 439 440 if (depthBits > 0) 441 { 442 depthBuffer.setStorage(getDepthFormat(depthBits), numSamples, width, height); 443 rr::clearMultisampleDepthBuffer(depthBuffer, CLEAR_DEPTH, rr::WindowRectangle(0, 0, width, height)); 444 } 445 446 if (stencilBits > 0) 447 { 448 stencilBuffer.setStorage(getStencilFormat(stencilBits), numSamples, width, height); 449 rr::clearMultisampleStencilBuffer(stencilBuffer, CLEAR_STENCIL, rr::WindowRectangle(0, 0, width, height)); 450 } 451 452 const rr::RenderTarget renderTarget(rr::MultisamplePixelBufferAccess::fromMultisampleAccess(colorBuffer.getAccess()), 453 rr::MultisamplePixelBufferAccess::fromMultisampleAccess(depthBuffer.getAccess()), 454 rr::MultisamplePixelBufferAccess::fromMultisampleAccess(stencilBuffer.getAccess())); 455 456 for (vector<DrawPrimitiveOp>::const_iterator drawOp = drawOps.begin(); drawOp != drawOps.end(); drawOp++) 457 { 458 // Translate state 459 rr::RenderState renderState((rr::ViewportState)(rr::MultisamplePixelBufferAccess::fromMultisampleAccess(colorBuffer.getAccess()))); 460 toReferenceRenderState(renderState, *drawOp); 461 462 DE_ASSERT(drawOp->type == PRIMITIVETYPE_TRIANGLE); 463 464 attributes[0].pointer = &drawOp->positions[0]; 465 attributes[1].pointer = &drawOp->colors[0]; 466 467 referenceRenderer.draw( 468 rr::DrawCommand( 469 renderState, 470 renderTarget, 471 rr::Program(static_cast<const rr::VertexShader*>(&shader), static_cast<const rr::FragmentShader*>(&shader)), 472 2, 473 attributes, 474 rr::PrimitiveList(rr::PRIMITIVETYPE_TRIANGLES, drawOp->count * 3, 0))); 475 } 476 477 rr::resolveMultisampleColorBuffer(dst, rr::MultisamplePixelBufferAccess::fromMultisampleAccess(colorBuffer.getAccess())); 478} 479 480// API rendering code 481 482class Program 483{ 484public: 485 Program (void) {} 486 virtual ~Program (void) {} 487 488 virtual void setup (void) const = DE_NULL; 489}; 490 491typedef de::SharedPtr<Program> ProgramSp; 492 493static glu::ProgramSources getProgramSourcesES2 (void) 494{ 495 static const char* s_vertexSrc = 496 "attribute highp vec4 a_position;\n" 497 "attribute mediump vec4 a_color;\n" 498 "varying mediump vec4 v_color;\n" 499 "void main (void)\n" 500 "{\n" 501 " gl_Position = a_position;\n" 502 " v_color = a_color;\n" 503 "}\n"; 504 505 static const char* s_fragmentSrc = 506 "varying mediump vec4 v_color;\n" 507 "void main (void)\n" 508 "{\n" 509 " gl_FragColor = v_color;\n" 510 "}\n"; 511 512 return glu::ProgramSources() << glu::VertexSource(s_vertexSrc) << glu::FragmentSource(s_fragmentSrc); 513} 514 515class GLES2Program : public Program 516{ 517public: 518 GLES2Program (const glw::Functions& gl) 519 : m_gl (gl) 520 , m_program (gl, getProgramSourcesES2()) 521 , m_positionLoc (0) 522 , m_colorLoc (0) 523 { 524 525 m_positionLoc = m_gl.getAttribLocation(m_program.getProgram(), "a_position"); 526 m_colorLoc = m_gl.getAttribLocation(m_program.getProgram(), "a_color"); 527 } 528 529 ~GLES2Program (void) 530 { 531 } 532 533 void setup (void) const 534 { 535 m_gl.useProgram(m_program.getProgram()); 536 m_gl.enableVertexAttribArray(m_positionLoc); 537 m_gl.enableVertexAttribArray(m_colorLoc); 538 GLU_CHECK_GLW_MSG(m_gl, "Program setup failed"); 539 } 540 541 int getPositionLoc (void) const { return m_positionLoc; } 542 int getColorLoc (void) const { return m_colorLoc; } 543 544private: 545 const glw::Functions& m_gl; 546 glu::ShaderProgram m_program; 547 int m_positionLoc; 548 int m_colorLoc; 549}; 550 551void clearGLES2 (const glw::Functions& gl, const tcu::Vec4& color, const float depth, const int stencil) 552{ 553 gl.clearColor(color.x(), color.y(), color.z(), color.w()); 554 gl.clearDepthf(depth); 555 gl.clearStencil(stencil); 556 gl.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT); 557} 558 559void drawGLES2 (const glw::Functions& gl, const Program& program, const DrawPrimitiveOp& drawOp) 560{ 561 const GLES2Program& gles2Program = dynamic_cast<const GLES2Program&>(program); 562 563 switch (drawOp.blend) 564 { 565 case BLENDMODE_NONE: 566 gl.disable(GL_BLEND); 567 break; 568 569 case BLENDMODE_ADDITIVE: 570 gl.enable(GL_BLEND); 571 gl.blendFunc(GL_ONE, GL_ONE); 572 break; 573 574 case BLENDMODE_SRC_OVER: 575 gl.enable(GL_BLEND); 576 gl.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 577 break; 578 579 default: 580 DE_ASSERT(false); 581 } 582 583 switch (drawOp.depth) 584 { 585 case DEPTHMODE_NONE: 586 gl.disable(GL_DEPTH_TEST); 587 break; 588 589 case DEPTHMODE_LESS: 590 gl.enable(GL_DEPTH_TEST); 591 break; 592 593 default: 594 DE_ASSERT(false); 595 } 596 597 switch (drawOp.stencil) 598 { 599 case STENCILMODE_NONE: 600 gl.disable(GL_STENCIL_TEST); 601 break; 602 603 case STENCILMODE_LEQUAL_INC: 604 gl.enable(GL_STENCIL_TEST); 605 gl.stencilFunc(GL_LEQUAL, drawOp.stencilRef, ~0u); 606 gl.stencilOp(GL_KEEP, GL_INCR, GL_INCR); 607 break; 608 609 default: 610 DE_ASSERT(false); 611 } 612 613 gl.disable(GL_DITHER); 614 615 gl.vertexAttribPointer(gles2Program.getPositionLoc(), 4, GL_FLOAT, GL_FALSE, 0, &drawOp.positions[0]); 616 gl.vertexAttribPointer(gles2Program.getColorLoc(), 4, GL_FLOAT, GL_FALSE, 0, &drawOp.colors[0]); 617 618 DE_ASSERT(drawOp.type == PRIMITIVETYPE_TRIANGLE); 619 gl.drawArrays(GL_TRIANGLES, 0, drawOp.count*3); 620} 621 622static void readPixelsGLES2 (const glw::Functions& gl, tcu::Surface& dst) 623{ 624 gl.readPixels(0, 0, dst.getWidth(), dst.getHeight(), GL_RGBA, GL_UNSIGNED_BYTE, dst.getAccess().getDataPtr()); 625} 626 627Program* createProgram (const glw::Functions& gl, EGLint api) 628{ 629 switch (api) 630 { 631 case EGL_OPENGL_ES2_BIT: return new GLES2Program(gl); 632 case EGL_OPENGL_ES3_BIT_KHR: return new GLES2Program(gl); 633 default: 634 throw tcu::NotSupportedError("Unsupported API"); 635 } 636} 637 638void draw (const glw::Functions& gl, EGLint api, const Program& program, const DrawPrimitiveOp& drawOp) 639{ 640 switch (api) 641 { 642 case EGL_OPENGL_ES2_BIT: drawGLES2(gl, program, drawOp); break; 643 case EGL_OPENGL_ES3_BIT_KHR: drawGLES2(gl, program, drawOp); break; 644 default: 645 throw tcu::NotSupportedError("Unsupported API"); 646 } 647} 648 649void clear (const glw::Functions& gl, EGLint api, const tcu::Vec4& color, const float depth, const int stencil) 650{ 651 switch (api) 652 { 653 case EGL_OPENGL_ES2_BIT: clearGLES2(gl, color, depth, stencil); break; 654 case EGL_OPENGL_ES3_BIT_KHR: clearGLES2(gl, color, depth, stencil); break; 655 default: 656 throw tcu::NotSupportedError("Unsupported API"); 657 } 658} 659 660static void readPixels (const glw::Functions& gl, EGLint api, tcu::Surface& dst) 661{ 662 switch (api) 663 { 664 case EGL_OPENGL_ES2_BIT: readPixelsGLES2(gl, dst); break; 665 case EGL_OPENGL_ES3_BIT_KHR: readPixelsGLES2(gl, dst); break; 666 default: 667 throw tcu::NotSupportedError("Unsupported API"); 668 } 669} 670 671static void finish (const glw::Functions& gl, EGLint api) 672{ 673 switch (api) 674 { 675 case EGL_OPENGL_ES2_BIT: 676 case EGL_OPENGL_ES3_BIT_KHR: 677 gl.finish(); 678 break; 679 680 default: 681 throw tcu::NotSupportedError("Unsupported API"); 682 } 683} 684 685tcu::PixelFormat getPixelFormat (const Library& egl, EGLDisplay display, EGLConfig config) 686{ 687 tcu::PixelFormat fmt; 688 fmt.redBits = eglu::getConfigAttribInt(egl, display, config, EGL_RED_SIZE); 689 fmt.greenBits = eglu::getConfigAttribInt(egl, display, config, EGL_GREEN_SIZE); 690 fmt.blueBits = eglu::getConfigAttribInt(egl, display, config, EGL_BLUE_SIZE); 691 fmt.alphaBits = eglu::getConfigAttribInt(egl, display, config, EGL_ALPHA_SIZE); 692 return fmt; 693} 694 695} // anonymous 696 697// SingleThreadRenderCase 698 699class SingleThreadRenderCase : public MultiContextRenderCase 700{ 701public: 702 SingleThreadRenderCase (EglTestContext& eglTestCtx, const char* name, const char* description, EGLint api, EGLint surfaceType, const eglu::FilterList& filters, int numContextsPerApi); 703 704 void init (void); 705 706private: 707 virtual void executeForContexts (EGLDisplay display, EGLSurface surface, const Config& config, const std::vector<std::pair<EGLint, EGLContext> >& contexts); 708 709 glw::Functions m_gl; 710}; 711 712// SingleThreadColorClearCase 713 714SingleThreadRenderCase::SingleThreadRenderCase (EglTestContext& eglTestCtx, const char* name, const char* description, EGLint api, EGLint surfaceType, const eglu::FilterList& filters, int numContextsPerApi) 715 : MultiContextRenderCase(eglTestCtx, name, description, api, surfaceType, filters, numContextsPerApi) 716{ 717} 718 719void SingleThreadRenderCase::init (void) 720{ 721 MultiContextRenderCase::init(); 722 m_eglTestCtx.initGLFunctions(&m_gl, glu::ApiType::es(2,0)); 723} 724 725void SingleThreadRenderCase::executeForContexts (EGLDisplay display, EGLSurface surface, const Config& config, const std::vector<std::pair<EGLint, EGLContext> >& contexts) 726{ 727 const Library& egl = m_eglTestCtx.getLibrary(); 728 const int width = eglu::querySurfaceInt(egl, display, surface, EGL_WIDTH); 729 const int height = eglu::querySurfaceInt(egl, display, surface, EGL_HEIGHT); 730 const int numContexts = (int)contexts.size(); 731 const int drawsPerCtx = 2; 732 const int numIters = 2; 733 const tcu::PixelFormat pixelFmt = getPixelFormat(egl, display, config.config); 734 const float threshold = getColorThreshold(pixelFmt); 735 736 const int depthBits = eglu::getConfigAttribInt(egl, display, config.config, EGL_DEPTH_SIZE); 737 const int stencilBits = eglu::getConfigAttribInt(egl, display, config.config, EGL_STENCIL_SIZE); 738 const int numSamples = eglu::getConfigAttribInt(egl, display, config.config, EGL_SAMPLES); 739 740 TestLog& log = m_testCtx.getLog(); 741 742 tcu::Surface refFrame (width, height); 743 tcu::Surface frame (width, height); 744 745 de::Random rnd (deStringHash(getName()) ^ deInt32Hash(numContexts)); 746 vector<ProgramSp> programs (contexts.size()); 747 vector<DrawPrimitiveOp> drawOps; 748 749 // Log basic information about config. 750 log << TestLog::Message << "EGL_RED_SIZE = " << pixelFmt.redBits << TestLog::EndMessage; 751 log << TestLog::Message << "EGL_GREEN_SIZE = " << pixelFmt.greenBits << TestLog::EndMessage; 752 log << TestLog::Message << "EGL_BLUE_SIZE = " << pixelFmt.blueBits << TestLog::EndMessage; 753 log << TestLog::Message << "EGL_ALPHA_SIZE = " << pixelFmt.alphaBits << TestLog::EndMessage; 754 log << TestLog::Message << "EGL_DEPTH_SIZE = " << depthBits << TestLog::EndMessage; 755 log << TestLog::Message << "EGL_STENCIL_SIZE = " << stencilBits << TestLog::EndMessage; 756 log << TestLog::Message << "EGL_SAMPLES = " << numSamples << TestLog::EndMessage; 757 758 // Generate draw ops. 759 drawOps.resize(numContexts*drawsPerCtx*numIters); 760 for (vector<DrawPrimitiveOp>::iterator drawOp = drawOps.begin(); drawOp != drawOps.end(); ++drawOp) 761 randomizeDrawOp(rnd, *drawOp); 762 763 // Create and setup programs per context 764 for (int ctxNdx = 0; ctxNdx < numContexts; ctxNdx++) 765 { 766 EGLint api = contexts[ctxNdx].first; 767 EGLContext context = contexts[ctxNdx].second; 768 769 EGLU_CHECK_CALL(egl, makeCurrent(display, surface, surface, context)); 770 771 programs[ctxNdx] = ProgramSp(createProgram(m_gl, api)); 772 programs[ctxNdx]->setup(); 773 } 774 775 // Clear to black using first context. 776 { 777 EGLint api = contexts[0].first; 778 EGLContext context = contexts[0].second; 779 780 EGLU_CHECK_CALL(egl, makeCurrent(display, surface, surface, context)); 781 782 clear(m_gl, api, CLEAR_COLOR, CLEAR_DEPTH, CLEAR_STENCIL); 783 finish(m_gl, api); 784 } 785 786 // Render. 787 for (int iterNdx = 0; iterNdx < numIters; iterNdx++) 788 { 789 for (int ctxNdx = 0; ctxNdx < numContexts; ctxNdx++) 790 { 791 EGLint api = contexts[ctxNdx].first; 792 EGLContext context = contexts[ctxNdx].second; 793 794 EGLU_CHECK_CALL(egl, makeCurrent(display, surface, surface, context)); 795 796 for (int drawNdx = 0; drawNdx < drawsPerCtx; drawNdx++) 797 { 798 const DrawPrimitiveOp& drawOp = drawOps[iterNdx*numContexts*drawsPerCtx + ctxNdx*drawsPerCtx + drawNdx]; 799 draw(m_gl, api, *programs[ctxNdx], drawOp); 800 } 801 802 finish(m_gl, api); 803 } 804 } 805 806 // Read pixels using first context. \todo [pyry] Randomize? 807 { 808 EGLint api = contexts[0].first; 809 EGLContext context = contexts[0].second; 810 811 EGLU_CHECK_CALL(egl, makeCurrent(display, surface, surface, context)); 812 813 readPixels(m_gl, api, frame); 814 } 815 816 EGLU_CHECK_CALL(egl, makeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); 817 818 // Render reference. 819 // \note Reference image is always generated using single-sampling. 820 renderReference(refFrame.getAccess(), drawOps, pixelFmt, depthBits, stencilBits, 1); 821 822 // Compare images 823 { 824 bool imagesOk = tcu::fuzzyCompare(log, "ComparisonResult", "Image comparison result", refFrame, frame, threshold, tcu::COMPARE_LOG_RESULT); 825 826 if (!imagesOk) 827 m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed"); 828 } 829} 830 831// MultiThreadRenderCase 832 833class MultiThreadRenderCase : public MultiContextRenderCase 834{ 835public: 836 MultiThreadRenderCase (EglTestContext& eglTestCtx, const char* name, const char* description, EGLint api, EGLint surfaceType, const eglu::FilterList& filters, int numContextsPerApi); 837 838 void init (void); 839 840private: 841 virtual void executeForContexts (EGLDisplay display, EGLSurface surface, const Config& config, const std::vector<std::pair<EGLint, EGLContext> >& contexts); 842 843 glw::Functions m_gl; 844}; 845 846class RenderTestThread; 847 848typedef de::SharedPtr<RenderTestThread> RenderTestThreadSp; 849typedef de::SharedPtr<de::Semaphore> SemaphoreSp; 850 851struct DrawOpPacket 852{ 853 DrawOpPacket (void) 854 : drawOps (DE_NULL) 855 , numOps (0) 856 { 857 } 858 859 const DrawPrimitiveOp* drawOps; 860 int numOps; 861 SemaphoreSp wait; 862 SemaphoreSp signal; 863}; 864 865class RenderTestThread : public de::Thread 866{ 867public: 868 RenderTestThread (const Library& egl, EGLDisplay display, EGLSurface surface, EGLContext context, EGLint api, const glw::Functions& gl, const Program& program, const std::vector<DrawOpPacket>& packets) 869 : m_egl (egl) 870 , m_display (display) 871 , m_surface (surface) 872 , m_context (context) 873 , m_api (api) 874 , m_gl (gl) 875 , m_program (program) 876 , m_packets (packets) 877 { 878 } 879 880 void run (void) 881 { 882 for (std::vector<DrawOpPacket>::const_iterator packetIter = m_packets.begin(); packetIter != m_packets.end(); packetIter++) 883 { 884 // Wait until it is our turn. 885 packetIter->wait->decrement(); 886 887 // Acquire context. 888 EGLU_CHECK_CALL(m_egl, makeCurrent(m_display, m_surface, m_surface, m_context)); 889 890 // Execute rendering. 891 for (int ndx = 0; ndx < packetIter->numOps; ndx++) 892 draw(m_gl, m_api, m_program, packetIter->drawOps[ndx]); 893 894 finish(m_gl, m_api); 895 896 // Release context. 897 EGLU_CHECK_CALL(m_egl, makeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); 898 899 // Signal completion. 900 packetIter->signal->increment(); 901 } 902 m_egl.releaseThread(); 903 } 904 905private: 906 const Library& m_egl; 907 EGLDisplay m_display; 908 EGLSurface m_surface; 909 EGLContext m_context; 910 EGLint m_api; 911 const glw::Functions& m_gl; 912 const Program& m_program; 913 const std::vector<DrawOpPacket>& m_packets; 914}; 915 916MultiThreadRenderCase::MultiThreadRenderCase (EglTestContext& eglTestCtx, const char* name, const char* description, EGLint api, EGLint surfaceType, const eglu::FilterList& filters, int numContextsPerApi) 917 : MultiContextRenderCase(eglTestCtx, name, description, api, surfaceType, filters, numContextsPerApi) 918{ 919} 920 921void MultiThreadRenderCase::init (void) 922{ 923 MultiContextRenderCase::init(); 924 m_eglTestCtx.initGLFunctions(&m_gl, glu::ApiType::es(2,0)); 925} 926 927void MultiThreadRenderCase::executeForContexts (EGLDisplay display, EGLSurface surface, const Config& config, const std::vector<std::pair<EGLint, EGLContext> >& contexts) 928{ 929 const Library& egl = m_eglTestCtx.getLibrary(); 930 const int width = eglu::querySurfaceInt(egl, display, surface, EGL_WIDTH); 931 const int height = eglu::querySurfaceInt(egl, display, surface, EGL_HEIGHT); 932 const int numContexts = (int)contexts.size(); 933 const int opsPerPacket = 2; 934 const int packetsPerThread = 2; 935 const int numThreads = numContexts; 936 const int numPackets = numThreads * packetsPerThread; 937 const tcu::PixelFormat pixelFmt = getPixelFormat(egl, display, config.config); 938 const float threshold = getColorThreshold(pixelFmt); 939 940 const int depthBits = eglu::getConfigAttribInt(egl, display, config.config, EGL_DEPTH_SIZE); 941 const int stencilBits = eglu::getConfigAttribInt(egl, display, config.config, EGL_STENCIL_SIZE); 942 const int numSamples = eglu::getConfigAttribInt(egl, display, config.config, EGL_SAMPLES); 943 944 TestLog& log = m_testCtx.getLog(); 945 946 tcu::Surface refFrame (width, height); 947 tcu::Surface frame (width, height); 948 949 de::Random rnd (deStringHash(getName()) ^ deInt32Hash(numContexts)); 950 951 // Resources that need cleanup 952 vector<ProgramSp> programs (numContexts); 953 vector<SemaphoreSp> semaphores (numPackets+1); 954 vector<DrawPrimitiveOp> drawOps (numPackets*opsPerPacket); 955 vector<vector<DrawOpPacket> > packets (numThreads); 956 vector<RenderTestThreadSp> threads (numThreads); 957 958 // Log basic information about config. 959 log << TestLog::Message << "EGL_RED_SIZE = " << pixelFmt.redBits << TestLog::EndMessage; 960 log << TestLog::Message << "EGL_GREEN_SIZE = " << pixelFmt.greenBits << TestLog::EndMessage; 961 log << TestLog::Message << "EGL_BLUE_SIZE = " << pixelFmt.blueBits << TestLog::EndMessage; 962 log << TestLog::Message << "EGL_ALPHA_SIZE = " << pixelFmt.alphaBits << TestLog::EndMessage; 963 log << TestLog::Message << "EGL_DEPTH_SIZE = " << depthBits << TestLog::EndMessage; 964 log << TestLog::Message << "EGL_STENCIL_SIZE = " << stencilBits << TestLog::EndMessage; 965 log << TestLog::Message << "EGL_SAMPLES = " << numSamples << TestLog::EndMessage; 966 967 // Initialize semaphores. 968 for (vector<SemaphoreSp>::iterator sem = semaphores.begin(); sem != semaphores.end(); ++sem) 969 *sem = SemaphoreSp(new de::Semaphore(0)); 970 971 // Create draw ops. 972 for (vector<DrawPrimitiveOp>::iterator drawOp = drawOps.begin(); drawOp != drawOps.end(); ++drawOp) 973 randomizeDrawOp(rnd, *drawOp); 974 975 // Create packets. 976 for (int threadNdx = 0; threadNdx < numThreads; threadNdx++) 977 { 978 packets[threadNdx].resize(packetsPerThread); 979 980 for (int packetNdx = 0; packetNdx < packetsPerThread; packetNdx++) 981 { 982 DrawOpPacket& packet = packets[threadNdx][packetNdx]; 983 984 // Threads take turns with packets. 985 packet.wait = semaphores[packetNdx*numThreads + threadNdx]; 986 packet.signal = semaphores[packetNdx*numThreads + threadNdx + 1]; 987 packet.numOps = opsPerPacket; 988 packet.drawOps = &drawOps[(packetNdx*numThreads + threadNdx)*opsPerPacket]; 989 } 990 } 991 992 // Create and setup programs per context 993 for (int ctxNdx = 0; ctxNdx < numContexts; ctxNdx++) 994 { 995 EGLint api = contexts[ctxNdx].first; 996 EGLContext context = contexts[ctxNdx].second; 997 998 EGLU_CHECK_CALL(egl, makeCurrent(display, surface, surface, context)); 999 1000 programs[ctxNdx] = ProgramSp(createProgram(m_gl, api)); 1001 programs[ctxNdx]->setup(); 1002 1003 // Release context 1004 EGLU_CHECK_CALL(egl, makeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); 1005 } 1006 1007 // Clear to black using first context. 1008 { 1009 EGLint api = contexts[0].first; 1010 EGLContext context = contexts[0].second; 1011 1012 EGLU_CHECK_CALL(egl, makeCurrent(display, surface, surface, context)); 1013 1014 clear(m_gl, api, CLEAR_COLOR, CLEAR_DEPTH, CLEAR_STENCIL); 1015 finish(m_gl, api); 1016 1017 // Release context 1018 EGLU_CHECK_CALL(egl, makeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); 1019 } 1020 1021 // Create and launch threads (actual rendering starts once first semaphore is signaled). 1022 for (int threadNdx = 0; threadNdx < numThreads; threadNdx++) 1023 { 1024 threads[threadNdx] = RenderTestThreadSp(new RenderTestThread(egl, display, surface, contexts[threadNdx].second, contexts[threadNdx].first, m_gl, *programs[threadNdx], packets[threadNdx])); 1025 threads[threadNdx]->start(); 1026 } 1027 1028 // Signal start and wait until complete. 1029 semaphores.front()->increment(); 1030 semaphores.back()->decrement(); 1031 1032 // Read pixels using first context. \todo [pyry] Randomize? 1033 { 1034 EGLint api = contexts[0].first; 1035 EGLContext context = contexts[0].second; 1036 1037 EGLU_CHECK_CALL(egl, makeCurrent(display, surface, surface, context)); 1038 1039 readPixels(m_gl, api, frame); 1040 } 1041 1042 EGLU_CHECK_CALL(egl, makeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); 1043 1044 // Join threads. 1045 for (int threadNdx = 0; threadNdx < numThreads; threadNdx++) 1046 threads[threadNdx]->join(); 1047 1048 // Render reference. 1049 renderReference(refFrame.getAccess(), drawOps, pixelFmt, depthBits, stencilBits, 1); 1050 1051 // Compare images 1052 { 1053 bool imagesOk = tcu::fuzzyCompare(log, "ComparisonResult", "Image comparison result", refFrame, frame, threshold, tcu::COMPARE_LOG_RESULT); 1054 1055 if (!imagesOk) 1056 m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed"); 1057 } 1058} 1059 1060RenderTests::RenderTests (EglTestContext& eglTestCtx) 1061 : TestCaseGroup(eglTestCtx, "render", "Basic rendering with different client APIs") 1062{ 1063} 1064 1065RenderTests::~RenderTests (void) 1066{ 1067} 1068 1069struct RenderGroupSpec 1070{ 1071 const char* name; 1072 const char* desc; 1073 EGLint apiBits; 1074 eglu::ConfigFilter baseFilter; 1075 int numContextsPerApi; 1076}; 1077 1078template <deUint32 Bits> 1079static bool renderable (const eglu::CandidateConfig& c) 1080{ 1081 return (c.renderableType() & Bits) == Bits; 1082} 1083 1084template <class RenderClass> 1085static void createRenderGroups (EglTestContext& eglTestCtx, tcu::TestCaseGroup* group, const RenderGroupSpec* first, const RenderGroupSpec* last) 1086{ 1087 for (const RenderGroupSpec* groupIter = first; groupIter != last; groupIter++) 1088 { 1089 tcu::TestCaseGroup* configGroup = new tcu::TestCaseGroup(eglTestCtx.getTestContext(), groupIter->name, groupIter->desc); 1090 group->addChild(configGroup); 1091 1092 vector<RenderFilterList> filterLists; 1093 eglu::FilterList baseFilters; 1094 baseFilters << groupIter->baseFilter; 1095 getDefaultRenderFilterLists(filterLists, baseFilters); 1096 1097 for (vector<RenderFilterList>::const_iterator listIter = filterLists.begin(); listIter != filterLists.end(); listIter++) 1098 configGroup->addChild(new RenderClass(eglTestCtx, listIter->getName(), "", groupIter->apiBits, listIter->getSurfaceTypeMask(), *listIter, groupIter->numContextsPerApi)); 1099 } 1100} 1101 1102void RenderTests::init (void) 1103{ 1104 static const RenderGroupSpec singleContextCases[] = 1105 { 1106 { 1107 "gles2", 1108 "Primitive rendering using GLES2", 1109 EGL_OPENGL_ES2_BIT, 1110 renderable<EGL_OPENGL_ES2_BIT>, 1111 1 1112 }, 1113 { 1114 "gles3", 1115 "Primitive rendering using GLES3", 1116 EGL_OPENGL_ES3_BIT, 1117 renderable<EGL_OPENGL_ES3_BIT>, 1118 1 1119 }, 1120 }; 1121 1122 static const RenderGroupSpec multiContextCases[] = 1123 { 1124 { 1125 "gles2", 1126 "Primitive rendering using multiple GLES2 contexts to shared surface", 1127 EGL_OPENGL_ES2_BIT, 1128 renderable<EGL_OPENGL_ES2_BIT>, 1129 3 1130 }, 1131 { 1132 "gles3", 1133 "Primitive rendering using multiple GLES3 contexts to shared surface", 1134 EGL_OPENGL_ES3_BIT, 1135 renderable<EGL_OPENGL_ES3_BIT>, 1136 3 1137 }, 1138 { 1139 "gles2_gles3", 1140 "Primitive rendering using multiple APIs to shared surface", 1141 EGL_OPENGL_ES2_BIT|EGL_OPENGL_ES3_BIT, 1142 renderable<EGL_OPENGL_ES2_BIT|EGL_OPENGL_ES3_BIT>, 1143 1 1144 }, 1145 }; 1146 1147 tcu::TestCaseGroup* singleContextGroup = new tcu::TestCaseGroup(m_testCtx, "single_context", "Single-context rendering"); 1148 addChild(singleContextGroup); 1149 createRenderGroups<SingleThreadRenderCase>(m_eglTestCtx, singleContextGroup, &singleContextCases[0], &singleContextCases[DE_LENGTH_OF_ARRAY(singleContextCases)]); 1150 1151 tcu::TestCaseGroup* multiContextGroup = new tcu::TestCaseGroup(m_testCtx, "multi_context", "Multi-context rendering with shared surface"); 1152 addChild(multiContextGroup); 1153 createRenderGroups<SingleThreadRenderCase>(m_eglTestCtx, multiContextGroup, &multiContextCases[0], &multiContextCases[DE_LENGTH_OF_ARRAY(multiContextCases)]); 1154 1155 tcu::TestCaseGroup* multiThreadGroup = new tcu::TestCaseGroup(m_testCtx, "multi_thread", "Multi-thread rendering with shared surface"); 1156 addChild(multiThreadGroup); 1157 createRenderGroups<MultiThreadRenderCase>(m_eglTestCtx, multiThreadGroup, &multiContextCases[0], &multiContextCases[DE_LENGTH_OF_ARRAY(multiContextCases)]); 1158} 1159 1160} // egl 1161} // deqp 1162