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