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