xref: /aosp_15_r20/external/deqp/modules/gles31/functional/es31fShaderHelperInvocationTests.cpp (revision 35238bce31c2a825756842865a792f8cf7f89930)
1 /*-------------------------------------------------------------------------
2  * drawElements Quality Program OpenGL ES 3.1 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 gl_HelperInvocation tests.
22  *//*--------------------------------------------------------------------*/
23 
24 #include "es31fShaderHelperInvocationTests.hpp"
25 
26 #include "gluObjectWrapper.hpp"
27 #include "gluShaderProgram.hpp"
28 #include "gluDrawUtil.hpp"
29 #include "gluPixelTransfer.hpp"
30 
31 #include "glwFunctions.hpp"
32 #include "glwEnums.hpp"
33 
34 #include "tcuTestLog.hpp"
35 #include "tcuVector.hpp"
36 #include "tcuSurface.hpp"
37 
38 #include "deUniquePtr.hpp"
39 #include "deStringUtil.hpp"
40 #include "deRandom.hpp"
41 #include "deString.h"
42 
43 namespace deqp
44 {
45 namespace gles31
46 {
47 namespace Functional
48 {
49 namespace
50 {
51 
52 using de::MovePtr;
53 using glu::ShaderProgram;
54 using std::string;
55 using std::vector;
56 using tcu::IVec2;
57 using tcu::TestLog;
58 using tcu::Vec2;
59 
60 enum PrimitiveType
61 {
62     PRIMITIVETYPE_TRIANGLE = 0,
63     PRIMITIVETYPE_LINE,
64     PRIMITIVETYPE_WIDE_LINE,
65     PRIMITIVETYPE_POINT,
66     PRIMITIVETYPE_WIDE_POINT,
67 
68     PRIMITIVETYPE_LAST
69 };
70 
getNumVerticesPerPrimitive(PrimitiveType primType)71 static int getNumVerticesPerPrimitive(PrimitiveType primType)
72 {
73     switch (primType)
74     {
75     case PRIMITIVETYPE_TRIANGLE:
76         return 3;
77     case PRIMITIVETYPE_LINE:
78         return 2;
79     case PRIMITIVETYPE_WIDE_LINE:
80         return 2;
81     case PRIMITIVETYPE_POINT:
82         return 1;
83     case PRIMITIVETYPE_WIDE_POINT:
84         return 1;
85     default:
86         DE_ASSERT(false);
87         return 0;
88     }
89 }
90 
getGluPrimitiveType(PrimitiveType primType)91 static glu::PrimitiveType getGluPrimitiveType(PrimitiveType primType)
92 {
93     switch (primType)
94     {
95     case PRIMITIVETYPE_TRIANGLE:
96         return glu::PRIMITIVETYPE_TRIANGLES;
97     case PRIMITIVETYPE_LINE:
98         return glu::PRIMITIVETYPE_LINES;
99     case PRIMITIVETYPE_WIDE_LINE:
100         return glu::PRIMITIVETYPE_LINES;
101     case PRIMITIVETYPE_POINT:
102         return glu::PRIMITIVETYPE_POINTS;
103     case PRIMITIVETYPE_WIDE_POINT:
104         return glu::PRIMITIVETYPE_POINTS;
105     default:
106         DE_ASSERT(false);
107         return glu::PRIMITIVETYPE_LAST;
108     }
109 }
110 
genVertices(PrimitiveType primType,int numPrimitives,de::Random * rnd,vector<Vec2> * dst)111 static void genVertices(PrimitiveType primType, int numPrimitives, de::Random *rnd, vector<Vec2> *dst)
112 {
113     const bool isTri                  = primType == PRIMITIVETYPE_TRIANGLE;
114     const float minCoord              = isTri ? -1.5f : -1.0f;
115     const float maxCoord              = isTri ? +1.5f : +1.0f;
116     const int numVerticesPerPrimitive = getNumVerticesPerPrimitive(primType);
117     const int numVert                 = numVerticesPerPrimitive * numPrimitives;
118 
119     dst->resize(numVert);
120 
121     for (size_t ndx = 0; ndx < dst->size(); ndx++)
122     {
123         (*dst)[ndx][0] = rnd->getFloat(minCoord, maxCoord);
124         (*dst)[ndx][1] = rnd->getFloat(minCoord, maxCoord);
125     }
126 
127     // Don't produce completely or almost completely discardable primitives.
128     // \note: This doesn't guarantee that resulting primitives are visible or
129     //        produce any fragments. This just removes trivially discardable
130     //        primitives.
131     for (int primitiveNdx = 0; primitiveNdx < numPrimitives; ++primitiveNdx)
132         for (int component = 0; component < 2; ++component)
133         {
134             bool negativeClip = true;
135             bool positiveClip = true;
136 
137             for (int vertexNdx = 0; vertexNdx < numVerticesPerPrimitive; ++vertexNdx)
138             {
139                 const float p = (*dst)[primitiveNdx * numVerticesPerPrimitive + vertexNdx][component];
140                 // \note 0.9 instead of 1.0 to avoid just barely visible primitives
141                 if (p > -0.9f)
142                     negativeClip = false;
143                 if (p < +0.9f)
144                     positiveClip = false;
145             }
146 
147             // if discardable, just mirror first vertex along center
148             if (negativeClip || positiveClip)
149             {
150                 (*dst)[primitiveNdx * numVerticesPerPrimitive + 0][0] *= -1.0f;
151                 (*dst)[primitiveNdx * numVerticesPerPrimitive + 0][1] *= -1.0f;
152             }
153         }
154 }
155 
getInteger(const glw::Functions & gl,uint32_t pname)156 static int getInteger(const glw::Functions &gl, uint32_t pname)
157 {
158     int v = 0;
159     gl.getIntegerv(pname, &v);
160     GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv()");
161     return v;
162 }
163 
getRange(const glw::Functions & gl,uint32_t pname)164 static Vec2 getRange(const glw::Functions &gl, uint32_t pname)
165 {
166     Vec2 v(0.0f);
167     gl.getFloatv(pname, v.getPtr());
168     GLU_EXPECT_NO_ERROR(gl.getError(), "glGetFloatv()");
169     return v;
170 }
171 
drawRandomPrimitives(const glu::RenderContext & renderCtx,uint32_t program,PrimitiveType primType,int numPrimitives,de::Random * rnd)172 static void drawRandomPrimitives(const glu::RenderContext &renderCtx, uint32_t program, PrimitiveType primType,
173                                  int numPrimitives, de::Random *rnd)
174 {
175     const glw::Functions &gl = renderCtx.getFunctions();
176     const float minPointSize = 16.0f;
177     const float maxPointSize = 32.0f;
178     const float minLineWidth = 16.0f;
179     const float maxLineWidth = 32.0f;
180     vector<Vec2> vertices;
181     vector<glu::VertexArrayBinding> vertexArrays;
182 
183     genVertices(primType, numPrimitives, rnd, &vertices);
184 
185     vertexArrays.push_back(glu::va::Float("a_position", 2, (int)vertices.size(), 0, (const float *)&vertices[0]));
186 
187     gl.useProgram(program);
188 
189     // Special state for certain primitives
190     if (primType == PRIMITIVETYPE_POINT || primType == PRIMITIVETYPE_WIDE_POINT)
191     {
192         const Vec2 range       = getRange(gl, glu::isContextTypeES(renderCtx.getType()) ? GL_ALIASED_POINT_SIZE_RANGE :
193                                                                                           GL_SMOOTH_POINT_SIZE_RANGE);
194         const bool isWidePoint = primType == PRIMITIVETYPE_WIDE_POINT;
195         const float pointSize  = isWidePoint ? de::min(rnd->getFloat(minPointSize, maxPointSize), range.y()) : 1.0f;
196         const int pointSizeLoc = gl.getUniformLocation(program, "u_pointSize");
197 
198         gl.uniform1f(pointSizeLoc, pointSize);
199     }
200     else if (primType == PRIMITIVETYPE_WIDE_LINE)
201     {
202         const Vec2 range      = getRange(gl, GL_ALIASED_LINE_WIDTH_RANGE);
203         const float lineWidth = de::min(rnd->getFloat(minLineWidth, maxLineWidth), range.y());
204 
205         gl.lineWidth(lineWidth);
206     }
207 
208     glu::draw(renderCtx, program, (int)vertexArrays.size(), &vertexArrays[0],
209               glu::PrimitiveList(getGluPrimitiveType(primType), (int)vertices.size()));
210 }
211 
212 class FboHelper
213 {
214 public:
215     FboHelper(const glu::RenderContext &renderCtx, int width, int height, uint32_t format, int numSamples);
216     ~FboHelper(void);
217 
218     void bindForRendering(void);
219     void readPixels(int x, int y, const tcu::PixelBufferAccess &dst);
220 
221 private:
222     const glu::RenderContext &m_renderCtx;
223     const int m_numSamples;
224     const IVec2 m_size;
225 
226     glu::Renderbuffer m_colorbuffer;
227     glu::Framebuffer m_framebuffer;
228     glu::Renderbuffer m_resolveColorbuffer;
229     glu::Framebuffer m_resolveFramebuffer;
230 };
231 
FboHelper(const glu::RenderContext & renderCtx,int width,int height,uint32_t format,int numSamples)232 FboHelper::FboHelper(const glu::RenderContext &renderCtx, int width, int height, uint32_t format, int numSamples)
233     : m_renderCtx(renderCtx)
234     , m_numSamples(numSamples)
235     , m_size(width, height)
236     , m_colorbuffer(renderCtx)
237     , m_framebuffer(renderCtx)
238     , m_resolveColorbuffer(renderCtx)
239     , m_resolveFramebuffer(renderCtx)
240 {
241     const glw::Functions &gl = m_renderCtx.getFunctions();
242     const int maxSamples     = getInteger(gl, GL_MAX_SAMPLES);
243 
244     gl.bindRenderbuffer(GL_RENDERBUFFER, *m_colorbuffer);
245     gl.renderbufferStorageMultisample(GL_RENDERBUFFER, m_numSamples, format, width, height);
246     gl.bindFramebuffer(GL_FRAMEBUFFER, *m_framebuffer);
247     gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *m_colorbuffer);
248 
249     if (m_numSamples > maxSamples && gl.checkFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
250         throw tcu::NotSupportedError("Sample count exceeds GL_MAX_SAMPLES");
251 
252     TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
253 
254     if (m_numSamples != 0)
255     {
256         gl.bindRenderbuffer(GL_RENDERBUFFER, *m_resolveColorbuffer);
257         gl.renderbufferStorage(GL_RENDERBUFFER, format, width, height);
258         gl.bindFramebuffer(GL_FRAMEBUFFER, *m_resolveFramebuffer);
259         gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *m_resolveColorbuffer);
260         TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
261     }
262 
263     GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to create framebuffer");
264 }
265 
~FboHelper(void)266 FboHelper::~FboHelper(void)
267 {
268 }
269 
bindForRendering(void)270 void FboHelper::bindForRendering(void)
271 {
272     const glw::Functions &gl = m_renderCtx.getFunctions();
273     gl.bindFramebuffer(GL_FRAMEBUFFER, *m_framebuffer);
274     GLU_EXPECT_NO_ERROR(gl.getError(), "glBindFramebuffer()");
275     gl.viewport(0, 0, m_size.x(), m_size.y());
276     GLU_EXPECT_NO_ERROR(gl.getError(), "viewport()");
277 }
278 
readPixels(int x,int y,const tcu::PixelBufferAccess & dst)279 void FboHelper::readPixels(int x, int y, const tcu::PixelBufferAccess &dst)
280 {
281     const glw::Functions &gl = m_renderCtx.getFunctions();
282     const int width          = dst.getWidth();
283     const int height         = dst.getHeight();
284 
285     if (m_numSamples != 0)
286     {
287         gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, *m_resolveFramebuffer);
288         gl.blitFramebuffer(x, y, width, height, x, y, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
289         gl.bindFramebuffer(GL_READ_FRAMEBUFFER, *m_resolveFramebuffer);
290     }
291 
292     glu::readPixels(m_renderCtx, x, y, dst);
293 }
294 
295 enum
296 {
297     FRAMEBUFFER_WIDTH  = 256,
298     FRAMEBUFFER_HEIGHT = 256,
299     FRAMEBUFFER_FORMAT = GL_RGBA8,
300     NUM_SAMPLES_MAX    = -1
301 };
302 
303 //! Verifies that gl_HelperInvocation is false in all rendered pixels.
304 class HelperInvocationValueCase : public TestCase
305 {
306 public:
307     HelperInvocationValueCase(Context &context, const char *name, const char *description, PrimitiveType primType,
308                               int numSamples);
309     ~HelperInvocationValueCase(void);
310 
311     void init(void);
312     void deinit(void);
313     IterateResult iterate(void);
314 
315 private:
316     const PrimitiveType m_primitiveType;
317     const int m_numSamples;
318 
319     const int m_numIters;
320     const int m_numPrimitivesPerIter;
321 
322     MovePtr<ShaderProgram> m_program;
323     MovePtr<FboHelper> m_fbo;
324     int m_iterNdx;
325 };
326 
HelperInvocationValueCase(Context & context,const char * name,const char * description,PrimitiveType primType,int numSamples)327 HelperInvocationValueCase::HelperInvocationValueCase(Context &context, const char *name, const char *description,
328                                                      PrimitiveType primType, int numSamples)
329     : TestCase(context, name, description)
330     , m_primitiveType(primType)
331     , m_numSamples(numSamples)
332     , m_numIters(5)
333     , m_numPrimitivesPerIter(10)
334     , m_iterNdx(0)
335 {
336 }
337 
~HelperInvocationValueCase(void)338 HelperInvocationValueCase::~HelperInvocationValueCase(void)
339 {
340     deinit();
341 }
342 
init(void)343 void HelperInvocationValueCase::init(void)
344 {
345     const glu::RenderContext &renderCtx = m_context.getRenderContext();
346     const glw::Functions &gl            = renderCtx.getFunctions();
347     const int maxSamples                = getInteger(gl, GL_MAX_SAMPLES);
348     const int actualSamples             = m_numSamples == NUM_SAMPLES_MAX ? maxSamples : m_numSamples;
349 
350     m_program = MovePtr<ShaderProgram>(
351         new ShaderProgram(m_context.getRenderContext(),
352                           glu::ProgramSources() << glu::VertexSource("#version 310 es\n"
353                                                                      "in highp vec2 a_position;\n"
354                                                                      "uniform highp float u_pointSize;\n"
355                                                                      "void main (void)\n"
356                                                                      "{\n"
357                                                                      "    gl_Position = vec4(a_position, 0.0, 1.0);\n"
358                                                                      "    gl_PointSize = u_pointSize;\n"
359                                                                      "}\n")
360                                                 << glu::FragmentSource("#version 310 es\n"
361                                                                        "out mediump vec4 o_color;\n"
362                                                                        "void main (void)\n"
363                                                                        "{\n"
364                                                                        "    if (gl_HelperInvocation)\n"
365                                                                        "        o_color = vec4(1.0, 0.0, 0.0, 1.0);\n"
366                                                                        "    else\n"
367                                                                        "        o_color = vec4(0.0, 1.0, 0.0, 1.0);\n"
368                                                                        "}\n")));
369 
370     m_testCtx.getLog() << *m_program;
371 
372     if (!m_program->isOk())
373     {
374         m_program.clear();
375         TCU_FAIL("Compile failed");
376     }
377 
378     m_testCtx.getLog() << TestLog::Message << "Using GL_RGBA8 framebuffer with " << actualSamples << " samples"
379                        << TestLog::EndMessage;
380 
381     m_fbo = MovePtr<FboHelper>(
382         new FboHelper(renderCtx, FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT, FRAMEBUFFER_FORMAT, actualSamples));
383 
384     m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
385 }
386 
deinit(void)387 void HelperInvocationValueCase::deinit(void)
388 {
389     m_program.clear();
390     m_fbo.clear();
391 }
392 
verifyHelperInvocationValue(TestLog & log,const tcu::Surface & result,bool isMultiSample)393 static bool verifyHelperInvocationValue(TestLog &log, const tcu::Surface &result, bool isMultiSample)
394 {
395     const tcu::RGBA bgRef(0, 0, 0, 255);
396     const tcu::RGBA fgRef(0, 255, 0, 255);
397     const tcu::RGBA threshold(1, isMultiSample ? 254 : 1, 1, 1);
398     int numInvalidPixels   = 0;
399     bool renderedSomething = false;
400 
401     for (int y = 0; y < result.getHeight(); ++y)
402     {
403         for (int x = 0; x < result.getWidth(); ++x)
404         {
405             const tcu::RGBA resPix = result.getPixel(x, y);
406             const bool isBg        = tcu::compareThreshold(resPix, bgRef, threshold);
407             const bool isFg        = tcu::compareThreshold(resPix, fgRef, threshold);
408 
409             if (!isBg && !isFg)
410                 numInvalidPixels += 1;
411 
412             if (isFg)
413                 renderedSomething = true;
414         }
415     }
416 
417     if (numInvalidPixels > 0)
418     {
419         log << TestLog::Image("Result", "Result image", result);
420         log << TestLog::Message << "ERROR: Found " << numInvalidPixels << " invalid result pixels!"
421             << TestLog::EndMessage;
422         return false;
423     }
424     else if (!renderedSomething)
425     {
426         log << TestLog::Image("Result", "Result image", result);
427         log << TestLog::Message << "ERROR: Result image was empty!" << TestLog::EndMessage;
428         return false;
429     }
430     else
431     {
432         log << TestLog::Message << "All result pixels are valid" << TestLog::EndMessage;
433         return true;
434     }
435 }
436 
iterate(void)437 HelperInvocationValueCase::IterateResult HelperInvocationValueCase::iterate(void)
438 {
439     const glu::RenderContext &renderCtx = m_context.getRenderContext();
440     const glw::Functions &gl            = renderCtx.getFunctions();
441     const string sectionName = string("Iteration ") + de::toString(m_iterNdx + 1) + " / " + de::toString(m_numIters);
442     const tcu::ScopedLogSection section(m_testCtx.getLog(), (string("Iter") + de::toString(m_iterNdx)), sectionName);
443     de::Random rnd(deStringHash(getName()) ^ deInt32Hash(m_iterNdx));
444     tcu::Surface result(FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT);
445 
446     m_fbo->bindForRendering();
447     gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
448     gl.clear(GL_COLOR_BUFFER_BIT);
449 
450     drawRandomPrimitives(renderCtx, m_program->getProgram(), m_primitiveType, m_numPrimitivesPerIter, &rnd);
451 
452     m_fbo->readPixels(0, 0, result.getAccess());
453 
454     if (!verifyHelperInvocationValue(m_testCtx.getLog(), result, m_numSamples != 0))
455         m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid pixels found");
456 
457     m_iterNdx += 1;
458     return (m_iterNdx < m_numIters) ? CONTINUE : STOP;
459 }
460 
461 //! Checks derivates when value depends on gl_HelperInvocation.
462 class HelperInvocationDerivateCase : public TestCase
463 {
464 public:
465     HelperInvocationDerivateCase(Context &context, const char *name, const char *description, PrimitiveType primType,
466                                  int numSamples, const char *derivateFunc, bool checkAbsoluteValue);
467     ~HelperInvocationDerivateCase(void);
468 
469     void init(void);
470     void deinit(void);
471     IterateResult iterate(void);
472 
473 private:
474     const PrimitiveType m_primitiveType;
475     const int m_numSamples;
476     const std::string m_derivateFunc;
477     const bool m_checkAbsoluteValue;
478 
479     const int m_numIters;
480 
481     MovePtr<ShaderProgram> m_program;
482     MovePtr<FboHelper> m_fbo;
483     int m_iterNdx;
484 };
485 
HelperInvocationDerivateCase(Context & context,const char * name,const char * description,PrimitiveType primType,int numSamples,const char * derivateFunc,bool checkAbsoluteValue)486 HelperInvocationDerivateCase::HelperInvocationDerivateCase(Context &context, const char *name, const char *description,
487                                                            PrimitiveType primType, int numSamples,
488                                                            const char *derivateFunc, bool checkAbsoluteValue)
489     : TestCase(context, name, description)
490     , m_primitiveType(primType)
491     , m_numSamples(numSamples)
492     , m_derivateFunc(derivateFunc)
493     , m_checkAbsoluteValue(checkAbsoluteValue)
494     , m_numIters(16)
495     , m_iterNdx(0)
496 {
497 }
498 
~HelperInvocationDerivateCase(void)499 HelperInvocationDerivateCase::~HelperInvocationDerivateCase(void)
500 {
501     deinit();
502 }
503 
init(void)504 void HelperInvocationDerivateCase::init(void)
505 {
506     const glu::RenderContext &renderCtx = m_context.getRenderContext();
507     const glw::Functions &gl            = renderCtx.getFunctions();
508     const int maxSamples                = getInteger(gl, GL_MAX_SAMPLES);
509     const int actualSamples             = m_numSamples == NUM_SAMPLES_MAX ? maxSamples : m_numSamples;
510     const std::string funcSource =
511         (m_checkAbsoluteValue) ? ("abs(" + m_derivateFunc + "(value))") : (m_derivateFunc + "(value)");
512 
513     m_program = MovePtr<ShaderProgram>(new ShaderProgram(
514         m_context.getRenderContext(),
515         glu::ProgramSources() << glu::VertexSource("#version 310 es\n"
516                                                    "in highp vec2 a_position;\n"
517                                                    "uniform highp float u_pointSize;\n"
518                                                    "void main (void)\n"
519                                                    "{\n"
520                                                    "    gl_Position = vec4(a_position, 0.0, 1.0);\n"
521                                                    "    gl_PointSize = u_pointSize;\n"
522                                                    "}\n")
523                               << glu::FragmentSource(string("#version 310 es\n"
524                                                             "out mediump vec4 o_color;\n"
525                                                             "void main (void)\n"
526                                                             "{\n"
527                                                             "    highp float value = gl_HelperInvocation ? 1.0 : 0.0;\n"
528                                                             "    highp float derivate = ") +
529                                                      funcSource +
530                                                      ";\n"
531                                                      "    if (gl_HelperInvocation)\n"
532                                                      "        o_color = vec4(1.0, 0.0, derivate, 1.0);\n"
533                                                      "    else\n"
534                                                      "        o_color = vec4(0.0, 1.0, derivate, 1.0);\n"
535                                                      "}\n")));
536 
537     m_testCtx.getLog() << *m_program;
538 
539     if (!m_program->isOk())
540     {
541         m_program.clear();
542         TCU_FAIL("Compile failed");
543     }
544 
545     m_testCtx.getLog() << TestLog::Message << "Using GL_RGBA8 framebuffer with " << actualSamples << " samples"
546                        << TestLog::EndMessage;
547 
548     m_fbo = MovePtr<FboHelper>(
549         new FboHelper(renderCtx, FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT, FRAMEBUFFER_FORMAT, actualSamples));
550 
551     m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
552 }
553 
deinit(void)554 void HelperInvocationDerivateCase::deinit(void)
555 {
556     m_program.clear();
557     m_fbo.clear();
558 }
559 
hasNeighborWithColor(const tcu::Surface & surface,int x,int y,tcu::RGBA color,tcu::RGBA threshold)560 static bool hasNeighborWithColor(const tcu::Surface &surface, int x, int y, tcu::RGBA color, tcu::RGBA threshold)
561 {
562     const int w = surface.getWidth();
563     const int h = surface.getHeight();
564 
565     for (int dx = -1; dx < 2; dx++)
566         for (int dy = -1; dy < 2; dy++)
567         {
568             const IVec2 pos = IVec2(x + dx, y + dy);
569 
570             if (dx == 0 && dy == 0)
571                 continue;
572 
573             if (de::inBounds(pos.x(), 0, w) && de::inBounds(pos.y(), 0, h))
574             {
575                 const tcu::RGBA neighborColor = surface.getPixel(pos.x(), pos.y());
576 
577                 if (tcu::compareThreshold(color, neighborColor, threshold))
578                     return true;
579             }
580             else
581                 return true; // Can't know for certain
582         }
583 
584     return false;
585 }
586 
verifyHelperInvocationDerivate(TestLog & log,const tcu::Surface & result,bool isMultiSample)587 static bool verifyHelperInvocationDerivate(TestLog &log, const tcu::Surface &result, bool isMultiSample)
588 {
589     const tcu::RGBA bgRef(0, 0, 0, 255);
590     const tcu::RGBA fgRef(0, 255, 0, 255);
591     const tcu::RGBA isBgThreshold(1, isMultiSample ? 254 : 1, 0, 1);
592     const tcu::RGBA isFgThreshold(1, isMultiSample ? 254 : 1, 255, 1);
593     int numInvalidPixels   = 0;
594     int numNonZeroDeriv    = 0;
595     bool renderedSomething = false;
596 
597     for (int y = 0; y < result.getHeight(); ++y)
598     {
599         for (int x = 0; x < result.getWidth(); ++x)
600         {
601             const tcu::RGBA resPix  = result.getPixel(x, y);
602             const bool isBg         = tcu::compareThreshold(resPix, bgRef, isBgThreshold);
603             const bool isFg         = tcu::compareThreshold(resPix, fgRef, isFgThreshold);
604             const bool nonZeroDeriv = resPix.getBlue() > 0;
605             const bool neighborBg   = nonZeroDeriv ? hasNeighborWithColor(result, x, y, bgRef, isBgThreshold) : false;
606 
607             if (nonZeroDeriv)
608                 numNonZeroDeriv += 1;
609 
610             if ((!isBg && !isFg) || // Neither of valid colors (ignoring blue channel that has derivate)
611                 (nonZeroDeriv && !neighborBg &&
612                  !isFg)) // Has non-zero derivate, but sample not at primitive edge or inside primitive
613                 numInvalidPixels += 1;
614 
615             if (isFg)
616                 renderedSomething = true;
617         }
618     }
619 
620     log << TestLog::Message << "Found " << numNonZeroDeriv
621         << " pixels with non-zero derivate (neighbor sample has gl_HelperInvocation = true)" << TestLog::EndMessage;
622 
623     if (numInvalidPixels > 0)
624     {
625         log << TestLog::Image("Result", "Result image", result);
626         log << TestLog::Message << "ERROR: Found " << numInvalidPixels << " invalid result pixels!"
627             << TestLog::EndMessage;
628         return false;
629     }
630     else if (!renderedSomething)
631     {
632         log << TestLog::Image("Result", "Result image", result);
633         log << TestLog::Message << "ERROR: Result image was empty!" << TestLog::EndMessage;
634         return false;
635     }
636     else
637     {
638         log << TestLog::Message << "All result pixels are valid" << TestLog::EndMessage;
639         return true;
640     }
641 }
642 
iterate(void)643 HelperInvocationDerivateCase::IterateResult HelperInvocationDerivateCase::iterate(void)
644 {
645     const glu::RenderContext &renderCtx = m_context.getRenderContext();
646     const glw::Functions &gl            = renderCtx.getFunctions();
647     const string sectionName = string("Iteration ") + de::toString(m_iterNdx + 1) + " / " + de::toString(m_numIters);
648     const tcu::ScopedLogSection section(m_testCtx.getLog(), (string("Iter") + de::toString(m_iterNdx)), sectionName);
649     de::Random rnd(deStringHash(getName()) ^ deInt32Hash(m_iterNdx));
650     tcu::Surface result(FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT);
651 
652     m_fbo->bindForRendering();
653     gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
654     gl.clear(GL_COLOR_BUFFER_BIT);
655 
656     drawRandomPrimitives(renderCtx, m_program->getProgram(), m_primitiveType, 1, &rnd);
657 
658     m_fbo->readPixels(0, 0, result.getAccess());
659 
660     if (!verifyHelperInvocationDerivate(m_testCtx.getLog(), result, m_numSamples != 0))
661         m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid pixels found");
662 
663     m_iterNdx += 1;
664     return (m_iterNdx < m_numIters) ? CONTINUE : STOP;
665 }
666 
667 } // namespace
668 
ShaderHelperInvocationTests(Context & context)669 ShaderHelperInvocationTests::ShaderHelperInvocationTests(Context &context)
670     : TestCaseGroup(context, "helper_invocation", "gl_HelperInvocation tests")
671 {
672 }
673 
~ShaderHelperInvocationTests(void)674 ShaderHelperInvocationTests::~ShaderHelperInvocationTests(void)
675 {
676 }
677 
init(void)678 void ShaderHelperInvocationTests::init(void)
679 {
680     static const struct
681     {
682         const char *caseName;
683         PrimitiveType primType;
684     } s_primTypes[] = {{"triangles", PRIMITIVETYPE_TRIANGLE},
685                        {"lines", PRIMITIVETYPE_LINE},
686                        {"wide_lines", PRIMITIVETYPE_WIDE_LINE},
687                        {"points", PRIMITIVETYPE_POINT},
688                        {"wide_points", PRIMITIVETYPE_WIDE_POINT}};
689 
690     static const struct
691     {
692         const char *suffix;
693         int numSamples;
694     } s_sampleCounts[] = {{"", 0}, {"_4_samples", 4}, {"_8_samples", 8}, {"_max_samples", NUM_SAMPLES_MAX}};
695 
696     // value
697     {
698         tcu::TestCaseGroup *const valueGroup =
699             new tcu::TestCaseGroup(m_testCtx, "value", "gl_HelperInvocation value in rendered pixels");
700         addChild(valueGroup);
701 
702         for (int sampleCountNdx = 0; sampleCountNdx < DE_LENGTH_OF_ARRAY(s_sampleCounts); sampleCountNdx++)
703         {
704             for (int primTypeNdx = 0; primTypeNdx < DE_LENGTH_OF_ARRAY(s_primTypes); primTypeNdx++)
705             {
706                 const string name = string(s_primTypes[primTypeNdx].caseName) + s_sampleCounts[sampleCountNdx].suffix;
707                 const PrimitiveType primType = s_primTypes[primTypeNdx].primType;
708                 const int numSamples         = s_sampleCounts[sampleCountNdx].numSamples;
709 
710                 valueGroup->addChild(new HelperInvocationValueCase(m_context, name.c_str(), "", primType, numSamples));
711             }
712         }
713     }
714 
715     // derivate
716     {
717         tcu::TestCaseGroup *const derivateGroup =
718             new tcu::TestCaseGroup(m_testCtx, "derivate", "Derivate of gl_HelperInvocation-dependent value");
719         addChild(derivateGroup);
720 
721         for (int sampleCountNdx = 0; sampleCountNdx < DE_LENGTH_OF_ARRAY(s_sampleCounts); sampleCountNdx++)
722         {
723             for (int primTypeNdx = 0; primTypeNdx < DE_LENGTH_OF_ARRAY(s_primTypes); primTypeNdx++)
724             {
725                 const string name = string(s_primTypes[primTypeNdx].caseName) + s_sampleCounts[sampleCountNdx].suffix;
726                 const PrimitiveType primType = s_primTypes[primTypeNdx].primType;
727                 const int numSamples         = s_sampleCounts[sampleCountNdx].numSamples;
728 
729                 derivateGroup->addChild(new HelperInvocationDerivateCase(m_context, (name + "_dfdx").c_str(), "",
730                                                                          primType, numSamples, "dFdx", true));
731                 derivateGroup->addChild(new HelperInvocationDerivateCase(m_context, (name + "_dfdy").c_str(), "",
732                                                                          primType, numSamples, "dFdy", true));
733                 derivateGroup->addChild(new HelperInvocationDerivateCase(m_context, (name + "_fwidth").c_str(), "",
734                                                                          primType, numSamples, "fwidth", false));
735             }
736         }
737     }
738 }
739 
740 } // namespace Functional
741 } // namespace gles31
742 } // namespace deqp
743