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