1 /*------------------------------------------------------------------------
2  * Vulkan Conformance Tests
3  * ------------------------
4  *
5  * Copyright (c) 2015 The Khronos Group Inc.
6  * Copyright (c) 2015 Samsung Electronics Co., Ltd.
7  * Copyright (c) 2016 The Android Open Source Project
8  *
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  *
21  *//*!
22  * \file
23  * \brief Common built-in function tests.
24  *//*--------------------------------------------------------------------*/
25 
26 #include "vktShaderCommonFunctionTests.hpp"
27 #include "vktShaderExecutor.hpp"
28 #include "vkQueryUtil.hpp"
29 #include "gluContextInfo.hpp"
30 #include "tcuTestLog.hpp"
31 #include "tcuFormatUtil.hpp"
32 #include "tcuFloat.hpp"
33 #include "tcuInterval.hpp"
34 #include "tcuFloatFormat.hpp"
35 #include "tcuVectorUtil.hpp"
36 #include "deRandom.hpp"
37 #include "deMath.h"
38 #include "deString.h"
39 #include "deArrayUtil.hpp"
40 #include "deSharedPtr.hpp"
41 #include <algorithm>
42 
43 namespace vkt
44 {
45 
46 namespace shaderexecutor
47 {
48 
49 using std::string;
50 using std::vector;
51 using tcu::TestLog;
52 
53 using tcu::IVec2;
54 using tcu::IVec3;
55 using tcu::IVec4;
56 using tcu::Vec2;
57 using tcu::Vec3;
58 using tcu::Vec4;
59 
60 namespace
61 {
62 
63 // Utilities
64 
65 template <typename T, int Size>
66 struct VecArrayAccess
67 {
68 public:
VecArrayAccessvkt::shaderexecutor::__anone4a428780111::VecArrayAccess69     VecArrayAccess(const void *ptr) : m_array((tcu::Vector<T, Size> *)ptr)
70     {
71     }
~VecArrayAccessvkt::shaderexecutor::__anone4a428780111::VecArrayAccess72     ~VecArrayAccess(void)
73     {
74     }
75 
operator []vkt::shaderexecutor::__anone4a428780111::VecArrayAccess76     const tcu::Vector<T, Size> &operator[](size_t offset) const
77     {
78         return m_array[offset];
79     }
operator []vkt::shaderexecutor::__anone4a428780111::VecArrayAccess80     tcu::Vector<T, Size> &operator[](size_t offset)
81     {
82         return m_array[offset];
83     }
84 
85 private:
86     tcu::Vector<T, Size> *m_array;
87 };
88 
89 template <typename T, int Size>
fillRandomVectors(de::Random & rnd,const tcu::Vector<T,Size> & minValue,const tcu::Vector<T,Size> & maxValue,void * dst,int numValues,int offset=0)90 static void fillRandomVectors(de::Random &rnd, const tcu::Vector<T, Size> &minValue,
91                               const tcu::Vector<T, Size> &maxValue, void *dst, int numValues, int offset = 0)
92 {
93     VecArrayAccess<T, Size> access(dst);
94     for (int ndx = 0; ndx < numValues; ndx++)
95         access[offset + ndx] = tcu::randomVector<T, Size>(rnd, minValue, maxValue);
96 }
97 
98 template <typename T>
fillRandomScalars(de::Random & rnd,T minValue,T maxValue,void * dst,int numValues,int offset=0)99 static void fillRandomScalars(de::Random &rnd, T minValue, T maxValue, void *dst, int numValues, int offset = 0)
100 {
101     T *typedPtr = (T *)dst;
102     for (int ndx = 0; ndx < numValues; ndx++)
103         typedPtr[offset + ndx] = de::randomScalar<T>(rnd, minValue, maxValue);
104 }
105 
getUlpDiff(float a,float b)106 inline uint32_t getUlpDiff(float a, float b)
107 {
108     const uint32_t aBits = tcu::Float32(a).bits();
109     const uint32_t bBits = tcu::Float32(b).bits();
110     return aBits > bBits ? aBits - bBits : bBits - aBits;
111 }
112 
getUlpDiffIgnoreZeroSign(float a,float b)113 inline uint32_t getUlpDiffIgnoreZeroSign(float a, float b)
114 {
115     if (tcu::Float32(a).isZero())
116         return getUlpDiff(tcu::Float32::construct(tcu::Float32(b).sign(), 0, 0).asFloat(), b);
117     else if (tcu::Float32(b).isZero())
118         return getUlpDiff(a, tcu::Float32::construct(tcu::Float32(a).sign(), 0, 0).asFloat());
119     else
120         return getUlpDiff(a, b);
121 }
122 
getMaxUlpDiffFromBits(int numAccurateBits,int numTotalBits)123 inline uint64_t getMaxUlpDiffFromBits(int numAccurateBits, int numTotalBits)
124 {
125     const int numGarbageBits = numTotalBits - numAccurateBits;
126     const uint64_t mask      = (1ull << numGarbageBits) - 1ull;
127 
128     return mask;
129 }
130 
getNumMantissaBits(glu::DataType type)131 static int getNumMantissaBits(glu::DataType type)
132 {
133     DE_ASSERT(glu::isDataTypeFloatOrVec(type) || glu::isDataTypeDoubleOrDVec(type));
134     return (glu::isDataTypeFloatOrVec(type) ? 23 : 52);
135 }
136 
getMinMantissaBits(glu::DataType type,glu::Precision precision)137 static int getMinMantissaBits(glu::DataType type, glu::Precision precision)
138 {
139     if (glu::isDataTypeDoubleOrDVec(type))
140     {
141         return tcu::Float64::MANTISSA_BITS;
142     }
143 
144     // Float case.
145     const int bits[] = {
146         7,                           // lowp
147         tcu::Float16::MANTISSA_BITS, // mediump
148         tcu::Float32::MANTISSA_BITS, // highp
149     };
150     DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(bits) == glu::PRECISION_LAST);
151     DE_ASSERT(de::inBounds<int>(precision, 0, DE_LENGTH_OF_ARRAY(bits)));
152     return bits[precision];
153 }
154 
getExponentBits(glu::DataType type)155 static int getExponentBits(glu::DataType type)
156 {
157     DE_ASSERT(glu::isDataTypeFloatOrVec(type) || glu::isDataTypeDoubleOrDVec(type));
158     return (glu::isDataTypeFloatOrVec(type) ? static_cast<int>(tcu::Float32::EXPONENT_BITS) :
159                                               static_cast<int>(tcu::Float64::EXPONENT_BITS));
160 }
161 
getExponentMask(int exponentBits)162 static uint32_t getExponentMask(int exponentBits)
163 {
164     DE_ASSERT(exponentBits > 0);
165     return ((1u << exponentBits) - 1u);
166 }
167 
getComponentByteSize(glu::DataType type)168 static int getComponentByteSize(glu::DataType type)
169 {
170     const glu::DataType scalarType = glu::getDataTypeScalarType(type);
171 
172     DE_ASSERT(scalarType == glu::TYPE_FLOAT || scalarType == glu::TYPE_FLOAT16 || scalarType == glu::TYPE_DOUBLE ||
173               scalarType == glu::TYPE_INT || scalarType == glu::TYPE_UINT || scalarType == glu::TYPE_INT8 ||
174               scalarType == glu::TYPE_UINT8 || scalarType == glu::TYPE_INT16 || scalarType == glu::TYPE_UINT16 ||
175               scalarType == glu::TYPE_BOOL);
176 
177     switch (scalarType)
178     {
179     case glu::TYPE_INT8:
180     case glu::TYPE_UINT8:
181         return 1;
182     case glu::TYPE_INT16:
183     case glu::TYPE_UINT16:
184     case glu::TYPE_FLOAT16:
185         return 2;
186     case glu::TYPE_BOOL:
187     case glu::TYPE_INT:
188     case glu::TYPE_UINT:
189     case glu::TYPE_FLOAT:
190         return 4;
191     case glu::TYPE_DOUBLE:
192         return 8;
193     default:
194         DE_ASSERT(false);
195         break;
196     }
197     // Unreachable.
198     return 0;
199 }
200 
getScalarSizes(const vector<Symbol> & symbols)201 static vector<int> getScalarSizes(const vector<Symbol> &symbols)
202 {
203     vector<int> sizes(symbols.size());
204     for (int ndx = 0; ndx < (int)symbols.size(); ++ndx)
205         sizes[ndx] = symbols[ndx].varType.getScalarSize();
206     return sizes;
207 }
208 
getComponentByteSizes(const vector<Symbol> & symbols)209 static vector<int> getComponentByteSizes(const vector<Symbol> &symbols)
210 {
211     vector<int> sizes;
212     sizes.reserve(symbols.size());
213     for (const auto &sym : symbols)
214         sizes.push_back(getComponentByteSize(sym.varType.getBasicType()));
215     return sizes;
216 }
217 
computeTotalByteSize(const vector<Symbol> & symbols)218 static int computeTotalByteSize(const vector<Symbol> &symbols)
219 {
220     int totalSize = 0;
221     for (const auto &sym : symbols)
222         totalSize += getComponentByteSize(sym.varType.getBasicType()) * sym.varType.getScalarSize();
223     return totalSize;
224 }
225 
getInputOutputPointers(const vector<Symbol> & symbols,vector<uint8_t> & data,const int numValues)226 static vector<void *> getInputOutputPointers(const vector<Symbol> &symbols, vector<uint8_t> &data, const int numValues)
227 {
228     vector<void *> pointers(symbols.size());
229     int curScalarOffset = 0;
230 
231     for (int varNdx = 0; varNdx < (int)symbols.size(); ++varNdx)
232     {
233         const Symbol &var         = symbols[varNdx];
234         const int scalarSize      = var.varType.getScalarSize();
235         const auto componentBytes = getComponentByteSize(var.varType.getBasicType());
236 
237         // Uses planar layout as input/output specs do not support strides.
238         pointers[varNdx] = &data[curScalarOffset];
239         curScalarOffset += scalarSize * numValues * componentBytes;
240     }
241 
242     DE_ASSERT(curScalarOffset == (int)data.size());
243 
244     return pointers;
245 }
246 
checkTypeSupport(Context & context,glu::DataType dataType)247 void checkTypeSupport(Context &context, glu::DataType dataType)
248 {
249     if (glu::isDataTypeDoubleOrDVec(dataType))
250     {
251         const auto &vki           = context.getInstanceInterface();
252         const auto physicalDevice = context.getPhysicalDevice();
253 
254         const auto features = vk::getPhysicalDeviceFeatures(vki, physicalDevice);
255         if (!features.shaderFloat64)
256             TCU_THROW(NotSupportedError, "64-bit floats not supported by the implementation");
257     }
258 }
259 
260 // \todo [2013-08-08 pyry] Make generic utility and move to glu?
261 
262 struct HexFloat
263 {
264     const float value;
HexFloatvkt::shaderexecutor::__anone4a428780111::HexFloat265     HexFloat(const float value_) : value(value_)
266     {
267     }
268 };
269 
operator <<(std::ostream & str,const HexFloat & v)270 std::ostream &operator<<(std::ostream &str, const HexFloat &v)
271 {
272     return str << v.value << " / " << tcu::toHex(tcu::Float32(v.value).bits());
273 }
274 
275 struct HexDouble
276 {
277     const double value;
HexDoublevkt::shaderexecutor::__anone4a428780111::HexDouble278     HexDouble(const double value_) : value(value_)
279     {
280     }
281 };
282 
operator <<(std::ostream & str,const HexDouble & v)283 std::ostream &operator<<(std::ostream &str, const HexDouble &v)
284 {
285     return str << v.value << " / " << tcu::toHex(tcu::Float64(v.value).bits());
286 }
287 
288 struct HexBool
289 {
290     const uint32_t value;
HexBoolvkt::shaderexecutor::__anone4a428780111::HexBool291     HexBool(const uint32_t value_) : value(value_)
292     {
293     }
294 };
295 
operator <<(std::ostream & str,const HexBool & v)296 std::ostream &operator<<(std::ostream &str, const HexBool &v)
297 {
298     return str << (v.value ? "true" : "false") << " / " << tcu::toHex(v.value);
299 }
300 
301 struct VarValue
302 {
303     const glu::VarType &type;
304     const void *value;
305 
VarValuevkt::shaderexecutor::__anone4a428780111::VarValue306     VarValue(const glu::VarType &type_, const void *value_) : type(type_), value(value_)
307     {
308     }
309 };
310 
operator <<(std::ostream & str,const VarValue & varValue)311 std::ostream &operator<<(std::ostream &str, const VarValue &varValue)
312 {
313     DE_ASSERT(varValue.type.isBasicType());
314 
315     const glu::DataType basicType  = varValue.type.getBasicType();
316     const glu::DataType scalarType = glu::getDataTypeScalarType(basicType);
317     const int numComponents        = glu::getDataTypeScalarSize(basicType);
318 
319     if (numComponents > 1)
320         str << glu::getDataTypeName(basicType) << "(";
321 
322     for (int compNdx = 0; compNdx < numComponents; compNdx++)
323     {
324         if (compNdx != 0)
325             str << ", ";
326 
327         switch (scalarType)
328         {
329         case glu::TYPE_FLOAT:
330             str << HexFloat(((const float *)varValue.value)[compNdx]);
331             break;
332         case glu::TYPE_INT:
333             str << ((const int32_t *)varValue.value)[compNdx];
334             break;
335         case glu::TYPE_UINT:
336             str << tcu::toHex(((const uint32_t *)varValue.value)[compNdx]);
337             break;
338         case glu::TYPE_BOOL:
339             str << HexBool(((const uint32_t *)varValue.value)[compNdx]);
340             break;
341         case glu::TYPE_DOUBLE:
342             str << HexDouble(((const double *)varValue.value)[compNdx]);
343             break;
344 
345         default:
346             DE_ASSERT(false);
347         }
348     }
349 
350     if (numComponents > 1)
351         str << ")";
352 
353     return str;
354 }
355 
getCommonFuncCaseName(glu::DataType baseType,glu::Precision precision)356 static std::string getCommonFuncCaseName(glu::DataType baseType, glu::Precision precision)
357 {
358     const bool isDouble = glu::isDataTypeDoubleOrDVec(baseType);
359     return string(glu::getDataTypeName(baseType)) + (isDouble ? "" : getPrecisionPostfix(precision)) + "_compute";
360 }
361 
362 template <class TestClass>
addFunctionCases(tcu::TestCaseGroup * parent,const char * functionName,const std::vector<glu::DataType> & scalarTypes)363 static void addFunctionCases(tcu::TestCaseGroup *parent, const char *functionName,
364                              const std::vector<glu::DataType> &scalarTypes)
365 {
366     tcu::TestCaseGroup *group = new tcu::TestCaseGroup(parent->getTestContext(), functionName);
367     parent->addChild(group);
368 
369     for (const auto scalarType : scalarTypes)
370     {
371         const bool isDouble   = glu::isDataTypeDoubleOrDVec(scalarType);
372         const int lowestPrec  = (isDouble ? glu::PRECISION_LAST : glu::PRECISION_MEDIUMP);
373         const int highestPrec = (isDouble ? glu::PRECISION_LAST : glu::PRECISION_HIGHP);
374 
375         for (int vecSize = 1; vecSize <= 4; vecSize++)
376         {
377             for (int prec = lowestPrec; prec <= highestPrec; prec++)
378             {
379                 group->addChild(new TestClass(parent->getTestContext(), glu::DataType(scalarType + vecSize - 1),
380                                               glu::Precision(prec)));
381             }
382         }
383     }
384 }
385 
386 // CommonFunctionCase
387 
388 class CommonFunctionCase : public TestCase
389 {
390 public:
391     CommonFunctionCase(tcu::TestContext &testCtx, const char *name);
392     ~CommonFunctionCase(void);
initPrograms(vk::SourceCollections & programCollection) const393     virtual void initPrograms(vk::SourceCollections &programCollection) const
394     {
395         generateSources(glu::SHADERTYPE_COMPUTE, m_spec, programCollection);
396     }
397 
398     virtual TestInstance *createInstance(Context &context) const = 0;
399 
400 protected:
401     CommonFunctionCase(const CommonFunctionCase &);
402     CommonFunctionCase &operator=(const CommonFunctionCase &);
403 
404     ShaderSpec m_spec;
405     const int m_numValues;
406 };
407 
CommonFunctionCase(tcu::TestContext & testCtx,const char * name)408 CommonFunctionCase::CommonFunctionCase(tcu::TestContext &testCtx, const char *name)
409     : TestCase(testCtx, name)
410     , m_numValues(100)
411 {
412 }
413 
~CommonFunctionCase(void)414 CommonFunctionCase::~CommonFunctionCase(void)
415 {
416 }
417 
418 // CommonFunctionTestInstance
419 
420 class CommonFunctionTestInstance : public TestInstance
421 {
422 public:
CommonFunctionTestInstance(Context & context,const ShaderSpec & spec,int numValues,const char * name)423     CommonFunctionTestInstance(Context &context, const ShaderSpec &spec, int numValues, const char *name)
424         : TestInstance(context)
425         , m_spec(spec)
426         , m_numValues(numValues)
427         , m_name(name)
428         , m_executor(createExecutor(context, glu::SHADERTYPE_COMPUTE, spec))
429     {
430     }
431     virtual tcu::TestStatus iterate(void);
432 
433 protected:
434     virtual void getInputValues(int numValues, void *const *values) const       = 0;
435     virtual bool compare(const void *const *inputs, const void *const *outputs) = 0;
436 
437     const ShaderSpec m_spec;
438     const int m_numValues;
439 
440     // \todo [2017-03-07 pyry] Hack used to generate seeds for test cases - get rid of this.
441     const char *m_name;
442 
443     std::ostringstream m_failMsg; //!< Comparison failure help message.
444 
445     de::UniquePtr<ShaderExecutor> m_executor;
446 };
447 
iterate(void)448 tcu::TestStatus CommonFunctionTestInstance::iterate(void)
449 {
450     const int numInputBytes  = computeTotalByteSize(m_spec.inputs);
451     const int numOutputBytes = computeTotalByteSize(m_spec.outputs);
452     vector<uint8_t> inputData(numInputBytes * m_numValues);
453     vector<uint8_t> outputData(numOutputBytes * m_numValues);
454     const vector<void *> inputPointers  = getInputOutputPointers(m_spec.inputs, inputData, m_numValues);
455     const vector<void *> outputPointers = getInputOutputPointers(m_spec.outputs, outputData, m_numValues);
456 
457     // Initialize input data.
458     getInputValues(m_numValues, &inputPointers[0]);
459 
460     // Execute shader.
461     m_executor->execute(m_numValues, &inputPointers[0], &outputPointers[0]);
462 
463     // Compare results.
464     {
465         const vector<int> inScalarSizes    = getScalarSizes(m_spec.inputs);
466         const vector<int> outScalarSizes   = getScalarSizes(m_spec.outputs);
467         const vector<int> inCompByteSizes  = getComponentByteSizes(m_spec.inputs);
468         const vector<int> outCompByteSizes = getComponentByteSizes(m_spec.outputs);
469         vector<void *> curInputPtr(inputPointers.size());
470         vector<void *> curOutputPtr(outputPointers.size());
471         int numFailed             = 0;
472         tcu::TestContext &testCtx = m_context.getTestContext();
473 
474         for (int valNdx = 0; valNdx < m_numValues; valNdx++)
475         {
476             // Set up pointers for comparison.
477             for (int inNdx = 0; inNdx < (int)curInputPtr.size(); ++inNdx)
478                 curInputPtr[inNdx] =
479                     (uint8_t *)inputPointers[inNdx] + inScalarSizes[inNdx] * inCompByteSizes[inNdx] * valNdx;
480 
481             for (int outNdx = 0; outNdx < (int)curOutputPtr.size(); ++outNdx)
482                 curOutputPtr[outNdx] =
483                     (uint8_t *)outputPointers[outNdx] + outScalarSizes[outNdx] * outCompByteSizes[outNdx] * valNdx;
484 
485             if (!compare(&curInputPtr[0], &curOutputPtr[0]))
486             {
487                 // \todo [2013-08-08 pyry] We probably want to log reference value as well?
488 
489                 testCtx.getLog() << TestLog::Message << "ERROR: comparison failed for value " << valNdx << ":\n  "
490                                  << m_failMsg.str() << TestLog::EndMessage;
491 
492                 testCtx.getLog() << TestLog::Message << "  inputs:" << TestLog::EndMessage;
493                 for (int inNdx = 0; inNdx < (int)curInputPtr.size(); inNdx++)
494                     testCtx.getLog() << TestLog::Message << "    " << m_spec.inputs[inNdx].name << " = "
495                                      << VarValue(m_spec.inputs[inNdx].varType, curInputPtr[inNdx])
496                                      << TestLog::EndMessage;
497 
498                 testCtx.getLog() << TestLog::Message << "  outputs:" << TestLog::EndMessage;
499                 for (int outNdx = 0; outNdx < (int)curOutputPtr.size(); outNdx++)
500                     testCtx.getLog() << TestLog::Message << "    " << m_spec.outputs[outNdx].name << " = "
501                                      << VarValue(m_spec.outputs[outNdx].varType, curOutputPtr[outNdx])
502                                      << TestLog::EndMessage;
503 
504                 m_failMsg.str("");
505                 m_failMsg.clear();
506                 numFailed += 1;
507             }
508         }
509 
510         testCtx.getLog() << TestLog::Message << (m_numValues - numFailed) << " / " << m_numValues << " values passed"
511                          << TestLog::EndMessage;
512 
513         if (numFailed == 0)
514             return tcu::TestStatus::pass("Pass");
515         else
516             return tcu::TestStatus::fail("Result comparison failed");
517     }
518 }
519 
520 // Test cases
521 
522 class AbsCaseInstance : public CommonFunctionTestInstance
523 {
524 public:
AbsCaseInstance(Context & context,const ShaderSpec & spec,int numValues,const char * name)525     AbsCaseInstance(Context &context, const ShaderSpec &spec, int numValues, const char *name)
526         : CommonFunctionTestInstance(context, spec, numValues, name)
527     {
528     }
529 
getInputValues(int numValues,void * const * values) const530     void getInputValues(int numValues, void *const *values) const
531     {
532         const IVec2 intRanges[] = {IVec2(-(1 << 7) + 1, (1 << 7) - 1), IVec2(-(1 << 15) + 1, (1 << 15) - 1),
533                                    IVec2(0x80000001, 0x7fffffff)};
534 
535         de::Random rnd(deStringHash(m_name) ^ 0x235facu);
536         const glu::DataType type       = m_spec.inputs[0].varType.getBasicType();
537         const glu::Precision precision = m_spec.inputs[0].varType.getPrecision();
538         const int scalarSize           = glu::getDataTypeScalarSize(type);
539 
540         DE_ASSERT(!glu::isDataTypeFloatOrVec(type));
541 
542         fillRandomScalars(rnd, intRanges[precision].x(), intRanges[precision].y(), values[0], numValues * scalarSize);
543     }
544 
compare(const void * const * inputs,const void * const * outputs)545     bool compare(const void *const *inputs, const void *const *outputs)
546     {
547         const glu::DataType type = m_spec.inputs[0].varType.getBasicType();
548         const int scalarSize     = glu::getDataTypeScalarSize(type);
549 
550         DE_ASSERT(!glu::isDataTypeFloatOrVec(type));
551 
552         for (int compNdx = 0; compNdx < scalarSize; compNdx++)
553         {
554             const int in0  = ((const int *)inputs[0])[compNdx];
555             const int out0 = ((const int *)outputs[0])[compNdx];
556             const int ref0 = de::abs(in0);
557 
558             if (out0 != ref0)
559             {
560                 m_failMsg << "Expected [" << compNdx << "] = " << ref0;
561                 return false;
562             }
563         }
564 
565         return true;
566     }
567 };
568 
569 class AbsCase : public CommonFunctionCase
570 {
571 public:
AbsCase(tcu::TestContext & testCtx,glu::DataType baseType,glu::Precision precision)572     AbsCase(tcu::TestContext &testCtx, glu::DataType baseType, glu::Precision precision)
573         : CommonFunctionCase(testCtx, getCommonFuncCaseName(baseType, precision).c_str())
574     {
575         m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
576         m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision)));
577         m_spec.source = "out0 = abs(in0);";
578     }
579 
createInstance(Context & ctx) const580     TestInstance *createInstance(Context &ctx) const
581     {
582         return new AbsCaseInstance(ctx, m_spec, m_numValues, getName());
583     }
584 };
585 
586 class SignCaseInstance : public CommonFunctionTestInstance
587 {
588 public:
SignCaseInstance(Context & context,const ShaderSpec & spec,int numValues,const char * name)589     SignCaseInstance(Context &context, const ShaderSpec &spec, int numValues, const char *name)
590         : CommonFunctionTestInstance(context, spec, numValues, name)
591     {
592     }
593 
getInputValues(int numValues,void * const * values) const594     void getInputValues(int numValues, void *const *values) const
595     {
596         const IVec2 intRanges[] = {IVec2(-(1 << 7), (1 << 7) - 1), IVec2(-(1 << 15), (1 << 15) - 1),
597                                    IVec2(0x80000000, 0x7fffffff)};
598 
599         de::Random rnd(deStringHash(m_name) ^ 0x324u);
600         const glu::DataType type       = m_spec.inputs[0].varType.getBasicType();
601         const glu::Precision precision = m_spec.inputs[0].varType.getPrecision();
602         const int scalarSize           = glu::getDataTypeScalarSize(type);
603 
604         DE_ASSERT(!glu::isDataTypeFloatOrVec(type));
605 
606         std::fill((int *)values[0] + scalarSize * 0, (int *)values[0] + scalarSize * 1, +1);
607         std::fill((int *)values[0] + scalarSize * 1, (int *)values[0] + scalarSize * 2, -1);
608         std::fill((int *)values[0] + scalarSize * 2, (int *)values[0] + scalarSize * 3, 0);
609         fillRandomScalars(rnd, intRanges[precision].x(), intRanges[precision].y(), (int *)values[0] + scalarSize * 3,
610                           (numValues - 3) * scalarSize);
611     }
612 
compare(const void * const * inputs,const void * const * outputs)613     bool compare(const void *const *inputs, const void *const *outputs)
614     {
615         const glu::DataType type = m_spec.inputs[0].varType.getBasicType();
616         const int scalarSize     = glu::getDataTypeScalarSize(type);
617 
618         DE_ASSERT(!glu::isDataTypeFloatOrVec(type));
619 
620         for (int compNdx = 0; compNdx < scalarSize; compNdx++)
621         {
622             const int in0  = ((const int *)inputs[0])[compNdx];
623             const int out0 = ((const int *)outputs[0])[compNdx];
624             const int ref0 = in0 < 0 ? -1 : in0 > 0 ? +1 : 0;
625 
626             if (out0 != ref0)
627             {
628                 m_failMsg << "Expected [" << compNdx << "] = " << ref0;
629                 return false;
630             }
631         }
632 
633         return true;
634     }
635 };
636 
637 class SignCase : public CommonFunctionCase
638 {
639 public:
SignCase(tcu::TestContext & testCtx,glu::DataType baseType,glu::Precision precision)640     SignCase(tcu::TestContext &testCtx, glu::DataType baseType, glu::Precision precision)
641         : CommonFunctionCase(testCtx, getCommonFuncCaseName(baseType, precision).c_str())
642     {
643         m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
644         m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision)));
645         m_spec.source = "out0 = sign(in0);";
646     }
647 
createInstance(Context & ctx) const648     TestInstance *createInstance(Context &ctx) const
649     {
650         return new SignCaseInstance(ctx, m_spec, m_numValues, getName());
651     }
652 };
653 
infNanRandomFloats(int numValues,void * const * values,const char * name,const ShaderSpec & spec)654 static void infNanRandomFloats(int numValues, void *const *values, const char *name, const ShaderSpec &spec)
655 {
656     constexpr uint64_t kOne = 1;
657     de::Random rnd(deStringHash(name) ^ 0xc2a39fu);
658     const glu::DataType type       = spec.inputs[0].varType.getBasicType();
659     const glu::Precision precision = spec.inputs[0].varType.getPrecision();
660     const int scalarSize           = glu::getDataTypeScalarSize(type);
661     const int minMantissaBits      = getMinMantissaBits(type, precision);
662     const int numMantissaBits      = getNumMantissaBits(type);
663     const uint64_t mantissaMask =
664         ~getMaxUlpDiffFromBits(minMantissaBits, numMantissaBits) & ((kOne << numMantissaBits) - kOne);
665     const int exponentBits      = getExponentBits(type);
666     const uint32_t exponentMask = getExponentMask(exponentBits);
667     const bool isDouble         = glu::isDataTypeDoubleOrDVec(type);
668     const uint64_t exponentBias = (isDouble ? static_cast<uint64_t>(tcu::Float64::EXPONENT_BIAS) :
669                                               static_cast<uint64_t>(tcu::Float32::EXPONENT_BIAS));
670 
671     int numInf = 0;
672     int numNan = 0;
673     for (int valNdx = 0; valNdx < numValues * scalarSize; valNdx++)
674     {
675         // Roughly 25% chance of each of Inf and NaN
676         const bool isInf    = rnd.getFloat() > 0.75f;
677         const bool isNan    = !isInf && rnd.getFloat() > 0.66f;
678         const uint64_t m    = rnd.getUint64() & mantissaMask;
679         const uint64_t e    = static_cast<uint64_t>(rnd.getUint32() & exponentMask);
680         const uint64_t sign = static_cast<uint64_t>(rnd.getUint32() & 0x1u);
681         // Ensure the 'quiet' bit is set on NaNs (also ensures we don't generate inf by mistake)
682         const uint64_t mantissa = isInf ? 0 : (isNan ? ((kOne << (numMantissaBits - 1)) | m) : m);
683         const uint64_t exp      = (isNan || isInf) ? exponentMask : std::min(e, exponentBias);
684         const uint64_t value =
685             (sign << (numMantissaBits + exponentBits)) | (exp << numMantissaBits) | static_cast<uint32_t>(mantissa);
686         if (isInf)
687             numInf++;
688         if (isNan)
689             numNan++;
690 
691         if (isDouble)
692         {
693             DE_ASSERT(tcu::Float64(value).isInf() == isInf && tcu::Float64(value).isNaN() == isNan);
694             ((uint64_t *)values[0])[valNdx] = value;
695         }
696         else
697         {
698             const auto value32 = static_cast<uint32_t>(value);
699             DE_ASSERT(tcu::Float32(value32).isInf() == isInf && tcu::Float32(value32).isNaN() == isNan);
700             ((uint32_t *)values[0])[valNdx] = value32;
701         }
702     }
703     // Check for minimal coverage of intended cases.
704     DE_ASSERT(0 < numInf);
705     DE_ASSERT(0 < numNan);
706     DE_ASSERT(numInf + numNan < numValues * scalarSize);
707 
708     // Release build does not use them
709     DE_UNREF(numInf);
710     DE_UNREF(numNan);
711 }
712 
713 class IsnanCaseInstance : public CommonFunctionTestInstance
714 {
715 public:
IsnanCaseInstance(Context & context,const ShaderSpec & spec,int numValues,const char * name)716     IsnanCaseInstance(Context &context, const ShaderSpec &spec, int numValues, const char *name)
717         : CommonFunctionTestInstance(context, spec, numValues, name)
718     {
719     }
720 
getInputValues(int numValues,void * const * values) const721     void getInputValues(int numValues, void *const *values) const
722     {
723         infNanRandomFloats(numValues, values, m_name, m_spec);
724     }
725 
compare(const void * const * inputs,const void * const * outputs)726     bool compare(const void *const *inputs, const void *const *outputs)
727     {
728         const glu::DataType type       = m_spec.inputs[0].varType.getBasicType();
729         const glu::Precision precision = m_spec.inputs[0].varType.getPrecision();
730         const int scalarSize           = glu::getDataTypeScalarSize(type);
731         const bool isDouble            = glu::isDataTypeDoubleOrDVec(type);
732 
733         for (int compNdx = 0; compNdx < scalarSize; compNdx++)
734         {
735             const bool out0 = reinterpret_cast<const uint32_t *>(outputs[0])[compNdx] != 0;
736             bool ok;
737             bool ref;
738 
739             if (isDouble)
740             {
741                 const double in0 = reinterpret_cast<const double *>(inputs[0])[compNdx];
742                 ref              = tcu::Float64(in0).isNaN();
743                 ok               = (out0 == ref);
744             }
745             else
746             {
747                 const float in0 = reinterpret_cast<const float *>(inputs[0])[compNdx];
748                 ref             = tcu::Float32(in0).isNaN();
749 
750                 // NaN support only required for highp. Otherwise just check for false positives.
751                 if (precision == glu::PRECISION_HIGHP)
752                     ok = (out0 == ref);
753                 else
754                     ok = ref || !out0;
755             }
756 
757             if (!ok)
758             {
759                 m_failMsg << "Expected [" << compNdx << "] = " << (ref ? "true" : "false");
760                 return false;
761             }
762         }
763 
764         return true;
765     }
766 };
767 
768 class IsnanCase : public CommonFunctionCase
769 {
770 public:
IsnanCase(tcu::TestContext & testCtx,glu::DataType baseType,glu::Precision precision)771     IsnanCase(tcu::TestContext &testCtx, glu::DataType baseType, glu::Precision precision)
772         : CommonFunctionCase(testCtx, getCommonFuncCaseName(baseType, precision).c_str())
773     {
774         DE_ASSERT(glu::isDataTypeFloatOrVec(baseType) || glu::isDataTypeDoubleOrDVec(baseType));
775 
776         const int vecSize            = glu::getDataTypeScalarSize(baseType);
777         const glu::DataType boolType = vecSize > 1 ? glu::getDataTypeBoolVec(vecSize) : glu::TYPE_BOOL;
778 
779         m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
780         m_spec.outputs.push_back(Symbol("out0", glu::VarType(boolType, glu::PRECISION_LAST)));
781         m_spec.source = "out0 = isnan(in0);";
782     }
783 
checkSupport(Context & context) const784     void checkSupport(Context &context) const
785     {
786         checkTypeSupport(context, m_spec.inputs[0].varType.getBasicType());
787     }
788 
createInstance(Context & ctx) const789     TestInstance *createInstance(Context &ctx) const
790     {
791         return new IsnanCaseInstance(ctx, m_spec, m_numValues, getName());
792     }
793 };
794 
795 class IsinfCaseInstance : public CommonFunctionTestInstance
796 {
797 public:
IsinfCaseInstance(Context & context,const ShaderSpec & spec,int numValues,const char * name)798     IsinfCaseInstance(Context &context, const ShaderSpec &spec, int numValues, const char *name)
799         : CommonFunctionTestInstance(context, spec, numValues, name)
800     {
801     }
802 
getInputValues(int numValues,void * const * values) const803     void getInputValues(int numValues, void *const *values) const
804     {
805         infNanRandomFloats(numValues, values, m_name, m_spec);
806     }
807 
compare(const void * const * inputs,const void * const * outputs)808     bool compare(const void *const *inputs, const void *const *outputs)
809     {
810         const glu::DataType type       = m_spec.inputs[0].varType.getBasicType();
811         const glu::Precision precision = m_spec.inputs[0].varType.getPrecision();
812         const int scalarSize           = glu::getDataTypeScalarSize(type);
813         const bool isDouble            = glu::isDataTypeDoubleOrDVec(type);
814 
815         for (int compNdx = 0; compNdx < scalarSize; compNdx++)
816         {
817             const bool out0 = reinterpret_cast<const uint32_t *>(outputs[0])[compNdx] != 0;
818             bool ref;
819             bool ok;
820 
821             if (isDouble)
822             {
823                 const double in0 = reinterpret_cast<const double *>(inputs[0])[compNdx];
824                 ref              = tcu::Float64(in0).isInf();
825                 ok               = (out0 == ref);
826             }
827             else
828             {
829                 const float in0 = reinterpret_cast<const float *>(inputs[0])[compNdx];
830                 if (precision == glu::PRECISION_HIGHP)
831                 {
832                     // Only highp is required to support inf/nan
833                     ref = tcu::Float32(in0).isInf();
834                     ok  = (out0 == ref);
835                 }
836                 else
837                 {
838                     // Inf support is optional, check that inputs that are not Inf in mediump don't result in true.
839                     ref = tcu::Float16(in0).isInf();
840                     ok  = (out0 || !ref);
841                 }
842             }
843 
844             if (!ok)
845             {
846                 m_failMsg << "Expected [" << compNdx << "] = " << HexBool(ref);
847                 return false;
848             }
849         }
850 
851         return true;
852     }
853 };
854 
855 class IsinfCase : public CommonFunctionCase
856 {
857 public:
IsinfCase(tcu::TestContext & testCtx,glu::DataType baseType,glu::Precision precision)858     IsinfCase(tcu::TestContext &testCtx, glu::DataType baseType, glu::Precision precision)
859         : CommonFunctionCase(testCtx, getCommonFuncCaseName(baseType, precision).c_str())
860     {
861         DE_ASSERT(glu::isDataTypeFloatOrVec(baseType) || glu::isDataTypeDoubleOrDVec(baseType));
862 
863         const int vecSize            = glu::getDataTypeScalarSize(baseType);
864         const glu::DataType boolType = vecSize > 1 ? glu::getDataTypeBoolVec(vecSize) : glu::TYPE_BOOL;
865 
866         m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
867         m_spec.outputs.push_back(Symbol("out0", glu::VarType(boolType, glu::PRECISION_LAST)));
868         m_spec.source = "out0 = isinf(in0);";
869     }
870 
checkSupport(Context & context) const871     void checkSupport(Context &context) const
872     {
873         checkTypeSupport(context, m_spec.inputs[0].varType.getBasicType());
874     }
875 
createInstance(Context & ctx) const876     TestInstance *createInstance(Context &ctx) const
877     {
878         return new IsinfCaseInstance(ctx, m_spec, m_numValues, getName());
879     }
880 };
881 
882 class FloatBitsToUintIntCaseInstance : public CommonFunctionTestInstance
883 {
884 public:
FloatBitsToUintIntCaseInstance(Context & context,const ShaderSpec & spec,int numValues,const char * name)885     FloatBitsToUintIntCaseInstance(Context &context, const ShaderSpec &spec, int numValues, const char *name)
886         : CommonFunctionTestInstance(context, spec, numValues, name)
887     {
888     }
889 
getInputValues(int numValues,void * const * values) const890     void getInputValues(int numValues, void *const *values) const
891     {
892         const Vec2 ranges[] = {
893             Vec2(-2.0f, 2.0f), // lowp
894             Vec2(-1e3f, 1e3f), // mediump
895             Vec2(-1e7f, 1e7f)  // highp
896         };
897 
898         de::Random rnd(deStringHash(m_name) ^ 0x2790au);
899         const glu::DataType type       = m_spec.inputs[0].varType.getBasicType();
900         const glu::Precision precision = m_spec.inputs[0].varType.getPrecision();
901         const int scalarSize           = glu::getDataTypeScalarSize(type);
902 
903         fillRandomScalars(rnd, ranges[precision].x(), ranges[precision].y(), values[0], numValues * scalarSize);
904     }
905 
compare(const void * const * inputs,const void * const * outputs)906     bool compare(const void *const *inputs, const void *const *outputs)
907     {
908         const glu::DataType type       = m_spec.inputs[0].varType.getBasicType();
909         const glu::Precision precision = m_spec.inputs[0].varType.getPrecision();
910         const int scalarSize           = glu::getDataTypeScalarSize(type);
911 
912         const int minMantissaBits = getMinMantissaBits(type, precision);
913         const int numMantissaBits = getNumMantissaBits(type);
914         const int maxUlpDiff      = static_cast<int>(getMaxUlpDiffFromBits(minMantissaBits, numMantissaBits));
915 
916         for (int compNdx = 0; compNdx < scalarSize; compNdx++)
917         {
918             const float in0        = ((const float *)inputs[0])[compNdx];
919             const uint32_t out0    = ((const uint32_t *)outputs[0])[compNdx];
920             const uint32_t refOut0 = tcu::Float32(in0).bits();
921             const int ulpDiff      = de::abs((int)out0 - (int)refOut0);
922 
923             if (ulpDiff > maxUlpDiff)
924             {
925                 m_failMsg << "Expected [" << compNdx << "] = " << tcu::toHex(refOut0) << " with threshold "
926                           << tcu::toHex(maxUlpDiff) << ", got diff " << tcu::toHex(ulpDiff);
927                 return false;
928             }
929         }
930 
931         return true;
932     }
933 };
934 
935 class FloatBitsToUintIntCase : public CommonFunctionCase
936 {
937 public:
FloatBitsToUintIntCase(tcu::TestContext & testCtx,glu::DataType baseType,glu::Precision precision,bool outIsSigned)938     FloatBitsToUintIntCase(tcu::TestContext &testCtx, glu::DataType baseType, glu::Precision precision,
939                            bool outIsSigned)
940         : CommonFunctionCase(testCtx, getCommonFuncCaseName(baseType, precision).c_str())
941     {
942         const int vecSize           = glu::getDataTypeScalarSize(baseType);
943         const glu::DataType intType = outIsSigned ? (vecSize > 1 ? glu::getDataTypeIntVec(vecSize) : glu::TYPE_INT) :
944                                                     (vecSize > 1 ? glu::getDataTypeUintVec(vecSize) : glu::TYPE_UINT);
945 
946         m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision)));
947         m_spec.outputs.push_back(Symbol("out0", glu::VarType(intType, glu::PRECISION_HIGHP)));
948         m_spec.source = outIsSigned ? "out0 = floatBitsToInt(in0);" : "out0 = floatBitsToUint(in0);";
949     }
950 
createInstance(Context & ctx) const951     TestInstance *createInstance(Context &ctx) const
952     {
953         return new FloatBitsToUintIntCaseInstance(ctx, m_spec, m_numValues, getName());
954     }
955 };
956 
957 class FloatBitsToIntCase : public FloatBitsToUintIntCase
958 {
959 public:
FloatBitsToIntCase(tcu::TestContext & testCtx,glu::DataType baseType,glu::Precision precision)960     FloatBitsToIntCase(tcu::TestContext &testCtx, glu::DataType baseType, glu::Precision precision)
961         : FloatBitsToUintIntCase(testCtx, baseType, precision, true)
962     {
963     }
964 };
965 
966 class FloatBitsToUintCase : public FloatBitsToUintIntCase
967 {
968 public:
FloatBitsToUintCase(tcu::TestContext & testCtx,glu::DataType baseType,glu::Precision precision)969     FloatBitsToUintCase(tcu::TestContext &testCtx, glu::DataType baseType, glu::Precision precision)
970         : FloatBitsToUintIntCase(testCtx, baseType, precision, false)
971     {
972     }
973 };
974 
975 class BitsToFloatCaseInstance : public CommonFunctionTestInstance
976 {
977 public:
BitsToFloatCaseInstance(Context & context,const ShaderSpec & spec,int numValues,const char * name)978     BitsToFloatCaseInstance(Context &context, const ShaderSpec &spec, int numValues, const char *name)
979         : CommonFunctionTestInstance(context, spec, numValues, name)
980     {
981     }
982 
getInputValues(int numValues,void * const * values) const983     void getInputValues(int numValues, void *const *values) const
984     {
985         de::Random rnd(deStringHash(m_name) ^ 0xbbb225u);
986         const glu::DataType type = m_spec.inputs[0].varType.getBasicType();
987         const int scalarSize     = glu::getDataTypeScalarSize(type);
988         const Vec2 range(-1e8f, +1e8f);
989 
990         // \note Filled as floats.
991         fillRandomScalars(rnd, range.x(), range.y(), values[0], numValues * scalarSize);
992     }
993 
compare(const void * const * inputs,const void * const * outputs)994     bool compare(const void *const *inputs, const void *const *outputs)
995     {
996         const glu::DataType type  = m_spec.inputs[0].varType.getBasicType();
997         const int scalarSize      = glu::getDataTypeScalarSize(type);
998         const uint32_t maxUlpDiff = 0;
999 
1000         for (int compNdx = 0; compNdx < scalarSize; compNdx++)
1001         {
1002             const float in0        = ((const float *)inputs[0])[compNdx];
1003             const float out0       = ((const float *)outputs[0])[compNdx];
1004             const uint32_t ulpDiff = getUlpDiffIgnoreZeroSign(in0, out0);
1005 
1006             if (ulpDiff > maxUlpDiff)
1007             {
1008                 m_failMsg << "Expected [" << compNdx << "] = " << tcu::toHex(tcu::Float32(in0).bits())
1009                           << " with ULP threshold " << tcu::toHex(maxUlpDiff) << ", got ULP diff "
1010                           << tcu::toHex(ulpDiff);
1011                 return false;
1012             }
1013         }
1014 
1015         return true;
1016     }
1017 };
1018 
1019 class BitsToFloatCase : public CommonFunctionCase
1020 {
1021 public:
BitsToFloatCase(tcu::TestContext & testCtx,glu::DataType baseType)1022     BitsToFloatCase(tcu::TestContext &testCtx, glu::DataType baseType)
1023         : CommonFunctionCase(testCtx, getCommonFuncCaseName(baseType, glu::PRECISION_HIGHP).c_str())
1024     {
1025         const bool inIsSigned         = glu::isDataTypeIntOrIVec(baseType);
1026         const int vecSize             = glu::getDataTypeScalarSize(baseType);
1027         const glu::DataType floatType = vecSize > 1 ? glu::getDataTypeFloatVec(vecSize) : glu::TYPE_FLOAT;
1028 
1029         m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, glu::PRECISION_HIGHP)));
1030         m_spec.outputs.push_back(Symbol("out0", glu::VarType(floatType, glu::PRECISION_HIGHP)));
1031         m_spec.source = inIsSigned ? "out0 = intBitsToFloat(in0);" : "out0 = uintBitsToFloat(in0);";
1032     }
1033 
createInstance(Context & ctx) const1034     TestInstance *createInstance(Context &ctx) const
1035     {
1036         return new BitsToFloatCaseInstance(ctx, m_spec, m_numValues, getName());
1037     }
1038 };
1039 
1040 } // namespace
1041 
ShaderCommonFunctionTests(tcu::TestContext & testCtx)1042 ShaderCommonFunctionTests::ShaderCommonFunctionTests(tcu::TestContext &testCtx) : tcu::TestCaseGroup(testCtx, "common")
1043 {
1044 }
1045 
~ShaderCommonFunctionTests(void)1046 ShaderCommonFunctionTests::~ShaderCommonFunctionTests(void)
1047 {
1048 }
1049 
init(void)1050 void ShaderCommonFunctionTests::init(void)
1051 {
1052     static const std::vector<glu::DataType> kIntOnly(1u, glu::TYPE_INT);
1053     static const std::vector<glu::DataType> kFloatOnly(1u, glu::TYPE_FLOAT);
1054     static const std::vector<glu::DataType> kFloatAndDouble{glu::TYPE_FLOAT, glu::TYPE_DOUBLE};
1055 
1056     addFunctionCases<AbsCase>(this, "abs", kIntOnly);
1057     addFunctionCases<SignCase>(this, "sign", kIntOnly);
1058     addFunctionCases<IsnanCase>(this, "isnan", kFloatAndDouble);
1059     addFunctionCases<IsinfCase>(this, "isinf", kFloatAndDouble);
1060     addFunctionCases<FloatBitsToIntCase>(this, "floatbitstoint", kFloatOnly);
1061     addFunctionCases<FloatBitsToUintCase>(this, "floatbitstouint", kFloatOnly);
1062 
1063     // (u)intBitsToFloat()
1064     {
1065         tcu::TestCaseGroup *intGroup  = new tcu::TestCaseGroup(m_testCtx, "intbitstofloat");
1066         tcu::TestCaseGroup *uintGroup = new tcu::TestCaseGroup(m_testCtx, "uintbitstofloat");
1067 
1068         addChild(intGroup);
1069         addChild(uintGroup);
1070 
1071         for (int vecSize = 1; vecSize < 4; vecSize++)
1072         {
1073             const glu::DataType intType  = vecSize > 1 ? glu::getDataTypeIntVec(vecSize) : glu::TYPE_INT;
1074             const glu::DataType uintType = vecSize > 1 ? glu::getDataTypeUintVec(vecSize) : glu::TYPE_UINT;
1075 
1076             intGroup->addChild(new BitsToFloatCase(getTestContext(), intType));
1077             uintGroup->addChild(new BitsToFloatCase(getTestContext(), uintType));
1078         }
1079     }
1080 }
1081 
1082 } // namespace shaderexecutor
1083 } // namespace vkt
1084