| /*------------------------------------------------------------------------- |
| * 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 de::Random; |
| using de::SharedPtr; |
| using glu::RenderContext; |
| using glu::ShaderProgram; |
| using tcu::RenderTarget; |
| using tcu::TestLog; |
| using tcu::Vec2; |
| using tcu::Vec3; |
| using tcu::Vec4; |
| |
| using std::string; |
| using std::vector; |
| |
| 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(uint32_t 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 uint32_t getQueryResult(const glw::Functions &gl, uint32_t queryObject) |
| { |
| uint32_t result = (uint32_t)-1; |
| gl.getQueryObjectuiv(queryObject, GL_QUERY_RESULT, &result); |
| TCU_CHECK(result != (uint32_t)-1); |
| return result; |
| } |
| |
| template <typename T> |
| static void readDataMapped(const glw::Functions &gl, uint32_t 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, uint32_t 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(uint32_t programGL, uint32_t 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( |
| uint32_t programGL, uint32_t 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 uint32_t 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 (uint32_t)-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); |
| } |
| }; |
| |
| } // namespace VerifyFractionalSpacingSingleInternal |
| |
| /*--------------------------------------------------------------------*//*! |
| * \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) |
| { |
| } |
| }; |
| |
| } // namespace VerifyFractionalSpacingMultipleInternal |
| |
| /*--------------------------------------------------------------------*//*! |
| * \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 bool supportsES32orGL45(Context &context) |
| { |
| glu::ContextType contextType = context.getRenderContext().getType(); |
| return glu::contextSupports(contextType, glu::ApiType::es(3, 2)) || |
| glu::contextSupports(contextType, glu::ApiType::core(4, 5)); |
| } |
| |
| static void checkGPUShader5Support(Context &context) |
| { |
| TCU_CHECK_AND_THROW(NotSupportedError, |
| supportsES32orGL45(context) || |
| context.getContextInfo().isExtensionSupported("GL_EXT_gpu_shader5"), |
| "GL_EXT_gpu_shader5 is not supported"); |
| } |
| |
| static void checkTessellationSupport(Context &context) |
| { |
| TCU_CHECK_AND_THROW(NotSupportedError, |
| supportsES32orGL45(context) || |
| 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); |
| const bool cSupportsES32orGL45 = supportsES32orGL45(context); |
| |
| std::map<std::string, std::string> specializationMap = { |
| {"GLSL_VERSION_DECL", glu::getGLSLVersionDeclaration(glslVersion)}, |
| {"GPU_SHADER5_REQUIRE", cSupportsES32orGL45 ? "" : "#extension GL_EXT_gpu_shader5 : require"}, |
| {"TESSELLATION_SHADER_REQUIRE", cSupportsES32orGL45 ? "" : "#extension GL_EXT_tessellation_shader : require"}, |
| {"GLSL_PER_VERTEX_OUT", ""}, // needed for GL4.5 |
| {"GLSL_PER_VERTEX_IN_ARR", ""}, |
| {"GLSL_PER_VERTEX_OUT_ARR", ""}, |
| {"GLSL_PRECISE_PER_VERTEX_OUT", ""}, |
| {"GLSL_PRECISE_PER_VERTEX_IN_ARR", ""}, |
| {"GLSL_PRECISE_PER_VERTEX_OUT_ARR", ""}}; |
| |
| // for gl4.5 we need to add per vertex sections |
| if (!glu::isContextTypeES(context.getRenderContext().getType())) |
| { |
| specializationMap["GLSL_PER_VERTEX_OUT"] = "out gl_PerVertex { vec4 gl_Position; };\n"; |
| specializationMap["GLSL_PER_VERTEX_IN_ARR"] = "in gl_PerVertex { vec4 gl_Position; } gl_in[];\n"; |
| specializationMap["GLSL_PER_VERTEX_OUT_ARR"] = "out gl_PerVertex { vec4 gl_Position; } gl_out[];\n"; |
| specializationMap["GLSL_PRECISE_PER_VERTEX_OUT"] = "out gl_PerVertex { precise vec4 gl_Position; };\n"; |
| specializationMap["GLSL_PRECISE_PER_VERTEX_IN_ARR"] = |
| "in gl_PerVertex { precise vec4 gl_Position; } gl_in[];\n"; |
| specializationMap["GLSL_PRECISE_PER_VERTEX_OUT_ARR"] = |
| "out gl_PerVertex { precise vec4 gl_Position; } gl_out[];\n"; |
| } |
| |
| 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) |
| { |
| bool isGL45 = glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5)); |
| if (!isGL45) |
| { |
| checkTessellationSupport(m_context); |
| if (m_caseType == CASETYPE_PRECISE) |
| checkGPUShader5Support(m_context); |
| } |
| |
| checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE); |
| |
| std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n" |
| "${GLSL_PER_VERTEX_OUT}\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" : "") + |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT_ARR}\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" : |
| DE_NULL) + |
| "}\n"); |
| |
| std::string tessellationEvaluationTemplate( |
| "${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" + |
| string(m_caseType == CASETYPE_PRECISE ? "${GPU_SHADER5_REQUIRE}\n" : "") + |
| "${GLSL_PRECISE_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PRECISE_PER_VERTEX_OUT}\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 && !isGL45) ? "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" : |
| DE_NULL) + |
| "\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" : |
| DE_NULL) + |
| "\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 uint32_t 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<uint16_t> 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 uint16_t corners[4] = { |
| (uint16_t)((i + 0) * (gridWidth + 1) + j + 0), (uint16_t)((i + 0) * (gridWidth + 1) + j + 1), |
| (uint16_t)((i + 1) * (gridWidth + 1) + j + 0), (uint16_t)((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((uint16_t)((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" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| "}\n"); |
| |
| std::string tessellationControlTemplate("${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT_ARR}\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" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT}\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 uint32_t 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" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| "}\n"); |
| std::string tessellationControlTemplate("${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT_ARR}\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" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT}\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 uint32_t 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" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT_ARR}\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 uint32_t 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 |
| { |
| bool isGL45 = glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5)); |
| std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n" |
| "${GLSL_PER_VERTEX_OUT}\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" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PRECISE_PER_VERTEX_OUT}\n" |
| "\n" + |
| getTessellationEvaluationInLayoutString(primitiveType, spacing) + |
| "\n" |
| "in highp vec2 in_te_position[];\n" |
| "\n" + |
| (isGL45 ? "" : "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" |
| "${GLSL_PER_VERTEX_OUT}\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" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT}\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" |
| " + (1.0-gl_TessCoord.x)*( gl_TessCoord.y)*corner2\n" |
| " + ( gl_TessCoord.x)*( gl_TessCoord.y)*corner3;\n" |
| " gl_Position = vec4(pos, 0.0, 1.0);\n" |
| " highp int phaseX = int(round((0.5 - abs(gl_TessCoord.x-0.5)) * u_tessLevelInner0));\n" |
| " highp int phaseY = int(round((0.5 - abs(gl_TessCoord.y-0.5)) * u_tessLevelInner1));\n" |
| " highp int phase = min(phaseX, phaseY) % 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" : |
| DE_NULL) + |
| "}\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"); |
| |
| 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())); |
| } |
| }; |
| |
| // Basic isolines rendering case. |
| class IsolinesRenderCase : public BasicVariousTessLevelsPosAttrCase |
| { |
| public: |
| IsolinesRenderCase(Context &context, const char *name, const char *description, SpacingMode spacing, |
| const char *referenceImagePathPrefix) |
| : BasicVariousTessLevelsPosAttrCase(context, name, description, TESSPRIMITIVETYPE_ISOLINES, spacing, |
| referenceImagePathPrefix) |
| { |
| } |
| |
| protected: |
| const glu::ProgramSources makeSources(TessPrimitiveType primitiveType, SpacingMode spacing, |
| const char *vtxOutPosAttrName) const |
| { |
| DE_ASSERT(primitiveType == TESSPRIMITIVETYPE_ISOLINES); |
| DE_UNREF(primitiveType); |
| |
| std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n" |
| "${GLSL_PER_VERTEX_OUT}\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" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "\n" + |
| getTessellationEvaluationInLayoutString(TESSPRIMITIVETYPE_ISOLINES, spacing) + |
| "\n" |
| "in highp vec2 in_te_position[];\n" |
| "\n" |
| "out mediump vec4 in_f_color;\n" |
| "\n" |
| "uniform mediump float u_tessLevelOuter0;\n" |
| "uniform mediump float u_tessLevelOuter1;\n" |
| "\n" |
| "void main (void)\n" |
| "{\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 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" |
| " pos.y += 0.15*sin(gl_TessCoord.x*10.0);\n" |
| " gl_Position = vec4(pos, 0.0, 1.0);\n" |
| " highp int phaseX = int(round(gl_TessCoord.x*u_tessLevelOuter1));\n" |
| " highp int phaseY = int(round(gl_TessCoord.y*u_tessLevelOuter0));\n" |
| " highp int phase = (phaseX + phaseY) % 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" |
| "}\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"); |
| |
| 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())); |
| } |
| }; |
| |
| // Test the "cw" and "ccw" TES input layout qualifiers. |
| class WindingCase : public TestCase |
| { |
| public: |
| WindingCase(Context &context, const char *name, const char *description, TessPrimitiveType primitiveType, |
| Winding winding) |
| : TestCase(context, name, description) |
| , m_primitiveType(primitiveType) |
| , m_winding(winding) |
| { |
| DE_ASSERT(primitiveType == TESSPRIMITIVETYPE_TRIANGLES || primitiveType == TESSPRIMITIVETYPE_QUADS); |
| } |
| |
| void init(void); |
| void deinit(void); |
| IterateResult iterate(void); |
| |
| private: |
| static const int RENDER_SIZE = 64; |
| |
| const TessPrimitiveType m_primitiveType; |
| const Winding m_winding; |
| |
| SharedPtr<const ShaderProgram> m_program; |
| }; |
| |
| void WindingCase::init(void) |
| { |
| checkTessellationSupport(m_context); |
| checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE); |
| |
| std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| "}\n"); |
| std::string tessellationControlTemplate("${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT_ARR}\n" |
| "\n" |
| "layout (vertices = 1) out;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " gl_TessLevelInner[0] = 5.0;\n" |
| " gl_TessLevelInner[1] = 5.0;\n" |
| "\n" |
| " gl_TessLevelOuter[0] = 5.0;\n" |
| " gl_TessLevelOuter[1] = 5.0;\n" |
| " gl_TessLevelOuter[2] = 5.0;\n" |
| " gl_TessLevelOuter[3] = 5.0;\n" |
| "}\n"); |
| std::string tessellationEvaluationTemplate("${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "\n" + |
| getTessellationEvaluationInLayoutString(m_primitiveType, m_winding) + |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " gl_Position = vec4(gl_TessCoord.xy*2.0 - 1.0, 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())))); |
| |
| m_testCtx.getLog() << *m_program; |
| if (!m_program->isOk()) |
| TCU_FAIL("Program compilation failed"); |
| } |
| |
| void WindingCase::deinit(void) |
| { |
| m_program.clear(); |
| } |
| |
| WindingCase::IterateResult WindingCase::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 uint32_t programGL = m_program->getProgram(); |
| const glw::Functions &gl = renderCtx.getFunctions(); |
| const glu::VertexArray vao(renderCtx); |
| |
| bool success = true; |
| |
| setViewport(gl, viewport); |
| gl.clearColor(1.0f, 0.0f, 0.0f, 1.0f); |
| gl.useProgram(programGL); |
| |
| gl.patchParameteri(GL_PATCH_VERTICES, 1); |
| |
| gl.enable(GL_CULL_FACE); |
| |
| gl.bindVertexArray(*vao); |
| |
| log << TestLog::Message << "Face culling enabled" << TestLog::EndMessage; |
| |
| for (int frontFaceWinding = 0; frontFaceWinding < WINDING_LAST; frontFaceWinding++) |
| { |
| log << TestLog::Message << "Setting glFrontFace(" << (frontFaceWinding == WINDING_CW ? "GL_CW" : "GL_CCW") |
| << ")" << TestLog::EndMessage; |
| |
| gl.frontFace(frontFaceWinding == WINDING_CW ? GL_CW : GL_CCW); |
| |
| gl.clear(GL_COLOR_BUFFER_BIT); |
| gl.drawArrays(GL_PATCHES, 0, 1); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed"); |
| |
| { |
| const tcu::Surface rendered = getPixels(renderCtx, viewport); |
| log << TestLog::Image("RenderedImage", "Rendered Image", rendered); |
| |
| { |
| const int totalNumPixels = rendered.getWidth() * rendered.getHeight(); |
| const int badPixelTolerance = m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? |
| 5 * de::max(rendered.getWidth(), rendered.getHeight()) : |
| 0; |
| |
| int numWhitePixels = 0; |
| int numRedPixels = 0; |
| for (int y = 0; y < rendered.getHeight(); y++) |
| for (int x = 0; x < rendered.getWidth(); x++) |
| { |
| numWhitePixels += rendered.getPixel(x, y) == tcu::RGBA::white() ? 1 : 0; |
| numRedPixels += rendered.getPixel(x, y) == tcu::RGBA::red() ? 1 : 0; |
| } |
| |
| DE_ASSERT(numWhitePixels + numRedPixels <= totalNumPixels); |
| |
| log << TestLog::Message << "Note: got " << numWhitePixels << " white and " << numRedPixels |
| << " red pixels" << TestLog::EndMessage; |
| |
| if (totalNumPixels - numWhitePixels - numRedPixels > badPixelTolerance) |
| { |
| log << TestLog::Message << "Failure: Got " << totalNumPixels - numWhitePixels - numRedPixels |
| << " other than white or red pixels (maximum tolerance " << badPixelTolerance << ")" |
| << TestLog::EndMessage; |
| success = false; |
| break; |
| } |
| |
| if ((Winding)frontFaceWinding == m_winding) |
| { |
| if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES) |
| { |
| if (de::abs(numWhitePixels - totalNumPixels / 2) > badPixelTolerance) |
| { |
| log << TestLog::Message << "Failure: wrong number of white pixels; expected approximately " |
| << totalNumPixels / 2 << TestLog::EndMessage; |
| success = false; |
| break; |
| } |
| } |
| else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS) |
| { |
| if (numWhitePixels != totalNumPixels) |
| { |
| log << TestLog::Message << "Failure: expected only white pixels (full-viewport quad)" |
| << TestLog::EndMessage; |
| success = false; |
| break; |
| } |
| } |
| else |
| DE_ASSERT(false); |
| } |
| else |
| { |
| if (numWhitePixels != 0) |
| { |
| log << TestLog::Message << "Failure: expected only red pixels (everything culled)" |
| << TestLog::EndMessage; |
| success = false; |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| m_testCtx.setTestResult(success ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, |
| success ? "Pass" : "Image verification failed"); |
| return STOP; |
| } |
| |
| // Test potentially differing input and output patch sizes. |
| class PatchVertexCountCase : public TestCase |
| { |
| public: |
| PatchVertexCountCase(Context &context, const char *name, const char *description, int inputPatchSize, |
| int outputPatchSize, const char *referenceImagePath) |
| : TestCase(context, name, description) |
| , m_inputPatchSize(inputPatchSize) |
| , m_outputPatchSize(outputPatchSize) |
| , m_referenceImagePath(referenceImagePath) |
| { |
| } |
| |
| void init(void); |
| void deinit(void); |
| IterateResult iterate(void); |
| |
| private: |
| static const int RENDER_SIZE = 256; |
| |
| const int m_inputPatchSize; |
| const int m_outputPatchSize; |
| |
| const string m_referenceImagePath; |
| |
| SharedPtr<const ShaderProgram> m_program; |
| }; |
| |
| void PatchVertexCountCase::init(void) |
| { |
| checkTessellationSupport(m_context); |
| checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE); |
| |
| const string inSizeStr = de::toString(m_inputPatchSize); |
| const string outSizeStr = de::toString(m_outputPatchSize); |
| |
| std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "\n" |
| "in highp float in_v_attr;\n" |
| "\n" |
| "out highp float in_tc_attr;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " in_tc_attr = in_v_attr;\n" |
| "}\n"); |
| std::string tessellationControlTemplate("${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT_ARR}\n" |
| "\n" |
| "layout (vertices = " + |
| outSizeStr + |
| ") out;\n" |
| "\n" |
| "in highp float in_tc_attr[];\n" |
| "\n" |
| "out highp float in_te_attr[];\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " in_te_attr[gl_InvocationID] = in_tc_attr[gl_InvocationID*" + |
| inSizeStr + "/" + outSizeStr + |
| "];\n" |
| "\n" |
| " gl_TessLevelInner[0] = 5.0;\n" |
| " gl_TessLevelInner[1] = 5.0;\n" |
| "\n" |
| " gl_TessLevelOuter[0] = 5.0;\n" |
| " gl_TessLevelOuter[1] = 5.0;\n" |
| " gl_TessLevelOuter[2] = 5.0;\n" |
| " gl_TessLevelOuter[3] = 5.0;\n" |
| "}\n"); |
| std::string tessellationEvaluationTemplate( |
| "${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "\n" + |
| getTessellationEvaluationInLayoutString(TESSPRIMITIVETYPE_QUADS) + |
| "\n" |
| "in highp float in_te_attr[];\n" |
| "\n" |
| "out mediump vec4 in_f_color;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " highp float x = gl_TessCoord.x*2.0 - 1.0;\n" |
| " highp float y = gl_TessCoord.y - in_te_attr[int(round(gl_TessCoord.x*float(" + |
| outSizeStr + |
| "-1)))];\n" |
| " gl_Position = vec4(x, y, 0.0, 1.0);\n" |
| " in_f_color = vec4(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 PatchVertexCountCase::deinit(void) |
| { |
| m_program.clear(); |
| } |
| |
| PatchVertexCountCase::IterateResult PatchVertexCountCase::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 uint32_t programGL = m_program->getProgram(); |
| const glw::Functions &gl = renderCtx.getFunctions(); |
| |
| setViewport(gl, viewport); |
| gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f); |
| gl.useProgram(programGL); |
| |
| log << TestLog::Message << "Note: input patch size is " << m_inputPatchSize << ", output patch size is " |
| << m_outputPatchSize << TestLog::EndMessage; |
| |
| { |
| vector<float> attributeData; |
| attributeData.reserve(m_inputPatchSize); |
| |
| for (int i = 0; i < m_inputPatchSize; i++) |
| { |
| const float f = (float)i / (float)(m_inputPatchSize - 1); |
| attributeData.push_back(f * f); |
| } |
| |
| gl.patchParameteri(GL_PATCH_VERTICES, m_inputPatchSize); |
| gl.clear(GL_COLOR_BUFFER_BIT); |
| |
| const glu::VertexArrayBinding attrBindings[] = { |
| glu::va::Float("in_v_attr", 1, (int)attributeData.size(), 0, &attributeData[0])}; |
| |
| glu::draw(m_context.getRenderContext(), programGL, DE_LENGTH_OF_ARRAY(attrBindings), &attrBindings[0], |
| glu::pr::Patches(m_inputPatchSize)); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed"); |
| } |
| |
| { |
| const tcu::Surface rendered = getPixels(renderCtx, viewport); |
| const tcu::TextureLevel reference = getPNG(m_testCtx.getArchive(), m_referenceImagePath); |
| const bool success = tcu::fuzzyCompare(log, "ImageComparison", "Image Comparison", reference.getAccess(), |
| rendered.getAccess(), 0.02f, tcu::COMPARE_LOG_RESULT); |
| |
| m_testCtx.setTestResult(success ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, |
| success ? "Pass" : "Image comparison failed"); |
| return STOP; |
| } |
| } |
| |
| // Test per-patch inputs/outputs. |
| class PerPatchDataCase : public TestCase |
| { |
| public: |
| enum CaseType |
| { |
| CASETYPE_PRIMITIVE_ID_TCS = 0, |
| CASETYPE_PRIMITIVE_ID_TES, |
| CASETYPE_PATCH_VERTICES_IN_TCS, |
| CASETYPE_PATCH_VERTICES_IN_TES, |
| CASETYPE_TESS_LEVEL_INNER0_TES, |
| CASETYPE_TESS_LEVEL_INNER1_TES, |
| CASETYPE_TESS_LEVEL_OUTER0_TES, |
| CASETYPE_TESS_LEVEL_OUTER1_TES, |
| CASETYPE_TESS_LEVEL_OUTER2_TES, |
| CASETYPE_TESS_LEVEL_OUTER3_TES, |
| |
| CASETYPE_LAST |
| }; |
| |
| PerPatchDataCase(Context &context, const char *name, const char *description, CaseType caseType, |
| const char *referenceImagePath) |
| : TestCase(context, name, description) |
| , m_caseType(caseType) |
| , m_referenceImagePath(caseTypeUsesRefImageFromFile(caseType) ? referenceImagePath : "") |
| { |
| DE_ASSERT(caseTypeUsesRefImageFromFile(caseType) == (referenceImagePath != DE_NULL)); |
| } |
| |
| void init(void); |
| void deinit(void); |
| IterateResult iterate(void); |
| |
| static const char *getCaseTypeName(CaseType); |
| static const char *getCaseTypeDescription(CaseType); |
| static bool caseTypeUsesRefImageFromFile(CaseType); |
| |
| private: |
| static const int RENDER_SIZE = 256; |
| static const int INPUT_PATCH_SIZE; |
| static const int OUTPUT_PATCH_SIZE; |
| |
| const CaseType m_caseType; |
| const string m_referenceImagePath; |
| |
| SharedPtr<const ShaderProgram> m_program; |
| }; |
| |
| const int PerPatchDataCase::INPUT_PATCH_SIZE = 10; |
| const int PerPatchDataCase::OUTPUT_PATCH_SIZE = 5; |
| |
| const char *PerPatchDataCase::getCaseTypeName(CaseType type) |
| { |
| switch (type) |
| { |
| case CASETYPE_PRIMITIVE_ID_TCS: |
| return "primitive_id_tcs"; |
| case CASETYPE_PRIMITIVE_ID_TES: |
| return "primitive_id_tes"; |
| case CASETYPE_PATCH_VERTICES_IN_TCS: |
| return "patch_vertices_in_tcs"; |
| case CASETYPE_PATCH_VERTICES_IN_TES: |
| return "patch_vertices_in_tes"; |
| case CASETYPE_TESS_LEVEL_INNER0_TES: |
| return "tess_level_inner_0_tes"; |
| case CASETYPE_TESS_LEVEL_INNER1_TES: |
| return "tess_level_inner_1_tes"; |
| case CASETYPE_TESS_LEVEL_OUTER0_TES: |
| return "tess_level_outer_0_tes"; |
| case CASETYPE_TESS_LEVEL_OUTER1_TES: |
| return "tess_level_outer_1_tes"; |
| case CASETYPE_TESS_LEVEL_OUTER2_TES: |
| return "tess_level_outer_2_tes"; |
| case CASETYPE_TESS_LEVEL_OUTER3_TES: |
| return "tess_level_outer_3_tes"; |
| default: |
| DE_ASSERT(false); |
| return DE_NULL; |
| } |
| } |
| |
| const char *PerPatchDataCase::getCaseTypeDescription(CaseType type) |
| { |
| switch (type) |
| { |
| case CASETYPE_PRIMITIVE_ID_TCS: |
| return "Read gl_PrimitiveID in TCS and pass it as patch output to TES"; |
| case CASETYPE_PRIMITIVE_ID_TES: |
| return "Read gl_PrimitiveID in TES"; |
| case CASETYPE_PATCH_VERTICES_IN_TCS: |
| return "Read gl_PatchVerticesIn in TCS and pass it as patch output to TES"; |
| case CASETYPE_PATCH_VERTICES_IN_TES: |
| return "Read gl_PatchVerticesIn in TES"; |
| case CASETYPE_TESS_LEVEL_INNER0_TES: |
| return "Read gl_TessLevelInner[0] in TES"; |
| case CASETYPE_TESS_LEVEL_INNER1_TES: |
| return "Read gl_TessLevelInner[1] in TES"; |
| case CASETYPE_TESS_LEVEL_OUTER0_TES: |
| return "Read gl_TessLevelOuter[0] in TES"; |
| case CASETYPE_TESS_LEVEL_OUTER1_TES: |
| return "Read gl_TessLevelOuter[1] in TES"; |
| case CASETYPE_TESS_LEVEL_OUTER2_TES: |
| return "Read gl_TessLevelOuter[2] in TES"; |
| case CASETYPE_TESS_LEVEL_OUTER3_TES: |
| return "Read gl_TessLevelOuter[3] in TES"; |
| default: |
| DE_ASSERT(false); |
| return DE_NULL; |
| } |
| } |
| |
| bool PerPatchDataCase::caseTypeUsesRefImageFromFile(CaseType type) |
| { |
| switch (type) |
| { |
| case CASETYPE_PRIMITIVE_ID_TCS: |
| case CASETYPE_PRIMITIVE_ID_TES: |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| void PerPatchDataCase::init(void) |
| { |
| checkTessellationSupport(m_context); |
| checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE); |
| |
| DE_ASSERT(OUTPUT_PATCH_SIZE < INPUT_PATCH_SIZE); |
| |
| const string inSizeStr = de::toString(INPUT_PATCH_SIZE); |
| const string outSizeStr = de::toString(OUTPUT_PATCH_SIZE); |
| |
| std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "\n" |
| "in highp float in_v_attr;\n" |
| "\n" |
| "out highp float in_tc_attr;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " in_tc_attr = in_v_attr;\n" |
| "}\n"); |
| std::string tessellationControlTemplate( |
| "${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT_ARR}\n" |
| "\n" |
| "layout (vertices = " + |
| outSizeStr + |
| ") out;\n" |
| "\n" |
| "in highp float in_tc_attr[];\n" |
| "\n" |
| "out highp float in_te_attr[];\n" + |
| (m_caseType == CASETYPE_PRIMITIVE_ID_TCS ? "patch out mediump int in_te_primitiveIDFromTCS;\n" : |
| m_caseType == CASETYPE_PATCH_VERTICES_IN_TCS ? "patch out mediump int in_te_patchVerticesInFromTCS;\n" : |
| "") + |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " in_te_attr[gl_InvocationID] = in_tc_attr[gl_InvocationID];\n" + |
| (m_caseType == CASETYPE_PRIMITIVE_ID_TCS ? "\tin_te_primitiveIDFromTCS = gl_PrimitiveID;\n" : |
| m_caseType == CASETYPE_PATCH_VERTICES_IN_TCS ? "\tin_te_patchVerticesInFromTCS = gl_PatchVerticesIn;\n" : |
| "") + |
| "\n" |
| " gl_TessLevelInner[0] = 9.0;\n" |
| " gl_TessLevelInner[1] = 8.0;\n" |
| "\n" |
| " gl_TessLevelOuter[0] = 7.0;\n" |
| " gl_TessLevelOuter[1] = 6.0;\n" |
| " gl_TessLevelOuter[2] = 5.0;\n" |
| " gl_TessLevelOuter[3] = 4.0;\n" |
| "}\n"); |
| std::string tessellationEvaluationTemplate( |
| "${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "\n" + |
| getTessellationEvaluationInLayoutString(TESSPRIMITIVETYPE_QUADS) + |
| "\n" |
| "in highp float in_te_attr[];\n" + |
| (m_caseType == CASETYPE_PRIMITIVE_ID_TCS ? "patch in mediump int in_te_primitiveIDFromTCS;\n" : |
| m_caseType == CASETYPE_PATCH_VERTICES_IN_TCS ? "patch in mediump int in_te_patchVerticesInFromTCS;\n" : |
| string()) + |
| "\n" |
| "out mediump vec4 in_f_color;\n" |
| "\n" |
| "uniform highp float u_xScale;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " highp float x = (gl_TessCoord.x*u_xScale + in_te_attr[0]) * 2.0 - 1.0;\n" |
| " highp float y = gl_TessCoord.y*2.0 - 1.0;\n" |
| " gl_Position = vec4(x, y, 0.0, 1.0);\n" + |
| (m_caseType == CASETYPE_PRIMITIVE_ID_TCS ? "\tbool ok = in_te_primitiveIDFromTCS == 3;\n" : |
| m_caseType == CASETYPE_PRIMITIVE_ID_TES ? "\tbool ok = gl_PrimitiveID == 3;\n" : |
| m_caseType == CASETYPE_PATCH_VERTICES_IN_TCS ? |
| "\tbool ok = in_te_patchVerticesInFromTCS == " + inSizeStr + ";\n" : |
| m_caseType == CASETYPE_PATCH_VERTICES_IN_TES ? "\tbool ok = gl_PatchVerticesIn == " + outSizeStr + ";\n" : |
| m_caseType == CASETYPE_TESS_LEVEL_INNER0_TES ? "\tbool ok = abs(gl_TessLevelInner[0] - 9.0) < 0.1f;\n" : |
| m_caseType == CASETYPE_TESS_LEVEL_INNER1_TES ? "\tbool ok = abs(gl_TessLevelInner[1] - 8.0) < 0.1f;\n" : |
| m_caseType == CASETYPE_TESS_LEVEL_OUTER0_TES ? "\tbool ok = abs(gl_TessLevelOuter[0] - 7.0) < 0.1f;\n" : |
| m_caseType == CASETYPE_TESS_LEVEL_OUTER1_TES ? "\tbool ok = abs(gl_TessLevelOuter[1] - 6.0) < 0.1f;\n" : |
| m_caseType == CASETYPE_TESS_LEVEL_OUTER2_TES ? "\tbool ok = abs(gl_TessLevelOuter[2] - 5.0) < 0.1f;\n" : |
| m_caseType == CASETYPE_TESS_LEVEL_OUTER3_TES ? "\tbool ok = abs(gl_TessLevelOuter[3] - 4.0) < 0.1f;\n" : |
| DE_NULL) + |
| " in_f_color = ok ? vec4(1.0) : vec4(vec3(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 PerPatchDataCase::deinit(void) |
| { |
| m_program.clear(); |
| } |
| |
| PerPatchDataCase::IterateResult PerPatchDataCase::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 uint32_t programGL = m_program->getProgram(); |
| const glw::Functions &gl = renderCtx.getFunctions(); |
| |
| setViewport(gl, viewport); |
| gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f); |
| gl.useProgram(programGL); |
| |
| log << TestLog::Message << "Note: input patch size is " << INPUT_PATCH_SIZE << ", output patch size is " |
| << OUTPUT_PATCH_SIZE << TestLog::EndMessage; |
| |
| { |
| const int numPrimitives = |
| m_caseType == CASETYPE_PRIMITIVE_ID_TCS || m_caseType == CASETYPE_PRIMITIVE_ID_TES ? 8 : 1; |
| |
| vector<float> attributeData; |
| attributeData.reserve(numPrimitives * INPUT_PATCH_SIZE); |
| |
| for (int i = 0; i < numPrimitives; i++) |
| { |
| attributeData.push_back((float)i / (float)numPrimitives); |
| for (int j = 0; j < INPUT_PATCH_SIZE - 1; j++) |
| attributeData.push_back(0.0f); |
| } |
| |
| gl.patchParameteri(GL_PATCH_VERTICES, INPUT_PATCH_SIZE); |
| gl.clear(GL_COLOR_BUFFER_BIT); |
| |
| gl.uniform1f(gl.getUniformLocation(programGL, "u_xScale"), 1.0f / (float)numPrimitives); |
| |
| const glu::VertexArrayBinding attrBindings[] = { |
| glu::va::Float("in_v_attr", 1, (int)attributeData.size(), 0, &attributeData[0])}; |
| |
| glu::draw(m_context.getRenderContext(), programGL, DE_LENGTH_OF_ARRAY(attrBindings), &attrBindings[0], |
| glu::pr::Patches(numPrimitives * INPUT_PATCH_SIZE)); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed"); |
| } |
| |
| { |
| const tcu::Surface rendered = getPixels(renderCtx, viewport); |
| |
| if (m_caseType == CASETYPE_PRIMITIVE_ID_TCS || m_caseType == CASETYPE_PRIMITIVE_ID_TES) |
| { |
| DE_ASSERT(caseTypeUsesRefImageFromFile(m_caseType)); |
| |
| const tcu::TextureLevel reference = getPNG(m_testCtx.getArchive(), m_referenceImagePath); |
| const bool success = tcu::fuzzyCompare(log, "ImageComparison", "Image Comparison", reference.getAccess(), |
| rendered.getAccess(), 0.02f, tcu::COMPARE_LOG_RESULT); |
| |
| m_testCtx.setTestResult(success ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, |
| success ? "Pass" : "Image comparison failed"); |
| return STOP; |
| } |
| else |
| { |
| DE_ASSERT(!caseTypeUsesRefImageFromFile(m_caseType)); |
| |
| log << TestLog::Image("RenderedImage", "Rendered Image", rendered); |
| |
| for (int y = 0; y < rendered.getHeight(); y++) |
| for (int x = 0; x < rendered.getWidth(); x++) |
| { |
| if (rendered.getPixel(x, y) != tcu::RGBA::white()) |
| { |
| log << TestLog::Message << "Failure: expected all white pixels" << TestLog::EndMessage; |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed"); |
| return STOP; |
| } |
| } |
| |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| return STOP; |
| } |
| } |
| } |
| |
| // Basic barrier() usage in TCS. |
| class BarrierCase : public TestCase |
| { |
| public: |
| BarrierCase(Context &context, const char *name, const char *description, const char *referenceImagePath) |
| : TestCase(context, name, description) |
| , m_referenceImagePath(referenceImagePath) |
| { |
| } |
| |
| void init(void); |
| void deinit(void); |
| IterateResult iterate(void); |
| |
| private: |
| static const int RENDER_SIZE = 256; |
| static const int NUM_VERTICES; |
| |
| const string m_referenceImagePath; |
| |
| SharedPtr<const ShaderProgram> m_program; |
| }; |
| |
| const int BarrierCase::NUM_VERTICES = 32; |
| |
| void BarrierCase::init(void) |
| { |
| checkTessellationSupport(m_context); |
| checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE); |
| |
| const string numVertsStr = de::toString(NUM_VERTICES); |
| |
| std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "\n" |
| "in highp float in_v_attr;\n" |
| "\n" |
| "out highp float in_tc_attr;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " in_tc_attr = in_v_attr;\n" |
| "}\n"); |
| std::string tessellationControlTemplate("${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT_ARR}\n" |
| "\n" |
| "layout (vertices = " + |
| numVertsStr + |
| ") out;\n" |
| "\n" |
| "in highp float in_tc_attr[];\n" |
| "\n" |
| "out highp float in_te_attr[];\n" |
| "patch out highp float in_te_patchAttr;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " in_te_attr[gl_InvocationID] = in_tc_attr[gl_InvocationID];\n" |
| " in_te_patchAttr = 0.0f;\n" |
| " barrier();\n" |
| " if (gl_InvocationID == 5)\n" |
| " in_te_patchAttr = float(gl_InvocationID)*0.1;\n" |
| " barrier();\n" |
| " highp float temp = in_te_patchAttr + in_te_attr[gl_InvocationID];\n" |
| " barrier();\n" |
| " if (gl_InvocationID == " + |
| numVertsStr + |
| "-1)\n" |
| " in_te_patchAttr = float(gl_InvocationID);\n" |
| " barrier();\n" |
| " in_te_attr[gl_InvocationID] = temp;\n" |
| " barrier();\n" |
| " temp = temp + in_te_attr[(gl_InvocationID+1) % " + |
| numVertsStr + |
| "];\n" |
| " barrier();\n" |
| " in_te_attr[gl_InvocationID] = 0.25*temp;\n" |
| "\n" |
| " gl_TessLevelInner[0] = 32.0;\n" |
| " gl_TessLevelInner[1] = 32.0;\n" |
| "\n" |
| " gl_TessLevelOuter[0] = 32.0;\n" |
| " gl_TessLevelOuter[1] = 32.0;\n" |
| " gl_TessLevelOuter[2] = 32.0;\n" |
| " gl_TessLevelOuter[3] = 32.0;\n" |
| "}\n"); |
| std::string tessellationEvaluationTemplate( |
| "${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "\n" + |
| getTessellationEvaluationInLayoutString(TESSPRIMITIVETYPE_QUADS) + |
| "\n" |
| "in highp float in_te_attr[];\n" |
| "patch in highp float in_te_patchAttr;\n" |
| "\n" |
| "out highp float in_f_blue;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " highp float x = gl_TessCoord.x*2.0 - 1.0;\n" |
| " highp float y = gl_TessCoord.y - in_te_attr[int(round(gl_TessCoord.x*float(" + |
| numVertsStr + |
| "-1)))];\n" |
| " gl_Position = vec4(x, y, 0.0, 1.0);\n" |
| " in_f_blue = abs(in_te_patchAttr - float(" + |
| numVertsStr + |
| "-1));\n" |
| "}\n"); |
| std::string fragmentShaderTemplate("${GLSL_VERSION_DECL}\n" |
| "\n" |
| "layout (location = 0) out mediump vec4 o_color;\n" |
| "\n" |
| "in highp float in_f_blue;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " o_color = vec4(1.0, 0.0, in_f_blue, 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())))); |
| |
| m_testCtx.getLog() << *m_program; |
| if (!m_program->isOk()) |
| TCU_FAIL("Program compilation failed"); |
| } |
| |
| void BarrierCase::deinit(void) |
| { |
| m_program.clear(); |
| } |
| |
| BarrierCase::IterateResult BarrierCase::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 uint32_t programGL = m_program->getProgram(); |
| const glw::Functions &gl = renderCtx.getFunctions(); |
| |
| setViewport(gl, viewport); |
| gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f); |
| gl.useProgram(programGL); |
| |
| { |
| vector<float> attributeData(NUM_VERTICES); |
| |
| for (int i = 0; i < NUM_VERTICES; i++) |
| attributeData[i] = (float)i / (float)(NUM_VERTICES - 1); |
| |
| gl.patchParameteri(GL_PATCH_VERTICES, NUM_VERTICES); |
| gl.clear(GL_COLOR_BUFFER_BIT); |
| |
| const glu::VertexArrayBinding attrBindings[] = { |
| glu::va::Float("in_v_attr", 1, (int)attributeData.size(), 0, &attributeData[0])}; |
| |
| glu::draw(m_context.getRenderContext(), programGL, DE_LENGTH_OF_ARRAY(attrBindings), &attrBindings[0], |
| glu::pr::Patches(NUM_VERTICES)); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed"); |
| } |
| |
| { |
| const tcu::Surface rendered = getPixels(renderCtx, viewport); |
| const tcu::TextureLevel reference = getPNG(m_testCtx.getArchive(), m_referenceImagePath); |
| const bool success = tcu::fuzzyCompare(log, "ImageComparison", "Image Comparison", reference.getAccess(), |
| rendered.getAccess(), 0.02f, tcu::COMPARE_LOG_RESULT); |
| |
| m_testCtx.setTestResult(success ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, |
| success ? "Pass" : "Image comparison failed"); |
| return STOP; |
| } |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Base class for testing invariance of entire primitive set |
| * |
| * Draws two patches with identical tessellation levels and compares the |
| * results. Repeats the same with other programs that are only different |
| * in irrelevant ways; compares the results between these two programs. |
| * Also potentially compares to results produced by different tessellation |
| * levels (see e.g. invariance rule #6). |
| * Furthermore, repeats the above with multiple different tessellation |
| * value sets. |
| * |
| * The manner of primitive set comparison is defined by subclass. E.g. |
| * case for invariance rule #1 tests that same vertices come out, in same |
| * order; rule #5 only requires that the same triangles are output, but |
| * not necessarily in the same order. |
| *//*--------------------------------------------------------------------*/ |
| class PrimitiveSetInvarianceCase : public TestCase |
| { |
| public: |
| enum WindingUsage |
| { |
| WINDINGUSAGE_CCW = 0, |
| WINDINGUSAGE_CW, |
| WINDINGUSAGE_VARY, |
| |
| WINDINGUSAGE_LAST |
| }; |
| |
| PrimitiveSetInvarianceCase(Context &context, const char *name, const char *description, TessPrimitiveType primType, |
| SpacingMode spacing, bool usePointMode, WindingUsage windingUsage) |
| : TestCase(context, name, description) |
| , m_primitiveType(primType) |
| , m_spacing(spacing) |
| , m_usePointMode(usePointMode) |
| , m_windingUsage(windingUsage) |
| { |
| } |
| |
| void init(void); |
| void deinit(void); |
| IterateResult iterate(void); |
| |
| protected: |
| struct TessLevels |
| { |
| float inner[2]; |
| float outer[4]; |
| string description(void) const |
| { |
| return tessellationLevelsString(&inner[0], &outer[0]); |
| } |
| }; |
| struct LevelCase |
| { |
| vector<TessLevels> levels; |
| int mem; //!< Subclass-defined arbitrary piece of data, for type of the levelcase, if needed. Passed to compare(). |
| LevelCase(const TessLevels &lev) : levels(vector<TessLevels>(1, lev)), mem(0) |
| { |
| } |
| LevelCase(void) : mem(0) |
| { |
| } |
| }; |
| |
| virtual vector<LevelCase> genTessLevelCases(void) const; |
| virtual bool compare(const vector<Vec3> &coordsA, const vector<Vec3> &coordsB, int levelCaseMem) const = 0; |
| |
| const TessPrimitiveType m_primitiveType; |
| |
| private: |
| struct Program |
| { |
| Winding winding; |
| SharedPtr<const ShaderProgram> program; |
| |
| Program(Winding w, const SharedPtr<const ShaderProgram> &prog) : winding(w), program(prog) |
| { |
| } |
| |
| string description(void) const |
| { |
| return string() + "winding mode " + getWindingShaderName(winding); |
| }; |
| }; |
| |
| static const int RENDER_SIZE = 16; |
| |
| const SpacingMode m_spacing; |
| const bool m_usePointMode; |
| const WindingUsage m_windingUsage; |
| |
| vector<Program> m_programs; |
| }; |
| |
| vector<PrimitiveSetInvarianceCase::LevelCase> PrimitiveSetInvarianceCase::genTessLevelCases(void) const |
| { |
| static const TessLevels basicTessLevelCases[] = { |
| {{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}}}; |
| |
| vector<LevelCase> result; |
| for (int i = 0; i < DE_LENGTH_OF_ARRAY(basicTessLevelCases); i++) |
| result.push_back(LevelCase(basicTessLevelCases[i])); |
| |
| { |
| de::Random rnd(123); |
| for (int i = 0; i < 10; i++) |
| { |
| TessLevels levels; |
| for (int j = 0; j < DE_LENGTH_OF_ARRAY(levels.inner); j++) |
| levels.inner[j] = rnd.getFloat(1.0f, 16.0f); |
| for (int j = 0; j < DE_LENGTH_OF_ARRAY(levels.outer); j++) |
| levels.outer[j] = rnd.getFloat(1.0f, 16.0f); |
| result.push_back(LevelCase(levels)); |
| } |
| } |
| |
| return result; |
| } |
| |
| void PrimitiveSetInvarianceCase::init(void) |
| { |
| const int numDifferentConstantExprCases = 2; |
| vector<Winding> windings; |
| switch (m_windingUsage) |
| { |
| case WINDINGUSAGE_CCW: |
| windings.push_back(WINDING_CCW); |
| break; |
| case WINDINGUSAGE_CW: |
| windings.push_back(WINDING_CW); |
| break; |
| case WINDINGUSAGE_VARY: |
| windings.push_back(WINDING_CCW); |
| windings.push_back(WINDING_CW); |
| break; |
| default: |
| DE_ASSERT(false); |
| } |
| |
| checkTessellationSupport(m_context); |
| checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE); |
| |
| for (int constantExprCaseNdx = 0; constantExprCaseNdx < numDifferentConstantExprCases; constantExprCaseNdx++) |
| { |
| for (int windingCaseNdx = 0; windingCaseNdx < (int)windings.size(); windingCaseNdx++) |
| { |
| const string floatLit01 = de::floatToString(10.0f / (float)(constantExprCaseNdx + 10), 2); |
| const int programNdx = (int)m_programs.size(); |
| |
| std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "\n" |
| "in highp float in_v_attr;\n" |
| "out highp float in_tc_attr;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " in_tc_attr = in_v_attr;\n" |
| "}\n"); |
| std::string tessellationControlTemplate("${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT_ARR}\n" |
| "\n" |
| "layout (vertices = " + |
| de::toString(constantExprCaseNdx + 1) + |
| ") out;\n" |
| "\n" |
| "in highp float in_tc_attr[];\n" |
| "\n" |
| "patch out highp float in_te_positionOffset;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " in_te_positionOffset = in_tc_attr[6];\n" |
| "\n" |
| " gl_TessLevelInner[0] = in_tc_attr[0];\n" |
| " gl_TessLevelInner[1] = in_tc_attr[1];\n" |
| "\n" |
| " gl_TessLevelOuter[0] = in_tc_attr[2];\n" |
| " gl_TessLevelOuter[1] = in_tc_attr[3];\n" |
| " gl_TessLevelOuter[2] = in_tc_attr[4];\n" |
| " gl_TessLevelOuter[3] = in_tc_attr[5];\n" |
| "}\n"); |
| std::string tessellationEvaluationTemplate( |
| "${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "\n" + |
| getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing, windings[windingCaseNdx], |
| m_usePointMode) + |
| "\n" |
| "patch in highp float in_te_positionOffset;\n" |
| "\n" |
| "out highp vec4 in_f_color;\n" |
| "invariant out highp vec3 out_te_tessCoord;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " gl_Position = vec4(gl_TessCoord.xy*" + |
| floatLit01 + |
| " - in_te_positionOffset + float(gl_PrimitiveID)*0.1, 0.0, 1.0);\n" |
| " in_f_color = vec4(" + |
| floatLit01 + |
| ");\n" |
| " out_te_tessCoord = gl_TessCoord;\n" |
| "}\n"); |
| std::string fragmentShaderTemplate("${GLSL_VERSION_DECL}\n" |
| "\n" |
| "layout (location = 0) out mediump vec4 o_color;\n" |
| "\n" |
| "in highp vec4 in_f_color;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " o_color = in_f_color;\n" |
| "}\n"); |
| |
| m_programs.push_back( |
| Program(windings[windingCaseNdx], |
| 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))))); |
| |
| { |
| const tcu::ScopedLogSection section(m_testCtx.getLog(), "Program" + de::toString(programNdx), |
| "Program " + de::toString(programNdx)); |
| |
| if (programNdx == 0 || !m_programs.back().program->isOk()) |
| m_testCtx.getLog() << *m_programs.back().program; |
| |
| if (!m_programs.back().program->isOk()) |
| TCU_FAIL("Program compilation failed"); |
| |
| if (programNdx > 0) |
| m_testCtx.getLog() << TestLog::Message << "Note: program " << programNdx |
| << " is similar to above, except some constants are different, and: " |
| << m_programs.back().description() << TestLog::EndMessage; |
| } |
| } |
| } |
| } |
| |
| void PrimitiveSetInvarianceCase::deinit(void) |
| { |
| m_programs.clear(); |
| } |
| |
| PrimitiveSetInvarianceCase::IterateResult PrimitiveSetInvarianceCase::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 glw::Functions &gl = renderCtx.getFunctions(); |
| const vector<LevelCase> tessLevelCases = genTessLevelCases(); |
| vector<vector<int>> primitiveCounts; |
| int maxNumPrimitives = -1; |
| |
| for (int caseNdx = 0; caseNdx < (int)tessLevelCases.size(); caseNdx++) |
| { |
| primitiveCounts.push_back(vector<int>()); |
| for (int i = 0; i < (int)tessLevelCases[caseNdx].levels.size(); i++) |
| { |
| const int primCount = referencePrimitiveCount(m_primitiveType, m_spacing, m_usePointMode, |
| &tessLevelCases[caseNdx].levels[i].inner[0], |
| &tessLevelCases[caseNdx].levels[i].outer[0]); |
| primitiveCounts.back().push_back(primCount); |
| maxNumPrimitives = de::max(maxNumPrimitives, primCount); |
| } |
| } |
| |
| const uint32_t primitiveTypeGL = outputPrimitiveTypeGL(m_primitiveType, m_usePointMode); |
| const TFHandler transformFeedback(m_context.getRenderContext(), |
| 2 * maxNumPrimitives * numVerticesPerPrimitive(primitiveTypeGL)); |
| |
| setViewport(gl, viewport); |
| gl.patchParameteri(GL_PATCH_VERTICES, 7); |
| |
| for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < (int)tessLevelCases.size(); tessLevelCaseNdx++) |
| { |
| const LevelCase &levelCase = tessLevelCases[tessLevelCaseNdx]; |
| vector<Vec3> firstPrimVertices; |
| |
| { |
| string tessLevelsStr; |
| for (int i = 0; i < (int)levelCase.levels.size(); i++) |
| tessLevelsStr += (levelCase.levels.size() > 1 ? "\n" : "") + levelCase.levels[i].description(); |
| log << TestLog::Message << "Tessellation level sets: " << tessLevelsStr << TestLog::EndMessage; |
| } |
| |
| for (int subTessLevelCaseNdx = 0; subTessLevelCaseNdx < (int)levelCase.levels.size(); subTessLevelCaseNdx++) |
| { |
| const TessLevels &tessLevels = levelCase.levels[subTessLevelCaseNdx]; |
| const float(&inner)[2] = tessLevels.inner; |
| const float(&outer)[4] = tessLevels.outer; |
| const float attribute[2 * 7] = {inner[0], inner[1], outer[0], outer[1], outer[2], outer[3], 0.0f, |
| inner[0], inner[1], outer[0], outer[1], outer[2], outer[3], 0.5f}; |
| const glu::VertexArrayBinding bindings[] = { |
| glu::va::Float("in_v_attr", 1, DE_LENGTH_OF_ARRAY(attribute), 0, &attribute[0])}; |
| |
| for (int programNdx = 0; programNdx < (int)m_programs.size(); programNdx++) |
| { |
| const uint32_t programGL = m_programs[programNdx].program->getProgram(); |
| gl.useProgram(programGL); |
| const TFHandler::Result tfResult = |
| transformFeedback.renderAndGetPrimitives(programGL, primitiveTypeGL, DE_LENGTH_OF_ARRAY(bindings), |
| &bindings[0], DE_LENGTH_OF_ARRAY(attribute)); |
| |
| if (tfResult.numPrimitives != 2 * primitiveCounts[tessLevelCaseNdx][subTessLevelCaseNdx]) |
| { |
| log << TestLog::Message << "Failure: GL reported GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN to be " |
| << tfResult.numPrimitives << ", reference value is " |
| << 2 * primitiveCounts[tessLevelCaseNdx][subTessLevelCaseNdx] << TestLog::EndMessage; |
| |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of primitives"); |
| return STOP; |
| } |
| |
| { |
| const int half = (int)tfResult.varying.size() / 2; |
| const vector<Vec3> prim0Vertices = |
| vector<Vec3>(tfResult.varying.begin(), tfResult.varying.begin() + half); |
| const Vec3 *const prim1Vertices = &tfResult.varying[half]; |
| |
| for (int vtxNdx = 0; vtxNdx < (int)prim0Vertices.size(); vtxNdx++) |
| { |
| if (prim0Vertices[vtxNdx] != prim1Vertices[vtxNdx]) |
| { |
| log << TestLog::Message << "Failure: tessellation coordinate at index " << vtxNdx |
| << " differs between two primitives drawn in one draw call" << TestLog::EndMessage |
| << TestLog::Message << "Note: the coordinate is " << prim0Vertices[vtxNdx] |
| << " for the first primitive and " << prim1Vertices[vtxNdx] << " for the second" |
| << TestLog::EndMessage << TestLog::Message |
| << "Note: tessellation levels for both primitives were: " |
| << tessellationLevelsString(&inner[0], &outer[0]) << TestLog::EndMessage; |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of primitives"); |
| return STOP; |
| } |
| } |
| |
| if (programNdx == 0 && subTessLevelCaseNdx == 0) |
| firstPrimVertices = prim0Vertices; |
| else |
| { |
| const bool compareOk = compare(firstPrimVertices, prim0Vertices, levelCase.mem); |
| if (!compareOk) |
| { |
| log << TestLog::Message |
| << "Note: comparison of tessellation coordinates failed; comparison was made between " |
| "following cases:\n" |
| << " - case A: program 0, tessellation levels: " |
| << tessLevelCases[tessLevelCaseNdx].levels[0].description() << "\n" |
| << " - case B: program " << programNdx |
| << ", tessellation levels: " << tessLevels.description() << TestLog::EndMessage; |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of primitives"); |
| return STOP; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| return STOP; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Test invariance rule #1 |
| * |
| * Test that the sequence of primitives input to the TES only depends on |
| * the tessellation levels, tessellation mode, spacing mode, winding, and |
| * point mode. |
| *//*--------------------------------------------------------------------*/ |
| class InvariantPrimitiveSetCase : public PrimitiveSetInvarianceCase |
| { |
| public: |
| InvariantPrimitiveSetCase(Context &context, const char *name, const char *description, TessPrimitiveType primType, |
| SpacingMode spacing, Winding winding, bool usePointMode) |
| : PrimitiveSetInvarianceCase(context, name, description, primType, spacing, usePointMode, |
| winding == WINDING_CCW ? WINDINGUSAGE_CCW : |
| winding == WINDING_CW ? WINDINGUSAGE_CW : |
| WINDINGUSAGE_LAST) |
| { |
| } |
| |
| protected: |
| virtual bool compare(const vector<Vec3> &coordsA, const vector<Vec3> &coordsB, int) const |
| { |
| for (int vtxNdx = 0; vtxNdx < (int)coordsA.size(); vtxNdx++) |
| { |
| if (coordsA[vtxNdx] != coordsB[vtxNdx]) |
| { |
| m_testCtx.getLog() << TestLog::Message << "Failure: tessellation coordinate at index " << vtxNdx |
| << " differs between two programs" << TestLog::EndMessage << TestLog::Message |
| << "Note: the coordinate is " << coordsA[vtxNdx] << " for the first program and " |
| << coordsB[vtxNdx] << " for the other" << TestLog::EndMessage; |
| return false; |
| } |
| } |
| return true; |
| } |
| }; |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Test invariance rule #2 |
| * |
| * Test that the set of vertices along an outer edge of a quad or triangle |
| * only depends on that edge's tessellation level, and spacing. |
| * |
| * For each (outer) edge in the quad or triangle, draw multiple patches |
| * with identical tessellation levels for that outer edge but with |
| * different values for the other outer edges; compare, among the |
| * primitives, the vertices generated for that outer edge. Repeat with |
| * different programs, using different winding etc. settings. Compare |
| * the edge's vertices between different programs. |
| *//*--------------------------------------------------------------------*/ |
| class InvariantOuterEdgeCase : public TestCase |
| { |
| public: |
| InvariantOuterEdgeCase(Context &context, const char *name, const char *description, TessPrimitiveType primType, |
| SpacingMode spacing) |
| : TestCase(context, name, description) |
| , m_primitiveType(primType) |
| , m_spacing(spacing) |
| { |
| DE_ASSERT(primType == TESSPRIMITIVETYPE_TRIANGLES || primType == TESSPRIMITIVETYPE_QUADS); |
| } |
| |
| void init(void); |
| void deinit(void); |
| IterateResult iterate(void); |
| |
| private: |
| struct Program |
| { |
| Winding winding; |
| bool usePointMode; |
| SharedPtr<const ShaderProgram> program; |
| |
| Program(Winding w, bool point, const SharedPtr<const ShaderProgram> &prog) |
| : winding(w) |
| , usePointMode(point) |
| , program(prog) |
| { |
| } |
| |
| string description(void) const |
| { |
| return string() + "winding mode " + getWindingShaderName(winding) + ", " + (usePointMode ? "" : "don't ") + |
| "use point mode"; |
| }; |
| }; |
| |
| static vector<float> generatePatchTessLevels(int numPatches, int constantOuterLevelIndex, float constantOuterLevel); |
| |
| static const int RENDER_SIZE = 16; |
| |
| const TessPrimitiveType m_primitiveType; |
| const SpacingMode m_spacing; |
| |
| vector<Program> m_programs; |
| }; |
| |
| vector<float> InvariantOuterEdgeCase::generatePatchTessLevels(int numPatches, int constantOuterLevelIndex, |
| float constantOuterLevel) |
| { |
| de::Random rnd(123); |
| return generateRandomPatchTessLevels(numPatches, constantOuterLevelIndex, constantOuterLevel, rnd); |
| } |
| |
| void InvariantOuterEdgeCase::init(void) |
| { |
| checkTessellationSupport(m_context); |
| checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE); |
| |
| for (int windingI = 0; windingI < WINDING_LAST; windingI++) |
| { |
| const Winding winding = (Winding)windingI; |
| |
| for (int usePointModeI = 0; usePointModeI <= 1; usePointModeI++) |
| { |
| const bool usePointMode = usePointModeI != 0; |
| const int programNdx = (int)m_programs.size(); |
| const string floatLit01 = de::floatToString(10.0f / (float)(programNdx + 10), 2); |
| |
| std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "\n" |
| "in highp float in_v_attr;\n" |
| "out highp float in_tc_attr;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " in_tc_attr = in_v_attr;\n" |
| "}\n"); |
| std::string tessellationControlTemplate("${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT_ARR}\n" |
| "\n" |
| "layout (vertices = " + |
| de::toString(programNdx + 1) + |
| ") out;\n" |
| "\n" |
| "in highp float in_tc_attr[];\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " gl_TessLevelInner[0] = in_tc_attr[0];\n" |
| " gl_TessLevelInner[1] = in_tc_attr[1];\n" |
| "\n" |
| " gl_TessLevelOuter[0] = in_tc_attr[2];\n" |
| " gl_TessLevelOuter[1] = in_tc_attr[3];\n" |
| " gl_TessLevelOuter[2] = in_tc_attr[4];\n" |
| " gl_TessLevelOuter[3] = in_tc_attr[5];\n" |
| "}\n"); |
| std::string tessellationEvaluationTemplate( |
| "${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "\n" + |
| getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing, winding, usePointMode) + |
| "\n" |
| "out highp vec4 in_f_color;\n" |
| "invariant out highp vec3 out_te_tessCoord;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " gl_Position = vec4(gl_TessCoord.xy*" + |
| floatLit01 + |
| " - float(gl_PrimitiveID)*0.05, 0.0, 1.0);\n" |
| " in_f_color = vec4(" + |
| floatLit01 + |
| ");\n" |
| " out_te_tessCoord = gl_TessCoord;\n" |
| "}\n"); |
| std::string fragmentShaderTemplate("${GLSL_VERSION_DECL}\n" |
| "\n" |
| "layout (location = 0) out mediump vec4 o_color;\n" |
| "\n" |
| "in highp vec4 in_f_color;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " o_color = in_f_color;\n" |
| "}\n"); |
| |
| m_programs.push_back( |
| Program(winding, usePointMode, |
| 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))))); |
| |
| { |
| const tcu::ScopedLogSection section(m_testCtx.getLog(), "Program" + de::toString(programNdx), |
| "Program " + de::toString(programNdx)); |
| |
| if (programNdx == 0 || !m_programs.back().program->isOk()) |
| m_testCtx.getLog() << *m_programs.back().program; |
| |
| if (!m_programs.back().program->isOk()) |
| TCU_FAIL("Program compilation failed"); |
| |
| if (programNdx > 0) |
| m_testCtx.getLog() << TestLog::Message << "Note: program " << programNdx |
| << " is similar to above, except some constants are different, and: " |
| << m_programs.back().description() << TestLog::EndMessage; |
| } |
| } |
| } |
| } |
| |
| void InvariantOuterEdgeCase::deinit(void) |
| { |
| m_programs.clear(); |
| } |
| |
| InvariantOuterEdgeCase::IterateResult InvariantOuterEdgeCase::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 glw::Functions &gl = renderCtx.getFunctions(); |
| |
| static const float singleOuterEdgeLevels[] = {1.0f, 1.2f, 1.9f, 2.3f, 2.8f, 3.3f, |
| 3.8f, 10.2f, 1.6f, 24.4f, 24.7f, 63.0f}; |
| const int numPatchesPerDrawCall = 10; |
| const vector<OuterEdgeDescription> edgeDescriptions = outerEdgeDescriptions(m_primitiveType); |
| |
| { |
| // Compute the number vertices in the largest draw call, so we can allocate the TF buffer just once. |
| int maxNumVerticesInDrawCall = 0; |
| { |
| const vector<float> patchTessLevels = |
| generatePatchTessLevels(numPatchesPerDrawCall, 0 /* outer-edge index doesn't affect vertex count */, |
| arrayMax(singleOuterEdgeLevels)); |
| |
| for (int usePointModeI = 0; usePointModeI <= 1; usePointModeI++) |
| maxNumVerticesInDrawCall = |
| de::max(maxNumVerticesInDrawCall, |
| multiplePatchReferenceVertexCount(m_primitiveType, m_spacing, usePointModeI != 0, |
| &patchTessLevels[0], numPatchesPerDrawCall)); |
| } |
| |
| { |
| const TFHandler tfHandler(m_context.getRenderContext(), maxNumVerticesInDrawCall); |
| |
| setViewport(gl, viewport); |
| gl.patchParameteri(GL_PATCH_VERTICES, 6); |
| |
| for (int outerEdgeIndex = 0; outerEdgeIndex < (int)edgeDescriptions.size(); outerEdgeIndex++) |
| { |
| const OuterEdgeDescription &edgeDesc = edgeDescriptions[outerEdgeIndex]; |
| |
| for (int outerEdgeLevelCaseNdx = 0; outerEdgeLevelCaseNdx < DE_LENGTH_OF_ARRAY(singleOuterEdgeLevels); |
| outerEdgeLevelCaseNdx++) |
| { |
| typedef std::set<Vec3, VecLexLessThan<3>> Vec3Set; |
| |
| const vector<float> patchTessLevels = generatePatchTessLevels( |
| numPatchesPerDrawCall, outerEdgeIndex, singleOuterEdgeLevels[outerEdgeLevelCaseNdx]); |
| const glu::VertexArrayBinding bindings[] = { |
| glu::va::Float("in_v_attr", 1, (int)patchTessLevels.size(), 0, &patchTessLevels[0])}; |
| Vec3Set |
| firstOuterEdgeVertices; // Vertices of the outer edge of the first patch of the first program's draw call; used for comparison with other patches. |
| |
| log << TestLog::Message << "Testing with outer tessellation level " |
| << singleOuterEdgeLevels[outerEdgeLevelCaseNdx] << " for the " << edgeDesc.description() |
| << " edge, and with various levels for other edges, and with all programs" |
| << TestLog::EndMessage; |
| |
| for (int programNdx = 0; programNdx < (int)m_programs.size(); programNdx++) |
| { |
| const Program &program = m_programs[programNdx]; |
| const uint32_t programGL = program.program->getProgram(); |
| |
| gl.useProgram(programGL); |
| |
| { |
| const TFHandler::Result tfResult = tfHandler.renderAndGetPrimitives( |
| programGL, outputPrimitiveTypeGL(m_primitiveType, program.usePointMode), |
| DE_LENGTH_OF_ARRAY(bindings), &bindings[0], (int)patchTessLevels.size()); |
| const int refNumVertices = |
| multiplePatchReferenceVertexCount(m_primitiveType, m_spacing, program.usePointMode, |
| &patchTessLevels[0], numPatchesPerDrawCall); |
| int numVerticesRead = 0; |
| |
| if ((int)tfResult.varying.size() != refNumVertices) |
| { |
| log << TestLog::Message |
| << "Failure: the number of vertices returned by transform feedback is " |
| << tfResult.varying.size() << ", expected " << refNumVertices << TestLog::EndMessage |
| << TestLog::Message << "Note: rendered " << numPatchesPerDrawCall |
| << " patches in one draw call; tessellation levels for each patch are (in order " |
| "[inner0, inner1, outer0, outer1, outer2, outer3]):\n" |
| << containerStr(patchTessLevels, 6) << TestLog::EndMessage; |
| |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices"); |
| return STOP; |
| } |
| |
| // Check the vertices of each patch. |
| |
| for (int patchNdx = 0; patchNdx < numPatchesPerDrawCall; patchNdx++) |
| { |
| const float *const innerLevels = &patchTessLevels[6 * patchNdx + 0]; |
| const float *const outerLevels = &patchTessLevels[6 * patchNdx + 2]; |
| const int patchNumVertices = referenceVertexCount( |
| m_primitiveType, m_spacing, program.usePointMode, innerLevels, outerLevels); |
| Vec3Set outerEdgeVertices; |
| |
| // We're interested in just the vertices on the current outer edge. |
| for (int vtxNdx = numVerticesRead; vtxNdx < numVerticesRead + patchNumVertices; |
| vtxNdx++) |
| { |
| const Vec3 &vtx = tfResult.varying[vtxNdx]; |
| if (edgeDesc.contains(vtx)) |
| outerEdgeVertices.insert(tfResult.varying[vtxNdx]); |
| } |
| |
| // Check that the outer edge contains an appropriate number of vertices. |
| { |
| const int refNumVerticesOnOuterEdge = |
| 1 + getClampedRoundedTessLevel(m_spacing, |
| singleOuterEdgeLevels[outerEdgeLevelCaseNdx]); |
| |
| if ((int)outerEdgeVertices.size() != refNumVerticesOnOuterEdge) |
| { |
| log << TestLog::Message << "Failure: the number of vertices on outer edge is " |
| << outerEdgeVertices.size() << ", expected " << refNumVerticesOnOuterEdge |
| << TestLog::EndMessage << TestLog::Message |
| << "Note: vertices on the outer edge are:\n" |
| << containerStr(outerEdgeVertices, 5, 0) << TestLog::EndMessage |
| << TestLog::Message |
| << "Note: the following parameters were used: " << program.description() |
| << ", tessellation levels: " |
| << tessellationLevelsString(innerLevels, outerLevels) |
| << TestLog::EndMessage; |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices"); |
| return STOP; |
| } |
| } |
| |
| // Compare the vertices to those of the first patch (unless this is the first patch). |
| |
| if (programNdx == 0 && patchNdx == 0) |
| firstOuterEdgeVertices = outerEdgeVertices; |
| else |
| { |
| if (firstOuterEdgeVertices != outerEdgeVertices) |
| { |
| log << TestLog::Message |
| << "Failure: vertices generated for the edge differ between the following " |
| "cases:\n" |
| << " - case A: " << m_programs[0].description() |
| << ", tessellation levels: " |
| << tessellationLevelsString(&patchTessLevels[0], &patchTessLevels[2]) |
| << "\n" |
| << " - case B: " << program.description() << ", tessellation levels: " |
| << tessellationLevelsString(innerLevels, outerLevels) |
| << TestLog::EndMessage; |
| |
| log << TestLog::Message |
| << "Note: resulting vertices for the edge for the cases were:\n" |
| << " - case A: " << containerStr(firstOuterEdgeVertices, 5, 14) << "\n" |
| << " - case B: " << containerStr(outerEdgeVertices, 5, 14) |
| << TestLog::EndMessage; |
| |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices"); |
| return STOP; |
| } |
| } |
| |
| numVerticesRead += patchNumVertices; |
| } |
| |
| DE_ASSERT(numVerticesRead == (int)tfResult.varying.size()); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| return STOP; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Test invariance rule #3 |
| * |
| * Test that the vertices along an outer edge are placed symmetrically. |
| * |
| * Draw multiple patches with different tessellation levels and different |
| * point_mode, winding etc. Before outputting tesscoords with TF, mirror |
| * the vertices in the TES such that every vertex on an outer edge - |
| * except the possible middle vertex - should be duplicated in the output. |
| * Check that appropriate duplicates exist. |
| *//*--------------------------------------------------------------------*/ |
| class SymmetricOuterEdgeCase : public TestCase |
| { |
| public: |
| SymmetricOuterEdgeCase(Context &context, const char *name, const char *description, TessPrimitiveType primType, |
| SpacingMode spacing, Winding winding, bool usePointMode) |
| : TestCase(context, name, description) |
| , m_primitiveType(primType) |
| , m_spacing(spacing) |
| , m_winding(winding) |
| , m_usePointMode(usePointMode) |
| { |
| } |
| |
| void init(void); |
| void deinit(void); |
| IterateResult iterate(void); |
| |
| private: |
| static vector<float> generatePatchTessLevels(int numPatches, int constantOuterLevelIndex, float constantOuterLevel); |
| |
| static const int RENDER_SIZE = 16; |
| |
| const TessPrimitiveType m_primitiveType; |
| const SpacingMode m_spacing; |
| const Winding m_winding; |
| const bool m_usePointMode; |
| |
| SharedPtr<const glu::ShaderProgram> m_program; |
| }; |
| |
| vector<float> SymmetricOuterEdgeCase::generatePatchTessLevels(int numPatches, int constantOuterLevelIndex, |
| float constantOuterLevel) |
| { |
| de::Random rnd(123); |
| return generateRandomPatchTessLevels(numPatches, constantOuterLevelIndex, constantOuterLevel, rnd); |
| } |
| |
| void SymmetricOuterEdgeCase::init(void) |
| { |
| checkTessellationSupport(m_context); |
| checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE); |
| |
| std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n" |
| "\n" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "in highp float in_v_attr;\n" |
| "out highp float in_tc_attr;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " in_tc_attr = in_v_attr;\n" |
| "}\n"); |
| std::string tessellationControlTemplate("${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT_ARR}\n" |
| "\n" |
| "layout (vertices = 1) out;\n" |
| "\n" |
| "in highp float in_tc_attr[];\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " gl_TessLevelInner[0] = in_tc_attr[0];\n" |
| " gl_TessLevelInner[1] = in_tc_attr[1];\n" |
| "\n" |
| " gl_TessLevelOuter[0] = in_tc_attr[2];\n" |
| " gl_TessLevelOuter[1] = in_tc_attr[3];\n" |
| " gl_TessLevelOuter[2] = in_tc_attr[4];\n" |
| " gl_TessLevelOuter[3] = in_tc_attr[5];\n" |
| "}\n"); |
| std::string tessellationEvaluationTemplate( |
| "${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "\n" + |
| getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing, m_winding, m_usePointMode) + |
| "\n" |
| "out highp vec4 in_f_color;\n" |
| "out highp vec4 out_te_tessCoord_isMirrored;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" + |
| (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? |
| " float x = gl_TessCoord.x;\n" |
| " float y = gl_TessCoord.y;\n" |
| " float z = gl_TessCoord.z;\n" |
| " // Mirror one half of each outer edge onto the other half, except the endpoints (because they belong " |
| "to two edges)\n" |
| " out_te_tessCoord_isMirrored = z == 0.0 && x > 0.5 && x != 1.0 ? vec4(1.0-x, 1.0-y, 0.0, 1.0)\n" |
| " : y == 0.0 && z > 0.5 && z != 1.0 ? vec4(1.0-x, 0.0, 1.0-z, 1.0)\n" |
| " : x == 0.0 && y > 0.5 && y != 1.0 ? vec4( 0.0, 1.0-y, 1.0-z, 1.0)\n" |
| " : vec4(x, y, z, 0.0);\n" : |
| m_primitiveType == TESSPRIMITIVETYPE_QUADS ? |
| " float x = gl_TessCoord.x;\n" |
| " float y = gl_TessCoord.y;\n" |
| " // Mirror one half of each outer edge onto the other half, except the endpoints (because they belong " |
| "to two edges)\n" |
| " out_te_tessCoord_isMirrored = (x == 0.0 || x == 1.0) && y > 0.5 && y != 1.0 ? vec4( x, 1.0-y, " |
| "0.0, 1.0)\n" |
| " : (y == 0.0 || y == 1.0) && x > 0.5 && x != 1.0 ? vec4(1.0-x, y, " |
| "0.0, 1.0)\n" |
| " : vec4(x, y, 0.0, 0.0);\n" : |
| m_primitiveType == TESSPRIMITIVETYPE_ISOLINES ? |
| " float x = gl_TessCoord.x;\n" |
| " float y = gl_TessCoord.y;\n" |
| " // Mirror one half of each outer edge onto the other half\n" |
| " out_te_tessCoord_isMirrored = (x == 0.0 || x == 1.0) && y > 0.5 ? vec4(x, 1.0-y, 0.0, 1.0)\n" |
| " : vec4(x, y, 0.0, 0.0f);\n" : |
| DE_NULL) + |
| "\n" |
| " gl_Position = vec4(gl_TessCoord.xy, 0.0, 1.0);\n" |
| " in_f_color = vec4(1.0);\n" |
| "}\n"); |
| std::string fragmentShaderTemplate("${GLSL_VERSION_DECL}\n" |
| "\n" |
| "layout (location = 0) out mediump vec4 o_color;\n" |
| "\n" |
| "in highp 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())) |
| << glu::TransformFeedbackVarying("out_te_tessCoord_isMirrored") |
| << glu::TransformFeedbackMode(GL_INTERLEAVED_ATTRIBS))); |
| |
| m_testCtx.getLog() << *m_program; |
| if (!m_program->isOk()) |
| TCU_FAIL("Program compilation failed"); |
| } |
| |
| void SymmetricOuterEdgeCase::deinit(void) |
| { |
| m_program.clear(); |
| } |
| |
| SymmetricOuterEdgeCase::IterateResult SymmetricOuterEdgeCase::iterate(void) |
| { |
| typedef TransformFeedbackHandler<Vec4> TFHandler; |
| |
| TestLog &log = m_testCtx.getLog(); |
| const RenderContext &renderCtx = m_context.getRenderContext(); |
| const RandomViewport viewport(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName())); |
| const glw::Functions &gl = renderCtx.getFunctions(); |
| |
| static const float singleOuterEdgeLevels[] = {1.0f, 1.2f, 1.9f, 2.3f, 2.8f, 3.3f, |
| 3.8f, 10.2f, 1.6f, 24.4f, 24.7f, 63.0f}; |
| const vector<OuterEdgeDescription> edgeDescriptions = outerEdgeDescriptions(m_primitiveType); |
| |
| { |
| // Compute the number vertices in the largest draw call, so we can allocate the TF buffer just once. |
| int maxNumVerticesInDrawCall; |
| { |
| const vector<float> patchTessLevels = generatePatchTessLevels( |
| 1, 0 /* outer-edge index doesn't affect vertex count */, arrayMax(singleOuterEdgeLevels)); |
| maxNumVerticesInDrawCall = referenceVertexCount(m_primitiveType, m_spacing, m_usePointMode, |
| &patchTessLevels[0], &patchTessLevels[2]); |
| } |
| |
| { |
| const TFHandler tfHandler(m_context.getRenderContext(), maxNumVerticesInDrawCall); |
| |
| setViewport(gl, viewport); |
| gl.patchParameteri(GL_PATCH_VERTICES, 6); |
| |
| for (int outerEdgeIndex = 0; outerEdgeIndex < (int)edgeDescriptions.size(); outerEdgeIndex++) |
| { |
| const OuterEdgeDescription &edgeDesc = edgeDescriptions[outerEdgeIndex]; |
| |
| for (int outerEdgeLevelCaseNdx = 0; outerEdgeLevelCaseNdx < DE_LENGTH_OF_ARRAY(singleOuterEdgeLevels); |
| outerEdgeLevelCaseNdx++) |
| { |
| typedef std::set<Vec3, VecLexLessThan<3>> Vec3Set; |
| |
| const vector<float> patchTessLevels = |
| generatePatchTessLevels(1, outerEdgeIndex, singleOuterEdgeLevels[outerEdgeLevelCaseNdx]); |
| const glu::VertexArrayBinding bindings[] = { |
| glu::va::Float("in_v_attr", 1, (int)patchTessLevels.size(), 0, &patchTessLevels[0])}; |
| |
| log << TestLog::Message << "Testing with outer tessellation level " |
| << singleOuterEdgeLevels[outerEdgeLevelCaseNdx] << " for the " << edgeDesc.description() |
| << " edge, and with various levels for other edges" << TestLog::EndMessage; |
| |
| { |
| const uint32_t programGL = m_program->getProgram(); |
| |
| gl.useProgram(programGL); |
| |
| { |
| const TFHandler::Result tfResult = tfHandler.renderAndGetPrimitives( |
| programGL, outputPrimitiveTypeGL(m_primitiveType, m_usePointMode), |
| DE_LENGTH_OF_ARRAY(bindings), &bindings[0], (int)patchTessLevels.size()); |
| const int refNumVertices = referenceVertexCount(m_primitiveType, m_spacing, m_usePointMode, |
| &patchTessLevels[0], &patchTessLevels[2]); |
| |
| if ((int)tfResult.varying.size() != refNumVertices) |
| { |
| log << TestLog::Message |
| << "Failure: the number of vertices returned by transform feedback is " |
| << tfResult.varying.size() << ", expected " << refNumVertices << TestLog::EndMessage |
| << TestLog::Message |
| << "Note: rendered 1 patch, tessellation levels are (in order [inner0, inner1, " |
| "outer0, outer1, outer2, outer3]):\n" |
| << containerStr(patchTessLevels, 6) << TestLog::EndMessage; |
| |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices"); |
| return STOP; |
| } |
| |
| // Check the vertices. |
| |
| { |
| Vec3Set nonMirroredEdgeVertices; |
| Vec3Set mirroredEdgeVertices; |
| |
| // We're interested in just the vertices on the current outer edge. |
| for (int vtxNdx = 0; vtxNdx < refNumVertices; vtxNdx++) |
| { |
| const Vec3 &vtx = tfResult.varying[vtxNdx].swizzle(0, 1, 2); |
| if (edgeDesc.contains(vtx)) |
| { |
| // Ignore the middle vertex of the outer edge, as it's exactly at the mirroring point; |
| // for isolines, also ignore (0, 0) and (1, 0) because there's no mirrored counterpart for them. |
| if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES && |
| vtx == tcu::select(Vec3(0.0f), Vec3(0.5f), |
| singleTrueMask<3>(edgeDesc.constantCoordinateIndex))) |
| continue; |
| if (m_primitiveType == TESSPRIMITIVETYPE_QUADS && |
| vtx.swizzle(0, 1) == |
| tcu::select(Vec2(edgeDesc.constantCoordinateValueChoices[0]), |
| Vec2(0.5f), |
| singleTrueMask<2>(edgeDesc.constantCoordinateIndex))) |
| continue; |
| if (m_primitiveType == TESSPRIMITIVETYPE_ISOLINES && |
| (vtx == Vec3(0.0f, 0.5f, 0.0f) || vtx == Vec3(1.0f, 0.5f, 0.0f) || |
| vtx == Vec3(0.0f, 0.0f, 0.0f) || vtx == Vec3(1.0f, 0.0f, 0.0f))) |
| continue; |
| |
| const bool isMirrored = tfResult.varying[vtxNdx].w() > 0.5f; |
| if (isMirrored) |
| mirroredEdgeVertices.insert(vtx); |
| else |
| nonMirroredEdgeVertices.insert(vtx); |
| } |
| } |
| |
| if (m_primitiveType != TESSPRIMITIVETYPE_ISOLINES) |
| { |
| // Check that both endpoints are present. Note that endpoints aren't mirrored by the shader, since they belong to more than one edge. |
| |
| Vec3 endpointA; |
| Vec3 endpointB; |
| |
| if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES) |
| { |
| endpointA = |
| tcu::select(Vec3(1.0f), Vec3(0.0f), |
| singleTrueMask<3>((edgeDesc.constantCoordinateIndex + 1) % 3)); |
| endpointB = |
| tcu::select(Vec3(1.0f), Vec3(0.0f), |
| singleTrueMask<3>((edgeDesc.constantCoordinateIndex + 2) % 3)); |
| } |
| else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS) |
| { |
| endpointA.xy() = |
| tcu::select(Vec2(edgeDesc.constantCoordinateValueChoices[0]), Vec2(0.0f), |
| singleTrueMask<2>(edgeDesc.constantCoordinateIndex)); |
| endpointB.xy() = |
| tcu::select(Vec2(edgeDesc.constantCoordinateValueChoices[0]), Vec2(1.0f), |
| singleTrueMask<2>(edgeDesc.constantCoordinateIndex)); |
| } |
| else |
| DE_ASSERT(false); |
| |
| if (!contains(nonMirroredEdgeVertices, endpointA) || |
| !contains(nonMirroredEdgeVertices, endpointB)) |
| { |
| log << TestLog::Message << "Failure: edge doesn't contain both endpoints, " |
| << endpointA << " and " << endpointB << TestLog::EndMessage |
| << TestLog::Message << "Note: non-mirrored vertices:\n" |
| << containerStr(nonMirroredEdgeVertices, 5) << "\nmirrored vertices:\n" |
| << containerStr(mirroredEdgeVertices, 5) << TestLog::EndMessage; |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices"); |
| return STOP; |
| } |
| nonMirroredEdgeVertices.erase(endpointA); |
| nonMirroredEdgeVertices.erase(endpointB); |
| } |
| |
| if (nonMirroredEdgeVertices != mirroredEdgeVertices) |
| { |
| log << TestLog::Message |
| << "Failure: the set of mirrored edges isn't equal to the set of non-mirrored " |
| "edges (ignoring endpoints and possible middle)" |
| << TestLog::EndMessage << TestLog::Message << "Note: non-mirrored vertices:\n" |
| << containerStr(nonMirroredEdgeVertices, 5) << "\nmirrored vertices:\n" |
| << containerStr(mirroredEdgeVertices, 5) << TestLog::EndMessage; |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices"); |
| return STOP; |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| return STOP; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Test invariance rule #4 |
| * |
| * Test that the vertices on an outer edge don't depend on which of the |
| * edges it is, other than with respect to component order. |
| *//*--------------------------------------------------------------------*/ |
| class OuterEdgeVertexSetIndexIndependenceCase : public TestCase |
| { |
| public: |
| OuterEdgeVertexSetIndexIndependenceCase(Context &context, const char *name, const char *description, |
| TessPrimitiveType primType, SpacingMode spacing, Winding winding, |
| bool usePointMode) |
| : TestCase(context, name, description) |
| , m_primitiveType(primType) |
| , m_spacing(spacing) |
| , m_winding(winding) |
| , m_usePointMode(usePointMode) |
| { |
| DE_ASSERT(primType == TESSPRIMITIVETYPE_TRIANGLES || primType == TESSPRIMITIVETYPE_QUADS); |
| } |
| |
| void init(void); |
| void deinit(void); |
| IterateResult iterate(void); |
| |
| private: |
| static vector<float> generatePatchTessLevels(int numPatches, int constantOuterLevelIndex, float constantOuterLevel); |
| |
| static const int RENDER_SIZE = 16; |
| |
| const TessPrimitiveType m_primitiveType; |
| const SpacingMode m_spacing; |
| const Winding m_winding; |
| const bool m_usePointMode; |
| |
| SharedPtr<const glu::ShaderProgram> m_program; |
| }; |
| |
| vector<float> OuterEdgeVertexSetIndexIndependenceCase::generatePatchTessLevels(int numPatches, |
| int constantOuterLevelIndex, |
| float constantOuterLevel) |
| { |
| de::Random rnd(123); |
| return generateRandomPatchTessLevels(numPatches, constantOuterLevelIndex, constantOuterLevel, rnd); |
| } |
| |
| void OuterEdgeVertexSetIndexIndependenceCase::init(void) |
| { |
| checkTessellationSupport(m_context); |
| checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE); |
| |
| std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "\n" |
| "in highp float in_v_attr;\n" |
| "out highp float in_tc_attr;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " in_tc_attr = in_v_attr;\n" |
| "}\n"); |
| std::string tessellationControlTemplate("${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT_ARR}\n" |
| "\n" |
| "layout (vertices = 1) out;\n" |
| "\n" |
| "in highp float in_tc_attr[];\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " gl_TessLevelInner[0] = in_tc_attr[0];\n" |
| " gl_TessLevelInner[1] = in_tc_attr[1];\n" |
| "\n" |
| " gl_TessLevelOuter[0] = in_tc_attr[2];\n" |
| " gl_TessLevelOuter[1] = in_tc_attr[3];\n" |
| " gl_TessLevelOuter[2] = in_tc_attr[4];\n" |
| " gl_TessLevelOuter[3] = in_tc_attr[5];\n" |
| "}\n"); |
| std::string tessellationEvaluationTemplate( |
| "${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "\n" + |
| getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing, m_winding, m_usePointMode) + |
| "\n" |
| "out highp vec4 in_f_color;\n" |
| "out highp vec3 out_te_tessCoord;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " out_te_tessCoord = gl_TessCoord;" |
| " gl_Position = vec4(gl_TessCoord.xy, 0.0, 1.0);\n" |
| " in_f_color = vec4(1.0);\n" |
| "}\n"); |
| std::string fragmentShaderTemplate("${GLSL_VERSION_DECL}\n" |
| "\n" |
| "layout (location = 0) out mediump vec4 o_color;\n" |
| "\n" |
| "in highp 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())) |
| << 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 OuterEdgeVertexSetIndexIndependenceCase::deinit(void) |
| { |
| m_program.clear(); |
| } |
| |
| OuterEdgeVertexSetIndexIndependenceCase::IterateResult OuterEdgeVertexSetIndexIndependenceCase::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 glw::Functions &gl = renderCtx.getFunctions(); |
| const uint32_t programGL = m_program->getProgram(); |
| |
| static const float singleOuterEdgeLevels[] = {1.0f, 1.2f, 1.9f, 2.3f, 2.8f, 3.3f, |
| 3.8f, 10.2f, 1.6f, 24.4f, 24.7f, 63.0f}; |
| const vector<OuterEdgeDescription> edgeDescriptions = outerEdgeDescriptions(m_primitiveType); |
| |
| gl.useProgram(programGL); |
| setViewport(gl, viewport); |
| gl.patchParameteri(GL_PATCH_VERTICES, 6); |
| |
| { |
| // Compute the number vertices in the largest draw call, so we can allocate the TF buffer just once. |
| int maxNumVerticesInDrawCall = 0; |
| { |
| const vector<float> patchTessLevels = generatePatchTessLevels( |
| 1, 0 /* outer-edge index doesn't affect vertex count */, arrayMax(singleOuterEdgeLevels)); |
| maxNumVerticesInDrawCall = referenceVertexCount(m_primitiveType, m_spacing, m_usePointMode, |
| &patchTessLevels[0], &patchTessLevels[2]); |
| } |
| |
| { |
| const TFHandler tfHandler(m_context.getRenderContext(), maxNumVerticesInDrawCall); |
| |
| for (int outerEdgeLevelCaseNdx = 0; outerEdgeLevelCaseNdx < DE_LENGTH_OF_ARRAY(singleOuterEdgeLevels); |
| outerEdgeLevelCaseNdx++) |
| { |
| typedef std::set<Vec3, VecLexLessThan<3>> Vec3Set; |
| |
| Vec3Set firstEdgeVertices; |
| |
| for (int outerEdgeIndex = 0; outerEdgeIndex < (int)edgeDescriptions.size(); outerEdgeIndex++) |
| { |
| const OuterEdgeDescription &edgeDesc = edgeDescriptions[outerEdgeIndex]; |
| const vector<float> patchTessLevels = |
| generatePatchTessLevels(1, outerEdgeIndex, singleOuterEdgeLevels[outerEdgeLevelCaseNdx]); |
| const glu::VertexArrayBinding bindings[] = { |
| glu::va::Float("in_v_attr", 1, (int)patchTessLevels.size(), 0, &patchTessLevels[0])}; |
| |
| log << TestLog::Message << "Testing with outer tessellation level " |
| << singleOuterEdgeLevels[outerEdgeLevelCaseNdx] << " for the " << edgeDesc.description() |
| << " edge, and with various levels for other edges" << TestLog::EndMessage; |
| |
| { |
| const TFHandler::Result tfResult = tfHandler.renderAndGetPrimitives( |
| programGL, outputPrimitiveTypeGL(m_primitiveType, m_usePointMode), |
| DE_LENGTH_OF_ARRAY(bindings), &bindings[0], (int)patchTessLevels.size()); |
| const int refNumVertices = referenceVertexCount(m_primitiveType, m_spacing, m_usePointMode, |
| &patchTessLevels[0], &patchTessLevels[2]); |
| |
| if ((int)tfResult.varying.size() != refNumVertices) |
| { |
| log << TestLog::Message |
| << "Failure: the number of vertices returned by transform feedback is " |
| << tfResult.varying.size() << ", expected " << refNumVertices << TestLog::EndMessage |
| << TestLog::Message |
| << "Note: rendered 1 patch, tessellation levels are (in order [inner0, inner1, outer0, " |
| "outer1, outer2, outer3]):\n" |
| << containerStr(patchTessLevels, 6) << TestLog::EndMessage; |
| |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices"); |
| return STOP; |
| } |
| |
| { |
| Vec3Set currentEdgeVertices; |
| |
| // Get the vertices on the current outer edge. |
| for (int vtxNdx = 0; vtxNdx < refNumVertices; vtxNdx++) |
| { |
| const Vec3 &vtx = tfResult.varying[vtxNdx]; |
| if (edgeDesc.contains(vtx)) |
| { |
| // Swizzle components to match the order of the first edge. |
| if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES) |
| { |
| currentEdgeVertices.insert(outerEdgeIndex == 0 ? vtx : |
| outerEdgeIndex == 1 ? vtx.swizzle(1, 0, 2) : |
| outerEdgeIndex == 2 ? vtx.swizzle(2, 1, 0) : |
| Vec3(-1.0f)); |
| } |
| else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS) |
| { |
| currentEdgeVertices.insert(Vec3(outerEdgeIndex == 0 ? vtx.y() : |
| outerEdgeIndex == 1 ? vtx.x() : |
| outerEdgeIndex == 2 ? vtx.y() : |
| outerEdgeIndex == 3 ? vtx.x() : |
| -1.0f, |
| 0.0f, 0.0f)); |
| } |
| else |
| DE_ASSERT(false); |
| } |
| } |
| |
| if (outerEdgeIndex == 0) |
| firstEdgeVertices = currentEdgeVertices; |
| else |
| { |
| // Compare vertices of this edge to those of the first edge. |
| |
| if (currentEdgeVertices != firstEdgeVertices) |
| { |
| const char *const swizzleDesc = m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? |
| (outerEdgeIndex == 1 ? "(y, x, z)" : |
| outerEdgeIndex == 2 ? "(z, y, x)" : |
| DE_NULL) : |
| m_primitiveType == TESSPRIMITIVETYPE_QUADS ? |
| (outerEdgeIndex == 1 ? "(x, 0)" : |
| outerEdgeIndex == 2 ? "(y, 0)" : |
| outerEdgeIndex == 3 ? "(x, 0)" : |
| DE_NULL) : |
| DE_NULL; |
| |
| log << TestLog::Message << "Failure: the set of vertices on the " |
| << edgeDesc.description() << " edge" |
| << " doesn't match the set of vertices on the " |
| << edgeDescriptions[0].description() << " edge" << TestLog::EndMessage |
| << TestLog::Message << "Note: set of vertices on " << edgeDesc.description() |
| << " edge, components swizzled like " << swizzleDesc |
| << " to match component order on first edge:\n" |
| << containerStr(currentEdgeVertices, 5) << "\non " |
| << edgeDescriptions[0].description() << " edge:\n" |
| << containerStr(firstEdgeVertices, 5) << TestLog::EndMessage; |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices"); |
| return STOP; |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| return STOP; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Test invariance rule #5 |
| * |
| * Test that the set of triangles input to the TES only depends on the |
| * tessellation levels, tessellation mode and spacing mode. Specifically, |
| * winding doesn't change the set of triangles, though it can change the |
| * order in which they are input to TES, and can (and will) change the |
| * vertex order within a triangle. |
| *//*--------------------------------------------------------------------*/ |
| class InvariantTriangleSetCase : public PrimitiveSetInvarianceCase |
| { |
| public: |
| InvariantTriangleSetCase(Context &context, const char *name, const char *description, TessPrimitiveType primType, |
| SpacingMode spacing) |
| : PrimitiveSetInvarianceCase(context, name, description, primType, spacing, false, WINDINGUSAGE_VARY) |
| { |
| DE_ASSERT(primType == TESSPRIMITIVETYPE_TRIANGLES || primType == TESSPRIMITIVETYPE_QUADS); |
| } |
| |
| protected: |
| virtual bool compare(const vector<Vec3> &coordsA, const vector<Vec3> &coordsB, int) const |
| { |
| return compareTriangleSets(coordsA, coordsB, m_testCtx.getLog()); |
| } |
| }; |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Test invariance rule #6 |
| * |
| * Test that the set of inner triangles input to the TES only depends on |
| * the inner tessellation levels, tessellation mode and spacing mode. |
| *//*--------------------------------------------------------------------*/ |
| class InvariantInnerTriangleSetCase : public PrimitiveSetInvarianceCase |
| { |
| public: |
| InvariantInnerTriangleSetCase(Context &context, const char *name, const char *description, |
| TessPrimitiveType primType, SpacingMode spacing) |
| : PrimitiveSetInvarianceCase(context, name, description, primType, spacing, false, WINDINGUSAGE_VARY) |
| { |
| DE_ASSERT(primType == TESSPRIMITIVETYPE_TRIANGLES || primType == TESSPRIMITIVETYPE_QUADS); |
| } |
| |
| protected: |
| virtual vector<LevelCase> genTessLevelCases(void) const |
| { |
| const int numSubCases = 4; |
| const vector<LevelCase> baseResults = PrimitiveSetInvarianceCase::genTessLevelCases(); |
| vector<LevelCase> result; |
| de::Random rnd(123); |
| |
| // Generate variants with different values for irrelevant levels. |
| for (int baseNdx = 0; baseNdx < (int)baseResults.size(); baseNdx++) |
| { |
| const TessLevels &base = baseResults[baseNdx].levels[0]; |
| TessLevels levels = base; |
| LevelCase levelCase; |
| |
| for (int subNdx = 0; subNdx < numSubCases; subNdx++) |
| { |
| levelCase.levels.push_back(levels); |
| |
| for (int i = 0; i < DE_LENGTH_OF_ARRAY(levels.outer); i++) |
| levels.outer[i] = rnd.getFloat(2.0f, 16.0f); |
| if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES) |
| levels.inner[1] = rnd.getFloat(2.0f, 16.0f); |
| } |
| |
| result.push_back(levelCase); |
| } |
| |
| return result; |
| } |
| |
| struct IsInnerTriangleTriangle |
| { |
| bool operator()(const Vec3 *vertices) const |
| { |
| for (int v = 0; v < 3; v++) |
| for (int c = 0; c < 3; c++) |
| if (vertices[v][c] == 0.0f) |
| return false; |
| return true; |
| } |
| }; |
| |
| struct IsInnerQuadTriangle |
| { |
| bool operator()(const Vec3 *vertices) const |
| { |
| for (int v = 0; v < 3; v++) |
| for (int c = 0; c < 2; c++) |
| if (vertices[v][c] == 0.0f || vertices[v][c] == 1.0f) |
| return false; |
| return true; |
| } |
| }; |
| |
| virtual bool compare(const vector<Vec3> &coordsA, const vector<Vec3> &coordsB, int) const |
| { |
| if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES) |
| return compareTriangleSets(coordsA, coordsB, m_testCtx.getLog(), IsInnerTriangleTriangle(), |
| "outer triangles"); |
| else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS) |
| return compareTriangleSets(coordsA, coordsB, m_testCtx.getLog(), IsInnerQuadTriangle(), "outer triangles"); |
| else |
| { |
| DE_ASSERT(false); |
| return false; |
| } |
| } |
| }; |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Test invariance rule #7 |
| * |
| * Test that the set of outer triangles input to the TES only depends on |
| * tessellation mode, spacing mode and the inner and outer tessellation |
| * levels corresponding to the inner and outer edges relevant to that |
| * triangle. |
| *//*--------------------------------------------------------------------*/ |
| class InvariantOuterTriangleSetCase : public PrimitiveSetInvarianceCase |
| { |
| public: |
| InvariantOuterTriangleSetCase(Context &context, const char *name, const char *description, |
| TessPrimitiveType primType, SpacingMode spacing) |
| : PrimitiveSetInvarianceCase(context, name, description, primType, spacing, false, WINDINGUSAGE_VARY) |
| { |
| DE_ASSERT(primType == TESSPRIMITIVETYPE_TRIANGLES || primType == TESSPRIMITIVETYPE_QUADS); |
| } |
| |
| protected: |
| virtual vector<LevelCase> genTessLevelCases(void) const |
| { |
| const int numSubCasesPerEdge = 4; |
| const int numEdges = m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? 3 : |
| m_primitiveType == TESSPRIMITIVETYPE_QUADS ? 4 : |
| -1; |
| const vector<LevelCase> baseResult = PrimitiveSetInvarianceCase::genTessLevelCases(); |
| vector<LevelCase> result; |
| de::Random rnd(123); |
| |
| // Generate variants with different values for irrelevant levels. |
| for (int baseNdx = 0; baseNdx < (int)baseResult.size(); baseNdx++) |
| { |
| const TessLevels &base = baseResult[baseNdx].levels[0]; |
| if (base.inner[0] == 1.0f || (m_primitiveType == TESSPRIMITIVETYPE_QUADS && base.inner[1] == 1.0f)) |
| continue; |
| |
| for (int edgeNdx = 0; edgeNdx < numEdges; edgeNdx++) |
| { |
| TessLevels levels = base; |
| LevelCase levelCase; |
| levelCase.mem = edgeNdx; |
| |
| for (int subCaseNdx = 0; subCaseNdx < numSubCasesPerEdge; subCaseNdx++) |
| { |
| levelCase.levels.push_back(levels); |
| |
| for (int i = 0; i < DE_LENGTH_OF_ARRAY(levels.outer); i++) |
| { |
| if (i != edgeNdx) |
| levels.outer[i] = rnd.getFloat(2.0f, 16.0f); |
| } |
| |
| if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES) |
| levels.inner[1] = rnd.getFloat(2.0f, 16.0f); |
| } |
| |
| result.push_back(levelCase); |
| } |
| } |
| |
| return result; |
| } |
| |
| class IsTriangleTriangleOnOuterEdge |
| { |
| public: |
| IsTriangleTriangleOnOuterEdge(int edgeNdx) : m_edgeNdx(edgeNdx) |
| { |
| } |
| bool operator()(const Vec3 *vertices) const |
| { |
| bool touchesAppropriateEdge = false; |
| for (int v = 0; v < 3; v++) |
| if (vertices[v][m_edgeNdx] == 0.0f) |
| touchesAppropriateEdge = true; |
| |
| if (touchesAppropriateEdge) |
| { |
| const Vec3 avg = (vertices[0] + vertices[1] + vertices[2]) / 3.0f; |
| return avg[m_edgeNdx] < avg[(m_edgeNdx + 1) % 3] && avg[m_edgeNdx] < avg[(m_edgeNdx + 2) % 3]; |
| } |
| return false; |
| } |
| |
| private: |
| int m_edgeNdx; |
| }; |
| |
| class IsQuadTriangleOnOuterEdge |
| { |
| public: |
| IsQuadTriangleOnOuterEdge(int edgeNdx) : m_edgeNdx(edgeNdx) |
| { |
| } |
| |
| bool onEdge(const Vec3 &v) const |
| { |
| return v[m_edgeNdx % 2] == (m_edgeNdx <= 1 ? 0.0f : 1.0f); |
| } |
| |
| static inline bool onAnyEdge(const Vec3 &v) |
| { |
| return v[0] == 0.0f || v[0] == 1.0f || v[1] == 0.0f || v[1] == 1.0f; |
| } |
| |
| bool operator()(const Vec3 *vertices) const |
| { |
| for (int v = 0; v < 3; v++) |
| { |
| const Vec3 &a = vertices[v]; |
| const Vec3 &b = vertices[(v + 1) % 3]; |
| const Vec3 &c = vertices[(v + 2) % 3]; |
| if (onEdge(a) && onEdge(b)) |
| return true; |
| if (onEdge(c) && !onAnyEdge(a) && !onAnyEdge(b) && a[m_edgeNdx % 2] == b[m_edgeNdx % 2]) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| private: |
| int m_edgeNdx; |
| }; |
| |
| virtual bool compare(const vector<Vec3> &coordsA, const vector<Vec3> &coordsB, int outerEdgeNdx) const |
| { |
| if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES) |
| { |
| return compareTriangleSets(coordsA, coordsB, m_testCtx.getLog(), |
| IsTriangleTriangleOnOuterEdge(outerEdgeNdx), |
| ("inner triangles, and outer triangles corresponding to other edge than edge " + |
| outerEdgeDescriptions(m_primitiveType)[outerEdgeNdx].description()) |
| .c_str()); |
| } |
| else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS) |
| { |
| return compareTriangleSets(coordsA, coordsB, m_testCtx.getLog(), IsQuadTriangleOnOuterEdge(outerEdgeNdx), |
| ("inner triangles, and outer triangles corresponding to other edge than edge " + |
| outerEdgeDescriptions(m_primitiveType)[outerEdgeNdx].description()) |
| .c_str()); |
| } |
| else |
| DE_ASSERT(false); |
| |
| return true; |
| } |
| }; |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Base class for testing individual components of tess coords |
| * |
| * Useful for testing parts of invariance rule #8. |
| *//*--------------------------------------------------------------------*/ |
| class TessCoordComponentInvarianceCase : public TestCase |
| { |
| public: |
| TessCoordComponentInvarianceCase(Context &context, const char *name, const char *description, |
| TessPrimitiveType primType, SpacingMode spacing, Winding winding, |
| bool usePointMode) |
| : TestCase(context, name, description) |
| , m_primitiveType(primType) |
| , m_spacing(spacing) |
| , m_winding(winding) |
| , m_usePointMode(usePointMode) |
| { |
| } |
| |
| void init(void); |
| void deinit(void); |
| IterateResult iterate(void); |
| |
| protected: |
| virtual string tessEvalOutputComponentStatements(const char *tessCoordComponentName, |
| const char *outputComponentName) const = 0; |
| virtual bool checkTessCoordComponent(float component) const = 0; |
| |
| private: |
| static vector<float> genTessLevelCases(int numCases); |
| |
| static const int RENDER_SIZE = 16; |
| |
| const TessPrimitiveType m_primitiveType; |
| const SpacingMode m_spacing; |
| const Winding m_winding; |
| const bool m_usePointMode; |
| |
| SharedPtr<const glu::ShaderProgram> m_program; |
| }; |
| |
| void TessCoordComponentInvarianceCase::init(void) |
| { |
| checkTessellationSupport(m_context); |
| checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE); |
| |
| std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "\n" |
| "in highp float in_v_attr;\n" |
| "out highp float in_tc_attr;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " in_tc_attr = in_v_attr;\n" |
| "}\n"); |
| std::string tessellationControlTemplate("${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT_ARR}\n" |
| "\n" |
| "layout (vertices = 1) out;\n" |
| "\n" |
| "in highp float in_tc_attr[];\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " gl_TessLevelInner[0] = in_tc_attr[0];\n" |
| " gl_TessLevelInner[1] = in_tc_attr[1];\n" |
| "\n" |
| " gl_TessLevelOuter[0] = in_tc_attr[2];\n" |
| " gl_TessLevelOuter[1] = in_tc_attr[3];\n" |
| " gl_TessLevelOuter[2] = in_tc_attr[4];\n" |
| " gl_TessLevelOuter[3] = in_tc_attr[5];\n" |
| "}\n"); |
| std::string tessellationEvaluationTemplate( |
| "${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "\n" + |
| getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing, m_winding, m_usePointMode) + |
| "\n" |
| "out highp vec4 in_f_color;\n" |
| "out highp vec3 out_te_output;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" + |
| tessEvalOutputComponentStatements("gl_TessCoord.x", "out_te_output.x") + |
| tessEvalOutputComponentStatements("gl_TessCoord.y", "out_te_output.y") |
| |
| + (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? |
| tessEvalOutputComponentStatements("gl_TessCoord.z", "out_te_output.z") : |
| " out_te_output.z = 0.0f;\n") + |
| " gl_Position = vec4(gl_TessCoord.xy, 0.0, 1.0);\n" |
| " in_f_color = vec4(1.0);\n" |
| "}\n"); |
| std::string fragmentShaderTemplate("${GLSL_VERSION_DECL}\n" |
| "\n" |
| "layout (location = 0) out mediump vec4 o_color;\n" |
| "\n" |
| "in highp 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())) |
| << glu::TransformFeedbackVarying("out_te_output") |
| << glu::TransformFeedbackMode(GL_INTERLEAVED_ATTRIBS))); |
| |
| m_testCtx.getLog() << *m_program; |
| if (!m_program->isOk()) |
| TCU_FAIL("Program compilation failed"); |
| } |
| |
| void TessCoordComponentInvarianceCase::deinit(void) |
| { |
| m_program.clear(); |
| } |
| |
| vector<float> TessCoordComponentInvarianceCase::genTessLevelCases(int numCases) |
| { |
| de::Random rnd(123); |
| vector<float> result; |
| |
| for (int i = 0; i < numCases; i++) |
| for (int j = 0; j < 6; j++) |
| result.push_back(rnd.getFloat(1.0f, 63.0f)); |
| |
| return result; |
| } |
| |
| TessCoordComponentInvarianceCase::IterateResult TessCoordComponentInvarianceCase::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 glw::Functions &gl = renderCtx.getFunctions(); |
| const int numTessLevelCases = 32; |
| const vector<float> tessLevels = genTessLevelCases(numTessLevelCases); |
| const uint32_t programGL = m_program->getProgram(); |
| |
| gl.useProgram(programGL); |
| setViewport(gl, viewport); |
| gl.patchParameteri(GL_PATCH_VERTICES, 6); |
| |
| { |
| // Compute the number vertices in the largest draw call, so we can allocate the TF buffer just once. |
| int maxNumVerticesInDrawCall = 0; |
| for (int i = 0; i < numTessLevelCases; i++) |
| maxNumVerticesInDrawCall = |
| de::max(maxNumVerticesInDrawCall, referenceVertexCount(m_primitiveType, m_spacing, m_usePointMode, |
| &tessLevels[6 * i + 0], &tessLevels[6 * i + 2])); |
| |
| { |
| const TFHandler tfHandler(m_context.getRenderContext(), maxNumVerticesInDrawCall); |
| |
| for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < numTessLevelCases; tessLevelCaseNdx++) |
| { |
| log << TestLog::Message << "Testing with tessellation levels: " |
| << tessellationLevelsString(&tessLevels[6 * tessLevelCaseNdx + 0], |
| &tessLevels[6 * tessLevelCaseNdx + 2]) |
| << TestLog::EndMessage; |
| |
| const glu::VertexArrayBinding bindings[] = { |
| glu::va::Float("in_v_attr", 1, (int)6, 0, &tessLevels[6 * tessLevelCaseNdx])}; |
| const TFHandler::Result tfResult = |
| tfHandler.renderAndGetPrimitives(programGL, outputPrimitiveTypeGL(m_primitiveType, m_usePointMode), |
| DE_LENGTH_OF_ARRAY(bindings), &bindings[0], 6); |
| |
| for (int vtxNdx = 0; vtxNdx < (int)tfResult.varying.size(); vtxNdx++) |
| { |
| const Vec3 &vec = tfResult.varying[vtxNdx]; |
| const int numComps = m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? 3 : 2; |
| |
| for (int compNdx = 0; compNdx < numComps; compNdx++) |
| { |
| if (!checkTessCoordComponent(vec[compNdx])) |
| { |
| log << TestLog::Message << "Note: output value at index " << vtxNdx << " is " |
| << (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? de::toString(vec) : |
| de::toString(vec.swizzle(0, 1))) |
| << TestLog::EndMessage; |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid tessellation coordinate component"); |
| return STOP; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| return STOP; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Test first part of invariance rule #8 |
| * |
| * Test that all (relevant) components of tess coord are in [0,1]. |
| *//*--------------------------------------------------------------------*/ |
| class TessCoordComponentRangeCase : public TessCoordComponentInvarianceCase |
| { |
| public: |
| TessCoordComponentRangeCase(Context &context, const char *name, const char *description, TessPrimitiveType primType, |
| SpacingMode spacing, Winding winding, bool usePointMode) |
| : TessCoordComponentInvarianceCase(context, name, description, primType, spacing, winding, usePointMode) |
| { |
| } |
| |
| protected: |
| virtual string tessEvalOutputComponentStatements(const char *tessCoordComponentName, |
| const char *outputComponentName) const |
| { |
| return string() + "\t" + outputComponentName + " = " + tessCoordComponentName + ";\n"; |
| } |
| |
| virtual bool checkTessCoordComponent(float component) const |
| { |
| if (!de::inRange(component, 0.0f, 1.0f)) |
| { |
| m_testCtx.getLog() << TestLog::Message << "Failure: tess coord component isn't in range [0,1]" |
| << TestLog::EndMessage; |
| return false; |
| } |
| return true; |
| } |
| }; |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Test second part of invariance rule #8 |
| * |
| * Test that all (relevant) components of tess coord are in [0,1] and |
| * 1.0-c is exact for every such component c. |
| *//*--------------------------------------------------------------------*/ |
| class OneMinusTessCoordComponentCase : public TessCoordComponentInvarianceCase |
| { |
| public: |
| OneMinusTessCoordComponentCase(Context &context, const char *name, const char *description, |
| TessPrimitiveType primType, SpacingMode spacing, Winding winding, bool usePointMode) |
| : TessCoordComponentInvarianceCase(context, name, description, primType, spacing, winding, usePointMode) |
| { |
| } |
| |
| protected: |
| virtual string tessEvalOutputComponentStatements(const char *tessCoordComponentName, |
| const char *outputComponentName) const |
| { |
| return string() + |
| " {\n" |
| " float oneMinusComp = 1.0 - " + |
| tessCoordComponentName + |
| ";\n" |
| " " + |
| outputComponentName + " = " + tessCoordComponentName + |
| " + oneMinusComp;\n" |
| " }\n"; |
| } |
| |
| virtual bool checkTessCoordComponent(float component) const |
| { |
| if (component != 1.0f) |
| { |
| m_testCtx.getLog() |
| << TestLog::Message |
| << "Failure: comp + (1.0-comp) doesn't equal 1.0 for some component of tessellation coordinate" |
| << TestLog::EndMessage; |
| return false; |
| } |
| return true; |
| } |
| }; |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Test that patch is discarded if relevant outer level <= 0.0 |
| * |
| * Draws patches with different combinations of tessellation levels, |
| * varying which levels are negative. Verifies by checking that colored |
| * pixels exist inside the area of valid primitives, and only black pixels |
| * exist inside the area of discarded primitives. An additional sanity |
| * test is done, checking that the number of primitives written by TF is |
| * correct. |
| *//*--------------------------------------------------------------------*/ |
| class PrimitiveDiscardCase : public TestCase |
| { |
| public: |
| PrimitiveDiscardCase(Context &context, const char *name, const char *description, TessPrimitiveType primType, |
| SpacingMode spacing, Winding winding, bool usePointMode) |
| : TestCase(context, name, description) |
| , m_primitiveType(primType) |
| , m_spacing(spacing) |
| , m_winding(winding) |
| , m_usePointMode(usePointMode) |
| { |
| } |
| |
| void init(void); |
| void deinit(void); |
| IterateResult iterate(void); |
| |
| private: |
| static vector<float> genAttributes(void); |
| |
| static const int RENDER_SIZE = 256; |
| |
| const TessPrimitiveType m_primitiveType; |
| const SpacingMode m_spacing; |
| const Winding m_winding; |
| const bool m_usePointMode; |
| |
| SharedPtr<const glu::ShaderProgram> m_program; |
| }; |
| |
| void PrimitiveDiscardCase::init(void) |
| { |
| checkTessellationSupport(m_context); |
| checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE); |
| |
| std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "\n" |
| "in highp float in_v_attr;\n" |
| "out highp float in_tc_attr;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " in_tc_attr = in_v_attr;\n" |
| "}\n"); |
| std::string tessellationControlTemplate("${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT_ARR}\n" |
| "\n" |
| "layout (vertices = 1) out;\n" |
| "\n" |
| "in highp float in_tc_attr[];\n" |
| "\n" |
| "patch out highp vec2 in_te_positionScale;\n" |
| "patch out highp vec2 in_te_positionOffset;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " in_te_positionScale = vec2(in_tc_attr[6], in_tc_attr[7]);\n" |
| " in_te_positionOffset = vec2(in_tc_attr[8], in_tc_attr[9]);\n" |
| "\n" |
| " gl_TessLevelInner[0] = in_tc_attr[0];\n" |
| " gl_TessLevelInner[1] = in_tc_attr[1];\n" |
| "\n" |
| " gl_TessLevelOuter[0] = in_tc_attr[2];\n" |
| " gl_TessLevelOuter[1] = in_tc_attr[3];\n" |
| " gl_TessLevelOuter[2] = in_tc_attr[4];\n" |
| " gl_TessLevelOuter[3] = in_tc_attr[5];\n" |
| "}\n"); |
| std::string tessellationEvaluationTemplate( |
| "${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "\n" + |
| getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing, m_winding, m_usePointMode) + |
| "\n" |
| "patch in highp vec2 in_te_positionScale;\n" |
| "patch in highp vec2 in_te_positionOffset;\n" |
| "\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*in_te_positionScale + in_te_positionOffset, 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 PrimitiveDiscardCase::deinit(void) |
| { |
| m_program.clear(); |
| } |
| |
| vector<float> PrimitiveDiscardCase::genAttributes(void) |
| { |
| // Generate input attributes (tessellation levels, and position scale and |
| // offset) for a number of primitives. Each primitive has a different |
| // combination of tessellatio levels; each level is either a valid |
| // value or an "invalid" value (negative or zero, chosen from |
| // invalidTessLevelChoices). |
| |
| // \note The attributes are generated in such an order that all of the |
| // valid attribute tuples come before the first invalid one both |
| // in the result vector, and when scanning the resulting 2d grid |
| // of primitives is scanned in y-major order. This makes |
| // verification somewhat simpler. |
| |
| static const float baseTessLevels[6] = {3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f}; |
| static const float invalidTessLevelChoices[] = {-0.42f, 0.0f}; |
| const int numChoices = 1 + DE_LENGTH_OF_ARRAY(invalidTessLevelChoices); |
| float choices[6][numChoices]; |
| vector<float> result; |
| |
| for (int levelNdx = 0; levelNdx < 6; levelNdx++) |
| for (int choiceNdx = 0; choiceNdx < numChoices; choiceNdx++) |
| choices[levelNdx][choiceNdx] = |
| choiceNdx == 0 ? baseTessLevels[levelNdx] : invalidTessLevelChoices[choiceNdx - 1]; |
| |
| { |
| const int numCols = intPow(numChoices, 6 / 2); // sqrt(numChoices**6) == sqrt(number of primitives) |
| const int numRows = numCols; |
| int index = 0; |
| int i[6]; |
| // We could do this with some generic combination-generation function, but meh, it's not that bad. |
| for (i[2] = 0; i[2] < numChoices; i[2]++) // First outer |
| for (i[3] = 0; i[3] < numChoices; i[3]++) // Second outer |
| for (i[4] = 0; i[4] < numChoices; i[4]++) // Third outer |
| for (i[5] = 0; i[5] < numChoices; i[5]++) // Fourth outer |
| for (i[0] = 0; i[0] < numChoices; i[0]++) // First inner |
| for (i[1] = 0; i[1] < numChoices; i[1]++) // Second inner |
| { |
| for (int j = 0; j < 6; j++) |
| result.push_back(choices[j][i[j]]); |
| |
| { |
| const int col = index % numCols; |
| const int row = index / numCols; |
| // Position scale. |
| result.push_back((float)2.0f / (float)numCols); |
| result.push_back((float)2.0f / (float)numRows); |
| // Position offset. |
| result.push_back((float)col / (float)numCols * 2.0f - 1.0f); |
| result.push_back((float)row / (float)numRows * 2.0f - 1.0f); |
| } |
| |
| index++; |
| } |
| } |
| |
| return result; |
| } |
| |
| PrimitiveDiscardCase::IterateResult PrimitiveDiscardCase::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 glw::Functions &gl = renderCtx.getFunctions(); |
| const vector<float> attributes = genAttributes(); |
| const int numAttribsPerPrimitive = 6 + 2 + 2; // Tess levels, scale, offset. |
| const int numPrimitives = (int)attributes.size() / numAttribsPerPrimitive; |
| const uint32_t programGL = m_program->getProgram(); |
| |
| gl.useProgram(programGL); |
| setViewport(gl, viewport); |
| gl.patchParameteri(GL_PATCH_VERTICES, numAttribsPerPrimitive); |
| |
| gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f); |
| gl.clear(GL_COLOR_BUFFER_BIT); |
| |
| // Check the convenience assertion that all discarded patches come after the last non-discarded patch. |
| { |
| bool discardedPatchEncountered = false; |
| for (int patchNdx = 0; patchNdx < numPrimitives; patchNdx++) |
| { |
| const bool discard = isPatchDiscarded(m_primitiveType, &attributes[numAttribsPerPrimitive * patchNdx + 2]); |
| DE_ASSERT(discard || !discardedPatchEncountered); |
| discardedPatchEncountered = discard; |
| } |
| DE_UNREF(discardedPatchEncountered); |
| } |
| |
| { |
| int numVerticesInDrawCall = 0; |
| for (int patchNdx = 0; patchNdx < numPrimitives; patchNdx++) |
| numVerticesInDrawCall += referenceVertexCount(m_primitiveType, m_spacing, m_usePointMode, |
| &attributes[numAttribsPerPrimitive * patchNdx + 0], |
| &attributes[numAttribsPerPrimitive * patchNdx + 2]); |
| |
| log << TestLog::Message << "Note: rendering " << numPrimitives |
| << " patches; first patches have valid relevant outer levels, " |
| << "but later patches have one or more invalid (i.e. less than or equal to 0.0) relevant outer levels" |
| << TestLog::EndMessage; |
| |
| { |
| const TFHandler tfHandler(m_context.getRenderContext(), numVerticesInDrawCall); |
| const glu::VertexArrayBinding bindings[] = { |
| glu::va::Float("in_v_attr", 1, (int)attributes.size(), 0, &attributes[0])}; |
| const TFHandler::Result tfResult = |
| tfHandler.renderAndGetPrimitives(programGL, outputPrimitiveTypeGL(m_primitiveType, m_usePointMode), |
| DE_LENGTH_OF_ARRAY(bindings), &bindings[0], (int)attributes.size()); |
| const tcu::Surface pixels = getPixels(renderCtx, viewport); |
| |
| log << TestLog::Image("RenderedImage", "Rendered image", pixels); |
| |
| if ((int)tfResult.varying.size() != numVerticesInDrawCall) |
| { |
| log << TestLog::Message << "Failure: expected " << numVerticesInDrawCall |
| << " vertices from transform feedback, got " << tfResult.varying.size() << TestLog::EndMessage; |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Wrong number of tessellation coordinates"); |
| return STOP; |
| } |
| |
| // Check that white pixels are found around every non-discarded |
| // patch, and that only black pixels are found after the last |
| // non-discarded patch. |
| { |
| int lastWhitePixelRow = 0; |
| int secondToLastWhitePixelRow = 0; |
| int lastWhitePixelColumnOnSecondToLastWhitePixelRow = 0; |
| |
| for (int patchNdx = 0; patchNdx < numPrimitives; patchNdx++) |
| { |
| const float *const attr = &attributes[numAttribsPerPrimitive * patchNdx]; |
| const bool validLevels = !isPatchDiscarded(m_primitiveType, &attr[2]); |
| |
| if (validLevels) |
| { |
| // Not a discarded patch; check that at least one white pixel is found in its area. |
| |
| const float *const scale = &attr[6]; |
| const float *const offset = &attr[8]; |
| const int x0 = (int)((offset[0] + 1.0f) * 0.5f * (float)pixels.getWidth()) - 1; |
| const int x1 = (int)((scale[0] + offset[0] + 1.0f) * 0.5f * (float)pixels.getWidth()) + 1; |
| const int y0 = (int)((offset[1] + 1.0f) * 0.5f * (float)pixels.getHeight()) - 1; |
| const int y1 = (int)((scale[1] + offset[1] + 1.0f) * 0.5f * (float)pixels.getHeight()) + 1; |
| const bool isMSAA = renderCtx.getRenderTarget().getNumSamples() > 1; |
| bool pixelOk = false; |
| |
| if (y1 > lastWhitePixelRow) |
| { |
| secondToLastWhitePixelRow = lastWhitePixelRow; |
| lastWhitePixelRow = y1; |
| } |
| lastWhitePixelColumnOnSecondToLastWhitePixelRow = x1; |
| |
| for (int y = y0; y <= y1 && !pixelOk; y++) |
| for (int x = x0; x <= x1 && !pixelOk; x++) |
| { |
| if (!de::inBounds(x, 0, pixels.getWidth()) || !de::inBounds(y, 0, pixels.getHeight())) |
| continue; |
| |
| if (isMSAA) |
| { |
| if (pixels.getPixel(x, y) != tcu::RGBA::black()) |
| pixelOk = true; |
| } |
| else |
| { |
| if (pixels.getPixel(x, y) == tcu::RGBA::white()) |
| pixelOk = true; |
| } |
| } |
| |
| if (!pixelOk) |
| { |
| log << TestLog::Message << "Failure: expected at least one " |
| << (isMSAA ? "non-black" : "white") << " pixel in the rectangle " |
| << "[x0=" << x0 << ", y0=" << y0 << ", x1=" << x1 << ", y1=" << y1 << "]" |
| << TestLog::EndMessage << TestLog::Message |
| << "Note: the rectangle approximately corresponds to the patch with these tessellation " |
| "levels: " |
| << tessellationLevelsString(&attr[0], &attr[1]) << TestLog::EndMessage; |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed"); |
| return STOP; |
| } |
| } |
| else |
| { |
| // First discarded primitive patch; the remaining are guaranteed to be discarded ones as well. |
| |
| for (int y = 0; y < pixels.getHeight(); y++) |
| for (int x = 0; x < pixels.getWidth(); x++) |
| { |
| if (y > lastWhitePixelRow || (y > secondToLastWhitePixelRow && |
| x > lastWhitePixelColumnOnSecondToLastWhitePixelRow)) |
| { |
| if (pixels.getPixel(x, y) != tcu::RGBA::black()) |
| { |
| log << TestLog::Message |
| << "Failure: expected all pixels to be black in the area " |
| << (lastWhitePixelColumnOnSecondToLastWhitePixelRow < |
| pixels.getWidth() - 1 ? |
| string() + "y > " + de::toString(lastWhitePixelRow) + " || (y > " + |
| de::toString(secondToLastWhitePixelRow) + " && x > " + |
| de::toString(lastWhitePixelColumnOnSecondToLastWhitePixelRow) + |
| ")" : |
| string() + "y > " + de::toString(lastWhitePixelRow)) |
| << " (they all correspond to patches that should be discarded)" |
| << TestLog::EndMessage << TestLog::Message << "Note: pixel " |
| << tcu::IVec2(x, y) << " isn't black" << TestLog::EndMessage; |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed"); |
| return STOP; |
| } |
| } |
| } |
| |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| return STOP; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Case testing user-defined IO between TCS and TES |
| * |
| * TCS outputs various values to TES, including aggregates. The outputs |
| * can be per-patch or per-vertex, and if per-vertex, they can also be in |
| * an IO block. Per-vertex input array size can be left implicit (i.e. |
| * inputArray[]) or explicit either by gl_MaxPatchVertices or an integer |
| * literal whose value is queried from GL. |
| * |
| * The values output are generated in TCS and verified in TES against |
| * similarly generated values. In case a verification of a value fails, the |
| * index of the invalid value is output with TF. |
| * As a sanity check, also the rendering result is verified (against pre- |
| * rendered reference). |
| *//*--------------------------------------------------------------------*/ |
| class UserDefinedIOCase : public TestCase |
| { |
| public: |
| enum IOType |
| { |
| IO_TYPE_PER_PATCH = 0, |
| IO_TYPE_PER_PATCH_ARRAY, |
| IO_TYPE_PER_PATCH_BLOCK, |
| IO_TYPE_PER_PATCH_BLOCK_ARRAY, |
| IO_TYPE_PER_VERTEX, |
| IO_TYPE_PER_VERTEX_BLOCK, |
| |
| IO_TYPE_LAST |
| }; |
| |
| enum VertexIOArraySize |
| { |
| VERTEX_IO_ARRAY_SIZE_IMPLICIT = 0, |
| VERTEX_IO_ARRAY_SIZE_EXPLICIT_SHADER_BUILTIN, //!< Use gl_MaxPatchVertices as size for per-vertex input array. |
| VERTEX_IO_ARRAY_SIZE_EXPLICIT_QUERY, //!< Query GL_MAX_PATCH_VERTICES, and use that as size for per-vertex input array. |
| |
| VERTEX_IO_ARRAY_SIZE_LAST |
| }; |
| |
| enum TessControlOutArraySize |
| { |
| TESS_CONTROL_OUT_ARRAY_SIZE_IMPLICIT = 0, |
| TESS_CONTROL_OUT_ARRAY_SIZE_LAYOUT, |
| TESS_CONTROL_OUT_ARRAY_SIZE_QUERY, |
| TESS_CONTROL_OUT_ARRAY_SIZE_SHADER_BUILTIN |
| }; |
| |
| UserDefinedIOCase(Context &context, const char *name, const char *description, TessPrimitiveType primType, |
| IOType ioType, VertexIOArraySize vertexIOArraySize, |
| TessControlOutArraySize tessControlOutArraySize, const char *referenceImagePath) |
| : TestCase(context, name, description) |
| , m_primitiveType(primType) |
| , m_ioType(ioType) |
| , m_vertexIOArraySize(vertexIOArraySize) |
| , m_tessControlOutArraySize(tessControlOutArraySize) |
| , m_referenceImagePath(referenceImagePath) |
| { |
| } |
| |
| void init(void); |
| void deinit(void); |
| IterateResult iterate(void); |
| |
| private: |
| typedef string (*BasicTypeVisitFunc)(const string &name, glu::DataType type, |
| int indentationDepth); //!< See glslTraverseBasicTypes below. |
| |
| class TopLevelObject |
| { |
| public: |
| virtual ~TopLevelObject(void) |
| { |
| } |
| |
| virtual string name(void) const = 0; |
| virtual string declare(void) const = 0; |
| virtual string declareArray(const string &arraySizeExpr) const = 0; |
| virtual string glslTraverseBasicTypeArray( |
| int numArrayElements, //!< If negative, traverse just array[gl_InvocationID], not all indices. |
| int indentationDepth, BasicTypeVisitFunc) const = 0; |
| virtual string glslTraverseBasicType(int indentationDepth, BasicTypeVisitFunc) const = 0; |
| virtual int numBasicSubobjectsInElementType(void) const = 0; |
| virtual string basicSubobjectAtIndex(int index, int arraySize) const = 0; |
| }; |
| |
| class Variable : public TopLevelObject |
| { |
| public: |
| Variable(const string &name_, const glu::VarType &type, bool isArray) |
| : m_name(name_) |
| , m_type(type) |
| , m_isArray(isArray) |
| { |
| DE_ASSERT(!type.isArrayType()); |
| } |
| |
| string name(void) const |
| { |
| return m_name; |
| } |
| string declare(void) const; |
| string declareArray(const string &arraySizeExpr) const; |
| string glslTraverseBasicTypeArray(int numArrayElements, int indentationDepth, BasicTypeVisitFunc) const; |
| string glslTraverseBasicType(int indentationDepth, BasicTypeVisitFunc) const; |
| int numBasicSubobjectsInElementType(void) const; |
| string basicSubobjectAtIndex(int index, int arraySize) const; |
| |
| private: |
| string m_name; |
| glu::VarType |
| m_type; //!< If this Variable is an array element, m_type is the element type; otherwise just the variable type. |
| const bool m_isArray; |
| }; |
| |
| class IOBlock : public TopLevelObject |
| { |
| public: |
| struct Member |
| { |
| string name; |
| glu::VarType type; |
| Member(const string &n, const glu::VarType &t) : name(n), type(t) |
| { |
| } |
| }; |
| |
| IOBlock(const string &blockName, const string &interfaceName, const vector<Member> &members) |
| : m_blockName(blockName) |
| , m_interfaceName(interfaceName) |
| , m_members(members) |
| { |
| } |
| |
| string name(void) const |
| { |
| return m_interfaceName; |
| } |
| string declare(void) const; |
| string declareArray(const string &arraySizeExpr) const; |
| string glslTraverseBasicTypeArray(int numArrayElements, int indentationDepth, BasicTypeVisitFunc) const; |
| string glslTraverseBasicType(int indentationDepth, BasicTypeVisitFunc) const; |
| int numBasicSubobjectsInElementType(void) const; |
| string basicSubobjectAtIndex(int index, int arraySize) const; |
| |
| private: |
| string m_blockName; |
| string m_interfaceName; |
| vector<Member> m_members; |
| }; |
| |
| static string glslTraverseBasicTypes(const string &rootName, const glu::VarType &rootType, int arrayNestingDepth, |
| int indentationDepth, BasicTypeVisitFunc visit); |
| |
| static string glslAssignBasicTypeObject(const string &name, glu::DataType, int indentationDepth); |
| static string glslCheckBasicTypeObject(const string &name, glu::DataType, int indentationDepth); |
| static int numBasicSubobjectsInElementType(const vector<SharedPtr<TopLevelObject>> &); |
| static string basicSubobjectAtIndex(int index, const vector<SharedPtr<TopLevelObject>> &, int topLevelArraySizes); |
| |
| enum |
| { |
| RENDER_SIZE = 256 |
| }; |
| enum |
| { |
| NUM_OUTPUT_VERTICES = 5 |
| }; |
| enum |
| { |
| NUM_PER_PATCH_ARRAY_ELEMS = 3 |
| }; |
| enum |
| { |
| NUM_PER_PATCH_BLOCKS = 2 |
| }; |
| |
| const TessPrimitiveType m_primitiveType; |
| const IOType m_ioType; |
| const VertexIOArraySize m_vertexIOArraySize; |
| const TessControlOutArraySize m_tessControlOutArraySize; |
| const string m_referenceImagePath; |
| |
| vector<glu::StructType> m_structTypes; |
| vector<SharedPtr<TopLevelObject>> m_tcsOutputs; |
| vector<SharedPtr<TopLevelObject>> m_tesInputs; |
| |
| SharedPtr<const glu::ShaderProgram> m_program; |
| }; |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Generate GLSL code to traverse (possibly aggregate) object |
| * |
| * Generates a string that represents GLSL code that traverses the |
| * basic-type subobjects in a rootType-typed object named rootName. Arrays |
| * are traversed with loops and struct members are each traversed |
| * separately. The code for each basic-type subobject is generated with |
| * the function given as the 'visit' argument. |
| *//*--------------------------------------------------------------------*/ |
| string UserDefinedIOCase::glslTraverseBasicTypes(const string &rootName, const glu::VarType &rootType, |
| int arrayNestingDepth, int indentationDepth, BasicTypeVisitFunc visit) |
| { |
| if (rootType.isBasicType()) |
| return visit(rootName, rootType.getBasicType(), indentationDepth); |
| else if (rootType.isArrayType()) |
| { |
| const string indentation = string(indentationDepth, '\t'); |
| const string loopIndexName = "i" + de::toString(arrayNestingDepth); |
| const string arrayLength = de::toString(rootType.getArraySize()); |
| return indentation + "for (int " + loopIndexName + " = 0; " + loopIndexName + " < " + |
| de::toString(rootType.getArraySize()) + "; " + loopIndexName + "++)\n" + indentation + "{\n" + |
| glslTraverseBasicTypes(rootName + "[" + loopIndexName + "]", rootType.getElementType(), |
| arrayNestingDepth + 1, indentationDepth + 1, visit) + |
| indentation + "}\n"; |
| } |
| else if (rootType.isStructType()) |
| { |
| const glu::StructType &structType = *rootType.getStructPtr(); |
| const int numMembers = structType.getNumMembers(); |
| string result; |
| |
| for (int membNdx = 0; membNdx < numMembers; membNdx++) |
| { |
| const glu::StructMember &member = structType.getMember(membNdx); |
| result += glslTraverseBasicTypes(rootName + "." + member.getName(), member.getType(), arrayNestingDepth, |
| indentationDepth, visit); |
| } |
| |
| return result; |
| } |
| else |
| { |
| DE_ASSERT(false); |
| return DE_NULL; |
| } |
| } |
| |
| string UserDefinedIOCase::Variable::declare(void) const |
| { |
| DE_ASSERT(!m_isArray); |
| return de::toString(glu::declare(m_type, m_name)) + ";\n"; |
| } |
| |
| string UserDefinedIOCase::Variable::declareArray(const string &sizeExpr) const |
| { |
| DE_ASSERT(m_isArray); |
| return de::toString(glu::declare(m_type, m_name)) + "[" + sizeExpr + "];\n"; |
| } |
| |
| string UserDefinedIOCase::IOBlock::declare(void) const |
| { |
| std::ostringstream buf; |
| |
| buf << m_blockName << "\n" |
| << "{\n"; |
| |
| for (int i = 0; i < (int)m_members.size(); i++) |
| buf << "\t" << glu::declare(m_members[i].type, m_members[i].name) << ";\n"; |
| |
| buf << "} " << m_interfaceName << ";\n"; |
| return buf.str(); |
| } |
| |
| string UserDefinedIOCase::IOBlock::declareArray(const string &sizeExpr) const |
| { |
| std::ostringstream buf; |
| |
| buf << m_blockName << "\n" |
| << "{\n"; |
| |
| for (int i = 0; i < (int)m_members.size(); i++) |
| buf << "\t" << glu::declare(m_members[i].type, m_members[i].name) << ";\n"; |
| |
| buf << "} " << m_interfaceName << "[" << sizeExpr << "];\n"; |
| return buf.str(); |
| } |
| |
| string UserDefinedIOCase::Variable::glslTraverseBasicTypeArray(int numArrayElements, int indentationDepth, |
| BasicTypeVisitFunc visit) const |
| { |
| DE_ASSERT(m_isArray); |
| |
| const bool traverseAsArray = numArrayElements >= 0; |
| const string traversedName = m_name + (!traverseAsArray ? "[gl_InvocationID]" : ""); |
| const glu::VarType type = traverseAsArray ? glu::VarType(m_type, numArrayElements) : m_type; |
| |
| return UserDefinedIOCase::glslTraverseBasicTypes(traversedName, type, 0, indentationDepth, visit); |
| } |
| |
| string UserDefinedIOCase::Variable::glslTraverseBasicType(int indentationDepth, BasicTypeVisitFunc visit) const |
| { |
| DE_ASSERT(!m_isArray); |
| |
| return UserDefinedIOCase::glslTraverseBasicTypes(m_name, m_type, 0, indentationDepth, visit); |
| } |
| |
| string UserDefinedIOCase::IOBlock::glslTraverseBasicTypeArray(int numArrayElements, int indentationDepth, |
| BasicTypeVisitFunc visit) const |
| { |
| if (numArrayElements >= 0) |
| { |
| const string indentation = string(indentationDepth, '\t'); |
| string result = |
| indentation + "for (int i0 = 0; i0 < " + de::toString(numArrayElements) + "; i0++)\n" + indentation + "{\n"; |
| for (int i = 0; i < (int)m_members.size(); i++) |
| result += UserDefinedIOCase::glslTraverseBasicTypes(m_interfaceName + "[i0]." + m_members[i].name, |
| m_members[i].type, 1, indentationDepth + 1, visit); |
| result += indentation + "}\n"; |
| return result; |
| } |
| else |
| { |
| string result; |
| for (int i = 0; i < (int)m_members.size(); i++) |
| result += |
| UserDefinedIOCase::glslTraverseBasicTypes(m_interfaceName + "[gl_InvocationID]." + m_members[i].name, |
| m_members[i].type, 0, indentationDepth, visit); |
| return result; |
| } |
| } |
| |
| string UserDefinedIOCase::IOBlock::glslTraverseBasicType(int indentationDepth, BasicTypeVisitFunc visit) const |
| { |
| string result; |
| for (int i = 0; i < (int)m_members.size(); i++) |
| result += UserDefinedIOCase::glslTraverseBasicTypes(m_interfaceName + "." + m_members[i].name, |
| m_members[i].type, 0, indentationDepth, visit); |
| return result; |
| } |
| |
| int UserDefinedIOCase::Variable::numBasicSubobjectsInElementType(void) const |
| { |
| return numBasicSubobjects(m_type); |
| } |
| |
| int UserDefinedIOCase::IOBlock::numBasicSubobjectsInElementType(void) const |
| { |
| int result = 0; |
| for (int i = 0; i < (int)m_members.size(); i++) |
| result += numBasicSubobjects(m_members[i].type); |
| return result; |
| } |
| |
| string UserDefinedIOCase::Variable::basicSubobjectAtIndex(int subobjectIndex, int arraySize) const |
| { |
| const glu::VarType type = m_isArray ? glu::VarType(m_type, arraySize) : m_type; |
| int currentIndex = 0; |
| |
| for (glu::BasicTypeIterator basicIt = glu::BasicTypeIterator::begin(&type); |
| basicIt != glu::BasicTypeIterator::end(&type); ++basicIt) |
| { |
| if (currentIndex == subobjectIndex) |
| return m_name + de::toString(glu::TypeAccessFormat(type, basicIt.getPath())); |
| currentIndex++; |
| } |
| DE_ASSERT(false); |
| return DE_NULL; |
| } |
| |
| string UserDefinedIOCase::IOBlock::basicSubobjectAtIndex(int subobjectIndex, int arraySize) const |
| { |
| int currentIndex = 0; |
| for (int arrayNdx = 0; arrayNdx < arraySize; arrayNdx++) |
| { |
| for (int memberNdx = 0; memberNdx < (int)m_members.size(); memberNdx++) |
| { |
| const glu::VarType &membType = m_members[memberNdx].type; |
| for (glu::BasicTypeIterator basicIt = glu::BasicTypeIterator::begin(&membType); |
| basicIt != glu::BasicTypeIterator::end(&membType); ++basicIt) |
| { |
| if (currentIndex == subobjectIndex) |
| return m_interfaceName + "[" + de::toString(arrayNdx) + "]." + m_members[memberNdx].name + |
| de::toString(glu::TypeAccessFormat(membType, basicIt.getPath())); |
| currentIndex++; |
| } |
| } |
| } |
| DE_ASSERT(false); |
| return DE_NULL; |
| } |
| |
| // Used as the 'visit' argument for glslTraverseBasicTypes. |
| string UserDefinedIOCase::glslAssignBasicTypeObject(const string &name, glu::DataType type, int indentationDepth) |
| { |
| const int scalarSize = glu::getDataTypeScalarSize(type); |
| const string indentation = string(indentationDepth, '\t'); |
| string result; |
| |
| result += indentation + name + " = "; |
| |
| if (type != glu::TYPE_FLOAT) |
| result += string() + glu::getDataTypeName(type) + "("; |
| for (int i = 0; i < scalarSize; i++) |
| result += (i > 0 ? ", v+" + de::floatToString(0.8f * (float)i, 1) : "v"); |
| if (type != glu::TYPE_FLOAT) |
| result += ")"; |
| result += ";\n" + indentation + "v += 0.4;\n"; |
| return result; |
| } |
| |
| // Used as the 'visit' argument for glslTraverseBasicTypes. |
| string UserDefinedIOCase::glslCheckBasicTypeObject(const string &name, glu::DataType type, int indentationDepth) |
| { |
| const int scalarSize = glu::getDataTypeScalarSize(type); |
| const string indentation = string(indentationDepth, '\t'); |
| string result; |
| |
| result += indentation + "allOk = allOk && compare_" + glu::getDataTypeName(type) + "(" + name + ", "; |
| |
| if (type != glu::TYPE_FLOAT) |
| result += string() + glu::getDataTypeName(type) + "("; |
| for (int i = 0; i < scalarSize; i++) |
| result += (i > 0 ? ", v+" + de::floatToString(0.8f * (float)i, 1) : "v"); |
| if (type != glu::TYPE_FLOAT) |
| result += ")"; |
| result += ");\n" + indentation + "v += 0.4;\n" + indentation + "if (allOk) firstFailedInputIndex++;\n"; |
| |
| return result; |
| } |
| |
| int UserDefinedIOCase::numBasicSubobjectsInElementType(const vector<SharedPtr<TopLevelObject>> &objects) |
| { |
| int result = 0; |
| for (int i = 0; i < (int)objects.size(); i++) |
| result += objects[i]->numBasicSubobjectsInElementType(); |
| return result; |
| } |
| |
| string UserDefinedIOCase::basicSubobjectAtIndex(int subobjectIndex, const vector<SharedPtr<TopLevelObject>> &objects, |
| int topLevelArraySize) |
| { |
| int currentIndex = 0; |
| int objectIndex = 0; |
| for (; currentIndex < subobjectIndex; objectIndex++) |
| currentIndex += objects[objectIndex]->numBasicSubobjectsInElementType() * topLevelArraySize; |
| if (currentIndex > subobjectIndex) |
| { |
| objectIndex--; |
| currentIndex -= objects[objectIndex]->numBasicSubobjectsInElementType() * topLevelArraySize; |
| } |
| |
| return objects[objectIndex]->basicSubobjectAtIndex(subobjectIndex - currentIndex, topLevelArraySize); |
| } |
| |
| void UserDefinedIOCase::init(void) |
| { |
| checkTessellationSupport(m_context); |
| checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE); |
| |
| const bool isPerPatchIO = m_ioType == IO_TYPE_PER_PATCH || m_ioType == IO_TYPE_PER_PATCH_ARRAY || |
| m_ioType == IO_TYPE_PER_PATCH_BLOCK || m_ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY; |
| |
| const bool isExplicitVertexArraySize = m_vertexIOArraySize == VERTEX_IO_ARRAY_SIZE_EXPLICIT_SHADER_BUILTIN || |
| m_vertexIOArraySize == VERTEX_IO_ARRAY_SIZE_EXPLICIT_QUERY; |
| |
| const string vertexAttrArrayInputSize = m_vertexIOArraySize == VERTEX_IO_ARRAY_SIZE_IMPLICIT ? |
| "" : |
| m_vertexIOArraySize == VERTEX_IO_ARRAY_SIZE_EXPLICIT_SHADER_BUILTIN ? |
| "gl_MaxPatchVertices" : |
| m_vertexIOArraySize == VERTEX_IO_ARRAY_SIZE_EXPLICIT_QUERY ? |
| de::toString(m_context.getContextInfo().getInt(GL_MAX_PATCH_VERTICES)) : |
| DE_NULL; |
| |
| const char *const maybePatch = isPerPatchIO ? "patch " : ""; |
| const string outMaybePatch = string() + maybePatch + "out "; |
| const string inMaybePatch = string() + maybePatch + "in "; |
| const bool useBlock = m_ioType == IO_TYPE_PER_VERTEX_BLOCK || m_ioType == IO_TYPE_PER_PATCH_BLOCK || |
| m_ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY; |
| |
| string tcsDeclarations; |
| string tcsStatements; |
| |
| string tesDeclarations; |
| string tesStatements; |
| |
| { |
| m_structTypes.push_back(glu::StructType("S")); |
| |
| const glu::VarType highpFloat(glu::TYPE_FLOAT, glu::PRECISION_HIGHP); |
| glu::StructType &structType = m_structTypes.back(); |
| const glu::VarType structVarType(&structType); |
| bool usedStruct = false; |
| |
| structType.addMember("x", glu::VarType(glu::TYPE_INT, glu::PRECISION_HIGHP)); |
| structType.addMember("y", glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP)); |
| |
| if (useBlock) |
| { |
| // It is illegal to have a structure containing an array as an output variable |
| structType.addMember("z", glu::VarType(highpFloat, 2)); |
| } |
| |
| if (useBlock) |
| { |
| const bool useLightweightBlock = |
| (m_ioType == |
| IO_TYPE_PER_PATCH_BLOCK_ARRAY); // use leaner block to make sure it is not larger than allowed (per-patch storage is very limited) |
| vector<IOBlock::Member> blockMembers; |
| |
| if (!useLightweightBlock) |
| blockMembers.push_back(IOBlock::Member("blockS", structVarType)); |
| |
| blockMembers.push_back(IOBlock::Member("blockFa", glu::VarType(highpFloat, 3))); |
| blockMembers.push_back(IOBlock::Member("blockSa", glu::VarType(structVarType, 2))); |
| blockMembers.push_back(IOBlock::Member("blockF", highpFloat)); |
| |
| m_tcsOutputs.push_back(SharedPtr<TopLevelObject>(new IOBlock("TheBlock", "tcBlock", blockMembers))); |
| m_tesInputs.push_back(SharedPtr<TopLevelObject>(new IOBlock("TheBlock", "teBlock", blockMembers))); |
| |
| usedStruct = true; |
| } |
| else |
| { |
| const Variable var0("in_te_s", structVarType, m_ioType != IO_TYPE_PER_PATCH); |
| const Variable var1("in_te_f", highpFloat, m_ioType != IO_TYPE_PER_PATCH); |
| |
| if (m_ioType != IO_TYPE_PER_PATCH_ARRAY) |
| { |
| // Arrays of structures are disallowed, add struct cases only if not arrayed variable |
| m_tcsOutputs.push_back(SharedPtr<TopLevelObject>(new Variable(var0))); |
| m_tesInputs.push_back(SharedPtr<TopLevelObject>(new Variable(var0))); |
| |
| usedStruct = true; |
| } |
| |
| m_tcsOutputs.push_back(SharedPtr<TopLevelObject>(new Variable(var1))); |
| m_tesInputs.push_back(SharedPtr<TopLevelObject>(new Variable(var1))); |
| } |
| |
| tcsDeclarations += "in " + Variable("in_tc_attr", highpFloat, true).declareArray(vertexAttrArrayInputSize); |
| |
| if (usedStruct) |
| tcsDeclarations += de::toString(glu::declare(structType)) + ";\n"; |
| |
| tcsStatements += "\t{\n" |
| "\t\thighp float v = 1.3;\n"; |
| |
| for (int tcsOutputNdx = 0; tcsOutputNdx < (int)m_tcsOutputs.size(); tcsOutputNdx++) |
| { |
| const TopLevelObject &output = *m_tcsOutputs[tcsOutputNdx]; |
| const int numElements = !isPerPatchIO ? -1 //!< \note -1 means indexing with gl_InstanceID |
| : |
| m_ioType == IO_TYPE_PER_PATCH ? 1 : |
| m_ioType == IO_TYPE_PER_PATCH_ARRAY ? NUM_PER_PATCH_ARRAY_ELEMS : |
| m_ioType == IO_TYPE_PER_PATCH_BLOCK ? 1 : |
| m_ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY ? NUM_PER_PATCH_BLOCKS : |
| -2; |
| const bool isArray = (numElements != 1); |
| |
| DE_ASSERT(numElements != -2); |
| |
| if (isArray) |
| { |
| tcsDeclarations += |
| outMaybePatch + |
| output.declareArray(m_ioType == IO_TYPE_PER_PATCH_ARRAY ? |
| de::toString(int(NUM_PER_PATCH_ARRAY_ELEMS)) : |
| m_ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY ? |
| de::toString(int(NUM_PER_PATCH_BLOCKS)) : |
| m_tessControlOutArraySize == TESS_CONTROL_OUT_ARRAY_SIZE_LAYOUT ? |
| de::toString(int(NUM_OUTPUT_VERTICES)) : |
| m_tessControlOutArraySize == TESS_CONTROL_OUT_ARRAY_SIZE_QUERY ? |
| de::toString(m_context.getContextInfo().getInt(GL_MAX_PATCH_VERTICES)) : |
| m_tessControlOutArraySize == TESS_CONTROL_OUT_ARRAY_SIZE_SHADER_BUILTIN ? |
| "gl_MaxPatchVertices" : |
| ""); |
| } |
| else |
| tcsDeclarations += outMaybePatch + output.declare(); |
| |
| if (!isPerPatchIO) |
| tcsStatements += "\t\tv += float(gl_InvocationID)*" + |
| de::floatToString(0.4f * (float)output.numBasicSubobjectsInElementType(), 1) + ";\n"; |
| |
| tcsStatements += "\n\t\t// Assign values to output " + output.name() + "\n"; |
| if (isArray) |
| tcsStatements += output.glslTraverseBasicTypeArray(numElements, 2, glslAssignBasicTypeObject); |
| else |
| tcsStatements += output.glslTraverseBasicType(2, glslAssignBasicTypeObject); |
| |
| if (!isPerPatchIO) |
| tcsStatements += "\t\tv += float(" + de::toString(int(NUM_OUTPUT_VERTICES)) + "-gl_InvocationID-1)*" + |
| de::floatToString(0.4f * (float)output.numBasicSubobjectsInElementType(), 1) + ";\n"; |
| } |
| tcsStatements += "\t}\n"; |
| |
| if (usedStruct) |
| tesDeclarations += de::toString(glu::declare(structType)) + ";\n"; |
| |
| tesStatements += "\tbool allOk = true;\n" |
| "\thighp uint firstFailedInputIndex = 0u;\n" |
| "\t{\n" |
| "\t\thighp float v = 1.3;\n"; |
| for (int tesInputNdx = 0; tesInputNdx < (int)m_tesInputs.size(); tesInputNdx++) |
| { |
| const TopLevelObject &input = *m_tesInputs[tesInputNdx]; |
| const int numElements = !isPerPatchIO ? (int)NUM_OUTPUT_VERTICES : |
| m_ioType == IO_TYPE_PER_PATCH ? 1 : |
| m_ioType == IO_TYPE_PER_PATCH_BLOCK ? 1 : |
| m_ioType == IO_TYPE_PER_PATCH_ARRAY ? NUM_PER_PATCH_ARRAY_ELEMS : |
| m_ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY ? NUM_PER_PATCH_BLOCKS : |
| -2; |
| const bool isArray = (numElements != 1); |
| |
| DE_ASSERT(numElements != -2); |
| |
| if (isArray) |
| tesDeclarations += inMaybePatch + input.declareArray(m_ioType == IO_TYPE_PER_PATCH_ARRAY ? |
| de::toString(int(NUM_PER_PATCH_ARRAY_ELEMS)) : |
| m_ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY ? |
| de::toString(int(NUM_PER_PATCH_BLOCKS)) : |
| isExplicitVertexArraySize ? |
| de::toString(vertexAttrArrayInputSize) : |
| ""); |
| else |
| tesDeclarations += inMaybePatch + input.declare(); |
| |
| tesStatements += "\n\t\t// Check values in input " + input.name() + "\n"; |
| if (isArray) |
| tesStatements += input.glslTraverseBasicTypeArray(numElements, 2, glslCheckBasicTypeObject); |
| else |
| tesStatements += input.glslTraverseBasicType(2, glslCheckBasicTypeObject); |
| } |
| tesStatements += "\t}\n"; |
| } |
| |
| std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "\n" |
| "in highp float in_v_attr;\n" |
| "out highp float in_tc_attr;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " in_tc_attr = in_v_attr;\n" |
| "}\n"); |
| std::string tessellationControlTemplate("${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT_ARR}\n" |
| "\n" |
| "layout (vertices = " + |
| de::toString(int(NUM_OUTPUT_VERTICES)) + |
| ") out;\n" |
| "\n" + |
| tcsDeclarations + |
| "\n" |
| "patch out highp vec2 in_te_positionScale;\n" |
| "patch out highp vec2 in_te_positionOffset;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" + |
| tcsStatements + |
| "\n" |
| " in_te_positionScale = vec2(in_tc_attr[6], in_tc_attr[7]);\n" |
| " in_te_positionOffset = vec2(in_tc_attr[8], in_tc_attr[9]);\n" |
| "\n" |
| " gl_TessLevelInner[0] = in_tc_attr[0];\n" |
| " gl_TessLevelInner[1] = in_tc_attr[1];\n" |
| "\n" |
| " gl_TessLevelOuter[0] = in_tc_attr[2];\n" |
| " gl_TessLevelOuter[1] = in_tc_attr[3];\n" |
| " gl_TessLevelOuter[2] = in_tc_attr[4];\n" |
| " gl_TessLevelOuter[3] = in_tc_attr[5];\n" |
| "}\n"); |
| std::string tessellationEvaluationTemplate( |
| "${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "\n" + |
| getTessellationEvaluationInLayoutString(m_primitiveType) + "\n" + tesDeclarations + |
| "\n" |
| "patch in highp vec2 in_te_positionScale;\n" |
| "patch in highp vec2 in_te_positionOffset;\n" |
| "\n" |
| "out highp vec4 in_f_color;\n" |
| "// Will contain the index of the first incorrect input,\n" |
| "// or the number of inputs if all are correct\n" |
| "flat out highp uint out_te_firstFailedInputIndex;\n" |
| "\n" |
| "bool compare_int (int a, int b) { return a == b; }\n" |
| "bool compare_float (float a, float b) { return abs(a - b) < 0.01f; }\n" |
| "bool compare_vec4 (vec4 a, vec4 b) { return all(lessThan(abs(a - b), vec4(0.01f))); }\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" + |
| tesStatements + |
| "\n" |
| " gl_Position = vec4(gl_TessCoord.xy*in_te_positionScale + in_te_positionOffset, 0.0, 1.0);\n" |
| " in_f_color = allOk ? vec4(0.0, 1.0, 0.0, 1.0)\n" |
| " : vec4(1.0, 0.0, 0.0, 1.0);\n" |
| " out_te_firstFailedInputIndex = firstFailedInputIndex;\n" |
| "}\n"); |
| std::string fragmentShaderTemplate("${GLSL_VERSION_DECL}\n" |
| "\n" |
| "layout (location = 0) out mediump vec4 o_color;\n" |
| "\n" |
| "in highp 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())) |
| << glu::TransformFeedbackVarying("out_te_firstFailedInputIndex") |
| << glu::TransformFeedbackMode(GL_INTERLEAVED_ATTRIBS))); |
| |
| m_testCtx.getLog() << *m_program; |
| if (!m_program->isOk()) |
| TCU_FAIL("Program compilation failed"); |
| } |
| |
| void UserDefinedIOCase::deinit(void) |
| { |
| m_program.clear(); |
| } |
| |
| UserDefinedIOCase::IterateResult UserDefinedIOCase::iterate(void) |
| { |
| typedef TransformFeedbackHandler<uint32_t> TFHandler; |
| |
| TestLog &log = m_testCtx.getLog(); |
| const RenderContext &renderCtx = m_context.getRenderContext(); |
| const RandomViewport viewport(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName())); |
| const glw::Functions &gl = renderCtx.getFunctions(); |
| static const float attributes[6 + 2 + 2] = { |
| /* inner */ 3.0f, 4.0f, /* outer */ 5.0f, 6.0f, 7.0f, 8.0f, |
| /* pos. scale */ 1.2f, 1.3f, /* pos. offset */ -0.3f, -0.4f}; |
| const uint32_t programGL = m_program->getProgram(); |
| const int numVertices = |
| referenceVertexCount(m_primitiveType, SPACINGMODE_EQUAL, false, &attributes[0], &attributes[2]); |
| const TFHandler tfHandler(renderCtx, numVertices); |
| tcu::ResultCollector result; |
| |
| gl.useProgram(programGL); |
| setViewport(gl, viewport); |
| gl.patchParameteri(GL_PATCH_VERTICES, DE_LENGTH_OF_ARRAY(attributes)); |
| |
| gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f); |
| gl.clear(GL_COLOR_BUFFER_BIT); |
| |
| { |
| const glu::VertexArrayBinding bindings[] = { |
| glu::va::Float("in_v_attr", 1, DE_LENGTH_OF_ARRAY(attributes), 0, &attributes[0])}; |
| const TFHandler::Result tfResult = tfHandler.renderAndGetPrimitives( |
| programGL, outputPrimitiveTypeGL(m_primitiveType, false), DE_LENGTH_OF_ARRAY(bindings), &bindings[0], |
| DE_LENGTH_OF_ARRAY(attributes)); |
| |
| { |
| const tcu::Surface pixels = getPixels(renderCtx, viewport); |
| const tcu::TextureLevel reference = getPNG(m_testCtx.getArchive(), m_referenceImagePath.c_str()); |
| const bool success = tcu::fuzzyCompare(log, "ImageComparison", "Image Comparison", reference.getAccess(), |
| pixels.getAccess(), 0.02f, tcu::COMPARE_LOG_RESULT); |
| |
| if (!success) |
| result.fail("Image comparison failed"); |
| } |
| |
| if ((int)tfResult.varying.size() != numVertices) |
| { |
| log << TestLog::Message << "Failure: transform feedback returned " << tfResult.varying.size() |
| << " vertices; expected " << numVertices << TestLog::EndMessage; |
| result.fail("Wrong number of vertices"); |
| } |
| else |
| { |
| const int topLevelArraySize = (m_ioType == IO_TYPE_PER_PATCH ? 1 : |
| m_ioType == IO_TYPE_PER_PATCH_ARRAY ? NUM_PER_PATCH_ARRAY_ELEMS : |
| m_ioType == IO_TYPE_PER_PATCH_BLOCK ? 1 : |
| m_ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY ? NUM_PER_PATCH_BLOCKS : |
| (int)NUM_OUTPUT_VERTICES); |
| const int numTEInputs = numBasicSubobjectsInElementType(m_tesInputs) * topLevelArraySize; |
| |
| for (int vertexNdx = 0; vertexNdx < (int)numVertices; vertexNdx++) |
| { |
| if (tfResult.varying[vertexNdx] > (uint32_t)numTEInputs) |
| { |
| log << TestLog::Message << "Failure: out_te_firstFailedInputIndex has value " |
| << tfResult.varying[vertexNdx] << ", should be in range [0, " << numTEInputs << "]" |
| << TestLog::EndMessage; |
| result.fail("Invalid transform feedback output"); |
| } |
| else if (tfResult.varying[vertexNdx] != (uint32_t)numTEInputs) |
| { |
| log << TestLog::Message << "Failure: in tessellation evaluation shader, check for input " |
| << basicSubobjectAtIndex(tfResult.varying[vertexNdx], m_tesInputs, topLevelArraySize) |
| << " failed" << TestLog::EndMessage; |
| result.fail("Invalid input value in tessellation evaluation shader"); |
| } |
| } |
| } |
| } |
| |
| result.setTestContextResult(m_testCtx); |
| return STOP; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Pass gl_Position between VS and TCS, or between TCS and TES. |
| * |
| * In TCS gl_Position is in the gl_out[] block and in TES in the gl_in[] |
| * block, and has no special semantics in those. Arbitrary vec4 data can |
| * thus be passed there. |
| *//*--------------------------------------------------------------------*/ |
| class GLPositionCase : public TestCase |
| { |
| public: |
| enum CaseType |
| { |
| CASETYPE_VS_TO_TCS = 0, |
| CASETYPE_TCS_TO_TES, |
| CASETYPE_VS_TO_TCS_TO_TES, |
| |
| CASETYPE_LAST |
| }; |
| |
| GLPositionCase(Context &context, const char *name, const char *description, CaseType caseType, |
| const char *referenceImagePath) |
| : TestCase(context, name, description) |
| , m_caseType(caseType) |
| , m_referenceImagePath(referenceImagePath) |
| { |
| } |
| |
| void init(void); |
| void deinit(void); |
| IterateResult iterate(void); |
| |
| static const char *getCaseTypeName(CaseType type); |
| |
| private: |
| static const int RENDER_SIZE = 256; |
| |
| const CaseType m_caseType; |
| const string m_referenceImagePath; |
| |
| SharedPtr<const glu::ShaderProgram> m_program; |
| }; |
| |
| const char *GLPositionCase::getCaseTypeName(CaseType type) |
| { |
| switch (type) |
| { |
| case CASETYPE_VS_TO_TCS: |
| return "gl_position_vs_to_tcs"; |
| case CASETYPE_TCS_TO_TES: |
| return "gl_position_tcs_to_tes"; |
| case CASETYPE_VS_TO_TCS_TO_TES: |
| return "gl_position_vs_to_tcs_to_tes"; |
| default: |
| DE_ASSERT(false); |
| return DE_NULL; |
| } |
| } |
| |
| void GLPositionCase::init(void) |
| { |
| checkTessellationSupport(m_context); |
| checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE); |
| |
| const bool vsToTCS = m_caseType == CASETYPE_VS_TO_TCS || m_caseType == CASETYPE_VS_TO_TCS_TO_TES; |
| const bool tcsToTES = m_caseType == CASETYPE_TCS_TO_TES || m_caseType == CASETYPE_VS_TO_TCS_TO_TES; |
| |
| const string tesIn0 = tcsToTES ? "gl_in[0].gl_Position" : "in_te_attr[0]"; |
| const string tesIn1 = tcsToTES ? "gl_in[1].gl_Position" : "in_te_attr[1]"; |
| const string tesIn2 = tcsToTES ? "gl_in[2].gl_Position" : "in_te_attr[2]"; |
| |
| std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "\n" |
| "in highp vec4 in_v_attr;\n" + |
| string(!vsToTCS ? "out highp vec4 in_tc_attr;\n" : "") + |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " " + |
| (vsToTCS ? "gl_Position" : "in_tc_attr") + |
| " = in_v_attr;\n" |
| "}\n"); |
| std::string tessellationControlTemplate( |
| "${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT_ARR}\n" |
| "\n" |
| "layout (vertices = 3) out;\n" |
| "\n" + |
| string(!vsToTCS ? "in highp vec4 in_tc_attr[];\n" : "") + "\n" + |
| (!tcsToTES ? "out highp vec4 in_te_attr[];\n" : "") + |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " " + |
| (tcsToTES ? "gl_out[gl_InvocationID].gl_Position" : "in_te_attr[gl_InvocationID]") + " = " + |
| (vsToTCS ? "gl_in[gl_InvocationID].gl_Position" : "in_tc_attr[gl_InvocationID]") + |
| ";\n" |
| "\n" |
| " gl_TessLevelInner[0] = 2.0;\n" |
| " gl_TessLevelInner[1] = 3.0;\n" |
| "\n" |
| " gl_TessLevelOuter[0] = 4.0;\n" |
| " gl_TessLevelOuter[1] = 5.0;\n" |
| " gl_TessLevelOuter[2] = 6.0;\n" |
| " gl_TessLevelOuter[3] = 7.0;\n" |
| "}\n"); |
| std::string tessellationEvaluationTemplate("${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "\n" + |
| getTessellationEvaluationInLayoutString(TESSPRIMITIVETYPE_TRIANGLES) + |
| "\n" + (!tcsToTES ? "in highp vec4 in_te_attr[];\n" : "") + |
| "\n" |
| "out highp vec4 in_f_color;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " highp vec2 xy = gl_TessCoord.x * " + |
| tesIn0 + |
| ".xy\n" |
| " + gl_TessCoord.y * " + |
| tesIn1 + |
| ".xy\n" |
| " + gl_TessCoord.z * " + |
| tesIn2 + |
| ".xy;\n" |
| " gl_Position = vec4(xy, 0.0, 1.0);\n" |
| " in_f_color = vec4(" + |
| tesIn0 + ".z + " + tesIn1 + |
| ".w,\n" |
| " " + |
| tesIn2 + ".z + " + tesIn0 + |
| ".w,\n" |
| " " + |
| tesIn1 + ".z + " + tesIn2 + |
| ".w,\n" |
| " 1.0);\n" |
| "}\n"); |
| std::string fragmentShaderTemplate("${GLSL_VERSION_DECL}\n" |
| "\n" |
| "layout (location = 0) out mediump vec4 o_color;\n" |
| "\n" |
| "in highp 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 GLPositionCase::deinit(void) |
| { |
| m_program.clear(); |
| } |
| |
| GLPositionCase::IterateResult GLPositionCase::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 glw::Functions &gl = renderCtx.getFunctions(); |
| const uint32_t programGL = m_program->getProgram(); |
| |
| static const float attributes[3 * 4] = {-0.8f, -0.7f, 0.1f, 0.7f, -0.5f, 0.4f, 0.2f, 0.5f, 0.3f, 0.2f, 0.3f, 0.45f}; |
| |
| gl.useProgram(programGL); |
| setViewport(gl, viewport); |
| gl.patchParameteri(GL_PATCH_VERTICES, 3); |
| |
| gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f); |
| gl.clear(GL_COLOR_BUFFER_BIT); |
| |
| log << TestLog::Message << "Note: input data for in_v_attr:\n" << arrayStr(attributes, 4) << TestLog::EndMessage; |
| |
| { |
| const glu::VertexArrayBinding bindings[] = {glu::va::Float("in_v_attr", 4, 3, 0, &attributes[0])}; |
| glu::draw(renderCtx, programGL, DE_LENGTH_OF_ARRAY(bindings), &bindings[0], glu::pr::Patches(3)); |
| |
| { |
| const tcu::Surface pixels = getPixels(renderCtx, viewport); |
| const tcu::TextureLevel reference = getPNG(m_testCtx.getArchive(), m_referenceImagePath.c_str()); |
| const bool success = tcu::fuzzyCompare(log, "ImageComparison", "Image Comparison", reference.getAccess(), |
| pixels.getAccess(), 0.02f, 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; |
| } |
| |
| class LimitQueryCase : public TestCase |
| { |
| public: |
| LimitQueryCase(Context &context, const char *name, const char *desc, glw::GLenum target, int minValue); |
| |
| private: |
| IterateResult iterate(void); |
| |
| const glw::GLenum m_target; |
| const int m_minValue; |
| }; |
| |
| LimitQueryCase::LimitQueryCase(Context &context, const char *name, const char *desc, glw::GLenum target, int minValue) |
| : TestCase(context, name, desc) |
| , m_target(target) |
| , m_minValue(minValue) |
| { |
| } |
| |
| LimitQueryCase::IterateResult LimitQueryCase::iterate(void) |
| { |
| checkTessellationSupport(m_context); |
| |
| glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog()); |
| tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: "); |
| |
| gl.enableLogging(true); |
| verifyStateIntegerMin(result, gl, m_target, m_minValue, QUERY_INTEGER); |
| |
| { |
| const tcu::ScopedLogSection section(m_testCtx.getLog(), "Types", "Alternative queries"); |
| verifyStateIntegerMin(result, gl, m_target, m_minValue, QUERY_BOOLEAN); |
| verifyStateIntegerMin(result, gl, m_target, m_minValue, QUERY_INTEGER64); |
| verifyStateIntegerMin(result, gl, m_target, m_minValue, QUERY_FLOAT); |
| } |
| |
| result.setTestContextResult(m_testCtx); |
| return STOP; |
| } |
| |
| class CombinedUniformLimitCase : public TestCase |
| { |
| public: |
| CombinedUniformLimitCase(Context &context, const char *name, const char *desc, glw::GLenum combined, |
| glw::GLenum numBlocks, glw::GLenum defaultComponents); |
| |
| private: |
| IterateResult iterate(void); |
| |
| const glw::GLenum m_combined; |
| const glw::GLenum m_numBlocks; |
| const glw::GLenum m_defaultComponents; |
| }; |
| |
| CombinedUniformLimitCase::CombinedUniformLimitCase(Context &context, const char *name, const char *desc, |
| glw::GLenum combined, glw::GLenum numBlocks, |
| glw::GLenum defaultComponents) |
| : TestCase(context, name, desc) |
| , m_combined(combined) |
| , m_numBlocks(numBlocks) |
| , m_defaultComponents(defaultComponents) |
| { |
| } |
| |
| CombinedUniformLimitCase::IterateResult CombinedUniformLimitCase::iterate(void) |
| { |
| checkTessellationSupport(m_context); |
| |
| glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog()); |
| tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: "); |
| |
| gl.enableLogging(true); |
| |
| m_testCtx.getLog() << tcu::TestLog::Message << "The minimum value of " << glu::getGettableStateStr(m_combined) |
| << " is " << glu::getGettableStateStr(m_numBlocks) << " x MAX_UNIFORM_BLOCK_SIZE / 4 + " |
| << glu::getGettableStateStr(m_defaultComponents) << tcu::TestLog::EndMessage; |
| |
| StateQueryMemoryWriteGuard<glw::GLint> maxUniformBlocks; |
| gl.glGetIntegerv(m_numBlocks, &maxUniformBlocks); |
| GLS_COLLECT_GL_ERROR(result, gl.glGetError(), "glGetIntegerv"); |
| |
| StateQueryMemoryWriteGuard<glw::GLint> maxUniformBlockSize; |
| gl.glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &maxUniformBlockSize); |
| GLS_COLLECT_GL_ERROR(result, gl.glGetError(), "glGetIntegerv"); |
| |
| StateQueryMemoryWriteGuard<glw::GLint> maxUniformComponents; |
| gl.glGetIntegerv(m_defaultComponents, &maxUniformComponents); |
| GLS_COLLECT_GL_ERROR(result, gl.glGetError(), "glGetIntegerv"); |
| |
| if (maxUniformBlocks.verifyValidity(result) && maxUniformBlockSize.verifyValidity(result) && |
| maxUniformComponents.verifyValidity(result)) |
| { |
| const int limit = ((int)maxUniformBlocks) * ((int)maxUniformBlockSize) / 4 + (int)maxUniformComponents; |
| verifyStateIntegerMin(result, gl, m_combined, limit, QUERY_INTEGER); |
| |
| { |
| const tcu::ScopedLogSection section(m_testCtx.getLog(), "Types", "Alternative queries"); |
| verifyStateIntegerMin(result, gl, m_combined, limit, QUERY_BOOLEAN); |
| verifyStateIntegerMin(result, gl, m_combined, limit, QUERY_INTEGER64); |
| verifyStateIntegerMin(result, gl, m_combined, limit, QUERY_FLOAT); |
| } |
| } |
| |
| result.setTestContextResult(m_testCtx); |
| return STOP; |
| } |
| |
| class PatchVerticesStateCase : public TestCase |
| { |
| public: |
| PatchVerticesStateCase(Context &context, const char *name, const char *desc); |
| |
| private: |
| IterateResult iterate(void); |
| }; |
| |
| PatchVerticesStateCase::PatchVerticesStateCase(Context &context, const char *name, const char *desc) |
| : TestCase(context, name, desc) |
| { |
| } |
| |
| PatchVerticesStateCase::IterateResult PatchVerticesStateCase::iterate(void) |
| { |
| checkTessellationSupport(m_context); |
| |
| glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog()); |
| tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: "); |
| |
| gl.enableLogging(true); |
| |
| // initial |
| { |
| const tcu::ScopedLogSection section(m_testCtx.getLog(), "initial", "Initial value"); |
| |
| verifyStateInteger(result, gl, GL_PATCH_VERTICES, 3, QUERY_INTEGER); |
| } |
| |
| // bind |
| { |
| const tcu::ScopedLogSection section(m_testCtx.getLog(), "set", "After set"); |
| |
| gl.glPatchParameteri(GL_PATCH_VERTICES, 22); |
| GLS_COLLECT_GL_ERROR(result, gl.glGetError(), "glPatchParameteri"); |
| |
| verifyStateInteger(result, gl, GL_PATCH_VERTICES, 22, QUERY_INTEGER); |
| { |
| const tcu::ScopedLogSection subsection(m_testCtx.getLog(), "Types", "Alternative queries"); |
| verifyStateIntegerMin(result, gl, GL_PATCH_VERTICES, 22, QUERY_BOOLEAN); |
| verifyStateIntegerMin(result, gl, GL_PATCH_VERTICES, 22, QUERY_INTEGER64); |
| verifyStateIntegerMin(result, gl, GL_PATCH_VERTICES, 22, QUERY_FLOAT); |
| } |
| } |
| |
| result.setTestContextResult(m_testCtx); |
| return STOP; |
| } |
| |
| class PrimitiveRestartForPatchesSupportedCase : public TestCase |
| { |
| public: |
| PrimitiveRestartForPatchesSupportedCase(Context &context, const char *name, const char *desc); |
| |
| private: |
| IterateResult iterate(void); |
| }; |
| |
| PrimitiveRestartForPatchesSupportedCase::PrimitiveRestartForPatchesSupportedCase(Context &context, const char *name, |
| const char *desc) |
| : TestCase(context, name, desc) |
| { |
| } |
| |
| PrimitiveRestartForPatchesSupportedCase::IterateResult PrimitiveRestartForPatchesSupportedCase::iterate(void) |
| { |
| checkTessellationSupport(m_context); |
| |
| glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog()); |
| tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: "); |
| QueriedState state; |
| |
| gl.enableLogging(true); |
| |
| queryState(result, gl, QUERY_BOOLEAN, GL_PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED, state); |
| |
| if (!state.isUndefined()) |
| { |
| const tcu::ScopedLogSection subsection(m_testCtx.getLog(), "Types", "Alternative types"); |
| verifyStateBoolean(result, gl, GL_PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED, state.getBoolAccess(), |
| QUERY_INTEGER); |
| verifyStateBoolean(result, gl, GL_PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED, state.getBoolAccess(), |
| QUERY_INTEGER64); |
| verifyStateBoolean(result, gl, GL_PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED, state.getBoolAccess(), QUERY_FLOAT); |
| } |
| |
| result.setTestContextResult(m_testCtx); |
| return STOP; |
| } |
| |
| class TessProgramQueryCase : public TestCase |
| { |
| public: |
| TessProgramQueryCase(Context &context, const char *name, const char *desc); |
| |
| std::string getVertexSource(void) const; |
| std::string getFragmentSource(void) const; |
| std::string getTessCtrlSource(const char *globalLayouts) const; |
| std::string getTessEvalSource(const char *globalLayouts) const; |
| }; |
| |
| TessProgramQueryCase::TessProgramQueryCase(Context &context, const char *name, const char *desc) |
| : TestCase(context, name, desc) |
| { |
| } |
| |
| std::string TessProgramQueryCase::getVertexSource(void) const |
| { |
| return "${GLSL_VERSION_DECL}\n" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "void main (void)\n" |
| "{\n" |
| " gl_Position = vec4(float(gl_VertexID), float(gl_VertexID / 2), 0.0, 1.0);\n" |
| "}\n"; |
| } |
| |
| std::string TessProgramQueryCase::getFragmentSource(void) const |
| { |
| return "${GLSL_VERSION_DECL}\n" |
| "layout (location = 0) out mediump vec4 o_color;\n" |
| "void main (void)\n" |
| "{\n" |
| " o_color = vec4(0.0, 1.0, 0.0, 1.0);\n" |
| "}\n"; |
| } |
| |
| std::string TessProgramQueryCase::getTessCtrlSource(const char *globalLayouts) const |
| { |
| return "${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT_ARR}\n" + |
| std::string(globalLayouts) + |
| ";\n" |
| "void main (void)\n" |
| "{\n" |
| " gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n" |
| " gl_TessLevelInner[0] = 2.8;\n" |
| " gl_TessLevelInner[1] = 2.8;\n" |
| " gl_TessLevelOuter[0] = 2.8;\n" |
| " gl_TessLevelOuter[1] = 2.8;\n" |
| " gl_TessLevelOuter[2] = 2.8;\n" |
| " gl_TessLevelOuter[3] = 2.8;\n" |
| "}\n"; |
| } |
| |
| std::string TessProgramQueryCase::getTessEvalSource(const char *globalLayouts) const |
| { |
| return "${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT}\n" + |
| std::string(globalLayouts) + |
| ";\n" |
| "void main (void)\n" |
| "{\n" |
| " gl_Position = gl_TessCoord.x * gl_in[0].gl_Position\n" |
| " + gl_TessCoord.y * gl_in[1].gl_Position\n" |
| " + gl_TessCoord.y * gl_in[2].gl_Position\n" |
| " + gl_TessCoord.z * gl_in[3].gl_Position;\n" |
| "}\n"; |
| } |
| |
| class TessControlOutputVerticesCase : public TessProgramQueryCase |
| { |
| public: |
| TessControlOutputVerticesCase(Context &context, const char *name, const char *desc); |
| |
| private: |
| IterateResult iterate(void); |
| }; |
| |
| TessControlOutputVerticesCase::TessControlOutputVerticesCase(Context &context, const char *name, const char *desc) |
| : TessProgramQueryCase(context, name, desc) |
| { |
| } |
| |
| TessControlOutputVerticesCase::IterateResult TessControlOutputVerticesCase::iterate(void) |
| { |
| checkTessellationSupport(m_context); |
| |
| glu::ShaderProgram program( |
| m_context.getRenderContext(), |
| glu::ProgramSources() << glu::VertexSource(specializeShader(m_context, getVertexSource().c_str())) |
| << glu::FragmentSource(specializeShader(m_context, getFragmentSource().c_str())) |
| << glu::TessellationControlSource( |
| specializeShader(m_context, getTessCtrlSource("layout(vertices=4) out").c_str())) |
| << glu::TessellationEvaluationSource( |
| specializeShader(m_context, getTessEvalSource("layout(triangles) in").c_str()))); |
| |
| m_testCtx.getLog() << program; |
| if (!program.isOk()) |
| throw tcu::TestError("failed to build program"); |
| |
| { |
| glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog()); |
| tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: "); |
| |
| gl.enableLogging(true); |
| verifyStateProgramInteger(result, gl, program.getProgram(), GL_TESS_CONTROL_OUTPUT_VERTICES, 4, |
| QUERY_PROGRAM_INTEGER); |
| |
| result.setTestContextResult(m_testCtx); |
| } |
| return STOP; |
| } |
| |
| class TessGenModeQueryCase : public TessProgramQueryCase |
| { |
| public: |
| TessGenModeQueryCase(Context &context, const char *name, const char *desc); |
| |
| private: |
| IterateResult iterate(void); |
| }; |
| |
| TessGenModeQueryCase::TessGenModeQueryCase(Context &context, const char *name, const char *desc) |
| : TessProgramQueryCase(context, name, desc) |
| { |
| } |
| |
| TessGenModeQueryCase::IterateResult TessGenModeQueryCase::iterate(void) |
| { |
| tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: "); |
| glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog()); |
| |
| static const struct |
| { |
| const char *description; |
| const char *layout; |
| glw::GLenum mode; |
| } s_modes[] = { |
| {"Triangles", "layout(triangles) in", GL_TRIANGLES}, |
| {"Isolines", "layout(isolines) in", GL_ISOLINES}, |
| {"Quads", "layout(quads) in", GL_QUADS}, |
| }; |
| |
| checkTessellationSupport(m_context); |
| gl.enableLogging(true); |
| |
| for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_modes); ++ndx) |
| { |
| const tcu::ScopedLogSection section(m_testCtx.getLog(), "Type", s_modes[ndx].description); |
| |
| glu::ShaderProgram program(m_context.getRenderContext(), |
| glu::ProgramSources() |
| << glu::VertexSource(specializeShader(m_context, getVertexSource().c_str())) |
| << glu::FragmentSource(specializeShader(m_context, getFragmentSource().c_str())) |
| << glu::TessellationControlSource(specializeShader( |
| m_context, getTessCtrlSource("layout(vertices=6) out").c_str())) |
| << glu::TessellationEvaluationSource(specializeShader( |
| m_context, getTessEvalSource(s_modes[ndx].layout).c_str()))); |
| |
| m_testCtx.getLog() << program; |
| if (!program.isOk()) |
| result.fail("failed to build program"); |
| else |
| verifyStateProgramInteger(result, gl, program.getProgram(), GL_TESS_GEN_MODE, s_modes[ndx].mode, |
| QUERY_PROGRAM_INTEGER); |
| } |
| |
| result.setTestContextResult(m_testCtx); |
| return STOP; |
| } |
| |
| class TessGenSpacingQueryCase : public TessProgramQueryCase |
| { |
| public: |
| TessGenSpacingQueryCase(Context &context, const char *name, const char *desc); |
| |
| private: |
| IterateResult iterate(void); |
| }; |
| |
| TessGenSpacingQueryCase::TessGenSpacingQueryCase(Context &context, const char *name, const char *desc) |
| : TessProgramQueryCase(context, name, desc) |
| { |
| } |
| |
| TessGenSpacingQueryCase::IterateResult TessGenSpacingQueryCase::iterate(void) |
| { |
| tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: "); |
| glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog()); |
| |
| static const struct |
| { |
| const char *description; |
| const char *layout; |
| glw::GLenum spacing; |
| } s_modes[] = { |
| {"Default spacing", "layout(triangles) in", GL_EQUAL}, |
| {"Equal spacing", "layout(triangles, equal_spacing) in", GL_EQUAL}, |
| {"Fractional even spacing", "layout(triangles, fractional_even_spacing) in", GL_FRACTIONAL_EVEN}, |
| {"Fractional odd spacing", "layout(triangles, fractional_odd_spacing) in", GL_FRACTIONAL_ODD}, |
| }; |
| |
| checkTessellationSupport(m_context); |
| gl.enableLogging(true); |
| |
| for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_modes); ++ndx) |
| { |
| const tcu::ScopedLogSection section(m_testCtx.getLog(), "Type", s_modes[ndx].description); |
| |
| glu::ShaderProgram program(m_context.getRenderContext(), |
| glu::ProgramSources() |
| << glu::VertexSource(specializeShader(m_context, getVertexSource().c_str())) |
| << glu::FragmentSource(specializeShader(m_context, getFragmentSource().c_str())) |
| << glu::TessellationControlSource(specializeShader( |
| m_context, getTessCtrlSource("layout(vertices=6) out").c_str())) |
| << glu::TessellationEvaluationSource(specializeShader( |
| m_context, getTessEvalSource(s_modes[ndx].layout).c_str()))); |
| |
| m_testCtx.getLog() << program; |
| if (!program.isOk()) |
| result.fail("failed to build program"); |
| else |
| verifyStateProgramInteger(result, gl, program.getProgram(), GL_TESS_GEN_SPACING, s_modes[ndx].spacing, |
| QUERY_PROGRAM_INTEGER); |
| } |
| |
| result.setTestContextResult(m_testCtx); |
| return STOP; |
| } |
| |
| class TessGenVertexOrderQueryCase : public TessProgramQueryCase |
| { |
| public: |
| TessGenVertexOrderQueryCase(Context &context, const char *name, const char *desc); |
| |
| private: |
| IterateResult iterate(void); |
| }; |
| |
| TessGenVertexOrderQueryCase::TessGenVertexOrderQueryCase(Context &context, const char *name, const char *desc) |
| : TessProgramQueryCase(context, name, desc) |
| { |
| } |
| |
| TessGenVertexOrderQueryCase::IterateResult TessGenVertexOrderQueryCase::iterate(void) |
| { |
| tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: "); |
| glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog()); |
| |
| static const struct |
| { |
| const char *description; |
| const char *layout; |
| glw::GLenum order; |
| } s_modes[] = { |
| {"Default order", "layout(triangles) in", GL_CCW}, |
| {"CW order", "layout(triangles, cw) in", GL_CW}, |
| {"CCW order", "layout(triangles, ccw) in", GL_CCW}, |
| }; |
| |
| checkTessellationSupport(m_context); |
| gl.enableLogging(true); |
| |
| for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_modes); ++ndx) |
| { |
| const tcu::ScopedLogSection section(m_testCtx.getLog(), "Type", s_modes[ndx].description); |
| |
| glu::ShaderProgram program(m_context.getRenderContext(), |
| glu::ProgramSources() |
| << glu::VertexSource(specializeShader(m_context, getVertexSource().c_str())) |
| << glu::FragmentSource(specializeShader(m_context, getFragmentSource().c_str())) |
| << glu::TessellationControlSource(specializeShader( |
| m_context, getTessCtrlSource("layout(vertices=6) out").c_str())) |
| << glu::TessellationEvaluationSource(specializeShader( |
| m_context, getTessEvalSource(s_modes[ndx].layout).c_str()))); |
| |
| m_testCtx.getLog() << program; |
| if (!program.isOk()) |
| result.fail("failed to build program"); |
| else |
| verifyStateProgramInteger(result, gl, program.getProgram(), GL_TESS_GEN_VERTEX_ORDER, s_modes[ndx].order, |
| QUERY_PROGRAM_INTEGER); |
| } |
| |
| result.setTestContextResult(m_testCtx); |
| return STOP; |
| } |
| |
| class TessGenPointModeQueryCase : public TessProgramQueryCase |
| { |
| public: |
| TessGenPointModeQueryCase(Context &context, const char *name, const char *desc); |
| |
| private: |
| IterateResult iterate(void); |
| }; |
| |
| TessGenPointModeQueryCase::TessGenPointModeQueryCase(Context &context, const char *name, const char *desc) |
| : TessProgramQueryCase(context, name, desc) |
| { |
| } |
| |
| TessGenPointModeQueryCase::IterateResult TessGenPointModeQueryCase::iterate(void) |
| { |
| tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: "); |
| glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog()); |
| |
| static const struct |
| { |
| const char *description; |
| const char *layout; |
| glw::GLenum mode; |
| } s_modes[] = { |
| {"Default mode", "layout(triangles) in", GL_FALSE}, |
| {"Point mode", "layout(triangles, point_mode) in", GL_TRUE}, |
| }; |
| |
| checkTessellationSupport(m_context); |
| gl.enableLogging(true); |
| |
| for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_modes); ++ndx) |
| { |
| const tcu::ScopedLogSection section(m_testCtx.getLog(), "Type", s_modes[ndx].description); |
| |
| glu::ShaderProgram program(m_context.getRenderContext(), |
| glu::ProgramSources() |
| << glu::VertexSource(specializeShader(m_context, getVertexSource().c_str())) |
| << glu::FragmentSource(specializeShader(m_context, getFragmentSource().c_str())) |
| << glu::TessellationControlSource(specializeShader( |
| m_context, getTessCtrlSource("layout(vertices=6) out").c_str())) |
| << glu::TessellationEvaluationSource(specializeShader( |
| m_context, getTessEvalSource(s_modes[ndx].layout).c_str()))); |
| |
| m_testCtx.getLog() << program; |
| if (!program.isOk()) |
| result.fail("failed to build program"); |
| else |
| verifyStateProgramInteger(result, gl, program.getProgram(), GL_TESS_GEN_POINT_MODE, s_modes[ndx].mode, |
| QUERY_PROGRAM_INTEGER); |
| } |
| |
| result.setTestContextResult(m_testCtx); |
| return STOP; |
| } |
| |
| class ReferencedByTessellationQueryCase : public TestCase |
| { |
| public: |
| ReferencedByTessellationQueryCase(Context &context, const char *name, const char *desc, bool isCtrlCase); |
| |
| private: |
| void init(void); |
| IterateResult iterate(void); |
| |
| std::string getVertexSource(void) const; |
| std::string getFragmentSource(void) const; |
| std::string getTessCtrlSource(void) const; |
| std::string getTessEvalSource(void) const; |
| |
| const bool m_isCtrlCase; |
| }; |
| |
| ReferencedByTessellationQueryCase::ReferencedByTessellationQueryCase(Context &context, const char *name, |
| const char *desc, bool isCtrlCase) |
| : TestCase(context, name, desc) |
| , m_isCtrlCase(isCtrlCase) |
| { |
| } |
| |
| void ReferencedByTessellationQueryCase::init(void) |
| { |
| checkTessellationSupport(m_context); |
| } |
| |
| ReferencedByTessellationQueryCase::IterateResult ReferencedByTessellationQueryCase::iterate(void) |
| { |
| tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: "); |
| glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog()); |
| glu::ShaderProgram program(m_context.getRenderContext(), |
| glu::ProgramSources() |
| << glu::VertexSource(specializeShader(m_context, getVertexSource().c_str())) |
| << glu::FragmentSource(specializeShader(m_context, getFragmentSource().c_str())) |
| << glu::TessellationControlSource( |
| specializeShader(m_context, getTessCtrlSource().c_str())) |
| << glu::TessellationEvaluationSource( |
| specializeShader(m_context, getTessEvalSource().c_str()))); |
| |
| gl.enableLogging(true); |
| |
| m_testCtx.getLog() << program; |
| if (!program.isOk()) |
| result.fail("failed to build program"); |
| else |
| { |
| const uint32_t props[1] = {(uint32_t)((m_isCtrlCase) ? (GL_REFERENCED_BY_TESS_CONTROL_SHADER) : |
| (GL_REFERENCED_BY_TESS_EVALUATION_SHADER))}; |
| |
| { |
| const tcu::ScopedLogSection section(m_testCtx.getLog(), "UnreferencedUniform", |
| "Unreferenced uniform u_unreferenced"); |
| uint32_t resourcePos; |
| glw::GLsizei length = 0; |
| glw::GLint referenced = 0; |
| |
| resourcePos = gl.glGetProgramResourceIndex(program.getProgram(), GL_UNIFORM, "u_unreferenced"); |
| m_testCtx.getLog() << tcu::TestLog::Message << "u_unreferenced resource index: " << resourcePos |
| << tcu::TestLog::EndMessage; |
| |
| if (resourcePos == GL_INVALID_INDEX) |
| result.fail("resourcePos was GL_INVALID_INDEX"); |
| else |
| { |
| gl.glGetProgramResourceiv(program.getProgram(), GL_UNIFORM, resourcePos, 1, props, 1, &length, |
| &referenced); |
| m_testCtx.getLog() << tcu::TestLog::Message << "Query " << glu::getProgramResourcePropertyStr(props[0]) |
| << ", got " << length << " value(s), value[0] = " << glu::getBooleanStr(referenced) |
| << tcu::TestLog::EndMessage; |
| |
| GLS_COLLECT_GL_ERROR(result, gl.glGetError(), "query resource"); |
| |
| if (length == 0 || referenced != GL_FALSE) |
| result.fail("expected GL_FALSE"); |
| } |
| } |
| |
| { |
| const tcu::ScopedLogSection section(m_testCtx.getLog(), "ReferencedUniform", |
| "Referenced uniform u_referenced"); |
| uint32_t resourcePos; |
| glw::GLsizei length = 0; |
| glw::GLint referenced = 0; |
| |
| resourcePos = gl.glGetProgramResourceIndex(program.getProgram(), GL_UNIFORM, "u_referenced"); |
| m_testCtx.getLog() << tcu::TestLog::Message << "u_referenced resource index: " << resourcePos |
| << tcu::TestLog::EndMessage; |
| |
| if (resourcePos == GL_INVALID_INDEX) |
| result.fail("resourcePos was GL_INVALID_INDEX"); |
| else |
| { |
| gl.glGetProgramResourceiv(program.getProgram(), GL_UNIFORM, resourcePos, 1, props, 1, &length, |
| &referenced); |
| m_testCtx.getLog() << tcu::TestLog::Message << "Query " << glu::getProgramResourcePropertyStr(props[0]) |
| << ", got " << length << " value(s), value[0] = " << glu::getBooleanStr(referenced) |
| << tcu::TestLog::EndMessage; |
| |
| GLU_EXPECT_NO_ERROR(gl.glGetError(), "query resource"); |
| |
| if (length == 0 || referenced != GL_TRUE) |
| result.fail("expected GL_TRUE"); |
| } |
| } |
| } |
| |
| result.setTestContextResult(m_testCtx); |
| return STOP; |
| } |
| |
| std::string ReferencedByTessellationQueryCase::getVertexSource(void) const |
| { |
| return "${GLSL_VERSION_DECL}\n" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "void main (void)\n" |
| "{\n" |
| " gl_Position = vec4(float(gl_VertexID), float(gl_VertexID / 2), 0.0, 1.0);\n" |
| "}\n"; |
| } |
| |
| std::string ReferencedByTessellationQueryCase::getFragmentSource(void) const |
| { |
| return "${GLSL_VERSION_DECL}\n" |
| "layout (location = 0) out mediump vec4 o_color;\n" |
| "void main (void)\n" |
| "{\n" |
| " o_color = vec4(0.0, 1.0, 0.0, 1.0);\n" |
| "}\n"; |
| } |
| |
| std::string ReferencedByTessellationQueryCase::getTessCtrlSource(void) const |
| { |
| std::ostringstream buf; |
| buf << "${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT_ARR}\n" |
| "layout(vertices = 3) out;\n" |
| "uniform highp vec4 " |
| << ((m_isCtrlCase) ? ("u_referenced") : ("u_unreferenced")) |
| << ";\n" |
| "void main (void)\n" |
| "{\n" |
| " vec4 offset = " |
| << ((m_isCtrlCase) ? ("u_referenced") : ("u_unreferenced")) |
| << ";\n" |
| " gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position + offset;\n" |
| " gl_TessLevelInner[0] = 2.8;\n" |
| " gl_TessLevelInner[1] = 2.8;\n" |
| " gl_TessLevelOuter[0] = 2.8;\n" |
| " gl_TessLevelOuter[1] = 2.8;\n" |
| " gl_TessLevelOuter[2] = 2.8;\n" |
| " gl_TessLevelOuter[3] = 2.8;\n" |
| "}\n"; |
| return buf.str(); |
| } |
| |
| std::string ReferencedByTessellationQueryCase::getTessEvalSource(void) const |
| { |
| std::ostringstream buf; |
| buf << "${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT}\n" |
| "layout(triangles) in;\n" |
| "uniform highp vec4 " |
| << ((m_isCtrlCase) ? ("u_unreferenced") : ("u_referenced")) |
| << ";\n" |
| "void main (void)\n" |
| "{\n" |
| " vec4 offset = " |
| << ((m_isCtrlCase) ? ("u_unreferenced") : ("u_referenced")) |
| << ";\n" |
| " gl_Position = gl_TessCoord.x * gl_in[0].gl_Position\n" |
| " + gl_TessCoord.y * gl_in[1].gl_Position\n" |
| " + gl_TessCoord.z * gl_in[2].gl_Position\n" |
| " + offset;\n" |
| "}\n"; |
| |
| return buf.str(); |
| } |
| |
| class IsPerPatchQueryCase : public TestCase |
| { |
| public: |
| IsPerPatchQueryCase(Context &context, const char *name, const char *desc); |
| |
| private: |
| void init(void); |
| IterateResult iterate(void); |
| }; |
| |
| IsPerPatchQueryCase::IsPerPatchQueryCase(Context &context, const char *name, const char *desc) |
| : TestCase(context, name, desc) |
| { |
| } |
| |
| void IsPerPatchQueryCase::init(void) |
| { |
| checkTessellationSupport(m_context); |
| } |
| |
| IsPerPatchQueryCase::IterateResult IsPerPatchQueryCase::iterate(void) |
| { |
| static const char *const s_controlSource = |
| "${GLSL_VERSION_DECL}\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "${GLSL_PER_VERTEX_IN_ARR}\n" |
| "${GLSL_PER_VERTEX_OUT_ARR}\n" |
| "layout(vertices = 3) out;\n" |
| "patch out highp vec4 v_perPatch;\n" |
| "out highp vec4 v_perVertex[];\n" |
| "void main (void)\n" |
| "{\n" |
| " gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n" |
| " v_perPatch = gl_in[0].gl_Position;\n" |
| " v_perVertex[gl_InvocationID] = -gl_in[gl_InvocationID].gl_Position;\n" |
| " gl_TessLevelInner[0] = 2.8;\n" |
| " gl_TessLevelInner[1] = 2.8;\n" |
| " gl_TessLevelOuter[0] = 2.8;\n" |
| " gl_TessLevelOuter[1] = 2.8;\n" |
| " gl_TessLevelOuter[2] = 2.8;\n" |
| " gl_TessLevelOuter[3] = 2.8;\n" |
| "}\n"; |
| tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: "); |
| glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog()); |
| glu::ShaderProgram program(m_context.getRenderContext(), |
| glu::ProgramSources() |
| << glu::TessellationControlSource(specializeShader(m_context, s_controlSource)) |
| << glu::ProgramSeparable(true)); |
| |
| gl.enableLogging(true); |
| |
| m_testCtx.getLog() << program; |
| if (!program.isOk()) |
| result.fail("failed to build program"); |
| else |
| { |
| const uint32_t props[1] = {GL_IS_PER_PATCH}; |
| |
| { |
| const tcu::ScopedLogSection section(m_testCtx.getLog(), "PerPatchOutput", "Per patch v_perPatch"); |
| uint32_t resourcePos; |
| glw::GLsizei length = 0; |
| glw::GLint referenced = 0; |
| |
| resourcePos = gl.glGetProgramResourceIndex(program.getProgram(), GL_PROGRAM_OUTPUT, "v_perPatch"); |
| m_testCtx.getLog() << tcu::TestLog::Message << "v_perPatch resource index: " << resourcePos |
| << tcu::TestLog::EndMessage; |
| |
| if (resourcePos == GL_INVALID_INDEX) |
| result.fail("resourcePos was GL_INVALID_INDEX"); |
| else |
| { |
| gl.glGetProgramResourceiv(program.getProgram(), GL_PROGRAM_OUTPUT, resourcePos, 1, props, 1, &length, |
| &referenced); |
| m_testCtx.getLog() << tcu::TestLog::Message << "Query " << glu::getProgramResourcePropertyStr(props[0]) |
| << ", got " << length << " value(s), value[0] = " << glu::getBooleanStr(referenced) |
| << tcu::TestLog::EndMessage; |
| |
| GLS_COLLECT_GL_ERROR(result, gl.glGetError(), "query resource"); |
| |
| if (length == 0 || referenced != GL_TRUE) |
| result.fail("expected GL_TRUE"); |
| } |
| } |
| |
| { |
| const tcu::ScopedLogSection section(m_testCtx.getLog(), "PerVertexhOutput", "Per vertex v_perVertex"); |
| uint32_t resourcePos; |
| glw::GLsizei length = 0; |
| glw::GLint referenced = 0; |
| |
| resourcePos = gl.glGetProgramResourceIndex(program.getProgram(), GL_PROGRAM_OUTPUT, "v_perVertex"); |
| m_testCtx.getLog() << tcu::TestLog::Message << "v_perVertex resource index: " << resourcePos |
| << tcu::TestLog::EndMessage; |
| |
| if (resourcePos == GL_INVALID_INDEX) |
| result.fail("resourcePos was GL_INVALID_INDEX"); |
| else |
| { |
| gl.glGetProgramResourceiv(program.getProgram(), GL_PROGRAM_OUTPUT, resourcePos, 1, props, 1, &length, |
| &referenced); |
| m_testCtx.getLog() << tcu::TestLog::Message << "Query " << glu::getProgramResourcePropertyStr(props[0]) |
| << ", got " << length << " value(s), value[0] = " << glu::getBooleanStr(referenced) |
| << tcu::TestLog::EndMessage; |
| |
| GLU_EXPECT_NO_ERROR(gl.glGetError(), "query resource"); |
| |
| if (length == 0 || referenced != GL_FALSE) |
| result.fail("expected GL_FALSE"); |
| } |
| } |
| } |
| |
| result.setTestContextResult(m_testCtx); |
| return STOP; |
| } |
| |
| } // namespace |
| |
| TessellationTests::TessellationTests(Context &context, bool isGL45) |
| : TestCaseGroup(context, "tessellation", "Tessellation Tests") |
| , m_isGL45(isGL45) |
| { |
| } |
| |
| TessellationTests::~TessellationTests(void) |
| { |
| } |
| |
| void TessellationTests::init(void) |
| { |
| { |
| tcu::TestCaseGroup *const queryGroup = new tcu::TestCaseGroup(m_testCtx, "state_query", "Query tests"); |
| addChild(queryGroup); |
| |
| // new limits |
| queryGroup->addChild( |
| new LimitQueryCase(m_context, "max_patch_vertices", "Test MAX_PATCH_VERTICES", GL_MAX_PATCH_VERTICES, 32)); |
| queryGroup->addChild( |
| new LimitQueryCase(m_context, "max_tess_gen_level", "Test MAX_TESS_GEN_LEVEL", GL_MAX_TESS_GEN_LEVEL, 64)); |
| queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_uniform_components", |
| "Test MAX_TESS_CONTROL_UNIFORM_COMPONENTS", |
| GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS, 1024)); |
| queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_evaluation_uniform_components", |
| "Test MAX_TESS_EVALUATION_UNIFORM_COMPONENTS", |
| GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS, 1024)); |
| queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_texture_image_units", |
| "Test MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS", |
| GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS, 16)); |
| queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_evaluation_texture_image_units", |
| "Test MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS", |
| GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS, 16)); |
| queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_output_components", |
| "Test MAX_TESS_CONTROL_OUTPUT_COMPONENTS", |
| GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS, 64)); |
| queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_patch_components", |
| "Test MAX_TESS_PATCH_COMPONENTS", GL_MAX_TESS_PATCH_COMPONENTS, 120)); |
| queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_total_output_components", |
| "Test MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS", |
| GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS, 2048)); |
| queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_evaluation_output_components", |
| "Test MAX_TESS_EVALUATION_OUTPUT_COMPONENTS", |
| GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS, 64)); |
| queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_uniform_blocks", |
| "Test MAX_TESS_CONTROL_UNIFORM_BLOCKS", |
| GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS, 12)); |
| queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_evaluation_uniform_blocks", |
| "Test MAX_TESS_EVALUATION_UNIFORM_BLOCKS", |
| GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS, 12)); |
| queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_input_components", |
| "Test MAX_TESS_CONTROL_INPUT_COMPONENTS", |
| GL_MAX_TESS_CONTROL_INPUT_COMPONENTS, 64)); |
| queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_evaluation_input_components", |
| "Test MAX_TESS_EVALUATION_INPUT_COMPONENTS", |
| GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS, 64)); |
| queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_atomic_counter_buffers", |
| "Test MAX_TESS_CONTROL_ATOMIC_COUNTER_BUFFERS", |
| GL_MAX_TESS_CONTROL_ATOMIC_COUNTER_BUFFERS, 0)); |
| queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_evaluation_atomic_counter_buffers", |
| "Test MAX_TESS_EVALUATION_ATOMIC_COUNTER_BUFFERS", |
| GL_MAX_TESS_EVALUATION_ATOMIC_COUNTER_BUFFERS, 0)); |
| queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_atomic_counters", |
| "Test MAX_TESS_CONTROL_ATOMIC_COUNTERS", |
| GL_MAX_TESS_CONTROL_ATOMIC_COUNTERS, 0)); |
| queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_evaluation_atomic_counters", |
| "Test MAX_TESS_EVALUATION_ATOMIC_COUNTERS", |
| GL_MAX_TESS_EVALUATION_ATOMIC_COUNTERS, 0)); |
| queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_image_uniforms", |
| "Test MAX_TESS_CONTROL_IMAGE_UNIFORMS", |
| GL_MAX_TESS_CONTROL_IMAGE_UNIFORMS, 0)); |
| queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_evaluation_image_uniforms", |
| "Test MAX_TESS_EVALUATION_IMAGE_UNIFORMS", |
| GL_MAX_TESS_EVALUATION_IMAGE_UNIFORMS, 0)); |
| queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_shader_storage_blocks", |
| "Test MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS", |
| GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS, 0)); |
| queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_evaluation_shader_storage_blocks", |
| "Test MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS", |
| GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS, 0)); |
| |
| // modified limits |
| queryGroup->addChild(new LimitQueryCase(m_context, "max_uniform_buffer_bindings", |
| "Test MAX_UNIFORM_BUFFER_BINDINGS", GL_MAX_UNIFORM_BUFFER_BINDINGS, |
| 72)); |
| queryGroup->addChild(new LimitQueryCase(m_context, "max_combined_uniform_blocks", |
| "Test MAX_COMBINED_UNIFORM_BLOCKS", GL_MAX_COMBINED_UNIFORM_BLOCKS, |
| 60)); |
| queryGroup->addChild(new LimitQueryCase(m_context, "max_combined_texture_image_units", |
| "Test MAX_COMBINED_TEXTURE_IMAGE_UNITS", |
| GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, 96)); |
| |
| // combined limits |
| queryGroup->addChild(new CombinedUniformLimitCase( |
| m_context, "max_combined_tess_control_uniform_components", |
| "Test MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS", GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS, |
| GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS, GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS)); |
| queryGroup->addChild(new CombinedUniformLimitCase( |
| m_context, "max_combined_tess_evaluation_uniform_components", |
| "Test MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS", GL_MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS, |
| GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS, GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS)); |
| |
| // features |
| queryGroup->addChild(new PrimitiveRestartForPatchesSupportedCase( |
| m_context, "primitive_restart_for_patches_supported", "Test PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED")); |
| |
| // states |
| queryGroup->addChild(new PatchVerticesStateCase(m_context, "patch_vertices", "Test PATCH_VERTICES")); |
| |
| // program states |
| queryGroup->addChild(new TessControlOutputVerticesCase(m_context, "tess_control_output_vertices", |
| "Test TESS_CONTROL_OUTPUT_VERTICES")); |
| queryGroup->addChild(new TessGenModeQueryCase(m_context, "tess_gen_mode", "Test TESS_GEN_MODE")); |
| queryGroup->addChild(new TessGenSpacingQueryCase(m_context, "tess_gen_spacing", "Test TESS_GEN_SPACING")); |
| queryGroup->addChild( |
| new TessGenVertexOrderQueryCase(m_context, "tess_gen_vertex_order", "Test TESS_GEN_VERTEX_ORDER")); |
| queryGroup->addChild( |
| new TessGenPointModeQueryCase(m_context, "tess_gen_point_mode", "Test TESS_GEN_POINT_MODE")); |
| |
| // resource queries |
| queryGroup->addChild(new ReferencedByTessellationQueryCase(m_context, "referenced_by_tess_control_shader", |
| "Test REFERENCED_BY_TESS_CONTROL_SHADER", true)); |
| queryGroup->addChild(new ReferencedByTessellationQueryCase(m_context, "referenced_by_tess_evaluation_shader", |
| "Test REFERENCED_BY_TESS_EVALUATION_SHADER", false)); |
| queryGroup->addChild(new IsPerPatchQueryCase(m_context, "is_per_patch", "Test IS_PER_PATCH")); |
| } |
| |
| { |
| TestCaseGroup *const tessCoordGroup = new TestCaseGroup( |
| m_context, "tesscoord", "Get tessellation coordinates with transform feedback and validate them"); |
| addChild(tessCoordGroup); |
| |
| for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++) |
| { |
| const TessPrimitiveType primitiveType = (TessPrimitiveType)primitiveTypeI; |
| |
| for (int spacingI = 0; spacingI < SPACINGMODE_LAST; spacingI++) |
| tessCoordGroup->addChild(new TessCoordCase(m_context, |
| (string() + getTessPrimitiveTypeShaderName(primitiveType) + |
| "_" + getSpacingModeShaderName((SpacingMode)spacingI)) |
| .c_str(), |
| "", primitiveType, (SpacingMode)spacingI)); |
| } |
| } |
| |
| { |
| TestCaseGroup *const windingGroup = |
| new TestCaseGroup(m_context, "winding", "Test the cw and ccw input layout qualifiers"); |
| addChild(windingGroup); |
| |
| for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++) |
| { |
| const TessPrimitiveType primitiveType = (TessPrimitiveType)primitiveTypeI; |
| if (primitiveType == TESSPRIMITIVETYPE_ISOLINES) |
| continue; |
| |
| for (int windingI = 0; windingI < WINDING_LAST; windingI++) |
| { |
| const Winding winding = (Winding)windingI; |
| windingGroup->addChild(new WindingCase( |
| m_context, |
| (string() + getTessPrimitiveTypeShaderName(primitiveType) + "_" + getWindingShaderName(winding)) |
| .c_str(), |
| "", primitiveType, winding)); |
| } |
| } |
| } |
| |
| { |
| TestCaseGroup *const shaderInputOutputGroup = new TestCaseGroup( |
| m_context, "shader_input_output", "Test tessellation control and evaluation shader inputs and outputs"); |
| addChild(shaderInputOutputGroup); |
| |
| { |
| static const struct |
| { |
| int inPatchSize; |
| int outPatchSize; |
| } patchVertexCountCases[] = {{5, 10}, {10, 5}}; |
| |
| for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(patchVertexCountCases); caseNdx++) |
| { |
| const int inSize = patchVertexCountCases[caseNdx].inPatchSize; |
| const int outSize = patchVertexCountCases[caseNdx].outPatchSize; |
| |
| const string caseName = |
| "patch_vertices_" + de::toString(inSize) + "_in_" + de::toString(outSize) + "_out"; |
| |
| shaderInputOutputGroup->addChild( |
| new PatchVertexCountCase(m_context, caseName.c_str(), "Test input and output patch vertex counts", |
| inSize, outSize, ("data/tessellation/" + caseName + "_ref.png").c_str())); |
| } |
| } |
| |
| for (int caseTypeI = 0; caseTypeI < PerPatchDataCase::CASETYPE_LAST; caseTypeI++) |
| { |
| const PerPatchDataCase::CaseType caseType = (PerPatchDataCase::CaseType)caseTypeI; |
| const char *const caseName = PerPatchDataCase::getCaseTypeName(caseType); |
| |
| shaderInputOutputGroup->addChild( |
| new PerPatchDataCase(m_context, caseName, PerPatchDataCase::getCaseTypeDescription(caseType), caseType, |
| PerPatchDataCase::caseTypeUsesRefImageFromFile(caseType) ? |
| (string() + "data/tessellation/" + caseName + "_ref.png").c_str() : |
| DE_NULL)); |
| } |
| |
| for (int caseTypeI = 0; caseTypeI < GLPositionCase::CASETYPE_LAST; caseTypeI++) |
| { |
| const GLPositionCase::CaseType caseType = (GLPositionCase::CaseType)caseTypeI; |
| const char *const caseName = GLPositionCase::getCaseTypeName(caseType); |
| |
| shaderInputOutputGroup->addChild( |
| new GLPositionCase(m_context, caseName, "", caseType, "data/tessellation/gl_position_ref.png")); |
| } |
| |
| shaderInputOutputGroup->addChild( |
| new BarrierCase(m_context, "barrier", "Basic barrier usage", "data/tessellation/barrier_ref.png")); |
| } |
| |
| { |
| TestCaseGroup *const miscDrawGroup = |
| new TestCaseGroup(m_context, "misc_draw", "Miscellaneous draw-result-verifying cases"); |
| addChild(miscDrawGroup); |
| |
| for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++) |
| { |
| const TessPrimitiveType primitiveType = (TessPrimitiveType)primitiveTypeI; |
| if (primitiveType == TESSPRIMITIVETYPE_ISOLINES) |
| continue; |
| |
| const char *const primTypeName = getTessPrimitiveTypeShaderName(primitiveType); |
| |
| for (int spacingI = 0; spacingI < SPACINGMODE_LAST; spacingI++) |
| { |
| const string caseName = |
| string() + "fill_cover_" + primTypeName + "_" + getSpacingModeShaderName((SpacingMode)spacingI); |
| |
| miscDrawGroup->addChild(new BasicTriangleFillCoverCase( |
| m_context, caseName.c_str(), |
| "Check that there are no obvious gaps in the triangle-filled area of a tessellated shape", |
| primitiveType, (SpacingMode)spacingI, ("data/tessellation/" + caseName + "_ref").c_str())); |
| } |
| } |
| |
| for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++) |
| { |
| const TessPrimitiveType primitiveType = (TessPrimitiveType)primitiveTypeI; |
| if (primitiveType == TESSPRIMITIVETYPE_ISOLINES) |
| continue; |
| |
| const char *const primTypeName = getTessPrimitiveTypeShaderName(primitiveType); |
| |
| for (int spacingI = 0; spacingI < SPACINGMODE_LAST; spacingI++) |
| { |
| const string caseName = |
| string() + "fill_overlap_" + primTypeName + "_" + getSpacingModeShaderName((SpacingMode)spacingI); |
| |
| miscDrawGroup->addChild(new BasicTriangleFillNonOverlapCase( |
| m_context, caseName.c_str(), |
| "Check that there are no obvious triangle overlaps in the triangle-filled area of a tessellated " |
| "shape", |
| primitiveType, (SpacingMode)spacingI, ("data/tessellation/" + caseName + "_ref").c_str())); |
| } |
| } |
| |
| for (int spacingI = 0; spacingI < SPACINGMODE_LAST; spacingI++) |
| { |
| const string caseName = string() + "isolines_" + getSpacingModeShaderName((SpacingMode)spacingI); |
| |
| miscDrawGroup->addChild(new IsolinesRenderCase(m_context, caseName.c_str(), "Basic isolines render test", |
| (SpacingMode)spacingI, |
| ("data/tessellation/" + caseName + "_ref").c_str())); |
| } |
| } |
| |
| { |
| TestCaseGroup *const commonEdgeGroup = new TestCaseGroup( |
| m_context, "common_edge", "Draw multiple adjacent shapes and check that no cracks appear between them"); |
| addChild(commonEdgeGroup); |
| |
| for (int caseTypeI = 0; caseTypeI < CommonEdgeCase::CASETYPE_LAST; caseTypeI++) |
| { |
| for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++) |
| { |
| const CommonEdgeCase::CaseType caseType = (CommonEdgeCase::CaseType)caseTypeI; |
| const TessPrimitiveType primitiveType = (TessPrimitiveType)primitiveTypeI; |
| if (primitiveType == TESSPRIMITIVETYPE_ISOLINES) |
| continue; |
| |
| for (int spacingI = 0; spacingI < SPACINGMODE_LAST; spacingI++) |
| { |
| const SpacingMode spacing = (SpacingMode)spacingI; |
| const string caseName = (string() + getTessPrimitiveTypeShaderName(primitiveType) + "_" + |
| getSpacingModeShaderName(spacing) + |
| (caseType == CommonEdgeCase::CASETYPE_BASIC ? "" : |
| caseType == CommonEdgeCase::CASETYPE_PRECISE ? "_precise" : |
| DE_NULL)); |
| |
| commonEdgeGroup->addChild( |
| new CommonEdgeCase(m_context, caseName.c_str(), "", primitiveType, spacing, caseType)); |
| } |
| } |
| } |
| } |
| |
| { |
| TestCaseGroup *const fractionalSpacingModeGroup = |
| new TestCaseGroup(m_context, "fractional_spacing", "Test fractional spacing modes"); |
| addChild(fractionalSpacingModeGroup); |
| |
| fractionalSpacingModeGroup->addChild( |
| new FractionalSpacingModeCase(m_context, "odd", "", SPACINGMODE_FRACTIONAL_ODD)); |
| fractionalSpacingModeGroup->addChild( |
| new FractionalSpacingModeCase(m_context, "even", "", SPACINGMODE_FRACTIONAL_EVEN)); |
| } |
| |
| { |
| TestCaseGroup *const primitiveDiscardGroup = new TestCaseGroup( |
| m_context, "primitive_discard", "Test primitive discard with relevant outer tessellation level <= 0.0"); |
| addChild(primitiveDiscardGroup); |
| |
| for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++) |
| { |
| for (int spacingI = 0; spacingI < SPACINGMODE_LAST; spacingI++) |
| { |
| for (int windingI = 0; windingI < WINDING_LAST; windingI++) |
| { |
| for (int usePointModeI = 0; usePointModeI <= 1; usePointModeI++) |
| { |
| const TessPrimitiveType primitiveType = (TessPrimitiveType)primitiveTypeI; |
| const SpacingMode spacing = (SpacingMode)spacingI; |
| const Winding winding = (Winding)windingI; |
| const bool usePointMode = usePointModeI != 0; |
| |
| primitiveDiscardGroup->addChild(new PrimitiveDiscardCase( |
| m_context, |
| (string() + getTessPrimitiveTypeShaderName(primitiveType) + "_" + |
| getSpacingModeShaderName(spacing) + "_" + getWindingShaderName(winding) + |
| (usePointMode ? "_point_mode" : "")) |
| .c_str(), |
| "", primitiveType, spacing, winding, usePointMode)); |
| } |
| } |
| } |
| } |
| } |
| |
| { |
| TestCaseGroup *const invarianceGroup = |
| new TestCaseGroup(m_context, "invariance", "Test tessellation invariance rules"); |
| |
| TestCaseGroup *const invariantPrimitiveSetGroup = |
| new TestCaseGroup(m_context, "primitive_set", "Test invariance rule #1"); |
| TestCaseGroup *const invariantOuterEdgeGroup = |
| new TestCaseGroup(m_context, "outer_edge_division", "Test invariance rule #2"); |
| TestCaseGroup *const symmetricOuterEdgeGroup = |
| new TestCaseGroup(m_context, "outer_edge_symmetry", "Test invariance rule #3"); |
| TestCaseGroup *const outerEdgeVertexSetIndexIndependenceGroup = |
| new TestCaseGroup(m_context, "outer_edge_index_independence", "Test invariance rule #4"); |
| TestCaseGroup *const invariantTriangleSetGroup = |
| new TestCaseGroup(m_context, "triangle_set", "Test invariance rule #5"); |
| TestCaseGroup *const invariantInnerTriangleSetGroup = |
| new TestCaseGroup(m_context, "inner_triangle_set", "Test invariance rule #6"); |
| TestCaseGroup *const invariantOuterTriangleSetGroup = |
| new TestCaseGroup(m_context, "outer_triangle_set", "Test invariance rule #7"); |
| TestCaseGroup *const tessCoordComponentRangeGroup = |
| new TestCaseGroup(m_context, "tess_coord_component_range", "Test invariance rule #8, first part"); |
| TestCaseGroup *const oneMinusTessCoordComponentGroup = |
| new TestCaseGroup(m_context, "one_minus_tess_coord_component", "Test invariance rule #8, second part"); |
| |
| addChild(invarianceGroup); |
| invarianceGroup->addChild(invariantPrimitiveSetGroup); |
| invarianceGroup->addChild(invariantOuterEdgeGroup); |
| invarianceGroup->addChild(symmetricOuterEdgeGroup); |
| invarianceGroup->addChild(outerEdgeVertexSetIndexIndependenceGroup); |
| invarianceGroup->addChild(invariantTriangleSetGroup); |
| invarianceGroup->addChild(invariantInnerTriangleSetGroup); |
| invarianceGroup->addChild(invariantOuterTriangleSetGroup); |
| invarianceGroup->addChild(tessCoordComponentRangeGroup); |
| invarianceGroup->addChild(oneMinusTessCoordComponentGroup); |
| |
| for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++) |
| { |
| const TessPrimitiveType primitiveType = (TessPrimitiveType)primitiveTypeI; |
| const string primName = getTessPrimitiveTypeShaderName(primitiveType); |
| const bool triOrQuad = |
| primitiveType == TESSPRIMITIVETYPE_TRIANGLES || primitiveType == TESSPRIMITIVETYPE_QUADS; |
| |
| for (int spacingI = 0; spacingI < SPACINGMODE_LAST; spacingI++) |
| { |
| const SpacingMode spacing = (SpacingMode)spacingI; |
| const string primSpacName = primName + "_" + getSpacingModeShaderName(spacing); |
| |
| if (triOrQuad) |
| { |
| invariantOuterEdgeGroup->addChild( |
| new InvariantOuterEdgeCase(m_context, primSpacName.c_str(), "", primitiveType, spacing)); |
| invariantTriangleSetGroup->addChild( |
| new InvariantTriangleSetCase(m_context, primSpacName.c_str(), "", primitiveType, spacing)); |
| invariantInnerTriangleSetGroup->addChild( |
| new InvariantInnerTriangleSetCase(m_context, primSpacName.c_str(), "", primitiveType, spacing)); |
| invariantOuterTriangleSetGroup->addChild( |
| new InvariantOuterTriangleSetCase(m_context, primSpacName.c_str(), "", primitiveType, spacing)); |
| } |
| |
| for (int windingI = 0; windingI < WINDING_LAST; windingI++) |
| { |
| const Winding winding = (Winding)windingI; |
| const string primSpacWindName = primSpacName + "_" + getWindingShaderName(winding); |
| |
| for (int usePointModeI = 0; usePointModeI <= 1; usePointModeI++) |
| { |
| const bool usePointMode = usePointModeI != 0; |
| const string primSpacWindPointName = primSpacWindName + (usePointMode ? "_point_mode" : ""); |
| |
| invariantPrimitiveSetGroup->addChild( |
| new InvariantPrimitiveSetCase(m_context, primSpacWindPointName.c_str(), "", primitiveType, |
| spacing, winding, usePointMode)); |
| symmetricOuterEdgeGroup->addChild( |
| new SymmetricOuterEdgeCase(m_context, primSpacWindPointName.c_str(), "", primitiveType, |
| spacing, winding, usePointMode)); |
| tessCoordComponentRangeGroup->addChild( |
| new TessCoordComponentRangeCase(m_context, primSpacWindPointName.c_str(), "", primitiveType, |
| spacing, winding, usePointMode)); |
| oneMinusTessCoordComponentGroup->addChild( |
| new OneMinusTessCoordComponentCase(m_context, primSpacWindPointName.c_str(), "", |
| primitiveType, spacing, winding, usePointMode)); |
| |
| if (triOrQuad) |
| outerEdgeVertexSetIndexIndependenceGroup->addChild( |
| new OuterEdgeVertexSetIndexIndependenceCase(m_context, primSpacWindPointName.c_str(), |
| "", primitiveType, spacing, winding, |
| usePointMode)); |
| } |
| } |
| } |
| } |
| } |
| |
| { |
| static const struct |
| { |
| const char *name; |
| const char *description; |
| UserDefinedIOCase::IOType ioType; |
| } ioCases[] = { |
| {"per_patch", "Per-patch TCS outputs", UserDefinedIOCase::IO_TYPE_PER_PATCH}, |
| {"per_patch_array", "Per-patch array TCS outputs", UserDefinedIOCase::IO_TYPE_PER_PATCH_ARRAY}, |
| {"per_patch_block", "Per-patch TCS outputs in IO block", UserDefinedIOCase::IO_TYPE_PER_PATCH_BLOCK}, |
| {"per_patch_block_array", "Per-patch TCS outputs in IO block array", |
| UserDefinedIOCase::IO_TYPE_PER_PATCH_BLOCK_ARRAY}, |
| {"per_vertex", "Per-vertex TCS outputs", UserDefinedIOCase::IO_TYPE_PER_VERTEX}, |
| {"per_vertex_block", "Per-vertex TCS outputs in IO block", UserDefinedIOCase::IO_TYPE_PER_VERTEX_BLOCK}, |
| }; |
| |
| TestCaseGroup *const userDefinedIOGroup = new TestCaseGroup( |
| m_context, "user_defined_io", "Test non-built-in per-patch and per-vertex inputs and outputs"); |
| addChild(userDefinedIOGroup); |
| |
| for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(ioCases); ++ndx) |
| { |
| TestCaseGroup *const ioTypeGroup = |
| new TestCaseGroup(m_context, ioCases[ndx].name, ioCases[ndx].description); |
| userDefinedIOGroup->addChild(ioTypeGroup); |
| |
| for (int vertexArraySizeI = 0; vertexArraySizeI < UserDefinedIOCase::VERTEX_IO_ARRAY_SIZE_LAST; |
| vertexArraySizeI++) |
| { |
| const UserDefinedIOCase::VertexIOArraySize vertexArraySize = |
| (UserDefinedIOCase::VertexIOArraySize)vertexArraySizeI; |
| TestCaseGroup *const vertexArraySizeGroup = new TestCaseGroup( |
| m_context, |
| vertexArraySizeI == UserDefinedIOCase::VERTEX_IO_ARRAY_SIZE_IMPLICIT ? |
| "vertex_io_array_size_implicit" : |
| vertexArraySizeI == UserDefinedIOCase::VERTEX_IO_ARRAY_SIZE_EXPLICIT_SHADER_BUILTIN ? |
| "vertex_io_array_size_shader_builtin" : |
| vertexArraySizeI == UserDefinedIOCase::VERTEX_IO_ARRAY_SIZE_EXPLICIT_QUERY ? |
| "vertex_io_array_size_query" : |
| DE_NULL, |
| ""); |
| ioTypeGroup->addChild(vertexArraySizeGroup); |
| |
| for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++) |
| { |
| const TessPrimitiveType primitiveType = (TessPrimitiveType)primitiveTypeI; |
| vertexArraySizeGroup->addChild(new UserDefinedIOCase( |
| m_context, getTessPrimitiveTypeShaderName(primitiveType), "", primitiveType, |
| ioCases[ndx].ioType, vertexArraySize, UserDefinedIOCase::TESS_CONTROL_OUT_ARRAY_SIZE_IMPLICIT, |
| (string() + "data/tessellation/user_defined_io_" + |
| getTessPrimitiveTypeShaderName(primitiveType) + "_ref.png") |
| .c_str())); |
| } |
| |
| if (ioCases[ndx].ioType == UserDefinedIOCase::IO_TYPE_PER_VERTEX || |
| ioCases[ndx].ioType == UserDefinedIOCase::IO_TYPE_PER_VERTEX_BLOCK) |
| { |
| for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++) |
| { |
| const TessPrimitiveType primitiveType = (TessPrimitiveType)primitiveTypeI; |
| vertexArraySizeGroup->addChild(new UserDefinedIOCase( |
| m_context, |
| (string(getTessPrimitiveTypeShaderName(primitiveType)) + "_explicit_tcs_out_size").c_str(), |
| "", primitiveType, ioCases[ndx].ioType, vertexArraySize, |
| UserDefinedIOCase::TESS_CONTROL_OUT_ARRAY_SIZE_LAYOUT, |
| (string() + "data/tessellation/user_defined_io_" + |
| getTessPrimitiveTypeShaderName(primitiveType) + "_ref.png") |
| .c_str())); |
| } |
| } |
| } |
| } |
| |
| // ES only |
| if (!m_isGL45) |
| { |
| de::MovePtr<TestCaseGroup> negativeGroup(new TestCaseGroup(m_context, "negative", "Negative cases")); |
| |
| { |
| de::MovePtr<TestCaseGroup> es31Group( |
| new TestCaseGroup(m_context, "es31", "GLSL ES 3.1 Negative cases")); |
| gls::ShaderLibrary shaderLibrary(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo()); |
| const std::vector<tcu::TestNode *> children = |
| shaderLibrary.loadShaderFile("shaders/es31/tessellation_negative_user_defined_io.test"); |
| |
| for (int i = 0; i < (int)children.size(); i++) |
| es31Group->addChild(children[i]); |
| |
| negativeGroup->addChild(es31Group.release()); |
| } |
| |
| { |
| de::MovePtr<TestCaseGroup> es32Group( |
| new TestCaseGroup(m_context, "es32", "GLSL ES 3.2 Negative cases")); |
| gls::ShaderLibrary shaderLibrary(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo()); |
| const std::vector<tcu::TestNode *> children = |
| shaderLibrary.loadShaderFile("shaders/es32/tessellation_negative_user_defined_io.test"); |
| |
| for (int i = 0; i < (int)children.size(); i++) |
| es32Group->addChild(children[i]); |
| |
| negativeGroup->addChild(es32Group.release()); |
| } |
| |
| userDefinedIOGroup->addChild(negativeGroup.release()); |
| } |
| } |
| } |
| |
| } // namespace Functional |
| } // namespace gles31 |
| } // namespace deqp |