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 Synchronization Tests
22 *//*--------------------------------------------------------------------*/
23
24 #include "es31fSynchronizationTests.hpp"
25 #include "tcuTestLog.hpp"
26 #include "tcuStringTemplate.hpp"
27 #include "tcuSurface.hpp"
28 #include "tcuRenderTarget.hpp"
29 #include "gluRenderContext.hpp"
30 #include "gluShaderProgram.hpp"
31 #include "gluObjectWrapper.hpp"
32 #include "gluPixelTransfer.hpp"
33 #include "gluContextInfo.hpp"
34 #include "glwFunctions.hpp"
35 #include "glwEnums.hpp"
36 #include "deStringUtil.hpp"
37 #include "deSharedPtr.hpp"
38 #include "deMemory.h"
39 #include "deRandom.hpp"
40
41 #include <map>
42
43 namespace deqp
44 {
45 namespace gles31
46 {
47 namespace Functional
48 {
49 namespace
50 {
51
checkSupport(Context & ctx)52 static bool checkSupport(Context &ctx)
53 {
54 auto ctxType = ctx.getRenderContext().getType();
55 return contextSupports(ctxType, glu::ApiType::es(3, 2)) || contextSupports(ctxType, glu::ApiType::core(4, 5)) ||
56 ctx.getContextInfo().isExtensionSupported("GL_OES_shader_image_atomic");
57 }
58
validateSortedAtomicRampAdditionValueChain(const std::vector<uint32_t> & valueChain,uint32_t sumValue,int & invalidOperationNdx,uint32_t & errorDelta,uint32_t & errorExpected)59 static bool validateSortedAtomicRampAdditionValueChain(const std::vector<uint32_t> &valueChain, uint32_t sumValue,
60 int &invalidOperationNdx, uint32_t &errorDelta,
61 uint32_t &errorExpected)
62 {
63 std::vector<uint32_t> chainDelta(valueChain.size());
64
65 for (int callNdx = 0; callNdx < (int)valueChain.size(); ++callNdx)
66 chainDelta[callNdx] =
67 ((callNdx + 1 == (int)valueChain.size()) ? (sumValue) : (valueChain[callNdx + 1])) - valueChain[callNdx];
68
69 // chainDelta contains now the actual additions applied to the value
70 // check there exists an addition ramp form 1 to ...
71 std::sort(chainDelta.begin(), chainDelta.end());
72
73 for (int callNdx = 0; callNdx < (int)valueChain.size(); ++callNdx)
74 {
75 if ((int)chainDelta[callNdx] != callNdx + 1)
76 {
77 invalidOperationNdx = callNdx;
78 errorDelta = chainDelta[callNdx];
79 errorExpected = callNdx + 1;
80
81 return false;
82 }
83 }
84
85 return true;
86 }
87
readBuffer(const glw::Functions & gl,uint32_t target,int numElements,std::vector<uint32_t> & result)88 static void readBuffer(const glw::Functions &gl, uint32_t target, int numElements, std::vector<uint32_t> &result)
89 {
90 const void *ptr = gl.mapBufferRange(target, 0, (int)(sizeof(uint32_t) * numElements), GL_MAP_READ_BIT);
91 GLU_EXPECT_NO_ERROR(gl.getError(), "map");
92
93 if (!ptr)
94 throw tcu::TestError("mapBufferRange returned NULL");
95
96 result.resize(numElements);
97 memcpy(&result[0], ptr, sizeof(uint32_t) * numElements);
98
99 if (gl.unmapBuffer(target) == GL_FALSE)
100 throw tcu::TestError("unmapBuffer returned false");
101 }
102
readBufferUint32(const glw::Functions & gl,uint32_t target)103 static uint32_t readBufferUint32(const glw::Functions &gl, uint32_t target)
104 {
105 std::vector<uint32_t> vec;
106
107 readBuffer(gl, target, 1, vec);
108
109 return vec[0];
110 }
111
112 //! Generate a ramp of values from 1 to numElements, and shuffle it
generateShuffledRamp(int numElements,std::vector<int> & ramp)113 void generateShuffledRamp(int numElements, std::vector<int> &ramp)
114 {
115 de::Random rng(0xabcd);
116
117 // some positive (non-zero) unique values
118 ramp.resize(numElements);
119 for (int callNdx = 0; callNdx < numElements; ++callNdx)
120 ramp[callNdx] = callNdx + 1;
121
122 rng.shuffle(ramp.begin(), ramp.end());
123 }
124
specializeShader(Context & context,const char * code)125 static std::string specializeShader(Context &context, const char *code)
126 {
127 auto ctxType = context.getRenderContext().getType();
128 const bool isES32orGL45 = glu::contextSupports(ctxType, glu::ApiType::es(3, 2)) ||
129 glu::contextSupports(ctxType, glu::ApiType::core(4, 5));
130 const glu::GLSLVersion glslVersion = glu::getContextTypeGLSLVersion(ctxType);
131
132 std::map<std::string, std::string> specializationMap;
133 specializationMap["GLSL_VERSION_DECL"] = glu::getGLSLVersionDeclaration(glslVersion);
134 specializationMap["SHADER_IMAGE_ATOMIC_REQUIRE"] =
135 isES32orGL45 ? "" : "#extension GL_OES_shader_image_atomic : require";
136
137 return tcu::StringTemplate(code).specialize(specializationMap);
138 }
139
140 class InterInvocationTestCase : public TestCase
141 {
142 public:
143 enum StorageType
144 {
145 STORAGE_BUFFER = 0,
146 STORAGE_IMAGE,
147
148 STORAGE_LAST
149 };
150 enum CaseFlags
151 {
152 FLAG_ATOMIC = 0x1,
153 FLAG_ALIASING_STORAGES = 0x2,
154 FLAG_IN_GROUP = 0x4,
155 };
156
157 InterInvocationTestCase(Context &context, const char *name, const char *desc, StorageType storage, int flags = 0);
158 ~InterInvocationTestCase(void);
159
160 private:
161 void init(void);
162 void deinit(void);
163 IterateResult iterate(void);
164
165 void runCompute(void);
166 bool verifyResults(void);
167 virtual std::string genShaderSource(void) const = 0;
168
169 protected:
170 std::string genBarrierSource(void) const;
171
172 const StorageType m_storage;
173 const bool m_useAtomic;
174 const bool m_aliasingStorages;
175 const bool m_syncWithGroup;
176 const int m_workWidth; // !< total work width
177 const int m_workHeight; // !< ... height
178 const int m_localWidth; // !< group width
179 const int m_localHeight; // !< group height
180 const int m_elementsPerInvocation; // !< elements accessed by a single invocation
181
182 private:
183 glw::GLuint m_storageBuf;
184 glw::GLuint m_storageTex;
185 glw::GLuint m_resultBuf;
186 glu::ShaderProgram *m_program;
187 };
188
InterInvocationTestCase(Context & context,const char * name,const char * desc,StorageType storage,int flags)189 InterInvocationTestCase::InterInvocationTestCase(Context &context, const char *name, const char *desc,
190 StorageType storage, int flags)
191 : TestCase(context, name, desc)
192 , m_storage(storage)
193 , m_useAtomic((flags & FLAG_ATOMIC) != 0)
194 , m_aliasingStorages((flags & FLAG_ALIASING_STORAGES) != 0)
195 , m_syncWithGroup((flags & FLAG_IN_GROUP) != 0)
196 , m_workWidth(256)
197 , m_workHeight(256)
198 , m_localWidth(16)
199 , m_localHeight(8)
200 , m_elementsPerInvocation(8)
201 , m_storageBuf(0)
202 , m_storageTex(0)
203 , m_resultBuf(0)
204 , m_program(DE_NULL)
205 {
206 DE_ASSERT(m_storage < STORAGE_LAST);
207 DE_ASSERT(m_localWidth * m_localHeight <= 128); // minimum MAX_COMPUTE_WORK_GROUP_INVOCATIONS value
208 }
209
~InterInvocationTestCase(void)210 InterInvocationTestCase::~InterInvocationTestCase(void)
211 {
212 deinit();
213 }
214
init(void)215 void InterInvocationTestCase::init(void)
216 {
217 const glw::Functions &gl = m_context.getRenderContext().getFunctions();
218
219 // requirements
220
221 if (m_useAtomic && m_storage == STORAGE_IMAGE && !checkSupport(m_context))
222 throw tcu::NotSupportedError("Test requires GL_OES_shader_image_atomic extension");
223
224 // program
225
226 m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
227 << glu::ComputeSource(genShaderSource()));
228 m_testCtx.getLog() << *m_program;
229 if (!m_program->isOk())
230 throw tcu::TestError("could not build program");
231
232 // source
233
234 if (m_storage == STORAGE_BUFFER)
235 {
236 const int bufferElements = m_workWidth * m_workHeight * m_elementsPerInvocation;
237 const int bufferSize = bufferElements * (int)sizeof(uint32_t);
238 std::vector<uint32_t> zeroBuffer(bufferElements, 0);
239
240 m_testCtx.getLog() << tcu::TestLog::Message << "Allocating zero-filled buffer for storage, size "
241 << bufferElements << " elements, " << bufferSize << " bytes." << tcu::TestLog::EndMessage;
242
243 gl.genBuffers(1, &m_storageBuf);
244 gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_storageBuf);
245 gl.bufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, &zeroBuffer[0], GL_STATIC_DRAW);
246 GLU_EXPECT_NO_ERROR(gl.getError(), "gen storage buf");
247 }
248 else if (m_storage == STORAGE_IMAGE)
249 {
250 const int bufferElements = m_workWidth * m_workHeight * m_elementsPerInvocation;
251 const int bufferSize = bufferElements * (int)sizeof(uint32_t);
252
253 m_testCtx.getLog() << tcu::TestLog::Message << "Allocating image for storage, size " << m_workWidth << "x"
254 << m_workHeight * m_elementsPerInvocation << ", " << bufferSize << " bytes."
255 << tcu::TestLog::EndMessage;
256
257 gl.genTextures(1, &m_storageTex);
258 gl.bindTexture(GL_TEXTURE_2D, m_storageTex);
259 gl.texStorage2D(GL_TEXTURE_2D, 1, GL_R32I, m_workWidth, m_workHeight * m_elementsPerInvocation);
260 gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
261 gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
262 GLU_EXPECT_NO_ERROR(gl.getError(), "gen storage image");
263
264 // Zero-fill
265 m_testCtx.getLog() << tcu::TestLog::Message << "Filling image with 0." << tcu::TestLog::EndMessage;
266
267 {
268 const std::vector<int32_t> zeroBuffer(m_workWidth * m_workHeight * m_elementsPerInvocation, 0);
269 gl.texSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_workWidth, m_workHeight * m_elementsPerInvocation,
270 GL_RED_INTEGER, GL_INT, &zeroBuffer[0]);
271 GLU_EXPECT_NO_ERROR(gl.getError(), "specify image contents");
272 }
273 }
274 else
275 DE_ASSERT(false);
276
277 // destination
278
279 {
280 const int bufferElements = m_workWidth * m_workHeight;
281 const int bufferSize = bufferElements * (int)sizeof(uint32_t);
282 std::vector<int32_t> negativeBuffer(bufferElements, -1);
283
284 m_testCtx.getLog() << tcu::TestLog::Message << "Allocating -1 filled buffer for results, size "
285 << bufferElements << " elements, " << bufferSize << " bytes." << tcu::TestLog::EndMessage;
286
287 gl.genBuffers(1, &m_resultBuf);
288 gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_resultBuf);
289 gl.bufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, &negativeBuffer[0], GL_STATIC_DRAW);
290 GLU_EXPECT_NO_ERROR(gl.getError(), "gen storage buf");
291 }
292 }
293
deinit(void)294 void InterInvocationTestCase::deinit(void)
295 {
296 if (m_storageBuf)
297 {
298 m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_storageBuf);
299 m_storageBuf = DE_NULL;
300 }
301
302 if (m_storageTex)
303 {
304 m_context.getRenderContext().getFunctions().deleteTextures(1, &m_storageTex);
305 m_storageTex = DE_NULL;
306 }
307
308 if (m_resultBuf)
309 {
310 m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_resultBuf);
311 m_resultBuf = DE_NULL;
312 }
313
314 delete m_program;
315 m_program = DE_NULL;
316 }
317
iterate(void)318 InterInvocationTestCase::IterateResult InterInvocationTestCase::iterate(void)
319 {
320 // Dispatch
321 runCompute();
322
323 // Verify buffer contents
324 if (verifyResults())
325 m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
326 else
327 m_testCtx.setTestResult(
328 QP_TEST_RESULT_FAIL,
329 (std::string((m_storage == STORAGE_BUFFER) ? ("buffer") : ("image")) + " content verification failed")
330 .c_str());
331
332 return STOP;
333 }
334
runCompute(void)335 void InterInvocationTestCase::runCompute(void)
336 {
337 const glw::Functions &gl = m_context.getRenderContext().getFunctions();
338 const int groupsX = m_workWidth / m_localWidth;
339 const int groupsY = m_workHeight / m_localHeight;
340
341 DE_ASSERT((m_workWidth % m_localWidth) == 0);
342 DE_ASSERT((m_workHeight % m_localHeight) == 0);
343
344 m_testCtx.getLog() << tcu::TestLog::Message << "Dispatching compute.\n"
345 << " group size: " << m_localWidth << "x" << m_localHeight << "\n"
346 << " dispatch size: " << groupsX << "x" << groupsY << "\n"
347 << " total work size: " << m_workWidth << "x" << m_workHeight << "\n"
348 << tcu::TestLog::EndMessage;
349
350 gl.useProgram(m_program->getProgram());
351
352 // source
353 if (m_storage == STORAGE_BUFFER && !m_aliasingStorages)
354 {
355 gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_storageBuf);
356 GLU_EXPECT_NO_ERROR(gl.getError(), "bind source buf");
357 }
358 else if (m_storage == STORAGE_BUFFER && m_aliasingStorages)
359 {
360 gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_storageBuf);
361 gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, m_storageBuf);
362 GLU_EXPECT_NO_ERROR(gl.getError(), "bind source buf");
363
364 m_testCtx.getLog() << tcu::TestLog::Message << "Binding same buffer object to buffer storages."
365 << tcu::TestLog::EndMessage;
366 }
367 else if (m_storage == STORAGE_IMAGE && !m_aliasingStorages)
368 {
369 gl.bindImageTexture(1, m_storageTex, 0, GL_FALSE, 0, GL_READ_WRITE, GL_R32I);
370 GLU_EXPECT_NO_ERROR(gl.getError(), "bind result buf");
371 }
372 else if (m_storage == STORAGE_IMAGE && m_aliasingStorages)
373 {
374 gl.bindImageTexture(1, m_storageTex, 0, GL_FALSE, 0, GL_READ_WRITE, GL_R32I);
375 gl.bindImageTexture(2, m_storageTex, 0, GL_FALSE, 0, GL_READ_WRITE, GL_R32I);
376
377 GLU_EXPECT_NO_ERROR(gl.getError(), "bind result buf");
378
379 m_testCtx.getLog() << tcu::TestLog::Message << "Binding same texture level to image storages."
380 << tcu::TestLog::EndMessage;
381 }
382 else
383 DE_ASSERT(false);
384
385 // destination
386 gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_resultBuf);
387 GLU_EXPECT_NO_ERROR(gl.getError(), "bind result buf");
388
389 // dispatch
390 gl.dispatchCompute(groupsX, groupsY, 1);
391 GLU_EXPECT_NO_ERROR(gl.getError(), "dispatchCompute");
392 }
393
verifyResults(void)394 bool InterInvocationTestCase::verifyResults(void)
395 {
396 const glw::Functions &gl = m_context.getRenderContext().getFunctions();
397 const int errorFloodThreshold = 5;
398 int numErrorsLogged = 0;
399 const void *mapped = DE_NULL;
400 std::vector<int32_t> results(m_workWidth * m_workHeight);
401 bool error = false;
402
403 gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_resultBuf);
404 gl.memoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);
405 mapped =
406 gl.mapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, m_workWidth * m_workHeight * sizeof(int32_t), GL_MAP_READ_BIT);
407 GLU_EXPECT_NO_ERROR(gl.getError(), "map buffer");
408
409 // copy to properly aligned array
410 deMemcpy(&results[0], mapped, m_workWidth * m_workHeight * sizeof(uint32_t));
411
412 if (gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER) != GL_TRUE)
413 throw tcu::TestError("memory map store corrupted");
414
415 // check the results
416 for (int ndx = 0; ndx < (int)results.size(); ++ndx)
417 {
418 if (results[ndx] != 1)
419 {
420 error = true;
421
422 if (numErrorsLogged == 0)
423 m_testCtx.getLog() << tcu::TestLog::Message << "Result buffer failed, got unexpected values.\n"
424 << tcu::TestLog::EndMessage;
425 if (numErrorsLogged++ < errorFloodThreshold)
426 m_testCtx.getLog() << tcu::TestLog::Message << " Error at index " << ndx << ": expected 1, got "
427 << results[ndx] << ".\n"
428 << tcu::TestLog::EndMessage;
429 else
430 {
431 // after N errors, no point continuing verification
432 m_testCtx.getLog() << tcu::TestLog::Message << " -- too many errors, skipping verification --\n"
433 << tcu::TestLog::EndMessage;
434 break;
435 }
436 }
437 }
438
439 if (!error)
440 m_testCtx.getLog() << tcu::TestLog::Message << "Result buffer ok." << tcu::TestLog::EndMessage;
441 return !error;
442 }
443
genBarrierSource(void) const444 std::string InterInvocationTestCase::genBarrierSource(void) const
445 {
446 std::ostringstream buf;
447
448 if (m_syncWithGroup)
449 {
450 // Wait until all invocations in this work group have their texture/buffer read/write operations complete
451 // \note We could also use memoryBarrierBuffer() or memoryBarrierImage() in place of groupMemoryBarrier() but
452 // we only require intra-workgroup synchronization.
453 buf << "\n"
454 << " groupMemoryBarrier();\n"
455 << " barrier();\n"
456 << "\n";
457 }
458 else if (m_storage == STORAGE_BUFFER)
459 {
460 DE_ASSERT(!m_syncWithGroup);
461
462 // Waiting only for data written by this invocation. Since all buffer reads and writes are
463 // processed in order (within a single invocation), we don't have to do anything.
464 buf << "\n";
465 }
466 else if (m_storage == STORAGE_IMAGE)
467 {
468 DE_ASSERT(!m_syncWithGroup);
469
470 // Waiting only for data written by this invocation. But since operations complete in undefined
471 // order, we have to wait for them to complete.
472 buf << "\n"
473 << " memoryBarrierImage();\n"
474 << "\n";
475 }
476 else
477 DE_ASSERT(false);
478
479 return buf.str();
480 }
481
482 class InvocationBasicCase : public InterInvocationTestCase
483 {
484 public:
485 InvocationBasicCase(Context &context, const char *name, const char *desc, StorageType storage, int flags);
486
487 private:
488 std::string genShaderSource(void) const;
489 virtual std::string genShaderMainBlock(void) const = 0;
490 };
491
InvocationBasicCase(Context & context,const char * name,const char * desc,StorageType storage,int flags)492 InvocationBasicCase::InvocationBasicCase(Context &context, const char *name, const char *desc, StorageType storage,
493 int flags)
494 : InterInvocationTestCase(context, name, desc, storage, flags)
495 {
496 }
497
genShaderSource(void) const498 std::string InvocationBasicCase::genShaderSource(void) const
499 {
500 const bool useImageAtomics = m_useAtomic && m_storage == STORAGE_IMAGE;
501 std::ostringstream buf;
502
503 buf << "${GLSL_VERSION_DECL}\n"
504 << ((useImageAtomics) ? ("${SHADER_IMAGE_ATOMIC_REQUIRE}\n") : ("")) << "layout (local_size_x=" << m_localWidth
505 << ", local_size_y=" << m_localHeight << ") in;\n"
506 << "layout(binding=0, std430) buffer Output\n"
507 << "{\n"
508 << " highp int values[];\n"
509 << "} sb_result;\n";
510
511 if (m_storage == STORAGE_BUFFER)
512 buf << "layout(binding=1, std430) coherent buffer Storage\n"
513 << "{\n"
514 << " highp int values[];\n"
515 << "} sb_store;\n"
516 << "\n"
517 << "highp int getIndex (in highp uvec2 localID, in highp int element)\n"
518 << "{\n"
519 << " highp uint groupNdx = gl_NumWorkGroups.x * gl_WorkGroupID.y + gl_WorkGroupID.x;\n"
520 << " return int((localID.y * gl_NumWorkGroups.x * gl_NumWorkGroups.y * gl_WorkGroupSize.x) + (groupNdx "
521 "* gl_WorkGroupSize.x) + localID.x) * "
522 << m_elementsPerInvocation << " + element;\n"
523 << "}\n";
524 else if (m_storage == STORAGE_IMAGE)
525 buf << "layout(r32i, binding=1) coherent uniform highp iimage2D u_image;\n"
526 << "\n"
527 << "highp ivec2 getCoord (in highp uvec2 localID, in highp int element)\n"
528 << "{\n"
529 << " return ivec2(int(gl_WorkGroupID.x * gl_WorkGroupSize.x + localID.x), int(gl_WorkGroupID.y * "
530 "gl_WorkGroupSize.y + localID.y) + element * "
531 << m_workHeight << ");\n"
532 << "}\n";
533 else
534 DE_ASSERT(false);
535
536 buf << "\n"
537 << "void main (void)\n"
538 << "{\n"
539 << " int resultNdx = int(gl_GlobalInvocationID.y * gl_NumWorkGroups.x * gl_WorkGroupSize.x + "
540 "gl_GlobalInvocationID.x);\n"
541 << " int groupNdx = int(gl_NumWorkGroups.x * gl_WorkGroupID.y + gl_WorkGroupID.x);\n"
542 << " bool allOk = true;\n"
543 << "\n"
544 << genShaderMainBlock() << "\n"
545 << " sb_result.values[resultNdx] = (allOk) ? (1) : (0);\n"
546 << "}\n";
547
548 return specializeShader(m_context, buf.str().c_str());
549 }
550
551 class InvocationWriteReadCase : public InvocationBasicCase
552 {
553 public:
554 InvocationWriteReadCase(Context &context, const char *name, const char *desc, StorageType storage, int flags);
555
556 private:
557 std::string genShaderMainBlock(void) const;
558 };
559
InvocationWriteReadCase(Context & context,const char * name,const char * desc,StorageType storage,int flags)560 InvocationWriteReadCase::InvocationWriteReadCase(Context &context, const char *name, const char *desc,
561 StorageType storage, int flags)
562 : InvocationBasicCase(context, name, desc, storage, flags)
563 {
564 }
565
genShaderMainBlock(void) const566 std::string InvocationWriteReadCase::genShaderMainBlock(void) const
567 {
568 std::ostringstream buf;
569
570 // write
571
572 for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
573 {
574 if (m_storage == STORAGE_BUFFER && m_useAtomic)
575 buf << "\tatomicAdd(sb_store.values[getIndex(gl_LocalInvocationID.xy, " << ndx << ")], groupNdx);\n";
576 else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
577 buf << "\tsb_store.values[getIndex(gl_LocalInvocationID.xy, " << ndx << ")] = groupNdx;\n";
578 else if (m_storage == STORAGE_IMAGE && m_useAtomic)
579 buf << "\timageAtomicAdd(u_image, getCoord(gl_LocalInvocationID.xy, " << ndx << "), int(groupNdx));\n";
580 else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
581 buf << "\timageStore(u_image, getCoord(gl_LocalInvocationID.xy, " << ndx
582 << "), ivec4(int(groupNdx), 0, 0, 0));\n";
583 else
584 DE_ASSERT(false);
585 }
586
587 // barrier
588
589 buf << genBarrierSource();
590
591 // read
592
593 for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
594 {
595 const std::string localID = (m_syncWithGroup) ? ("(gl_LocalInvocationID.xy + uvec2(" + de::toString(ndx + 1) +
596 ", " + de::toString(2 * ndx) + ")) % gl_WorkGroupSize.xy") :
597 ("gl_LocalInvocationID.xy");
598
599 if (m_storage == STORAGE_BUFFER && m_useAtomic)
600 buf << "\tallOk = allOk && (atomicExchange(sb_store.values[getIndex(" << localID << ", " << ndx
601 << ")], 0) == groupNdx);\n";
602 else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
603 buf << "\tallOk = allOk && (sb_store.values[getIndex(" << localID << ", " << ndx << ")] == groupNdx);\n";
604 else if (m_storage == STORAGE_IMAGE && m_useAtomic)
605 buf << "\tallOk = allOk && (imageAtomicExchange(u_image, getCoord(" << localID << ", " << ndx
606 << "), 0) == groupNdx);\n";
607 else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
608 buf << "\tallOk = allOk && (imageLoad(u_image, getCoord(" << localID << ", " << ndx
609 << ")).x == groupNdx);\n";
610 else
611 DE_ASSERT(false);
612 }
613
614 return buf.str();
615 }
616
617 class InvocationReadWriteCase : public InvocationBasicCase
618 {
619 public:
620 InvocationReadWriteCase(Context &context, const char *name, const char *desc, StorageType storage, int flags);
621
622 private:
623 std::string genShaderMainBlock(void) const;
624 };
625
InvocationReadWriteCase(Context & context,const char * name,const char * desc,StorageType storage,int flags)626 InvocationReadWriteCase::InvocationReadWriteCase(Context &context, const char *name, const char *desc,
627 StorageType storage, int flags)
628 : InvocationBasicCase(context, name, desc, storage, flags)
629 {
630 }
631
genShaderMainBlock(void) const632 std::string InvocationReadWriteCase::genShaderMainBlock(void) const
633 {
634 std::ostringstream buf;
635
636 // read
637
638 for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
639 {
640 const std::string localID = (m_syncWithGroup) ? ("(gl_LocalInvocationID.xy + uvec2(" + de::toString(ndx + 1) +
641 ", " + de::toString(2 * ndx) + ")) % gl_WorkGroupSize.xy") :
642 ("gl_LocalInvocationID.xy");
643
644 if (m_storage == STORAGE_BUFFER && m_useAtomic)
645 buf << "\tallOk = allOk && (atomicExchange(sb_store.values[getIndex(" << localID << ", " << ndx
646 << ")], 123) == 0);\n";
647 else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
648 buf << "\tallOk = allOk && (sb_store.values[getIndex(" << localID << ", " << ndx << ")] == 0);\n";
649 else if (m_storage == STORAGE_IMAGE && m_useAtomic)
650 buf << "\tallOk = allOk && (imageAtomicExchange(u_image, getCoord(" << localID << ", " << ndx
651 << "), 123) == 0);\n";
652 else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
653 buf << "\tallOk = allOk && (imageLoad(u_image, getCoord(" << localID << ", " << ndx << ")).x == 0);\n";
654 else
655 DE_ASSERT(false);
656 }
657
658 // barrier
659
660 buf << genBarrierSource();
661
662 // write
663
664 for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
665 {
666 if (m_storage == STORAGE_BUFFER && m_useAtomic)
667 buf << "\tatomicAdd(sb_store.values[getIndex(gl_LocalInvocationID.xy, " << ndx << ")], groupNdx);\n";
668 else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
669 buf << "\tsb_store.values[getIndex(gl_LocalInvocationID.xy, " << ndx << ")] = groupNdx;\n";
670 else if (m_storage == STORAGE_IMAGE && m_useAtomic)
671 buf << "\timageAtomicAdd(u_image, getCoord(gl_LocalInvocationID.xy, " << ndx << "), int(groupNdx));\n";
672 else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
673 buf << "\timageStore(u_image, getCoord(gl_LocalInvocationID.xy, " << ndx
674 << "), ivec4(int(groupNdx), 0, 0, 0));\n";
675 else
676 DE_ASSERT(false);
677 }
678
679 return buf.str();
680 }
681
682 class InvocationOverWriteCase : public InvocationBasicCase
683 {
684 public:
685 InvocationOverWriteCase(Context &context, const char *name, const char *desc, StorageType storage, int flags);
686
687 private:
688 std::string genShaderMainBlock(void) const;
689 };
690
InvocationOverWriteCase(Context & context,const char * name,const char * desc,StorageType storage,int flags)691 InvocationOverWriteCase::InvocationOverWriteCase(Context &context, const char *name, const char *desc,
692 StorageType storage, int flags)
693 : InvocationBasicCase(context, name, desc, storage, flags)
694 {
695 }
696
genShaderMainBlock(void) const697 std::string InvocationOverWriteCase::genShaderMainBlock(void) const
698 {
699 std::ostringstream buf;
700
701 // write
702
703 for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
704 {
705 if (m_storage == STORAGE_BUFFER && m_useAtomic)
706 buf << "\tatomicAdd(sb_store.values[getIndex(gl_LocalInvocationID.xy, " << ndx << ")], 456);\n";
707 else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
708 buf << "\tsb_store.values[getIndex(gl_LocalInvocationID.xy, " << ndx << ")] = 456;\n";
709 else if (m_storage == STORAGE_IMAGE && m_useAtomic)
710 buf << "\timageAtomicAdd(u_image, getCoord(gl_LocalInvocationID.xy, " << ndx << "), 456);\n";
711 else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
712 buf << "\timageStore(u_image, getCoord(gl_LocalInvocationID.xy, " << ndx << "), ivec4(456, 0, 0, 0));\n";
713 else
714 DE_ASSERT(false);
715 }
716
717 // barrier
718
719 buf << genBarrierSource();
720
721 // write over
722
723 for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
724 {
725 // write another invocation's value or our own value depending on test type
726 const std::string localID = (m_syncWithGroup) ? ("(gl_LocalInvocationID.xy + uvec2(" + de::toString(ndx + 4) +
727 ", " + de::toString(3 * ndx) + ")) % gl_WorkGroupSize.xy") :
728 ("gl_LocalInvocationID.xy");
729
730 if (m_storage == STORAGE_BUFFER && m_useAtomic)
731 buf << "\tatomicExchange(sb_store.values[getIndex(" << localID << ", " << ndx << ")], groupNdx);\n";
732 else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
733 buf << "\tsb_store.values[getIndex(" << localID << ", " << ndx << ")] = groupNdx;\n";
734 else if (m_storage == STORAGE_IMAGE && m_useAtomic)
735 buf << "\timageAtomicExchange(u_image, getCoord(" << localID << ", " << ndx << "), groupNdx);\n";
736 else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
737 buf << "\timageStore(u_image, getCoord(" << localID << ", " << ndx << "), ivec4(groupNdx, 0, 0, 0));\n";
738 else
739 DE_ASSERT(false);
740 }
741
742 // barrier
743
744 buf << genBarrierSource();
745
746 // read
747
748 for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
749 {
750 // check another invocation's value or our own value depending on test type
751 const std::string localID = (m_syncWithGroup) ? ("(gl_LocalInvocationID.xy + uvec2(" + de::toString(ndx + 1) +
752 ", " + de::toString(2 * ndx) + ")) % gl_WorkGroupSize.xy") :
753 ("gl_LocalInvocationID.xy");
754
755 if (m_storage == STORAGE_BUFFER && m_useAtomic)
756 buf << "\tallOk = allOk && (atomicExchange(sb_store.values[getIndex(" << localID << ", " << ndx
757 << ")], 123) == groupNdx);\n";
758 else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
759 buf << "\tallOk = allOk && (sb_store.values[getIndex(" << localID << ", " << ndx << ")] == groupNdx);\n";
760 else if (m_storage == STORAGE_IMAGE && m_useAtomic)
761 buf << "\tallOk = allOk && (imageAtomicExchange(u_image, getCoord(" << localID << ", " << ndx
762 << "), 123) == groupNdx);\n";
763 else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
764 buf << "\tallOk = allOk && (imageLoad(u_image, getCoord(" << localID << ", " << ndx
765 << ")).x == groupNdx);\n";
766 else
767 DE_ASSERT(false);
768 }
769
770 return buf.str();
771 }
772
773 class InvocationAliasWriteCase : public InterInvocationTestCase
774 {
775 public:
776 enum TestType
777 {
778 TYPE_WRITE = 0,
779 TYPE_OVERWRITE,
780
781 TYPE_LAST
782 };
783
784 InvocationAliasWriteCase(Context &context, const char *name, const char *desc, TestType type, StorageType storage,
785 int flags);
786
787 private:
788 std::string genShaderSource(void) const;
789
790 const TestType m_type;
791 };
792
InvocationAliasWriteCase(Context & context,const char * name,const char * desc,TestType type,StorageType storage,int flags)793 InvocationAliasWriteCase::InvocationAliasWriteCase(Context &context, const char *name, const char *desc, TestType type,
794 StorageType storage, int flags)
795 : InterInvocationTestCase(context, name, desc, storage, flags | FLAG_ALIASING_STORAGES)
796 , m_type(type)
797 {
798 DE_ASSERT(type < TYPE_LAST);
799 }
800
genShaderSource(void) const801 std::string InvocationAliasWriteCase::genShaderSource(void) const
802 {
803 const bool useImageAtomics = m_useAtomic && m_storage == STORAGE_IMAGE;
804 std::ostringstream buf;
805
806 buf << "${GLSL_VERSION_DECL}\n"
807 << ((useImageAtomics) ? ("${SHADER_IMAGE_ATOMIC_REQUIRE}\n") : ("")) << "layout (local_size_x=" << m_localWidth
808 << ", local_size_y=" << m_localHeight << ") in;\n"
809 << "layout(binding=0, std430) buffer Output\n"
810 << "{\n"
811 << " highp int values[];\n"
812 << "} sb_result;\n";
813
814 if (m_storage == STORAGE_BUFFER)
815 buf << "layout(binding=1, std430) coherent buffer Storage0\n"
816 << "{\n"
817 << " highp int values[];\n"
818 << "} sb_store0;\n"
819 << "layout(binding=2, std430) coherent buffer Storage1\n"
820 << "{\n"
821 << " highp int values[];\n"
822 << "} sb_store1;\n"
823 << "\n"
824 << "highp int getIndex (in highp uvec2 localID, in highp int element)\n"
825 << "{\n"
826 << " highp uint groupNdx = gl_NumWorkGroups.x * gl_WorkGroupID.y + gl_WorkGroupID.x;\n"
827 << " return int((localID.y * gl_NumWorkGroups.x * gl_NumWorkGroups.y * gl_WorkGroupSize.x) + (groupNdx "
828 "* gl_WorkGroupSize.x) + localID.x) * "
829 << m_elementsPerInvocation << " + element;\n"
830 << "}\n";
831 else if (m_storage == STORAGE_IMAGE)
832 buf << "layout(r32i, binding=1) coherent uniform highp iimage2D u_image0;\n"
833 << "layout(r32i, binding=2) coherent uniform highp iimage2D u_image1;\n"
834 << "\n"
835 << "highp ivec2 getCoord (in highp uvec2 localID, in highp int element)\n"
836 << "{\n"
837 << " return ivec2(int(gl_WorkGroupID.x * gl_WorkGroupSize.x + localID.x), int(gl_WorkGroupID.y * "
838 "gl_WorkGroupSize.y + localID.y) + element * "
839 << m_workHeight << ");\n"
840 << "}\n";
841 else
842 DE_ASSERT(false);
843
844 buf << "\n"
845 << "void main (void)\n"
846 << "{\n"
847 << " int resultNdx = int(gl_GlobalInvocationID.y * gl_NumWorkGroups.x * gl_WorkGroupSize.x + "
848 "gl_GlobalInvocationID.x);\n"
849 << " int groupNdx = int(gl_NumWorkGroups.x * gl_WorkGroupID.y + gl_WorkGroupID.x);\n"
850 << " bool allOk = true;\n"
851 << "\n";
852
853 if (m_type == TYPE_OVERWRITE)
854 {
855 // write
856
857 for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
858 {
859 if (m_storage == STORAGE_BUFFER && m_useAtomic)
860 buf << "\tatomicAdd(sb_store0.values[getIndex(gl_LocalInvocationID.xy, " << ndx << ")], 456);\n";
861 else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
862 buf << "\tsb_store0.values[getIndex(gl_LocalInvocationID.xy, " << ndx << ")] = 456;\n";
863 else if (m_storage == STORAGE_IMAGE && m_useAtomic)
864 buf << "\timageAtomicAdd(u_image0, getCoord(gl_LocalInvocationID.xy, " << ndx << "), 456);\n";
865 else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
866 buf << "\timageStore(u_image0, getCoord(gl_LocalInvocationID.xy, " << ndx
867 << "), ivec4(456, 0, 0, 0));\n";
868 else
869 DE_ASSERT(false);
870 }
871
872 // barrier
873
874 buf << genBarrierSource();
875 }
876 else
877 DE_ASSERT(m_type == TYPE_WRITE);
878
879 // write (again)
880
881 for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
882 {
883 const std::string localID = (m_syncWithGroup) ? ("(gl_LocalInvocationID.xy + uvec2(" + de::toString(ndx + 2) +
884 ", " + de::toString(2 * ndx) + ")) % gl_WorkGroupSize.xy") :
885 ("gl_LocalInvocationID.xy");
886
887 if (m_storage == STORAGE_BUFFER && m_useAtomic)
888 buf << "\tatomicExchange(sb_store1.values[getIndex(" << localID << ", " << ndx << ")], groupNdx);\n";
889 else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
890 buf << "\tsb_store1.values[getIndex(" << localID << ", " << ndx << ")] = groupNdx;\n";
891 else if (m_storage == STORAGE_IMAGE && m_useAtomic)
892 buf << "\timageAtomicExchange(u_image1, getCoord(" << localID << ", " << ndx << "), groupNdx);\n";
893 else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
894 buf << "\timageStore(u_image1, getCoord(" << localID << ", " << ndx << "), ivec4(groupNdx, 0, 0, 0));\n";
895 else
896 DE_ASSERT(false);
897 }
898
899 // barrier
900
901 buf << genBarrierSource();
902
903 // read
904
905 for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
906 {
907 if (m_storage == STORAGE_BUFFER && m_useAtomic)
908 buf << "\tallOk = allOk && (atomicExchange(sb_store0.values[getIndex(gl_LocalInvocationID.xy, " << ndx
909 << ")], 123) == groupNdx);\n";
910 else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
911 buf << "\tallOk = allOk && (sb_store0.values[getIndex(gl_LocalInvocationID.xy, " << ndx
912 << ")] == groupNdx);\n";
913 else if (m_storage == STORAGE_IMAGE && m_useAtomic)
914 buf << "\tallOk = allOk && (imageAtomicExchange(u_image0, getCoord(gl_LocalInvocationID.xy, " << ndx
915 << "), 123) == groupNdx);\n";
916 else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
917 buf << "\tallOk = allOk && (imageLoad(u_image0, getCoord(gl_LocalInvocationID.xy, " << ndx
918 << ")).x == groupNdx);\n";
919 else
920 DE_ASSERT(false);
921 }
922
923 // return result
924
925 buf << "\n"
926 << " sb_result.values[resultNdx] = (allOk) ? (1) : (0);\n"
927 << "}\n";
928
929 return specializeShader(m_context, buf.str().c_str());
930 }
931
932 namespace op
933 {
934
935 struct WriteData
936 {
937 int targetHandle;
938 int seed;
939
Generatedeqp::gles31::Functional::__anon966269800111::op::WriteData940 static WriteData Generate(int targetHandle, int seed)
941 {
942 WriteData retVal;
943
944 retVal.targetHandle = targetHandle;
945 retVal.seed = seed;
946
947 return retVal;
948 }
949 };
950
951 struct ReadData
952 {
953 int targetHandle;
954 int seed;
955
Generatedeqp::gles31::Functional::__anon966269800111::op::ReadData956 static ReadData Generate(int targetHandle, int seed)
957 {
958 ReadData retVal;
959
960 retVal.targetHandle = targetHandle;
961 retVal.seed = seed;
962
963 return retVal;
964 }
965 };
966
967 struct Barrier
968 {
969 };
970
971 struct WriteDataInterleaved
972 {
973 int targetHandle;
974 int seed;
975 bool evenOdd;
976
Generatedeqp::gles31::Functional::__anon966269800111::op::WriteDataInterleaved977 static WriteDataInterleaved Generate(int targetHandle, int seed, bool evenOdd)
978 {
979 WriteDataInterleaved retVal;
980
981 retVal.targetHandle = targetHandle;
982 retVal.seed = seed;
983 retVal.evenOdd = evenOdd;
984
985 return retVal;
986 }
987 };
988
989 struct ReadDataInterleaved
990 {
991 int targetHandle;
992 int seed0;
993 int seed1;
994
Generatedeqp::gles31::Functional::__anon966269800111::op::ReadDataInterleaved995 static ReadDataInterleaved Generate(int targetHandle, int seed0, int seed1)
996 {
997 ReadDataInterleaved retVal;
998
999 retVal.targetHandle = targetHandle;
1000 retVal.seed0 = seed0;
1001 retVal.seed1 = seed1;
1002
1003 return retVal;
1004 }
1005 };
1006
1007 struct ReadMultipleData
1008 {
1009 int targetHandle0;
1010 int seed0;
1011 int targetHandle1;
1012 int seed1;
1013
Generatedeqp::gles31::Functional::__anon966269800111::op::ReadMultipleData1014 static ReadMultipleData Generate(int targetHandle0, int seed0, int targetHandle1, int seed1)
1015 {
1016 ReadMultipleData retVal;
1017
1018 retVal.targetHandle0 = targetHandle0;
1019 retVal.seed0 = seed0;
1020 retVal.targetHandle1 = targetHandle1;
1021 retVal.seed1 = seed1;
1022
1023 return retVal;
1024 }
1025 };
1026
1027 struct ReadZeroData
1028 {
1029 int targetHandle;
1030
Generatedeqp::gles31::Functional::__anon966269800111::op::ReadZeroData1031 static ReadZeroData Generate(int targetHandle)
1032 {
1033 ReadZeroData retVal;
1034
1035 retVal.targetHandle = targetHandle;
1036
1037 return retVal;
1038 }
1039 };
1040
1041 } // namespace op
1042
1043 class InterCallTestCase;
1044
1045 class InterCallOperations
1046 {
1047 public:
1048 InterCallOperations &operator<<(const op::WriteData &);
1049 InterCallOperations &operator<<(const op::ReadData &);
1050 InterCallOperations &operator<<(const op::Barrier &);
1051 InterCallOperations &operator<<(const op::ReadMultipleData &);
1052 InterCallOperations &operator<<(const op::WriteDataInterleaved &);
1053 InterCallOperations &operator<<(const op::ReadDataInterleaved &);
1054 InterCallOperations &operator<<(const op::ReadZeroData &);
1055
1056 private:
1057 struct Command
1058 {
1059 enum CommandType
1060 {
1061 TYPE_WRITE = 0,
1062 TYPE_READ,
1063 TYPE_BARRIER,
1064 TYPE_READ_MULTIPLE,
1065 TYPE_WRITE_INTERLEAVE,
1066 TYPE_READ_INTERLEAVE,
1067 TYPE_READ_ZERO,
1068
1069 TYPE_LAST
1070 };
1071
1072 CommandType type;
1073
1074 union CommandUnion
1075 {
1076 op::WriteData write;
1077 op::ReadData read;
1078 op::Barrier barrier;
1079 op::ReadMultipleData readMulti;
1080 op::WriteDataInterleaved writeInterleave;
1081 op::ReadDataInterleaved readInterleave;
1082 op::ReadZeroData readZero;
1083 } u_cmd;
1084 };
1085
1086 friend class InterCallTestCase;
1087
1088 std::vector<Command> m_cmds;
1089 };
1090
operator <<(const op::WriteData & cmd)1091 InterCallOperations &InterCallOperations::operator<<(const op::WriteData &cmd)
1092 {
1093 m_cmds.push_back(Command());
1094 m_cmds.back().type = Command::TYPE_WRITE;
1095 m_cmds.back().u_cmd.write = cmd;
1096
1097 return *this;
1098 }
1099
operator <<(const op::ReadData & cmd)1100 InterCallOperations &InterCallOperations::operator<<(const op::ReadData &cmd)
1101 {
1102 m_cmds.push_back(Command());
1103 m_cmds.back().type = Command::TYPE_READ;
1104 m_cmds.back().u_cmd.read = cmd;
1105
1106 return *this;
1107 }
1108
operator <<(const op::Barrier & cmd)1109 InterCallOperations &InterCallOperations::operator<<(const op::Barrier &cmd)
1110 {
1111 m_cmds.push_back(Command());
1112 m_cmds.back().type = Command::TYPE_BARRIER;
1113 m_cmds.back().u_cmd.barrier = cmd;
1114
1115 return *this;
1116 }
1117
operator <<(const op::ReadMultipleData & cmd)1118 InterCallOperations &InterCallOperations::operator<<(const op::ReadMultipleData &cmd)
1119 {
1120 m_cmds.push_back(Command());
1121 m_cmds.back().type = Command::TYPE_READ_MULTIPLE;
1122 m_cmds.back().u_cmd.readMulti = cmd;
1123
1124 return *this;
1125 }
1126
operator <<(const op::WriteDataInterleaved & cmd)1127 InterCallOperations &InterCallOperations::operator<<(const op::WriteDataInterleaved &cmd)
1128 {
1129 m_cmds.push_back(Command());
1130 m_cmds.back().type = Command::TYPE_WRITE_INTERLEAVE;
1131 m_cmds.back().u_cmd.writeInterleave = cmd;
1132
1133 return *this;
1134 }
1135
operator <<(const op::ReadDataInterleaved & cmd)1136 InterCallOperations &InterCallOperations::operator<<(const op::ReadDataInterleaved &cmd)
1137 {
1138 m_cmds.push_back(Command());
1139 m_cmds.back().type = Command::TYPE_READ_INTERLEAVE;
1140 m_cmds.back().u_cmd.readInterleave = cmd;
1141
1142 return *this;
1143 }
1144
operator <<(const op::ReadZeroData & cmd)1145 InterCallOperations &InterCallOperations::operator<<(const op::ReadZeroData &cmd)
1146 {
1147 m_cmds.push_back(Command());
1148 m_cmds.back().type = Command::TYPE_READ_ZERO;
1149 m_cmds.back().u_cmd.readZero = cmd;
1150
1151 return *this;
1152 }
1153
1154 class InterCallTestCase : public TestCase
1155 {
1156 public:
1157 enum StorageType
1158 {
1159 STORAGE_BUFFER = 0,
1160 STORAGE_IMAGE,
1161
1162 STORAGE_LAST
1163 };
1164 enum Flags
1165 {
1166 FLAG_USE_ATOMIC = 1,
1167 FLAG_USE_INT = 2,
1168 };
1169 InterCallTestCase(Context &context, const char *name, const char *desc, StorageType storage, int flags,
1170 const InterCallOperations &ops);
1171 ~InterCallTestCase(void);
1172
1173 private:
1174 void init(void);
1175 void deinit(void);
1176 IterateResult iterate(void);
1177 bool verifyResults(void);
1178
1179 void runCommand(const op::WriteData &cmd, int stepNdx, int &programFriendlyName);
1180 void runCommand(const op::ReadData &cmd, int stepNdx, int &programFriendlyName, int &resultStorageFriendlyName);
1181 void runCommand(const op::Barrier &);
1182 void runCommand(const op::ReadMultipleData &cmd, int stepNdx, int &programFriendlyName,
1183 int &resultStorageFriendlyName);
1184 void runCommand(const op::WriteDataInterleaved &cmd, int stepNdx, int &programFriendlyName);
1185 void runCommand(const op::ReadDataInterleaved &cmd, int stepNdx, int &programFriendlyName,
1186 int &resultStorageFriendlyName);
1187 void runCommand(const op::ReadZeroData &cmd, int stepNdx, int &programFriendlyName, int &resultStorageFriendlyName);
1188 void runSingleRead(int targetHandle, int stepNdx, int &programFriendlyName, int &resultStorageFriendlyName);
1189
1190 glw::GLuint genStorage(int friendlyName);
1191 glw::GLuint genResultStorage(void);
1192 glu::ShaderProgram *genWriteProgram(int seed);
1193 glu::ShaderProgram *genReadProgram(int seed);
1194 glu::ShaderProgram *genReadMultipleProgram(int seed0, int seed1);
1195 glu::ShaderProgram *genWriteInterleavedProgram(int seed, bool evenOdd);
1196 glu::ShaderProgram *genReadInterleavedProgram(int seed0, int seed1);
1197 glu::ShaderProgram *genReadZeroProgram(void);
1198
1199 const StorageType m_storage;
1200 const int m_invocationGridSize; // !< width and height of the two dimensional work dispatch
1201 const int m_perInvocationSize; // !< number of elements accessed in single invocation
1202 const std::vector<InterCallOperations::Command> m_cmds;
1203 const bool m_useAtomic;
1204 const bool m_formatInteger;
1205
1206 std::vector<glu::ShaderProgram *> m_operationPrograms;
1207 std::vector<glw::GLuint> m_operationResultStorages;
1208 std::map<int, glw::GLuint> m_storageIDs;
1209 };
1210
InterCallTestCase(Context & context,const char * name,const char * desc,StorageType storage,int flags,const InterCallOperations & ops)1211 InterCallTestCase::InterCallTestCase(Context &context, const char *name, const char *desc, StorageType storage,
1212 int flags, const InterCallOperations &ops)
1213 : TestCase(context, name, desc)
1214 , m_storage(storage)
1215 , m_invocationGridSize(512)
1216 , m_perInvocationSize(2)
1217 , m_cmds(ops.m_cmds)
1218 , m_useAtomic((flags & FLAG_USE_ATOMIC) != 0)
1219 , m_formatInteger((flags & FLAG_USE_INT) != 0)
1220 {
1221 }
1222
~InterCallTestCase(void)1223 InterCallTestCase::~InterCallTestCase(void)
1224 {
1225 deinit();
1226 }
1227
init(void)1228 void InterCallTestCase::init(void)
1229 {
1230 int programFriendlyName = 0;
1231
1232 // requirements
1233
1234 if (m_useAtomic && m_storage == STORAGE_IMAGE && !checkSupport(m_context))
1235 throw tcu::NotSupportedError("Test requires GL_OES_shader_image_atomic extension");
1236
1237 // generate resources and validate command list
1238
1239 m_operationPrograms.resize(m_cmds.size(), DE_NULL);
1240 m_operationResultStorages.resize(m_cmds.size(), 0);
1241
1242 for (int step = 0; step < (int)m_cmds.size(); ++step)
1243 {
1244 switch (m_cmds[step].type)
1245 {
1246 case InterCallOperations::Command::TYPE_WRITE:
1247 {
1248 const op::WriteData &cmd = m_cmds[step].u_cmd.write;
1249
1250 // new storage handle?
1251 if (m_storageIDs.find(cmd.targetHandle) == m_storageIDs.end())
1252 m_storageIDs[cmd.targetHandle] = genStorage(cmd.targetHandle);
1253
1254 // program
1255 {
1256 glu::ShaderProgram *program = genWriteProgram(cmd.seed);
1257
1258 m_testCtx.getLog() << tcu::TestLog::Message << "Program #" << ++programFriendlyName
1259 << tcu::TestLog::EndMessage;
1260 m_testCtx.getLog() << *program;
1261
1262 if (!program->isOk())
1263 throw tcu::TestError("could not build program");
1264
1265 m_operationPrograms[step] = program;
1266 }
1267 break;
1268 }
1269
1270 case InterCallOperations::Command::TYPE_READ:
1271 {
1272 const op::ReadData &cmd = m_cmds[step].u_cmd.read;
1273 DE_ASSERT(m_storageIDs.find(cmd.targetHandle) != m_storageIDs.end());
1274
1275 // program and result storage
1276 {
1277 glu::ShaderProgram *program = genReadProgram(cmd.seed);
1278
1279 m_testCtx.getLog() << tcu::TestLog::Message << "Program #" << ++programFriendlyName
1280 << tcu::TestLog::EndMessage;
1281 m_testCtx.getLog() << *program;
1282
1283 if (!program->isOk())
1284 throw tcu::TestError("could not build program");
1285
1286 m_operationPrograms[step] = program;
1287 m_operationResultStorages[step] = genResultStorage();
1288 }
1289 break;
1290 }
1291
1292 case InterCallOperations::Command::TYPE_BARRIER:
1293 {
1294 break;
1295 }
1296
1297 case InterCallOperations::Command::TYPE_READ_MULTIPLE:
1298 {
1299 const op::ReadMultipleData &cmd = m_cmds[step].u_cmd.readMulti;
1300 DE_ASSERT(m_storageIDs.find(cmd.targetHandle0) != m_storageIDs.end());
1301 DE_ASSERT(m_storageIDs.find(cmd.targetHandle1) != m_storageIDs.end());
1302
1303 // program
1304 {
1305 glu::ShaderProgram *program = genReadMultipleProgram(cmd.seed0, cmd.seed1);
1306
1307 m_testCtx.getLog() << tcu::TestLog::Message << "Program #" << ++programFriendlyName
1308 << tcu::TestLog::EndMessage;
1309 m_testCtx.getLog() << *program;
1310
1311 if (!program->isOk())
1312 throw tcu::TestError("could not build program");
1313
1314 m_operationPrograms[step] = program;
1315 m_operationResultStorages[step] = genResultStorage();
1316 }
1317 break;
1318 }
1319
1320 case InterCallOperations::Command::TYPE_WRITE_INTERLEAVE:
1321 {
1322 const op::WriteDataInterleaved &cmd = m_cmds[step].u_cmd.writeInterleave;
1323
1324 // new storage handle?
1325 if (m_storageIDs.find(cmd.targetHandle) == m_storageIDs.end())
1326 m_storageIDs[cmd.targetHandle] = genStorage(cmd.targetHandle);
1327
1328 // program
1329 {
1330 glu::ShaderProgram *program = genWriteInterleavedProgram(cmd.seed, cmd.evenOdd);
1331
1332 m_testCtx.getLog() << tcu::TestLog::Message << "Program #" << ++programFriendlyName
1333 << tcu::TestLog::EndMessage;
1334 m_testCtx.getLog() << *program;
1335
1336 if (!program->isOk())
1337 throw tcu::TestError("could not build program");
1338
1339 m_operationPrograms[step] = program;
1340 }
1341 break;
1342 }
1343
1344 case InterCallOperations::Command::TYPE_READ_INTERLEAVE:
1345 {
1346 const op::ReadDataInterleaved &cmd = m_cmds[step].u_cmd.readInterleave;
1347 DE_ASSERT(m_storageIDs.find(cmd.targetHandle) != m_storageIDs.end());
1348
1349 // program
1350 {
1351 glu::ShaderProgram *program = genReadInterleavedProgram(cmd.seed0, cmd.seed1);
1352
1353 m_testCtx.getLog() << tcu::TestLog::Message << "Program #" << ++programFriendlyName
1354 << tcu::TestLog::EndMessage;
1355 m_testCtx.getLog() << *program;
1356
1357 if (!program->isOk())
1358 throw tcu::TestError("could not build program");
1359
1360 m_operationPrograms[step] = program;
1361 m_operationResultStorages[step] = genResultStorage();
1362 }
1363 break;
1364 }
1365
1366 case InterCallOperations::Command::TYPE_READ_ZERO:
1367 {
1368 const op::ReadZeroData &cmd = m_cmds[step].u_cmd.readZero;
1369
1370 // new storage handle?
1371 if (m_storageIDs.find(cmd.targetHandle) == m_storageIDs.end())
1372 m_storageIDs[cmd.targetHandle] = genStorage(cmd.targetHandle);
1373
1374 // program
1375 {
1376 glu::ShaderProgram *program = genReadZeroProgram();
1377
1378 m_testCtx.getLog() << tcu::TestLog::Message << "Program #" << ++programFriendlyName
1379 << tcu::TestLog::EndMessage;
1380 m_testCtx.getLog() << *program;
1381
1382 if (!program->isOk())
1383 throw tcu::TestError("could not build program");
1384
1385 m_operationPrograms[step] = program;
1386 m_operationResultStorages[step] = genResultStorage();
1387 }
1388 break;
1389 }
1390
1391 default:
1392 DE_ASSERT(false);
1393 }
1394 }
1395 }
1396
deinit(void)1397 void InterCallTestCase::deinit(void)
1398 {
1399 // programs
1400 for (int ndx = 0; ndx < (int)m_operationPrograms.size(); ++ndx)
1401 delete m_operationPrograms[ndx];
1402 m_operationPrograms.clear();
1403
1404 // result storages
1405 for (int ndx = 0; ndx < (int)m_operationResultStorages.size(); ++ndx)
1406 {
1407 if (m_operationResultStorages[ndx])
1408 m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_operationResultStorages[ndx]);
1409 }
1410 m_operationResultStorages.clear();
1411
1412 // storage
1413 for (std::map<int, glw::GLuint>::const_iterator it = m_storageIDs.begin(); it != m_storageIDs.end(); ++it)
1414 {
1415 const glw::Functions &gl = m_context.getRenderContext().getFunctions();
1416
1417 if (m_storage == STORAGE_BUFFER)
1418 gl.deleteBuffers(1, &it->second);
1419 else if (m_storage == STORAGE_IMAGE)
1420 gl.deleteTextures(1, &it->second);
1421 else
1422 DE_ASSERT(false);
1423 }
1424 m_storageIDs.clear();
1425 }
1426
iterate(void)1427 InterCallTestCase::IterateResult InterCallTestCase::iterate(void)
1428 {
1429 int programFriendlyName = 0;
1430 int resultStorageFriendlyName = 0;
1431
1432 m_testCtx.getLog() << tcu::TestLog::Message << "Running operations:" << tcu::TestLog::EndMessage;
1433
1434 // run steps
1435
1436 for (int step = 0; step < (int)m_cmds.size(); ++step)
1437 {
1438 switch (m_cmds[step].type)
1439 {
1440 case InterCallOperations::Command::TYPE_WRITE:
1441 runCommand(m_cmds[step].u_cmd.write, step, programFriendlyName);
1442 break;
1443 case InterCallOperations::Command::TYPE_READ:
1444 runCommand(m_cmds[step].u_cmd.read, step, programFriendlyName, resultStorageFriendlyName);
1445 break;
1446 case InterCallOperations::Command::TYPE_BARRIER:
1447 runCommand(m_cmds[step].u_cmd.barrier);
1448 break;
1449 case InterCallOperations::Command::TYPE_READ_MULTIPLE:
1450 runCommand(m_cmds[step].u_cmd.readMulti, step, programFriendlyName, resultStorageFriendlyName);
1451 break;
1452 case InterCallOperations::Command::TYPE_WRITE_INTERLEAVE:
1453 runCommand(m_cmds[step].u_cmd.writeInterleave, step, programFriendlyName);
1454 break;
1455 case InterCallOperations::Command::TYPE_READ_INTERLEAVE:
1456 runCommand(m_cmds[step].u_cmd.readInterleave, step, programFriendlyName, resultStorageFriendlyName);
1457 break;
1458 case InterCallOperations::Command::TYPE_READ_ZERO:
1459 runCommand(m_cmds[step].u_cmd.readZero, step, programFriendlyName, resultStorageFriendlyName);
1460 break;
1461 default:
1462 DE_ASSERT(false);
1463 }
1464 }
1465
1466 // read results from result buffers
1467 if (verifyResults())
1468 m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
1469 else
1470 m_testCtx.setTestResult(
1471 QP_TEST_RESULT_FAIL,
1472 (std::string((m_storage == STORAGE_BUFFER) ? ("buffer") : ("image")) + " content verification failed")
1473 .c_str());
1474
1475 return STOP;
1476 }
1477
verifyResults(void)1478 bool InterCallTestCase::verifyResults(void)
1479 {
1480 int resultBufferFriendlyName = 0;
1481 bool allResultsOk = true;
1482 bool anyResult = false;
1483
1484 m_testCtx.getLog() << tcu::TestLog::Message << "Reading verifier program results" << tcu::TestLog::EndMessage;
1485
1486 for (int step = 0; step < (int)m_cmds.size(); ++step)
1487 {
1488 const int errorFloodThreshold = 5;
1489 int numErrorsLogged = 0;
1490
1491 if (m_operationResultStorages[step])
1492 {
1493 const glw::Functions &gl = m_context.getRenderContext().getFunctions();
1494 const void *mapped = DE_NULL;
1495 std::vector<int32_t> results(m_invocationGridSize * m_invocationGridSize);
1496 bool error = false;
1497
1498 anyResult = true;
1499
1500 gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_operationResultStorages[step]);
1501 mapped = gl.mapBufferRange(GL_SHADER_STORAGE_BUFFER, 0,
1502 m_invocationGridSize * m_invocationGridSize * sizeof(uint32_t), GL_MAP_READ_BIT);
1503 GLU_EXPECT_NO_ERROR(gl.getError(), "map buffer");
1504
1505 // copy to properly aligned array
1506 deMemcpy(&results[0], mapped, m_invocationGridSize * m_invocationGridSize * sizeof(uint32_t));
1507
1508 if (gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER) != GL_TRUE)
1509 throw tcu::TestError("memory map store corrupted");
1510
1511 // check the results
1512 for (int ndx = 0; ndx < (int)results.size(); ++ndx)
1513 {
1514 if (results[ndx] != 1)
1515 {
1516 error = true;
1517
1518 if (numErrorsLogged == 0)
1519 m_testCtx.getLog() << tcu::TestLog::Message << "Result storage #" << ++resultBufferFriendlyName
1520 << " failed, got unexpected values.\n"
1521 << tcu::TestLog::EndMessage;
1522 if (numErrorsLogged++ < errorFloodThreshold)
1523 m_testCtx.getLog() << tcu::TestLog::Message << " Error at index " << ndx
1524 << ": expected 1, got " << results[ndx] << ".\n"
1525 << tcu::TestLog::EndMessage;
1526 else
1527 {
1528 // after N errors, no point continuing verification
1529 m_testCtx.getLog()
1530 << tcu::TestLog::Message << " -- too many errors, skipping verification --\n"
1531 << tcu::TestLog::EndMessage;
1532 break;
1533 }
1534 }
1535 }
1536
1537 if (error)
1538 {
1539 allResultsOk = false;
1540 }
1541 else
1542 m_testCtx.getLog() << tcu::TestLog::Message << "Result storage #" << ++resultBufferFriendlyName
1543 << " ok." << tcu::TestLog::EndMessage;
1544 }
1545 }
1546
1547 DE_ASSERT(anyResult);
1548 DE_UNREF(anyResult);
1549
1550 return allResultsOk;
1551 }
1552
runCommand(const op::WriteData & cmd,int stepNdx,int & programFriendlyName)1553 void InterCallTestCase::runCommand(const op::WriteData &cmd, int stepNdx, int &programFriendlyName)
1554 {
1555 const glw::Functions &gl = m_context.getRenderContext().getFunctions();
1556
1557 m_testCtx.getLog() << tcu::TestLog::Message << "Running program #" << ++programFriendlyName << " to write "
1558 << ((m_storage == STORAGE_BUFFER) ? ("buffer") : ("image")) << " #" << cmd.targetHandle << ".\n"
1559 << " Dispatch size: " << m_invocationGridSize << "x" << m_invocationGridSize << "."
1560 << tcu::TestLog::EndMessage;
1561
1562 gl.useProgram(m_operationPrograms[stepNdx]->getProgram());
1563
1564 // set destination
1565 if (m_storage == STORAGE_BUFFER)
1566 {
1567 DE_ASSERT(m_storageIDs[cmd.targetHandle]);
1568
1569 gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_storageIDs[cmd.targetHandle]);
1570 GLU_EXPECT_NO_ERROR(gl.getError(), "bind destination buffer");
1571 }
1572 else if (m_storage == STORAGE_IMAGE)
1573 {
1574 DE_ASSERT(m_storageIDs[cmd.targetHandle]);
1575
1576 gl.bindImageTexture(0, m_storageIDs[cmd.targetHandle], 0, GL_FALSE, 0,
1577 (m_useAtomic) ? (GL_READ_WRITE) : (GL_WRITE_ONLY),
1578 (m_formatInteger) ? (GL_R32I) : (GL_R32F));
1579 GLU_EXPECT_NO_ERROR(gl.getError(), "bind destination image");
1580 }
1581 else
1582 DE_ASSERT(false);
1583
1584 // calc
1585 gl.dispatchCompute(m_invocationGridSize, m_invocationGridSize, 1);
1586 GLU_EXPECT_NO_ERROR(gl.getError(), "dispatch write");
1587 }
1588
runCommand(const op::ReadData & cmd,int stepNdx,int & programFriendlyName,int & resultStorageFriendlyName)1589 void InterCallTestCase::runCommand(const op::ReadData &cmd, int stepNdx, int &programFriendlyName,
1590 int &resultStorageFriendlyName)
1591 {
1592 runSingleRead(cmd.targetHandle, stepNdx, programFriendlyName, resultStorageFriendlyName);
1593 }
1594
runCommand(const op::Barrier & cmd)1595 void InterCallTestCase::runCommand(const op::Barrier &cmd)
1596 {
1597 const glw::Functions &gl = m_context.getRenderContext().getFunctions();
1598
1599 DE_UNREF(cmd);
1600
1601 if (m_storage == STORAGE_BUFFER)
1602 {
1603 m_testCtx.getLog() << tcu::TestLog::Message << "Memory Barrier\n\tbits = GL_SHADER_STORAGE_BARRIER_BIT"
1604 << tcu::TestLog::EndMessage;
1605 gl.memoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
1606 }
1607 else if (m_storage == STORAGE_IMAGE)
1608 {
1609 m_testCtx.getLog() << tcu::TestLog::Message << "Memory Barrier\n\tbits = GL_SHADER_IMAGE_ACCESS_BARRIER_BIT"
1610 << tcu::TestLog::EndMessage;
1611 gl.memoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
1612 }
1613 else
1614 DE_ASSERT(false);
1615 }
1616
runCommand(const op::ReadMultipleData & cmd,int stepNdx,int & programFriendlyName,int & resultStorageFriendlyName)1617 void InterCallTestCase::runCommand(const op::ReadMultipleData &cmd, int stepNdx, int &programFriendlyName,
1618 int &resultStorageFriendlyName)
1619 {
1620 const glw::Functions &gl = m_context.getRenderContext().getFunctions();
1621
1622 m_testCtx.getLog() << tcu::TestLog::Message << "Running program #" << ++programFriendlyName << " to verify "
1623 << ((m_storage == STORAGE_BUFFER) ? ("buffers") : ("images")) << " #" << cmd.targetHandle0
1624 << " and #" << cmd.targetHandle1 << ".\n"
1625 << " Writing results to result storage #" << ++resultStorageFriendlyName << ".\n"
1626 << " Dispatch size: " << m_invocationGridSize << "x" << m_invocationGridSize << "."
1627 << tcu::TestLog::EndMessage;
1628
1629 gl.useProgram(m_operationPrograms[stepNdx]->getProgram());
1630
1631 // set sources
1632 if (m_storage == STORAGE_BUFFER)
1633 {
1634 DE_ASSERT(m_storageIDs[cmd.targetHandle0]);
1635 DE_ASSERT(m_storageIDs[cmd.targetHandle1]);
1636
1637 gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_storageIDs[cmd.targetHandle0]);
1638 gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, m_storageIDs[cmd.targetHandle1]);
1639 GLU_EXPECT_NO_ERROR(gl.getError(), "bind source buffers");
1640 }
1641 else if (m_storage == STORAGE_IMAGE)
1642 {
1643 DE_ASSERT(m_storageIDs[cmd.targetHandle0]);
1644 DE_ASSERT(m_storageIDs[cmd.targetHandle1]);
1645
1646 gl.bindImageTexture(1, m_storageIDs[cmd.targetHandle0], 0, GL_FALSE, 0,
1647 (m_useAtomic) ? (GL_READ_WRITE) : (GL_READ_ONLY),
1648 (m_formatInteger) ? (GL_R32I) : (GL_R32F));
1649 gl.bindImageTexture(2, m_storageIDs[cmd.targetHandle1], 0, GL_FALSE, 0,
1650 (m_useAtomic) ? (GL_READ_WRITE) : (GL_READ_ONLY),
1651 (m_formatInteger) ? (GL_R32I) : (GL_R32F));
1652 GLU_EXPECT_NO_ERROR(gl.getError(), "bind source images");
1653 }
1654 else
1655 DE_ASSERT(false);
1656
1657 // set destination
1658 DE_ASSERT(m_operationResultStorages[stepNdx]);
1659 gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_operationResultStorages[stepNdx]);
1660 GLU_EXPECT_NO_ERROR(gl.getError(), "bind result buffer");
1661
1662 // calc
1663 gl.dispatchCompute(m_invocationGridSize, m_invocationGridSize, 1);
1664 GLU_EXPECT_NO_ERROR(gl.getError(), "dispatch read multi");
1665 }
1666
runCommand(const op::WriteDataInterleaved & cmd,int stepNdx,int & programFriendlyName)1667 void InterCallTestCase::runCommand(const op::WriteDataInterleaved &cmd, int stepNdx, int &programFriendlyName)
1668 {
1669 const glw::Functions &gl = m_context.getRenderContext().getFunctions();
1670
1671 m_testCtx.getLog() << tcu::TestLog::Message << "Running program #" << ++programFriendlyName << " to write "
1672 << ((m_storage == STORAGE_BUFFER) ? ("buffer") : ("image")) << " #" << cmd.targetHandle << ".\n"
1673 << " Writing to every " << ((cmd.evenOdd) ? ("even") : ("odd")) << " "
1674 << ((m_storage == STORAGE_BUFFER) ? ("element") : ("column")) << ".\n"
1675 << " Dispatch size: " << m_invocationGridSize / 2 << "x" << m_invocationGridSize << "."
1676 << tcu::TestLog::EndMessage;
1677
1678 gl.useProgram(m_operationPrograms[stepNdx]->getProgram());
1679
1680 // set destination
1681 if (m_storage == STORAGE_BUFFER)
1682 {
1683 DE_ASSERT(m_storageIDs[cmd.targetHandle]);
1684
1685 gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_storageIDs[cmd.targetHandle]);
1686 GLU_EXPECT_NO_ERROR(gl.getError(), "bind destination buffer");
1687 }
1688 else if (m_storage == STORAGE_IMAGE)
1689 {
1690 DE_ASSERT(m_storageIDs[cmd.targetHandle]);
1691
1692 gl.bindImageTexture(0, m_storageIDs[cmd.targetHandle], 0, GL_FALSE, 0,
1693 (m_useAtomic) ? (GL_READ_WRITE) : (GL_WRITE_ONLY),
1694 (m_formatInteger) ? (GL_R32I) : (GL_R32F));
1695 GLU_EXPECT_NO_ERROR(gl.getError(), "bind destination image");
1696 }
1697 else
1698 DE_ASSERT(false);
1699
1700 // calc
1701 gl.dispatchCompute(m_invocationGridSize / 2, m_invocationGridSize, 1);
1702 GLU_EXPECT_NO_ERROR(gl.getError(), "dispatch write");
1703 }
1704
runCommand(const op::ReadDataInterleaved & cmd,int stepNdx,int & programFriendlyName,int & resultStorageFriendlyName)1705 void InterCallTestCase::runCommand(const op::ReadDataInterleaved &cmd, int stepNdx, int &programFriendlyName,
1706 int &resultStorageFriendlyName)
1707 {
1708 runSingleRead(cmd.targetHandle, stepNdx, programFriendlyName, resultStorageFriendlyName);
1709 }
1710
runCommand(const op::ReadZeroData & cmd,int stepNdx,int & programFriendlyName,int & resultStorageFriendlyName)1711 void InterCallTestCase::runCommand(const op::ReadZeroData &cmd, int stepNdx, int &programFriendlyName,
1712 int &resultStorageFriendlyName)
1713 {
1714 runSingleRead(cmd.targetHandle, stepNdx, programFriendlyName, resultStorageFriendlyName);
1715 }
1716
runSingleRead(int targetHandle,int stepNdx,int & programFriendlyName,int & resultStorageFriendlyName)1717 void InterCallTestCase::runSingleRead(int targetHandle, int stepNdx, int &programFriendlyName,
1718 int &resultStorageFriendlyName)
1719 {
1720 const glw::Functions &gl = m_context.getRenderContext().getFunctions();
1721
1722 m_testCtx.getLog() << tcu::TestLog::Message << "Running program #" << ++programFriendlyName << " to verify "
1723 << ((m_storage == STORAGE_BUFFER) ? ("buffer") : ("image")) << " #" << targetHandle << ".\n"
1724 << " Writing results to result storage #" << ++resultStorageFriendlyName << ".\n"
1725 << " Dispatch size: " << m_invocationGridSize << "x" << m_invocationGridSize << "."
1726 << tcu::TestLog::EndMessage;
1727
1728 gl.useProgram(m_operationPrograms[stepNdx]->getProgram());
1729
1730 // set source
1731 if (m_storage == STORAGE_BUFFER)
1732 {
1733 DE_ASSERT(m_storageIDs[targetHandle]);
1734
1735 gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_storageIDs[targetHandle]);
1736 GLU_EXPECT_NO_ERROR(gl.getError(), "bind source buffer");
1737 }
1738 else if (m_storage == STORAGE_IMAGE)
1739 {
1740 DE_ASSERT(m_storageIDs[targetHandle]);
1741
1742 gl.bindImageTexture(1, m_storageIDs[targetHandle], 0, GL_FALSE, 0,
1743 (m_useAtomic) ? (GL_READ_WRITE) : (GL_READ_ONLY),
1744 (m_formatInteger) ? (GL_R32I) : (GL_R32F));
1745 GLU_EXPECT_NO_ERROR(gl.getError(), "bind source image");
1746 }
1747 else
1748 DE_ASSERT(false);
1749
1750 // set destination
1751 DE_ASSERT(m_operationResultStorages[stepNdx]);
1752 gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_operationResultStorages[stepNdx]);
1753 GLU_EXPECT_NO_ERROR(gl.getError(), "bind result buffer");
1754
1755 // calc
1756 gl.dispatchCompute(m_invocationGridSize, m_invocationGridSize, 1);
1757 GLU_EXPECT_NO_ERROR(gl.getError(), "dispatch read");
1758 }
1759
genStorage(int friendlyName)1760 glw::GLuint InterCallTestCase::genStorage(int friendlyName)
1761 {
1762 const glw::Functions &gl = m_context.getRenderContext().getFunctions();
1763
1764 if (m_storage == STORAGE_BUFFER)
1765 {
1766 const int numElements = m_invocationGridSize * m_invocationGridSize * m_perInvocationSize;
1767 const int bufferSize = numElements * (int)((m_formatInteger) ? (sizeof(int32_t)) : (sizeof(glw::GLfloat)));
1768 glw::GLuint retVal = 0;
1769
1770 m_testCtx.getLog() << tcu::TestLog::Message << "Creating buffer #" << friendlyName << ", size " << bufferSize
1771 << " bytes." << tcu::TestLog::EndMessage;
1772
1773 gl.genBuffers(1, &retVal);
1774 gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, retVal);
1775
1776 if (m_formatInteger)
1777 {
1778 const std::vector<uint32_t> zeroBuffer(numElements, 0);
1779 gl.bufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, &zeroBuffer[0], GL_STATIC_DRAW);
1780 }
1781 else
1782 {
1783 const std::vector<float> zeroBuffer(numElements, 0.0f);
1784 gl.bufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, &zeroBuffer[0], GL_STATIC_DRAW);
1785 }
1786 GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffer");
1787
1788 return retVal;
1789 }
1790 else if (m_storage == STORAGE_IMAGE)
1791 {
1792 const int imageWidth = m_invocationGridSize;
1793 const int imageHeight = m_invocationGridSize * m_perInvocationSize;
1794 glw::GLuint retVal = 0;
1795
1796 m_testCtx.getLog() << tcu::TestLog::Message << "Creating image #" << friendlyName << ", size " << imageWidth
1797 << "x" << imageHeight << ", internalformat = " << ((m_formatInteger) ? ("r32i") : ("r32f"))
1798 << ", size = " << (imageWidth * imageHeight * sizeof(uint32_t)) << " bytes."
1799 << tcu::TestLog::EndMessage;
1800
1801 gl.genTextures(1, &retVal);
1802 gl.bindTexture(GL_TEXTURE_2D, retVal);
1803
1804 if (m_formatInteger)
1805 gl.texStorage2D(GL_TEXTURE_2D, 1, GL_R32I, imageWidth, imageHeight);
1806 else
1807 gl.texStorage2D(GL_TEXTURE_2D, 1, GL_R32F, imageWidth, imageHeight);
1808
1809 gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
1810 gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
1811 GLU_EXPECT_NO_ERROR(gl.getError(), "gen image");
1812
1813 m_testCtx.getLog() << tcu::TestLog::Message << "Filling image with 0" << tcu::TestLog::EndMessage;
1814
1815 if (m_formatInteger)
1816 {
1817 const std::vector<int32_t> zeroBuffer(imageWidth * imageHeight, 0);
1818 gl.texSubImage2D(GL_TEXTURE_2D, 0, 0, 0, imageWidth, imageHeight, GL_RED_INTEGER, GL_INT, &zeroBuffer[0]);
1819 }
1820 else
1821 {
1822 const std::vector<float> zeroBuffer(imageWidth * imageHeight, 0.0f);
1823 gl.texSubImage2D(GL_TEXTURE_2D, 0, 0, 0, imageWidth, imageHeight, GL_RED, GL_FLOAT, &zeroBuffer[0]);
1824 }
1825
1826 GLU_EXPECT_NO_ERROR(gl.getError(), "specify image contents");
1827
1828 return retVal;
1829 }
1830 else
1831 {
1832 DE_ASSERT(false);
1833 return 0;
1834 }
1835 }
1836
genResultStorage(void)1837 glw::GLuint InterCallTestCase::genResultStorage(void)
1838 {
1839 const glw::Functions &gl = m_context.getRenderContext().getFunctions();
1840 glw::GLuint retVal = 0;
1841
1842 gl.genBuffers(1, &retVal);
1843 gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, retVal);
1844 gl.bufferData(GL_SHADER_STORAGE_BUFFER, m_invocationGridSize * m_invocationGridSize * sizeof(uint32_t), DE_NULL,
1845 GL_STATIC_DRAW);
1846 GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffer");
1847
1848 return retVal;
1849 }
1850
genWriteProgram(int seed)1851 glu::ShaderProgram *InterCallTestCase::genWriteProgram(int seed)
1852 {
1853 const bool useImageAtomics = m_useAtomic && m_storage == STORAGE_IMAGE;
1854 std::ostringstream buf;
1855
1856 buf << "${GLSL_VERSION_DECL}\n"
1857 << ((useImageAtomics) ? ("${SHADER_IMAGE_ATOMIC_REQUIRE}\n") : (""))
1858 << "layout (local_size_x = 1, local_size_y = 1) in;\n";
1859
1860 if (m_storage == STORAGE_BUFFER)
1861 buf << "layout(binding=0, std430) " << ((m_useAtomic) ? ("coherent ") : ("")) << "buffer Buffer\n"
1862 << "{\n"
1863 << " highp " << ((m_formatInteger) ? ("int") : ("float")) << " values[];\n"
1864 << "} sb_out;\n";
1865 else if (m_storage == STORAGE_IMAGE)
1866 buf << "layout(" << ((m_formatInteger) ? ("r32i") : ("r32f")) << ", binding=0) "
1867 << ((m_useAtomic) ? ("coherent ") : ("writeonly ")) << "uniform highp "
1868 << ((m_formatInteger) ? ("iimage2D") : ("image2D")) << " u_imageOut;\n";
1869 else
1870 DE_ASSERT(false);
1871
1872 buf << "\n"
1873 << "void main (void)\n"
1874 << "{\n"
1875 << " uvec3 size = gl_NumWorkGroups * gl_WorkGroupSize;\n"
1876 << " int groupNdx = int(size.x * size.y * gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + "
1877 "gl_GlobalInvocationID.x);\n"
1878 << "\n";
1879
1880 // Write to buffer/image m_perInvocationSize elements
1881 if (m_storage == STORAGE_BUFFER)
1882 {
1883 for (int writeNdx = 0; writeNdx < m_perInvocationSize; ++writeNdx)
1884 {
1885 if (m_useAtomic)
1886 buf << " atomicExchange(";
1887 else
1888 buf << " ";
1889
1890 buf << "sb_out.values[(groupNdx + " << seed + writeNdx * m_invocationGridSize * m_invocationGridSize
1891 << ") % " << m_invocationGridSize * m_invocationGridSize * m_perInvocationSize << "]";
1892
1893 if (m_useAtomic)
1894 buf << ", " << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx));\n";
1895 else
1896 buf << " = " << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx);\n";
1897 }
1898 }
1899 else if (m_storage == STORAGE_IMAGE)
1900 {
1901 for (int writeNdx = 0; writeNdx < m_perInvocationSize; ++writeNdx)
1902 {
1903 if (m_useAtomic)
1904 buf << " imageAtomicExchange";
1905 else
1906 buf << " imageStore";
1907
1908 buf << "(u_imageOut, ivec2((int(gl_GlobalInvocationID.x) + " << (seed + writeNdx * 100) << ") % "
1909 << m_invocationGridSize << ", int(gl_GlobalInvocationID.y) + " << writeNdx * m_invocationGridSize
1910 << "), ";
1911
1912 if (m_useAtomic)
1913 buf << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx));\n";
1914 else
1915 buf << ((m_formatInteger) ? ("ivec4(int(groupNdx), 0, 0, 0)") :
1916 ("vec4(float(groupNdx), 0.0, 0.0, 0.0)"))
1917 << ");\n";
1918 }
1919 }
1920 else
1921 DE_ASSERT(false);
1922
1923 buf << "}\n";
1924
1925 return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(
1926 specializeShader(m_context, buf.str().c_str())));
1927 }
1928
genReadProgram(int seed)1929 glu::ShaderProgram *InterCallTestCase::genReadProgram(int seed)
1930 {
1931 const bool useImageAtomics = m_useAtomic && m_storage == STORAGE_IMAGE;
1932 std::ostringstream buf;
1933
1934 buf << "${GLSL_VERSION_DECL}\n"
1935 << ((useImageAtomics) ? ("${SHADER_IMAGE_ATOMIC_REQUIRE}\n") : (""))
1936 << "layout (local_size_x = 1, local_size_y = 1) in;\n";
1937
1938 if (m_storage == STORAGE_BUFFER)
1939 buf << "layout(binding=1, std430) " << ((m_useAtomic) ? ("coherent ") : ("")) << "buffer Buffer\n"
1940 << "{\n"
1941 << " highp " << ((m_formatInteger) ? ("int") : ("float")) << " values[];\n"
1942 << "} sb_in;\n";
1943 else if (m_storage == STORAGE_IMAGE)
1944 buf << "layout(" << ((m_formatInteger) ? ("r32i") : ("r32f")) << ", binding=1) "
1945 << ((m_useAtomic) ? ("coherent ") : ("readonly ")) << "uniform highp "
1946 << ((m_formatInteger) ? ("iimage2D") : ("image2D")) << " u_imageIn;\n";
1947 else
1948 DE_ASSERT(false);
1949
1950 buf << "layout(binding=0, std430) buffer ResultBuffer\n"
1951 << "{\n"
1952 << " highp int resultOk[];\n"
1953 << "} sb_result;\n"
1954 << "\n"
1955 << "void main (void)\n"
1956 << "{\n"
1957 << " uvec3 size = gl_NumWorkGroups * gl_WorkGroupSize;\n"
1958 << " int groupNdx = int(size.x * size.y * gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + "
1959 "gl_GlobalInvocationID.x);\n"
1960 << " " << ((m_formatInteger) ? ("int") : ("float")) << " zero = " << ((m_formatInteger) ? ("0") : ("0.0"))
1961 << ";\n"
1962 << " bool allOk = true;\n"
1963 << "\n";
1964
1965 // Verify data
1966
1967 if (m_storage == STORAGE_BUFFER)
1968 {
1969 for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
1970 {
1971 if (!m_useAtomic)
1972 buf << " allOk = allOk && (sb_in.values[(groupNdx + "
1973 << seed + readNdx * m_invocationGridSize * m_invocationGridSize << ") % "
1974 << m_invocationGridSize * m_invocationGridSize * m_perInvocationSize
1975 << "] == " << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx));\n";
1976 else
1977 buf << " allOk = allOk && (atomicExchange(sb_in.values[(groupNdx + "
1978 << seed + readNdx * m_invocationGridSize * m_invocationGridSize << ") % "
1979 << m_invocationGridSize * m_invocationGridSize * m_perInvocationSize
1980 << "], zero) == " << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx));\n";
1981 }
1982 }
1983 else if (m_storage == STORAGE_IMAGE)
1984 {
1985 for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
1986 {
1987 if (!m_useAtomic)
1988 buf << " allOk = allOk && (imageLoad(u_imageIn, ivec2((gl_GlobalInvocationID.x + "
1989 << (seed + readNdx * 100) << "u) % " << m_invocationGridSize << "u, gl_GlobalInvocationID.y + "
1990 << readNdx * m_invocationGridSize << "u)).x == " << ((m_formatInteger) ? ("int") : ("float"))
1991 << "(groupNdx));\n";
1992 else
1993 buf << " allOk = allOk && (imageAtomicExchange(u_imageIn, ivec2((gl_GlobalInvocationID.x + "
1994 << (seed + readNdx * 100) << "u) % " << m_invocationGridSize << "u, gl_GlobalInvocationID.y + "
1995 << readNdx * m_invocationGridSize << "u), zero) == " << ((m_formatInteger) ? ("int") : ("float"))
1996 << "(groupNdx));\n";
1997 }
1998 }
1999 else
2000 DE_ASSERT(false);
2001
2002 buf << " sb_result.resultOk[groupNdx] = (allOk) ? (1) : (0);\n"
2003 << "}\n";
2004
2005 return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(
2006 specializeShader(m_context, buf.str().c_str())));
2007 }
2008
genReadMultipleProgram(int seed0,int seed1)2009 glu::ShaderProgram *InterCallTestCase::genReadMultipleProgram(int seed0, int seed1)
2010 {
2011 const bool useImageAtomics = m_useAtomic && m_storage == STORAGE_IMAGE;
2012 std::ostringstream buf;
2013
2014 buf << "${GLSL_VERSION_DECL}\n"
2015 << ((useImageAtomics) ? ("${SHADER_IMAGE_ATOMIC_REQUIRE}\n") : (""))
2016 << "layout (local_size_x = 1, local_size_y = 1) in;\n";
2017
2018 if (m_storage == STORAGE_BUFFER)
2019 buf << "layout(binding=1, std430) " << ((m_useAtomic) ? ("coherent ") : ("")) << "buffer Buffer0\n"
2020 << "{\n"
2021 << " highp " << ((m_formatInteger) ? ("int") : ("float")) << " values[];\n"
2022 << "} sb_in0;\n"
2023 << "layout(binding=2, std430) " << ((m_useAtomic) ? ("coherent ") : ("")) << "buffer Buffer1\n"
2024 << "{\n"
2025 << " highp " << ((m_formatInteger) ? ("int") : ("float")) << " values[];\n"
2026 << "} sb_in1;\n";
2027 else if (m_storage == STORAGE_IMAGE)
2028 buf << "layout(" << ((m_formatInteger) ? ("r32i") : ("r32f")) << ", binding=1) "
2029 << ((m_useAtomic) ? ("coherent ") : ("readonly ")) << "uniform highp "
2030 << ((m_formatInteger) ? ("iimage2D") : ("image2D")) << " u_imageIn0;\n"
2031 << "layout(" << ((m_formatInteger) ? ("r32i") : ("r32f")) << ", binding=2) "
2032 << ((m_useAtomic) ? ("coherent ") : ("readonly ")) << "uniform highp "
2033 << ((m_formatInteger) ? ("iimage2D") : ("image2D")) << " u_imageIn1;\n";
2034 else
2035 DE_ASSERT(false);
2036
2037 buf << "layout(binding=0, std430) buffer ResultBuffer\n"
2038 << "{\n"
2039 << " highp int resultOk[];\n"
2040 << "} sb_result;\n"
2041 << "\n"
2042 << "void main (void)\n"
2043 << "{\n"
2044 << " uvec3 size = gl_NumWorkGroups * gl_WorkGroupSize;\n"
2045 << " int groupNdx = int(size.x * size.y * gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + "
2046 "gl_GlobalInvocationID.x);\n"
2047 << " " << ((m_formatInteger) ? ("int") : ("float")) << " zero = " << ((m_formatInteger) ? ("0") : ("0.0"))
2048 << ";\n"
2049 << " bool allOk = true;\n"
2050 << "\n";
2051
2052 // Verify data
2053
2054 if (m_storage == STORAGE_BUFFER)
2055 {
2056 for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
2057 buf << " allOk = allOk && (" << ((m_useAtomic) ? ("atomicExchange(") : (""))
2058 << "sb_in0.values[(groupNdx + " << seed0 + readNdx * m_invocationGridSize * m_invocationGridSize
2059 << ") % " << m_invocationGridSize * m_invocationGridSize * m_perInvocationSize << "]"
2060 << ((m_useAtomic) ? (", zero)") : ("")) << " == " << ((m_formatInteger) ? ("int") : ("float"))
2061 << "(groupNdx));\n"
2062 << " allOk = allOk && (" << ((m_useAtomic) ? ("atomicExchange(") : (""))
2063 << "sb_in1.values[(groupNdx + " << seed1 + readNdx * m_invocationGridSize * m_invocationGridSize
2064 << ") % " << m_invocationGridSize * m_invocationGridSize * m_perInvocationSize << "]"
2065 << ((m_useAtomic) ? (", zero)") : ("")) << " == " << ((m_formatInteger) ? ("int") : ("float"))
2066 << "(groupNdx));\n";
2067 }
2068 else if (m_storage == STORAGE_IMAGE)
2069 {
2070 for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
2071 buf << " allOk = allOk && (" << ((m_useAtomic) ? ("imageAtomicExchange") : ("imageLoad"))
2072 << "(u_imageIn0, ivec2((gl_GlobalInvocationID.x + " << (seed0 + readNdx * 100) << "u) % "
2073 << m_invocationGridSize << "u, gl_GlobalInvocationID.y + " << readNdx * m_invocationGridSize << "u)"
2074 << ((m_useAtomic) ? (", zero)") : (").x")) << " == " << ((m_formatInteger) ? ("int") : ("float"))
2075 << "(groupNdx));\n"
2076 << " allOk = allOk && (" << ((m_useAtomic) ? ("imageAtomicExchange") : ("imageLoad"))
2077 << "(u_imageIn1, ivec2((gl_GlobalInvocationID.x + " << (seed1 + readNdx * 100) << "u) % "
2078 << m_invocationGridSize << "u, gl_GlobalInvocationID.y + " << readNdx * m_invocationGridSize << "u)"
2079 << ((m_useAtomic) ? (", zero)") : (").x")) << " == " << ((m_formatInteger) ? ("int") : ("float"))
2080 << "(groupNdx));\n";
2081 }
2082 else
2083 DE_ASSERT(false);
2084
2085 buf << " sb_result.resultOk[groupNdx] = (allOk) ? (1) : (0);\n"
2086 << "}\n";
2087
2088 return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(
2089 specializeShader(m_context, buf.str().c_str())));
2090 }
2091
genWriteInterleavedProgram(int seed,bool evenOdd)2092 glu::ShaderProgram *InterCallTestCase::genWriteInterleavedProgram(int seed, bool evenOdd)
2093 {
2094 const bool useImageAtomics = m_useAtomic && m_storage == STORAGE_IMAGE;
2095 std::ostringstream buf;
2096
2097 buf << "${GLSL_VERSION_DECL}\n"
2098 << ((useImageAtomics) ? ("${SHADER_IMAGE_ATOMIC_REQUIRE}\n") : (""))
2099 << "layout (local_size_x = 1, local_size_y = 1) in;\n";
2100
2101 if (m_storage == STORAGE_BUFFER)
2102 buf << "layout(binding=0, std430) " << ((m_useAtomic) ? ("coherent ") : ("")) << "buffer Buffer\n"
2103 << "{\n"
2104 << " highp " << ((m_formatInteger) ? ("int") : ("float")) << " values[];\n"
2105 << "} sb_out;\n";
2106 else if (m_storage == STORAGE_IMAGE)
2107 buf << "layout(" << ((m_formatInteger) ? ("r32i") : ("r32f")) << ", binding=0) "
2108 << ((m_useAtomic) ? ("coherent ") : ("writeonly ")) << "uniform highp "
2109 << ((m_formatInteger) ? ("iimage2D") : ("image2D")) << " u_imageOut;\n";
2110 else
2111 DE_ASSERT(false);
2112
2113 buf << "\n"
2114 << "void main (void)\n"
2115 << "{\n"
2116 << " uvec3 size = gl_NumWorkGroups * gl_WorkGroupSize;\n"
2117 << " int groupNdx = int(size.x * size.y * gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + "
2118 "gl_GlobalInvocationID.x);\n"
2119 << "\n";
2120
2121 // Write to buffer/image m_perInvocationSize elements
2122 if (m_storage == STORAGE_BUFFER)
2123 {
2124 for (int writeNdx = 0; writeNdx < m_perInvocationSize; ++writeNdx)
2125 {
2126 if (m_useAtomic)
2127 buf << " atomicExchange(";
2128 else
2129 buf << " ";
2130
2131 buf << "sb_out.values[((groupNdx + " << seed + writeNdx * m_invocationGridSize * m_invocationGridSize / 2
2132 << ") % " << m_invocationGridSize * m_invocationGridSize / 2 * m_perInvocationSize << ") * 2 + "
2133 << ((evenOdd) ? (0) : (1)) << "]";
2134
2135 if (m_useAtomic)
2136 buf << ", " << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx));\n";
2137 else
2138 buf << "= " << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx);\n";
2139 }
2140 }
2141 else if (m_storage == STORAGE_IMAGE)
2142 {
2143 for (int writeNdx = 0; writeNdx < m_perInvocationSize; ++writeNdx)
2144 {
2145 if (m_useAtomic)
2146 buf << " imageAtomicExchange";
2147 else
2148 buf << " imageStore";
2149
2150 buf << "(u_imageOut, ivec2(((int(gl_GlobalInvocationID.x) + " << (seed + writeNdx * 100) << ") % "
2151 << m_invocationGridSize / 2 << ") * 2 + " << ((evenOdd) ? (0) : (1))
2152 << ", int(gl_GlobalInvocationID.y) + " << writeNdx * m_invocationGridSize << "), ";
2153
2154 if (m_useAtomic)
2155 buf << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx));\n";
2156 else
2157 buf << ((m_formatInteger) ? ("ivec4(int(groupNdx), 0, 0, 0)") :
2158 ("vec4(float(groupNdx), 0.0, 0.0, 0.0)"))
2159 << ");\n";
2160 }
2161 }
2162 else
2163 DE_ASSERT(false);
2164
2165 buf << "}\n";
2166
2167 return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(
2168 specializeShader(m_context, buf.str().c_str())));
2169 }
2170
genReadInterleavedProgram(int seed0,int seed1)2171 glu::ShaderProgram *InterCallTestCase::genReadInterleavedProgram(int seed0, int seed1)
2172 {
2173 const bool useImageAtomics = m_useAtomic && m_storage == STORAGE_IMAGE;
2174 std::ostringstream buf;
2175
2176 buf << "${GLSL_VERSION_DECL}\n"
2177 << ((useImageAtomics) ? ("${SHADER_IMAGE_ATOMIC_REQUIRE}\n") : (""))
2178 << "layout (local_size_x = 1, local_size_y = 1) in;\n";
2179
2180 if (m_storage == STORAGE_BUFFER)
2181 buf << "layout(binding=1, std430) " << ((m_useAtomic) ? ("coherent ") : ("")) << "buffer Buffer\n"
2182 << "{\n"
2183 << " highp " << ((m_formatInteger) ? ("int") : ("float")) << " values[];\n"
2184 << "} sb_in;\n";
2185 else if (m_storage == STORAGE_IMAGE)
2186 buf << "layout(" << ((m_formatInteger) ? ("r32i") : ("r32f")) << ", binding=1) "
2187 << ((m_useAtomic) ? ("coherent ") : ("readonly ")) << "uniform highp "
2188 << ((m_formatInteger) ? ("iimage2D") : ("image2D")) << " u_imageIn;\n";
2189 else
2190 DE_ASSERT(false);
2191
2192 buf << "layout(binding=0, std430) buffer ResultBuffer\n"
2193 << "{\n"
2194 << " highp int resultOk[];\n"
2195 << "} sb_result;\n"
2196 << "\n"
2197 << "void main (void)\n"
2198 << "{\n"
2199 << " uvec3 size = gl_NumWorkGroups * gl_WorkGroupSize;\n"
2200 << " int groupNdx = int(size.x * size.y * gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + "
2201 "gl_GlobalInvocationID.x);\n"
2202 << " int interleavedGroupNdx = int((size.x >> 1U) * size.y * gl_GlobalInvocationID.z + (size.x >> 1U) * "
2203 "gl_GlobalInvocationID.y + (gl_GlobalInvocationID.x >> 1U));\n"
2204 << " " << ((m_formatInteger) ? ("int") : ("float")) << " zero = " << ((m_formatInteger) ? ("0") : ("0.0"))
2205 << ";\n"
2206 << " bool allOk = true;\n"
2207 << "\n";
2208
2209 // Verify data
2210
2211 if (m_storage == STORAGE_BUFFER)
2212 {
2213 buf << " if (groupNdx % 2 == 0)\n"
2214 << " {\n";
2215 for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
2216 buf << " allOk = allOk && (" << ((m_useAtomic) ? ("atomicExchange(") : (""))
2217 << "sb_in.values[((interleavedGroupNdx + "
2218 << seed0 + readNdx * m_invocationGridSize * m_invocationGridSize / 2 << ") % "
2219 << m_invocationGridSize * m_invocationGridSize * m_perInvocationSize / 2 << ") * 2 + 0]"
2220 << ((m_useAtomic) ? (", zero)") : ("")) << " == " << ((m_formatInteger) ? ("int") : ("float"))
2221 << "(interleavedGroupNdx));\n";
2222 buf << " }\n"
2223 << " else\n"
2224 << " {\n";
2225 for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
2226 buf << " allOk = allOk && (" << ((m_useAtomic) ? ("atomicExchange(") : (""))
2227 << "sb_in.values[((interleavedGroupNdx + "
2228 << seed1 + readNdx * m_invocationGridSize * m_invocationGridSize / 2 << ") % "
2229 << m_invocationGridSize * m_invocationGridSize * m_perInvocationSize / 2 << ") * 2 + 1]"
2230 << ((m_useAtomic) ? (", zero)") : ("")) << " == " << ((m_formatInteger) ? ("int") : ("float"))
2231 << "(interleavedGroupNdx));\n";
2232 buf << " }\n";
2233 }
2234 else if (m_storage == STORAGE_IMAGE)
2235 {
2236 buf << " if (groupNdx % 2 == 0)\n"
2237 << " {\n";
2238 for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
2239 buf << " allOk = allOk && (" << ((m_useAtomic) ? ("imageAtomicExchange") : ("imageLoad"))
2240 << "(u_imageIn, ivec2(((int(gl_GlobalInvocationID.x >> 1U) + " << (seed0 + readNdx * 100) << ") % "
2241 << m_invocationGridSize / 2 << ") * 2 + 0, int(gl_GlobalInvocationID.y) + "
2242 << readNdx * m_invocationGridSize << ")" << ((m_useAtomic) ? (", zero)") : (").x"))
2243 << " == " << ((m_formatInteger) ? ("int") : ("float")) << "(interleavedGroupNdx));\n";
2244 buf << " }\n"
2245 << " else\n"
2246 << " {\n";
2247 for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
2248 buf << " allOk = allOk && (" << ((m_useAtomic) ? ("imageAtomicExchange") : ("imageLoad"))
2249 << "(u_imageIn, ivec2(((int(gl_GlobalInvocationID.x >> 1U) + " << (seed1 + readNdx * 100) << ") % "
2250 << m_invocationGridSize / 2 << ") * 2 + 1, int(gl_GlobalInvocationID.y) + "
2251 << readNdx * m_invocationGridSize << ")" << ((m_useAtomic) ? (", zero)") : (").x"))
2252 << " == " << ((m_formatInteger) ? ("int") : ("float")) << "(interleavedGroupNdx));\n";
2253 buf << " }\n";
2254 }
2255 else
2256 DE_ASSERT(false);
2257
2258 buf << " sb_result.resultOk[groupNdx] = (allOk) ? (1) : (0);\n"
2259 << "}\n";
2260
2261 return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(
2262 specializeShader(m_context, buf.str().c_str())));
2263 }
2264
genReadZeroProgram(void)2265 glu::ShaderProgram *InterCallTestCase::genReadZeroProgram(void)
2266 {
2267 const bool useImageAtomics = m_useAtomic && m_storage == STORAGE_IMAGE;
2268 std::ostringstream buf;
2269
2270 buf << "${GLSL_VERSION_DECL}\n"
2271 << ((useImageAtomics) ? ("${SHADER_IMAGE_ATOMIC_REQUIRE}\n") : (""))
2272 << "layout (local_size_x = 1, local_size_y = 1) in;\n";
2273
2274 if (m_storage == STORAGE_BUFFER)
2275 buf << "layout(binding=1, std430) " << ((m_useAtomic) ? ("coherent ") : ("")) << "buffer Buffer\n"
2276 << "{\n"
2277 << " highp " << ((m_formatInteger) ? ("int") : ("float")) << " values[];\n"
2278 << "} sb_in;\n";
2279 else if (m_storage == STORAGE_IMAGE)
2280 buf << "layout(" << ((m_formatInteger) ? ("r32i") : ("r32f")) << ", binding=1) "
2281 << ((m_useAtomic) ? ("coherent ") : ("readonly ")) << "uniform highp "
2282 << ((m_formatInteger) ? ("iimage2D") : ("image2D")) << " u_imageIn;\n";
2283 else
2284 DE_ASSERT(false);
2285
2286 buf << "layout(binding=0, std430) buffer ResultBuffer\n"
2287 << "{\n"
2288 << " highp int resultOk[];\n"
2289 << "} sb_result;\n"
2290 << "\n"
2291 << "void main (void)\n"
2292 << "{\n"
2293 << " uvec3 size = gl_NumWorkGroups * gl_WorkGroupSize;\n"
2294 << " int groupNdx = int(size.x * size.y * gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + "
2295 "gl_GlobalInvocationID.x);\n"
2296 << " " << ((m_formatInteger) ? ("int") : ("float"))
2297 << " anything = " << ((m_formatInteger) ? ("5") : ("5.0")) << ";\n"
2298 << " bool allOk = true;\n"
2299 << "\n";
2300
2301 // Verify data
2302
2303 if (m_storage == STORAGE_BUFFER)
2304 {
2305 for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
2306 buf << " allOk = allOk && (" << ((m_useAtomic) ? ("atomicExchange(") : (""))
2307 << "sb_in.values[groupNdx * " << m_perInvocationSize << " + " << readNdx << "]"
2308 << ((m_useAtomic) ? (", anything)") : ("")) << " == " << ((m_formatInteger) ? ("0") : ("0.0"))
2309 << ");\n";
2310 }
2311 else if (m_storage == STORAGE_IMAGE)
2312 {
2313 for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
2314 buf << " allOk = allOk && (" << ((m_useAtomic) ? ("imageAtomicExchange") : ("imageLoad"))
2315 << "(u_imageIn, ivec2(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y + "
2316 << (readNdx * m_invocationGridSize) << "u)" << ((m_useAtomic) ? (", anything)") : (").x"))
2317 << " == " << ((m_formatInteger) ? ("0") : ("0.0")) << ");\n";
2318 }
2319 else
2320 DE_ASSERT(false);
2321
2322 buf << " sb_result.resultOk[groupNdx] = (allOk) ? (1) : (0);\n"
2323 << "}\n";
2324
2325 return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(
2326 specializeShader(m_context, buf.str().c_str())));
2327 }
2328
2329 class SSBOConcurrentAtomicCase : public TestCase
2330 {
2331 public:
2332 SSBOConcurrentAtomicCase(Context &context, const char *name, const char *description, int numCalls, int workSize);
2333 ~SSBOConcurrentAtomicCase(void);
2334
2335 void init(void);
2336 void deinit(void);
2337 IterateResult iterate(void);
2338
2339 private:
2340 std::string genComputeSource(void) const;
2341
2342 const int m_numCalls;
2343 const int m_workSize;
2344 glu::ShaderProgram *m_program;
2345 uint32_t m_bufferID;
2346 std::vector<uint32_t> m_intermediateResultBuffers;
2347 };
2348
SSBOConcurrentAtomicCase(Context & context,const char * name,const char * description,int numCalls,int workSize)2349 SSBOConcurrentAtomicCase::SSBOConcurrentAtomicCase(Context &context, const char *name, const char *description,
2350 int numCalls, int workSize)
2351 : TestCase(context, name, description)
2352 , m_numCalls(numCalls)
2353 , m_workSize(workSize)
2354 , m_program(DE_NULL)
2355 , m_bufferID(DE_NULL)
2356 {
2357 }
2358
~SSBOConcurrentAtomicCase(void)2359 SSBOConcurrentAtomicCase::~SSBOConcurrentAtomicCase(void)
2360 {
2361 deinit();
2362 }
2363
init(void)2364 void SSBOConcurrentAtomicCase::init(void)
2365 {
2366 const glw::Functions &gl = m_context.getRenderContext().getFunctions();
2367 std::vector<uint32_t> zeroData(m_workSize, 0);
2368
2369 // gen buffers
2370
2371 gl.genBuffers(1, &m_bufferID);
2372 gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_bufferID);
2373 gl.bufferData(GL_SHADER_STORAGE_BUFFER, sizeof(uint32_t) * m_workSize, &zeroData[0], GL_DYNAMIC_COPY);
2374
2375 for (int ndx = 0; ndx < m_numCalls; ++ndx)
2376 {
2377 uint32_t buffer = 0;
2378
2379 gl.genBuffers(1, &buffer);
2380 gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
2381 gl.bufferData(GL_SHADER_STORAGE_BUFFER, sizeof(uint32_t) * m_workSize, &zeroData[0], GL_DYNAMIC_COPY);
2382
2383 m_intermediateResultBuffers.push_back(buffer);
2384 GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffers");
2385 }
2386
2387 // gen program
2388
2389 m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
2390 << glu::ComputeSource(genComputeSource()));
2391 m_testCtx.getLog() << *m_program;
2392 if (!m_program->isOk())
2393 throw tcu::TestError("could not build program");
2394 }
2395
deinit(void)2396 void SSBOConcurrentAtomicCase::deinit(void)
2397 {
2398 if (m_bufferID)
2399 {
2400 m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_bufferID);
2401 m_bufferID = 0;
2402 }
2403
2404 for (int ndx = 0; ndx < (int)m_intermediateResultBuffers.size(); ++ndx)
2405 m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_intermediateResultBuffers[ndx]);
2406 m_intermediateResultBuffers.clear();
2407
2408 delete m_program;
2409 m_program = DE_NULL;
2410 }
2411
iterate(void)2412 TestCase::IterateResult SSBOConcurrentAtomicCase::iterate(void)
2413 {
2414 const glw::Functions &gl = m_context.getRenderContext().getFunctions();
2415 const uint32_t sumValue = (uint32_t)(m_numCalls * (m_numCalls + 1) / 2);
2416 std::vector<int> deltas;
2417
2418 // generate unique deltas
2419 generateShuffledRamp(m_numCalls, deltas);
2420
2421 // invoke program N times, each with a different delta
2422 {
2423 const int deltaLocation = gl.getUniformLocation(m_program->getProgram(), "u_atomicDelta");
2424
2425 m_testCtx.getLog() << tcu::TestLog::Message << "Running shader " << m_numCalls << " times.\n"
2426 << "Num groups = (" << m_workSize << ", 1, 1)\n"
2427 << "Setting u_atomicDelta to a unique value for each call.\n"
2428 << tcu::TestLog::EndMessage;
2429
2430 if (deltaLocation == -1)
2431 throw tcu::TestError("u_atomicDelta location was -1");
2432
2433 gl.useProgram(m_program->getProgram());
2434 gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, m_bufferID);
2435
2436 for (int callNdx = 0; callNdx < m_numCalls; ++callNdx)
2437 {
2438 m_testCtx.getLog() << tcu::TestLog::Message << "Call " << callNdx << ": u_atomicDelta = " << deltas[callNdx]
2439 << tcu::TestLog::EndMessage;
2440
2441 gl.uniform1ui(deltaLocation, deltas[callNdx]);
2442 gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_intermediateResultBuffers[callNdx]);
2443 gl.dispatchCompute(m_workSize, 1, 1);
2444 }
2445
2446 GLU_EXPECT_NO_ERROR(gl.getError(), "post dispatch");
2447 }
2448
2449 // Verify result
2450 {
2451 std::vector<uint32_t> result;
2452
2453 m_testCtx.getLog() << tcu::TestLog::Message << "Verifying work buffer, it should be filled with value "
2454 << sumValue << tcu::TestLog::EndMessage;
2455
2456 gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_bufferID);
2457 readBuffer(gl, GL_SHADER_STORAGE_BUFFER, m_workSize, result);
2458
2459 for (int ndx = 0; ndx < m_workSize; ++ndx)
2460 {
2461 if (result[ndx] != sumValue)
2462 {
2463 m_testCtx.getLog() << tcu::TestLog::Message << "Work buffer error, at index " << ndx
2464 << " expected value " << (sumValue) << ", got " << result[ndx] << "\n"
2465 << "Work buffer contains invalid values." << tcu::TestLog::EndMessage;
2466
2467 m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Buffer contents invalid");
2468 return STOP;
2469 }
2470 }
2471
2472 m_testCtx.getLog() << tcu::TestLog::Message << "Work buffer contents are valid." << tcu::TestLog::EndMessage;
2473 }
2474
2475 // verify steps
2476 {
2477 std::vector<std::vector<uint32_t>> intermediateResults(m_numCalls);
2478 std::vector<uint32_t> valueChain(m_numCalls);
2479
2480 m_testCtx.getLog() << tcu::TestLog::Message << "Verifying intermediate results. " << tcu::TestLog::EndMessage;
2481
2482 // collect results
2483
2484 for (int callNdx = 0; callNdx < m_numCalls; ++callNdx)
2485 {
2486 gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_intermediateResultBuffers[callNdx]);
2487 readBuffer(gl, GL_SHADER_STORAGE_BUFFER, m_workSize, intermediateResults[callNdx]);
2488 }
2489
2490 // verify values
2491
2492 for (int valueNdx = 0; valueNdx < m_workSize; ++valueNdx)
2493 {
2494 int invalidOperationNdx;
2495 uint32_t errorDelta;
2496 uint32_t errorExpected;
2497
2498 // collect result chain for each element
2499 for (int callNdx = 0; callNdx < m_numCalls; ++callNdx)
2500 valueChain[callNdx] = intermediateResults[callNdx][valueNdx];
2501
2502 // check there exists a path from 0 to sumValue using each addition once
2503 // decompose cumulative results to addition operations (all additions positive => this works)
2504
2505 std::sort(valueChain.begin(), valueChain.end());
2506
2507 // validate chain
2508 if (!validateSortedAtomicRampAdditionValueChain(valueChain, sumValue, invalidOperationNdx, errorDelta,
2509 errorExpected))
2510 {
2511 m_testCtx.getLog() << tcu::TestLog::Message << "Intermediate buffer error, at value index " << valueNdx
2512 << ", applied operation index " << invalidOperationNdx << ", value was increased by "
2513 << errorDelta << ", but expected " << errorExpected << ".\n"
2514 << "Intermediate buffer contains invalid values. Values at index " << valueNdx
2515 << "\n"
2516 << tcu::TestLog::EndMessage;
2517
2518 for (int logCallNdx = 0; logCallNdx < m_numCalls; ++logCallNdx)
2519 m_testCtx.getLog() << tcu::TestLog::Message << "Value[" << logCallNdx
2520 << "] = " << intermediateResults[logCallNdx][valueNdx]
2521 << tcu::TestLog::EndMessage;
2522 m_testCtx.getLog() << tcu::TestLog::Message << "Result = " << sumValue << tcu::TestLog::EndMessage;
2523
2524 m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Buffer contents invalid");
2525 return STOP;
2526 }
2527 }
2528
2529 m_testCtx.getLog() << tcu::TestLog::Message << "Intermediate buffers are valid." << tcu::TestLog::EndMessage;
2530 }
2531
2532 m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
2533 return STOP;
2534 }
2535
genComputeSource(void) const2536 std::string SSBOConcurrentAtomicCase::genComputeSource(void) const
2537 {
2538 std::ostringstream buf;
2539
2540 buf << "${GLSL_VERSION_DECL}\n"
2541 << "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
2542 << "layout (binding = 1, std430) writeonly buffer IntermediateResults\n"
2543 << "{\n"
2544 << " highp uint values[" << m_workSize << "];\n"
2545 << "} sb_ires;\n"
2546 << "\n"
2547 << "layout (binding = 2, std430) volatile buffer WorkBuffer\n"
2548 << "{\n"
2549 << " highp uint values[" << m_workSize << "];\n"
2550 << "} sb_work;\n"
2551 << "uniform highp uint u_atomicDelta;\n"
2552 << "\n"
2553 << "void main ()\n"
2554 << "{\n"
2555 << " highp uint invocationIndex = gl_GlobalInvocationID.x;\n"
2556 << " sb_ires.values[invocationIndex] = atomicAdd(sb_work.values[invocationIndex], u_atomicDelta);\n"
2557 << "}";
2558
2559 return specializeShader(m_context, buf.str().c_str());
2560 }
2561
2562 class ConcurrentAtomicCounterCase : public TestCase
2563 {
2564 public:
2565 ConcurrentAtomicCounterCase(Context &context, const char *name, const char *description, int numCalls,
2566 int workSize);
2567 ~ConcurrentAtomicCounterCase(void);
2568
2569 void init(void);
2570 void deinit(void);
2571 IterateResult iterate(void);
2572
2573 private:
2574 std::string genComputeSource(bool evenOdd) const;
2575
2576 const int m_numCalls;
2577 const int m_workSize;
2578 glu::ShaderProgram *m_evenProgram;
2579 glu::ShaderProgram *m_oddProgram;
2580 uint32_t m_counterBuffer;
2581 uint32_t m_intermediateResultBuffer;
2582 };
2583
ConcurrentAtomicCounterCase(Context & context,const char * name,const char * description,int numCalls,int workSize)2584 ConcurrentAtomicCounterCase::ConcurrentAtomicCounterCase(Context &context, const char *name, const char *description,
2585 int numCalls, int workSize)
2586 : TestCase(context, name, description)
2587 , m_numCalls(numCalls)
2588 , m_workSize(workSize)
2589 , m_evenProgram(DE_NULL)
2590 , m_oddProgram(DE_NULL)
2591 , m_counterBuffer(DE_NULL)
2592 , m_intermediateResultBuffer(DE_NULL)
2593 {
2594 }
2595
~ConcurrentAtomicCounterCase(void)2596 ConcurrentAtomicCounterCase::~ConcurrentAtomicCounterCase(void)
2597 {
2598 deinit();
2599 }
2600
init(void)2601 void ConcurrentAtomicCounterCase::init(void)
2602 {
2603 const glw::Functions &gl = m_context.getRenderContext().getFunctions();
2604 const std::vector<uint32_t> zeroData(m_numCalls * m_workSize, 0);
2605
2606 // gen buffer
2607
2608 gl.genBuffers(1, &m_counterBuffer);
2609 gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_counterBuffer);
2610 gl.bufferData(GL_SHADER_STORAGE_BUFFER, sizeof(uint32_t), &zeroData[0], GL_DYNAMIC_COPY);
2611
2612 gl.genBuffers(1, &m_intermediateResultBuffer);
2613 gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_intermediateResultBuffer);
2614 gl.bufferData(GL_SHADER_STORAGE_BUFFER, sizeof(uint32_t) * m_numCalls * m_workSize, &zeroData[0], GL_DYNAMIC_COPY);
2615
2616 GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffers");
2617
2618 // gen programs
2619
2620 {
2621 const tcu::ScopedLogSection section(m_testCtx.getLog(), "EvenProgram", "Even program");
2622
2623 m_evenProgram = new glu::ShaderProgram(m_context.getRenderContext(),
2624 glu::ProgramSources() << glu::ComputeSource(genComputeSource(true)));
2625 m_testCtx.getLog() << *m_evenProgram;
2626 if (!m_evenProgram->isOk())
2627 throw tcu::TestError("could not build program");
2628 }
2629 {
2630 const tcu::ScopedLogSection section(m_testCtx.getLog(), "OddProgram", "Odd program");
2631
2632 m_oddProgram = new glu::ShaderProgram(m_context.getRenderContext(),
2633 glu::ProgramSources() << glu::ComputeSource(genComputeSource(false)));
2634 m_testCtx.getLog() << *m_oddProgram;
2635 if (!m_oddProgram->isOk())
2636 throw tcu::TestError("could not build program");
2637 }
2638 }
2639
deinit(void)2640 void ConcurrentAtomicCounterCase::deinit(void)
2641 {
2642 if (m_counterBuffer)
2643 {
2644 m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_counterBuffer);
2645 m_counterBuffer = 0;
2646 }
2647 if (m_intermediateResultBuffer)
2648 {
2649 m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_intermediateResultBuffer);
2650 m_intermediateResultBuffer = 0;
2651 }
2652
2653 delete m_evenProgram;
2654 m_evenProgram = DE_NULL;
2655
2656 delete m_oddProgram;
2657 m_oddProgram = DE_NULL;
2658 }
2659
iterate(void)2660 TestCase::IterateResult ConcurrentAtomicCounterCase::iterate(void)
2661 {
2662 const glw::Functions &gl = m_context.getRenderContext().getFunctions();
2663
2664 // invoke program N times, each with a different delta
2665 {
2666 const int evenCallNdxLocation = gl.getUniformLocation(m_evenProgram->getProgram(), "u_callNdx");
2667 const int oddCallNdxLocation = gl.getUniformLocation(m_oddProgram->getProgram(), "u_callNdx");
2668
2669 m_testCtx.getLog() << tcu::TestLog::Message << "Running shader pair (even & odd) " << m_numCalls << " times.\n"
2670 << "Num groups = (" << m_workSize << ", 1, 1)\n"
2671 << tcu::TestLog::EndMessage;
2672
2673 if (evenCallNdxLocation == -1)
2674 throw tcu::TestError("u_callNdx location was -1");
2675 if (oddCallNdxLocation == -1)
2676 throw tcu::TestError("u_callNdx location was -1");
2677
2678 gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_intermediateResultBuffer);
2679 gl.bindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, m_counterBuffer);
2680
2681 for (int callNdx = 0; callNdx < m_numCalls; ++callNdx)
2682 {
2683 gl.useProgram(m_evenProgram->getProgram());
2684 gl.uniform1ui(evenCallNdxLocation, (uint32_t)callNdx);
2685 gl.dispatchCompute(m_workSize, 1, 1);
2686
2687 gl.useProgram(m_oddProgram->getProgram());
2688 gl.uniform1ui(oddCallNdxLocation, (uint32_t)callNdx);
2689 gl.dispatchCompute(m_workSize, 1, 1);
2690 }
2691
2692 GLU_EXPECT_NO_ERROR(gl.getError(), "post dispatch");
2693 }
2694
2695 // Verify result
2696 {
2697 uint32_t result;
2698
2699 m_testCtx.getLog() << tcu::TestLog::Message << "Verifying work buffer, it should be " << m_numCalls * m_workSize
2700 << tcu::TestLog::EndMessage;
2701
2702 gl.bindBuffer(GL_ATOMIC_COUNTER_BUFFER, m_counterBuffer);
2703 result = readBufferUint32(gl, GL_ATOMIC_COUNTER_BUFFER);
2704
2705 if ((int)result != m_numCalls * m_workSize)
2706 {
2707 m_testCtx.getLog() << tcu::TestLog::Message << "Counter buffer error, expected value "
2708 << (m_numCalls * m_workSize) << ", got " << result << "\n"
2709 << tcu::TestLog::EndMessage;
2710
2711 m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Buffer contents invalid");
2712 return STOP;
2713 }
2714
2715 m_testCtx.getLog() << tcu::TestLog::Message << "Counter buffer is valid." << tcu::TestLog::EndMessage;
2716 }
2717
2718 // verify steps
2719 {
2720 std::vector<uint32_t> intermediateResults;
2721
2722 m_testCtx.getLog() << tcu::TestLog::Message << "Verifying intermediate results. " << tcu::TestLog::EndMessage;
2723
2724 // collect results
2725
2726 gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_intermediateResultBuffer);
2727 readBuffer(gl, GL_SHADER_STORAGE_BUFFER, m_numCalls * m_workSize, intermediateResults);
2728
2729 // verify values
2730
2731 std::sort(intermediateResults.begin(), intermediateResults.end());
2732
2733 for (int valueNdx = 0; valueNdx < m_workSize * m_numCalls; ++valueNdx)
2734 {
2735 if ((int)intermediateResults[valueNdx] != valueNdx)
2736 {
2737 m_testCtx.getLog() << tcu::TestLog::Message << "Intermediate buffer error, at value index " << valueNdx
2738 << ", expected " << valueNdx << ", got " << intermediateResults[valueNdx] << ".\n"
2739 << "Intermediate buffer contains invalid values. Intermediate results:\n"
2740 << tcu::TestLog::EndMessage;
2741
2742 for (int logCallNdx = 0; logCallNdx < m_workSize * m_numCalls; ++logCallNdx)
2743 m_testCtx.getLog() << tcu::TestLog::Message << "Value[" << logCallNdx
2744 << "] = " << intermediateResults[logCallNdx] << tcu::TestLog::EndMessage;
2745
2746 m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Buffer contents invalid");
2747 return STOP;
2748 }
2749 }
2750
2751 m_testCtx.getLog() << tcu::TestLog::Message << "Intermediate buffers are valid." << tcu::TestLog::EndMessage;
2752 }
2753
2754 m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
2755 return STOP;
2756 }
2757
genComputeSource(bool evenOdd) const2758 std::string ConcurrentAtomicCounterCase::genComputeSource(bool evenOdd) const
2759 {
2760 std::ostringstream buf;
2761
2762 buf << "${GLSL_VERSION_DECL}\n"
2763 << "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
2764 << "layout (binding = 1, std430) writeonly buffer IntermediateResults\n"
2765 << "{\n"
2766 << " highp uint values[" << m_workSize * m_numCalls << "];\n"
2767 << "} sb_ires;\n"
2768 << "\n"
2769 << "layout (binding = 0, offset = 0) uniform atomic_uint u_counter;\n"
2770 << "uniform highp uint u_callNdx;\n"
2771 << "\n"
2772 << "void main ()\n"
2773 << "{\n"
2774 << " highp uint dataNdx = u_callNdx * " << m_workSize << "u + gl_GlobalInvocationID.x;\n"
2775 << " if ((dataNdx % 2u) == " << ((evenOdd) ? (0) : (1)) << "u)\n"
2776 << " sb_ires.values[dataNdx] = atomicCounterIncrement(u_counter);\n"
2777 << "}";
2778
2779 return specializeShader(m_context, buf.str().c_str());
2780 }
2781
2782 class ConcurrentImageAtomicCase : public TestCase
2783 {
2784 public:
2785 ConcurrentImageAtomicCase(Context &context, const char *name, const char *description, int numCalls, int workSize);
2786 ~ConcurrentImageAtomicCase(void);
2787
2788 void init(void);
2789 void deinit(void);
2790 IterateResult iterate(void);
2791
2792 private:
2793 void readWorkImage(std::vector<uint32_t> &result);
2794
2795 std::string genComputeSource(void) const;
2796 std::string genImageReadSource(void) const;
2797 std::string genImageClearSource(void) const;
2798
2799 const int m_numCalls;
2800 const int m_workSize;
2801 glu::ShaderProgram *m_program;
2802 glu::ShaderProgram *m_imageReadProgram;
2803 glu::ShaderProgram *m_imageClearProgram;
2804 uint32_t m_imageID;
2805 std::vector<uint32_t> m_intermediateResultBuffers;
2806 };
2807
ConcurrentImageAtomicCase(Context & context,const char * name,const char * description,int numCalls,int workSize)2808 ConcurrentImageAtomicCase::ConcurrentImageAtomicCase(Context &context, const char *name, const char *description,
2809 int numCalls, int workSize)
2810 : TestCase(context, name, description)
2811 , m_numCalls(numCalls)
2812 , m_workSize(workSize)
2813 , m_program(DE_NULL)
2814 , m_imageReadProgram(DE_NULL)
2815 , m_imageClearProgram(DE_NULL)
2816 , m_imageID(DE_NULL)
2817 {
2818 }
2819
~ConcurrentImageAtomicCase(void)2820 ConcurrentImageAtomicCase::~ConcurrentImageAtomicCase(void)
2821 {
2822 deinit();
2823 }
2824
init(void)2825 void ConcurrentImageAtomicCase::init(void)
2826 {
2827 const glw::Functions &gl = m_context.getRenderContext().getFunctions();
2828 std::vector<uint32_t> zeroData(m_workSize * m_workSize, 0);
2829
2830 if (!checkSupport(m_context))
2831 throw tcu::NotSupportedError("Test requires GL_OES_shader_image_atomic");
2832
2833 // gen image
2834
2835 gl.genTextures(1, &m_imageID);
2836 gl.bindTexture(GL_TEXTURE_2D, m_imageID);
2837 gl.texStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, m_workSize, m_workSize);
2838 gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
2839 gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
2840 GLU_EXPECT_NO_ERROR(gl.getError(), "gen tex");
2841
2842 // gen buffers
2843
2844 for (int ndx = 0; ndx < m_numCalls; ++ndx)
2845 {
2846 uint32_t buffer = 0;
2847
2848 gl.genBuffers(1, &buffer);
2849 gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
2850 gl.bufferData(GL_SHADER_STORAGE_BUFFER, sizeof(uint32_t) * m_workSize * m_workSize, &zeroData[0],
2851 GL_DYNAMIC_COPY);
2852
2853 m_intermediateResultBuffers.push_back(buffer);
2854 GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffers");
2855 }
2856
2857 // gen programs
2858
2859 m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
2860 << glu::ComputeSource(genComputeSource()));
2861 m_testCtx.getLog() << *m_program;
2862 if (!m_program->isOk())
2863 throw tcu::TestError("could not build program");
2864
2865 m_imageReadProgram = new glu::ShaderProgram(m_context.getRenderContext(),
2866 glu::ProgramSources() << glu::ComputeSource(genImageReadSource()));
2867 if (!m_imageReadProgram->isOk())
2868 {
2869 const tcu::ScopedLogSection section(m_testCtx.getLog(), "ImageReadProgram", "Image read program");
2870
2871 m_testCtx.getLog() << *m_imageReadProgram;
2872 throw tcu::TestError("could not build program");
2873 }
2874
2875 m_imageClearProgram = new glu::ShaderProgram(m_context.getRenderContext(),
2876 glu::ProgramSources() << glu::ComputeSource(genImageClearSource()));
2877 if (!m_imageClearProgram->isOk())
2878 {
2879 const tcu::ScopedLogSection section(m_testCtx.getLog(), "ImageClearProgram", "Image read program");
2880
2881 m_testCtx.getLog() << *m_imageClearProgram;
2882 throw tcu::TestError("could not build program");
2883 }
2884 }
2885
deinit(void)2886 void ConcurrentImageAtomicCase::deinit(void)
2887 {
2888 if (m_imageID)
2889 {
2890 m_context.getRenderContext().getFunctions().deleteTextures(1, &m_imageID);
2891 m_imageID = 0;
2892 }
2893
2894 for (int ndx = 0; ndx < (int)m_intermediateResultBuffers.size(); ++ndx)
2895 m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_intermediateResultBuffers[ndx]);
2896 m_intermediateResultBuffers.clear();
2897
2898 delete m_program;
2899 m_program = DE_NULL;
2900
2901 delete m_imageReadProgram;
2902 m_imageReadProgram = DE_NULL;
2903
2904 delete m_imageClearProgram;
2905 m_imageClearProgram = DE_NULL;
2906 }
2907
iterate(void)2908 TestCase::IterateResult ConcurrentImageAtomicCase::iterate(void)
2909 {
2910 const glw::Functions &gl = m_context.getRenderContext().getFunctions();
2911 const uint32_t sumValue = (uint32_t)(m_numCalls * (m_numCalls + 1) / 2);
2912 std::vector<int> deltas;
2913
2914 // generate unique deltas
2915 generateShuffledRamp(m_numCalls, deltas);
2916
2917 // clear image
2918 {
2919 m_testCtx.getLog() << tcu::TestLog::Message << "Clearing image contents" << tcu::TestLog::EndMessage;
2920
2921 gl.useProgram(m_imageClearProgram->getProgram());
2922 gl.bindImageTexture(2, m_imageID, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
2923 gl.dispatchCompute(m_workSize, m_workSize, 1);
2924 gl.memoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
2925
2926 GLU_EXPECT_NO_ERROR(gl.getError(), "clear");
2927 }
2928
2929 // invoke program N times, each with a different delta
2930 {
2931 const int deltaLocation = gl.getUniformLocation(m_program->getProgram(), "u_atomicDelta");
2932
2933 m_testCtx.getLog() << tcu::TestLog::Message << "Running shader " << m_numCalls << " times.\n"
2934 << "Num groups = (" << m_workSize << ", " << m_workSize << ", 1)\n"
2935 << "Setting u_atomicDelta to a unique value for each call.\n"
2936 << tcu::TestLog::EndMessage;
2937
2938 if (deltaLocation == -1)
2939 throw tcu::TestError("u_atomicDelta location was -1");
2940
2941 gl.useProgram(m_program->getProgram());
2942 gl.bindImageTexture(2, m_imageID, 0, GL_FALSE, 0, GL_READ_WRITE, GL_R32UI);
2943
2944 for (int callNdx = 0; callNdx < m_numCalls; ++callNdx)
2945 {
2946 m_testCtx.getLog() << tcu::TestLog::Message << "Call " << callNdx << ": u_atomicDelta = " << deltas[callNdx]
2947 << tcu::TestLog::EndMessage;
2948
2949 gl.uniform1ui(deltaLocation, deltas[callNdx]);
2950 gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_intermediateResultBuffers[callNdx]);
2951 gl.dispatchCompute(m_workSize, m_workSize, 1);
2952 }
2953
2954 GLU_EXPECT_NO_ERROR(gl.getError(), "post dispatch");
2955 }
2956
2957 // Verify result
2958 {
2959 std::vector<uint32_t> result;
2960
2961 m_testCtx.getLog() << tcu::TestLog::Message << "Verifying work image, it should be filled with value "
2962 << sumValue << tcu::TestLog::EndMessage;
2963
2964 readWorkImage(result);
2965
2966 for (int ndx = 0; ndx < m_workSize * m_workSize; ++ndx)
2967 {
2968 if (result[ndx] != sumValue)
2969 {
2970 m_testCtx.getLog() << tcu::TestLog::Message << "Work image error, at index (" << ndx % m_workSize
2971 << ", " << ndx / m_workSize << ") expected value " << (sumValue) << ", got "
2972 << result[ndx] << "\n"
2973 << "Work image contains invalid values." << tcu::TestLog::EndMessage;
2974
2975 m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image contents invalid");
2976 return STOP;
2977 }
2978 }
2979
2980 m_testCtx.getLog() << tcu::TestLog::Message << "Work image contents are valid." << tcu::TestLog::EndMessage;
2981 }
2982
2983 // verify steps
2984 {
2985 std::vector<std::vector<uint32_t>> intermediateResults(m_numCalls);
2986 std::vector<uint32_t> valueChain(m_numCalls);
2987 std::vector<uint32_t> chainDelta(m_numCalls);
2988
2989 m_testCtx.getLog() << tcu::TestLog::Message << "Verifying intermediate results. " << tcu::TestLog::EndMessage;
2990
2991 // collect results
2992
2993 for (int callNdx = 0; callNdx < m_numCalls; ++callNdx)
2994 {
2995 gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_intermediateResultBuffers[callNdx]);
2996 readBuffer(gl, GL_SHADER_STORAGE_BUFFER, m_workSize * m_workSize, intermediateResults[callNdx]);
2997 }
2998
2999 // verify values
3000
3001 for (int valueNdx = 0; valueNdx < m_workSize; ++valueNdx)
3002 {
3003 int invalidOperationNdx;
3004 uint32_t errorDelta;
3005 uint32_t errorExpected;
3006
3007 // collect result chain for each element
3008 for (int callNdx = 0; callNdx < m_numCalls; ++callNdx)
3009 valueChain[callNdx] = intermediateResults[callNdx][valueNdx];
3010
3011 // check there exists a path from 0 to sumValue using each addition once
3012 // decompose cumulative results to addition operations (all additions positive => this works)
3013
3014 std::sort(valueChain.begin(), valueChain.end());
3015
3016 for (int callNdx = 0; callNdx < m_numCalls; ++callNdx)
3017 chainDelta[callNdx] =
3018 ((callNdx + 1 == m_numCalls) ? (sumValue) : (valueChain[callNdx + 1])) - valueChain[callNdx];
3019
3020 // chainDelta contains now the actual additions applied to the value
3021 std::sort(chainDelta.begin(), chainDelta.end());
3022
3023 // validate chain
3024 if (!validateSortedAtomicRampAdditionValueChain(valueChain, sumValue, invalidOperationNdx, errorDelta,
3025 errorExpected))
3026 {
3027 m_testCtx.getLog() << tcu::TestLog::Message << "Intermediate buffer error, at index ("
3028 << valueNdx % m_workSize << ", " << valueNdx / m_workSize
3029 << "), applied operation index " << invalidOperationNdx
3030 << ", value was increased by " << errorDelta << ", but expected " << errorExpected
3031 << ".\n"
3032 << "Intermediate buffer contains invalid values. Values at index ("
3033 << valueNdx % m_workSize << ", " << valueNdx / m_workSize << ")\n"
3034 << tcu::TestLog::EndMessage;
3035
3036 for (int logCallNdx = 0; logCallNdx < m_numCalls; ++logCallNdx)
3037 m_testCtx.getLog() << tcu::TestLog::Message << "Value[" << logCallNdx
3038 << "] = " << intermediateResults[logCallNdx][valueNdx]
3039 << tcu::TestLog::EndMessage;
3040 m_testCtx.getLog() << tcu::TestLog::Message << "Result = " << sumValue << tcu::TestLog::EndMessage;
3041
3042 m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Buffer contents invalid");
3043 return STOP;
3044 }
3045 }
3046
3047 m_testCtx.getLog() << tcu::TestLog::Message << "Intermediate buffers are valid." << tcu::TestLog::EndMessage;
3048 }
3049
3050 m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
3051 return STOP;
3052 }
3053
readWorkImage(std::vector<uint32_t> & result)3054 void ConcurrentImageAtomicCase::readWorkImage(std::vector<uint32_t> &result)
3055 {
3056 const glw::Functions &gl = m_context.getRenderContext().getFunctions();
3057 glu::Buffer resultBuffer(m_context.getRenderContext());
3058
3059 // Read image to an ssbo
3060
3061 {
3062 const std::vector<uint32_t> zeroData(m_workSize * m_workSize, 0);
3063
3064 gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *resultBuffer);
3065 gl.bufferData(GL_SHADER_STORAGE_BUFFER, (int)(sizeof(uint32_t) * m_workSize * m_workSize), &zeroData[0],
3066 GL_DYNAMIC_COPY);
3067
3068 gl.memoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
3069 gl.useProgram(m_imageReadProgram->getProgram());
3070
3071 gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, *resultBuffer);
3072 gl.bindImageTexture(2, m_imageID, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI);
3073 gl.dispatchCompute(m_workSize, m_workSize, 1);
3074
3075 GLU_EXPECT_NO_ERROR(gl.getError(), "read");
3076 }
3077
3078 // Read ssbo
3079 {
3080 const void *ptr = gl.mapBufferRange(GL_SHADER_STORAGE_BUFFER, 0,
3081 (int)(sizeof(uint32_t) * m_workSize * m_workSize), GL_MAP_READ_BIT);
3082 GLU_EXPECT_NO_ERROR(gl.getError(), "map");
3083
3084 if (!ptr)
3085 throw tcu::TestError("mapBufferRange returned NULL");
3086
3087 result.resize(m_workSize * m_workSize);
3088 memcpy(&result[0], ptr, sizeof(uint32_t) * m_workSize * m_workSize);
3089
3090 if (gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER) == GL_FALSE)
3091 throw tcu::TestError("unmapBuffer returned false");
3092 }
3093 }
3094
genComputeSource(void) const3095 std::string ConcurrentImageAtomicCase::genComputeSource(void) const
3096 {
3097 std::ostringstream buf;
3098
3099 buf << "${GLSL_VERSION_DECL}\n"
3100 << "${SHADER_IMAGE_ATOMIC_REQUIRE}\n"
3101 << "\n"
3102 << "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
3103 << "layout (binding = 1, std430) writeonly buffer IntermediateResults\n"
3104 << "{\n"
3105 << " highp uint values[" << m_workSize * m_workSize << "];\n"
3106 << "} sb_ires;\n"
3107 << "\n"
3108 << "layout (binding = 2, r32ui) volatile uniform highp uimage2D u_workImage;\n"
3109 << "uniform highp uint u_atomicDelta;\n"
3110 << "\n"
3111 << "void main ()\n"
3112 << "{\n"
3113 << " highp uint invocationIndex = gl_GlobalInvocationID.x + gl_GlobalInvocationID.y * uint(" << m_workSize
3114 << ");\n"
3115 << " sb_ires.values[invocationIndex] = imageAtomicAdd(u_workImage, ivec2(gl_GlobalInvocationID.xy), "
3116 "u_atomicDelta);\n"
3117 << "}";
3118
3119 return specializeShader(m_context, buf.str().c_str());
3120 }
3121
genImageReadSource(void) const3122 std::string ConcurrentImageAtomicCase::genImageReadSource(void) const
3123 {
3124 std::ostringstream buf;
3125
3126 buf << "${GLSL_VERSION_DECL}\n"
3127 << "\n"
3128 << "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
3129 << "layout (binding = 1, std430) writeonly buffer ImageValues\n"
3130 << "{\n"
3131 << " highp uint values[" << m_workSize * m_workSize << "];\n"
3132 << "} sb_res;\n"
3133 << "\n"
3134 << "layout (binding = 2, r32ui) readonly uniform highp uimage2D u_workImage;\n"
3135 << "\n"
3136 << "void main ()\n"
3137 << "{\n"
3138 << " highp uint invocationIndex = gl_GlobalInvocationID.x + gl_GlobalInvocationID.y * uint(" << m_workSize
3139 << ");\n"
3140 << " sb_res.values[invocationIndex] = imageLoad(u_workImage, ivec2(gl_GlobalInvocationID.xy)).x;\n"
3141 << "}";
3142
3143 return specializeShader(m_context, buf.str().c_str());
3144 }
3145
genImageClearSource(void) const3146 std::string ConcurrentImageAtomicCase::genImageClearSource(void) const
3147 {
3148 std::ostringstream buf;
3149
3150 buf << "${GLSL_VERSION_DECL}\n"
3151 << "\n"
3152 << "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
3153 << "layout (binding = 2, r32ui) writeonly uniform highp uimage2D u_workImage;\n"
3154 << "\n"
3155 << "void main ()\n"
3156 << "{\n"
3157 << " imageStore(u_workImage, ivec2(gl_GlobalInvocationID.xy), uvec4(0, 0, 0, 0));\n"
3158 << "}";
3159
3160 return specializeShader(m_context, buf.str().c_str());
3161 }
3162
3163 class ConcurrentSSBOAtomicCounterMixedCase : public TestCase
3164 {
3165 public:
3166 ConcurrentSSBOAtomicCounterMixedCase(Context &context, const char *name, const char *description, int numCalls,
3167 int workSize);
3168 ~ConcurrentSSBOAtomicCounterMixedCase(void);
3169
3170 void init(void);
3171 void deinit(void);
3172 IterateResult iterate(void);
3173
3174 private:
3175 std::string genSSBOComputeSource(void) const;
3176 std::string genAtomicCounterComputeSource(void) const;
3177
3178 const int m_numCalls;
3179 const int m_workSize;
3180 uint32_t m_bufferID;
3181 glu::ShaderProgram *m_ssboAtomicProgram;
3182 glu::ShaderProgram *m_atomicCounterProgram;
3183 };
3184
ConcurrentSSBOAtomicCounterMixedCase(Context & context,const char * name,const char * description,int numCalls,int workSize)3185 ConcurrentSSBOAtomicCounterMixedCase::ConcurrentSSBOAtomicCounterMixedCase(Context &context, const char *name,
3186 const char *description, int numCalls,
3187 int workSize)
3188 : TestCase(context, name, description)
3189 , m_numCalls(numCalls)
3190 , m_workSize(workSize)
3191 , m_bufferID(DE_NULL)
3192 , m_ssboAtomicProgram(DE_NULL)
3193 , m_atomicCounterProgram(DE_NULL)
3194 {
3195 // SSBO atomic XORs cancel out
3196 DE_ASSERT((workSize * numCalls) % (16 * 2) == 0);
3197 }
3198
~ConcurrentSSBOAtomicCounterMixedCase(void)3199 ConcurrentSSBOAtomicCounterMixedCase::~ConcurrentSSBOAtomicCounterMixedCase(void)
3200 {
3201 deinit();
3202 }
3203
init(void)3204 void ConcurrentSSBOAtomicCounterMixedCase::init(void)
3205 {
3206 const glw::Functions &gl = m_context.getRenderContext().getFunctions();
3207 const uint32_t zeroBuf[2] = {0, 0};
3208
3209 // gen buffer
3210
3211 gl.genBuffers(1, &m_bufferID);
3212 gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_bufferID);
3213 gl.bufferData(GL_SHADER_STORAGE_BUFFER, (int)(sizeof(uint32_t) * 2), zeroBuf, GL_DYNAMIC_COPY);
3214
3215 GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffers");
3216
3217 // gen programs
3218
3219 {
3220 const tcu::ScopedLogSection section(m_testCtx.getLog(), "SSBOProgram", "SSBO atomic program");
3221
3222 m_ssboAtomicProgram = new glu::ShaderProgram(
3223 m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(genSSBOComputeSource()));
3224 m_testCtx.getLog() << *m_ssboAtomicProgram;
3225 if (!m_ssboAtomicProgram->isOk())
3226 throw tcu::TestError("could not build program");
3227 }
3228 {
3229 const tcu::ScopedLogSection section(m_testCtx.getLog(), "AtomicCounterProgram", "Atomic counter program");
3230
3231 m_atomicCounterProgram = new glu::ShaderProgram(
3232 m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(genAtomicCounterComputeSource()));
3233 m_testCtx.getLog() << *m_atomicCounterProgram;
3234 if (!m_atomicCounterProgram->isOk())
3235 throw tcu::TestError("could not build program");
3236 }
3237 }
3238
deinit(void)3239 void ConcurrentSSBOAtomicCounterMixedCase::deinit(void)
3240 {
3241 if (m_bufferID)
3242 {
3243 m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_bufferID);
3244 m_bufferID = 0;
3245 }
3246
3247 delete m_ssboAtomicProgram;
3248 m_ssboAtomicProgram = DE_NULL;
3249
3250 delete m_atomicCounterProgram;
3251 m_atomicCounterProgram = DE_NULL;
3252 }
3253
iterate(void)3254 TestCase::IterateResult ConcurrentSSBOAtomicCounterMixedCase::iterate(void)
3255 {
3256 const glw::Functions &gl = m_context.getRenderContext().getFunctions();
3257
3258 m_testCtx.getLog() << tcu::TestLog::Message
3259 << "Testing atomic counters and SSBO atomic operations with both backed by the same buffer."
3260 << tcu::TestLog::EndMessage;
3261
3262 // invoke programs N times
3263 {
3264 m_testCtx.getLog() << tcu::TestLog::Message << "Running SSBO atomic program and atomic counter program "
3265 << m_numCalls << " times. (interleaved)\n"
3266 << "Num groups = (" << m_workSize << ", 1, 1)\n"
3267 << tcu::TestLog::EndMessage;
3268
3269 gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_bufferID);
3270 gl.bindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, m_bufferID);
3271
3272 for (int callNdx = 0; callNdx < m_numCalls; ++callNdx)
3273 {
3274 gl.useProgram(m_atomicCounterProgram->getProgram());
3275 gl.dispatchCompute(m_workSize, 1, 1);
3276
3277 gl.useProgram(m_ssboAtomicProgram->getProgram());
3278 gl.dispatchCompute(m_workSize, 1, 1);
3279 }
3280
3281 GLU_EXPECT_NO_ERROR(gl.getError(), "post dispatch");
3282 }
3283
3284 // Verify result
3285 {
3286 uint32_t result;
3287
3288 // XORs cancel out, only addition is left
3289 m_testCtx.getLog() << tcu::TestLog::Message << "Verifying work buffer, it should be " << m_numCalls * m_workSize
3290 << tcu::TestLog::EndMessage;
3291
3292 gl.bindBuffer(GL_ATOMIC_COUNTER_BUFFER, m_bufferID);
3293 result = readBufferUint32(gl, GL_ATOMIC_COUNTER_BUFFER);
3294
3295 if ((int)result != m_numCalls * m_workSize)
3296 {
3297 m_testCtx.getLog() << tcu::TestLog::Message << "Buffer value error, expected value "
3298 << (m_numCalls * m_workSize) << ", got " << result << "\n"
3299 << tcu::TestLog::EndMessage;
3300
3301 m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Buffer contents invalid");
3302 return STOP;
3303 }
3304
3305 m_testCtx.getLog() << tcu::TestLog::Message << "Buffer is valid." << tcu::TestLog::EndMessage;
3306 }
3307
3308 m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
3309 return STOP;
3310 }
3311
genSSBOComputeSource(void) const3312 std::string ConcurrentSSBOAtomicCounterMixedCase::genSSBOComputeSource(void) const
3313 {
3314 std::ostringstream buf;
3315
3316 buf << "${GLSL_VERSION_DECL}\n"
3317 << "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
3318 << "layout (binding = 1, std430) volatile buffer WorkBuffer\n"
3319 << "{\n"
3320 << " highp uint targetValue;\n"
3321 << " highp uint unused;\n"
3322 << "} sb_work;\n"
3323 << "\n"
3324 << "void main ()\n"
3325 << "{\n"
3326 << " // flip high bits\n"
3327 << " highp uint mask = uint(1) << (24u + (gl_GlobalInvocationID.x % 8u));\n"
3328 << " sb_work.unused = atomicXor(sb_work.targetValue, mask);\n"
3329 << "}";
3330
3331 return specializeShader(m_context, buf.str().c_str());
3332 }
3333
genAtomicCounterComputeSource(void) const3334 std::string ConcurrentSSBOAtomicCounterMixedCase::genAtomicCounterComputeSource(void) const
3335 {
3336 std::ostringstream buf;
3337
3338 buf << "${GLSL_VERSION_DECL}\n"
3339 << "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
3340 << "\n"
3341 << "layout (binding = 0, offset = 0) uniform atomic_uint u_counter;\n"
3342 << "\n"
3343 << "void main ()\n"
3344 << "{\n"
3345 << " atomicCounterIncrement(u_counter);\n"
3346 << "}";
3347
3348 return specializeShader(m_context, buf.str().c_str());
3349 }
3350
3351 } // namespace
3352
SynchronizationTests(Context & context)3353 SynchronizationTests::SynchronizationTests(Context &context)
3354 : TestCaseGroup(context, "synchronization", "Synchronization tests")
3355 {
3356 }
3357
~SynchronizationTests(void)3358 SynchronizationTests::~SynchronizationTests(void)
3359 {
3360 }
3361
init(void)3362 void SynchronizationTests::init(void)
3363 {
3364 tcu::TestCaseGroup *const inInvocationGroup =
3365 new tcu::TestCaseGroup(m_testCtx, "in_invocation", "Test intra-invocation synchronization");
3366 tcu::TestCaseGroup *const interInvocationGroup =
3367 new tcu::TestCaseGroup(m_testCtx, "inter_invocation", "Test inter-invocation synchronization");
3368 tcu::TestCaseGroup *const interCallGroup =
3369 new tcu::TestCaseGroup(m_testCtx, "inter_call", "Test inter-call synchronization");
3370
3371 addChild(inInvocationGroup);
3372 addChild(interInvocationGroup);
3373 addChild(interCallGroup);
3374
3375 // .in_invocation & .inter_invocation
3376 {
3377 static const struct CaseConfig
3378 {
3379 const char *namePrefix;
3380 const InterInvocationTestCase::StorageType storage;
3381 const int flags;
3382 } configs[] = {
3383 {"image", InterInvocationTestCase::STORAGE_IMAGE, 0},
3384 {"image_atomic", InterInvocationTestCase::STORAGE_IMAGE, InterInvocationTestCase::FLAG_ATOMIC},
3385 {"ssbo", InterInvocationTestCase::STORAGE_BUFFER, 0},
3386 {"ssbo_atomic", InterInvocationTestCase::STORAGE_BUFFER, InterInvocationTestCase::FLAG_ATOMIC},
3387 };
3388
3389 for (int groupNdx = 0; groupNdx < 2; ++groupNdx)
3390 {
3391 tcu::TestCaseGroup *const targetGroup = (groupNdx == 0) ? (inInvocationGroup) : (interInvocationGroup);
3392 const int extraFlags = (groupNdx == 0) ? (0) : (InterInvocationTestCase::FLAG_IN_GROUP);
3393
3394 for (int configNdx = 0; configNdx < DE_LENGTH_OF_ARRAY(configs); ++configNdx)
3395 {
3396 const char *const target =
3397 (configs[configNdx].storage == InterInvocationTestCase::STORAGE_BUFFER) ? ("buffer") : ("image");
3398
3399 targetGroup->addChild(new InvocationWriteReadCase(
3400 m_context, (std::string(configs[configNdx].namePrefix) + "_write_read").c_str(),
3401 (std::string("Write to ") + target + " and read it").c_str(), configs[configNdx].storage,
3402 configs[configNdx].flags | extraFlags));
3403
3404 targetGroup->addChild(new InvocationReadWriteCase(
3405 m_context, (std::string(configs[configNdx].namePrefix) + "_read_write").c_str(),
3406 (std::string("Read form ") + target + " and then write to it").c_str(), configs[configNdx].storage,
3407 configs[configNdx].flags | extraFlags));
3408
3409 targetGroup->addChild(new InvocationOverWriteCase(
3410 m_context, (std::string(configs[configNdx].namePrefix) + "_overwrite").c_str(),
3411 (std::string("Write to ") + target + " twice and read it").c_str(), configs[configNdx].storage,
3412 configs[configNdx].flags | extraFlags));
3413
3414 targetGroup->addChild(new InvocationAliasWriteCase(
3415 m_context, (std::string(configs[configNdx].namePrefix) + "_alias_write").c_str(),
3416 (std::string("Write to aliasing ") + target + " and read it").c_str(),
3417 InvocationAliasWriteCase::TYPE_WRITE, configs[configNdx].storage,
3418 configs[configNdx].flags | extraFlags));
3419
3420 targetGroup->addChild(new InvocationAliasWriteCase(
3421 m_context, (std::string(configs[configNdx].namePrefix) + "_alias_overwrite").c_str(),
3422 (std::string("Write to aliasing ") + target + "s and read it").c_str(),
3423 InvocationAliasWriteCase::TYPE_OVERWRITE, configs[configNdx].storage,
3424 configs[configNdx].flags | extraFlags));
3425 }
3426 }
3427 }
3428
3429 // .inter_call
3430 {
3431 tcu::TestCaseGroup *const withBarrierGroup =
3432 new tcu::TestCaseGroup(m_testCtx, "with_memory_barrier", "Synchronize with memory barrier");
3433 tcu::TestCaseGroup *const withoutBarrierGroup =
3434 new tcu::TestCaseGroup(m_testCtx, "without_memory_barrier", "Synchronize without memory barrier");
3435
3436 interCallGroup->addChild(withBarrierGroup);
3437 interCallGroup->addChild(withoutBarrierGroup);
3438
3439 // .with_memory_barrier
3440 {
3441 static const struct CaseConfig
3442 {
3443 const char *namePrefix;
3444 const InterCallTestCase::StorageType storage;
3445 const int flags;
3446 } configs[] = {
3447 {"image", InterCallTestCase::STORAGE_IMAGE, 0},
3448 {"image_atomic", InterCallTestCase::STORAGE_IMAGE,
3449 InterCallTestCase::FLAG_USE_ATOMIC | InterCallTestCase::FLAG_USE_INT},
3450 {"ssbo", InterCallTestCase::STORAGE_BUFFER, 0},
3451 {"ssbo_atomic", InterCallTestCase::STORAGE_BUFFER,
3452 InterCallTestCase::FLAG_USE_ATOMIC | InterCallTestCase::FLAG_USE_INT},
3453 };
3454
3455 const int seed0 = 123;
3456 const int seed1 = 457;
3457
3458 for (int configNdx = 0; configNdx < DE_LENGTH_OF_ARRAY(configs); ++configNdx)
3459 {
3460 const char *const target =
3461 (configs[configNdx].storage == InterCallTestCase::STORAGE_BUFFER) ? ("buffer") : ("image");
3462
3463 withBarrierGroup->addChild(new InterCallTestCase(
3464 m_context, (std::string(configs[configNdx].namePrefix) + "_write_read").c_str(),
3465 (std::string("Write to ") + target + " and read it").c_str(), configs[configNdx].storage,
3466 configs[configNdx].flags,
3467 InterCallOperations()
3468 << op::WriteData::Generate(1, seed0) << op::Barrier() << op::ReadData::Generate(1, seed0)));
3469
3470 withBarrierGroup->addChild(new InterCallTestCase(
3471 m_context, (std::string(configs[configNdx].namePrefix) + "_read_write").c_str(),
3472 (std::string("Read from ") + target + " and then write to it").c_str(), configs[configNdx].storage,
3473 configs[configNdx].flags,
3474 InterCallOperations()
3475 << op::ReadZeroData::Generate(1) << op::Barrier() << op::WriteData::Generate(1, seed0)));
3476
3477 withBarrierGroup->addChild(new InterCallTestCase(
3478 m_context, (std::string(configs[configNdx].namePrefix) + "_overwrite").c_str(),
3479 (std::string("Write to ") + target + " twice and read it").c_str(), configs[configNdx].storage,
3480 configs[configNdx].flags,
3481 InterCallOperations()
3482 << op::WriteData::Generate(1, seed0) << op::Barrier() << op::WriteData::Generate(1, seed1)
3483 << op::Barrier() << op::ReadData::Generate(1, seed1)));
3484
3485 withBarrierGroup->addChild(new InterCallTestCase(
3486 m_context, (std::string(configs[configNdx].namePrefix) + "_multiple_write_read").c_str(),
3487 (std::string("Write to multiple ") + target + "s and read them").c_str(),
3488 configs[configNdx].storage, configs[configNdx].flags,
3489 InterCallOperations() << op::WriteData::Generate(1, seed0) << op::WriteData::Generate(2, seed1)
3490 << op::Barrier() << op::ReadMultipleData::Generate(1, seed0, 2, seed1)));
3491
3492 withBarrierGroup->addChild(new InterCallTestCase(
3493 m_context,
3494 (std::string(configs[configNdx].namePrefix) + "_multiple_interleaved_write_read").c_str(),
3495 (std::string("Write to same ") + target + " in multiple calls and read it").c_str(),
3496 configs[configNdx].storage, configs[configNdx].flags,
3497 InterCallOperations() << op::WriteDataInterleaved::Generate(1, seed0, true)
3498 << op::WriteDataInterleaved::Generate(1, seed1, false) << op::Barrier()
3499 << op::ReadDataInterleaved::Generate(1, seed0, seed1)));
3500
3501 withBarrierGroup->addChild(new InterCallTestCase(
3502 m_context,
3503 (std::string(configs[configNdx].namePrefix) + "_multiple_unrelated_write_read_ordered").c_str(),
3504 (std::string("Two unrelated ") + target + " write-reads").c_str(), configs[configNdx].storage,
3505 configs[configNdx].flags,
3506 InterCallOperations()
3507 << op::WriteData::Generate(1, seed0) << op::WriteData::Generate(2, seed1) << op::Barrier()
3508 << op::ReadData::Generate(1, seed0) << op::ReadData::Generate(2, seed1)));
3509
3510 withBarrierGroup->addChild(new InterCallTestCase(
3511 m_context,
3512 (std::string(configs[configNdx].namePrefix) + "_multiple_unrelated_write_read_non_ordered").c_str(),
3513 (std::string("Two unrelated ") + target + " write-reads").c_str(), configs[configNdx].storage,
3514 configs[configNdx].flags,
3515 InterCallOperations()
3516 << op::WriteData::Generate(1, seed0) << op::WriteData::Generate(2, seed1) << op::Barrier()
3517 << op::ReadData::Generate(2, seed1) << op::ReadData::Generate(1, seed0)));
3518 }
3519
3520 // .without_memory_barrier
3521 {
3522 struct InvocationConfig
3523 {
3524 const char *name;
3525 int count;
3526 };
3527
3528 static const InvocationConfig ssboInvocations[] = {
3529 {"1k", 1024},
3530 {"4k", 4096},
3531 {"32k", 32768},
3532 };
3533 static const InvocationConfig imageInvocations[] = {
3534 {"8x8", 8},
3535 {"32x32", 32},
3536 {"128x128", 128},
3537 };
3538 static const InvocationConfig counterInvocations[] = {
3539 {"32", 32},
3540 {"128", 128},
3541 {"1k", 1024},
3542 };
3543 static const int callCounts[] = {2, 5, 100};
3544
3545 for (int invocationNdx = 0; invocationNdx < DE_LENGTH_OF_ARRAY(ssboInvocations); ++invocationNdx)
3546 for (int callCountNdx = 0; callCountNdx < DE_LENGTH_OF_ARRAY(callCounts); ++callCountNdx)
3547 withoutBarrierGroup->addChild(new SSBOConcurrentAtomicCase(
3548 m_context,
3549 (std::string("ssbo_atomic_dispatch_") + de::toString(callCounts[callCountNdx]) + "_calls_" +
3550 ssboInvocations[invocationNdx].name + "_invocations")
3551 .c_str(),
3552 "", callCounts[callCountNdx], ssboInvocations[invocationNdx].count));
3553
3554 for (int invocationNdx = 0; invocationNdx < DE_LENGTH_OF_ARRAY(imageInvocations); ++invocationNdx)
3555 for (int callCountNdx = 0; callCountNdx < DE_LENGTH_OF_ARRAY(callCounts); ++callCountNdx)
3556 withoutBarrierGroup->addChild(new ConcurrentImageAtomicCase(
3557 m_context,
3558 (std::string("image_atomic_dispatch_") + de::toString(callCounts[callCountNdx]) +
3559 "_calls_" + imageInvocations[invocationNdx].name + "_invocations")
3560 .c_str(),
3561 "", callCounts[callCountNdx], imageInvocations[invocationNdx].count));
3562
3563 for (int invocationNdx = 0; invocationNdx < DE_LENGTH_OF_ARRAY(counterInvocations); ++invocationNdx)
3564 for (int callCountNdx = 0; callCountNdx < DE_LENGTH_OF_ARRAY(callCounts); ++callCountNdx)
3565 withoutBarrierGroup->addChild(new ConcurrentAtomicCounterCase(
3566 m_context,
3567 (std::string("atomic_counter_dispatch_") + de::toString(callCounts[callCountNdx]) +
3568 "_calls_" + counterInvocations[invocationNdx].name + "_invocations")
3569 .c_str(),
3570 "", callCounts[callCountNdx], counterInvocations[invocationNdx].count));
3571
3572 for (int invocationNdx = 0; invocationNdx < DE_LENGTH_OF_ARRAY(counterInvocations); ++invocationNdx)
3573 for (int callCountNdx = 0; callCountNdx < DE_LENGTH_OF_ARRAY(callCounts); ++callCountNdx)
3574 withoutBarrierGroup->addChild(new ConcurrentSSBOAtomicCounterMixedCase(
3575 m_context,
3576 (std::string("ssbo_atomic_counter_mixed_dispatch_") +
3577 de::toString(callCounts[callCountNdx]) + "_calls_" +
3578 counterInvocations[invocationNdx].name + "_invocations")
3579 .c_str(),
3580 "", callCounts[callCountNdx], counterInvocations[invocationNdx].count));
3581 }
3582 }
3583 }
3584 }
3585
3586 } // namespace Functional
3587 } // namespace gles31
3588 } // namespace deqp
3589