/*------------------------------------------------------------------------- * drawElements Quality Program OpenGL (ES) 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 Draw tests *//*--------------------------------------------------------------------*/ #include "glsDrawTest.hpp" #include "deRandom.h" #include "deRandom.hpp" #include "deMath.h" #include "deStringUtil.hpp" #include "deFloat16.h" #include "deUniquePtr.hpp" #include "deArrayUtil.hpp" #include "tcuTestLog.hpp" #include "tcuPixelFormat.hpp" #include "tcuRGBA.hpp" #include "tcuSurface.hpp" #include "tcuVector.hpp" #include "tcuTestLog.hpp" #include "tcuRenderTarget.hpp" #include "tcuStringTemplate.hpp" #include "tcuImageCompare.hpp" #include "tcuFloat.hpp" #include "tcuTextureUtil.hpp" #include "gluPixelTransfer.hpp" #include "gluCallLogWrapper.hpp" #include "sglrContext.hpp" #include "sglrReferenceContext.hpp" #include "sglrGLContext.hpp" #include "rrGenericVector.hpp" #include #include #include #include #include #include "glwDefs.hpp" #include "glwEnums.hpp" namespace deqp { namespace gls { namespace { using tcu::TestLog; using namespace glw; // GL types const int MAX_RENDER_TARGET_SIZE = 512; // Utils static GLenum targetToGL (DrawTestSpec::Target target) { static const GLenum targets[] = { GL_ELEMENT_ARRAY_BUFFER, // TARGET_ELEMENT_ARRAY = 0, GL_ARRAY_BUFFER // TARGET_ARRAY, }; return de::getSizedArrayElement(targets, (int)target); } static GLenum usageToGL (DrawTestSpec::Usage usage) { static const GLenum usages[] = { GL_DYNAMIC_DRAW, // USAGE_DYNAMIC_DRAW = 0, GL_STATIC_DRAW, // USAGE_STATIC_DRAW, GL_STREAM_DRAW, // USAGE_STREAM_DRAW, GL_STREAM_READ, // USAGE_STREAM_READ, GL_STREAM_COPY, // USAGE_STREAM_COPY, GL_STATIC_READ, // USAGE_STATIC_READ, GL_STATIC_COPY, // USAGE_STATIC_COPY, GL_DYNAMIC_READ, // USAGE_DYNAMIC_READ, GL_DYNAMIC_COPY // USAGE_DYNAMIC_COPY, }; return de::getSizedArrayElement(usages, (int)usage); } static GLenum inputTypeToGL (DrawTestSpec::InputType type) { static const GLenum types[] = { GL_FLOAT, // INPUTTYPE_FLOAT = 0, GL_FIXED, // INPUTTYPE_FIXED, GL_DOUBLE, // INPUTTYPE_DOUBLE GL_BYTE, // INPUTTYPE_BYTE, GL_SHORT, // INPUTTYPE_SHORT, GL_UNSIGNED_BYTE, // INPUTTYPE_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, // INPUTTYPE_UNSIGNED_SHORT, GL_INT, // INPUTTYPE_INT, GL_UNSIGNED_INT, // INPUTTYPE_UNSIGNED_INT, GL_HALF_FLOAT, // INPUTTYPE_HALF, GL_UNSIGNED_INT_2_10_10_10_REV, // INPUTTYPE_UNSIGNED_INT_2_10_10_10, GL_INT_2_10_10_10_REV // INPUTTYPE_INT_2_10_10_10, }; return de::getSizedArrayElement(types, (int)type); } static std::string outputTypeToGLType (DrawTestSpec::OutputType type) { static const char* types[] = { "float", // OUTPUTTYPE_FLOAT = 0, "vec2", // OUTPUTTYPE_VEC2, "vec3", // OUTPUTTYPE_VEC3, "vec4", // OUTPUTTYPE_VEC4, "int", // OUTPUTTYPE_INT, "uint", // OUTPUTTYPE_UINT, "ivec2", // OUTPUTTYPE_IVEC2, "ivec3", // OUTPUTTYPE_IVEC3, "ivec4", // OUTPUTTYPE_IVEC4, "uvec2", // OUTPUTTYPE_UVEC2, "uvec3", // OUTPUTTYPE_UVEC3, "uvec4", // OUTPUTTYPE_UVEC4, }; return de::getSizedArrayElement(types, (int)type); } static GLenum primitiveToGL (DrawTestSpec::Primitive primitive) { static const GLenum primitives[] = { GL_POINTS, // PRIMITIVE_POINTS = 0, GL_TRIANGLES, // PRIMITIVE_TRIANGLES, GL_TRIANGLE_FAN, // PRIMITIVE_TRIANGLE_FAN, GL_TRIANGLE_STRIP, // PRIMITIVE_TRIANGLE_STRIP, GL_LINES, // PRIMITIVE_LINES GL_LINE_STRIP, // PRIMITIVE_LINE_STRIP GL_LINE_LOOP, // PRIMITIVE_LINE_LOOP GL_LINES_ADJACENCY, // PRIMITIVE_LINES_ADJACENCY GL_LINE_STRIP_ADJACENCY, // PRIMITIVE_LINE_STRIP_ADJACENCY GL_TRIANGLES_ADJACENCY, // PRIMITIVE_TRIANGLES_ADJACENCY GL_TRIANGLE_STRIP_ADJACENCY, // PRIMITIVE_TRIANGLE_STRIP_ADJACENCY }; return de::getSizedArrayElement(primitives, (int)primitive); } static deUint32 indexTypeToGL (DrawTestSpec::IndexType indexType) { static const GLenum indexTypes[] = { GL_UNSIGNED_BYTE, // INDEXTYPE_BYTE = 0, GL_UNSIGNED_SHORT, // INDEXTYPE_SHORT, GL_UNSIGNED_INT, // INDEXTYPE_INT, }; return de::getSizedArrayElement(indexTypes, (int)indexType); } static bool inputTypeIsFloatType (DrawTestSpec::InputType type) { if (type == DrawTestSpec::INPUTTYPE_FLOAT) return true; if (type == DrawTestSpec::INPUTTYPE_FIXED) return true; if (type == DrawTestSpec::INPUTTYPE_HALF) return true; if (type == DrawTestSpec::INPUTTYPE_DOUBLE) return true; return false; } static bool outputTypeIsFloatType (DrawTestSpec::OutputType type) { if (type == DrawTestSpec::OUTPUTTYPE_FLOAT || type == DrawTestSpec::OUTPUTTYPE_VEC2 || type == DrawTestSpec::OUTPUTTYPE_VEC3 || type == DrawTestSpec::OUTPUTTYPE_VEC4) return true; return false; } static bool outputTypeIsIntType (DrawTestSpec::OutputType type) { if (type == DrawTestSpec::OUTPUTTYPE_INT || type == DrawTestSpec::OUTPUTTYPE_IVEC2 || type == DrawTestSpec::OUTPUTTYPE_IVEC3 || type == DrawTestSpec::OUTPUTTYPE_IVEC4) return true; return false; } static bool outputTypeIsUintType (DrawTestSpec::OutputType type) { if (type == DrawTestSpec::OUTPUTTYPE_UINT || type == DrawTestSpec::OUTPUTTYPE_UVEC2 || type == DrawTestSpec::OUTPUTTYPE_UVEC3 || type == DrawTestSpec::OUTPUTTYPE_UVEC4) return true; return false; } static size_t getElementCount (DrawTestSpec::Primitive primitive, size_t primitiveCount) { switch (primitive) { case DrawTestSpec::PRIMITIVE_POINTS: return primitiveCount; case DrawTestSpec::PRIMITIVE_TRIANGLES: return primitiveCount * 3; case DrawTestSpec::PRIMITIVE_TRIANGLE_FAN: return primitiveCount + 2; case DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP: return primitiveCount + 2; case DrawTestSpec::PRIMITIVE_LINES: return primitiveCount * 2; case DrawTestSpec::PRIMITIVE_LINE_STRIP: return primitiveCount + 1; case DrawTestSpec::PRIMITIVE_LINE_LOOP: return (primitiveCount==1) ? (2) : (primitiveCount); case DrawTestSpec::PRIMITIVE_LINES_ADJACENCY: return primitiveCount * 4; case DrawTestSpec::PRIMITIVE_LINE_STRIP_ADJACENCY: return primitiveCount + 3; case DrawTestSpec::PRIMITIVE_TRIANGLES_ADJACENCY: return primitiveCount * 6; case DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP_ADJACENCY: return primitiveCount * 2 + 4; default: DE_ASSERT(false); return 0; } } struct MethodInfo { bool indexed; bool instanced; bool ranged; bool first; bool baseVertex; bool indirect; }; static MethodInfo getMethodInfo (gls::DrawTestSpec::DrawMethod method) { static const MethodInfo infos[] = { // indexed instanced ranged first baseVertex indirect { false, false, false, true, false, false }, //!< DRAWMETHOD_DRAWARRAYS, { false, true, false, true, false, false }, //!< DRAWMETHOD_DRAWARRAYS_INSTANCED, { false, true, false, true, false, true }, //!< DRAWMETHOD_DRAWARRAYS_INDIRECT, { true, false, false, false, false, false }, //!< DRAWMETHOD_DRAWELEMENTS, { true, false, true, false, false, false }, //!< DRAWMETHOD_DRAWELEMENTS_RANGED, { true, true, false, false, false, false }, //!< DRAWMETHOD_DRAWELEMENTS_INSTANCED, { true, true, false, false, true, true }, //!< DRAWMETHOD_DRAWELEMENTS_INDIRECT, { true, false, false, false, true, false }, //!< DRAWMETHOD_DRAWELEMENTS_BASEVERTEX, { true, true, false, false, true, false }, //!< DRAWMETHOD_DRAWELEMENTS_INSTANCED_BASEVERTEX, { true, false, true, false, true, false }, //!< DRAWMETHOD_DRAWELEMENTS_RANGED_BASEVERTEX, }; return de::getSizedArrayElement(infos, (int)method); } template inline static void alignmentSafeAssignment (char* dst, T val) { std::memcpy(dst, &val, sizeof(T)); } static bool checkSpecsShaderCompatible (const DrawTestSpec& a, const DrawTestSpec& b) { // Only the attributes matter if (a.attribs.size() != b.attribs.size()) return false; for (size_t ndx = 0; ndx < a.attribs.size(); ++ndx) { // Only the output type (== shader input type) matters and the usage in the shader. if (a.attribs[ndx].additionalPositionAttribute != b.attribs[ndx].additionalPositionAttribute) return false; // component counts need not to match if (outputTypeIsFloatType(a.attribs[ndx].outputType) && outputTypeIsFloatType(b.attribs[ndx].outputType)) continue; if (outputTypeIsIntType(a.attribs[ndx].outputType) && outputTypeIsIntType(b.attribs[ndx].outputType)) continue; if (outputTypeIsUintType(a.attribs[ndx].outputType) && outputTypeIsUintType(b.attribs[ndx].outputType)) continue; return false; } return true; } // generate random vectors in a way that does not depend on argument evaluation order tcu::Vec4 generateRandomVec4 (de::Random& random) { tcu::Vec4 retVal; for (int i = 0; i < 4; ++i) retVal[i] = random.getFloat(); return retVal; } tcu::IVec4 generateRandomIVec4 (de::Random& random) { tcu::IVec4 retVal; for (int i = 0; i < 4; ++i) retVal[i] = random.getUint32(); return retVal; } tcu::UVec4 generateRandomUVec4 (de::Random& random) { tcu::UVec4 retVal; for (int i = 0; i < 4; ++i) retVal[i] = random.getUint32(); return retVal; } // IterationLogSectionEmitter class IterationLogSectionEmitter { public: IterationLogSectionEmitter (tcu::TestLog& log, size_t testIteration, size_t testIterations, const std::string& description, bool enabled); ~IterationLogSectionEmitter (void); private: IterationLogSectionEmitter (const IterationLogSectionEmitter&); // delete IterationLogSectionEmitter& operator= (const IterationLogSectionEmitter&); // delete tcu::TestLog& m_log; bool m_enabled; }; IterationLogSectionEmitter::IterationLogSectionEmitter (tcu::TestLog& log, size_t testIteration, size_t testIterations, const std::string& description, bool enabled) : m_log (log) , m_enabled (enabled) { if (m_enabled) { std::ostringstream buf; buf << "Iteration " << (testIteration+1) << "/" << testIterations; if (!description.empty()) buf << " - " << description; m_log << tcu::TestLog::Section(buf.str(), buf.str()); } } IterationLogSectionEmitter::~IterationLogSectionEmitter (void) { if (m_enabled) m_log << tcu::TestLog::EndSection; } // GLValue class GLValue { public: template class WrappedType { public: static WrappedType create (Type value) { WrappedType v; v.m_value = value; return v; } inline Type getValue (void) const { return m_value; } inline WrappedType operator+ (const WrappedType& other) const { return WrappedType::create(m_value + other.getValue()); } inline WrappedType operator* (const WrappedType& other) const { return WrappedType::create(m_value * other.getValue()); } inline WrappedType operator/ (const WrappedType& other) const { return WrappedType::create(m_value / other.getValue()); } inline WrappedType operator- (const WrappedType& other) const { return WrappedType::create(m_value - other.getValue()); } inline WrappedType& operator+= (const WrappedType& other) { m_value += other.getValue(); return *this; } inline WrappedType& operator*= (const WrappedType& other) { m_value *= other.getValue(); return *this; } inline WrappedType& operator/= (const WrappedType& other) { m_value /= other.getValue(); return *this; } inline WrappedType& operator-= (const WrappedType& other) { m_value -= other.getValue(); return *this; } inline bool operator== (const WrappedType& other) const { return m_value == other.m_value; } inline bool operator!= (const WrappedType& other) const { return m_value != other.m_value; } inline bool operator< (const WrappedType& other) const { return m_value < other.m_value; } inline bool operator> (const WrappedType& other) const { return m_value > other.m_value; } inline bool operator<= (const WrappedType& other) const { return m_value <= other.m_value; } inline bool operator>= (const WrappedType& other) const { return m_value >= other.m_value; } inline operator Type (void) const { return m_value; } template inline T to (void) const { return (T)m_value; } private: Type m_value; }; typedef WrappedType Short; typedef WrappedType Ushort; typedef WrappedType Byte; typedef WrappedType Ubyte; typedef WrappedType Float; typedef WrappedType Double; typedef WrappedType Int; typedef WrappedType Uint; class Half { public: static Half create (float value) { Half h; h.m_value = floatToHalf(value); return h; } inline deFloat16 getValue (void) const { return m_value; } inline Half operator+ (const Half& other) const { return create(halfToFloat(m_value) + halfToFloat(other.getValue())); } inline Half operator* (const Half& other) const { return create(halfToFloat(m_value) * halfToFloat(other.getValue())); } inline Half operator/ (const Half& other) const { return create(halfToFloat(m_value) / halfToFloat(other.getValue())); } inline Half operator- (const Half& other) const { return create(halfToFloat(m_value) - halfToFloat(other.getValue())); } inline Half& operator+= (const Half& other) { m_value = floatToHalf(halfToFloat(other.getValue()) + halfToFloat(m_value)); return *this; } inline Half& operator*= (const Half& other) { m_value = floatToHalf(halfToFloat(other.getValue()) * halfToFloat(m_value)); return *this; } inline Half& operator/= (const Half& other) { m_value = floatToHalf(halfToFloat(other.getValue()) / halfToFloat(m_value)); return *this; } inline Half& operator-= (const Half& other) { m_value = floatToHalf(halfToFloat(other.getValue()) - halfToFloat(m_value)); return *this; } inline bool operator== (const Half& other) const { return m_value == other.m_value; } inline bool operator!= (const Half& other) const { return m_value != other.m_value; } inline bool operator< (const Half& other) const { return halfToFloat(m_value) < halfToFloat(other.m_value); } inline bool operator> (const Half& other) const { return halfToFloat(m_value) > halfToFloat(other.m_value); } inline bool operator<= (const Half& other) const { return halfToFloat(m_value) <= halfToFloat(other.m_value); } inline bool operator>= (const Half& other) const { return halfToFloat(m_value) >= halfToFloat(other.m_value); } template inline T to (void) const { return (T)halfToFloat(m_value); } inline static deFloat16 floatToHalf (float f); inline static float halfToFloat (deFloat16 h); private: deFloat16 m_value; }; class Fixed { public: static Fixed create (deInt32 value) { Fixed v; v.m_value = value; return v; } inline deInt32 getValue (void) const { return m_value; } inline Fixed operator+ (const Fixed& other) const { return create(m_value + other.getValue()); } inline Fixed operator* (const Fixed& other) const { return create(m_value * other.getValue()); } inline Fixed operator/ (const Fixed& other) const { return create(m_value / other.getValue()); } inline Fixed operator- (const Fixed& other) const { return create(m_value - other.getValue()); } inline Fixed& operator+= (const Fixed& other) { m_value += other.getValue(); return *this; } inline Fixed& operator*= (const Fixed& other) { m_value *= other.getValue(); return *this; } inline Fixed& operator/= (const Fixed& other) { m_value /= other.getValue(); return *this; } inline Fixed& operator-= (const Fixed& other) { m_value -= other.getValue(); return *this; } inline bool operator== (const Fixed& other) const { return m_value == other.m_value; } inline bool operator!= (const Fixed& other) const { return m_value != other.m_value; } inline bool operator< (const Fixed& other) const { return m_value < other.m_value; } inline bool operator> (const Fixed& other) const { return m_value > other.m_value; } inline bool operator<= (const Fixed& other) const { return m_value <= other.m_value; } inline bool operator>= (const Fixed& other) const { return m_value >= other.m_value; } inline operator deInt32 (void) const { return m_value; } template inline T to (void) const { return (T)m_value; } private: deInt32 m_value; }; // \todo [mika] This is pretty messy GLValue (void) : type(DrawTestSpec::INPUTTYPE_LAST) {} explicit GLValue (Float value) : type(DrawTestSpec::INPUTTYPE_FLOAT), fl(value) {} explicit GLValue (Fixed value) : type(DrawTestSpec::INPUTTYPE_FIXED), fi(value) {} explicit GLValue (Byte value) : type(DrawTestSpec::INPUTTYPE_BYTE), b(value) {} explicit GLValue (Ubyte value) : type(DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE), ub(value) {} explicit GLValue (Short value) : type(DrawTestSpec::INPUTTYPE_SHORT), s(value) {} explicit GLValue (Ushort value) : type(DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT), us(value) {} explicit GLValue (Int value) : type(DrawTestSpec::INPUTTYPE_INT), i(value) {} explicit GLValue (Uint value) : type(DrawTestSpec::INPUTTYPE_UNSIGNED_INT), ui(value) {} explicit GLValue (Half value) : type(DrawTestSpec::INPUTTYPE_HALF), h(value) {} explicit GLValue (Double value) : type(DrawTestSpec::INPUTTYPE_DOUBLE), d(value) {} float toFloat (void) const; static GLValue getMaxValue (DrawTestSpec::InputType type); static GLValue getMinValue (DrawTestSpec::InputType type); DrawTestSpec::InputType type; union { Float fl; Fixed fi; Double d; Byte b; Ubyte ub; Short s; Ushort us; Int i; Uint ui; Half h; }; }; inline deFloat16 GLValue::Half::floatToHalf (float f) { // No denorm support. tcu::Float v(f); DE_ASSERT(!v.isNaN() && !v.isInf()); return v.bits(); } inline float GLValue::Half::halfToFloat (deFloat16 h) { return tcu::Float16((deUint16)h).asFloat(); } float GLValue::toFloat (void) const { switch (type) { case DrawTestSpec::INPUTTYPE_FLOAT: return fl.getValue(); break; case DrawTestSpec::INPUTTYPE_BYTE: return b.getValue(); break; case DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE: return ub.getValue(); break; case DrawTestSpec::INPUTTYPE_SHORT: return s.getValue(); break; case DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT: return us.getValue(); break; case DrawTestSpec::INPUTTYPE_FIXED: { int maxValue = 65536; return (float)(double(2 * fi.getValue() + 1) / (maxValue - 1)); break; } case DrawTestSpec::INPUTTYPE_UNSIGNED_INT: return (float)ui.getValue(); break; case DrawTestSpec::INPUTTYPE_INT: return (float)i.getValue(); break; case DrawTestSpec::INPUTTYPE_HALF: return h.to(); break; case DrawTestSpec::INPUTTYPE_DOUBLE: return d.to(); break; default: DE_ASSERT(false); return 0.0f; break; }; } GLValue GLValue::getMaxValue (DrawTestSpec::InputType type) { GLValue rangesHi[(int)DrawTestSpec::INPUTTYPE_LAST]; rangesHi[(int)DrawTestSpec::INPUTTYPE_FLOAT] = GLValue(Float::create(127.0f)); rangesHi[(int)DrawTestSpec::INPUTTYPE_DOUBLE] = GLValue(Double::create(127.0f)); rangesHi[(int)DrawTestSpec::INPUTTYPE_BYTE] = GLValue(Byte::create(127)); rangesHi[(int)DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE] = GLValue(Ubyte::create(255)); rangesHi[(int)DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT] = GLValue(Ushort::create(65530)); rangesHi[(int)DrawTestSpec::INPUTTYPE_SHORT] = GLValue(Short::create(32760)); rangesHi[(int)DrawTestSpec::INPUTTYPE_FIXED] = GLValue(Fixed::create(32760)); rangesHi[(int)DrawTestSpec::INPUTTYPE_INT] = GLValue(Int::create(2147483647)); rangesHi[(int)DrawTestSpec::INPUTTYPE_UNSIGNED_INT] = GLValue(Uint::create(4294967295u)); rangesHi[(int)DrawTestSpec::INPUTTYPE_HALF] = GLValue(Half::create(256.0f)); return rangesHi[(int)type]; } GLValue GLValue::getMinValue (DrawTestSpec::InputType type) { GLValue rangesLo[(int)DrawTestSpec::INPUTTYPE_LAST]; rangesLo[(int)DrawTestSpec::INPUTTYPE_FLOAT] = GLValue(Float::create(-127.0f)); rangesLo[(int)DrawTestSpec::INPUTTYPE_DOUBLE] = GLValue(Double::create(-127.0f)); rangesLo[(int)DrawTestSpec::INPUTTYPE_BYTE] = GLValue(Byte::create(-127)); rangesLo[(int)DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE] = GLValue(Ubyte::create(0)); rangesLo[(int)DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT] = GLValue(Ushort::create(0)); rangesLo[(int)DrawTestSpec::INPUTTYPE_SHORT] = GLValue(Short::create(-32760)); rangesLo[(int)DrawTestSpec::INPUTTYPE_FIXED] = GLValue(Fixed::create(-32760)); rangesLo[(int)DrawTestSpec::INPUTTYPE_INT] = GLValue(Int::create(-2147483647)); rangesLo[(int)DrawTestSpec::INPUTTYPE_UNSIGNED_INT] = GLValue(Uint::create(0)); rangesLo[(int)DrawTestSpec::INPUTTYPE_HALF] = GLValue(Half::create(-256.0f)); return rangesLo[(int)type]; } template struct GLValueTypeTraits; template<> struct GLValueTypeTraits { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_FLOAT; }; template<> struct GLValueTypeTraits { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_DOUBLE; }; template<> struct GLValueTypeTraits { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_BYTE; }; template<> struct GLValueTypeTraits { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE; }; template<> struct GLValueTypeTraits { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT; }; template<> struct GLValueTypeTraits { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_SHORT; }; template<> struct GLValueTypeTraits { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_FIXED; }; template<> struct GLValueTypeTraits { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_INT; }; template<> struct GLValueTypeTraits { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_UNSIGNED_INT; }; template<> struct GLValueTypeTraits { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_HALF; }; template inline T extractGLValue (const GLValue& v); template<> GLValue::Float inline extractGLValue (const GLValue& v) { return v.fl; }; template<> GLValue::Double inline extractGLValue (const GLValue& v) { return v.d; }; template<> GLValue::Byte inline extractGLValue (const GLValue& v) { return v.b; }; template<> GLValue::Ubyte inline extractGLValue (const GLValue& v) { return v.ub; }; template<> GLValue::Ushort inline extractGLValue (const GLValue& v) { return v.us; }; template<> GLValue::Short inline extractGLValue (const GLValue& v) { return v.s; }; template<> GLValue::Fixed inline extractGLValue (const GLValue& v) { return v.fi; }; template<> GLValue::Int inline extractGLValue (const GLValue& v) { return v.i; }; template<> GLValue::Uint inline extractGLValue (const GLValue& v) { return v.ui; }; template<> GLValue::Half inline extractGLValue (const GLValue& v) { return v.h; }; template inline T getRandom (deRandom& rnd, T min, T max); template<> inline GLValue::Float getRandom (deRandom& rnd, GLValue::Float min, GLValue::Float max) { if (max < min) return min; return GLValue::Float::create(min + deRandom_getFloat(&rnd) * (max.to() - min.to())); } template<> inline GLValue::Double getRandom (deRandom& rnd, GLValue::Double min, GLValue::Double max) { if (max < min) return min; return GLValue::Double::create(min + deRandom_getFloat(&rnd) * (max.to() - min.to())); } template<> inline GLValue::Short getRandom (deRandom& rnd, GLValue::Short min, GLValue::Short max) { if (max < min) return min; return GLValue::Short::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to() - min.to())))); } template<> inline GLValue::Ushort getRandom (deRandom& rnd, GLValue::Ushort min, GLValue::Ushort max) { if (max < min) return min; return GLValue::Ushort::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to() - min.to())))); } template<> inline GLValue::Byte getRandom (deRandom& rnd, GLValue::Byte min, GLValue::Byte max) { if (max < min) return min; return GLValue::Byte::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to() - min.to())))); } template<> inline GLValue::Ubyte getRandom (deRandom& rnd, GLValue::Ubyte min, GLValue::Ubyte max) { if (max < min) return min; return GLValue::Ubyte::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to() - min.to())))); } template<> inline GLValue::Fixed getRandom (deRandom& rnd, GLValue::Fixed min, GLValue::Fixed max) { if (max < min) return min; return GLValue::Fixed::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to() - min.to())))); } template<> inline GLValue::Half getRandom (deRandom& rnd, GLValue::Half min, GLValue::Half max) { if (max < min) return min; float fMax = max.to(); float fMin = min.to(); GLValue::Half h = GLValue::Half::create(fMin + deRandom_getFloat(&rnd) * (fMax - fMin)); return h; } template<> inline GLValue::Int getRandom (deRandom& rnd, GLValue::Int min, GLValue::Int max) { if (max < min) return min; return GLValue::Int::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to() - min.to())))); } template<> inline GLValue::Uint getRandom (deRandom& rnd, GLValue::Uint min, GLValue::Uint max) { if (max < min) return min; return GLValue::Uint::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to() - min.to())))); } // Minimum difference required between coordinates template inline T minValue (void); template<> inline GLValue::Float minValue (void) { return GLValue::Float::create(4 * 1.0f); } template<> inline GLValue::Double minValue (void) { return GLValue::Double::create(4 * 1.0f); } template<> inline GLValue::Short minValue (void) { return GLValue::Short::create(4 * 256); } template<> inline GLValue::Ushort minValue (void) { return GLValue::Ushort::create(4 * 256); } template<> inline GLValue::Byte minValue (void) { return GLValue::Byte::create(4 * 1); } template<> inline GLValue::Ubyte minValue (void) { return GLValue::Ubyte::create(4 * 2); } template<> inline GLValue::Fixed minValue (void) { return GLValue::Fixed::create(4 * 1); } template<> inline GLValue::Int minValue (void) { return GLValue::Int::create(4 * 16777216); } template<> inline GLValue::Uint minValue (void) { return GLValue::Uint::create(4 * 16777216); } template<> inline GLValue::Half minValue (void) { return GLValue::Half::create(4 * 1.0f); } template inline T abs (T val); template<> inline GLValue::Fixed abs (GLValue::Fixed val) { return GLValue::Fixed::create(0x7FFFu & val.getValue()); } template<> inline GLValue::Ubyte abs (GLValue::Ubyte val) { return val; } template<> inline GLValue::Byte abs (GLValue::Byte val) { return GLValue::Byte::create(0x7Fu & val.getValue()); } template<> inline GLValue::Ushort abs (GLValue::Ushort val) { return val; } template<> inline GLValue::Short abs (GLValue::Short val) { return GLValue::Short::create(0x7FFFu & val.getValue()); } template<> inline GLValue::Float abs (GLValue::Float val) { return GLValue::Float::create(std::fabs(val.to())); } template<> inline GLValue::Double abs (GLValue::Double val) { return GLValue::Double::create(std::fabs(val.to())); } template<> inline GLValue::Uint abs (GLValue::Uint val) { return val; } template<> inline GLValue::Int abs (GLValue::Int val) { return GLValue::Int::create(0x7FFFFFFFu & val.getValue()); } template<> inline GLValue::Half abs (GLValue::Half val) { return GLValue::Half::create(std::fabs(val.to())); } // AttriuteArray class AttributeArray { public: AttributeArray (DrawTestSpec::Storage storage, sglr::Context& context); ~AttributeArray (void); void data (DrawTestSpec::Target target, size_t size, const char* data, DrawTestSpec::Usage usage); void subdata (DrawTestSpec::Target target, int offset, int size, const char* data); void setupArray (bool bound, int offset, int size, DrawTestSpec::InputType inType, DrawTestSpec::OutputType outType, bool normalized, int stride, int instanceDivisor, const rr::GenericVec4& defaultAttrib, bool isPositionAttr, bool bgraComponentOrder); void bindAttribute (deUint32 loc); void bindIndexArray (DrawTestSpec::Target storage); int getComponentCount (void) const { return m_componentCount; } DrawTestSpec::Target getTarget (void) const { return m_target; } DrawTestSpec::InputType getInputType (void) const { return m_inputType; } DrawTestSpec::OutputType getOutputType (void) const { return m_outputType; } DrawTestSpec::Storage getStorageType (void) const { return m_storage; } bool getNormalized (void) const { return m_normalize; } int getStride (void) const { return m_stride; } bool isBound (void) const { return m_bound; } bool isPositionAttribute (void) const { return m_isPositionAttr; } private: DrawTestSpec::Storage m_storage; sglr::Context& m_ctx; deUint32 m_glBuffer; int m_size; char* m_data; int m_componentCount; bool m_bound; DrawTestSpec::Target m_target; DrawTestSpec::InputType m_inputType; DrawTestSpec::OutputType m_outputType; bool m_normalize; int m_stride; int m_offset; rr::GenericVec4 m_defaultAttrib; int m_instanceDivisor; bool m_isPositionAttr; bool m_bgraOrder; }; AttributeArray::AttributeArray (DrawTestSpec::Storage storage, sglr::Context& context) : m_storage (storage) , m_ctx (context) , m_glBuffer (0) , m_size (0) , m_data (DE_NULL) , m_componentCount (1) , m_bound (false) , m_target (DrawTestSpec::TARGET_ARRAY) , m_inputType (DrawTestSpec::INPUTTYPE_FLOAT) , m_outputType (DrawTestSpec::OUTPUTTYPE_VEC4) , m_normalize (false) , m_stride (0) , m_offset (0) , m_instanceDivisor (0) , m_isPositionAttr (false) , m_bgraOrder (false) { if (m_storage == DrawTestSpec::STORAGE_BUFFER) { m_ctx.genBuffers(1, &m_glBuffer); GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glGenBuffers()"); } } AttributeArray::~AttributeArray (void) { if (m_storage == DrawTestSpec::STORAGE_BUFFER) { m_ctx.deleteBuffers(1, &m_glBuffer); GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDeleteBuffers()"); } else if (m_storage == DrawTestSpec::STORAGE_USER) delete[] m_data; else DE_ASSERT(false); } void AttributeArray::data (DrawTestSpec::Target target, size_t size, const char* ptr, DrawTestSpec::Usage usage) { m_size = (int)size; m_target = target; if (m_storage == DrawTestSpec::STORAGE_BUFFER) { m_ctx.bindBuffer(targetToGL(target), m_glBuffer); GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glBindBuffer()"); m_ctx.bufferData(targetToGL(target), size, ptr, usageToGL(usage)); GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glBufferData()"); } else if (m_storage == DrawTestSpec::STORAGE_USER) { if (m_data) delete[] m_data; m_data = new char[size]; std::memcpy(m_data, ptr, size); } else DE_ASSERT(false); } void AttributeArray::subdata (DrawTestSpec::Target target, int offset, int size, const char* ptr) { m_target = target; if (m_storage == DrawTestSpec::STORAGE_BUFFER) { m_ctx.bindBuffer(targetToGL(target), m_glBuffer); GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glBindBuffer()"); m_ctx.bufferSubData(targetToGL(target), offset, size, ptr); GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glBufferSubData()"); } else if (m_storage == DrawTestSpec::STORAGE_USER) std::memcpy(m_data + offset, ptr, size); else DE_ASSERT(false); } void AttributeArray::setupArray (bool bound, int offset, int size, DrawTestSpec::InputType inputType, DrawTestSpec::OutputType outType, bool normalized, int stride, int instanceDivisor, const rr::GenericVec4& defaultAttrib, bool isPositionAttr, bool bgraComponentOrder) { m_componentCount = size; m_bound = bound; m_inputType = inputType; m_outputType = outType; m_normalize = normalized; m_stride = stride; m_offset = offset; m_defaultAttrib = defaultAttrib; m_instanceDivisor = instanceDivisor; m_isPositionAttr = isPositionAttr; m_bgraOrder = bgraComponentOrder; } void AttributeArray::bindAttribute (deUint32 loc) { if (!isBound()) { switch (m_inputType) { case DrawTestSpec::INPUTTYPE_FLOAT: { tcu::Vec4 attr = m_defaultAttrib.get(); switch (m_componentCount) { case 1: m_ctx.vertexAttrib1f(loc, attr.x()); break; case 2: m_ctx.vertexAttrib2f(loc, attr.x(), attr.y()); break; case 3: m_ctx.vertexAttrib3f(loc, attr.x(), attr.y(), attr.z()); break; case 4: m_ctx.vertexAttrib4f(loc, attr.x(), attr.y(), attr.z(), attr.w()); break; default: DE_ASSERT(DE_FALSE); break; } break; } case DrawTestSpec::INPUTTYPE_INT: { tcu::IVec4 attr = m_defaultAttrib.get(); m_ctx.vertexAttribI4i(loc, attr.x(), attr.y(), attr.z(), attr.w()); break; } case DrawTestSpec::INPUTTYPE_UNSIGNED_INT: { tcu::UVec4 attr = m_defaultAttrib.get(); m_ctx.vertexAttribI4ui(loc, attr.x(), attr.y(), attr.z(), attr.w()); break; } default: DE_ASSERT(DE_FALSE); break; } } else { const deUint8* basePtr = DE_NULL; if (m_storage == DrawTestSpec::STORAGE_BUFFER) { m_ctx.bindBuffer(targetToGL(m_target), m_glBuffer); GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glBindBuffer()"); basePtr = DE_NULL; } else if (m_storage == DrawTestSpec::STORAGE_USER) { m_ctx.bindBuffer(targetToGL(m_target), 0); GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glBindBuffer()"); basePtr = (const deUint8*)m_data; } else DE_ASSERT(DE_FALSE); if (!inputTypeIsFloatType(m_inputType)) { // Input is not float type if (outputTypeIsFloatType(m_outputType)) { const int size = (m_bgraOrder) ? (GL_BGRA) : (m_componentCount); DE_ASSERT(!(m_bgraOrder && m_componentCount != 4)); // Output type is float type m_ctx.vertexAttribPointer(loc, size, inputTypeToGL(m_inputType), m_normalize, m_stride, basePtr + m_offset); GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glVertexAttribPointer()"); } else { // Output type is int type m_ctx.vertexAttribIPointer(loc, m_componentCount, inputTypeToGL(m_inputType), m_stride, basePtr + m_offset); GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glVertexAttribIPointer()"); } } else { // Input type is float type // Output type must be float type DE_ASSERT(outputTypeIsFloatType(m_outputType)); m_ctx.vertexAttribPointer(loc, m_componentCount, inputTypeToGL(m_inputType), m_normalize, m_stride, basePtr + m_offset); GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glVertexAttribPointer()"); } if (m_instanceDivisor) m_ctx.vertexAttribDivisor(loc, m_instanceDivisor); } } void AttributeArray::bindIndexArray (DrawTestSpec::Target target) { if (m_storage == DrawTestSpec::STORAGE_USER) { } else if (m_storage == DrawTestSpec::STORAGE_BUFFER) { m_ctx.bindBuffer(targetToGL(target), m_glBuffer); } } // DrawTestShaderProgram class DrawTestShaderProgram : public sglr::ShaderProgram { public: DrawTestShaderProgram (const glu::RenderContext& ctx, const std::vector& arrays); void shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const; void shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const; private: static std::string genVertexSource (const glu::RenderContext& ctx, const std::vector& arrays); static std::string genFragmentSource (const glu::RenderContext& ctx); static void generateShaderParams (std::map& params, glu::ContextType type); static rr::GenericVecType mapOutputType (const DrawTestSpec::OutputType& type); static int getComponentCount (const DrawTestSpec::OutputType& type); static sglr::pdec::ShaderProgramDeclaration createProgramDeclaration (const glu::RenderContext& ctx, const std::vector& arrays); std::vector m_componentCount; std::vector m_isCoord; std::vector m_attrType; }; DrawTestShaderProgram::DrawTestShaderProgram (const glu::RenderContext& ctx, const std::vector& arrays) : sglr::ShaderProgram (createProgramDeclaration(ctx, arrays)) , m_componentCount (arrays.size()) , m_isCoord (arrays.size()) , m_attrType (arrays.size()) { for (int arrayNdx = 0; arrayNdx < (int)arrays.size(); arrayNdx++) { m_componentCount[arrayNdx] = getComponentCount(arrays[arrayNdx]->getOutputType()); m_isCoord[arrayNdx] = arrays[arrayNdx]->isPositionAttribute(); m_attrType[arrayNdx] = mapOutputType(arrays[arrayNdx]->getOutputType()); } } template void calcShaderColorCoord (tcu::Vec2& coord, tcu::Vec3& color, const tcu::Vector& attribValue, bool isCoordinate, int numComponents) { if (isCoordinate) switch (numComponents) { case 1: coord += tcu::Vec2((float)attribValue.x(), (float)attribValue.x()); break; case 2: coord += tcu::Vec2((float)attribValue.x(), (float)attribValue.y()); break; case 3: coord += tcu::Vec2((float)attribValue.x() + attribValue.z(), (float)attribValue.y()); break; case 4: coord += tcu::Vec2((float)attribValue.x() + attribValue.z(), (float)attribValue.y() + attribValue.w()); break; default: DE_ASSERT(false); } else { switch (numComponents) { case 1: color = color * (float)attribValue.x(); break; case 2: color.x() = color.x() * attribValue.x(); color.y() = color.y() * attribValue.y(); break; case 3: color.x() = color.x() * attribValue.x(); color.y() = color.y() * attribValue.y(); color.z() = color.z() * attribValue.z(); break; case 4: color.x() = color.x() * attribValue.x() * attribValue.w(); color.y() = color.y() * attribValue.y() * attribValue.w(); color.z() = color.z() * attribValue.z() * attribValue.w(); break; default: DE_ASSERT(false); } } } void DrawTestShaderProgram::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const { const float u_coordScale = getUniformByName("u_coordScale").value.f; const float u_colorScale = getUniformByName("u_colorScale").value.f; for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx) { const size_t varyingLocColor = 0; rr::VertexPacket& packet = *packets[packetNdx]; // Calc output color tcu::Vec2 coord = tcu::Vec2(0.0, 0.0); tcu::Vec3 color = tcu::Vec3(1.0, 1.0, 1.0); for (int attribNdx = 0; attribNdx < (int)m_attrType.size(); attribNdx++) { const int numComponents = m_componentCount[attribNdx]; const bool isCoord = m_isCoord[attribNdx]; switch (m_attrType[attribNdx]) { case rr::GENERICVECTYPE_FLOAT: calcShaderColorCoord(coord, color, rr::readVertexAttribFloat(inputs[attribNdx], packet.instanceNdx, packet.vertexNdx), isCoord, numComponents); break; case rr::GENERICVECTYPE_INT32: calcShaderColorCoord(coord, color, rr::readVertexAttribInt (inputs[attribNdx], packet.instanceNdx, packet.vertexNdx), isCoord, numComponents); break; case rr::GENERICVECTYPE_UINT32: calcShaderColorCoord(coord, color, rr::readVertexAttribUint (inputs[attribNdx], packet.instanceNdx, packet.vertexNdx), isCoord, numComponents); break; default: DE_ASSERT(false); } } // Transform position { packet.position = tcu::Vec4(u_coordScale * coord.x(), u_coordScale * coord.y(), 1.0f, 1.0f); packet.pointSize = 1.0f; } // Pass color to FS { packet.outputs[varyingLocColor] = tcu::Vec4(u_colorScale * color.x(), u_colorScale * color.y(), u_colorScale * color.z(), 1.0f) * 0.5f + tcu::Vec4(0.5f, 0.5f, 0.5f, 0.5f); } } } void DrawTestShaderProgram::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const { const size_t varyingLocColor = 0; for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx) { rr::FragmentPacket& packet = packets[packetNdx]; for (int fragNdx = 0; fragNdx < 4; ++fragNdx) rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, rr::readVarying(packet, context, varyingLocColor, fragNdx)); } } std::string DrawTestShaderProgram::genVertexSource (const glu::RenderContext& ctx, const std::vector& arrays) { std::map params; std::stringstream vertexShaderTmpl; generateShaderParams(params, ctx.getType()); vertexShaderTmpl << "${VTX_HDR}"; for (int arrayNdx = 0; arrayNdx < (int)arrays.size(); arrayNdx++) { vertexShaderTmpl << "${VTX_IN} highp " << outputTypeToGLType(arrays[arrayNdx]->getOutputType()) << " a_" << arrayNdx << ";\n"; } vertexShaderTmpl << "uniform highp float u_coordScale;\n" "uniform highp float u_colorScale;\n" "${VTX_OUT} ${COL_PRECISION} vec4 v_color;\n" "void main(void)\n" "{\n" "\tgl_PointSize = 1.0;\n" "\thighp vec2 coord = vec2(0.0, 0.0);\n" "\thighp vec3 color = vec3(1.0, 1.0, 1.0);\n"; for (int arrayNdx = 0; arrayNdx < (int)arrays.size(); arrayNdx++) { const bool isPositionAttr = arrays[arrayNdx]->isPositionAttribute(); if (isPositionAttr) { switch (arrays[arrayNdx]->getOutputType()) { case (DrawTestSpec::OUTPUTTYPE_FLOAT): case (DrawTestSpec::OUTPUTTYPE_INT): case (DrawTestSpec::OUTPUTTYPE_UINT): vertexShaderTmpl << "\tcoord += vec2(float(a_" << arrayNdx << "), float(a_" << arrayNdx << "));\n"; break; case (DrawTestSpec::OUTPUTTYPE_VEC2): case (DrawTestSpec::OUTPUTTYPE_IVEC2): case (DrawTestSpec::OUTPUTTYPE_UVEC2): vertexShaderTmpl << "\tcoord += vec2(a_" << arrayNdx << ".xy);\n"; break; case (DrawTestSpec::OUTPUTTYPE_VEC3): case (DrawTestSpec::OUTPUTTYPE_IVEC3): case (DrawTestSpec::OUTPUTTYPE_UVEC3): vertexShaderTmpl << "\tcoord += vec2(a_" << arrayNdx << ".xy);\n" "\tcoord.x += float(a_" << arrayNdx << ".z);\n"; break; case (DrawTestSpec::OUTPUTTYPE_VEC4): case (DrawTestSpec::OUTPUTTYPE_IVEC4): case (DrawTestSpec::OUTPUTTYPE_UVEC4): vertexShaderTmpl << "\tcoord += vec2(a_" << arrayNdx << ".xy);\n" "\tcoord += vec2(a_" << arrayNdx << ".zw);\n"; break; default: DE_ASSERT(false); break; } } else { switch (arrays[arrayNdx]->getOutputType()) { case (DrawTestSpec::OUTPUTTYPE_FLOAT): case (DrawTestSpec::OUTPUTTYPE_INT): case (DrawTestSpec::OUTPUTTYPE_UINT): vertexShaderTmpl << "\tcolor = color * float(a_" << arrayNdx << ");\n"; break; case (DrawTestSpec::OUTPUTTYPE_VEC2): case (DrawTestSpec::OUTPUTTYPE_IVEC2): case (DrawTestSpec::OUTPUTTYPE_UVEC2): vertexShaderTmpl << "\tcolor.rg = color.rg * vec2(a_" << arrayNdx << ".xy);\n"; break; case (DrawTestSpec::OUTPUTTYPE_VEC3): case (DrawTestSpec::OUTPUTTYPE_IVEC3): case (DrawTestSpec::OUTPUTTYPE_UVEC3): vertexShaderTmpl << "\tcolor = color.rgb * vec3(a_" << arrayNdx << ".xyz);\n"; break; case (DrawTestSpec::OUTPUTTYPE_VEC4): case (DrawTestSpec::OUTPUTTYPE_IVEC4): case (DrawTestSpec::OUTPUTTYPE_UVEC4): vertexShaderTmpl << "\tcolor = color.rgb * vec3(a_" << arrayNdx << ".xyz) * float(a_" << arrayNdx << ".w);\n"; break; default: DE_ASSERT(false); break; } } } vertexShaderTmpl << "\tv_color = vec4(u_colorScale * color, 1.0) * 0.5 + vec4(0.5, 0.5, 0.5, 0.5);\n" "\tgl_Position = vec4(u_coordScale * coord, 1.0, 1.0);\n" "}\n"; return tcu::StringTemplate(vertexShaderTmpl.str().c_str()).specialize(params); } std::string DrawTestShaderProgram::genFragmentSource (const glu::RenderContext& ctx) { std::map params; generateShaderParams(params, ctx.getType()); static const char* fragmentShaderTmpl = "${FRAG_HDR}" "${FRAG_IN} ${COL_PRECISION} vec4 v_color;\n" "void main(void)\n" "{\n" "\t${FRAG_COLOR} = v_color;\n" "}\n"; return tcu::StringTemplate(fragmentShaderTmpl).specialize(params); } void DrawTestShaderProgram::generateShaderParams (std::map& params, glu::ContextType type) { if (glu::isGLSLVersionSupported(type, glu::GLSL_VERSION_300_ES)) { params["VTX_IN"] = "in"; params["VTX_OUT"] = "out"; params["FRAG_IN"] = "in"; params["FRAG_COLOR"] = "dEQP_FragColor"; params["VTX_HDR"] = "#version 300 es\n"; params["FRAG_HDR"] = "#version 300 es\nlayout(location = 0) out mediump vec4 dEQP_FragColor;\n"; params["COL_PRECISION"] = "mediump"; } else if (glu::isGLSLVersionSupported(type, glu::GLSL_VERSION_100_ES)) { params["VTX_IN"] = "attribute"; params["VTX_OUT"] = "varying"; params["FRAG_IN"] = "varying"; params["FRAG_COLOR"] = "gl_FragColor"; params["VTX_HDR"] = ""; params["FRAG_HDR"] = ""; params["COL_PRECISION"] = "mediump"; } else if (glu::isGLSLVersionSupported(type, glu::GLSL_VERSION_430)) { params["VTX_IN"] = "in"; params["VTX_OUT"] = "out"; params["FRAG_IN"] = "in"; params["FRAG_COLOR"] = "dEQP_FragColor"; params["VTX_HDR"] = "#version 430\n"; params["FRAG_HDR"] = "#version 430\nlayout(location = 0) out highp vec4 dEQP_FragColor;\n"; params["COL_PRECISION"] = "highp"; } else if (glu::isGLSLVersionSupported(type, glu::GLSL_VERSION_330)) { params["VTX_IN"] = "in"; params["VTX_OUT"] = "out"; params["FRAG_IN"] = "in"; params["FRAG_COLOR"] = "dEQP_FragColor"; params["VTX_HDR"] = "#version 330\n"; params["FRAG_HDR"] = "#version 330\nlayout(location = 0) out mediump vec4 dEQP_FragColor;\n"; params["COL_PRECISION"] = "mediump"; } else DE_ASSERT(DE_FALSE); } rr::GenericVecType DrawTestShaderProgram::mapOutputType (const DrawTestSpec::OutputType& type) { switch (type) { case (DrawTestSpec::OUTPUTTYPE_FLOAT): case (DrawTestSpec::OUTPUTTYPE_VEC2): case (DrawTestSpec::OUTPUTTYPE_VEC3): case (DrawTestSpec::OUTPUTTYPE_VEC4): return rr::GENERICVECTYPE_FLOAT; case (DrawTestSpec::OUTPUTTYPE_INT): case (DrawTestSpec::OUTPUTTYPE_IVEC2): case (DrawTestSpec::OUTPUTTYPE_IVEC3): case (DrawTestSpec::OUTPUTTYPE_IVEC4): return rr::GENERICVECTYPE_INT32; case (DrawTestSpec::OUTPUTTYPE_UINT): case (DrawTestSpec::OUTPUTTYPE_UVEC2): case (DrawTestSpec::OUTPUTTYPE_UVEC3): case (DrawTestSpec::OUTPUTTYPE_UVEC4): return rr::GENERICVECTYPE_UINT32; default: DE_ASSERT(false); return rr::GENERICVECTYPE_LAST; } } int DrawTestShaderProgram::getComponentCount (const DrawTestSpec::OutputType& type) { switch (type) { case (DrawTestSpec::OUTPUTTYPE_FLOAT): case (DrawTestSpec::OUTPUTTYPE_INT): case (DrawTestSpec::OUTPUTTYPE_UINT): return 1; case (DrawTestSpec::OUTPUTTYPE_VEC2): case (DrawTestSpec::OUTPUTTYPE_IVEC2): case (DrawTestSpec::OUTPUTTYPE_UVEC2): return 2; case (DrawTestSpec::OUTPUTTYPE_VEC3): case (DrawTestSpec::OUTPUTTYPE_IVEC3): case (DrawTestSpec::OUTPUTTYPE_UVEC3): return 3; case (DrawTestSpec::OUTPUTTYPE_VEC4): case (DrawTestSpec::OUTPUTTYPE_IVEC4): case (DrawTestSpec::OUTPUTTYPE_UVEC4): return 4; default: DE_ASSERT(false); return 0; } } sglr::pdec::ShaderProgramDeclaration DrawTestShaderProgram::createProgramDeclaration (const glu::RenderContext& ctx, const std::vector& arrays) { sglr::pdec::ShaderProgramDeclaration decl; for (int arrayNdx = 0; arrayNdx < (int)arrays.size(); arrayNdx++) decl << sglr::pdec::VertexAttribute(std::string("a_") + de::toString(arrayNdx), mapOutputType(arrays[arrayNdx]->getOutputType())); decl << sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT); decl << sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT); decl << sglr::pdec::VertexSource(genVertexSource(ctx, arrays)); decl << sglr::pdec::FragmentSource(genFragmentSource(ctx)); decl << sglr::pdec::Uniform("u_coordScale", glu::TYPE_FLOAT); decl << sglr::pdec::Uniform("u_colorScale", glu::TYPE_FLOAT); return decl; } class RandomArrayGenerator { public: static char* generateArray (int seed, int elementCount, int componentCount, int offset, int stride, DrawTestSpec::InputType type); static char* generateIndices (int seed, int elementCount, DrawTestSpec::IndexType type, int offset, int min, int max, int indexBase); static rr::GenericVec4 generateAttributeValue (int seed, DrawTestSpec::InputType type); private: template static char* createIndices (int seed, int elementCount, int offset, int min, int max, int indexBase); static void setData (char* data, DrawTestSpec::InputType type, deRandom& rnd, GLValue min, GLValue max); static char* generateBasicArray (int seed, int elementCount, int componentCount, int offset, int stride, DrawTestSpec::InputType type); template static char* createBasicArray (int seed, int elementCount, int componentCount, int offset, int stride); static char* generatePackedArray (int seed, int elementCount, int componentCount, int offset, int stride); }; void RandomArrayGenerator::setData (char* data, DrawTestSpec::InputType type, deRandom& rnd, GLValue min, GLValue max) { switch (type) { case DrawTestSpec::INPUTTYPE_FLOAT: { alignmentSafeAssignment(data, getRandom(rnd, min.fl, max.fl)); break; } case DrawTestSpec::INPUTTYPE_SHORT: { alignmentSafeAssignment(data, getRandom(rnd, min.s, max.s)); break; } case DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT: { alignmentSafeAssignment(data, getRandom(rnd, min.us, max.us)); break; } case DrawTestSpec::INPUTTYPE_BYTE: { alignmentSafeAssignment(data, getRandom(rnd, min.b, max.b)); break; } case DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE: { alignmentSafeAssignment(data, getRandom(rnd, min.ub, max.ub)); break; } case DrawTestSpec::INPUTTYPE_FIXED: { alignmentSafeAssignment(data, getRandom(rnd, min.fi, max.fi)); break; } case DrawTestSpec::INPUTTYPE_INT: { alignmentSafeAssignment(data, getRandom(rnd, min.i, max.i)); break; } case DrawTestSpec::INPUTTYPE_UNSIGNED_INT: { alignmentSafeAssignment(data, getRandom(rnd, min.ui, max.ui)); break; } case DrawTestSpec::INPUTTYPE_HALF: { alignmentSafeAssignment(data, getRandom(rnd, min.h, max.h).getValue()); break; } default: DE_ASSERT(false); break; } } char* RandomArrayGenerator::generateArray (int seed, int elementCount, int componentCount, int offset, int stride, DrawTestSpec::InputType type) { if (type == DrawTestSpec::INPUTTYPE_INT_2_10_10_10 || type == DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10) return generatePackedArray(seed, elementCount, componentCount, offset, stride); else return generateBasicArray(seed, elementCount, componentCount, offset, stride, type); } char* RandomArrayGenerator::generateBasicArray (int seed, int elementCount, int componentCount, int offset, int stride, DrawTestSpec::InputType type) { switch (type) { case DrawTestSpec::INPUTTYPE_FLOAT: return createBasicArray (seed, elementCount, componentCount, offset, stride); case DrawTestSpec::INPUTTYPE_DOUBLE: return createBasicArray(seed, elementCount, componentCount, offset, stride); case DrawTestSpec::INPUTTYPE_SHORT: return createBasicArray (seed, elementCount, componentCount, offset, stride); case DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT: return createBasicArray(seed, elementCount, componentCount, offset, stride); case DrawTestSpec::INPUTTYPE_BYTE: return createBasicArray (seed, elementCount, componentCount, offset, stride); case DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE: return createBasicArray (seed, elementCount, componentCount, offset, stride); case DrawTestSpec::INPUTTYPE_FIXED: return createBasicArray (seed, elementCount, componentCount, offset, stride); case DrawTestSpec::INPUTTYPE_INT: return createBasicArray (seed, elementCount, componentCount, offset, stride); case DrawTestSpec::INPUTTYPE_UNSIGNED_INT: return createBasicArray (seed, elementCount, componentCount, offset, stride); case DrawTestSpec::INPUTTYPE_HALF: return createBasicArray (seed, elementCount, componentCount, offset, stride); default: DE_ASSERT(false); break; } return DE_NULL; } #if (DE_COMPILER == DE_COMPILER_GCC) && (__GNUC__ == 4) && (__GNUC_MINOR__ >= 8) // GCC 4.8/4.9 incorrectly emits array-bounds warning from createBasicArray() # define GCC_ARRAY_BOUNDS_FALSE_NEGATIVE 1 #endif #if defined(GCC_ARRAY_BOUNDS_FALSE_NEGATIVE) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Warray-bounds" #endif template char* RandomArrayGenerator::createBasicArray (int seed, int elementCount, int componentCount, int offset, int stride) { DE_ASSERT(componentCount >= 1 && componentCount <= 4); const GLType min = extractGLValue(GLValue::getMinValue(GLValueTypeTraits::Type)); const GLType max = extractGLValue(GLValue::getMaxValue(GLValueTypeTraits::Type)); const size_t componentSize = sizeof(T); const size_t elementSize = componentSize * componentCount; const size_t bufferSize = offset + (elementCount - 1) * stride + elementSize; char* data = new char[bufferSize]; char* writePtr = data + offset; GLType previousComponents[4]; deRandom rnd; deRandom_init(&rnd, seed); for (int vertexNdx = 0; vertexNdx < elementCount; vertexNdx++) { GLType components[4]; for (int componentNdx = 0; componentNdx < componentCount; componentNdx++) { components[componentNdx] = getRandom(rnd, min, max); // Try to not create vertex near previous if (vertexNdx != 0 && abs(components[componentNdx] - previousComponents[componentNdx]) < minValue()) { // Too close, try again (but only once) components[componentNdx] = getRandom(rnd, min, max); } } for (int componentNdx = 0; componentNdx < componentCount; componentNdx++) previousComponents[componentNdx] = components[componentNdx]; for (int componentNdx = 0; componentNdx < componentCount; componentNdx++) alignmentSafeAssignment(writePtr + componentNdx*componentSize, components[componentNdx].getValue()); writePtr += stride; } return data; } #if defined(GCC_ARRAY_BOUNDS_FALSE_NEGATIVE) # pragma GCC diagnostic pop #endif char* RandomArrayGenerator::generatePackedArray (int seed, int elementCount, int componentCount, int offset, int stride) { DE_ASSERT(componentCount == 4); DE_UNREF(componentCount); const deUint32 limit10 = (1 << 10); const deUint32 limit2 = (1 << 2); const size_t elementSize = 4; const size_t bufferSize = offset + (elementCount - 1) * stride + elementSize; char* data = new char[bufferSize]; char* writePtr = data + offset; deRandom rnd; deRandom_init(&rnd, seed); for (int vertexNdx = 0; vertexNdx < elementCount; vertexNdx++) { const deUint32 x = deRandom_getUint32(&rnd) % limit10; const deUint32 y = deRandom_getUint32(&rnd) % limit10; const deUint32 z = deRandom_getUint32(&rnd) % limit10; const deUint32 w = deRandom_getUint32(&rnd) % limit2; const deUint32 packedValue = (w << 30) | (z << 20) | (y << 10) | (x); alignmentSafeAssignment(writePtr, packedValue); writePtr += stride; } return data; } char* RandomArrayGenerator::generateIndices (int seed, int elementCount, DrawTestSpec::IndexType type, int offset, int min, int max, int indexBase) { char* data = DE_NULL; switch (type) { case DrawTestSpec::INDEXTYPE_BYTE: data = createIndices(seed, elementCount, offset, min, max, indexBase); break; case DrawTestSpec::INDEXTYPE_SHORT: data = createIndices(seed, elementCount, offset, min, max, indexBase); break; case DrawTestSpec::INDEXTYPE_INT: data = createIndices(seed, elementCount, offset, min, max, indexBase); break; default: DE_ASSERT(false); break; } return data; } template char* RandomArrayGenerator::createIndices (int seed, int elementCount, int offset, int min, int max, int indexBase) { const size_t elementSize = sizeof(T); const size_t bufferSize = offset + elementCount * elementSize; char* data = new char[bufferSize]; char* writePtr = data + offset; deUint32 oldNdx1 = deUint32(-1); deUint32 oldNdx2 = deUint32(-1); deRandom rnd; deRandom_init(&rnd, seed); DE_ASSERT(indexBase >= 0); // watch for underflows if (min < 0 || (size_t)min > std::numeric_limits::max() || max < 0 || (size_t)max > std::numeric_limits::max() || min > max) DE_ASSERT(!"Invalid range"); for (int elementNdx = 0; elementNdx < elementCount; ++elementNdx) { deUint32 ndx = getRandom(rnd, GLValue::Uint::create(min), GLValue::Uint::create(max)).getValue(); // Try not to generate same index as any of previous two. This prevents // generation of degenerate triangles and lines. If [min, max] is too // small this cannot be guaranteed. if (ndx == oldNdx1) ++ndx; if (ndx > (deUint32)max) ndx = min; if (ndx == oldNdx2) ++ndx; if (ndx > (deUint32)max) ndx = min; if (ndx == oldNdx1) ++ndx; if (ndx > (deUint32)max) ndx = min; oldNdx2 = oldNdx1; oldNdx1 = ndx; ndx += indexBase; alignmentSafeAssignment(writePtr + elementSize * elementNdx, T(ndx)); } return data; } rr::GenericVec4 RandomArrayGenerator::generateAttributeValue (int seed, DrawTestSpec::InputType type) { de::Random random(seed); switch (type) { case DrawTestSpec::INPUTTYPE_FLOAT: return rr::GenericVec4(generateRandomVec4(random)); case DrawTestSpec::INPUTTYPE_INT: return rr::GenericVec4(generateRandomIVec4(random)); case DrawTestSpec::INPUTTYPE_UNSIGNED_INT: return rr::GenericVec4(generateRandomUVec4(random)); default: DE_ASSERT(false); return rr::GenericVec4(tcu::Vec4(1, 1, 1, 1)); } } } // anonymous // AttributePack class AttributePack { public: AttributePack (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, sglr::Context& drawContext, const tcu::UVec2& screenSize, bool useVao, bool logEnabled); ~AttributePack (void); AttributeArray* getArray (int i); int getArrayCount (void); void newArray (DrawTestSpec::Storage storage); void clearArrays (void); void updateProgram (void); void render (DrawTestSpec::Primitive primitive, DrawTestSpec::DrawMethod drawMethod, int firstVertex, int vertexCount, DrawTestSpec::IndexType indexType, const void* indexOffset, int rangeStart, int rangeEnd, int instanceCount, int indirectOffset, int baseVertex, float coordScale, float colorScale, AttributeArray* indexArray); const tcu::Surface& getSurface (void) const { return m_screen; } private: tcu::TestContext& m_testCtx; glu::RenderContext& m_renderCtx; sglr::Context& m_ctx; std::vectorm_arrays; sglr::ShaderProgram* m_program; tcu::Surface m_screen; const bool m_useVao; const bool m_logEnabled; deUint32 m_programID; deUint32 m_vaoID; }; AttributePack::AttributePack (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, sglr::Context& drawContext, const tcu::UVec2& screenSize, bool useVao, bool logEnabled) : m_testCtx (testCtx) , m_renderCtx (renderCtx) , m_ctx (drawContext) , m_program (DE_NULL) , m_screen (screenSize.x(), screenSize.y()) , m_useVao (useVao) , m_logEnabled (logEnabled) , m_programID (0) , m_vaoID (0) { if (m_useVao) m_ctx.genVertexArrays(1, &m_vaoID); } AttributePack::~AttributePack (void) { clearArrays(); if (m_programID) m_ctx.deleteProgram(m_programID); if (m_program) delete m_program; if (m_useVao) m_ctx.deleteVertexArrays(1, &m_vaoID); } AttributeArray* AttributePack::getArray (int i) { return m_arrays.at(i); } int AttributePack::getArrayCount (void) { return (int)m_arrays.size(); } void AttributePack::newArray (DrawTestSpec::Storage storage) { m_arrays.push_back(new AttributeArray(storage, m_ctx)); } void AttributePack::clearArrays (void) { for (std::vector::iterator itr = m_arrays.begin(); itr != m_arrays.end(); itr++) delete *itr; m_arrays.clear(); } void AttributePack::updateProgram (void) { if (m_programID) m_ctx.deleteProgram(m_programID); if (m_program) delete m_program; m_program = new DrawTestShaderProgram(m_renderCtx, m_arrays); m_programID = m_ctx.createProgram(m_program); } void AttributePack::render (DrawTestSpec::Primitive primitive, DrawTestSpec::DrawMethod drawMethod, int firstVertex, int vertexCount, DrawTestSpec::IndexType indexType, const void* indexOffset, int rangeStart, int rangeEnd, int instanceCount, int indirectOffset, int baseVertex, float coordScale, float colorScale, AttributeArray* indexArray) { DE_ASSERT(m_program != DE_NULL); DE_ASSERT(m_programID != 0); m_ctx.viewport(0, 0, m_screen.getWidth(), m_screen.getHeight()); m_ctx.clearColor(0.0, 0.0, 0.0, 1.0); m_ctx.clear(GL_COLOR_BUFFER_BIT); m_ctx.useProgram(m_programID); GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glUseProgram()"); m_ctx.uniform1f(m_ctx.getUniformLocation(m_programID, "u_coordScale"), coordScale); m_ctx.uniform1f(m_ctx.getUniformLocation(m_programID, "u_colorScale"), colorScale); if (m_useVao) m_ctx.bindVertexArray(m_vaoID); if (indexArray) indexArray->bindIndexArray(DrawTestSpec::TARGET_ELEMENT_ARRAY); for (int arrayNdx = 0; arrayNdx < (int)m_arrays.size(); arrayNdx++) { std::stringstream attribName; attribName << "a_" << arrayNdx; deUint32 loc = m_ctx.getAttribLocation(m_programID, attribName.str().c_str()); if (m_arrays[arrayNdx]->isBound()) { m_ctx.enableVertexAttribArray(loc); GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glEnableVertexAttribArray()"); } m_arrays[arrayNdx]->bindAttribute(loc); } if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWARRAYS) { m_ctx.drawArrays(primitiveToGL(primitive), firstVertex, vertexCount); GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawArrays()"); } else if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWARRAYS_INSTANCED) { m_ctx.drawArraysInstanced(primitiveToGL(primitive), firstVertex, vertexCount, instanceCount); GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawArraysInstanced()"); } else if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWELEMENTS) { m_ctx.drawElements(primitiveToGL(primitive), vertexCount, indexTypeToGL(indexType), indexOffset); GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawElements()"); } else if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED) { m_ctx.drawRangeElements(primitiveToGL(primitive), rangeStart, rangeEnd, vertexCount, indexTypeToGL(indexType), indexOffset); GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawRangeElements()"); } else if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INSTANCED) { m_ctx.drawElementsInstanced(primitiveToGL(primitive), vertexCount, indexTypeToGL(indexType), indexOffset, instanceCount); GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawElementsInstanced()"); } else if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWARRAYS_INDIRECT) { struct DrawCommand { GLuint count; GLuint primCount; GLuint first; GLuint reservedMustBeZero; }; deUint8* buffer = new deUint8[sizeof(DrawCommand) + indirectOffset]; { DrawCommand command; command.count = vertexCount; command.primCount = instanceCount; command.first = firstVertex; command.reservedMustBeZero = 0; memcpy(buffer + indirectOffset, &command, sizeof(command)); if (m_logEnabled) m_testCtx.getLog() << tcu::TestLog::Message << "DrawArraysIndirectCommand:\n" << "\tcount: " << command.count << "\n" << "\tprimCount: " << command.primCount << "\n" << "\tfirst: " << command.first << "\n" << "\treservedMustBeZero: " << command.reservedMustBeZero << "\n" << tcu::TestLog::EndMessage; } GLuint indirectBuf = 0; m_ctx.genBuffers(1, &indirectBuf); m_ctx.bindBuffer(GL_DRAW_INDIRECT_BUFFER, indirectBuf); m_ctx.bufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(DrawCommand) + indirectOffset, buffer, GL_STATIC_DRAW); delete [] buffer; GLU_EXPECT_NO_ERROR(m_ctx.getError(), "Setup draw indirect buffer"); m_ctx.drawArraysIndirect(primitiveToGL(primitive), (const deInt8*)DE_NULL + indirectOffset); GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawArraysIndirect()"); m_ctx.deleteBuffers(1, &indirectBuf); } else if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INDIRECT) { struct DrawCommand { GLuint count; GLuint primCount; GLuint firstIndex; GLint baseVertex; GLuint reservedMustBeZero; }; deUint8* buffer = new deUint8[sizeof(DrawCommand) + indirectOffset]; { DrawCommand command; // index offset must be converted to firstIndex by dividing with the index element size DE_ASSERT(((const deUint8*)indexOffset - (const deUint8*)DE_NULL) % gls::DrawTestSpec::indexTypeSize(indexType) == 0); // \note This is checked in spec validation command.count = vertexCount; command.primCount = instanceCount; command.firstIndex = (glw::GLuint)(((const deUint8*)indexOffset - (const deUint8*)DE_NULL) / gls::DrawTestSpec::indexTypeSize(indexType)); command.baseVertex = baseVertex; command.reservedMustBeZero = 0; memcpy(buffer + indirectOffset, &command, sizeof(command)); if (m_logEnabled) m_testCtx.getLog() << tcu::TestLog::Message << "DrawElementsIndirectCommand:\n" << "\tcount: " << command.count << "\n" << "\tprimCount: " << command.primCount << "\n" << "\tfirstIndex: " << command.firstIndex << "\n" << "\tbaseVertex: " << command.baseVertex << "\n" << "\treservedMustBeZero: " << command.reservedMustBeZero << "\n" << tcu::TestLog::EndMessage; } GLuint indirectBuf = 0; m_ctx.genBuffers(1, &indirectBuf); m_ctx.bindBuffer(GL_DRAW_INDIRECT_BUFFER, indirectBuf); m_ctx.bufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(DrawCommand) + indirectOffset, buffer, GL_STATIC_DRAW); delete [] buffer; GLU_EXPECT_NO_ERROR(m_ctx.getError(), "Setup draw indirect buffer"); m_ctx.drawElementsIndirect(primitiveToGL(primitive), indexTypeToGL(indexType), (const deInt8*)DE_NULL + indirectOffset); GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawArraysIndirect()"); m_ctx.deleteBuffers(1, &indirectBuf); } else if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_BASEVERTEX) { m_ctx.drawElementsBaseVertex(primitiveToGL(primitive), vertexCount, indexTypeToGL(indexType), indexOffset, baseVertex); GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawElementsBaseVertex()"); } else if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INSTANCED_BASEVERTEX) { m_ctx.drawElementsInstancedBaseVertex(primitiveToGL(primitive), vertexCount, indexTypeToGL(indexType), indexOffset, instanceCount, baseVertex); GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawElementsInstancedBaseVertex()"); } else if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED_BASEVERTEX) { m_ctx.drawRangeElementsBaseVertex(primitiveToGL(primitive), rangeStart, rangeEnd, vertexCount, indexTypeToGL(indexType), indexOffset, baseVertex); GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawRangeElementsBaseVertex()"); } else DE_ASSERT(DE_FALSE); for (int arrayNdx = 0; arrayNdx < (int)m_arrays.size(); arrayNdx++) { if (m_arrays[arrayNdx]->isBound()) { std::stringstream attribName; attribName << "a_" << arrayNdx; deUint32 loc = m_ctx.getAttribLocation(m_programID, attribName.str().c_str()); m_ctx.disableVertexAttribArray(loc); GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDisableVertexAttribArray()"); } } if (m_useVao) m_ctx.bindVertexArray(0); m_ctx.useProgram(0); m_ctx.readPixels(m_screen, 0, 0, m_screen.getWidth(), m_screen.getHeight()); } // DrawTestSpec DrawTestSpec::AttributeSpec DrawTestSpec::AttributeSpec::createAttributeArray (InputType inputType, OutputType outputType, Storage storage, Usage usage, int componentCount, int offset, int stride, bool normalize, int instanceDivisor) { DrawTestSpec::AttributeSpec spec; spec.inputType = inputType; spec.outputType = outputType; spec.storage = storage; spec.usage = usage; spec.componentCount = componentCount; spec.offset = offset; spec.stride = stride; spec.normalize = normalize; spec.instanceDivisor = instanceDivisor; spec.useDefaultAttribute= false; return spec; } DrawTestSpec::AttributeSpec DrawTestSpec::AttributeSpec::createDefaultAttribute (InputType inputType, OutputType outputType, int componentCount) { DE_ASSERT(inputType == INPUTTYPE_INT || inputType == INPUTTYPE_UNSIGNED_INT || inputType == INPUTTYPE_FLOAT); DE_ASSERT(inputType == INPUTTYPE_FLOAT || componentCount == 4); DrawTestSpec::AttributeSpec spec; spec.inputType = inputType; spec.outputType = outputType; spec.storage = DrawTestSpec::STORAGE_LAST; spec.usage = DrawTestSpec::USAGE_LAST; spec.componentCount = componentCount; spec.offset = 0; spec.stride = 0; spec.normalize = 0; spec.instanceDivisor = 0; spec.useDefaultAttribute = true; return spec; } DrawTestSpec::AttributeSpec::AttributeSpec (void) { inputType = DrawTestSpec::INPUTTYPE_LAST; outputType = DrawTestSpec::OUTPUTTYPE_LAST; storage = DrawTestSpec::STORAGE_LAST; usage = DrawTestSpec::USAGE_LAST; componentCount = 0; offset = 0; stride = 0; normalize = false; instanceDivisor = 0; useDefaultAttribute = false; additionalPositionAttribute = false; bgraComponentOrder = false; } int DrawTestSpec::AttributeSpec::hash (void) const { if (useDefaultAttribute) { return 1 * int(inputType) + 7 * int(outputType) + 13 * componentCount; } else { return 1 * int(inputType) + 2 * int(outputType) + 3 * int(storage) + 5 * int(usage) + 7 * componentCount + 11 * offset + 13 * stride + 17 * (normalize ? 0 : 1) + 19 * instanceDivisor; } } bool DrawTestSpec::AttributeSpec::valid (glu::ApiType ctxType) const { const bool inputTypeFloat = inputType == DrawTestSpec::INPUTTYPE_FLOAT || inputType == DrawTestSpec::INPUTTYPE_FIXED || inputType == DrawTestSpec::INPUTTYPE_HALF; const bool inputTypeUnsignedInteger = inputType == DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE || inputType == DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT || inputType == DrawTestSpec::INPUTTYPE_UNSIGNED_INT || inputType == DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10; const bool inputTypeSignedInteger = inputType == DrawTestSpec::INPUTTYPE_BYTE || inputType == DrawTestSpec::INPUTTYPE_SHORT || inputType == DrawTestSpec::INPUTTYPE_INT || inputType == DrawTestSpec::INPUTTYPE_INT_2_10_10_10; const bool inputTypePacked = inputType == DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10 || inputType == DrawTestSpec::INPUTTYPE_INT_2_10_10_10; const bool outputTypeFloat = outputType == DrawTestSpec::OUTPUTTYPE_FLOAT || outputType == DrawTestSpec::OUTPUTTYPE_VEC2 || outputType == DrawTestSpec::OUTPUTTYPE_VEC3 || outputType == DrawTestSpec::OUTPUTTYPE_VEC4; const bool outputTypeSignedInteger = outputType == DrawTestSpec::OUTPUTTYPE_INT || outputType == DrawTestSpec::OUTPUTTYPE_IVEC2 || outputType == DrawTestSpec::OUTPUTTYPE_IVEC3 || outputType == DrawTestSpec::OUTPUTTYPE_IVEC4; const bool outputTypeUnsignedInteger = outputType == DrawTestSpec::OUTPUTTYPE_UINT || outputType == DrawTestSpec::OUTPUTTYPE_UVEC2 || outputType == DrawTestSpec::OUTPUTTYPE_UVEC3 || outputType == DrawTestSpec::OUTPUTTYPE_UVEC4; if (useDefaultAttribute) { if (inputType != DrawTestSpec::INPUTTYPE_INT && inputType != DrawTestSpec::INPUTTYPE_UNSIGNED_INT && inputType != DrawTestSpec::INPUTTYPE_FLOAT) return false; if (inputType != DrawTestSpec::INPUTTYPE_FLOAT && componentCount != 4) return false; // no casting allowed (undefined results) if (inputType == DrawTestSpec::INPUTTYPE_INT && !outputTypeSignedInteger) return false; if (inputType == DrawTestSpec::INPUTTYPE_UNSIGNED_INT && !outputTypeUnsignedInteger) return false; } if (inputTypePacked && componentCount != 4) return false; // Invalid conversions: // float -> [u]int if (inputTypeFloat && !outputTypeFloat) return false; // uint -> int (undefined results) if (inputTypeUnsignedInteger && outputTypeSignedInteger) return false; // int -> uint (undefined results) if (inputTypeSignedInteger && outputTypeUnsignedInteger) return false; // packed -> non-float (packed formats are converted to floats) if (inputTypePacked && !outputTypeFloat) return false; // Invalid normalize. Normalize is only valid if output type is float if (normalize && !outputTypeFloat) return false; // Allow reverse order (GL_BGRA) only for packed and 4-component ubyte if (bgraComponentOrder && componentCount != 4) return false; if (bgraComponentOrder && inputType != DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10 && inputType != DrawTestSpec::INPUTTYPE_INT_2_10_10_10 && inputType != DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE) return false; if (bgraComponentOrder && normalize != true) return false; // GLES2 limits if (ctxType == glu::ApiType::es(2,0)) { if (inputType != DrawTestSpec::INPUTTYPE_FLOAT && inputType != DrawTestSpec::INPUTTYPE_FIXED && inputType != DrawTestSpec::INPUTTYPE_BYTE && inputType != DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE && inputType != DrawTestSpec::INPUTTYPE_SHORT && inputType != DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT) return false; if (!outputTypeFloat) return false; if (bgraComponentOrder) return false; } // GLES3 limits if (ctxType.getProfile() == glu::PROFILE_ES && ctxType.getMajorVersion() == 3) { if (bgraComponentOrder) return false; } // No user pointers in GL core if (ctxType.getProfile() == glu::PROFILE_CORE) { if (!useDefaultAttribute && storage == DrawTestSpec::STORAGE_USER) return false; } return true; } bool DrawTestSpec::AttributeSpec::isBufferAligned (void) const { const bool inputTypePacked = inputType == DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10 || inputType == DrawTestSpec::INPUTTYPE_INT_2_10_10_10; // Buffer alignment, offset is a multiple of underlying data type size? if (storage == STORAGE_BUFFER) { int dataTypeSize = gls::DrawTestSpec::inputTypeSize(inputType); if (inputTypePacked) dataTypeSize = 4; if (offset % dataTypeSize != 0) return false; } return true; } bool DrawTestSpec::AttributeSpec::isBufferStrideAligned (void) const { const bool inputTypePacked = inputType == DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10 || inputType == DrawTestSpec::INPUTTYPE_INT_2_10_10_10; // Buffer alignment, offset is a multiple of underlying data type size? if (storage == STORAGE_BUFFER) { int dataTypeSize = gls::DrawTestSpec::inputTypeSize(inputType); if (inputTypePacked) dataTypeSize = 4; if (stride % dataTypeSize != 0) return false; } return true; } std::string DrawTestSpec::targetToString(Target target) { static const char* targets[] = { "element_array", // TARGET_ELEMENT_ARRAY = 0, "array" // TARGET_ARRAY, }; return de::getSizedArrayElement(targets, (int)target); } std::string DrawTestSpec::inputTypeToString(InputType type) { static const char* types[] = { "float", // INPUTTYPE_FLOAT = 0, "fixed", // INPUTTYPE_FIXED, "double", // INPUTTYPE_DOUBLE "byte", // INPUTTYPE_BYTE, "short", // INPUTTYPE_SHORT, "unsigned_byte", // INPUTTYPE_UNSIGNED_BYTE, "unsigned_short", // INPUTTYPE_UNSIGNED_SHORT, "int", // INPUTTYPE_INT, "unsigned_int", // INPUTTYPE_UNSIGNED_INT, "half", // INPUTTYPE_HALF, "unsigned_int2_10_10_10", // INPUTTYPE_UNSIGNED_INT_2_10_10_10, "int2_10_10_10" // INPUTTYPE_INT_2_10_10_10, }; return de::getSizedArrayElement(types, (int)type); } std::string DrawTestSpec::outputTypeToString(OutputType type) { static const char* types[] = { "float", // OUTPUTTYPE_FLOAT = 0, "vec2", // OUTPUTTYPE_VEC2, "vec3", // OUTPUTTYPE_VEC3, "vec4", // OUTPUTTYPE_VEC4, "int", // OUTPUTTYPE_INT, "uint", // OUTPUTTYPE_UINT, "ivec2", // OUTPUTTYPE_IVEC2, "ivec3", // OUTPUTTYPE_IVEC3, "ivec4", // OUTPUTTYPE_IVEC4, "uvec2", // OUTPUTTYPE_UVEC2, "uvec3", // OUTPUTTYPE_UVEC3, "uvec4", // OUTPUTTYPE_UVEC4, }; return de::getSizedArrayElement(types, (int)type); } std::string DrawTestSpec::usageTypeToString(Usage usage) { static const char* usages[] = { "dynamic_draw", // USAGE_DYNAMIC_DRAW = 0, "static_draw", // USAGE_STATIC_DRAW, "stream_draw", // USAGE_STREAM_DRAW, "stream_read", // USAGE_STREAM_READ, "stream_copy", // USAGE_STREAM_COPY, "static_read", // USAGE_STATIC_READ, "static_copy", // USAGE_STATIC_COPY, "dynamic_read", // USAGE_DYNAMIC_READ, "dynamic_copy", // USAGE_DYNAMIC_COPY, }; return de::getSizedArrayElement(usages, (int)usage); } std::string DrawTestSpec::storageToString (Storage storage) { static const char* storages[] = { "user_ptr", // STORAGE_USER = 0, "buffer" // STORAGE_BUFFER, }; return de::getSizedArrayElement(storages, (int)storage); } std::string DrawTestSpec::primitiveToString (Primitive primitive) { static const char* primitives[] = { "points", // PRIMITIVE_POINTS , "triangles", // PRIMITIVE_TRIANGLES, "triangle_fan", // PRIMITIVE_TRIANGLE_FAN, "triangle_strip", // PRIMITIVE_TRIANGLE_STRIP, "lines", // PRIMITIVE_LINES "line_strip", // PRIMITIVE_LINE_STRIP "line_loop", // PRIMITIVE_LINE_LOOP "lines_adjacency", // PRIMITIVE_LINES_ADJACENCY "line_strip_adjacency", // PRIMITIVE_LINE_STRIP_ADJACENCY "triangles_adjacency", // PRIMITIVE_TRIANGLES_ADJACENCY "triangle_strip_adjacency", // PRIMITIVE_TRIANGLE_STRIP_ADJACENCY }; return de::getSizedArrayElement(primitives, (int)primitive); } std::string DrawTestSpec::indexTypeToString (IndexType type) { static const char* indexTypes[] = { "byte", // INDEXTYPE_BYTE = 0, "short", // INDEXTYPE_SHORT, "int", // INDEXTYPE_INT, }; return de::getSizedArrayElement(indexTypes, (int)type); } std::string DrawTestSpec::drawMethodToString (DrawTestSpec::DrawMethod method) { static const char* methods[] = { "draw_arrays", //!< DRAWMETHOD_DRAWARRAYS "draw_arrays_instanced", //!< DRAWMETHOD_DRAWARRAYS_INSTANCED "draw_arrays_indirect", //!< DRAWMETHOD_DRAWARRAYS_INDIRECT "draw_elements", //!< DRAWMETHOD_DRAWELEMENTS "draw_range_elements", //!< DRAWMETHOD_DRAWELEMENTS_RANGED "draw_elements_instanced", //!< DRAWMETHOD_DRAWELEMENTS_INSTANCED "draw_elements_indirect", //!< DRAWMETHOD_DRAWELEMENTS_INDIRECT "draw_elements_base_vertex", //!< DRAWMETHOD_DRAWELEMENTS_BASEVERTEX, "draw_elements_instanced_base_vertex", //!< DRAWMETHOD_DRAWELEMENTS_INSTANCED_BASEVERTEX, "draw_range_elements_base_vertex", //!< DRAWMETHOD_DRAWELEMENTS_RANGED_BASEVERTEX, }; return de::getSizedArrayElement(methods, (int)method); } int DrawTestSpec::inputTypeSize (InputType type) { static const int size[] = { sizeof(float), // INPUTTYPE_FLOAT = 0, sizeof(deInt32), // INPUTTYPE_FIXED, sizeof(double), // INPUTTYPE_DOUBLE sizeof(deInt8), // INPUTTYPE_BYTE, sizeof(deInt16), // INPUTTYPE_SHORT, sizeof(deUint8), // INPUTTYPE_UNSIGNED_BYTE, sizeof(deUint16), // INPUTTYPE_UNSIGNED_SHORT, sizeof(deInt32), // INPUTTYPE_INT, sizeof(deUint32), // INPUTTYPE_UNSIGNED_INT, sizeof(deFloat16), // INPUTTYPE_HALF, sizeof(deUint32) / 4, // INPUTTYPE_UNSIGNED_INT_2_10_10_10, sizeof(deUint32) / 4 // INPUTTYPE_INT_2_10_10_10, }; return de::getSizedArrayElement(size, (int)type); } int DrawTestSpec::indexTypeSize (IndexType type) { static const int size[] = { sizeof(deUint8), // INDEXTYPE_BYTE, sizeof(deUint16), // INDEXTYPE_SHORT, sizeof(deUint32), // INDEXTYPE_INT, }; return de::getSizedArrayElement(size, (int)type); } std::string DrawTestSpec::getName (void) const { const MethodInfo methodInfo = getMethodInfo(drawMethod); const bool hasFirst = methodInfo.first; const bool instanced = methodInfo.instanced; const bool ranged = methodInfo.ranged; const bool indexed = methodInfo.indexed; std::stringstream name; for (size_t ndx = 0; ndx < attribs.size(); ++ndx) { const AttributeSpec& attrib = attribs[ndx]; if (attribs.size() > 1) name << "attrib" << ndx << "_"; if (ndx == 0|| attrib.additionalPositionAttribute) name << "pos_"; else name << "col_"; if (attrib.useDefaultAttribute) { name << "non_array_" << DrawTestSpec::inputTypeToString((DrawTestSpec::InputType)attrib.inputType) << "_" << attrib.componentCount << "_" << DrawTestSpec::outputTypeToString(attrib.outputType) << "_"; } else { name << DrawTestSpec::storageToString(attrib.storage) << "_" << attrib.offset << "_" << attrib.stride << "_" << DrawTestSpec::inputTypeToString((DrawTestSpec::InputType)attrib.inputType); if (attrib.inputType != DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10 && attrib.inputType != DrawTestSpec::INPUTTYPE_INT_2_10_10_10) name << attrib.componentCount; name << "_" << (attrib.normalize ? "normalized_" : "") << DrawTestSpec::outputTypeToString(attrib.outputType) << "_" << DrawTestSpec::usageTypeToString(attrib.usage) << "_" << attrib.instanceDivisor << "_"; } } if (indexed) name << "index_" << DrawTestSpec::indexTypeToString(indexType) << "_" << DrawTestSpec::storageToString(indexStorage) << "_" << "offset" << indexPointerOffset << "_"; if (hasFirst) name << "first" << first << "_"; if (ranged) name << "ranged_" << indexMin << "_" << indexMax << "_"; if (instanced) name << "instances" << instanceCount << "_"; switch (primitive) { case DrawTestSpec::PRIMITIVE_POINTS: name << "points_"; break; case DrawTestSpec::PRIMITIVE_TRIANGLES: name << "triangles_"; break; case DrawTestSpec::PRIMITIVE_TRIANGLE_FAN: name << "triangle_fan_"; break; case DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP: name << "triangle_strip_"; break; case DrawTestSpec::PRIMITIVE_LINES: name << "lines_"; break; case DrawTestSpec::PRIMITIVE_LINE_STRIP: name << "line_strip_"; break; case DrawTestSpec::PRIMITIVE_LINE_LOOP: name << "line_loop_"; break; case DrawTestSpec::PRIMITIVE_LINES_ADJACENCY: name << "line_adjancency"; break; case DrawTestSpec::PRIMITIVE_LINE_STRIP_ADJACENCY: name << "line_strip_adjancency"; break; case DrawTestSpec::PRIMITIVE_TRIANGLES_ADJACENCY: name << "triangles_adjancency"; break; case DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP_ADJACENCY: name << "triangle_strip_adjancency"; break; default: DE_ASSERT(false); break; } name << primitiveCount; return name.str(); } std::string DrawTestSpec::getDesc (void) const { std::stringstream desc; for (size_t ndx = 0; ndx < attribs.size(); ++ndx) { const AttributeSpec& attrib = attribs[ndx]; if (attrib.useDefaultAttribute) { desc << "Attribute " << ndx << ": default, " << ((ndx == 0|| attrib.additionalPositionAttribute) ? ("position ,") : ("color ,")) << "input datatype " << DrawTestSpec::inputTypeToString((DrawTestSpec::InputType)attrib.inputType) << ", " << "input component count " << attrib.componentCount << ", " << "used as " << DrawTestSpec::outputTypeToString(attrib.outputType) << ", "; } else { desc << "Attribute " << ndx << ": " << ((ndx == 0|| attrib.additionalPositionAttribute) ? ("position ,") : ("color ,")) << "Storage in " << DrawTestSpec::storageToString(attrib.storage) << ", " << "stride " << attrib.stride << ", " << "input datatype " << DrawTestSpec::inputTypeToString((DrawTestSpec::InputType)attrib.inputType) << ", " << "input component count " << attrib.componentCount << ", " << (attrib.normalize ? "normalized, " : "") << "used as " << DrawTestSpec::outputTypeToString(attrib.outputType) << ", " << "instance divisor " << attrib.instanceDivisor << ", "; } } if (drawMethod == DRAWMETHOD_DRAWARRAYS) { desc << "drawArrays(), " << "first " << first << ", "; } else if (drawMethod == DRAWMETHOD_DRAWARRAYS_INSTANCED) { desc << "drawArraysInstanced(), " << "first " << first << ", " << "instance count " << instanceCount << ", "; } else if (drawMethod == DRAWMETHOD_DRAWELEMENTS) { desc << "drawElements(), " << "index type " << DrawTestSpec::indexTypeToString(indexType) << ", " << "index storage in " << DrawTestSpec::storageToString(indexStorage) << ", " << "index offset " << indexPointerOffset << ", "; } else if (drawMethod == DRAWMETHOD_DRAWELEMENTS_RANGED) { desc << "drawElementsRanged(), " << "index type " << DrawTestSpec::indexTypeToString(indexType) << ", " << "index storage in " << DrawTestSpec::storageToString(indexStorage) << ", " << "index offset " << indexPointerOffset << ", " << "range start " << indexMin << ", " << "range end " << indexMax << ", "; } else if (drawMethod == DRAWMETHOD_DRAWELEMENTS_INSTANCED) { desc << "drawElementsInstanced(), " << "index type " << DrawTestSpec::indexTypeToString(indexType) << ", " << "index storage in " << DrawTestSpec::storageToString(indexStorage) << ", " << "index offset " << indexPointerOffset << ", " << "instance count " << instanceCount << ", "; } else if (drawMethod == DRAWMETHOD_DRAWARRAYS_INDIRECT) { desc << "drawArraysIndirect(), " << "first " << first << ", " << "instance count " << instanceCount << ", " << "indirect offset " << indirectOffset << ", "; } else if (drawMethod == DRAWMETHOD_DRAWELEMENTS_INDIRECT) { desc << "drawElementsIndirect(), " << "index type " << DrawTestSpec::indexTypeToString(indexType) << ", " << "index storage in " << DrawTestSpec::storageToString(indexStorage) << ", " << "index offset " << indexPointerOffset << ", " << "instance count " << instanceCount << ", " << "indirect offset " << indirectOffset << ", " << "base vertex " << baseVertex << ", "; } else DE_ASSERT(DE_FALSE); desc << primitiveCount; switch (primitive) { case DrawTestSpec::PRIMITIVE_POINTS: desc << "points"; break; case DrawTestSpec::PRIMITIVE_TRIANGLES: desc << "triangles"; break; case DrawTestSpec::PRIMITIVE_TRIANGLE_FAN: desc << "triangles (fan)"; break; case DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP: desc << "triangles (strip)"; break; case DrawTestSpec::PRIMITIVE_LINES: desc << "lines"; break; case DrawTestSpec::PRIMITIVE_LINE_STRIP: desc << "lines (strip)"; break; case DrawTestSpec::PRIMITIVE_LINE_LOOP: desc << "lines (loop)"; break; case DrawTestSpec::PRIMITIVE_LINES_ADJACENCY: desc << "lines (adjancency)"; break; case DrawTestSpec::PRIMITIVE_LINE_STRIP_ADJACENCY: desc << "lines (strip, adjancency)"; break; case DrawTestSpec::PRIMITIVE_TRIANGLES_ADJACENCY: desc << "triangles (adjancency)"; break; case DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP_ADJACENCY: desc << "triangles (strip, adjancency)"; break; default: DE_ASSERT(false); break; } return desc.str(); } std::string DrawTestSpec::getMultilineDesc (void) const { std::stringstream desc; for (size_t ndx = 0; ndx < attribs.size(); ++ndx) { const AttributeSpec& attrib = attribs[ndx]; if (attrib.useDefaultAttribute) { desc << "Attribute " << ndx << ": default, " << ((ndx == 0|| attrib.additionalPositionAttribute) ? ("position\n") : ("color\n")) << "\tinput datatype " << DrawTestSpec::inputTypeToString((DrawTestSpec::InputType)attrib.inputType) << "\n" << "\tinput component count " << attrib.componentCount << "\n" << "\tused as " << DrawTestSpec::outputTypeToString(attrib.outputType) << "\n"; } else { desc << "Attribute " << ndx << ": " << ((ndx == 0|| attrib.additionalPositionAttribute) ? ("position\n") : ("color\n")) << "\tStorage in " << DrawTestSpec::storageToString(attrib.storage) << "\n" << "\tstride " << attrib.stride << "\n" << "\tinput datatype " << DrawTestSpec::inputTypeToString((DrawTestSpec::InputType)attrib.inputType) << "\n" << "\tinput component count " << attrib.componentCount << "\n" << (attrib.normalize ? "\tnormalized\n" : "") << "\tused as " << DrawTestSpec::outputTypeToString(attrib.outputType) << "\n" << "\tinstance divisor " << attrib.instanceDivisor << "\n"; } } if (drawMethod == DRAWMETHOD_DRAWARRAYS) { desc << "drawArrays()\n" << "\tfirst " << first << "\n"; } else if (drawMethod == DRAWMETHOD_DRAWARRAYS_INSTANCED) { desc << "drawArraysInstanced()\n" << "\tfirst " << first << "\n" << "\tinstance count " << instanceCount << "\n"; } else if (drawMethod == DRAWMETHOD_DRAWELEMENTS) { desc << "drawElements()\n" << "\tindex type " << DrawTestSpec::indexTypeToString(indexType) << "\n" << "\tindex storage in " << DrawTestSpec::storageToString(indexStorage) << "\n" << "\tindex offset " << indexPointerOffset << "\n"; } else if (drawMethod == DRAWMETHOD_DRAWELEMENTS_RANGED) { desc << "drawElementsRanged()\n" << "\tindex type " << DrawTestSpec::indexTypeToString(indexType) << "\n" << "\tindex storage in " << DrawTestSpec::storageToString(indexStorage) << "\n" << "\tindex offset " << indexPointerOffset << "\n" << "\trange start " << indexMin << "\n" << "\trange end " << indexMax << "\n"; } else if (drawMethod == DRAWMETHOD_DRAWELEMENTS_INSTANCED) { desc << "drawElementsInstanced()\n" << "\tindex type " << DrawTestSpec::indexTypeToString(indexType) << "\n" << "\tindex storage in " << DrawTestSpec::storageToString(indexStorage) << "\n" << "\tindex offset " << indexPointerOffset << "\n" << "\tinstance count " << instanceCount << "\n"; } else if (drawMethod == DRAWMETHOD_DRAWARRAYS_INDIRECT) { desc << "drawArraysIndirect()\n" << "\tfirst " << first << "\n" << "\tinstance count " << instanceCount << "\n" << "\tindirect offset " << indirectOffset << "\n"; } else if (drawMethod == DRAWMETHOD_DRAWELEMENTS_INDIRECT) { desc << "drawElementsIndirect()\n" << "\tindex type " << DrawTestSpec::indexTypeToString(indexType) << "\n" << "\tindex storage in " << DrawTestSpec::storageToString(indexStorage) << "\n" << "\tindex offset " << indexPointerOffset << "\n" << "\tinstance count " << instanceCount << "\n" << "\tindirect offset " << indirectOffset << "\n" << "\tbase vertex " << baseVertex << "\n"; } else if (drawMethod == DRAWMETHOD_DRAWELEMENTS_BASEVERTEX) { desc << "drawElementsBaseVertex()\n" << "\tindex type " << DrawTestSpec::indexTypeToString(indexType) << "\n" << "\tindex storage in " << DrawTestSpec::storageToString(indexStorage) << "\n" << "\tindex offset " << indexPointerOffset << "\n" << "\tbase vertex " << baseVertex << "\n"; } else if (drawMethod == DRAWMETHOD_DRAWELEMENTS_INSTANCED_BASEVERTEX) { desc << "drawElementsInstancedBaseVertex()\n" << "\tindex type " << DrawTestSpec::indexTypeToString(indexType) << "\n" << "\tindex storage in " << DrawTestSpec::storageToString(indexStorage) << "\n" << "\tindex offset " << indexPointerOffset << "\n" << "\tinstance count " << instanceCount << "\n" << "\tbase vertex " << baseVertex << "\n"; } else if (drawMethod == DRAWMETHOD_DRAWELEMENTS_RANGED_BASEVERTEX) { desc << "drawRangeElementsBaseVertex()\n" << "\tindex type " << DrawTestSpec::indexTypeToString(indexType) << "\n" << "\tindex storage in " << DrawTestSpec::storageToString(indexStorage) << "\n" << "\tindex offset " << indexPointerOffset << "\n" << "\tbase vertex " << baseVertex << "\n" << "\trange start " << indexMin << "\n" << "\trange end " << indexMax << "\n"; } else DE_ASSERT(DE_FALSE); desc << "\t" << primitiveCount << " "; switch (primitive) { case DrawTestSpec::PRIMITIVE_POINTS: desc << "points"; break; case DrawTestSpec::PRIMITIVE_TRIANGLES: desc << "triangles"; break; case DrawTestSpec::PRIMITIVE_TRIANGLE_FAN: desc << "triangles (fan)"; break; case DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP: desc << "triangles (strip)"; break; case DrawTestSpec::PRIMITIVE_LINES: desc << "lines"; break; case DrawTestSpec::PRIMITIVE_LINE_STRIP: desc << "lines (strip)"; break; case DrawTestSpec::PRIMITIVE_LINE_LOOP: desc << "lines (loop)"; break; case DrawTestSpec::PRIMITIVE_LINES_ADJACENCY: desc << "lines (adjancency)"; break; case DrawTestSpec::PRIMITIVE_LINE_STRIP_ADJACENCY: desc << "lines (strip, adjancency)"; break; case DrawTestSpec::PRIMITIVE_TRIANGLES_ADJACENCY: desc << "triangles (adjancency)"; break; case DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP_ADJACENCY: desc << "triangles (strip, adjancency)"; break; default: DE_ASSERT(false); break; } desc << "\n"; return desc.str(); } DrawTestSpec::DrawTestSpec (void) { primitive = PRIMITIVE_LAST; primitiveCount = 0; drawMethod = DRAWMETHOD_LAST; indexType = INDEXTYPE_LAST; indexPointerOffset = 0; indexStorage = STORAGE_LAST; first = 0; indexMin = 0; indexMax = 0; instanceCount = 0; indirectOffset = 0; baseVertex = 0; } int DrawTestSpec::hash (void) const { // Use only drawmode-relevant values in "hashing" as the unrelevant values might not be set (causing non-deterministic behavior). const MethodInfo methodInfo = getMethodInfo(drawMethod); const bool arrayed = methodInfo.first; const bool instanced = methodInfo.instanced; const bool ranged = methodInfo.ranged; const bool indexed = methodInfo.indexed; const bool indirect = methodInfo.indirect; const bool hasBaseVtx = methodInfo.baseVertex; const int indexHash = (!indexed) ? (0) : (int(indexType) + 10 * indexPointerOffset + 100 * int(indexStorage)); const int arrayHash = (!arrayed) ? (0) : (first); const int indexRangeHash = (!ranged) ? (0) : (indexMin + 10 * indexMax); const int instanceHash = (!instanced) ? (0) : (instanceCount); const int indirectHash = (!indirect) ? (0) : (indirectOffset); const int baseVtxHash = (!hasBaseVtx) ? (0) : (baseVertex); const int basicHash = int(primitive) + 10 * primitiveCount + 100 * int(drawMethod); return indexHash + 3 * arrayHash + 5 * indexRangeHash + 7 * instanceHash + 13 * basicHash + 17 * (int)attribs.size() + 19 * primitiveCount + 23 * indirectHash + 27 * baseVtxHash; } bool DrawTestSpec::valid (void) const { DE_ASSERT(apiType.getProfile() != glu::PROFILE_LAST); DE_ASSERT(primitive != PRIMITIVE_LAST); DE_ASSERT(drawMethod != DRAWMETHOD_LAST); const MethodInfo methodInfo = getMethodInfo(drawMethod); for (int ndx = 0; ndx < (int)attribs.size(); ++ndx) if (!attribs[ndx].valid(apiType)) return false; if (methodInfo.ranged) { deUint32 maxIndexValue = 0; if (indexType == INDEXTYPE_BYTE) maxIndexValue = GLValue::getMaxValue(INPUTTYPE_UNSIGNED_BYTE).ub.getValue(); else if (indexType == INDEXTYPE_SHORT) maxIndexValue = GLValue::getMaxValue(INPUTTYPE_UNSIGNED_SHORT).us.getValue(); else if (indexType == INDEXTYPE_INT) maxIndexValue = GLValue::getMaxValue(INPUTTYPE_UNSIGNED_INT).ui.getValue(); else DE_ASSERT(DE_FALSE); if (indexMin > indexMax) return false; if (indexMin < 0 || indexMax < 0) return false; if ((deUint32)indexMin > maxIndexValue || (deUint32)indexMax > maxIndexValue) return false; } if (methodInfo.first && first < 0) return false; // GLES2 limits if (apiType == glu::ApiType::es(2,0)) { if (drawMethod != gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS && drawMethod != gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS) return false; if (drawMethod == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS && (indexType != INDEXTYPE_BYTE && indexType != INDEXTYPE_SHORT)) return false; } // Indirect limitations if (methodInfo.indirect) { // Indirect offset alignment if (indirectOffset % 4 != 0) return false; // All attribute arrays must be stored in a buffer for (int ndx = 0; ndx < (int)attribs.size(); ++ndx) if (!attribs[ndx].useDefaultAttribute && attribs[ndx].storage == gls::DrawTestSpec::STORAGE_USER) return false; } if (drawMethod == DRAWMETHOD_DRAWELEMENTS_INDIRECT) { // index offset must be convertable to firstIndex if (indexPointerOffset % gls::DrawTestSpec::indexTypeSize(indexType) != 0) return false; // Indices must be in a buffer if (indexStorage != STORAGE_BUFFER) return false; } // Do not allow user pointer in GL core if (apiType.getProfile() == glu::PROFILE_CORE) { if (methodInfo.indexed && indexStorage == DrawTestSpec::STORAGE_USER) return false; } return true; } DrawTestSpec::CompatibilityTestType DrawTestSpec::isCompatibilityTest (void) const { const MethodInfo methodInfo = getMethodInfo(drawMethod); bool bufferAlignmentBad = false; bool strideAlignmentBad = false; // Attribute buffer alignment for (int ndx = 0; ndx < (int)attribs.size(); ++ndx) if (!attribs[ndx].isBufferAligned()) bufferAlignmentBad = true; // Attribute stride alignment for (int ndx = 0; ndx < (int)attribs.size(); ++ndx) if (!attribs[ndx].isBufferStrideAligned()) strideAlignmentBad = true; // Index buffer alignment if (methodInfo.indexed) { if (indexStorage == STORAGE_BUFFER) { int indexSize = 0; if (indexType == INDEXTYPE_BYTE) indexSize = 1; else if (indexType == INDEXTYPE_SHORT) indexSize = 2; else if (indexType == INDEXTYPE_INT) indexSize = 4; else DE_ASSERT(DE_FALSE); if (indexPointerOffset % indexSize != 0) bufferAlignmentBad = true; } } // \note combination bad alignment & stride is treated as bad offset if (bufferAlignmentBad) return COMPATIBILITY_UNALIGNED_OFFSET; else if (strideAlignmentBad) return COMPATIBILITY_UNALIGNED_STRIDE; else return COMPATIBILITY_NONE; } enum PrimitiveClass { PRIMITIVECLASS_POINT = 0, PRIMITIVECLASS_LINE, PRIMITIVECLASS_TRIANGLE, PRIMITIVECLASS_LAST }; static PrimitiveClass getDrawPrimitiveClass (gls::DrawTestSpec::Primitive primitiveType) { switch (primitiveType) { case gls::DrawTestSpec::PRIMITIVE_POINTS: return PRIMITIVECLASS_POINT; case gls::DrawTestSpec::PRIMITIVE_LINES: case gls::DrawTestSpec::PRIMITIVE_LINE_STRIP: case gls::DrawTestSpec::PRIMITIVE_LINE_LOOP: case gls::DrawTestSpec::PRIMITIVE_LINES_ADJACENCY: case gls::DrawTestSpec::PRIMITIVE_LINE_STRIP_ADJACENCY: return PRIMITIVECLASS_LINE; case gls::DrawTestSpec::PRIMITIVE_TRIANGLES: case gls::DrawTestSpec::PRIMITIVE_TRIANGLE_FAN: case gls::DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP: case gls::DrawTestSpec::PRIMITIVE_TRIANGLES_ADJACENCY: case gls::DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP_ADJACENCY: return PRIMITIVECLASS_TRIANGLE; default: DE_ASSERT(false); return PRIMITIVECLASS_LAST; } } static bool containsLineCases (const std::vector& m_specs) { for (int ndx = 0; ndx < (int)m_specs.size(); ++ndx) { if (getDrawPrimitiveClass(m_specs[ndx].primitive) == PRIMITIVECLASS_LINE) return true; } return false; } // DrawTest DrawTest::DrawTest (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const DrawTestSpec& spec, const char* name, const char* desc) : TestCase (testCtx, name, desc) , m_renderCtx (renderCtx) , m_refBuffers (DE_NULL) , m_refContext (DE_NULL) , m_glesContext (DE_NULL) , m_glArrayPack (DE_NULL) , m_rrArrayPack (DE_NULL) , m_maxDiffRed (-1) , m_maxDiffGreen (-1) , m_maxDiffBlue (-1) , m_iteration (0) , m_result () // \note no per-iteration result logging (only one iteration) { addIteration(spec); } DrawTest::DrawTest (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* desc) : TestCase (testCtx, name, desc) , m_renderCtx (renderCtx) , m_refBuffers (DE_NULL) , m_refContext (DE_NULL) , m_glesContext (DE_NULL) , m_glArrayPack (DE_NULL) , m_rrArrayPack (DE_NULL) , m_maxDiffRed (-1) , m_maxDiffGreen (-1) , m_maxDiffBlue (-1) , m_iteration (0) , m_result (testCtx.getLog(), "Iteration result: ") { } DrawTest::~DrawTest (void) { deinit(); } void DrawTest::addIteration (const DrawTestSpec& spec, const char* description) { // Validate spec const bool validSpec = spec.valid(); DE_ASSERT(validSpec); if (!validSpec) return; // Check the context type is the same with other iterations if (!m_specs.empty()) { const bool validContext = m_specs[0].apiType == spec.apiType; DE_ASSERT(validContext); if (!validContext) return; } m_specs.push_back(spec); if (description) m_iteration_descriptions.push_back(std::string(description)); else m_iteration_descriptions.push_back(std::string()); } void DrawTest::init (void) { DE_ASSERT(!m_specs.empty()); DE_ASSERT(contextSupports(m_renderCtx.getType(), m_specs[0].apiType)); const int renderTargetWidth = de::min(MAX_RENDER_TARGET_SIZE, m_renderCtx.getRenderTarget().getWidth()); const int renderTargetHeight = de::min(MAX_RENDER_TARGET_SIZE, m_renderCtx.getRenderTarget().getHeight()); // lines have significantly different rasterization in MSAA mode const bool isLineCase = containsLineCases(m_specs); const bool isMSAACase = m_renderCtx.getRenderTarget().getNumSamples() > 1; const int renderTargetSamples = (isMSAACase && isLineCase) ? (4) : (1); sglr::ReferenceContextLimits limits (m_renderCtx); bool useVao = false; m_glesContext = new sglr::GLContext(m_renderCtx, m_testCtx.getLog(), sglr::GLCONTEXT_LOG_CALLS | sglr::GLCONTEXT_LOG_PROGRAMS, tcu::IVec4(0, 0, renderTargetWidth, renderTargetHeight)); if (m_renderCtx.getType().getAPI() == glu::ApiType::es(2,0) || m_renderCtx.getType().getAPI() == glu::ApiType::es(3,0)) useVao = false; else if (contextSupports(m_renderCtx.getType(), glu::ApiType::es(3,1)) || glu::isContextTypeGLCore(m_renderCtx.getType())) useVao = true; else DE_ASSERT(!"Unknown context type"); m_refBuffers = new sglr::ReferenceContextBuffers(m_renderCtx.getRenderTarget().getPixelFormat(), 0, 0, renderTargetWidth, renderTargetHeight, renderTargetSamples); m_refContext = new sglr::ReferenceContext(limits, m_refBuffers->getColorbuffer(), m_refBuffers->getDepthbuffer(), m_refBuffers->getStencilbuffer()); m_glArrayPack = new AttributePack(m_testCtx, m_renderCtx, *m_glesContext, tcu::UVec2(renderTargetWidth, renderTargetHeight), useVao, true); m_rrArrayPack = new AttributePack(m_testCtx, m_renderCtx, *m_refContext, tcu::UVec2(renderTargetWidth, renderTargetHeight), useVao, false); m_maxDiffRed = deCeilFloatToInt32(256.0f * (6.0f / (1 << m_renderCtx.getRenderTarget().getPixelFormat().redBits))); m_maxDiffGreen = deCeilFloatToInt32(256.0f * (6.0f / (1 << m_renderCtx.getRenderTarget().getPixelFormat().greenBits))); m_maxDiffBlue = deCeilFloatToInt32(256.0f * (6.0f / (1 << m_renderCtx.getRenderTarget().getPixelFormat().blueBits))); } void DrawTest::deinit (void) { delete m_glArrayPack; delete m_rrArrayPack; delete m_refBuffers; delete m_refContext; delete m_glesContext; m_glArrayPack = DE_NULL; m_rrArrayPack = DE_NULL; m_refBuffers = DE_NULL; m_refContext = DE_NULL; m_glesContext = DE_NULL; } DrawTest::IterateResult DrawTest::iterate (void) { const int specNdx = (m_iteration / 2); const bool drawStep = (m_iteration % 2) == 0; const bool compareStep = (m_iteration % 2) == 1; const IterateResult iterateResult = ((size_t)m_iteration + 1 == m_specs.size()*2) ? (STOP) : (CONTINUE); const DrawTestSpec& spec = m_specs[specNdx]; const bool updateProgram = (m_iteration == 0) || (drawStep && !checkSpecsShaderCompatible(m_specs[specNdx], m_specs[specNdx-1])); // try to use the same shader in all iterations IterationLogSectionEmitter sectionEmitter (m_testCtx.getLog(), specNdx, m_specs.size(), m_iteration_descriptions[specNdx], drawStep && m_specs.size()!=1); if (drawStep) { const MethodInfo methodInfo = getMethodInfo(spec.drawMethod); const bool indexed = methodInfo.indexed; const bool instanced = methodInfo.instanced; const bool ranged = methodInfo.ranged; const bool hasFirst = methodInfo.first; const bool hasBaseVtx = methodInfo.baseVertex; const size_t primitiveElementCount = getElementCount(spec.primitive, spec.primitiveCount); // !< elements to be drawn const int indexMin = (ranged) ? (spec.indexMin) : (0); const int firstAddition = (hasFirst) ? (spec.first) : (0); const int baseVertexAddition = (hasBaseVtx && spec.baseVertex > 0) ? ( spec.baseVertex) : (0); // spec.baseVertex > 0 => Create bigger attribute buffer const int indexBase = (hasBaseVtx && spec.baseVertex < 0) ? (-spec.baseVertex) : (0); // spec.baseVertex < 0 => Create bigger indices const size_t elementCount = primitiveElementCount + indexMin + firstAddition + baseVertexAddition; // !< elements in buffer (buffer should have at least primitiveElementCount ACCESSIBLE (index range, first) elements) const int maxElementIndex = (int)primitiveElementCount + indexMin + firstAddition - 1; const int indexMax = de::max(0, (ranged) ? (de::clamp(spec.indexMax, 0, maxElementIndex)) : (maxElementIndex)); float coordScale = getCoordScale(spec); float colorScale = getColorScale(spec); rr::GenericVec4 nullAttribValue; // Log info m_testCtx.getLog() << TestLog::Message << spec.getMultilineDesc() << TestLog::EndMessage; m_testCtx.getLog() << TestLog::Message << TestLog::EndMessage; // extra line for clarity // Data m_glArrayPack->clearArrays(); m_rrArrayPack->clearArrays(); for (int attribNdx = 0; attribNdx < (int)spec.attribs.size(); attribNdx++) { DrawTestSpec::AttributeSpec attribSpec = spec.attribs[attribNdx]; const bool isPositionAttr = (attribNdx == 0) || (attribSpec.additionalPositionAttribute); if (attribSpec.useDefaultAttribute) { const int seed = 10 * attribSpec.hash() + 100 * spec.hash() + attribNdx; rr::GenericVec4 attribValue = RandomArrayGenerator::generateAttributeValue(seed, attribSpec.inputType); m_glArrayPack->newArray(DrawTestSpec::STORAGE_USER); m_rrArrayPack->newArray(DrawTestSpec::STORAGE_USER); m_glArrayPack->getArray(attribNdx)->setupArray(false, 0, attribSpec.componentCount, attribSpec.inputType, attribSpec.outputType, false, 0, 0, attribValue, isPositionAttr, false); m_rrArrayPack->getArray(attribNdx)->setupArray(false, 0, attribSpec.componentCount, attribSpec.inputType, attribSpec.outputType, false, 0, 0, attribValue, isPositionAttr, false); } else { const int seed = attribSpec.hash() + 100 * spec.hash() + attribNdx; const size_t elementSize = attribSpec.componentCount * DrawTestSpec::inputTypeSize(attribSpec.inputType); const size_t stride = (attribSpec.stride == 0) ? (elementSize) : (attribSpec.stride); const size_t evaluatedElementCount = (instanced && attribSpec.instanceDivisor > 0) ? (spec.instanceCount / attribSpec.instanceDivisor + 1) : (elementCount); const size_t referencedElementCount = (ranged) ? (de::max(evaluatedElementCount, spec.indexMax + 1)) : (evaluatedElementCount); const size_t bufferSize = attribSpec.offset + stride * (referencedElementCount - 1) + elementSize; const char* data = RandomArrayGenerator::generateArray(seed, (int)referencedElementCount, attribSpec.componentCount, attribSpec.offset, (int)stride, attribSpec.inputType); try { m_glArrayPack->newArray(attribSpec.storage); m_rrArrayPack->newArray(attribSpec.storage); m_glArrayPack->getArray(attribNdx)->data(DrawTestSpec::TARGET_ARRAY, bufferSize, data, attribSpec.usage); m_rrArrayPack->getArray(attribNdx)->data(DrawTestSpec::TARGET_ARRAY, bufferSize, data, attribSpec.usage); m_glArrayPack->getArray(attribNdx)->setupArray(true, attribSpec.offset, attribSpec.componentCount, attribSpec.inputType, attribSpec.outputType, attribSpec.normalize, attribSpec.stride, attribSpec.instanceDivisor, nullAttribValue, isPositionAttr, attribSpec.bgraComponentOrder); m_rrArrayPack->getArray(attribNdx)->setupArray(true, attribSpec.offset, attribSpec.componentCount, attribSpec.inputType, attribSpec.outputType, attribSpec.normalize, attribSpec.stride, attribSpec.instanceDivisor, nullAttribValue, isPositionAttr, attribSpec.bgraComponentOrder); delete [] data; data = NULL; } catch (...) { delete [] data; throw; } } } // Shader program if (updateProgram) { m_glArrayPack->updateProgram(); m_rrArrayPack->updateProgram(); } // Draw try { // indices if (indexed) { const int seed = spec.hash(); const size_t indexElementSize = DrawTestSpec::indexTypeSize(spec.indexType); const size_t indexArraySize = spec.indexPointerOffset + indexElementSize * elementCount; const char* indexArray = RandomArrayGenerator::generateIndices(seed, (int)elementCount, spec.indexType, spec.indexPointerOffset, indexMin, indexMax, indexBase); const char* indexPointerBase = (spec.indexStorage == DrawTestSpec::STORAGE_USER) ? (indexArray) : ((char*)DE_NULL); const char* indexPointer = indexPointerBase + spec.indexPointerOffset; de::UniquePtr glArray (new AttributeArray(spec.indexStorage, *m_glesContext)); de::UniquePtr rrArray (new AttributeArray(spec.indexStorage, *m_refContext)); try { glArray->data(DrawTestSpec::TARGET_ELEMENT_ARRAY, indexArraySize, indexArray, DrawTestSpec::USAGE_STATIC_DRAW); rrArray->data(DrawTestSpec::TARGET_ELEMENT_ARRAY, indexArraySize, indexArray, DrawTestSpec::USAGE_STATIC_DRAW); m_glArrayPack->render(spec.primitive, spec.drawMethod, 0, (int)primitiveElementCount, spec.indexType, indexPointer, spec.indexMin, spec.indexMax, spec.instanceCount, spec.indirectOffset, spec.baseVertex, coordScale, colorScale, glArray.get()); m_rrArrayPack->render(spec.primitive, spec.drawMethod, 0, (int)primitiveElementCount, spec.indexType, indexPointer, spec.indexMin, spec.indexMax, spec.instanceCount, spec.indirectOffset, spec.baseVertex, coordScale, colorScale, rrArray.get()); delete [] indexArray; indexArray = NULL; } catch (...) { delete [] indexArray; throw; } } else { m_glArrayPack->render(spec.primitive, spec.drawMethod, spec.first, (int)primitiveElementCount, DrawTestSpec::INDEXTYPE_LAST, DE_NULL, 0, 0, spec.instanceCount, spec.indirectOffset, 0, coordScale, colorScale, DE_NULL); m_rrArrayPack->render(spec.primitive, spec.drawMethod, spec.first, (int)primitiveElementCount, DrawTestSpec::INDEXTYPE_LAST, DE_NULL, 0, 0, spec.instanceCount, spec.indirectOffset, 0, coordScale, colorScale, DE_NULL); } } catch (glu::Error& err) { // GL Errors are ok if the mode is not properly aligned const DrawTestSpec::CompatibilityTestType ctype = spec.isCompatibilityTest(); m_testCtx.getLog() << TestLog::Message << "Got error: " << err.what() << TestLog::EndMessage; if (ctype == DrawTestSpec::COMPATIBILITY_UNALIGNED_OFFSET) m_result.addResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "Failed to draw with unaligned buffers."); else if (ctype == DrawTestSpec::COMPATIBILITY_UNALIGNED_STRIDE) m_result.addResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "Failed to draw with unaligned stride."); else throw; } } else if (compareStep) { if (!compare(spec.primitive)) { const DrawTestSpec::CompatibilityTestType ctype = spec.isCompatibilityTest(); if (ctype == DrawTestSpec::COMPATIBILITY_UNALIGNED_OFFSET) m_result.addResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "Failed to draw with unaligned buffers."); else if (ctype == DrawTestSpec::COMPATIBILITY_UNALIGNED_STRIDE) m_result.addResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "Failed to draw with unaligned stride."); else m_result.addResult(QP_TEST_RESULT_FAIL, "Image comparison failed."); } } else { DE_ASSERT(false); return STOP; } m_result.setTestContextResult(m_testCtx); m_iteration++; return iterateResult; } static bool isBlack (const tcu::RGBA& c) { // ignore alpha channel return c.getRed() == 0 && c.getGreen() == 0 && c.getBlue() == 0; } static bool isEdgeTripletComponent (int c1, int c2, int c3, int renderTargetDifference) { const int roundingDifference = 2 * renderTargetDifference; // src and dst pixels rounded to different directions const int d1 = c2 - c1; const int d2 = c3 - c2; const int rampDiff = de::abs(d2 - d1); return rampDiff > roundingDifference; } static bool isEdgeTriplet (const tcu::RGBA& c1, const tcu::RGBA& c2, const tcu::RGBA& c3, const tcu::IVec3& renderTargetThreshold) { // black (background color) and non-black is always an edge { const bool b1 = isBlack(c1); const bool b2 = isBlack(c2); const bool b3 = isBlack(c3); // both pixels with coverage and pixels without coverage if ((b1 && b2 && b3) == false && (b1 || b2 || b3) == true) return true; // all black if (b1 && b2 && b3) return false; // all with coverage DE_ASSERT(!b1 && !b2 && !b3); } // Color is always linearly interpolated => component values change nearly linearly // in any constant direction on triangle hull. (df/dx ~= C). // Edge detection (this function) is run against the reference image // => no dithering to worry about return isEdgeTripletComponent(c1.getRed(), c2.getRed(), c3.getRed(), renderTargetThreshold.x()) || isEdgeTripletComponent(c1.getGreen(), c2.getGreen(), c3.getGreen(), renderTargetThreshold.y()) || isEdgeTripletComponent(c1.getBlue(), c2.getBlue(), c3.getBlue(), renderTargetThreshold.z()); } static bool pixelNearEdge (int x, int y, const tcu::Surface& ref, const tcu::IVec3& renderTargetThreshold) { // should not be called for edge pixels DE_ASSERT(x >= 1 && x <= ref.getWidth()-2); DE_ASSERT(y >= 1 && y <= ref.getHeight()-2); // horizontal for (int dy = -1; dy < 2; ++dy) { const tcu::RGBA c1 = ref.getPixel(x-1, y+dy); const tcu::RGBA c2 = ref.getPixel(x, y+dy); const tcu::RGBA c3 = ref.getPixel(x+1, y+dy); if (isEdgeTriplet(c1, c2, c3, renderTargetThreshold)) return true; } // vertical for (int dx = -1; dx < 2; ++dx) { const tcu::RGBA c1 = ref.getPixel(x+dx, y-1); const tcu::RGBA c2 = ref.getPixel(x+dx, y); const tcu::RGBA c3 = ref.getPixel(x+dx, y+1); if (isEdgeTriplet(c1, c2, c3, renderTargetThreshold)) return true; } return false; } static deUint32 getVisualizationGrayscaleColor (const tcu::RGBA& c) { // make triangle coverage and error pixels obvious by converting coverage to grayscale if (isBlack(c)) return 0; else return 50u + (deUint32)(c.getRed() + c.getBlue() + c.getGreen()) / 8u; } static bool pixelNearLineIntersection (int x, int y, const tcu::Surface& target) { // should not be called for edge pixels DE_ASSERT(x >= 1 && x <= target.getWidth()-2); DE_ASSERT(y >= 1 && y <= target.getHeight()-2); int coveredPixels = 0; for (int dy = -1; dy < 2; dy++) for (int dx = -1; dx < 2; dx++) { const bool targetCoverage = !isBlack(target.getPixel(x+dx, y+dy)); if (targetCoverage) { ++coveredPixels; // A single thin line cannot have more than 3 covered pixels in a 3x3 area if (coveredPixels >= 4) return true; } } return false; } static inline bool colorsEqual (const tcu::RGBA& colorA, const tcu::RGBA& colorB, const tcu::IVec3& compareThreshold) { enum { TCU_RGBA_RGB_MASK = tcu::RGBA::RED_MASK | tcu::RGBA::GREEN_MASK | tcu::RGBA::BLUE_MASK }; return tcu::compareThresholdMasked(colorA, colorB, tcu::RGBA(compareThreshold.x(), compareThreshold.y(), compareThreshold.z(), 0), TCU_RGBA_RGB_MASK); } // search 3x3 are for matching color static bool pixelNeighborhoodContainsColor (const tcu::Surface& target, int x, int y, const tcu::RGBA& color, const tcu::IVec3& compareThreshold) { // should not be called for edge pixels DE_ASSERT(x >= 1 && x <= target.getWidth()-2); DE_ASSERT(y >= 1 && y <= target.getHeight()-2); for (int dy = -1; dy < 2; dy++) for (int dx = -1; dx < 2; dx++) { const tcu::RGBA targetCmpPixel = target.getPixel(x+dx, y+dy); if (colorsEqual(color, targetCmpPixel, compareThreshold)) return true; } return false; } // search 3x3 are for matching coverage (coverage == (color != background color)) static bool pixelNeighborhoodContainsCoverage (const tcu::Surface& target, int x, int y, bool coverage) { // should not be called for edge pixels DE_ASSERT(x >= 1 && x <= target.getWidth()-2); DE_ASSERT(y >= 1 && y <= target.getHeight()-2); for (int dy = -1; dy < 2; dy++) for (int dx = -1; dx < 2; dx++) { const bool targetCmpCoverage = !isBlack(target.getPixel(x+dx, y+dy)); if (targetCmpCoverage == coverage) return true; } return false; } static bool edgeRelaxedImageCompare (tcu::TestLog& log, const char* imageSetName, const char* imageSetDesc, const tcu::Surface& reference, const tcu::Surface& result, const tcu::IVec3& compareThreshold, const tcu::IVec3& renderTargetThreshold, int maxAllowedInvalidPixels) { DE_ASSERT(result.getWidth() == reference.getWidth() && result.getHeight() == reference.getHeight()); const tcu::IVec4 green (0, 255, 0, 255); const tcu::IVec4 red (255, 0, 0, 255); const int width = reference.getWidth(); const int height = reference.getHeight(); tcu::TextureLevel errorMask (tcu::TextureFormat(tcu::TextureFormat::RGB, tcu::TextureFormat::UNORM_INT8), width, height); const tcu::PixelBufferAccess errorAccess = errorMask.getAccess(); int numFailingPixels = 0; // clear errormask edges which would otherwise be transparent tcu::clear(tcu::getSubregion(errorAccess, 0, 0, width, 1), green); tcu::clear(tcu::getSubregion(errorAccess, 0, height-1, width, 1), green); tcu::clear(tcu::getSubregion(errorAccess, 0, 0, 1, height), green); tcu::clear(tcu::getSubregion(errorAccess, width-1, 0, 1, height), green); // skip edge pixels since coverage on edge cannot be verified for (int y = 1; y < height - 1; ++y) for (int x = 1; x < width - 1; ++x) { const tcu::RGBA refPixel = reference.getPixel(x, y); const tcu::RGBA screenPixel = result.getPixel(x, y); const bool directMatch = colorsEqual(refPixel, screenPixel, compareThreshold); const bool isOkReferencePixel = directMatch || pixelNeighborhoodContainsColor(result, x, y, refPixel, compareThreshold); // screen image has a matching pixel nearby (~= If something is drawn on reference, it must be drawn to screen too.) const bool isOkScreenPixel = directMatch || pixelNeighborhoodContainsColor(reference, x, y, screenPixel, compareThreshold); // reference image has a matching pixel nearby (~= If something is drawn on screen, it must be drawn to reference too.) if (isOkScreenPixel && isOkReferencePixel) { // pixel valid, write greenish pixels to make the result image easier to read const deUint32 grayscaleValue = getVisualizationGrayscaleColor(screenPixel); errorAccess.setPixel(tcu::UVec4(grayscaleValue, 255, grayscaleValue, 255), x, y); } else if (!pixelNearEdge(x, y, reference, renderTargetThreshold)) { // non-edge pixel values must be within threshold of the reference values errorAccess.setPixel(red, x, y); ++numFailingPixels; } else { // we are on/near an edge, verify only coverage (coverage == not background colored) const bool referenceCoverage = !isBlack(refPixel); const bool screenCoverage = !isBlack(screenPixel); const bool isOkReferenceCoverage = pixelNeighborhoodContainsCoverage(result, x, y, referenceCoverage); // Check reference pixel against screen pixel const bool isOkScreenCoverage = pixelNeighborhoodContainsCoverage(reference, x, y, screenCoverage); // Check screen pixels against reference pixel if (isOkScreenCoverage && isOkReferenceCoverage) { // pixel valid, write greenish pixels to make the result image easier to read const deUint32 grayscaleValue = getVisualizationGrayscaleColor(screenPixel); errorAccess.setPixel(tcu::UVec4(grayscaleValue, 255, grayscaleValue, 255), x, y); } else { // coverage does not match errorAccess.setPixel(red, x, y); ++numFailingPixels; } } } log << TestLog::Message << "Comparing images:\n" << "\tallowed deviation in pixel positions = 1\n" << "\tnumber of allowed invalid pixels = " << maxAllowedInvalidPixels << "\n" << "\tnumber of invalid pixels = " << numFailingPixels << TestLog::EndMessage; if (numFailingPixels > maxAllowedInvalidPixels) { log << TestLog::Message << "Image comparison failed. Color threshold = (" << compareThreshold.x() << ", " << compareThreshold.y() << ", " << compareThreshold.z() << ")" << TestLog::EndMessage << TestLog::ImageSet(imageSetName, imageSetDesc) << TestLog::Image("Result", "Result", result) << TestLog::Image("Reference", "Reference", reference) << TestLog::Image("ErrorMask", "Error mask", errorMask) << TestLog::EndImageSet; return false; } else { log << TestLog::ImageSet(imageSetName, imageSetDesc) << TestLog::Image("Result", "Result", result) << TestLog::EndImageSet; return true; } } static bool intersectionRelaxedLineImageCompare (tcu::TestLog& log, const char* imageSetName, const char* imageSetDesc, const tcu::Surface& reference, const tcu::Surface& result, const tcu::IVec3& compareThreshold, int maxAllowedInvalidPixels) { DE_ASSERT(result.getWidth() == reference.getWidth() && result.getHeight() == reference.getHeight()); const tcu::IVec4 green (0, 255, 0, 255); const tcu::IVec4 red (255, 0, 0, 255); const int width = reference.getWidth(); const int height = reference.getHeight(); tcu::TextureLevel errorMask (tcu::TextureFormat(tcu::TextureFormat::RGB, tcu::TextureFormat::UNORM_INT8), width, height); const tcu::PixelBufferAccess errorAccess = errorMask.getAccess(); int numFailingPixels = 0; // clear errormask edges which would otherwise be transparent tcu::clear(tcu::getSubregion(errorAccess, 0, 0, width, 1), green); tcu::clear(tcu::getSubregion(errorAccess, 0, height-1, width, 1), green); tcu::clear(tcu::getSubregion(errorAccess, 0, 0, 1, height), green); tcu::clear(tcu::getSubregion(errorAccess, width-1, 0, 1, height), green); // skip edge pixels since coverage on edge cannot be verified for (int y = 1; y < height - 1; ++y) for (int x = 1; x < width - 1; ++x) { const tcu::RGBA refPixel = reference.getPixel(x, y); const tcu::RGBA screenPixel = result.getPixel(x, y); const bool directMatch = colorsEqual(refPixel, screenPixel, compareThreshold); const bool isOkScreenPixel = directMatch || pixelNeighborhoodContainsColor(reference, x, y, screenPixel, compareThreshold); // reference image has a matching pixel nearby (~= If something is drawn on screen, it must be drawn to reference too.) const bool isOkReferencePixel = directMatch || pixelNeighborhoodContainsColor(result, x, y, refPixel, compareThreshold); // screen image has a matching pixel nearby (~= If something is drawn on reference, it must be drawn to screen too.) if (isOkScreenPixel && isOkReferencePixel) { // pixel valid, write greenish pixels to make the result image easier to read const deUint32 grayscaleValue = getVisualizationGrayscaleColor(screenPixel); errorAccess.setPixel(tcu::UVec4(grayscaleValue, 255, grayscaleValue, 255), x, y); } else if (!pixelNearLineIntersection(x, y, reference) && !pixelNearLineIntersection(x, y, result)) { // non-intersection pixel values must be within threshold of the reference values errorAccess.setPixel(red, x, y); ++numFailingPixels; } else { // pixel is near a line intersection // we are on/near an edge, verify only coverage (coverage == not background colored) const bool referenceCoverage = !isBlack(refPixel); const bool screenCoverage = !isBlack(screenPixel); const bool isOkScreenCoverage = pixelNeighborhoodContainsCoverage(reference, x, y, screenCoverage); // Check screen pixels against reference pixel const bool isOkReferenceCoverage = pixelNeighborhoodContainsCoverage(result, x, y, referenceCoverage); // Check reference pixel against screen pixel if (isOkScreenCoverage && isOkReferenceCoverage) { // pixel valid, write greenish pixels to make the result image easier to read const deUint32 grayscaleValue = getVisualizationGrayscaleColor(screenPixel); errorAccess.setPixel(tcu::UVec4(grayscaleValue, 255, grayscaleValue, 255), x, y); } else { // coverage does not match errorAccess.setPixel(red, x, y); ++numFailingPixels; } } } log << TestLog::Message << "Comparing images:\n" << "\tallowed deviation in pixel positions = 1\n" << "\tnumber of allowed invalid pixels = " << maxAllowedInvalidPixels << "\n" << "\tnumber of invalid pixels = " << numFailingPixels << TestLog::EndMessage; if (numFailingPixels > maxAllowedInvalidPixels) { log << TestLog::Message << "Image comparison failed. Color threshold = (" << compareThreshold.x() << ", " << compareThreshold.y() << ", " << compareThreshold.z() << ")" << TestLog::EndMessage << TestLog::ImageSet(imageSetName, imageSetDesc) << TestLog::Image("Result", "Result", result) << TestLog::Image("Reference", "Reference", reference) << TestLog::Image("ErrorMask", "Error mask", errorMask) << TestLog::EndImageSet; return false; } else { log << TestLog::ImageSet(imageSetName, imageSetDesc) << TestLog::Image("Result", "Result", result) << TestLog::EndImageSet; return true; } } bool DrawTest::compare (gls::DrawTestSpec::Primitive primitiveType) { const tcu::Surface& ref = m_rrArrayPack->getSurface(); const tcu::Surface& screen = m_glArrayPack->getSurface(); if (m_renderCtx.getRenderTarget().getNumSamples() > 1) { // \todo [mika] Improve compare when using multisampling m_testCtx.getLog() << tcu::TestLog::Message << "Warning: Comparision of result from multisample render targets are not as stricts as without multisampling. Might produce false positives!" << tcu::TestLog::EndMessage; return tcu::fuzzyCompare(m_testCtx.getLog(), "Compare Results", "Compare Results", ref.getAccess(), screen.getAccess(), 0.3f, tcu::COMPARE_LOG_RESULT); } else { const PrimitiveClass primitiveClass = getDrawPrimitiveClass(primitiveType); const int maxAllowedInvalidPixelsWithPoints = 0; //!< points are unlikely to have overlapping fragments const int maxAllowedInvalidPixelsWithLines = 5; //!< line are allowed to have a few bad pixels const int maxAllowedInvalidPixelsWithTriangles = 10; switch (primitiveClass) { case PRIMITIVECLASS_POINT: { // Point are extremely unlikely to have overlapping regions, don't allow any no extra / missing pixels return tcu::intThresholdPositionDeviationErrorThresholdCompare(m_testCtx.getLog(), "CompareResult", "Result of rendering", ref.getAccess(), screen.getAccess(), tcu::UVec4(m_maxDiffRed, m_maxDiffGreen, m_maxDiffBlue, 256), tcu::IVec3(1, 1, 0), //!< 3x3 search kernel true, //!< relax comparison on the image boundary maxAllowedInvalidPixelsWithPoints, //!< error threshold tcu::COMPARE_LOG_RESULT); } case PRIMITIVECLASS_LINE: { // Lines can potentially have a large number of overlapping pixels. Pixel comparison may potentially produce // false negatives in such pixels if for example the pixel in question is overdrawn by another line in the // reference image but not in the resultin image. Relax comparison near line intersection points (areas) and // compare only coverage, not color, in such pixels return intersectionRelaxedLineImageCompare(m_testCtx.getLog(), "CompareResult", "Result of rendering", ref, screen, tcu::IVec3(m_maxDiffRed, m_maxDiffGreen, m_maxDiffBlue), maxAllowedInvalidPixelsWithLines); } case PRIMITIVECLASS_TRIANGLE: { // Triangles are likely to partially or fully overlap. Pixel difference comparison is fragile in pixels // where there could be potential overlapping since the pixels might be covered by one triangle in the // reference image and by the other in the result image. Relax comparsion near primitive edges and // compare only coverage, not color, in such pixels. const tcu::IVec3 renderTargetThreshold = m_renderCtx.getRenderTarget().getPixelFormat().getColorThreshold().toIVec().xyz(); return edgeRelaxedImageCompare(m_testCtx.getLog(), "CompareResult", "Result of rendering", ref, screen, tcu::IVec3(m_maxDiffRed, m_maxDiffGreen, m_maxDiffBlue), renderTargetThreshold, maxAllowedInvalidPixelsWithTriangles); } default: DE_ASSERT(false); return false; } } } float DrawTest::getCoordScale (const DrawTestSpec& spec) const { float maxValue = 1.0f; for (int arrayNdx = 0; arrayNdx < (int)spec.attribs.size(); arrayNdx++) { DrawTestSpec::AttributeSpec attribSpec = spec.attribs[arrayNdx]; const bool isPositionAttr = (arrayNdx == 0) || (attribSpec.additionalPositionAttribute); float attrMaxValue = 0; if (!isPositionAttr) continue; if (attribSpec.inputType == DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10) { if (attribSpec.normalize) attrMaxValue += 1.0f; else attrMaxValue += 1024.0; } else if (attribSpec.inputType == DrawTestSpec::INPUTTYPE_INT_2_10_10_10) { if (attribSpec.normalize) attrMaxValue += 1.0f; else attrMaxValue += 512.0; } else { const float max = GLValue::getMaxValue(attribSpec.inputType).toFloat(); attrMaxValue += (attribSpec.normalize && !inputTypeIsFloatType(attribSpec.inputType)) ? (1.0f) : (max * 1.1f); } if (attribSpec.outputType == DrawTestSpec::OUTPUTTYPE_VEC3 || attribSpec.outputType == DrawTestSpec::OUTPUTTYPE_VEC4 || attribSpec.outputType == DrawTestSpec::OUTPUTTYPE_IVEC3 || attribSpec.outputType == DrawTestSpec::OUTPUTTYPE_IVEC4 || attribSpec.outputType == DrawTestSpec::OUTPUTTYPE_UVEC3 || attribSpec.outputType == DrawTestSpec::OUTPUTTYPE_UVEC4) attrMaxValue *= 2; maxValue += attrMaxValue; } return 1.0f / maxValue; } float DrawTest::getColorScale (const DrawTestSpec& spec) const { float colorScale = 1.0f; for (int arrayNdx = 1; arrayNdx < (int)spec.attribs.size(); arrayNdx++) { DrawTestSpec::AttributeSpec attribSpec = spec.attribs[arrayNdx]; const bool isPositionAttr = (arrayNdx == 0) || (attribSpec.additionalPositionAttribute); if (isPositionAttr) continue; if (attribSpec.inputType == DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10) { if (!attribSpec.normalize) colorScale *= 1.0 / 1024.0; } else if (attribSpec.inputType == DrawTestSpec::INPUTTYPE_INT_2_10_10_10) { if (!attribSpec.normalize) colorScale *= 1.0 / 512.0; } else { const float max = GLValue::getMaxValue(attribSpec.inputType).toFloat(); colorScale *= (attribSpec.normalize && !inputTypeIsFloatType(attribSpec.inputType) ? 1.0f : float(1.0 / double(max))); if (attribSpec.outputType == DrawTestSpec::OUTPUTTYPE_VEC4 || attribSpec.outputType == DrawTestSpec::OUTPUTTYPE_UVEC4 || attribSpec.outputType == DrawTestSpec::OUTPUTTYPE_IVEC4) colorScale *= (attribSpec.normalize && !inputTypeIsFloatType(attribSpec.inputType) ? 1.0f : float(1.0 / double(max))); } } return colorScale; } } // gls } // deqp