/*------------------------------------------------------------------------- * drawElements Quality Program OpenGL ES 3.0 Module * ------------------------------------------------- * * Copyright 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *//*! * \file * \brief Common built-in function tests. *//*--------------------------------------------------------------------*/ #include "es3fShaderCommonFunctionTests.hpp" #include "glsShaderExecUtil.hpp" #include "tcuTestLog.hpp" #include "tcuFormatUtil.hpp" #include "tcuVectorUtil.hpp" #include "tcuFloat.hpp" #include "deRandom.hpp" #include "deMath.h" #include "deString.h" namespace deqp { namespace gles3 { namespace Functional { using std::vector; using std::string; using tcu::TestLog; using namespace gls::ShaderExecUtil; using tcu::Vec2; using tcu::Vec3; using tcu::Vec4; using tcu::IVec2; using tcu::IVec3; using tcu::IVec4; // Utilities template struct VecArrayAccess { public: VecArrayAccess (const void* ptr) : m_array((tcu::Vector*)ptr) {} ~VecArrayAccess (void) {} const tcu::Vector& operator[] (size_t offset) const { return m_array[offset]; } tcu::Vector& operator[] (size_t offset) { return m_array[offset]; } private: tcu::Vector* m_array; }; template static void fillRandomVectors (de::Random& rnd, const tcu::Vector& minValue, const tcu::Vector& maxValue, void* dst, int numValues, int offset = 0) { VecArrayAccess access(dst); for (int ndx = 0; ndx < numValues; ndx++) access[offset + ndx] = tcu::randomVector(rnd, minValue, maxValue); } template static void fillRandomScalars (de::Random& rnd, T minValue, T maxValue, void* dst, int numValues, int offset = 0) { T* typedPtr = (T*)dst; for (int ndx = 0; ndx < numValues; ndx++) typedPtr[offset + ndx] = de::randomScalar(rnd, minValue, maxValue); } inline int numBitsLostInOp (float input, float output) { const int inExp = tcu::Float32(input).exponent(); const int outExp = tcu::Float32(output).exponent(); return de::max(0, inExp-outExp); // Lost due to mantissa shift. } inline deUint32 getUlpDiff (float a, float b) { const deUint32 aBits = tcu::Float32(a).bits(); const deUint32 bBits = tcu::Float32(b).bits(); return aBits > bBits ? aBits - bBits : bBits - aBits; } inline deUint32 getUlpDiffIgnoreZeroSign (float a, float b) { if (tcu::Float32(a).isZero()) return getUlpDiff(tcu::Float32::construct(tcu::Float32(b).sign(), 0, 0).asFloat(), b); else if (tcu::Float32(b).isZero()) return getUlpDiff(a, tcu::Float32::construct(tcu::Float32(a).sign(), 0, 0).asFloat()); else return getUlpDiff(a, b); } inline bool supportsSignedZero (glu::Precision precision) { // \note GLSL ES 3.0 doesn't really require support for -0, but we require it for highp // as it is very widely supported. return precision == glu::PRECISION_HIGHP; } inline float getEpsFromMaxUlpDiff (float value, deUint32 ulpDiff) { const int exp = tcu::Float32(value).exponent(); return tcu::Float32::construct(+1, exp, (1u<<23) | ulpDiff).asFloat() - tcu::Float32::construct(+1, exp, 1u<<23).asFloat(); } inline deUint32 getMaxUlpDiffFromBits (int numAccurateBits) { const int numGarbageBits = 23-numAccurateBits; const deUint32 mask = (1u<(precision, 0, DE_LENGTH_OF_ARRAY(bits))); return bits[precision]; } // CommonFunctionCase class CommonFunctionCase : public TestCase { public: CommonFunctionCase (Context& context, const char* name, const char* description, glu::ShaderType shaderType); ~CommonFunctionCase (void); void init (void); void deinit (void); IterateResult iterate (void); protected: CommonFunctionCase (const CommonFunctionCase& other); CommonFunctionCase& operator= (const CommonFunctionCase& other); virtual void getInputValues (int numValues, void* const* values) const = 0; virtual bool compare (const void* const* inputs, const void* const* outputs) = 0; glu::ShaderType m_shaderType; ShaderSpec m_spec; int m_numValues; std::ostringstream m_failMsg; //!< Comparison failure help message. private: ShaderExecutor* m_executor; }; CommonFunctionCase::CommonFunctionCase (Context& context, const char* name, const char* description, glu::ShaderType shaderType) : TestCase (context, name, description) , m_shaderType (shaderType) , m_numValues (100) , m_executor (DE_NULL) { m_spec.version = glu::GLSL_VERSION_300_ES; } CommonFunctionCase::~CommonFunctionCase (void) { CommonFunctionCase::deinit(); } void CommonFunctionCase::init (void) { DE_ASSERT(!m_executor); m_executor = createExecutor(m_context.getRenderContext(), m_shaderType, m_spec); m_testCtx.getLog() << m_executor; if (!m_executor->isOk()) throw tcu::TestError("Compile failed"); } void CommonFunctionCase::deinit (void) { delete m_executor; m_executor = DE_NULL; } static vector getScalarSizes (const vector& symbols) { vector sizes(symbols.size()); for (int ndx = 0; ndx < (int)symbols.size(); ++ndx) sizes[ndx] = symbols[ndx].varType.getScalarSize(); return sizes; } static int computeTotalScalarSize (const vector& symbols) { int totalSize = 0; for (vector::const_iterator sym = symbols.begin(); sym != symbols.end(); ++sym) totalSize += sym->varType.getScalarSize(); return totalSize; } static vector getInputOutputPointers (const vector& symbols, vector& data, const int numValues) { vector pointers (symbols.size()); int curScalarOffset = 0; for (int varNdx = 0; varNdx < (int)symbols.size(); ++varNdx) { const Symbol& var = symbols[varNdx]; const int scalarSize = var.varType.getScalarSize(); // Uses planar layout as input/output specs do not support strides. pointers[varNdx] = &data[curScalarOffset]; curScalarOffset += scalarSize*numValues; } DE_ASSERT(curScalarOffset == (int)data.size()); return pointers; } // \todo [2013-08-08 pyry] Make generic utility and move to glu? struct HexFloat { const float value; HexFloat (const float value_) : value(value_) {} }; std::ostream& operator<< (std::ostream& str, const HexFloat& v) { return str << v.value << " / " << tcu::toHex(tcu::Float32(v.value).bits()); } struct HexBool { const deUint32 value; HexBool (const deUint32 value_) : value(value_) {} }; std::ostream& operator<< (std::ostream& str, const HexBool& v) { return str << (v.value ? "true" : "false") << " / " << tcu::toHex(v.value); } struct VarValue { const glu::VarType& type; const void* value; VarValue (const glu::VarType& type_, const void* value_) : type(type_), value(value_) {} }; std::ostream& operator<< (std::ostream& str, const VarValue& varValue) { DE_ASSERT(varValue.type.isBasicType()); const glu::DataType basicType = varValue.type.getBasicType(); const glu::DataType scalarType = glu::getDataTypeScalarType(basicType); const int numComponents = glu::getDataTypeScalarSize(basicType); if (numComponents > 1) str << glu::getDataTypeName(basicType) << "("; for (int compNdx = 0; compNdx < numComponents; compNdx++) { if (compNdx != 0) str << ", "; switch (scalarType) { case glu::TYPE_FLOAT: str << HexFloat(((const float*)varValue.value)[compNdx]); break; case glu::TYPE_INT: str << ((const deInt32*)varValue.value)[compNdx]; break; case glu::TYPE_UINT: str << tcu::toHex(((const deUint32*)varValue.value)[compNdx]); break; case glu::TYPE_BOOL: str << HexBool(((const deUint32*)varValue.value)[compNdx]); break; default: DE_ASSERT(false); } } if (numComponents > 1) str << ")"; return str; } CommonFunctionCase::IterateResult CommonFunctionCase::iterate (void) { const int numInputScalars = computeTotalScalarSize(m_spec.inputs); const int numOutputScalars = computeTotalScalarSize(m_spec.outputs); vector inputData (numInputScalars * m_numValues); vector outputData (numOutputScalars * m_numValues); const vector inputPointers = getInputOutputPointers(m_spec.inputs, inputData, m_numValues); const vector outputPointers = getInputOutputPointers(m_spec.outputs, outputData, m_numValues); // Initialize input data. getInputValues(m_numValues, &inputPointers[0]); // Execute shader. m_executor->useProgram(); m_executor->execute(m_numValues, &inputPointers[0], &outputPointers[0]); // Compare results. { const vector inScalarSizes = getScalarSizes(m_spec.inputs); const vector outScalarSizes = getScalarSizes(m_spec.outputs); vector curInputPtr (inputPointers.size()); vector curOutputPtr (outputPointers.size()); int numFailed = 0; for (int valNdx = 0; valNdx < m_numValues; valNdx++) { // Set up pointers for comparison. for (int inNdx = 0; inNdx < (int)curInputPtr.size(); ++inNdx) curInputPtr[inNdx] = (deUint32*)inputPointers[inNdx] + inScalarSizes[inNdx]*valNdx; for (int outNdx = 0; outNdx < (int)curOutputPtr.size(); ++outNdx) curOutputPtr[outNdx] = (deUint32*)outputPointers[outNdx] + outScalarSizes[outNdx]*valNdx; if (!compare(&curInputPtr[0], &curOutputPtr[0])) { // \todo [2013-08-08 pyry] We probably want to log reference value as well? m_testCtx.getLog() << TestLog::Message << "ERROR: comparison failed for value " << valNdx << ":\n " << m_failMsg.str() << TestLog::EndMessage; m_testCtx.getLog() << TestLog::Message << " inputs:" << TestLog::EndMessage; for (int inNdx = 0; inNdx < (int)curInputPtr.size(); inNdx++) m_testCtx.getLog() << TestLog::Message << " " << m_spec.inputs[inNdx].name << " = " << VarValue(m_spec.inputs[inNdx].varType, curInputPtr[inNdx]) << TestLog::EndMessage; m_testCtx.getLog() << TestLog::Message << " outputs:" << TestLog::EndMessage; for (int outNdx = 0; outNdx < (int)curOutputPtr.size(); outNdx++) m_testCtx.getLog() << TestLog::Message << " " << m_spec.outputs[outNdx].name << " = " << VarValue(m_spec.outputs[outNdx].varType, curOutputPtr[outNdx]) << TestLog::EndMessage; m_failMsg.str(""); m_failMsg.clear(); numFailed += 1; } } m_testCtx.getLog() << TestLog::Message << (m_numValues - numFailed) << " / " << m_numValues << " values passed" << TestLog::EndMessage; m_testCtx.setTestResult(numFailed == 0 ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, numFailed == 0 ? "Pass" : "Result comparison failed"); } return STOP; } static std::string getCommonFuncCaseName (glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType) { return string(glu::getDataTypeName(baseType)) + getPrecisionPostfix(precision) + getShaderTypePostfix(shaderType); } class AbsCase : public CommonFunctionCase { public: AbsCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType) : CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "abs", shaderType) { m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision))); m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision))); m_spec.source = "out0 = abs(in0);"; } void getInputValues (int numValues, void* const* values) const { const Vec2 floatRanges[] = { Vec2(-2.0f, 2.0f), // lowp Vec2(-1e3f, 1e3f), // mediump Vec2(-1e7f, 1e7f) // highp }; const IVec2 intRanges[] = { IVec2(-(1<<7)+1, (1<<7)-1), IVec2(-(1<<15)+1, (1<<15)-1), IVec2(0x80000001, 0x7fffffff) }; de::Random rnd (deStringHash(getName()) ^ 0x235facu); const glu::DataType type = m_spec.inputs[0].varType.getBasicType(); const glu::Precision precision = m_spec.inputs[0].varType.getPrecision(); const int scalarSize = glu::getDataTypeScalarSize(type); if (glu::isDataTypeFloatOrVec(type)) fillRandomScalars(rnd, floatRanges[precision].x(), floatRanges[precision].y(), values[0], numValues*scalarSize); else fillRandomScalars(rnd, intRanges[precision].x(), intRanges[precision].y(), values[0], numValues*scalarSize); } bool compare (const void* const* inputs, const void* const* outputs) { const glu::DataType type = m_spec.inputs[0].varType.getBasicType(); const glu::Precision precision = m_spec.inputs[0].varType.getPrecision(); const int scalarSize = glu::getDataTypeScalarSize(type); if (glu::isDataTypeFloatOrVec(type)) { const int mantissaBits = getMinMantissaBits(precision); const deUint32 maxUlpDiff = (1u<<(23-mantissaBits))-1u; for (int compNdx = 0; compNdx < scalarSize; compNdx++) { const float in0 = ((const float*)inputs[0])[compNdx]; const float out0 = ((const float*)outputs[0])[compNdx]; const float ref0 = de::abs(in0); const deUint32 ulpDiff0 = getUlpDiff(out0, ref0); if (ulpDiff0 > maxUlpDiff) { m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref0) << " with ULP threshold " << maxUlpDiff << ", got ULP diff " << ulpDiff0; return false; } } } else { for (int compNdx = 0; compNdx < scalarSize; compNdx++) { const int in0 = ((const int*)inputs[0])[compNdx]; const int out0 = ((const int*)outputs[0])[compNdx]; const int ref0 = de::abs(in0); if (out0 != ref0) { m_failMsg << "Expected [" << compNdx << "] = " << ref0; return false; } } } return true; } }; class SignCase : public CommonFunctionCase { public: SignCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType) : CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "sign", shaderType) { m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision))); m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision))); m_spec.source = "out0 = sign(in0);"; } void getInputValues (int numValues, void* const* values) const { const Vec2 floatRanges[] = { Vec2(-2.0f, 2.0f), // lowp Vec2(-1e4f, 1e4f), // mediump - note: may end up as inf Vec2(-1e8f, 1e8f) // highp - note: may end up as inf }; const IVec2 intRanges[] = { IVec2(-(1<<7), (1<<7)-1), IVec2(-(1<<15), (1<<15)-1), IVec2(0x80000000, 0x7fffffff) }; de::Random rnd (deStringHash(getName()) ^ 0x324u); const glu::DataType type = m_spec.inputs[0].varType.getBasicType(); const glu::Precision precision = m_spec.inputs[0].varType.getPrecision(); const int scalarSize = glu::getDataTypeScalarSize(type); if (glu::isDataTypeFloatOrVec(type)) { // Special cases. std::fill((float*)values[0], (float*)values[0] + scalarSize, +1.0f); std::fill((float*)values[0] + scalarSize*1, (float*)values[0] + scalarSize*2, -1.0f); std::fill((float*)values[0] + scalarSize*2, (float*)values[0] + scalarSize*3, 0.0f); fillRandomScalars(rnd, floatRanges[precision].x(), floatRanges[precision].y(), (float*)values[0] + scalarSize*3, (numValues-3)*scalarSize); } else { std::fill((int*)values[0], (int*)values[0] + scalarSize, +1); std::fill((int*)values[0] + scalarSize*1, (int*)values[0] + scalarSize*2, -1); std::fill((int*)values[0] + scalarSize*2, (int*)values[0] + scalarSize*3, 0); fillRandomScalars(rnd, intRanges[precision].x(), intRanges[precision].y(), (int*)values[0] + scalarSize*3, (numValues-3)*scalarSize); } } bool compare (const void* const* inputs, const void* const* outputs) { const glu::DataType type = m_spec.inputs[0].varType.getBasicType(); const glu::Precision precision = m_spec.inputs[0].varType.getPrecision(); const int scalarSize = glu::getDataTypeScalarSize(type); if (glu::isDataTypeFloatOrVec(type)) { // Both highp and mediump should be able to represent -1, 0, and +1 exactly const deUint32 maxUlpDiff = precision == glu::PRECISION_LOWP ? getMaxUlpDiffFromBits(getMinMantissaBits(precision)) : 0; for (int compNdx = 0; compNdx < scalarSize; compNdx++) { const float in0 = ((const float*)inputs[0])[compNdx]; const float out0 = ((const float*)outputs[0])[compNdx]; const float ref0 = in0 < 0.0f ? -1.0f : in0 > 0.0f ? +1.0f : 0.0f; const deUint32 ulpDiff0 = getUlpDiff(out0, ref0); if (ulpDiff0 > maxUlpDiff) { m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref0) << " with ULP threshold " << maxUlpDiff << ", got ULP diff " << ulpDiff0; return false; } } } else { for (int compNdx = 0; compNdx < scalarSize; compNdx++) { const int in0 = ((const int*)inputs[0])[compNdx]; const int out0 = ((const int*)outputs[0])[compNdx]; const int ref0 = in0 < 0 ? -1 : in0 > 0 ? +1 : 0; if (out0 != ref0) { m_failMsg << "Expected [" << compNdx << "] = " << ref0; return false; } } } return true; } }; static float roundEven (float v) { const float q = deFloatFrac(v); const int truncated = int(v-q); const int rounded = (q > 0.5f) ? (truncated + 1) : // Rounded up (q == 0.5f && (truncated % 2 != 0)) ? (truncated + 1) : // Round to nearest even at 0.5 truncated; // Rounded down return float(rounded); } class RoundEvenCase : public CommonFunctionCase { public: RoundEvenCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType) : CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "roundEven", shaderType) { m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision))); m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision))); m_spec.source = "out0 = roundEven(in0);"; } void getInputValues (int numValues, void* const* values) const { const Vec2 ranges[] = { Vec2(-2.0f, 2.0f), // lowp Vec2(-1e3f, 1e3f), // mediump Vec2(-1e7f, 1e7f) // highp }; de::Random rnd (deStringHash(getName()) ^ 0xac23fu); const glu::DataType type = m_spec.inputs[0].varType.getBasicType(); const glu::Precision precision = m_spec.inputs[0].varType.getPrecision(); const int scalarSize = glu::getDataTypeScalarSize(type); int numSpecialCases = 0; // Special cases. if (precision != glu::PRECISION_LOWP) { DE_ASSERT(numValues >= 20); for (int ndx = 0; ndx < 20; ndx++) { const float v = de::clamp(float(ndx) - 10.5f, ranges[precision].x(), ranges[precision].y()); std::fill((float*)values[0], (float*)values[0] + scalarSize, v); numSpecialCases += 1; } } // Random cases. fillRandomScalars(rnd, ranges[precision].x(), ranges[precision].y(), (float*)values[0] + numSpecialCases*scalarSize, (numValues-numSpecialCases)*scalarSize); // If precision is mediump, make sure values can be represented in fp16 exactly if (precision == glu::PRECISION_MEDIUMP) { for (int ndx = 0; ndx < numValues*scalarSize; ndx++) ((float*)values[0])[ndx] = tcu::Float16(((float*)values[0])[ndx]).asFloat(); } } bool compare (const void* const* inputs, const void* const* outputs) { const glu::DataType type = m_spec.inputs[0].varType.getBasicType(); const glu::Precision precision = m_spec.inputs[0].varType.getPrecision(); const bool hasSignedZero = supportsSignedZero(precision); const int scalarSize = glu::getDataTypeScalarSize(type); if (precision == glu::PRECISION_HIGHP || precision == glu::PRECISION_MEDIUMP) { // Require exact rounding result. for (int compNdx = 0; compNdx < scalarSize; compNdx++) { const float in0 = ((const float*)inputs[0])[compNdx]; const float out0 = ((const float*)outputs[0])[compNdx]; const float ref = roundEven(in0); const deUint32 ulpDiff = hasSignedZero ? getUlpDiff(out0, ref) : getUlpDiffIgnoreZeroSign(out0, ref); if (ulpDiff > 0) { m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref) << ", got ULP diff " << tcu::toHex(ulpDiff); return false; } } } else { const int mantissaBits = getMinMantissaBits(precision); const deUint32 maxUlpDiff = getMaxUlpDiffFromBits(mantissaBits); // ULP diff for rounded integer value. const float eps = getEpsFromBits(1.0f, mantissaBits); // epsilon for rounding bounds for (int compNdx = 0; compNdx < scalarSize; compNdx++) { const float in0 = ((const float*)inputs[0])[compNdx]; const float out0 = ((const float*)outputs[0])[compNdx]; const int minRes = int(roundEven(in0-eps)); const int maxRes = int(roundEven(in0+eps)); bool anyOk = false; for (int roundedVal = minRes; roundedVal <= maxRes; roundedVal++) { const deUint32 ulpDiff = getUlpDiffIgnoreZeroSign(out0, float(roundedVal)); if (ulpDiff <= maxUlpDiff) { anyOk = true; break; } } if (!anyOk) { m_failMsg << "Expected [" << compNdx << "] = [" << minRes << ", " << maxRes << "] with ULP threshold " << tcu::toHex(maxUlpDiff); return false; } } } return true; } }; class ModfCase : public CommonFunctionCase { public: ModfCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType) : CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "modf", shaderType) { m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision))); m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision))); m_spec.outputs.push_back(Symbol("out1", glu::VarType(baseType, precision))); m_spec.source = "out0 = modf(in0, out1);"; } void getInputValues (int numValues, void* const* values) const { const Vec2 ranges[] = { Vec2(-2.0f, 2.0f), // lowp Vec2(-1e3f, 1e3f), // mediump Vec2(-1e7f, 1e7f) // highp }; de::Random rnd (deStringHash(getName()) ^ 0xac23fu); const glu::DataType type = m_spec.inputs[0].varType.getBasicType(); const glu::Precision precision = m_spec.inputs[0].varType.getPrecision(); const int scalarSize = glu::getDataTypeScalarSize(type); fillRandomScalars(rnd, ranges[precision].x(), ranges[precision].y(), values[0], numValues*scalarSize); } bool compare (const void* const* inputs, const void* const* outputs) { const glu::DataType type = m_spec.inputs[0].varType.getBasicType(); const glu::Precision precision = m_spec.inputs[0].varType.getPrecision(); const bool hasZeroSign = supportsSignedZero(precision); const int scalarSize = glu::getDataTypeScalarSize(type); const int mantissaBits = getMinMantissaBits(precision); for (int compNdx = 0; compNdx < scalarSize; compNdx++) { const float in0 = ((const float*)inputs[0])[compNdx]; const float out0 = ((const float*)outputs[0])[compNdx]; const float out1 = ((const float*)outputs[1])[compNdx]; const float refOut1 = float(int(in0)); const float refOut0 = in0 - refOut1; const int bitsLost = precision != glu::PRECISION_HIGHP ? numBitsLostInOp(in0, refOut0) : 0; const deUint32 maxUlpDiff = getMaxUlpDiffFromBits(de::max(mantissaBits - bitsLost, 0)); const float resSum = out0 + out1; const deUint32 ulpDiff = hasZeroSign ? getUlpDiff(resSum, in0) : getUlpDiffIgnoreZeroSign(resSum, in0); if (ulpDiff > maxUlpDiff) { m_failMsg << "Expected [" << compNdx << "] = (" << HexFloat(refOut0) << ") + (" << HexFloat(refOut1) << ") = " << HexFloat(in0) << " with ULP threshold " << tcu::toHex(maxUlpDiff) << ", got ULP diff " << tcu::toHex(ulpDiff); return false; } } return true; } }; class IsnanCase : public CommonFunctionCase { public: IsnanCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType) : CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "isnan", shaderType) { DE_ASSERT(glu::isDataTypeFloatOrVec(baseType)); const int vecSize = glu::getDataTypeScalarSize(baseType); const glu::DataType boolType = vecSize > 1 ? glu::getDataTypeBoolVec(vecSize) : glu::TYPE_BOOL; m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision))); m_spec.outputs.push_back(Symbol("out0", glu::VarType(boolType, glu::PRECISION_LAST))); m_spec.source = "out0 = isnan(in0);"; } void getInputValues (int numValues, void* const* values) const { de::Random rnd (deStringHash(getName()) ^ 0xc2a39fu); const glu::DataType type = m_spec.inputs[0].varType.getBasicType(); const glu::Precision precision = m_spec.inputs[0].varType.getPrecision(); const int scalarSize = glu::getDataTypeScalarSize(type); const int mantissaBits = getMinMantissaBits(precision); const deUint32 mantissaMask = ~getMaxUlpDiffFromBits(mantissaBits) & ((1u<<23)-1u); for (int valNdx = 0; valNdx < numValues*scalarSize; valNdx++) { const bool isNan = rnd.getFloat() > 0.3f; const bool isInf = !isNan && rnd.getFloat() > 0.4f; const deUint32 mantissa = !isInf ? ((1u<<22) | (rnd.getUint32() & mantissaMask)) : 0; const deUint32 exp = !isNan && !isInf ? (rnd.getUint32() & 0x7fu) : 0xffu; const deUint32 sign = rnd.getUint32() & 0x1u; const deUint32 value = (sign << 31) | (exp << 23) | mantissa; DE_ASSERT(tcu::Float32(value).isInf() == isInf && tcu::Float32(value).isNaN() == isNan); ((deUint32*)values[0])[valNdx] = value; } } bool compare (const void* const* inputs, const void* const* outputs) { const glu::DataType type = m_spec.inputs[0].varType.getBasicType(); const glu::Precision precision = m_spec.inputs[0].varType.getPrecision(); const int scalarSize = glu::getDataTypeScalarSize(type); if (precision == glu::PRECISION_HIGHP) { // Only highp is required to support inf/nan for (int compNdx = 0; compNdx < scalarSize; compNdx++) { const float in0 = ((const float*)inputs[0])[compNdx]; const deUint32 out0 = ((const deUint32*)outputs[0])[compNdx]; const deUint32 ref = tcu::Float32(in0).isNaN() ? 1u : 0u; if (out0 != ref) { m_failMsg << "Expected [" << compNdx << "] = " << HexBool(ref); return false; } } } else { // Value can be either 0 or 1 for (int compNdx = 0; compNdx < scalarSize; compNdx++) { const int out0 = ((const int*)outputs[0])[compNdx]; if (out0 != 0 && out0 != 1) { m_failMsg << "Expected [" << compNdx << "] = 0 / 1"; return false; } } } return true; } }; class IsinfCase : public CommonFunctionCase { public: IsinfCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType) : CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "isinf", shaderType) { DE_ASSERT(glu::isDataTypeFloatOrVec(baseType)); const int vecSize = glu::getDataTypeScalarSize(baseType); const glu::DataType boolType = vecSize > 1 ? glu::getDataTypeBoolVec(vecSize) : glu::TYPE_BOOL; m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision))); m_spec.outputs.push_back(Symbol("out0", glu::VarType(boolType, glu::PRECISION_LAST))); m_spec.source = "out0 = isinf(in0);"; } void getInputValues (int numValues, void* const* values) const { de::Random rnd (deStringHash(getName()) ^ 0xc2a39fu); const glu::DataType type = m_spec.inputs[0].varType.getBasicType(); const glu::Precision precision = m_spec.inputs[0].varType.getPrecision(); const int scalarSize = glu::getDataTypeScalarSize(type); const int mantissaBits = getMinMantissaBits(precision); const deUint32 mantissaMask = ~getMaxUlpDiffFromBits(mantissaBits) & ((1u<<23)-1u); for (int valNdx = 0; valNdx < numValues*scalarSize; valNdx++) { const bool isInf = rnd.getFloat() > 0.3f; const bool isNan = !isInf && rnd.getFloat() > 0.4f; const deUint32 mantissa = !isInf ? ((1u<<22) | (rnd.getUint32() & mantissaMask)) : 0; const deUint32 exp = !isNan && !isInf ? (rnd.getUint32() & 0x7fu) : 0xffu; const deUint32 sign = rnd.getUint32() & 0x1u; const deUint32 value = (sign << 31) | (exp << 23) | mantissa; DE_ASSERT(tcu::Float32(value).isInf() == isInf && tcu::Float32(value).isNaN() == isNan); ((deUint32*)values[0])[valNdx] = value; } } bool compare (const void* const* inputs, const void* const* outputs) { const glu::DataType type = m_spec.inputs[0].varType.getBasicType(); const glu::Precision precision = m_spec.inputs[0].varType.getPrecision(); const int scalarSize = glu::getDataTypeScalarSize(type); if (precision == glu::PRECISION_HIGHP) { // Only highp is required to support inf/nan for (int compNdx = 0; compNdx < scalarSize; compNdx++) { const float in0 = ((const float*)inputs[0])[compNdx]; const deUint32 out0 = ((const deUint32*)outputs[0])[compNdx]; const deUint32 ref = tcu::Float32(in0).isInf() ? 1u : 0u; if (out0 != ref) { m_failMsg << "Expected [" << compNdx << "] = " << HexBool(ref); return false; } } } else { // Value can be either 0 or 1 for (int compNdx = 0; compNdx < scalarSize; compNdx++) { const int out0 = ((const int*)outputs[0])[compNdx]; if (out0 != 0 && out0 != 1) { m_failMsg << "Expected [" << compNdx << "] = 0 / 1"; return false; } } } return true; } }; class FloatBitsToUintIntCase : public CommonFunctionCase { public: FloatBitsToUintIntCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType, bool outIsSigned) : CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), outIsSigned ? "floatBitsToInt" : "floatBitsToUint", shaderType) { const int vecSize = glu::getDataTypeScalarSize(baseType); const glu::DataType intType = outIsSigned ? (vecSize > 1 ? glu::getDataTypeIntVec(vecSize) : glu::TYPE_INT) : (vecSize > 1 ? glu::getDataTypeUintVec(vecSize) : glu::TYPE_UINT); m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision))); m_spec.outputs.push_back(Symbol("out0", glu::VarType(intType, glu::PRECISION_HIGHP))); m_spec.source = outIsSigned ? "out0 = floatBitsToInt(in0);" : "out0 = floatBitsToUint(in0);"; } void getInputValues (int numValues, void* const* values) const { const Vec2 ranges[] = { Vec2(-2.0f, 2.0f), // lowp Vec2(-1e3f, 1e3f), // mediump Vec2(-1e7f, 1e7f) // highp }; de::Random rnd (deStringHash(getName()) ^ 0x2790au); const glu::DataType type = m_spec.inputs[0].varType.getBasicType(); const glu::Precision precision = m_spec.inputs[0].varType.getPrecision(); const int scalarSize = glu::getDataTypeScalarSize(type); fillRandomScalars(rnd, ranges[precision].x(), ranges[precision].y(), values[0], numValues*scalarSize); } bool compare (const void* const* inputs, const void* const* outputs) { const glu::DataType type = m_spec.inputs[0].varType.getBasicType(); const glu::Precision precision = m_spec.inputs[0].varType.getPrecision(); const int scalarSize = glu::getDataTypeScalarSize(type); const int mantissaBits = getMinMantissaBits(precision); const int maxUlpDiff = getMaxUlpDiffFromBits(mantissaBits); for (int compNdx = 0; compNdx < scalarSize; compNdx++) { const float in0 = ((const float*)inputs[0])[compNdx]; const deUint32 out0 = ((const deUint32*)outputs[0])[compNdx]; const deUint32 refOut0 = tcu::Float32(in0).bits(); const int ulpDiff = de::abs((int)out0 - (int)refOut0); if (ulpDiff > maxUlpDiff) { m_failMsg << "Expected [" << compNdx << "] = " << tcu::toHex(refOut0) << " with threshold " << tcu::toHex(maxUlpDiff) << ", got diff " << tcu::toHex(ulpDiff); return false; } } return true; } }; class FloatBitsToIntCase : public FloatBitsToUintIntCase { public: FloatBitsToIntCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType) : FloatBitsToUintIntCase(context, baseType, precision, shaderType, true) { } }; class FloatBitsToUintCase : public FloatBitsToUintIntCase { public: FloatBitsToUintCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType) : FloatBitsToUintIntCase(context, baseType, precision, shaderType, false) { } }; class BitsToFloatCase : public CommonFunctionCase { public: BitsToFloatCase (Context& context, glu::DataType baseType, glu::ShaderType shaderType) : CommonFunctionCase(context, getCommonFuncCaseName(baseType, glu::PRECISION_HIGHP, shaderType).c_str(), glu::isDataTypeIntOrIVec(baseType) ? "intBitsToFloat" : "uintBitsToFloat", shaderType) { const bool inIsSigned = glu::isDataTypeIntOrIVec(baseType); const int vecSize = glu::getDataTypeScalarSize(baseType); const glu::DataType floatType = vecSize > 1 ? glu::getDataTypeFloatVec(vecSize) : glu::TYPE_FLOAT; m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, glu::PRECISION_HIGHP))); m_spec.outputs.push_back(Symbol("out0", glu::VarType(floatType, glu::PRECISION_HIGHP))); m_spec.source = inIsSigned ? "out0 = intBitsToFloat(in0);" : "out0 = uintBitsToFloat(in0);"; } void getInputValues (int numValues, void* const* values) const { de::Random rnd (deStringHash(getName()) ^ 0xbbb225u); const glu::DataType type = m_spec.inputs[0].varType.getBasicType(); const int scalarSize = glu::getDataTypeScalarSize(type); const Vec2 range (-1e8f, +1e8f); // \note Filled as floats. fillRandomScalars(rnd, range.x(), range.y(), values[0], numValues*scalarSize); } bool compare (const void* const* inputs, const void* const* outputs) { const glu::DataType type = m_spec.inputs[0].varType.getBasicType(); const int scalarSize = glu::getDataTypeScalarSize(type); const deUint32 maxUlpDiff = 0; for (int compNdx = 0; compNdx < scalarSize; compNdx++) { const float in0 = ((const float*)inputs[0])[compNdx]; const float out0 = ((const float*)outputs[0])[compNdx]; const deUint32 ulpDiff = getUlpDiff(in0, out0); if (ulpDiff > maxUlpDiff) { m_failMsg << "Expected [" << compNdx << "] = " << tcu::toHex(tcu::Float32(in0).bits()) << " with ULP threshold " << tcu::toHex(maxUlpDiff) << ", got ULP diff " << tcu::toHex(ulpDiff); return false; } } return true; } }; class FloorCase : public CommonFunctionCase { public: FloorCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType) : CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "floor", shaderType) { m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision))); m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision))); m_spec.source = "out0 = floor(in0);"; } void getInputValues (int numValues, void* const* values) const { const Vec2 ranges[] = { Vec2(-2.0f, 2.0f), // lowp Vec2(-1e3f, 1e3f), // mediump Vec2(-1e7f, 1e7f) // highp }; de::Random rnd (deStringHash(getName()) ^ 0xac23fu); const glu::DataType type = m_spec.inputs[0].varType.getBasicType(); const glu::Precision precision = m_spec.inputs[0].varType.getPrecision(); const int scalarSize = glu::getDataTypeScalarSize(type); // Random cases. fillRandomScalars(rnd, ranges[precision].x(), ranges[precision].y(), (float*)values[0], numValues*scalarSize); // If precision is mediump, make sure values can be represented in fp16 exactly if (precision == glu::PRECISION_MEDIUMP) { for (int ndx = 0; ndx < numValues*scalarSize; ndx++) ((float*)values[0])[ndx] = tcu::Float16(((float*)values[0])[ndx]).asFloat(); } } bool compare (const void* const* inputs, const void* const* outputs) { const glu::DataType type = m_spec.inputs[0].varType.getBasicType(); const glu::Precision precision = m_spec.inputs[0].varType.getPrecision(); const int scalarSize = glu::getDataTypeScalarSize(type); if (precision == glu::PRECISION_HIGHP || precision == glu::PRECISION_MEDIUMP) { // Require exact result. for (int compNdx = 0; compNdx < scalarSize; compNdx++) { const float in0 = ((const float*)inputs[0])[compNdx]; const float out0 = ((const float*)outputs[0])[compNdx]; const float ref = deFloatFloor(in0); const deUint32 ulpDiff = getUlpDiff(out0, ref); if (ulpDiff > 0) { m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref) << ", got ULP diff " << tcu::toHex(ulpDiff); return false; } } } else { const int mantissaBits = getMinMantissaBits(precision); const deUint32 maxUlpDiff = getMaxUlpDiffFromBits(mantissaBits); // ULP diff for rounded integer value. const float eps = getEpsFromBits(1.0f, mantissaBits); // epsilon for rounding bounds for (int compNdx = 0; compNdx < scalarSize; compNdx++) { const float in0 = ((const float*)inputs[0])[compNdx]; const float out0 = ((const float*)outputs[0])[compNdx]; const int minRes = int(deFloatFloor(in0-eps)); const int maxRes = int(deFloatFloor(in0+eps)); bool anyOk = false; for (int roundedVal = minRes; roundedVal <= maxRes; roundedVal++) { const deUint32 ulpDiff = getUlpDiff(out0, float(roundedVal)); if (ulpDiff <= maxUlpDiff) { anyOk = true; break; } } if (!anyOk) { m_failMsg << "Expected [" << compNdx << "] = [" << minRes << ", " << maxRes << "] with ULP threshold " << tcu::toHex(maxUlpDiff); return false; } } } return true; } }; class TruncCase : public CommonFunctionCase { public: TruncCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType) : CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "trunc", shaderType) { m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision))); m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision))); m_spec.source = "out0 = trunc(in0);"; } void getInputValues (int numValues, void* const* values) const { const Vec2 ranges[] = { Vec2(-2.0f, 2.0f), // lowp Vec2(-1e3f, 1e3f), // mediump Vec2(-1e7f, 1e7f) // highp }; de::Random rnd (deStringHash(getName()) ^ 0xac23fu); const glu::DataType type = m_spec.inputs[0].varType.getBasicType(); const glu::Precision precision = m_spec.inputs[0].varType.getPrecision(); const int scalarSize = glu::getDataTypeScalarSize(type); const float specialCases[] = { 0.0f, -0.0f, -0.9f, 0.9f, 1.0f, -1.0f }; const int numSpecialCases = DE_LENGTH_OF_ARRAY(specialCases); // Special cases for (int caseNdx = 0; caseNdx < numSpecialCases; caseNdx++) { for (int scalarNdx = 0; scalarNdx < scalarSize; scalarNdx++) ((float*)values[0])[caseNdx*scalarSize + scalarNdx] = specialCases[caseNdx]; } // Random cases. fillRandomScalars(rnd, ranges[precision].x(), ranges[precision].y(), (float*)values[0] + scalarSize*numSpecialCases, (numValues-numSpecialCases)*scalarSize); // If precision is mediump, make sure values can be represented in fp16 exactly if (precision == glu::PRECISION_MEDIUMP) { for (int ndx = 0; ndx < numValues*scalarSize; ndx++) ((float*)values[0])[ndx] = tcu::Float16(((float*)values[0])[ndx]).asFloat(); } } bool compare (const void* const* inputs, const void* const* outputs) { const glu::DataType type = m_spec.inputs[0].varType.getBasicType(); const glu::Precision precision = m_spec.inputs[0].varType.getPrecision(); const int scalarSize = glu::getDataTypeScalarSize(type); if (precision == glu::PRECISION_HIGHP || precision == glu::PRECISION_MEDIUMP) { // Require exact result. for (int compNdx = 0; compNdx < scalarSize; compNdx++) { const float in0 = ((const float*)inputs[0])[compNdx]; const float out0 = ((const float*)outputs[0])[compNdx]; const bool isNeg = tcu::Float32(in0).sign() < 0; const float ref = isNeg ? (-float(int(-in0))) : float(int(in0)); // \note: trunc() function definition is a bit broad on negative zeros. Ignore result sign if zero. const deUint32 ulpDiff = getUlpDiffIgnoreZeroSign(out0, ref); if (ulpDiff > 0) { m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref) << ", got ULP diff " << tcu::toHex(ulpDiff); return false; } } } else { const int mantissaBits = getMinMantissaBits(precision); const deUint32 maxUlpDiff = getMaxUlpDiffFromBits(mantissaBits); // ULP diff for rounded integer value. const float eps = getEpsFromBits(1.0f, mantissaBits); // epsilon for rounding bounds for (int compNdx = 0; compNdx < scalarSize; compNdx++) { const float in0 = ((const float*)inputs[0])[compNdx]; const float out0 = ((const float*)outputs[0])[compNdx]; const int minRes = int(in0-eps); const int maxRes = int(in0+eps); bool anyOk = false; for (int roundedVal = minRes; roundedVal <= maxRes; roundedVal++) { const deUint32 ulpDiff = getUlpDiffIgnoreZeroSign(out0, float(roundedVal)); if (ulpDiff <= maxUlpDiff) { anyOk = true; break; } } if (!anyOk) { m_failMsg << "Expected [" << compNdx << "] = [" << minRes << ", " << maxRes << "] with ULP threshold " << tcu::toHex(maxUlpDiff); return false; } } } return true; } }; class RoundCase : public CommonFunctionCase { public: RoundCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType) : CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "round", shaderType) { m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision))); m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision))); m_spec.source = "out0 = round(in0);"; } void getInputValues (int numValues, void* const* values) const { const Vec2 ranges[] = { Vec2(-2.0f, 2.0f), // lowp Vec2(-1e3f, 1e3f), // mediump Vec2(-1e7f, 1e7f) // highp }; de::Random rnd (deStringHash(getName()) ^ 0xac23fu); const glu::DataType type = m_spec.inputs[0].varType.getBasicType(); const glu::Precision precision = m_spec.inputs[0].varType.getPrecision(); const int scalarSize = glu::getDataTypeScalarSize(type); int numSpecialCases = 0; // Special cases. if (precision != glu::PRECISION_LOWP) { DE_ASSERT(numValues >= 10); for (int ndx = 0; ndx < 10; ndx++) { const float v = de::clamp(float(ndx) - 5.5f, ranges[precision].x(), ranges[precision].y()); std::fill((float*)values[0], (float*)values[0] + scalarSize, v); numSpecialCases += 1; } } // Random cases. fillRandomScalars(rnd, ranges[precision].x(), ranges[precision].y(), (float*)values[0] + numSpecialCases*scalarSize, (numValues-numSpecialCases)*scalarSize); // If precision is mediump, make sure values can be represented in fp16 exactly if (precision == glu::PRECISION_MEDIUMP) { for (int ndx = 0; ndx < numValues*scalarSize; ndx++) ((float*)values[0])[ndx] = tcu::Float16(((float*)values[0])[ndx]).asFloat(); } } bool compare (const void* const* inputs, const void* const* outputs) { const glu::DataType type = m_spec.inputs[0].varType.getBasicType(); const glu::Precision precision = m_spec.inputs[0].varType.getPrecision(); const bool hasZeroSign = supportsSignedZero(precision); const int scalarSize = glu::getDataTypeScalarSize(type); if (precision == glu::PRECISION_HIGHP || precision == glu::PRECISION_MEDIUMP) { for (int compNdx = 0; compNdx < scalarSize; compNdx++) { const float in0 = ((const float*)inputs[0])[compNdx]; const float out0 = ((const float*)outputs[0])[compNdx]; if (deFloatFrac(in0) == 0.5f) { // Allow both ceil(in) and floor(in) const float ref0 = deFloatFloor(in0); const float ref1 = deFloatCeil(in0); const deUint32 ulpDiff0 = hasZeroSign ? getUlpDiff(out0, ref0) : getUlpDiffIgnoreZeroSign(out0, ref0); const deUint32 ulpDiff1 = hasZeroSign ? getUlpDiff(out0, ref1) : getUlpDiffIgnoreZeroSign(out0, ref1); if (ulpDiff0 > 0 && ulpDiff1 > 0) { m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref0) << " or " << HexFloat(ref1) << ", got ULP diff " << tcu::toHex(de::min(ulpDiff0, ulpDiff1)); return false; } } else { // Require exact result const float ref = roundEven(in0); const deUint32 ulpDiff = hasZeroSign ? getUlpDiff(out0, ref) : getUlpDiffIgnoreZeroSign(out0, ref); if (ulpDiff > 0) { m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref) << ", got ULP diff " << tcu::toHex(ulpDiff); return false; } } } } else { const int mantissaBits = getMinMantissaBits(precision); const deUint32 maxUlpDiff = getMaxUlpDiffFromBits(mantissaBits); // ULP diff for rounded integer value. const float eps = getEpsFromBits(1.0f, mantissaBits); // epsilon for rounding bounds for (int compNdx = 0; compNdx < scalarSize; compNdx++) { const float in0 = ((const float*)inputs[0])[compNdx]; const float out0 = ((const float*)outputs[0])[compNdx]; const int minRes = int(roundEven(in0-eps)); const int maxRes = int(roundEven(in0+eps)); bool anyOk = false; for (int roundedVal = minRes; roundedVal <= maxRes; roundedVal++) { const deUint32 ulpDiff = getUlpDiffIgnoreZeroSign(out0, float(roundedVal)); if (ulpDiff <= maxUlpDiff) { anyOk = true; break; } } if (!anyOk) { m_failMsg << "Expected [" << compNdx << "] = [" << minRes << ", " << maxRes << "] with ULP threshold " << tcu::toHex(maxUlpDiff); return false; } } } return true; } }; class CeilCase : public CommonFunctionCase { public: CeilCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType) : CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "ceil", shaderType) { m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision))); m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision))); m_spec.source = "out0 = ceil(in0);"; } void getInputValues (int numValues, void* const* values) const { const Vec2 ranges[] = { Vec2(-2.0f, 2.0f), // lowp Vec2(-1e3f, 1e3f), // mediump Vec2(-1e7f, 1e7f) // highp }; de::Random rnd (deStringHash(getName()) ^ 0xac23fu); const glu::DataType type = m_spec.inputs[0].varType.getBasicType(); const glu::Precision precision = m_spec.inputs[0].varType.getPrecision(); const int scalarSize = glu::getDataTypeScalarSize(type); // Random cases. fillRandomScalars(rnd, ranges[precision].x(), ranges[precision].y(), (float*)values[0], numValues*scalarSize); // If precision is mediump, make sure values can be represented in fp16 exactly if (precision == glu::PRECISION_MEDIUMP) { for (int ndx = 0; ndx < numValues*scalarSize; ndx++) ((float*)values[0])[ndx] = tcu::Float16(((float*)values[0])[ndx]).asFloat(); } } bool compare (const void* const* inputs, const void* const* outputs) { const glu::DataType type = m_spec.inputs[0].varType.getBasicType(); const glu::Precision precision = m_spec.inputs[0].varType.getPrecision(); const bool hasZeroSign = supportsSignedZero(precision); const int scalarSize = glu::getDataTypeScalarSize(type); if (precision == glu::PRECISION_HIGHP || precision == glu::PRECISION_MEDIUMP) { // Require exact result. for (int compNdx = 0; compNdx < scalarSize; compNdx++) { const float in0 = ((const float*)inputs[0])[compNdx]; const float out0 = ((const float*)outputs[0])[compNdx]; const float ref = deFloatCeil(in0); const deUint32 ulpDiff = hasZeroSign ? getUlpDiff(out0, ref) : getUlpDiffIgnoreZeroSign(out0, ref); if (ulpDiff > 0) { m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref) << ", got ULP diff " << tcu::toHex(ulpDiff); return false; } } } else { const int mantissaBits = getMinMantissaBits(precision); const deUint32 maxUlpDiff = getMaxUlpDiffFromBits(mantissaBits); // ULP diff for rounded integer value. const float eps = getEpsFromBits(1.0f, mantissaBits); // epsilon for rounding bounds for (int compNdx = 0; compNdx < scalarSize; compNdx++) { const float in0 = ((const float*)inputs[0])[compNdx]; const float out0 = ((const float*)outputs[0])[compNdx]; const int minRes = int(deFloatCeil(in0-eps)); const int maxRes = int(deFloatCeil(in0+eps)); bool anyOk = false; for (int roundedVal = minRes; roundedVal <= maxRes; roundedVal++) { const deUint32 ulpDiff = getUlpDiffIgnoreZeroSign(out0, float(roundedVal)); if (ulpDiff <= maxUlpDiff) { anyOk = true; break; } } if (!anyOk && de::inRange(0, minRes, maxRes)) { // Allow -0 as well. const int ulpDiff = de::abs((int)tcu::Float32(out0).bits() - (int)0x80000000u); anyOk = ((deUint32)ulpDiff <= maxUlpDiff); } if (!anyOk) { m_failMsg << "Expected [" << compNdx << "] = [" << minRes << ", " << maxRes << "] with ULP threshold " << tcu::toHex(maxUlpDiff); return false; } } } return true; } }; class FractCase : public CommonFunctionCase { public: FractCase (Context& context, glu::DataType baseType, glu::Precision precision, glu::ShaderType shaderType) : CommonFunctionCase(context, getCommonFuncCaseName(baseType, precision, shaderType).c_str(), "fract", shaderType) { m_spec.inputs.push_back(Symbol("in0", glu::VarType(baseType, precision))); m_spec.outputs.push_back(Symbol("out0", glu::VarType(baseType, precision))); m_spec.source = "out0 = fract(in0);"; } void getInputValues (int numValues, void* const* values) const { const Vec2 ranges[] = { Vec2(-2.0f, 2.0f), // lowp Vec2(-1e3f, 1e3f), // mediump Vec2(-1e7f, 1e7f) // highp }; de::Random rnd (deStringHash(getName()) ^ 0xac23fu); const glu::DataType type = m_spec.inputs[0].varType.getBasicType(); const glu::Precision precision = m_spec.inputs[0].varType.getPrecision(); const int scalarSize = glu::getDataTypeScalarSize(type); int numSpecialCases = 0; // Special cases. if (precision != glu::PRECISION_LOWP) { DE_ASSERT(numValues >= 10); for (int ndx = 0; ndx < 10; ndx++) { const float v = de::clamp(float(ndx) - 5.5f, ranges[precision].x(), ranges[precision].y()); std::fill((float*)values[0], (float*)values[0] + scalarSize, v); numSpecialCases += 1; } } // Random cases. fillRandomScalars(rnd, ranges[precision].x(), ranges[precision].y(), (float*)values[0] + numSpecialCases*scalarSize, (numValues-numSpecialCases)*scalarSize); // If precision is mediump, make sure values can be represented in fp16 exactly if (precision == glu::PRECISION_MEDIUMP) { for (int ndx = 0; ndx < numValues*scalarSize; ndx++) ((float*)values[0])[ndx] = tcu::Float16(((float*)values[0])[ndx]).asFloat(); } } bool compare (const void* const* inputs, const void* const* outputs) { const glu::DataType type = m_spec.inputs[0].varType.getBasicType(); const glu::Precision precision = m_spec.inputs[0].varType.getPrecision(); const bool hasZeroSign = supportsSignedZero(precision); const int scalarSize = glu::getDataTypeScalarSize(type); if (precision == glu::PRECISION_HIGHP || precision == glu::PRECISION_MEDIUMP) { // Require exact result. for (int compNdx = 0; compNdx < scalarSize; compNdx++) { const float in0 = ((const float*)inputs[0])[compNdx]; const float out0 = ((const float*)outputs[0])[compNdx]; const float ref = deFloatFrac(in0); const deUint32 ulpDiff = hasZeroSign ? getUlpDiff(out0, ref) : getUlpDiffIgnoreZeroSign(out0, ref); if (ulpDiff > 0) { m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref) << ", got ULP diff " << tcu::toHex(ulpDiff); return false; } } } else { const int mantissaBits = getMinMantissaBits(precision); const float eps = getEpsFromBits(1.0f, mantissaBits); // epsilon for rounding bounds for (int compNdx = 0; compNdx < scalarSize; compNdx++) { const float in0 = ((const float*)inputs[0])[compNdx]; const float out0 = ((const float*)outputs[0])[compNdx]; if (int(deFloatFloor(in0-eps)) == int(deFloatFloor(in0+eps))) { const float ref = deFloatFrac(in0); const int bitsLost = numBitsLostInOp(in0, ref); const deUint32 maxUlpDiff = getMaxUlpDiffFromBits(de::max(0, mantissaBits-bitsLost)); // ULP diff for rounded integer value. const deUint32 ulpDiff = getUlpDiffIgnoreZeroSign(out0, ref); if (ulpDiff > maxUlpDiff) { m_failMsg << "Expected [" << compNdx << "] = " << HexFloat(ref) << " with ULP threshold " << tcu::toHex(maxUlpDiff) << ", got diff " << tcu::toHex(ulpDiff); return false; } } else { if (out0 >= 1.0f) { m_failMsg << "Expected [" << compNdx << "] < 1.0"; return false; } } } } return true; } }; ShaderCommonFunctionTests::ShaderCommonFunctionTests (Context& context) : TestCaseGroup(context, "common", "Common function tests") { } ShaderCommonFunctionTests::~ShaderCommonFunctionTests (void) { } template static void addFunctionCases (TestCaseGroup* parent, const char* functionName, bool floatTypes, bool intTypes, bool uintTypes) { tcu::TestCaseGroup* group = new tcu::TestCaseGroup(parent->getTestContext(), functionName, functionName); parent->addChild(group); const glu::DataType scalarTypes[] = { glu::TYPE_FLOAT, glu::TYPE_INT, glu::TYPE_UINT }; for (int scalarTypeNdx = 0; scalarTypeNdx < DE_LENGTH_OF_ARRAY(scalarTypes); scalarTypeNdx++) { const glu::DataType scalarType = scalarTypes[scalarTypeNdx]; if ((!floatTypes && scalarType == glu::TYPE_FLOAT) || (!intTypes && scalarType == glu::TYPE_INT) || (!uintTypes && scalarType == glu::TYPE_UINT)) continue; for (int vecSize = 1; vecSize <= 4; vecSize++) { for (int prec = glu::PRECISION_LOWP; prec <= glu::PRECISION_HIGHP; prec++) { for (int shaderType = glu::SHADERTYPE_VERTEX; shaderType <= glu::SHADERTYPE_FRAGMENT; shaderType++) group->addChild(new TestClass(parent->getContext(), glu::DataType(scalarType + vecSize - 1), glu::Precision(prec), glu::ShaderType(shaderType))); } } } } void ShaderCommonFunctionTests::init (void) { // Float? Int? Uint? addFunctionCases (this, "abs", true, true, false); addFunctionCases (this, "sign", true, true, false); addFunctionCases (this, "floor", true, false, false); addFunctionCases (this, "trunc", true, false, false); addFunctionCases (this, "round", true, false, false); addFunctionCases (this, "roundeven", true, false, false); addFunctionCases (this, "ceil", true, false, false); addFunctionCases (this, "fract", true, false, false); // mod addFunctionCases (this, "modf", true, false, false); // min // max // clamp // mix // step // smoothstep addFunctionCases (this, "isnan", true, false, false); addFunctionCases (this, "isinf", true, false, false); addFunctionCases (this, "floatbitstoint", true, false, false); addFunctionCases (this, "floatbitstouint", true, false, false); // (u)intBitsToFloat() { tcu::TestCaseGroup* intGroup = new tcu::TestCaseGroup(m_testCtx, "intbitstofloat", "intBitsToFloat() Tests"); tcu::TestCaseGroup* uintGroup = new tcu::TestCaseGroup(m_testCtx, "uintbitstofloat", "uintBitsToFloat() Tests"); addChild(intGroup); addChild(uintGroup); for (int vecSize = 1; vecSize < 4; vecSize++) { const glu::DataType intType = vecSize > 1 ? glu::getDataTypeIntVec(vecSize) : glu::TYPE_INT; const glu::DataType uintType = vecSize > 1 ? glu::getDataTypeUintVec(vecSize) : glu::TYPE_UINT; for (int shaderType = glu::SHADERTYPE_VERTEX; shaderType <= glu::SHADERTYPE_FRAGMENT; shaderType++) { intGroup->addChild(new BitsToFloatCase(m_context, intType, glu::ShaderType(shaderType))); uintGroup->addChild(new BitsToFloatCase(m_context, uintType, glu::ShaderType(shaderType))); } } } } } // Functional } // gles3 } // deqp