| /*------------------------------------------------------------------------- |
| * drawElements Quality Program OpenGL ES 3.1 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 Tessellation Tests. |
| *//*--------------------------------------------------------------------*/ |
| |
| #include "es31fTessellationTests.hpp" |
| #include "glsTextureTestUtil.hpp" |
| #include "glsShaderLibrary.hpp" |
| #include "glsStateQueryUtil.hpp" |
| #include "gluShaderProgram.hpp" |
| #include "gluRenderContext.hpp" |
| #include "gluPixelTransfer.hpp" |
| #include "gluDrawUtil.hpp" |
| #include "gluObjectWrapper.hpp" |
| #include "gluStrUtil.hpp" |
| #include "gluContextInfo.hpp" |
| #include "gluVarType.hpp" |
| #include "gluVarTypeUtil.hpp" |
| #include "gluCallLogWrapper.hpp" |
| #include "tcuTestLog.hpp" |
| #include "tcuRenderTarget.hpp" |
| #include "tcuStringTemplate.hpp" |
| #include "tcuSurface.hpp" |
| #include "tcuTextureUtil.hpp" |
| #include "tcuVectorUtil.hpp" |
| #include "tcuImageIO.hpp" |
| #include "tcuResource.hpp" |
| #include "tcuImageCompare.hpp" |
| #include "deRandom.hpp" |
| #include "deStringUtil.hpp" |
| #include "deSharedPtr.hpp" |
| #include "deUniquePtr.hpp" |
| #include "deString.h" |
| #include "deMath.h" |
| |
| #include "glwEnums.hpp" |
| #include "glwDefs.hpp" |
| #include "glwFunctions.hpp" |
| |
| #include <vector> |
| #include <string> |
| #include <algorithm> |
| #include <functional> |
| #include <set> |
| #include <limits> |
| |
| using glu::ShaderProgram; |
| using glu::RenderContext; |
| using tcu::RenderTarget; |
| using tcu::TestLog; |
| using tcu::Vec2; |
| using tcu::Vec3; |
| using tcu::Vec4; |
| using de::Random; |
| using de::SharedPtr; |
| |
| using std::vector; |
| using std::string; |
| |
| using namespace glw; // For GL types. |
| |
| namespace deqp |
| { |
| |
| using gls::TextureTestUtil::RandomViewport; |
| |
| namespace gles31 |
| { |
| namespace Functional |
| { |
| |
| using namespace gls::StateQueryUtil; |
| |
| enum |
| { |
| MINIMUM_MAX_TESS_GEN_LEVEL = 64 //!< GL-defined minimum for GL_MAX_TESS_GEN_LEVEL. |
| }; |
| |
| static inline bool vec3XLessThan (const Vec3& a, const Vec3& b) { return a.x() < b.x(); } |
| |
| template <typename IterT> |
| static string elemsStr (const IterT& begin, const IterT& end, int wrapLengthParam = 0, int numIndentationSpaces = 0) |
| { |
| const string baseIndentation = string(numIndentationSpaces, ' '); |
| const string deepIndentation = baseIndentation + string(4, ' '); |
| const int wrapLength = wrapLengthParam > 0 ? wrapLengthParam : std::numeric_limits<int>::max(); |
| const int length = (int)std::distance(begin, end); |
| string result; |
| |
| if (length > wrapLength) |
| result += "(amount: " + de::toString(length) + ") "; |
| result += string() + "{" + (length > wrapLength ? "\n"+deepIndentation : " "); |
| |
| { |
| int index = 0; |
| for (IterT it = begin; it != end; ++it) |
| { |
| if (it != begin) |
| result += string() + ", " + (index % wrapLength == 0 ? "\n"+deepIndentation : ""); |
| result += de::toString(*it); |
| index++; |
| } |
| |
| result += length > wrapLength ? "\n"+baseIndentation : " "; |
| } |
| |
| result += "}"; |
| return result; |
| } |
| |
| template <typename ContainerT> |
| static string containerStr (const ContainerT& c, int wrapLengthParam = 0, int numIndentationSpaces = 0) |
| { |
| return elemsStr(c.begin(), c.end(), wrapLengthParam, numIndentationSpaces); |
| } |
| |
| template <typename T, int N> |
| static string arrayStr (const T (&arr)[N], int wrapLengthParam = 0, int numIndentationSpaces = 0) |
| { |
| return elemsStr(DE_ARRAY_BEGIN(arr), DE_ARRAY_END(arr), wrapLengthParam, numIndentationSpaces); |
| } |
| |
| template <typename T, int N> |
| static T arrayMax (const T (&arr)[N]) |
| { |
| return *std::max_element(DE_ARRAY_BEGIN(arr), DE_ARRAY_END(arr)); |
| } |
| |
| template <typename T, typename MembT> |
| static vector<MembT> members (const vector<T>& objs, MembT T::* membP) |
| { |
| vector<MembT> result(objs.size()); |
| for (int i = 0; i < (int)objs.size(); i++) |
| result[i] = objs[i].*membP; |
| return result; |
| } |
| |
| template <typename T, int N> |
| static vector<T> arrayToVector (const T (&arr)[N]) |
| { |
| return vector<T>(DE_ARRAY_BEGIN(arr), DE_ARRAY_END(arr)); |
| } |
| |
| template <typename ContainerT, typename T> |
| static inline bool contains (const ContainerT& c, const T& key) |
| { |
| return c.find(key) != c.end(); |
| } |
| |
| template <int Size> |
| static inline tcu::Vector<bool, Size> singleTrueMask (int index) |
| { |
| DE_ASSERT(de::inBounds(index, 0, Size)); |
| tcu::Vector<bool, Size> result; |
| result[index] = true; |
| return result; |
| } |
| |
| static int intPow (int base, int exp) |
| { |
| DE_ASSERT(exp >= 0); |
| if (exp == 0) |
| return 1; |
| else |
| { |
| const int sub = intPow(base, exp/2); |
| if (exp % 2 == 0) |
| return sub*sub; |
| else |
| return sub*sub*base; |
| } |
| } |
| |
| tcu::Surface getPixels (const glu::RenderContext& rCtx, int x, int y, int width, int height) |
| { |
| tcu::Surface result(width, height); |
| glu::readPixels(rCtx, x, y, result.getAccess()); |
| return result; |
| } |
| |
| tcu::Surface getPixels (const glu::RenderContext& rCtx, const RandomViewport& vp) |
| { |
| return getPixels(rCtx, vp.x, vp.y, vp.width, vp.height); |
| } |
| |
| static inline void checkRenderTargetSize (const RenderTarget& renderTarget, int minSize) |
| { |
| if (renderTarget.getWidth() < minSize || renderTarget.getHeight() < minSize) |
| throw tcu::NotSupportedError("Render target width and height must be at least " + de::toString(minSize)); |
| } |
| |
| tcu::TextureLevel getPNG (const tcu::Archive& archive, const string& filename) |
| { |
| tcu::TextureLevel result; |
| tcu::ImageIO::loadPNG(result, archive, filename.c_str()); |
| return result; |
| } |
| |
| static int numBasicSubobjects (const glu::VarType& type) |
| { |
| if (type.isBasicType()) |
| return 1; |
| else if (type.isArrayType()) |
| return type.getArraySize()*numBasicSubobjects(type.getElementType()); |
| else if (type.isStructType()) |
| { |
| const glu::StructType& structType = *type.getStructPtr(); |
| int result = 0; |
| for (int i = 0; i < structType.getNumMembers(); i++) |
| result += numBasicSubobjects(structType.getMember(i).getType()); |
| return result; |
| } |
| else |
| { |
| DE_ASSERT(false); |
| return -1; |
| } |
| } |
| |
| static inline int numVerticesPerPrimitive (deUint32 primitiveTypeGL) |
| { |
| switch (primitiveTypeGL) |
| { |
| case GL_POINTS: return 1; |
| case GL_TRIANGLES: return 3; |
| case GL_LINES: return 2; |
| default: |
| DE_ASSERT(false); |
| return -1; |
| } |
| } |
| |
| static inline void setViewport (const glw::Functions& gl, const RandomViewport& vp) |
| { |
| gl.viewport(vp.x, vp.y, vp.width, vp.height); |
| } |
| |
| static inline deUint32 getQueryResult (const glw::Functions& gl, deUint32 queryObject) |
| { |
| deUint32 result = (deUint32)-1; |
| gl.getQueryObjectuiv(queryObject, GL_QUERY_RESULT, &result); |
| TCU_CHECK(result != (deUint32)-1); |
| return result; |
| } |
| |
| template <typename T> |
| static void readDataMapped (const glw::Functions& gl, deUint32 bufferTarget, int numElems, T* dst) |
| { |
| const int numBytes = numElems*(int)sizeof(T); |
| const T* const mappedData = (const T*)gl.mapBufferRange(bufferTarget, 0, numBytes, GL_MAP_READ_BIT); |
| GLU_EXPECT_NO_ERROR(gl.getError(), (string() + "glMapBufferRange(" + glu::getBufferTargetName((int)bufferTarget) + ", 0, " + de::toString(numBytes) + ", GL_MAP_READ_BIT)").c_str()); |
| TCU_CHECK(mappedData != DE_NULL); |
| |
| for (int i = 0; i < numElems; i++) |
| dst[i] = mappedData[i]; |
| |
| gl.unmapBuffer(bufferTarget); |
| } |
| |
| template <typename T> |
| static vector<T> readDataMapped (const glw::Functions& gl, deUint32 bufferTarget, int numElems) |
| { |
| vector<T> result(numElems); |
| readDataMapped(gl, bufferTarget, numElems, &result[0]); |
| return result; |
| } |
| |
| namespace |
| { |
| |
| template <typename ArgT, bool res> |
| struct ConstantUnaryPredicate |
| { |
| bool operator() (const ArgT&) const { return res; } |
| }; |
| |
| //! Helper for handling simple, one-varying transform feedbacks. |
| template <typename VaryingT> |
| class TransformFeedbackHandler |
| { |
| public: |
| struct Result |
| { |
| int numPrimitives; |
| vector<VaryingT> varying; |
| |
| Result (void) : numPrimitives(-1) {} |
| Result (int n, const vector<VaryingT>& v) : numPrimitives(n), varying(v) {} |
| }; |
| |
| TransformFeedbackHandler (const glu::RenderContext& renderCtx, int maxNumVertices); |
| |
| Result renderAndGetPrimitives (deUint32 programGL, deUint32 tfPrimTypeGL, int numBindings, const glu::VertexArrayBinding* bindings, int numVertices) const; |
| |
| private: |
| const glu::RenderContext& m_renderCtx; |
| const glu::TransformFeedback m_tf; |
| const glu::Buffer m_tfBuffer; |
| const glu::Query m_tfPrimQuery; |
| }; |
| |
| template <typename AttribType> |
| TransformFeedbackHandler<AttribType>::TransformFeedbackHandler (const glu::RenderContext& renderCtx, int maxNumVertices) |
| : m_renderCtx (renderCtx) |
| , m_tf (renderCtx) |
| , m_tfBuffer (renderCtx) |
| , m_tfPrimQuery (renderCtx) |
| { |
| const glw::Functions& gl = m_renderCtx.getFunctions(); |
| // \note Room for 1 extra triangle, to detect if GL returns too many primitives. |
| const int bufferSize = (maxNumVertices + 3) * (int)sizeof(AttribType); |
| |
| gl.bindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, *m_tfBuffer); |
| gl.bufferData(GL_TRANSFORM_FEEDBACK_BUFFER, bufferSize, DE_NULL, GL_DYNAMIC_READ); |
| } |
| |
| template <typename AttribType> |
| typename TransformFeedbackHandler<AttribType>::Result TransformFeedbackHandler<AttribType>::renderAndGetPrimitives (deUint32 programGL, deUint32 tfPrimTypeGL, int numBindings, const glu::VertexArrayBinding* bindings, int numVertices) const |
| { |
| DE_ASSERT(tfPrimTypeGL == GL_POINTS || tfPrimTypeGL == GL_LINES || tfPrimTypeGL == GL_TRIANGLES); |
| |
| const glw::Functions& gl = m_renderCtx.getFunctions(); |
| |
| gl.bindTransformFeedback(GL_TRANSFORM_FEEDBACK, *m_tf); |
| gl.bindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, *m_tfBuffer); |
| gl.bindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, *m_tfBuffer); |
| |
| gl.beginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, *m_tfPrimQuery); |
| gl.beginTransformFeedback(tfPrimTypeGL); |
| |
| glu::draw(m_renderCtx, programGL, numBindings, bindings, glu::pr::Patches(numVertices)); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed"); |
| |
| gl.endTransformFeedback(); |
| gl.endQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN); |
| |
| { |
| const int numPrimsWritten = (int)getQueryResult(gl, *m_tfPrimQuery); |
| return Result(numPrimsWritten, readDataMapped<AttribType>(gl, GL_TRANSFORM_FEEDBACK_BUFFER, numPrimsWritten * numVerticesPerPrimitive(tfPrimTypeGL))); |
| } |
| } |
| |
| template <typename T> |
| class SizeLessThan |
| { |
| public: |
| bool operator() (const T& a, const T& b) const { return a.size() < b.size(); } |
| }; |
| |
| //! Predicate functor for comparing structs by their members. |
| template <typename Pred, typename T, typename MembT> |
| class MemberPred |
| { |
| public: |
| MemberPred (MembT T::* membP) : m_membP(membP), m_pred(Pred()) {} |
| bool operator() (const T& a, const T& b) const { return m_pred(a.*m_membP, b.*m_membP); } |
| |
| private: |
| MembT T::* m_membP; |
| Pred m_pred; |
| }; |
| |
| //! Convenience wrapper for MemberPred, because class template arguments aren't deduced based on constructor arguments. |
| template <template <typename> class Pred, typename T, typename MembT> |
| static MemberPred<Pred<MembT>, T, MembT> memberPred (MembT T::* membP) { return MemberPred<Pred<MembT>, T, MembT>(membP); } |
| |
| template <typename SeqT, int Size, typename Pred> |
| class LexCompare |
| { |
| public: |
| LexCompare (void) : m_pred(Pred()) {} |
| |
| bool operator() (const SeqT& a, const SeqT& b) const |
| { |
| for (int i = 0; i < Size; i++) |
| { |
| if (m_pred(a[i], b[i])) |
| return true; |
| if (m_pred(b[i], a[i])) |
| return false; |
| } |
| return false; |
| } |
| |
| private: |
| Pred m_pred; |
| }; |
| |
| template <int Size> |
| class VecLexLessThan : public LexCompare<tcu::Vector<float, Size>, Size, std::less<float> > |
| { |
| }; |
| |
| enum TessPrimitiveType |
| { |
| TESSPRIMITIVETYPE_TRIANGLES = 0, |
| TESSPRIMITIVETYPE_QUADS, |
| TESSPRIMITIVETYPE_ISOLINES, |
| |
| TESSPRIMITIVETYPE_LAST |
| }; |
| |
| enum SpacingMode |
| { |
| SPACINGMODE_EQUAL, |
| SPACINGMODE_FRACTIONAL_ODD, |
| SPACINGMODE_FRACTIONAL_EVEN, |
| |
| SPACINGMODE_LAST |
| }; |
| |
| enum Winding |
| { |
| WINDING_CCW = 0, |
| WINDING_CW, |
| |
| WINDING_LAST |
| }; |
| |
| static inline const char* getTessPrimitiveTypeShaderName (TessPrimitiveType type) |
| { |
| switch (type) |
| { |
| case TESSPRIMITIVETYPE_TRIANGLES: return "triangles"; |
| case TESSPRIMITIVETYPE_QUADS: return "quads"; |
| case TESSPRIMITIVETYPE_ISOLINES: return "isolines"; |
| default: |
| DE_ASSERT(false); |
| return DE_NULL; |
| } |
| } |
| |
| static inline const char* getSpacingModeShaderName (SpacingMode mode) |
| { |
| switch (mode) |
| { |
| case SPACINGMODE_EQUAL: return "equal_spacing"; |
| case SPACINGMODE_FRACTIONAL_ODD: return "fractional_odd_spacing"; |
| case SPACINGMODE_FRACTIONAL_EVEN: return "fractional_even_spacing"; |
| default: |
| DE_ASSERT(false); |
| return DE_NULL; |
| } |
| } |
| |
| static inline const char* getWindingShaderName (Winding winding) |
| { |
| switch (winding) |
| { |
| case WINDING_CCW: return "ccw"; |
| case WINDING_CW: return "cw"; |
| default: |
| DE_ASSERT(false); |
| return DE_NULL; |
| } |
| } |
| |
| static inline string getTessellationEvaluationInLayoutString (TessPrimitiveType primType, SpacingMode spacing, Winding winding, bool usePointMode=false) |
| { |
| return string() + "layout (" + getTessPrimitiveTypeShaderName(primType) |
| + ", " + getSpacingModeShaderName(spacing) |
| + ", " + getWindingShaderName(winding) |
| + (usePointMode ? ", point_mode" : "") |
| + ") in;\n"; |
| } |
| |
| static inline string getTessellationEvaluationInLayoutString (TessPrimitiveType primType, SpacingMode spacing, bool usePointMode=false) |
| { |
| return string() + "layout (" + getTessPrimitiveTypeShaderName(primType) |
| + ", " + getSpacingModeShaderName(spacing) |
| + (usePointMode ? ", point_mode" : "") |
| + ") in;\n"; |
| } |
| |
| static inline string getTessellationEvaluationInLayoutString (TessPrimitiveType primType, Winding winding, bool usePointMode=false) |
| { |
| return string() + "layout (" + getTessPrimitiveTypeShaderName(primType) |
| + ", " + getWindingShaderName(winding) |
| + (usePointMode ? ", point_mode" : "") |
| + ") in;\n"; |
| } |
| |
| static inline string getTessellationEvaluationInLayoutString (TessPrimitiveType primType, bool usePointMode=false) |
| { |
| return string() + "layout (" + getTessPrimitiveTypeShaderName(primType) |
| + (usePointMode ? ", point_mode" : "") |
| + ") in;\n"; |
| } |
| |
| static inline deUint32 outputPrimitiveTypeGL (TessPrimitiveType tessPrimType, bool usePointMode) |
| { |
| if (usePointMode) |
| return GL_POINTS; |
| else |
| { |
| switch (tessPrimType) |
| { |
| case TESSPRIMITIVETYPE_TRIANGLES: return GL_TRIANGLES; |
| case TESSPRIMITIVETYPE_QUADS: return GL_TRIANGLES; |
| case TESSPRIMITIVETYPE_ISOLINES: return GL_LINES; |
| default: |
| DE_ASSERT(false); |
| return (deUint32)-1; |
| } |
| } |
| } |
| |
| static inline int numInnerTessellationLevels (TessPrimitiveType primType) |
| { |
| switch (primType) |
| { |
| case TESSPRIMITIVETYPE_TRIANGLES: return 1; |
| case TESSPRIMITIVETYPE_QUADS: return 2; |
| case TESSPRIMITIVETYPE_ISOLINES: return 0; |
| default: DE_ASSERT(false); return -1; |
| } |
| } |
| |
| static inline int numOuterTessellationLevels (TessPrimitiveType primType) |
| { |
| switch (primType) |
| { |
| case TESSPRIMITIVETYPE_TRIANGLES: return 3; |
| case TESSPRIMITIVETYPE_QUADS: return 4; |
| case TESSPRIMITIVETYPE_ISOLINES: return 2; |
| default: DE_ASSERT(false); return -1; |
| } |
| } |
| |
| static string tessellationLevelsString (const float* inner, int numInner, const float* outer, int numOuter) |
| { |
| DE_ASSERT(numInner >= 0 && numOuter >= 0); |
| return "inner: " + elemsStr(inner, inner+numInner) + ", outer: " + elemsStr(outer, outer+numOuter); |
| } |
| |
| static string tessellationLevelsString (const float* inner, const float* outer, TessPrimitiveType primType) |
| { |
| return tessellationLevelsString(inner, numInnerTessellationLevels(primType), outer, numOuterTessellationLevels(primType)); |
| } |
| |
| static string tessellationLevelsString (const float* inner, const float* outer) |
| { |
| return tessellationLevelsString(inner, 2, outer, 4); |
| } |
| |
| static inline float getClampedTessLevel (SpacingMode mode, float tessLevel) |
| { |
| switch (mode) |
| { |
| case SPACINGMODE_EQUAL: return de::max(1.0f, tessLevel); |
| case SPACINGMODE_FRACTIONAL_ODD: return de::max(1.0f, tessLevel); |
| case SPACINGMODE_FRACTIONAL_EVEN: return de::max(2.0f, tessLevel); |
| default: |
| DE_ASSERT(false); |
| return -1.0f; |
| } |
| } |
| |
| static inline int getRoundedTessLevel (SpacingMode mode, float clampedTessLevel) |
| { |
| int result = (int)deFloatCeil(clampedTessLevel); |
| |
| switch (mode) |
| { |
| case SPACINGMODE_EQUAL: break; |
| case SPACINGMODE_FRACTIONAL_ODD: result += 1 - result % 2; break; |
| case SPACINGMODE_FRACTIONAL_EVEN: result += result % 2; break; |
| default: |
| DE_ASSERT(false); |
| } |
| DE_ASSERT(de::inRange<int>(result, 1, MINIMUM_MAX_TESS_GEN_LEVEL)); |
| |
| return result; |
| } |
| |
| static int getClampedRoundedTessLevel (SpacingMode mode, float tessLevel) |
| { |
| return getRoundedTessLevel(mode, getClampedTessLevel(mode, tessLevel)); |
| } |
| |
| //! A description of an outer edge of a triangle, quad or isolines. |
| //! An outer edge can be described by the index of a u/v/w coordinate |
| //! and the coordinate's value along that edge. |
| struct OuterEdgeDescription |
| { |
| int constantCoordinateIndex; |
| float constantCoordinateValueChoices[2]; |
| int numConstantCoordinateValueChoices; |
| |
| OuterEdgeDescription (int i, float c0) : constantCoordinateIndex(i), numConstantCoordinateValueChoices(1) { constantCoordinateValueChoices[0] = c0; } |
| OuterEdgeDescription (int i, float c0, float c1) : constantCoordinateIndex(i), numConstantCoordinateValueChoices(2) { constantCoordinateValueChoices[0] = c0; constantCoordinateValueChoices[1] = c1; } |
| |
| string description (void) const |
| { |
| static const char* const coordinateNames[] = { "u", "v", "w" }; |
| string result; |
| for (int i = 0; i < numConstantCoordinateValueChoices; i++) |
| result += string() + (i > 0 ? " or " : "") + coordinateNames[constantCoordinateIndex] + "=" + de::toString(constantCoordinateValueChoices[i]); |
| return result; |
| } |
| |
| bool contains (const Vec3& v) const |
| { |
| for (int i = 0; i < numConstantCoordinateValueChoices; i++) |
| if (v[constantCoordinateIndex] == constantCoordinateValueChoices[i]) |
| return true; |
| return false; |
| } |
| }; |
| |
| static vector<OuterEdgeDescription> outerEdgeDescriptions (TessPrimitiveType primType) |
| { |
| static const OuterEdgeDescription triangleOuterEdgeDescriptions[3] = |
| { |
| OuterEdgeDescription(0, 0.0f), |
| OuterEdgeDescription(1, 0.0f), |
| OuterEdgeDescription(2, 0.0f) |
| }; |
| |
| static const OuterEdgeDescription quadOuterEdgeDescriptions[4] = |
| { |
| OuterEdgeDescription(0, 0.0f), |
| OuterEdgeDescription(1, 0.0f), |
| OuterEdgeDescription(0, 1.0f), |
| OuterEdgeDescription(1, 1.0f) |
| }; |
| |
| static const OuterEdgeDescription isolinesOuterEdgeDescriptions[1] = |
| { |
| OuterEdgeDescription(0, 0.0f, 1.0f), |
| }; |
| |
| switch (primType) |
| { |
| case TESSPRIMITIVETYPE_TRIANGLES: return arrayToVector(triangleOuterEdgeDescriptions); |
| case TESSPRIMITIVETYPE_QUADS: return arrayToVector(quadOuterEdgeDescriptions); |
| case TESSPRIMITIVETYPE_ISOLINES: return arrayToVector(isolinesOuterEdgeDescriptions); |
| default: DE_ASSERT(false); return vector<OuterEdgeDescription>(); |
| } |
| } |
| |
| // \note The tessellation coordinates generated by this function could break some of the rules given in the spec (e.g. it may not exactly hold that u+v+w == 1.0f, or [uvw] + (1.0f-[uvw]) == 1.0f). |
| static vector<Vec3> generateReferenceTriangleTessCoords (SpacingMode spacingMode, int inner, int outer0, int outer1, int outer2) |
| { |
| vector<Vec3> tessCoords; |
| |
| if (inner == 1) |
| { |
| if (outer0 == 1 && outer1 == 1 && outer2 == 1) |
| { |
| tessCoords.push_back(Vec3(1.0f, 0.0f, 0.0f)); |
| tessCoords.push_back(Vec3(0.0f, 1.0f, 0.0f)); |
| tessCoords.push_back(Vec3(0.0f, 0.0f, 1.0f)); |
| return tessCoords; |
| } |
| else |
| return generateReferenceTriangleTessCoords(spacingMode, spacingMode == SPACINGMODE_FRACTIONAL_ODD ? 3 : 2, |
| outer0, outer1, outer2); |
| } |
| else |
| { |
| for (int i = 0; i < outer0; i++) { const float v = (float)i / (float)outer0; tessCoords.push_back(Vec3( 0.0f, v, 1.0f - v)); } |
| for (int i = 0; i < outer1; i++) { const float v = (float)i / (float)outer1; tessCoords.push_back(Vec3(1.0f - v, 0.0f, v)); } |
| for (int i = 0; i < outer2; i++) { const float v = (float)i / (float)outer2; tessCoords.push_back(Vec3( v, 1.0f - v, 0.0f)); } |
| |
| const int numInnerTriangles = inner/2; |
| for (int innerTriangleNdx = 0; innerTriangleNdx < numInnerTriangles; innerTriangleNdx++) |
| { |
| const int curInnerTriangleLevel = inner - 2*(innerTriangleNdx+1); |
| |
| if (curInnerTriangleLevel == 0) |
| tessCoords.push_back(Vec3(1.0f/3.0f)); |
| else |
| { |
| const float minUVW = (float)(2 * (innerTriangleNdx + 1)) / (float)(3 * inner); |
| const float maxUVW = 1.0f - 2.0f*minUVW; |
| const Vec3 corners[3] = |
| { |
| Vec3(maxUVW, minUVW, minUVW), |
| Vec3(minUVW, maxUVW, minUVW), |
| Vec3(minUVW, minUVW, maxUVW) |
| }; |
| |
| for (int i = 0; i < curInnerTriangleLevel; i++) |
| { |
| const float f = (float)i / (float)curInnerTriangleLevel; |
| for (int j = 0; j < 3; j++) |
| tessCoords.push_back((1.0f - f)*corners[j] + f*corners[(j+1)%3]); |
| } |
| } |
| } |
| |
| return tessCoords; |
| } |
| } |
| |
| static int referenceTriangleNonPointModePrimitiveCount (SpacingMode spacingMode, int inner, int outer0, int outer1, int outer2) |
| { |
| if (inner == 1) |
| { |
| if (outer0 == 1 && outer1 == 1 && outer2 == 1) |
| return 1; |
| else |
| return referenceTriangleNonPointModePrimitiveCount(spacingMode, spacingMode == SPACINGMODE_FRACTIONAL_ODD ? 3 : 2, |
| outer0, outer1, outer2); |
| } |
| else |
| { |
| int result = outer0 + outer1 + outer2; |
| |
| const int numInnerTriangles = inner/2; |
| for (int innerTriangleNdx = 0; innerTriangleNdx < numInnerTriangles; innerTriangleNdx++) |
| { |
| const int curInnerTriangleLevel = inner - 2*(innerTriangleNdx+1); |
| |
| if (curInnerTriangleLevel == 1) |
| result += 4; |
| else |
| result += 2*3*curInnerTriangleLevel; |
| } |
| |
| return result; |
| } |
| } |
| |
| // \note The tessellation coordinates generated by this function could break some of the rules given in the spec (e.g. it may not exactly hold that [uv] + (1.0f-[uv]) == 1.0f). |
| static vector<Vec3> generateReferenceQuadTessCoords (SpacingMode spacingMode, int inner0, int inner1, int outer0, int outer1, int outer2, int outer3) |
| { |
| vector<Vec3> tessCoords; |
| |
| if (inner0 == 1 || inner1 == 1) |
| { |
| if (inner0 == 1 && inner1 == 1 && outer0 == 1 && outer1 == 1 && outer2 == 1 && outer3 == 1) |
| { |
| tessCoords.push_back(Vec3(0.0f, 0.0f, 0.0f)); |
| tessCoords.push_back(Vec3(1.0f, 0.0f, 0.0f)); |
| tessCoords.push_back(Vec3(0.0f, 1.0f, 0.0f)); |
| tessCoords.push_back(Vec3(1.0f, 1.0f, 0.0f)); |
| return tessCoords; |
| } |
| else |
| return generateReferenceQuadTessCoords(spacingMode, inner0 > 1 ? inner0 : spacingMode == SPACINGMODE_FRACTIONAL_ODD ? 3 : 2, |
| inner1 > 1 ? inner1 : spacingMode == SPACINGMODE_FRACTIONAL_ODD ? 3 : 2, |
| outer0, outer1, outer2, outer3); |
| } |
| else |
| { |
| for (int i = 0; i < outer0; i++) { const float v = (float)i / (float)outer0; tessCoords.push_back(Vec3(0.0f, v, 0.0f)); } |
| for (int i = 0; i < outer1; i++) { const float v = (float)i / (float)outer1; tessCoords.push_back(Vec3(1.0f-v, 0.0f, 0.0f)); } |
| for (int i = 0; i < outer2; i++) { const float v = (float)i / (float)outer2; tessCoords.push_back(Vec3(1.0f, 1.0f-v, 0.0f)); } |
| for (int i = 0; i < outer3; i++) { const float v = (float)i / (float)outer3; tessCoords.push_back(Vec3(v, 1.0f, 0.0f)); } |
| |
| for (int innerVtxY = 0; innerVtxY < inner1-1; innerVtxY++) |
| for (int innerVtxX = 0; innerVtxX < inner0-1; innerVtxX++) |
| tessCoords.push_back(Vec3((float)(innerVtxX + 1) / (float)inner0, |
| (float)(innerVtxY + 1) / (float)inner1, |
| 0.0f)); |
| |
| return tessCoords; |
| } |
| } |
| |
| static int referenceQuadNonPointModePrimitiveCount (SpacingMode spacingMode, int inner0, int inner1, int outer0, int outer1, int outer2, int outer3) |
| { |
| vector<Vec3> tessCoords; |
| |
| if (inner0 == 1 || inner1 == 1) |
| { |
| if (inner0 == 1 && inner1 == 1 && outer0 == 1 && outer1 == 1 && outer2 == 1 && outer3 == 1) |
| return 2; |
| else |
| return referenceQuadNonPointModePrimitiveCount(spacingMode, inner0 > 1 ? inner0 : spacingMode == SPACINGMODE_FRACTIONAL_ODD ? 3 : 2, |
| inner1 > 1 ? inner1 : spacingMode == SPACINGMODE_FRACTIONAL_ODD ? 3 : 2, |
| outer0, outer1, outer2, outer3); |
| } |
| else |
| return 2*(inner0-2)*(inner1-2) + 2*(inner0-2) + 2*(inner1-2) + outer0+outer1+outer2+outer3; |
| } |
| |
| // \note The tessellation coordinates generated by this function could break some of the rules given in the spec (e.g. it may not exactly hold that [uv] + (1.0f-[uv]) == 1.0f). |
| static vector<Vec3> generateReferenceIsolineTessCoords (int outer0, int outer1) |
| { |
| vector<Vec3> tessCoords; |
| |
| for (int y = 0; y < outer0; y++) |
| for (int x = 0; x < outer1+1; x++) |
| tessCoords.push_back(Vec3((float)x / (float)outer1, |
| (float)y / (float)outer0, |
| 0.0f)); |
| |
| return tessCoords; |
| } |
| |
| static int referenceIsolineNonPointModePrimitiveCount (int outer0, int outer1) |
| { |
| return outer0*outer1; |
| } |
| |
| static void getClampedRoundedTriangleTessLevels (SpacingMode spacingMode, const float* innerSrc, const float* outerSrc, int* innerDst, int *outerDst) |
| { |
| innerDst[0] = getClampedRoundedTessLevel(spacingMode, innerSrc[0]); |
| for (int i = 0; i < 3; i++) |
| outerDst[i] = getClampedRoundedTessLevel(spacingMode, outerSrc[i]); |
| } |
| |
| static void getClampedRoundedQuadTessLevels (SpacingMode spacingMode, const float* innerSrc, const float* outerSrc, int* innerDst, int *outerDst) |
| { |
| for (int i = 0; i < 2; i++) |
| innerDst[i] = getClampedRoundedTessLevel(spacingMode, innerSrc[i]); |
| for (int i = 0; i < 4; i++) |
| outerDst[i] = getClampedRoundedTessLevel(spacingMode, outerSrc[i]); |
| } |
| |
| static void getClampedRoundedIsolineTessLevels (SpacingMode spacingMode, const float* outerSrc, int* outerDst) |
| { |
| outerDst[0] = getClampedRoundedTessLevel(SPACINGMODE_EQUAL, outerSrc[0]); |
| outerDst[1] = getClampedRoundedTessLevel(spacingMode, outerSrc[1]); |
| } |
| |
| static inline bool isPatchDiscarded (TessPrimitiveType primitiveType, const float* outerLevels) |
| { |
| const int numOuterLevels = numOuterTessellationLevels(primitiveType); |
| for (int i = 0; i < numOuterLevels; i++) |
| if (outerLevels[i] <= 0.0f) |
| return true; |
| return false; |
| } |
| |
| static vector<Vec3> generateReferenceTessCoords (TessPrimitiveType primitiveType, SpacingMode spacingMode, const float* innerLevels, const float* outerLevels) |
| { |
| if (isPatchDiscarded(primitiveType, outerLevels)) |
| return vector<Vec3>(); |
| |
| switch (primitiveType) |
| { |
| case TESSPRIMITIVETYPE_TRIANGLES: |
| { |
| int inner; |
| int outer[3]; |
| getClampedRoundedTriangleTessLevels(spacingMode, innerLevels, outerLevels, &inner, &outer[0]); |
| |
| if (spacingMode != SPACINGMODE_EQUAL) |
| { |
| // \note For fractional spacing modes, exact results are implementation-defined except in special cases. |
| DE_ASSERT(de::abs(innerLevels[0] - (float)inner) < 0.001f); |
| for (int i = 0; i < 3; i++) |
| DE_ASSERT(de::abs(outerLevels[i] - (float)outer[i]) < 0.001f); |
| DE_ASSERT(inner > 1 || (outer[0] == 1 && outer[1] == 1 && outer[2] == 1)); |
| } |
| |
| return generateReferenceTriangleTessCoords(spacingMode, inner, outer[0], outer[1], outer[2]); |
| } |
| |
| case TESSPRIMITIVETYPE_QUADS: |
| { |
| int inner[2]; |
| int outer[4]; |
| getClampedRoundedQuadTessLevels(spacingMode, innerLevels, outerLevels, &inner[0], &outer[0]); |
| |
| if (spacingMode != SPACINGMODE_EQUAL) |
| { |
| // \note For fractional spacing modes, exact results are implementation-defined except in special cases. |
| for (int i = 0; i < 2; i++) |
| DE_ASSERT(de::abs(innerLevels[i] - (float)inner[i]) < 0.001f); |
| for (int i = 0; i < 4; i++) |
| DE_ASSERT(de::abs(outerLevels[i] - (float)outer[i]) < 0.001f); |
| |
| DE_ASSERT((inner[0] > 1 && inner[1] > 1) || (inner[0] == 1 && inner[1] == 1 && outer[0] == 1 && outer[1] == 1 && outer[2] == 1 && outer[3] == 1)); |
| } |
| |
| return generateReferenceQuadTessCoords(spacingMode, inner[0], inner[1], outer[0], outer[1], outer[2], outer[3]); |
| } |
| |
| case TESSPRIMITIVETYPE_ISOLINES: |
| { |
| int outer[2]; |
| getClampedRoundedIsolineTessLevels(spacingMode, &outerLevels[0], &outer[0]); |
| |
| if (spacingMode != SPACINGMODE_EQUAL) |
| { |
| // \note For fractional spacing modes, exact results are implementation-defined except in special cases. |
| DE_ASSERT(de::abs(outerLevels[1] - (float)outer[1]) < 0.001f); |
| } |
| |
| return generateReferenceIsolineTessCoords(outer[0], outer[1]); |
| } |
| |
| default: |
| DE_ASSERT(false); |
| return vector<Vec3>(); |
| } |
| } |
| |
| static int referencePointModePrimitiveCount (TessPrimitiveType primitiveType, SpacingMode spacingMode, const float* innerLevels, const float* outerLevels) |
| { |
| if (isPatchDiscarded(primitiveType, outerLevels)) |
| return 0; |
| |
| switch (primitiveType) |
| { |
| case TESSPRIMITIVETYPE_TRIANGLES: |
| { |
| int inner; |
| int outer[3]; |
| getClampedRoundedTriangleTessLevels(spacingMode, innerLevels, outerLevels, &inner, &outer[0]); |
| return (int)generateReferenceTriangleTessCoords(spacingMode, inner, outer[0], outer[1], outer[2]).size(); |
| } |
| |
| case TESSPRIMITIVETYPE_QUADS: |
| { |
| int inner[2]; |
| int outer[4]; |
| getClampedRoundedQuadTessLevels(spacingMode, innerLevels, outerLevels, &inner[0], &outer[0]); |
| return (int)generateReferenceQuadTessCoords(spacingMode, inner[0], inner[1], outer[0], outer[1], outer[2], outer[3]).size(); |
| } |
| |
| case TESSPRIMITIVETYPE_ISOLINES: |
| { |
| int outer[2]; |
| getClampedRoundedIsolineTessLevels(spacingMode, &outerLevels[0], &outer[0]); |
| return (int)generateReferenceIsolineTessCoords(outer[0], outer[1]).size(); |
| } |
| |
| default: |
| DE_ASSERT(false); |
| return -1; |
| } |
| } |
| |
| static int referenceNonPointModePrimitiveCount (TessPrimitiveType primitiveType, SpacingMode spacingMode, const float* innerLevels, const float* outerLevels) |
| { |
| if (isPatchDiscarded(primitiveType, outerLevels)) |
| return 0; |
| |
| switch (primitiveType) |
| { |
| case TESSPRIMITIVETYPE_TRIANGLES: |
| { |
| int inner; |
| int outer[3]; |
| getClampedRoundedTriangleTessLevels(spacingMode, innerLevels, outerLevels, &inner, &outer[0]); |
| return referenceTriangleNonPointModePrimitiveCount(spacingMode, inner, outer[0], outer[1], outer[2]); |
| } |
| |
| case TESSPRIMITIVETYPE_QUADS: |
| { |
| int inner[2]; |
| int outer[4]; |
| getClampedRoundedQuadTessLevels(spacingMode, innerLevels, outerLevels, &inner[0], &outer[0]); |
| return referenceQuadNonPointModePrimitiveCount(spacingMode, inner[0], inner[1], outer[0], outer[1], outer[2], outer[3]); |
| } |
| |
| case TESSPRIMITIVETYPE_ISOLINES: |
| { |
| int outer[2]; |
| getClampedRoundedIsolineTessLevels(spacingMode, &outerLevels[0], &outer[0]); |
| return referenceIsolineNonPointModePrimitiveCount(outer[0], outer[1]); |
| } |
| |
| default: |
| DE_ASSERT(false); |
| return -1; |
| } |
| } |
| |
| static int referencePrimitiveCount (TessPrimitiveType primitiveType, SpacingMode spacingMode, bool usePointMode, const float* innerLevels, const float* outerLevels) |
| { |
| return usePointMode ? referencePointModePrimitiveCount (primitiveType, spacingMode, innerLevels, outerLevels) |
| : referenceNonPointModePrimitiveCount (primitiveType, spacingMode, innerLevels, outerLevels); |
| } |
| |
| static int referenceVertexCount (TessPrimitiveType primitiveType, SpacingMode spacingMode, bool usePointMode, const float* innerLevels, const float* outerLevels) |
| { |
| return referencePrimitiveCount(primitiveType, spacingMode, usePointMode, innerLevels, outerLevels) |
| * numVerticesPerPrimitive(outputPrimitiveTypeGL(primitiveType, usePointMode)); |
| } |
| |
| //! Helper for calling referenceVertexCount multiple times with different tessellation levels. |
| //! \note Levels contains inner and outer levels, per patch, in order IIOOOO. The full 6 levels must always be present, irrespective of primitiveType. |
| static int multiplePatchReferenceVertexCount (TessPrimitiveType primitiveType, SpacingMode spacingMode, bool usePointMode, const float* levels, int numPatches) |
| { |
| int result = 0; |
| for (int patchNdx = 0; patchNdx < numPatches; patchNdx++) |
| result += referenceVertexCount(primitiveType, spacingMode, usePointMode, &levels[6*patchNdx + 0], &levels[6*patchNdx + 2]); |
| return result; |
| } |
| |
| vector<float> generateRandomPatchTessLevels (int numPatches, int constantOuterLevelIndex, float constantOuterLevel, de::Random& rnd) |
| { |
| vector<float> tessLevels(numPatches*6); |
| |
| for (int patchNdx = 0; patchNdx < numPatches; patchNdx++) |
| { |
| float* const inner = &tessLevels[patchNdx*6 + 0]; |
| float* const outer = &tessLevels[patchNdx*6 + 2]; |
| |
| for (int j = 0; j < 2; j++) |
| inner[j] = rnd.getFloat(1.0f, 62.0f); |
| for (int j = 0; j < 4; j++) |
| outer[j] = j == constantOuterLevelIndex ? constantOuterLevel : rnd.getFloat(1.0f, 62.0f); |
| } |
| |
| return tessLevels; |
| } |
| |
| static inline void drawPoint (tcu::Surface& dst, int centerX, int centerY, const tcu::RGBA& color, int size) |
| { |
| const int width = dst.getWidth(); |
| const int height = dst.getHeight(); |
| DE_ASSERT(de::inBounds(centerX, 0, width) && de::inBounds(centerY, 0, height)); |
| DE_ASSERT(size > 0); |
| |
| for (int yOff = -((size-1)/2); yOff <= size/2; yOff++) |
| for (int xOff = -((size-1)/2); xOff <= size/2; xOff++) |
| { |
| const int pixX = centerX + xOff; |
| const int pixY = centerY + yOff; |
| if (de::inBounds(pixX, 0, width) && de::inBounds(pixY, 0, height)) |
| dst.setPixel(pixX, pixY, color); |
| } |
| } |
| |
| static void drawTessCoordPoint (tcu::Surface& dst, TessPrimitiveType primitiveType, const Vec3& pt, const tcu::RGBA& color, int size) |
| { |
| // \note These coordinates should match the description in the log message in TessCoordCase::iterate. |
| |
| static const Vec2 triangleCorners[3] = |
| { |
| Vec2(0.95f, 0.95f), |
| Vec2(0.5f, 0.95f - 0.9f*deFloatSqrt(3.0f/4.0f)), |
| Vec2(0.05f, 0.95f) |
| }; |
| |
| static const float quadIsolineLDRU[4] = |
| { |
| 0.1f, 0.9f, 0.9f, 0.1f |
| }; |
| |
| const Vec2 dstPos = primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? pt.x()*triangleCorners[0] |
| + pt.y()*triangleCorners[1] |
| + pt.z()*triangleCorners[2] |
| |
| : primitiveType == TESSPRIMITIVETYPE_QUADS || |
| primitiveType == TESSPRIMITIVETYPE_ISOLINES ? Vec2((1.0f - pt.x())*quadIsolineLDRU[0] + pt.x()*quadIsolineLDRU[2], |
| (1.0f - pt.y())*quadIsolineLDRU[1] + pt.y()*quadIsolineLDRU[3]) |
| |
| : Vec2(-1.0f); |
| |
| drawPoint(dst, (int)(dstPos.x() * (float)dst.getWidth()), (int)(dstPos.y() * (float)dst.getHeight()), color, size); |
| } |
| |
| static void drawTessCoordVisualization (tcu::Surface& dst, TessPrimitiveType primitiveType, const vector<Vec3>& coords) |
| { |
| const int imageWidth = 256; |
| const int imageHeight = 256; |
| dst.setSize(imageWidth, imageHeight); |
| |
| tcu::clear(dst.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f)); |
| |
| for (int i = 0; i < (int)coords.size(); i++) |
| drawTessCoordPoint(dst, primitiveType, coords[i], tcu::RGBA::white(), 2); |
| } |
| |
| static int binarySearchFirstVec3WithXAtLeast (const vector<Vec3>& sorted, float x) |
| { |
| const Vec3 ref(x, 0.0f, 0.0f); |
| const vector<Vec3>::const_iterator first = std::lower_bound(sorted.begin(), sorted.end(), ref, vec3XLessThan); |
| if (first == sorted.end()) |
| return -1; |
| return (int)std::distance(sorted.begin(), first); |
| } |
| |
| template <typename T, typename P> |
| static vector<T> sorted (const vector<T>& unsorted, P pred) |
| { |
| vector<T> result = unsorted; |
| std::sort(result.begin(), result.end(), pred); |
| return result; |
| } |
| |
| template <typename T> |
| static vector<T> sorted (const vector<T>& unsorted) |
| { |
| vector<T> result = unsorted; |
| std::sort(result.begin(), result.end()); |
| return result; |
| } |
| |
| // Check that all points in subset are (approximately) present also in superset. |
| static bool oneWayComparePointSets (TestLog& log, |
| tcu::Surface& errorDst, |
| TessPrimitiveType primitiveType, |
| const vector<Vec3>& subset, |
| const vector<Vec3>& superset, |
| const char* subsetName, |
| const char* supersetName, |
| const tcu::RGBA& errorColor) |
| { |
| const vector<Vec3> supersetSorted = sorted(superset, vec3XLessThan); |
| const float epsilon = 0.01f; |
| const int maxNumFailurePrints = 5; |
| int numFailuresDetected = 0; |
| |
| for (int subNdx = 0; subNdx < (int)subset.size(); subNdx++) |
| { |
| const Vec3& subPt = subset[subNdx]; |
| |
| bool matchFound = false; |
| |
| { |
| // Binary search the index of the first point in supersetSorted with x in the [subPt.x() - epsilon, subPt.x() + epsilon] range. |
| const Vec3 matchMin = subPt - epsilon; |
| const Vec3 matchMax = subPt + epsilon; |
| int firstCandidateNdx = binarySearchFirstVec3WithXAtLeast(supersetSorted, matchMin.x()); |
| |
| if (firstCandidateNdx >= 0) |
| { |
| // Compare subPt to all points in supersetSorted with x in the [subPt.x() - epsilon, subPt.x() + epsilon] range. |
| for (int superNdx = firstCandidateNdx; superNdx < (int)supersetSorted.size() && supersetSorted[superNdx].x() <= matchMax.x(); superNdx++) |
| { |
| const Vec3& superPt = supersetSorted[superNdx]; |
| |
| if (tcu::boolAll(tcu::greaterThanEqual (superPt, matchMin)) && |
| tcu::boolAll(tcu::lessThanEqual (superPt, matchMax))) |
| { |
| matchFound = true; |
| break; |
| } |
| } |
| } |
| } |
| |
| if (!matchFound) |
| { |
| numFailuresDetected++; |
| if (numFailuresDetected < maxNumFailurePrints) |
| log << TestLog::Message << "Failure: no matching " << supersetName << " point found for " << subsetName << " point " << subPt << TestLog::EndMessage; |
| else if (numFailuresDetected == maxNumFailurePrints) |
| log << TestLog::Message << "Note: More errors follow" << TestLog::EndMessage; |
| |
| drawTessCoordPoint(errorDst, primitiveType, subPt, errorColor, 4); |
| } |
| } |
| |
| return numFailuresDetected == 0; |
| } |
| |
| static bool compareTessCoords (TestLog& log, TessPrimitiveType primitiveType, const vector<Vec3>& refCoords, const vector<Vec3>& resCoords) |
| { |
| tcu::Surface refVisual; |
| tcu::Surface resVisual; |
| bool success = true; |
| |
| drawTessCoordVisualization(refVisual, primitiveType, refCoords); |
| drawTessCoordVisualization(resVisual, primitiveType, resCoords); |
| |
| // Check that all points in reference also exist in result. |
| success = oneWayComparePointSets(log, refVisual, primitiveType, refCoords, resCoords, "reference", "result", tcu::RGBA::blue()) && success; |
| // Check that all points in result also exist in reference. |
| success = oneWayComparePointSets(log, resVisual, primitiveType, resCoords, refCoords, "result", "reference", tcu::RGBA::red()) && success; |
| |
| if (!success) |
| { |
| log << TestLog::Message << "Note: in the following reference visualization, points that are missing in result point set are blue (if any)" << TestLog::EndMessage |
| << TestLog::Image("RefTessCoordVisualization", "Reference tessCoord visualization", refVisual) |
| << TestLog::Message << "Note: in the following result visualization, points that are missing in reference point set are red (if any)" << TestLog::EndMessage; |
| } |
| |
| log << TestLog::Image("ResTessCoordVisualization", "Result tessCoord visualization", resVisual); |
| |
| return success; |
| } |
| |
| namespace VerifyFractionalSpacingSingleInternal |
| { |
| |
| struct Segment |
| { |
| int index; //!< Index of left coordinate in sortedXCoords. |
| float length; |
| Segment (void) : index(-1), length(-1.0f) {} |
| Segment (int index_, float length_) : index(index_), length(length_) {} |
| |
| static vector<float> lengths (const vector<Segment>& segments) { return members(segments, &Segment::length); } |
| }; |
| |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Verify fractional spacing conditions for a single line |
| * |
| * Verify that the splitting of an edge (resulting from e.g. an isoline |
| * with outer levels { 1.0, tessLevel }) with a given fractional spacing |
| * mode fulfills certain conditions given in the spec. |
| * |
| * Note that some conditions can't be checked from just one line |
| * (specifically, that the additional segment decreases monotonically |
| * length and the requirement that the additional segments be placed |
| * identically for identical values of clamped level). |
| * |
| * Therefore, the function stores some values to additionalSegmentLengthDst |
| * and additionalSegmentLocationDst that can later be given to |
| * verifyFractionalSpacingMultiple(). A negative value in length means that |
| * no additional segments are present, i.e. there's just one segment. |
| * A negative value in location means that the value wasn't determinable, |
| * i.e. all segments had same length. |
| * The values are not stored if false is returned. |
| *//*--------------------------------------------------------------------*/ |
| static bool verifyFractionalSpacingSingle (TestLog& log, SpacingMode spacingMode, float tessLevel, const vector<float>& coords, float& additionalSegmentLengthDst, int& additionalSegmentLocationDst) |
| { |
| using namespace VerifyFractionalSpacingSingleInternal; |
| |
| DE_ASSERT(spacingMode == SPACINGMODE_FRACTIONAL_ODD || spacingMode == SPACINGMODE_FRACTIONAL_EVEN); |
| |
| const float clampedLevel = getClampedTessLevel(spacingMode, tessLevel); |
| const int finalLevel = getRoundedTessLevel(spacingMode, clampedLevel); |
| const vector<float> sortedCoords = sorted(coords); |
| string failNote = "Note: tessellation level is " + de::toString(tessLevel) + "\nNote: sorted coordinates are:\n " + containerStr(sortedCoords); |
| |
| if ((int)coords.size() != finalLevel + 1) |
| { |
| log << TestLog::Message << "Failure: number of vertices is " << coords.size() << "; expected " << finalLevel + 1 |
| << " (clamped tessellation level is " << clampedLevel << ")" |
| << "; final level (clamped level rounded up to " << (spacingMode == SPACINGMODE_FRACTIONAL_EVEN ? "even" : "odd") << ") is " << finalLevel |
| << " and should equal the number of segments, i.e. number of vertices minus 1" << TestLog::EndMessage |
| << TestLog::Message << failNote << TestLog::EndMessage; |
| return false; |
| } |
| |
| if (sortedCoords[0] != 0.0f || sortedCoords.back() != 1.0f) |
| { |
| log << TestLog::Message << "Failure: smallest coordinate should be 0.0 and biggest should be 1.0" << TestLog::EndMessage |
| << TestLog::Message << failNote << TestLog::EndMessage; |
| return false; |
| } |
| |
| { |
| vector<Segment> segments(finalLevel); |
| for (int i = 0; i < finalLevel; i++) |
| segments[i] = Segment(i, sortedCoords[i+1] - sortedCoords[i]); |
| |
| failNote += "\nNote: segment lengths are, from left to right:\n " + containerStr(Segment::lengths(segments)); |
| |
| { |
| // Divide segments to two different groups based on length. |
| |
| vector<Segment> segmentsA; |
| vector<Segment> segmentsB; |
| segmentsA.push_back(segments[0]); |
| |
| for (int segNdx = 1; segNdx < (int)segments.size(); segNdx++) |
| { |
| const float epsilon = 0.001f; |
| const Segment& seg = segments[segNdx]; |
| |
| if (de::abs(seg.length - segmentsA[0].length) < epsilon) |
| segmentsA.push_back(seg); |
| else if (segmentsB.empty() || de::abs(seg.length - segmentsB[0].length) < epsilon) |
| segmentsB.push_back(seg); |
| else |
| { |
| log << TestLog::Message << "Failure: couldn't divide segments to 2 groups by length; " |
| << "e.g. segment of length " << seg.length << " isn't approximately equal to either " |
| << segmentsA[0].length << " or " << segmentsB[0].length << TestLog::EndMessage |
| << TestLog::Message << failNote << TestLog::EndMessage; |
| return false; |
| } |
| } |
| |
| if (clampedLevel == (float)finalLevel) |
| { |
| // All segments should be of equal length. |
| if (!segmentsA.empty() && !segmentsB.empty()) |
| { |
| log << TestLog::Message << "Failure: clamped and final tessellation level are equal, but not all segments are of equal length." << TestLog::EndMessage |
| << TestLog::Message << failNote << TestLog::EndMessage; |
| return false; |
| } |
| } |
| |
| if (segmentsA.empty() || segmentsB.empty()) // All segments have same length. This is ok. |
| { |
| additionalSegmentLengthDst = segments.size() == 1 ? -1.0f : segments[0].length; |
| additionalSegmentLocationDst = -1; |
| return true; |
| } |
| |
| if (segmentsA.size() != 2 && segmentsB.size() != 2) |
| { |
| log << TestLog::Message << "Failure: when dividing the segments to 2 groups by length, neither of the two groups has exactly 2 or 0 segments in it" << TestLog::EndMessage |
| << TestLog::Message << failNote << TestLog::EndMessage; |
| return false; |
| } |
| |
| // For convenience, arrange so that the 2-segment group is segmentsB. |
| if (segmentsB.size() != 2) |
| std::swap(segmentsA, segmentsB); |
| |
| // \note For 4-segment lines both segmentsA and segmentsB have 2 segments each. |
| // Thus, we can't be sure which ones were meant as the additional segments. |
| // We give the benefit of the doubt by assuming that they're the shorter |
| // ones (as they should). |
| |
| if (segmentsA.size() != 2) |
| { |
| if (segmentsB[0].length > segmentsA[0].length + 0.001f) |
| { |
| log << TestLog::Message << "Failure: the two additional segments are longer than the other segments" << TestLog::EndMessage |
| << TestLog::Message << failNote << TestLog::EndMessage; |
| return false; |
| } |
| } |
| else |
| { |
| // We have 2 segmentsA and 2 segmentsB, ensure segmentsB has the shorter lengths |
| if (segmentsB[0].length > segmentsA[0].length) |
| std::swap(segmentsA, segmentsB); |
| } |
| |
| // Check that the additional segments are placed symmetrically. |
| if (segmentsB[0].index + segmentsB[1].index + 1 != (int)segments.size()) |
| { |
| log << TestLog::Message << "Failure: the two additional segments aren't placed symmetrically; " |
| << "one is at index " << segmentsB[0].index << " and other is at index " << segmentsB[1].index |
| << " (note: the two indexes should sum to " << (int)segments.size()-1 << ", i.e. numberOfSegments-1)" << TestLog::EndMessage |
| << TestLog::Message << failNote << TestLog::EndMessage; |
| return false; |
| } |
| |
| additionalSegmentLengthDst = segmentsB[0].length; |
| if (segmentsA.size() != 2) |
| additionalSegmentLocationDst = de::min(segmentsB[0].index, segmentsB[1].index); |
| else |
| additionalSegmentLocationDst = segmentsB[0].length < segmentsA[0].length - 0.001f ? de::min(segmentsB[0].index, segmentsB[1].index) |
| : -1; // \note -1 when can't reliably decide which ones are the additional segments, a or b. |
| |
| return true; |
| } |
| } |
| } |
| |
| namespace VerifyFractionalSpacingMultipleInternal |
| { |
| |
| struct LineData |
| { |
| float tessLevel; |
| float additionalSegmentLength; |
| int additionalSegmentLocation; |
| LineData (float lev, float len, int loc) : tessLevel(lev), additionalSegmentLength(len), additionalSegmentLocation(loc) {} |
| }; |
| |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Verify fractional spacing conditions between multiple lines |
| * |
| * Verify the fractional spacing conditions that are not checked in |
| * verifyFractionalSpacingSingle(). Uses values given by said function |
| * as parameters, in addition to the spacing mode and tessellation level. |
| *//*--------------------------------------------------------------------*/ |
| static bool verifyFractionalSpacingMultiple (TestLog& log, SpacingMode spacingMode, const vector<float>& tessLevels, const vector<float>& additionalSegmentLengths, const vector<int>& additionalSegmentLocations) |
| { |
| using namespace VerifyFractionalSpacingMultipleInternal; |
| |
| DE_ASSERT(spacingMode == SPACINGMODE_FRACTIONAL_ODD || spacingMode == SPACINGMODE_FRACTIONAL_EVEN); |
| DE_ASSERT(tessLevels.size() == additionalSegmentLengths.size() && |
| tessLevels.size() == additionalSegmentLocations.size()); |
| |
| vector<LineData> lineDatas; |
| |
| for (int i = 0; i < (int)tessLevels.size(); i++) |
| lineDatas.push_back(LineData(tessLevels[i], additionalSegmentLengths[i], additionalSegmentLocations[i])); |
| |
| { |
| const vector<LineData> lineDatasSortedByLevel = sorted(lineDatas, memberPred<std::less>(&LineData::tessLevel)); |
| |
| // Check that lines with identical clamped tessellation levels have identical additionalSegmentLocation. |
| |
| for (int lineNdx = 1; lineNdx < (int)lineDatasSortedByLevel.size(); lineNdx++) |
| { |
| const LineData& curData = lineDatasSortedByLevel[lineNdx]; |
| const LineData& prevData = lineDatasSortedByLevel[lineNdx-1]; |
| |
| if (curData.additionalSegmentLocation < 0 || prevData.additionalSegmentLocation < 0) |
| continue; // Unknown locations, skip. |
| |
| if (getClampedTessLevel(spacingMode, curData.tessLevel) == getClampedTessLevel(spacingMode, prevData.tessLevel) && |
| curData.additionalSegmentLocation != prevData.additionalSegmentLocation) |
| { |
| log << TestLog::Message << "Failure: additional segments not located identically for two edges with identical clamped tessellation levels" << TestLog::EndMessage |
| << TestLog::Message << "Note: tessellation levels are " << curData.tessLevel << " and " << prevData.tessLevel |
| << " (clamped level " << getClampedTessLevel(spacingMode, curData.tessLevel) << ")" |
| << "; but first additional segments located at indices " |
| << curData.additionalSegmentLocation << " and " << prevData.additionalSegmentLocation << ", respectively" << TestLog::EndMessage; |
| return false; |
| } |
| } |
| |
| // Check that, among lines with same clamped rounded tessellation level, additionalSegmentLength is monotonically decreasing with "clampedRoundedTessLevel - clampedTessLevel" (the "fraction"). |
| |
| for (int lineNdx = 1; lineNdx < (int)lineDatasSortedByLevel.size(); lineNdx++) |
| { |
| const LineData& curData = lineDatasSortedByLevel[lineNdx]; |
| const LineData& prevData = lineDatasSortedByLevel[lineNdx-1]; |
| |
| if (curData.additionalSegmentLength < 0.0f || prevData.additionalSegmentLength < 0.0f) |
| continue; // Unknown segment lengths, skip. |
| |
| const float curClampedLevel = getClampedTessLevel(spacingMode, curData.tessLevel); |
| const float prevClampedLevel = getClampedTessLevel(spacingMode, prevData.tessLevel); |
| const int curFinalLevel = getRoundedTessLevel(spacingMode, curClampedLevel); |
| const int prevFinalLevel = getRoundedTessLevel(spacingMode, prevClampedLevel); |
| |
| if (curFinalLevel != prevFinalLevel) |
| continue; |
| |
| const float curFraction = (float)curFinalLevel - curClampedLevel; |
| const float prevFraction = (float)prevFinalLevel - prevClampedLevel; |
| |
| if (curData.additionalSegmentLength < prevData.additionalSegmentLength || |
| (curClampedLevel == prevClampedLevel && curData.additionalSegmentLength != prevData.additionalSegmentLength)) |
| { |
| log << TestLog::Message << "Failure: additional segment length isn't monotonically decreasing with the fraction <n> - <f>, among edges with same final tessellation level" << TestLog::EndMessage |
| << TestLog::Message << "Note: <f> stands for the clamped tessellation level and <n> for the final (rounded and clamped) tessellation level" << TestLog::EndMessage |
| << TestLog::Message << "Note: two edges have tessellation levels " << prevData.tessLevel << " and " << curData.tessLevel << " respectively" |
| << ", clamped " << prevClampedLevel << " and " << curClampedLevel << ", final " << prevFinalLevel << " and " << curFinalLevel |
| << "; fractions are " << prevFraction << " and " << curFraction |
| << ", but resulted in segment lengths " << prevData.additionalSegmentLength << " and " << curData.additionalSegmentLength << TestLog::EndMessage; |
| return false; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| //! Compare triangle sets, ignoring triangle order and vertex order within triangle, and possibly exclude some triangles too. |
| template <typename IsTriangleRelevantT> |
| static bool compareTriangleSets (const vector<Vec3>& coordsA, |
| const vector<Vec3>& coordsB, |
| TestLog& log, |
| const IsTriangleRelevantT& isTriangleRelevant, |
| const char* ignoredTriangleDescription = DE_NULL) |
| { |
| typedef tcu::Vector<Vec3, 3> Triangle; |
| typedef LexCompare<Triangle, 3, VecLexLessThan<3> > TriangleLexLessThan; |
| typedef std::set<Triangle, TriangleLexLessThan> TriangleSet; |
| |
| DE_ASSERT(coordsA.size() % 3 == 0 && coordsB.size() % 3 == 0); |
| |
| const int numTrianglesA = (int)coordsA.size()/3; |
| const int numTrianglesB = (int)coordsB.size()/3; |
| TriangleSet trianglesA; |
| TriangleSet trianglesB; |
| |
| for (int aOrB = 0; aOrB < 2; aOrB++) |
| { |
| const vector<Vec3>& coords = aOrB == 0 ? coordsA : coordsB; |
| const int numTriangles = aOrB == 0 ? numTrianglesA : numTrianglesB; |
| TriangleSet& triangles = aOrB == 0 ? trianglesA : trianglesB; |
| |
| for (int triNdx = 0; triNdx < numTriangles; triNdx++) |
| { |
| Triangle triangle(coords[3*triNdx + 0], |
| coords[3*triNdx + 1], |
| coords[3*triNdx + 2]); |
| |
| if (isTriangleRelevant(triangle.getPtr())) |
| { |
| std::sort(triangle.getPtr(), triangle.getPtr()+3, VecLexLessThan<3>()); |
| triangles.insert(triangle); |
| } |
| } |
| } |
| |
| { |
| TriangleSet::const_iterator aIt = trianglesA.begin(); |
| TriangleSet::const_iterator bIt = trianglesB.begin(); |
| |
| while (aIt != trianglesA.end() || bIt != trianglesB.end()) |
| { |
| const bool aEnd = aIt == trianglesA.end(); |
| const bool bEnd = bIt == trianglesB.end(); |
| |
| if (aEnd || bEnd || *aIt != *bIt) |
| { |
| log << TestLog::Message << "Failure: triangle sets in two cases are not equal (when ignoring triangle and vertex order" |
| << (ignoredTriangleDescription == DE_NULL ? "" : string() + ", and " + ignoredTriangleDescription) << ")" << TestLog::EndMessage; |
| |
| if (!aEnd && (bEnd || TriangleLexLessThan()(*aIt, *bIt))) |
| log << TestLog::Message << "Note: e.g. triangle " << *aIt << " exists for first case but not for second" << TestLog::EndMessage; |
| else |
| log << TestLog::Message << "Note: e.g. triangle " << *bIt << " exists for second case but not for first" << TestLog::EndMessage; |
| |
| return false; |
| } |
| |
| ++aIt; |
| ++bIt; |
| } |
| |
| return true; |
| } |
| } |
| |
| static bool compareTriangleSets (const vector<Vec3>& coordsA, const vector<Vec3>& coordsB, TestLog& log) |
| { |
| return compareTriangleSets(coordsA, coordsB, log, ConstantUnaryPredicate<const Vec3*, true>()); |
| } |
| |
| static void checkGPUShader5Support (Context& context) |
| { |
| const bool supportsES32 = glu::contextSupports(context.getRenderContext().getType(), glu::ApiType::es(3, 2)); |
| TCU_CHECK_AND_THROW(NotSupportedError, supportsES32 || context.getContextInfo().isExtensionSupported("GL_EXT_gpu_shader5"), "GL_EXT_gpu_shader5 is not supported"); |
| } |
| |
| static void checkTessellationSupport (Context& context) |
| { |
| const bool supportsES32 = glu::contextSupports(context.getRenderContext().getType(), glu::ApiType::es(3, 2)); |
| TCU_CHECK_AND_THROW(NotSupportedError, supportsES32 || context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader"), "GL_EXT_tessellation_shader is not supported"); |
| } |
| |
| static std::string specializeShader(Context& context, const char* code) |
| { |
| const glu::ContextType contextType = context.getRenderContext().getType(); |
| const glu::GLSLVersion glslVersion = glu::getContextTypeGLSLVersion(contextType); |
| bool supportsES32 = glu::contextSupports(contextType, glu::ApiType::es(3, 2)); |
| |
| std::map<std::string, std::string> specializationMap; |
| |
| specializationMap["GLSL_VERSION_DECL"] = glu::getGLSLVersionDeclaration(glslVersion); |
| specializationMap["GPU_SHADER5_REQUIRE"] = supportsES32 ? "" : "#extension GL_EXT_gpu_shader5 : require"; |
| specializationMap["TESSELLATION_SHADER_REQUIRE"] = supportsES32 ? "" : "#extension GL_EXT_tessellation_shader : require"; |
| |
| return tcu::StringTemplate(code).specialize(specializationMap); |
| } |
| |
| // Draw primitives with shared edges and check that no cracks are visible at the shared edges. |
| class CommonEdgeCase : public TestCase |
| { |
| public: |
| enum CaseType |
| { |
| CASETYPE_BASIC = 0, //!< Order patch vertices such that when two patches share a vertex, it's at the same index for both. |
| CASETYPE_PRECISE, //!< Vertex indices don't match like for CASETYPE_BASIC, but other measures are taken, using the 'precise' qualifier. |
| |
| CASETYPE_LAST |
| }; |
| |
| CommonEdgeCase (Context& context, const char* name, const char* description, TessPrimitiveType primitiveType, SpacingMode spacing, CaseType caseType) |
| : TestCase (context, name, description) |
| , m_primitiveType (primitiveType) |
| , m_spacing (spacing) |
| , m_caseType (caseType) |
| { |
| DE_ASSERT(m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES || m_primitiveType == TESSPRIMITIVETYPE_QUADS); |
| } |
| |
| void init (void); |
| void deinit (void); |
| IterateResult iterate (void); |
| |
| private: |
| static const int RENDER_SIZE = 256; |
| |
| const TessPrimitiveType m_primitiveType; |
| const SpacingMode m_spacing; |
| const CaseType m_caseType; |
| |
| SharedPtr<const ShaderProgram> m_program; |
| }; |
| |
| void CommonEdgeCase::init (void) |
| { |
| checkTessellationSupport(m_context); |
| |
| if (m_caseType == CASETYPE_PRECISE) |
| checkGPUShader5Support(m_context); |
| |
| checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE); |
| |
| std::string vertexShaderTemplate ("${GLSL_VERSION_DECL}\n" |
| "\n" |
| "in highp vec2 in_v_position;\n" |
| "in highp float in_v_tessParam;\n" |
| "\n" |
| "out highp vec2 in_tc_position;\n" |
| "out highp float in_tc_tessParam;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " in_tc_position = in_v_position;\n" |
| " in_tc_tessParam = in_v_tessParam;\n" |
| "}\n"); |
| |
| std::string tessellationControlTemplate ("${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| + string(m_caseType == CASETYPE_PRECISE ? "${GPU_SHADER5_REQUIRE}\n" : "") + |
| "\n" |
| "layout (vertices = " + string(m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? "3" : m_primitiveType == TESSPRIMITIVETYPE_QUADS ? "4" : DE_NULL) + ") out;\n" |
| "\n" |
| "in highp vec2 in_tc_position[];\n" |
| "in highp float in_tc_tessParam[];\n" |
| "\n" |
| "out highp vec2 in_te_position[];\n" |
| "\n" |
| + (m_caseType == CASETYPE_PRECISE ? "precise gl_TessLevelOuter;\n\n" : "") + |
| "void main (void)\n" |
| "{\n" |
| " in_te_position[gl_InvocationID] = in_tc_position[gl_InvocationID];\n" |
| "\n" |
| " gl_TessLevelInner[0] = 5.0;\n" |
| " gl_TessLevelInner[1] = 5.0;\n" |
| "\n" |
| + (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? |
| " gl_TessLevelOuter[0] = 1.0 + 59.0 * 0.5 * (in_tc_tessParam[1] + in_tc_tessParam[2]);\n" |
| " gl_TessLevelOuter[1] = 1.0 + 59.0 * 0.5 * (in_tc_tessParam[2] + in_tc_tessParam[0]);\n" |
| " gl_TessLevelOuter[2] = 1.0 + 59.0 * 0.5 * (in_tc_tessParam[0] + in_tc_tessParam[1]);\n" |
| : m_primitiveType == TESSPRIMITIVETYPE_QUADS ? |
| " gl_TessLevelOuter[0] = 1.0 + 59.0 * 0.5 * (in_tc_tessParam[0] + in_tc_tessParam[2]);\n" |
| " gl_TessLevelOuter[1] = 1.0 + 59.0 * 0.5 * (in_tc_tessParam[1] + in_tc_tessParam[0]);\n" |
| " gl_TessLevelOuter[2] = 1.0 + 59.0 * 0.5 * (in_tc_tessParam[3] + in_tc_tessParam[1]);\n" |
| " gl_TessLevelOuter[3] = 1.0 + 59.0 * 0.5 * (in_tc_tessParam[2] + in_tc_tessParam[3]);\n" |
| : deFatalStr("Invalid TessPrimitiveType")) + |
| "}\n"); |
| |
| std::string tessellationEvaluationTemplate ("${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| + string(m_caseType == CASETYPE_PRECISE ? "${GPU_SHADER5_REQUIRE}\n" : "") + |
| "\n" |
| + getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing) + |
| "\n" |
| "in highp vec2 in_te_position[];\n" |
| "\n" |
| "out mediump vec4 in_f_color;\n" |
| "\n" |
| + (m_caseType == CASETYPE_PRECISE ? "precise gl_Position;\n\n" : "") + |
| "void main (void)\n" |
| "{\n" |
| + (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? |
| " highp vec2 pos = gl_TessCoord.x*in_te_position[0] + gl_TessCoord.y*in_te_position[1] + gl_TessCoord.z*in_te_position[2];\n" |
| "\n" |
| " highp float f = sqrt(3.0 * min(gl_TessCoord.x, min(gl_TessCoord.y, gl_TessCoord.z))) * 0.5 + 0.5;\n" |
| " in_f_color = vec4(gl_TessCoord*f, 1.0);\n" |
| : m_primitiveType == TESSPRIMITIVETYPE_QUADS ? |
| string() |
| + (m_caseType == CASETYPE_BASIC ? |
| " highp vec2 pos = (1.0-gl_TessCoord.x)*(1.0-gl_TessCoord.y)*in_te_position[0]\n" |
| " + ( gl_TessCoord.x)*(1.0-gl_TessCoord.y)*in_te_position[1]\n" |
| " + (1.0-gl_TessCoord.x)*( gl_TessCoord.y)*in_te_position[2]\n" |
| " + ( gl_TessCoord.x)*( gl_TessCoord.y)*in_te_position[3];\n" |
| : m_caseType == CASETYPE_PRECISE ? |
| " highp vec2 a = (1.0-gl_TessCoord.x)*(1.0-gl_TessCoord.y)*in_te_position[0];\n" |
| " highp vec2 b = ( gl_TessCoord.x)*(1.0-gl_TessCoord.y)*in_te_position[1];\n" |
| " highp vec2 c = (1.0-gl_TessCoord.x)*( gl_TessCoord.y)*in_te_position[2];\n" |
| " highp vec2 d = ( gl_TessCoord.x)*( gl_TessCoord.y)*in_te_position[3];\n" |
| " highp vec2 pos = a+b+c+d;\n" |
| : deFatalStr("Invalid CaseType")) + |
| "\n" |
| " highp float f = sqrt(1.0 - 2.0 * max(abs(gl_TessCoord.x - 0.5), abs(gl_TessCoord.y - 0.5)))*0.5 + 0.5;\n" |
| " in_f_color = vec4(0.1, gl_TessCoord.xy*f, 1.0);\n" |
| : deFatalStr("Invalid TessPrimitiveType")) + |
| "\n" |
| " // Offset the position slightly, based on the parity of the bits in the float representation.\n" |
| " // This is done to detect possible small differences in edge vertex positions between patches.\n" |
| " uvec2 bits = floatBitsToUint(pos);\n" |
| " uint numBits = 0u;\n" |
| " for (uint i = 0u; i < 32u; i++)\n" |
| " numBits += ((bits[0] >> i) & 1u) + ((bits[1] >> i) & 1u);\n" |
| " pos += float(numBits&1u)*0.04;\n" |
| "\n" |
| " gl_Position = vec4(pos, 0.0, 1.0);\n" |
| "}\n"); |
| |
| std::string fragmentShaderTemplate ("${GLSL_VERSION_DECL}\n" |
| "\n" |
| "layout (location = 0) out mediump vec4 o_color;\n" |
| "\n" |
| "in mediump vec4 in_f_color;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " o_color = in_f_color;\n" |
| "}\n"); |
| |
| m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() |
| << glu::VertexSource (specializeShader(m_context, vertexShaderTemplate.c_str())) |
| << glu::TessellationControlSource (specializeShader(m_context, tessellationControlTemplate.c_str())) |
| << glu::TessellationEvaluationSource (specializeShader(m_context, tessellationEvaluationTemplate.c_str())) |
| << glu::FragmentSource (specializeShader(m_context, fragmentShaderTemplate.c_str())))); |
| |
| m_testCtx.getLog() << *m_program; |
| if (!m_program->isOk()) |
| TCU_FAIL("Program compilation failed"); |
| } |
| |
| void CommonEdgeCase::deinit (void) |
| { |
| m_program.clear(); |
| } |
| |
| CommonEdgeCase::IterateResult CommonEdgeCase::iterate (void) |
| { |
| TestLog& log = m_testCtx.getLog(); |
| const RenderContext& renderCtx = m_context.getRenderContext(); |
| const RandomViewport viewport (renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName())); |
| const deUint32 programGL = m_program->getProgram(); |
| const glw::Functions& gl = renderCtx.getFunctions(); |
| |
| const int gridWidth = 4; |
| const int gridHeight = 4; |
| const int numVertices = (gridWidth+1)*(gridHeight+1); |
| const int numIndices = gridWidth*gridHeight * (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? 3*2 : m_primitiveType == TESSPRIMITIVETYPE_QUADS ? 4 : -1); |
| const int numPosCompsPerVertex = 2; |
| const int totalNumPosComps = numPosCompsPerVertex*numVertices; |
| vector<float> gridPosComps; |
| vector<float> gridTessParams; |
| vector<deUint16> gridIndices; |
| |
| gridPosComps.reserve(totalNumPosComps); |
| gridTessParams.reserve(numVertices); |
| gridIndices.reserve(numIndices); |
| |
| { |
| for (int i = 0; i < gridHeight+1; i++) |
| for (int j = 0; j < gridWidth+1; j++) |
| { |
| gridPosComps.push_back(-1.0f + 2.0f * ((float)j + 0.5f) / (float)(gridWidth+1)); |
| gridPosComps.push_back(-1.0f + 2.0f * ((float)i + 0.5f) / (float)(gridHeight+1)); |
| gridTessParams.push_back((float)(i*(gridWidth+1) + j) / (float)(numVertices-1)); |
| } |
| } |
| |
| // Generate patch vertex indices. |
| // \note If CASETYPE_BASIC, the vertices are ordered such that when multiple |
| // triangles/quads share a vertex, it's at the same index for everyone. |
| |
| if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES) |
| { |
| for (int i = 0; i < gridHeight; i++) |
| for (int j = 0; j < gridWidth; j++) |
| { |
| const deUint16 corners[4] = |
| { |
| (deUint16)((i+0)*(gridWidth+1) + j+0), |
| (deUint16)((i+0)*(gridWidth+1) + j+1), |
| (deUint16)((i+1)*(gridWidth+1) + j+0), |
| (deUint16)((i+1)*(gridWidth+1) + j+1) |
| }; |
| |
| const int secondTriangleVertexIndexOffset = m_caseType == CASETYPE_BASIC ? 0 |
| : m_caseType == CASETYPE_PRECISE ? 1 |
| : -1; |
| DE_ASSERT(secondTriangleVertexIndexOffset != -1); |
| |
| for (int k = 0; k < 3; k++) |
| gridIndices.push_back(corners[(k+0 + i + (2-j%3)) % 3]); |
| for (int k = 0; k < 3; k++) |
| gridIndices.push_back(corners[(k+2 + i + (2-j%3) + secondTriangleVertexIndexOffset) % 3 + 1]); |
| } |
| } |
| else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS) |
| { |
| for (int i = 0; i < gridHeight; i++) |
| for (int j = 0; j < gridWidth; j++) |
| { |
| // \note The vertices are ordered such that when multiple quads |
| // share a vertices, it's at the same index for everyone. |
| for (int m = 0; m < 2; m++) |
| for (int n = 0; n < 2; n++) |
| gridIndices.push_back((deUint16)((i+(i+m)%2)*(gridWidth+1) + j+(j+n)%2)); |
| |
| if(m_caseType == CASETYPE_PRECISE && (i+j) % 2 == 0) |
| std::reverse(gridIndices.begin() + (gridIndices.size() - 4), |
| gridIndices.begin() + gridIndices.size()); |
| } |
| } |
| else |
| DE_ASSERT(false); |
| |
| DE_ASSERT((int)gridPosComps.size() == totalNumPosComps); |
| DE_ASSERT((int)gridTessParams.size() == numVertices); |
| DE_ASSERT((int)gridIndices.size() == numIndices); |
| |
| setViewport(gl, viewport); |
| gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f); |
| gl.useProgram(programGL); |
| |
| { |
| gl.patchParameteri(GL_PATCH_VERTICES, m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? 3 : m_primitiveType == TESSPRIMITIVETYPE_QUADS ? 4 : -1); |
| gl.clear(GL_COLOR_BUFFER_BIT); |
| |
| const glu::VertexArrayBinding attrBindings[] = |
| { |
| glu::va::Float("in_v_position", numPosCompsPerVertex, numVertices, 0, &gridPosComps[0]), |
| glu::va::Float("in_v_tessParam", 1, numVertices, 0, &gridTessParams[0]) |
| }; |
| |
| glu::draw(renderCtx, programGL, DE_LENGTH_OF_ARRAY(attrBindings), &attrBindings[0], |
| glu::pr::Patches((int)gridIndices.size(), &gridIndices[0])); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed"); |
| } |
| |
| { |
| const tcu::Surface rendered = getPixels(renderCtx, viewport); |
| |
| log << TestLog::Image("RenderedImage", "Rendered Image", rendered) |
| << TestLog::Message << "Note: coloring is done to clarify the positioning and orientation of the " |
| << (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? "triangles" : m_primitiveType == TESSPRIMITIVETYPE_QUADS ? "quads" : DE_NULL) |
| << "; the color of a vertex corresponds to the index of that vertex in the patch" |
| << TestLog::EndMessage; |
| |
| if (m_caseType == CASETYPE_BASIC) |
| log << TestLog::Message << "Note: each shared vertex has the same index among the primitives it belongs to" << TestLog::EndMessage; |
| else if (m_caseType == CASETYPE_PRECISE) |
| log << TestLog::Message << "Note: the 'precise' qualifier is used to avoid cracks between primitives" << TestLog::EndMessage; |
| else |
| DE_ASSERT(false); |
| |
| // Ad-hoc result verification - check that a certain rectangle in the image contains no black pixels. |
| |
| const int startX = (int)(0.15f * (float)rendered.getWidth()); |
| const int endX = (int)(0.85f * (float)rendered.getWidth()); |
| const int startY = (int)(0.15f * (float)rendered.getHeight()); |
| const int endY = (int)(0.85f * (float)rendered.getHeight()); |
| |
| for (int y = startY; y < endY; y++) |
| for (int x = startX; x < endX; x++) |
| { |
| const tcu::RGBA pixel = rendered.getPixel(x, y); |
| |
| if (pixel.getRed() == 0 && pixel.getGreen() == 0 && pixel.getBlue() == 0) |
| { |
| log << TestLog::Message << "Failure: there seem to be cracks in the rendered result" << TestLog::EndMessage |
| << TestLog::Message << "Note: pixel with zero r, g and b channels found at " << tcu::IVec2(x, y) << TestLog::EndMessage; |
| |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed"); |
| return STOP; |
| } |
| } |
| |
| log << TestLog::Message << "Success: there seem to be no cracks in the rendered result" << TestLog::EndMessage; |
| } |
| |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| return STOP; |
| } |
| |
| // Check tessellation coordinates (read with transform feedback). |
| class TessCoordCase : public TestCase |
| { |
| public: |
| TessCoordCase (Context& context, const char* name, const char* description, TessPrimitiveType primitiveType, SpacingMode spacing) |
| : TestCase (context, name, description) |
| , m_primitiveType (primitiveType) |
| , m_spacing (spacing) |
| { |
| } |
| |
| void init (void); |
| void deinit (void); |
| IterateResult iterate (void); |
| |
| private: |
| struct TessLevels |
| { |
| float inner[2]; |
| float outer[4]; |
| }; |
| |
| static const int RENDER_SIZE = 16; |
| |
| vector<TessLevels> genTessLevelCases (void) const; |
| |
| const TessPrimitiveType m_primitiveType; |
| const SpacingMode m_spacing; |
| |
| SharedPtr<const ShaderProgram> m_program; |
| }; |
| |
| void TessCoordCase::init (void) |
| { |
| checkTessellationSupport(m_context); |
| checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE); |
| |
| std::string vertexShaderTemplate ("${GLSL_VERSION_DECL}\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| "}\n"); |
| |
| std::string tessellationControlTemplate ("${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "\n" |
| "layout (vertices = 1) out;\n" |
| "\n" |
| "uniform mediump float u_tessLevelInner0;\n" |
| "uniform mediump float u_tessLevelInner1;\n" |
| "\n" |
| "uniform mediump float u_tessLevelOuter0;\n" |
| "uniform mediump float u_tessLevelOuter1;\n" |
| "uniform mediump float u_tessLevelOuter2;\n" |
| "uniform mediump float u_tessLevelOuter3;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " gl_TessLevelInner[0] = u_tessLevelInner0;\n" |
| " gl_TessLevelInner[1] = u_tessLevelInner1;\n" |
| "\n" |
| " gl_TessLevelOuter[0] = u_tessLevelOuter0;\n" |
| " gl_TessLevelOuter[1] = u_tessLevelOuter1;\n" |
| " gl_TessLevelOuter[2] = u_tessLevelOuter2;\n" |
| " gl_TessLevelOuter[3] = u_tessLevelOuter3;\n" |
| "}\n"); |
| |
| std::string tessellationEvaluationTemplate ("${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "\n" |
| + getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing, true) + |
| "\n" |
| "out highp vec3 out_te_tessCoord;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " out_te_tessCoord = gl_TessCoord;\n" |
| " gl_Position = vec4(gl_TessCoord.xy*1.6 - 0.8, 0.0, 1.0);\n" |
| "}\n"); |
| |
| std::string fragmentShaderTemplate ("${GLSL_VERSION_DECL}\n" |
| "\n" |
| "layout (location = 0) out mediump vec4 o_color;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " o_color = vec4(1.0);\n" |
| "}\n"); |
| |
| m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() |
| << glu::VertexSource (specializeShader(m_context, vertexShaderTemplate.c_str())) |
| << glu::TessellationControlSource (specializeShader(m_context, tessellationControlTemplate.c_str())) |
| << glu::TessellationEvaluationSource (specializeShader(m_context, tessellationEvaluationTemplate.c_str())) |
| << glu::FragmentSource (specializeShader(m_context, fragmentShaderTemplate.c_str())) |
| << glu::TransformFeedbackVarying ("out_te_tessCoord") |
| << glu::TransformFeedbackMode (GL_INTERLEAVED_ATTRIBS))); |
| |
| m_testCtx.getLog() << *m_program; |
| if (!m_program->isOk()) |
| TCU_FAIL("Program compilation failed"); |
| } |
| |
| void TessCoordCase::deinit (void) |
| { |
| m_program.clear(); |
| } |
| |
| vector<TessCoordCase::TessLevels> TessCoordCase::genTessLevelCases (void) const |
| { |
| static const TessLevels rawTessLevelCases[] = |
| { |
| { { 1.0f, 1.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } }, |
| { { 63.0f, 24.0f }, { 15.0f, 42.0f, 10.0f, 12.0f } }, |
| { { 3.0f, 2.0f }, { 6.0f, 8.0f, 7.0f, 9.0f } }, |
| { { 4.0f, 6.0f }, { 2.0f, 3.0f, 1.0f, 4.0f } }, |
| { { 2.0f, 2.0f }, { 6.0f, 8.0f, 7.0f, 9.0f } }, |
| { { 5.0f, 6.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } }, |
| { { 1.0f, 6.0f }, { 2.0f, 3.0f, 1.0f, 4.0f } }, |
| { { 5.0f, 1.0f }, { 2.0f, 3.0f, 1.0f, 4.0f } }, |
| { { 5.2f, 1.6f }, { 2.9f, 3.4f, 1.5f, 4.1f } } |
| }; |
| |
| if (m_spacing == SPACINGMODE_EQUAL) |
| return vector<TessLevels>(DE_ARRAY_BEGIN(rawTessLevelCases), DE_ARRAY_END(rawTessLevelCases)); |
| else |
| { |
| vector<TessLevels> result; |
| result.reserve(DE_LENGTH_OF_ARRAY(rawTessLevelCases)); |
| |
| for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < DE_LENGTH_OF_ARRAY(rawTessLevelCases); tessLevelCaseNdx++) |
| { |
| TessLevels curTessLevelCase = rawTessLevelCases[tessLevelCaseNdx]; |
| |
| float* const inner = &curTessLevelCase.inner[0]; |
| float* const outer = &curTessLevelCase.outer[0]; |
| |
| for (int j = 0; j < 2; j++) inner[j] = (float)getClampedRoundedTessLevel(m_spacing, inner[j]); |
| for (int j = 0; j < 4; j++) outer[j] = (float)getClampedRoundedTessLevel(m_spacing, outer[j]); |
| |
| if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES) |
| { |
| if (outer[0] > 1.0f || outer[1] > 1.0f || outer[2] > 1.0f) |
| { |
| if (inner[0] == 1.0f) |
| inner[0] = (float)getClampedRoundedTessLevel(m_spacing, inner[0] + 0.1f); |
| } |
| } |
| else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS) |
| { |
| if (outer[0] > 1.0f || outer[1] > 1.0f || outer[2] > 1.0f || outer[3] > 1.0f) |
| { |
| if (inner[0] == 1.0f) inner[0] = (float)getClampedRoundedTessLevel(m_spacing, inner[0] + 0.1f); |
| if (inner[1] == 1.0f) inner[1] = (float)getClampedRoundedTessLevel(m_spacing, inner[1] + 0.1f); |
| } |
| } |
| |
| result.push_back(curTessLevelCase); |
| } |
| |
| DE_ASSERT((int)result.size() == DE_LENGTH_OF_ARRAY(rawTessLevelCases)); |
| return result; |
| } |
| } |
| |
| TessCoordCase::IterateResult TessCoordCase::iterate (void) |
| { |
| typedef TransformFeedbackHandler<Vec3> TFHandler; |
| |
| TestLog& log = m_testCtx.getLog(); |
| const RenderContext& renderCtx = m_context.getRenderContext(); |
| const RandomViewport viewport (renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName())); |
| const deUint32 programGL = m_program->getProgram(); |
| const glw::Functions& gl = renderCtx.getFunctions(); |
| |
| const int tessLevelInner0Loc = gl.getUniformLocation(programGL, "u_tessLevelInner0"); |
| const int tessLevelInner1Loc = gl.getUniformLocation(programGL, "u_tessLevelInner1"); |
| const int tessLevelOuter0Loc = gl.getUniformLocation(programGL, "u_tessLevelOuter0"); |
| const int tessLevelOuter1Loc = gl.getUniformLocation(programGL, "u_tessLevelOuter1"); |
| const int tessLevelOuter2Loc = gl.getUniformLocation(programGL, "u_tessLevelOuter2"); |
| const int tessLevelOuter3Loc = gl.getUniformLocation(programGL, "u_tessLevelOuter3"); |
| |
| const vector<TessLevels> tessLevelCases = genTessLevelCases(); |
| vector<vector<Vec3> > caseReferences (tessLevelCases.size()); |
| |
| for (int i = 0; i < (int)tessLevelCases.size(); i++) |
| caseReferences[i] = generateReferenceTessCoords(m_primitiveType, m_spacing, &tessLevelCases[i].inner[0], &tessLevelCases[i].outer[0]); |
| |
| const int maxNumVertices = (int)std::max_element(caseReferences.begin(), caseReferences.end(), SizeLessThan<vector<Vec3> >())->size(); |
| const TFHandler tfHandler (m_context.getRenderContext(), maxNumVertices); |
| |
| bool success = true; |
| |
| setViewport(gl, viewport); |
| gl.useProgram(programGL); |
| |
| gl.patchParameteri(GL_PATCH_VERTICES, 1); |
| |
| for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < (int)tessLevelCases.size(); tessLevelCaseNdx++) |
| { |
| const float* const innerLevels = &tessLevelCases[tessLevelCaseNdx].inner[0]; |
| const float* const outerLevels = &tessLevelCases[tessLevelCaseNdx].outer[0]; |
| |
| log << TestLog::Message << "Tessellation levels: " << tessellationLevelsString(innerLevels, outerLevels, m_primitiveType) << TestLog::EndMessage; |
| |
| gl.uniform1f(tessLevelInner0Loc, innerLevels[0]); |
| gl.uniform1f(tessLevelInner1Loc, innerLevels[1]); |
| gl.uniform1f(tessLevelOuter0Loc, outerLevels[0]); |
| gl.uniform1f(tessLevelOuter1Loc, outerLevels[1]); |
| gl.uniform1f(tessLevelOuter2Loc, outerLevels[2]); |
| gl.uniform1f(tessLevelOuter3Loc, outerLevels[3]); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Setup failed"); |
| |
| { |
| const vector<Vec3>& tessCoordsRef = caseReferences[tessLevelCaseNdx]; |
| const TFHandler::Result tfResult = tfHandler.renderAndGetPrimitives(programGL, GL_POINTS, 0, DE_NULL, 1); |
| |
| if (tfResult.numPrimitives != (int)tessCoordsRef.size()) |
| { |
| log << TestLog::Message << "Failure: GL reported GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN to be " |
| << tfResult.numPrimitives << ", reference value is " << tessCoordsRef.size() |
| << " (logging further info anyway)" << TestLog::EndMessage; |
| success = false; |
| } |
| else |
| log << TestLog::Message << "Note: GL reported GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN to be " << tfResult.numPrimitives << TestLog::EndMessage; |
| |
| if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES) |
| log << TestLog::Message << "Note: in the following visualization(s), the u=1, v=1, w=1 corners are at the right, top, and left corners, respectively" << TestLog::EndMessage; |
| else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS || m_primitiveType == TESSPRIMITIVETYPE_ISOLINES) |
| log << TestLog::Message << "Note: in the following visualization(s), u and v coordinate go left-to-right and bottom-to-top, respectively" << TestLog::EndMessage; |
| else |
| DE_ASSERT(false); |
| |
| success = compareTessCoords(log, m_primitiveType, tessCoordsRef, tfResult.varying) && success; |
| } |
| |
| if (!success) |
| break; |
| else |
| log << TestLog::Message << "All OK" << TestLog::EndMessage; |
| } |
| |
| m_testCtx.setTestResult(success ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, success ? "Pass" : "Invalid tessellation coordinates"); |
| return STOP; |
| } |
| |
| // Check validity of fractional spacing modes. Draws a single isoline, reads tesscoords with transform feedback. |
| class FractionalSpacingModeCase : public TestCase |
| { |
| public: |
| FractionalSpacingModeCase (Context& context, const char* name, const char* description, SpacingMode spacing) |
| : TestCase (context, name, description) |
| , m_spacing (spacing) |
| { |
| DE_ASSERT(m_spacing == SPACINGMODE_FRACTIONAL_EVEN || m_spacing == SPACINGMODE_FRACTIONAL_ODD); |
| } |
| |
| void init (void); |
| void deinit (void); |
| IterateResult iterate (void); |
| |
| private: |
| static const int RENDER_SIZE = 16; |
| |
| static vector<float> genTessLevelCases (void); |
| |
| const SpacingMode m_spacing; |
| |
| SharedPtr<const ShaderProgram> m_program; |
| }; |
| |
| void FractionalSpacingModeCase::init (void) |
| { |
| checkTessellationSupport(m_context); |
| checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE); |
| |
| std::string vertexShaderTemplate ("${GLSL_VERSION_DECL}\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| "}\n"); |
| std::string tessellationControlTemplate ("${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "\n" |
| "layout (vertices = 1) out;\n" |
| "\n" |
| "uniform mediump float u_tessLevelOuter1;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " gl_TessLevelOuter[0] = 1.0;\n" |
| " gl_TessLevelOuter[1] = u_tessLevelOuter1;\n" |
| "}\n"); |
| std::string tessellationEvaluationTemplate ("${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "\n" |
| + getTessellationEvaluationInLayoutString(TESSPRIMITIVETYPE_ISOLINES, m_spacing, true) + |
| "\n" |
| "out highp float out_te_tessCoord;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " out_te_tessCoord = gl_TessCoord.x;\n" |
| " gl_Position = vec4(gl_TessCoord.xy*1.6 - 0.8, 0.0, 1.0);\n" |
| "}\n"); |
| std::string fragmentShaderTemplate ("${GLSL_VERSION_DECL}\n" |
| "\n" |
| "layout (location = 0) out mediump vec4 o_color;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " o_color = vec4(1.0);\n" |
| "}\n"); |
| |
| m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() |
| << glu::VertexSource (specializeShader(m_context, vertexShaderTemplate.c_str())) |
| << glu::TessellationControlSource (specializeShader(m_context, tessellationControlTemplate.c_str())) |
| << glu::TessellationEvaluationSource (specializeShader(m_context, tessellationEvaluationTemplate.c_str())) |
| << glu::FragmentSource (specializeShader(m_context, fragmentShaderTemplate.c_str())) |
| << glu::TransformFeedbackVarying ("out_te_tessCoord") |
| << glu::TransformFeedbackMode (GL_INTERLEAVED_ATTRIBS))); |
| |
| m_testCtx.getLog() << *m_program; |
| if (!m_program->isOk()) |
| TCU_FAIL("Program compilation failed"); |
| } |
| |
| void FractionalSpacingModeCase::deinit (void) |
| { |
| m_program.clear(); |
| } |
| |
| vector<float> FractionalSpacingModeCase::genTessLevelCases (void) |
| { |
| vector<float> result; |
| |
| // Ranges [7.0 .. 8.0), [8.0 .. 9.0) and [9.0 .. 10.0) |
| { |
| static const float rangeStarts[] = { 7.0f, 8.0f, 9.0f }; |
| const int numSamplesPerRange = 10; |
| |
| for (int rangeNdx = 0; rangeNdx < DE_LENGTH_OF_ARRAY(rangeStarts); rangeNdx++) |
| for (int i = 0; i < numSamplesPerRange; i++) |
| result.push_back(rangeStarts[rangeNdx] + (float)i/(float)numSamplesPerRange); |
| } |
| |
| // 0.3, 1.3, 2.3, ... , 62.3 |
| for (int i = 0; i <= 62; i++) |
| result.push_back((float)i + 0.3f); |
| |
| return result; |
| } |
| |
| FractionalSpacingModeCase::IterateResult FractionalSpacingModeCase::iterate (void) |
| { |
| typedef TransformFeedbackHandler<float> TFHandler; |
| |
| TestLog& log = m_testCtx.getLog(); |
| const RenderContext& renderCtx = m_context.getRenderContext(); |
| const RandomViewport viewport (renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName())); |
| const deUint32 programGL = m_program->getProgram(); |
| const glw::Functions& gl = renderCtx.getFunctions(); |
| |
| const int tessLevelOuter1Loc = gl.getUniformLocation(programGL, "u_tessLevelOuter1"); |
| |
| // Second outer tessellation levels. |
| const vector<float> tessLevelCases = genTessLevelCases(); |
| const int maxNumVertices = 1 + getClampedRoundedTessLevel(m_spacing, *std::max_element(tessLevelCases.begin(), tessLevelCases.end())); |
| vector<float> additionalSegmentLengths; |
| vector<int> additionalSegmentLocations; |
| |
| const TFHandler tfHandler (m_context.getRenderContext(), maxNumVertices); |
| |
| bool success = true; |
| |
| setViewport(gl, viewport); |
| gl.useProgram(programGL); |
| |
| gl.patchParameteri(GL_PATCH_VERTICES, 1); |
| |
| for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < (int)tessLevelCases.size(); tessLevelCaseNdx++) |
| { |
| const float outerLevel1 = tessLevelCases[tessLevelCaseNdx]; |
| |
| gl.uniform1f(tessLevelOuter1Loc, outerLevel1); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Setup failed"); |
| |
| { |
| const TFHandler::Result tfResult = tfHandler.renderAndGetPrimitives(programGL, GL_POINTS, 0, DE_NULL, 1); |
| float additionalSegmentLength; |
| int additionalSegmentLocation; |
| |
| success = verifyFractionalSpacingSingle(log, m_spacing, outerLevel1, tfResult.varying, |
| additionalSegmentLength, additionalSegmentLocation); |
| |
| if (!success) |
| break; |
| |
| additionalSegmentLengths.push_back(additionalSegmentLength); |
| additionalSegmentLocations.push_back(additionalSegmentLocation); |
| } |
| } |
| |
| if (success) |
| success = verifyFractionalSpacingMultiple(log, m_spacing, tessLevelCases, additionalSegmentLengths, additionalSegmentLocations); |
| |
| m_testCtx.setTestResult(success ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, success ? "Pass" : "Invalid tessellation coordinates"); |
| return STOP; |
| } |
| |
| // Base class for a case with one input attribute (in_v_position) and optionally a TCS; tests with a couple of different sets of tessellation levels. |
| class BasicVariousTessLevelsPosAttrCase : public TestCase |
| { |
| public: |
| BasicVariousTessLevelsPosAttrCase (Context& context, |
| const char* name, |
| const char* description, |
| TessPrimitiveType primitiveType, |
| SpacingMode spacing, |
| const char* referenceImagePathPrefix) |
| : TestCase (context, name, description) |
| , m_primitiveType (primitiveType) |
| , m_spacing (spacing) |
| , m_referenceImagePathPrefix (referenceImagePathPrefix) |
| { |
| } |
| |
| void init (void); |
| void deinit (void); |
| IterateResult iterate (void); |
| |
| protected: |
| virtual const glu::ProgramSources makeSources (TessPrimitiveType, SpacingMode, const char* vtxOutPosAttrName) const = DE_NULL; |
| |
| private: |
| static const int RENDER_SIZE = 256; |
| |
| const TessPrimitiveType m_primitiveType; |
| const SpacingMode m_spacing; |
| const string m_referenceImagePathPrefix; |
| |
| SharedPtr<const ShaderProgram> m_program; |
| }; |
| |
| void BasicVariousTessLevelsPosAttrCase::init (void) |
| { |
| checkTessellationSupport(m_context); |
| checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE); |
| |
| { |
| glu::ProgramSources sources = makeSources(m_primitiveType, m_spacing, "in_tc_position"); |
| DE_ASSERT(sources.sources[glu::SHADERTYPE_TESSELLATION_CONTROL].empty()); |
| |
| std::string tessellationControlTemplate ("${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "\n" |
| "layout (vertices = " + string(m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? "3" : "4") + ") out;\n" |
| "\n" |
| "in highp vec2 in_tc_position[];\n" |
| "\n" |
| "out highp vec2 in_te_position[];\n" |
| "\n" |
| "uniform mediump float u_tessLevelInner0;\n" |
| "uniform mediump float u_tessLevelInner1;\n" |
| "uniform mediump float u_tessLevelOuter0;\n" |
| "uniform mediump float u_tessLevelOuter1;\n" |
| "uniform mediump float u_tessLevelOuter2;\n" |
| "uniform mediump float u_tessLevelOuter3;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " in_te_position[gl_InvocationID] = in_tc_position[gl_InvocationID];\n" |
| "\n" |
| " gl_TessLevelInner[0] = u_tessLevelInner0;\n" |
| " gl_TessLevelInner[1] = u_tessLevelInner1;\n" |
| "\n" |
| " gl_TessLevelOuter[0] = u_tessLevelOuter0;\n" |
| " gl_TessLevelOuter[1] = u_tessLevelOuter1;\n" |
| " gl_TessLevelOuter[2] = u_tessLevelOuter2;\n" |
| " gl_TessLevelOuter[3] = u_tessLevelOuter3;\n" |
| "}\n"); |
| |
| sources << glu::TessellationControlSource(specializeShader(m_context, tessellationControlTemplate.c_str())); |
| |
| m_program = SharedPtr<const ShaderProgram>(new glu::ShaderProgram(m_context.getRenderContext(), sources)); |
| } |
| |
| m_testCtx.getLog() << *m_program; |
| if (!m_program->isOk()) |
| TCU_FAIL("Program compilation failed"); |
| } |
| |
| void BasicVariousTessLevelsPosAttrCase::deinit (void) |
| { |
| m_program.clear(); |
| } |
| |
| BasicVariousTessLevelsPosAttrCase::IterateResult BasicVariousTessLevelsPosAttrCase::iterate (void) |
| { |
| static const struct |
| { |
| float inner[2]; |
| float outer[4]; |
| } tessLevelCases[] = |
| { |
| { { 9.0f, 9.0f }, { 9.0f, 9.0f, 9.0f, 9.0f } }, |
| { { 8.0f, 11.0f }, { 13.0f, 15.0f, 18.0f, 21.0f } }, |
| { { 17.0f, 14.0f }, { 3.0f, 6.0f, 9.0f, 12.0f } } |
| }; |
| |
| TestLog& log = m_testCtx.getLog(); |
| const RenderContext& renderCtx = m_context.getRenderContext(); |
| const RandomViewport viewport (renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName())); |
| const deUint32 programGL = m_program->getProgram(); |
| const glw::Functions& gl = renderCtx.getFunctions(); |
| const int patchSize = m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? 3 |
| : m_primitiveType == TESSPRIMITIVETYPE_QUADS ? 4 |
| : m_primitiveType == TESSPRIMITIVETYPE_ISOLINES ? 4 |
| : -1; |
| |
| setViewport(gl, viewport); |
| gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f); |
| gl.useProgram(programGL); |
| |
| gl.patchParameteri(GL_PATCH_VERTICES, patchSize); |
| |
| for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < DE_LENGTH_OF_ARRAY(tessLevelCases); tessLevelCaseNdx++) |
| { |
| float innerLevels[2]; |
| float outerLevels[4]; |
| |
| for (int i = 0; i < DE_LENGTH_OF_ARRAY(innerLevels); i++) |
| innerLevels[i] = (float)getClampedRoundedTessLevel(m_spacing, tessLevelCases[tessLevelCaseNdx].inner[i]); |
| |
| for (int i = 0; i < DE_LENGTH_OF_ARRAY(outerLevels); i++) |
| outerLevels[i] = (float)getClampedRoundedTessLevel(m_spacing, tessLevelCases[tessLevelCaseNdx].outer[i]); |
| |
| log << TestLog::Message << "Tessellation levels: " << tessellationLevelsString(&innerLevels[0], &outerLevels[0], m_primitiveType) << TestLog::EndMessage; |
| |
| gl.uniform1f(gl.getUniformLocation(programGL, "u_tessLevelInner0"), innerLevels[0]); |
| gl.uniform1f(gl.getUniformLocation(programGL, "u_tessLevelInner1"), innerLevels[1]); |
| gl.uniform1f(gl.getUniformLocation(programGL, "u_tessLevelOuter0"), outerLevels[0]); |
| gl.uniform1f(gl.getUniformLocation(programGL, "u_tessLevelOuter1"), outerLevels[1]); |
| gl.uniform1f(gl.getUniformLocation(programGL, "u_tessLevelOuter2"), outerLevels[2]); |
| gl.uniform1f(gl.getUniformLocation(programGL, "u_tessLevelOuter3"), outerLevels[3]); |
| |
| gl.clear(GL_COLOR_BUFFER_BIT); |
| |
| { |
| vector<Vec2> positions; |
| positions.reserve(4); |
| |
| if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES) |
| { |
| positions.push_back(Vec2( 0.8f, 0.6f)); |
| positions.push_back(Vec2( 0.0f, -0.786f)); |
| positions.push_back(Vec2(-0.8f, 0.6f)); |
| } |
| else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS || m_primitiveType == TESSPRIMITIVETYPE_ISOLINES) |
| { |
| positions.push_back(Vec2(-0.8f, -0.8f)); |
| positions.push_back(Vec2( 0.8f, -0.8f)); |
| positions.push_back(Vec2(-0.8f, 0.8f)); |
| positions.push_back(Vec2( 0.8f, 0.8f)); |
| } |
| else |
| DE_ASSERT(false); |
| |
| DE_ASSERT((int)positions.size() == patchSize); |
| |
| const glu::VertexArrayBinding attrBindings[] = |
| { |
| glu::va::Float("in_v_position", 2, (int)positions.size(), 0, &positions[0].x()) |
| }; |
| |
| glu::draw(m_context.getRenderContext(), programGL, DE_LENGTH_OF_ARRAY(attrBindings), &attrBindings[0], |
| glu::pr::Patches(patchSize)); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed"); |
| } |
| |
| { |
| const tcu::Surface rendered = getPixels(renderCtx, viewport); |
| const tcu::TextureLevel reference = getPNG(m_testCtx.getArchive(), m_referenceImagePathPrefix + "_" + de::toString(tessLevelCaseNdx) + ".png"); |
| const bool success = tcu::fuzzyCompare(log, "ImageComparison", "Image Comparison", reference.getAccess(), rendered.getAccess(), 0.002f, tcu::COMPARE_LOG_RESULT); |
| |
| if (!success) |
| { |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed"); |
| return STOP; |
| } |
| } |
| } |
| |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| return STOP; |
| } |
| |
| // Test that there are no obvious gaps in the triangulation of a tessellated triangle or quad. |
| class BasicTriangleFillCoverCase : public BasicVariousTessLevelsPosAttrCase |
| { |
| public: |
| BasicTriangleFillCoverCase (Context& context, const char* name, const char* description, TessPrimitiveType primitiveType, SpacingMode spacing, const char* referenceImagePathPrefix) |
| : BasicVariousTessLevelsPosAttrCase (context, name, description, primitiveType, spacing, referenceImagePathPrefix) |
| { |
| DE_ASSERT(primitiveType == TESSPRIMITIVETYPE_TRIANGLES || primitiveType == TESSPRIMITIVETYPE_QUADS); |
| } |
| |
| protected: |
| void init (void) |
| { |
| checkGPUShader5Support(m_context); |
| BasicVariousTessLevelsPosAttrCase::init(); |
| } |
| |
| const glu::ProgramSources makeSources (TessPrimitiveType primitiveType, SpacingMode spacing, const char* vtxOutPosAttrName) const |
| { |
| std::string vertexShaderTemplate ("${GLSL_VERSION_DECL}\n" |
| "\n" |
| "in highp vec2 in_v_position;\n" |
| "\n" |
| "out highp vec2 " + string(vtxOutPosAttrName) + ";\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " " + vtxOutPosAttrName + " = in_v_position;\n" |
| "}\n"); |
| std::string tessellationEvaluationTemplate ("${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GPU_SHADER5_REQUIRE}\n" |
| "\n" |
| + getTessellationEvaluationInLayoutString(primitiveType, spacing) + |
| "\n" |
| "in highp vec2 in_te_position[];\n" |
| "\n" |
| "precise gl_Position;\n" |
| "void main (void)\n" |
| "{\n" |
| + (primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? |
| "\n" |
| " highp float d = 3.0 * min(gl_TessCoord.x, min(gl_TessCoord.y, gl_TessCoord.z));\n" |
| " highp vec2 corner0 = in_te_position[0];\n" |
| " highp vec2 corner1 = in_te_position[1];\n" |
| " highp vec2 corner2 = in_te_position[2];\n" |
| " highp vec2 pos = corner0*gl_TessCoord.x + corner1*gl_TessCoord.y + corner2*gl_TessCoord.z;\n" |
| " highp vec2 fromCenter = pos - (corner0 + corner1 + corner2) / 3.0;\n" |
| " highp float f = (1.0 - length(fromCenter)) * (1.5 - d);\n" |
| " pos += 0.75 * f * fromCenter / (length(fromCenter) + 0.3);\n" |
| " gl_Position = vec4(pos, 0.0, 1.0);\n" |
| : primitiveType == TESSPRIMITIVETYPE_QUADS ? |
| " highp vec2 corner0 = in_te_position[0];\n" |
| " highp vec2 corner1 = in_te_position[1];\n" |
| " highp vec2 corner2 = in_te_position[2];\n" |
| " highp vec2 corner3 = in_te_position[3];\n" |
| " highp vec2 pos = (1.0-gl_TessCoord.x)*(1.0-gl_TessCoord.y)*corner0\n" |
| " + ( gl_TessCoord.x)*(1.0-gl_TessCoord.y)*corner1\n" |
| " + (1.0-gl_TessCoord.x)*( gl_TessCoord.y)*corner2\n" |
| " + ( gl_TessCoord.x)*( gl_TessCoord.y)*corner3;\n" |
| " highp float d = 2.0 * min(abs(gl_TessCoord.x-0.5), abs(gl_TessCoord.y-0.5));\n" |
| " highp vec2 fromCenter = pos - (corner0 + corner1 + corner2 + corner3) / 4.0;\n" |
| " highp float f = (1.0 - length(fromCenter)) * sqrt(1.7 - d);\n" |
| " pos += 0.75 * f * fromCenter / (length(fromCenter) + 0.3);\n" |
| " gl_Position = vec4(pos, 0.0, 1.0);\n" |
| : DE_NULL) + |
| "}\n"); |
| std::string fragmentShaderTemplate ("${GLSL_VERSION_DECL}\n" |
| "\n" |
| "layout (location = 0) out mediump vec4 o_color;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " o_color = vec4(1.0);\n" |
| "}\n"); |
| |
| return glu::ProgramSources() |
| << glu::VertexSource (specializeShader(m_context, vertexShaderTemplate.c_str())) |
| << glu::TessellationEvaluationSource (specializeShader(m_context, tessellationEvaluationTemplate.c_str())) |
| << glu::FragmentSource (specializeShader(m_context, fragmentShaderTemplate.c_str())); |
| } |
| }; |
| |
| // Check that there are no obvious overlaps in the triangulation of a tessellated triangle or quad. |
| class BasicTriangleFillNonOverlapCase : public BasicVariousTessLevelsPosAttrCase |
| { |
| public: |
| BasicTriangleFillNonOverlapCase (Context& context, const char* name, const char* description, TessPrimitiveType primitiveType, SpacingMode spacing, const char* referenceImagePathPrefix) |
| : BasicVariousTessLevelsPosAttrCase (context, name, description, primitiveType, spacing, referenceImagePathPrefix) |
| { |
| DE_ASSERT(primitiveType == TESSPRIMITIVETYPE_TRIANGLES || primitiveType == TESSPRIMITIVETYPE_QUADS); |
| } |
| |
| protected: |
| const glu::ProgramSources makeSources (TessPrimitiveType primitiveType, SpacingMode spacing, const char* vtxOutPosAttrName) const |
| { |
| std::string vertexShaderTemplate ("${GLSL_VERSION_DECL}\n" |
| "\n" |
| "in highp vec2 in_v_position;\n" |
| "\n" |
| "out highp vec2 " + string(vtxOutPosAttrName) + ";\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " " + vtxOutPosAttrName + " = in_v_position;\n" |
| "}\n"); |
| std::string tessellationEvaluationTemplate ("${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "\n" |
| + getTessellationEvaluationInLayoutString(primitiveType, spacing) + |
| "\n" |
| "in highp vec2 in_te_position[];\n" |
| "\n" |
| "out mediump vec4 in_f_color;\n" |
| "\n" |
| "uniform mediump float u_tessLevelInner0;\n" |
| "uniform mediump float u_tessLevelInner1;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| + (primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? |
| "\n" |
| " highp vec2 corner0 = in_te_position[0];\n" |
| " highp vec2 corner1 = in_te_position[1];\n" |
| " highp vec2 corner2 = in_te_position[2];\n" |
| " highp vec2 pos = corner0*gl_TessCoord.x + corner1*gl_TessCoord.y + corner2*gl_TessCoord.z;\n" |
| " gl_Position = vec4(pos, 0.0, 1.0);\n" |
| " highp int numConcentricTriangles = int(round(u_tessLevelInner0)) / 2 + 1;\n" |
| " highp float d = 3.0 * min(gl_TessCoord.x, min(gl_TessCoord.y, gl_TessCoord.z));\n" |
| " highp int phase = int(d*float(numConcentricTriangles)) % 3;\n" |
| " in_f_color = phase == 0 ? vec4(1.0, 0.0, 0.0, 1.0)\n" |
| " : phase == 1 ? vec4(0.0, 1.0, 0.0, 1.0)\n" |
| " : vec4(0.0, 0.0, 1.0, 1.0);\n" |
| : primitiveType == TESSPRIMITIVETYPE_QUADS ? |
| " highp vec2 corner0 = in_te_position[0];\n" |
| " highp vec2 corner1 = in_te_position[1];\n" |
| " highp vec2 corner2 = in_te_position[2];\n" |
| " highp vec2 corner3 = in_te_position[3];\n" |
| " highp vec2 pos = (1.0-gl_TessCoord.x)*(1.0-gl_TessCoord.y)*corner0\n" |
| " + ( gl_TessCoord.x)*(1.0-gl_TessCoord.y)*corner1\n" |
| |