blob: 97c54974edefc3347e3ec02e030b90574ed2e042 [file] [log] [blame]
/*-------------------------------------------------------------------------
* drawElements Quality Program OpenGL ES 3.0 Module
* -------------------------------------------------
*
* Copyright 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*//*!
* \file
* \brief Transform feedback tests.
*//*--------------------------------------------------------------------*/
#include "es3fTransformFeedbackTests.hpp"
#include "tcuTestLog.hpp"
#include "tcuSurface.hpp"
#include "tcuImageCompare.hpp"
#include "tcuVector.hpp"
#include "tcuFormatUtil.hpp"
#include "tcuRenderTarget.hpp"
#include "gluShaderUtil.hpp"
#include "gluVarType.hpp"
#include "gluVarTypeUtil.hpp"
#include "gluPixelTransfer.hpp"
#include "gluRenderContext.hpp"
#include "gluShaderProgram.hpp"
#include "gluObjectWrapper.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"
#include "deRandom.hpp"
#include "deStringUtil.hpp"
#include "deMemory.h"
#include "deString.h"
#include <set>
#include <map>
#include <algorithm>
using std::set;
using std::string;
using std::vector;
using std::map;
using std::set;
using tcu::TestLog;
namespace deqp
{
namespace gles3
{
namespace Functional
{
namespace TransformFeedback
{
enum
{
VIEWPORT_WIDTH = 128,
VIEWPORT_HEIGHT = 128,
BUFFER_GUARD_MULTIPLIER =
2 //!< stride*BUFFER_GUARD_MULTIPLIER bytes are added to the end of tf buffer and used to check for overruns.
};
enum Interpolation
{
INTERPOLATION_SMOOTH = 0,
INTERPOLATION_FLAT,
INTERPOLATION_CENTROID,
INTERPOLATION_LAST
};
static const char *getInterpolationName(Interpolation interp)
{
switch (interp)
{
case INTERPOLATION_SMOOTH:
return "smooth";
case INTERPOLATION_FLAT:
return "flat";
case INTERPOLATION_CENTROID:
return "centroid";
default:
DE_ASSERT(false);
return DE_NULL;
}
}
struct Varying
{
Varying(const char *name_, const glu::VarType &type_, Interpolation interp_)
: name(name_)
, type(type_)
, interpolation(interp_)
{
}
std::string name; //!< Variable name.
glu::VarType type; //!< Variable type.
Interpolation interpolation; //!< Interpolation mode (smooth, flat, centroid).
};
struct VaryingNameEquals
{
VaryingNameEquals(const std::string &name_) : name(name_)
{
}
bool operator()(const Varying &var) const
{
return var.name == name;
}
std::string name;
};
struct Attribute
{
Attribute(const std::string &name_, const glu::VarType &type_, int offset_)
: name(name_)
, type(type_)
, offset(offset_)
{
}
std::string name;
glu::VarType type;
int offset;
};
struct AttributeNameEquals
{
AttributeNameEquals(const std::string &name_) : name(name_)
{
}
bool operator()(const Attribute &attr) const
{
return attr.name == name;
}
std::string name;
};
struct Output
{
Output(void) : bufferNdx(0), offset(0)
{
}
std::string name;
glu::VarType type;
int bufferNdx;
int offset;
vector<const Attribute *> inputs;
};
struct DrawCall
{
DrawCall(int numElements_, bool tfEnabled_) : numElements(numElements_), transformFeedbackEnabled(tfEnabled_)
{
}
DrawCall(void) : numElements(0), transformFeedbackEnabled(false)
{
}
int numElements;
bool transformFeedbackEnabled;
};
std::ostream &operator<<(std::ostream &str, const DrawCall &call)
{
return str << "(" << call.numElements << ", " << (call.transformFeedbackEnabled ? "resumed" : "paused") << ")";
}
class ProgramSpec
{
public:
ProgramSpec(void);
~ProgramSpec(void);
glu::StructType *createStruct(const char *name);
void addVarying(const char *name, const glu::VarType &type, Interpolation interp);
void addTransformFeedbackVarying(const char *name);
const vector<glu::StructType *> &getStructs(void) const
{
return m_structs;
}
const vector<Varying> &getVaryings(void) const
{
return m_varyings;
}
const vector<string> &getTransformFeedbackVaryings(void) const
{
return m_transformFeedbackVaryings;
}
bool isPointSizeUsed(void) const;
private:
ProgramSpec(const ProgramSpec &other);
ProgramSpec &operator=(const ProgramSpec &other);
vector<glu::StructType *> m_structs;
vector<Varying> m_varyings;
vector<string> m_transformFeedbackVaryings;
};
// ProgramSpec
ProgramSpec::ProgramSpec(void)
{
}
ProgramSpec::~ProgramSpec(void)
{
for (vector<glu::StructType *>::iterator i = m_structs.begin(); i != m_structs.end(); i++)
delete *i;
}
glu::StructType *ProgramSpec::createStruct(const char *name)
{
m_structs.reserve(m_structs.size() + 1);
m_structs.push_back(new glu::StructType(name));
return m_structs.back();
}
void ProgramSpec::addVarying(const char *name, const glu::VarType &type, Interpolation interp)
{
m_varyings.push_back(Varying(name, type, interp));
}
void ProgramSpec::addTransformFeedbackVarying(const char *name)
{
m_transformFeedbackVaryings.push_back(name);
}
bool ProgramSpec::isPointSizeUsed(void) const
{
return std::find(m_transformFeedbackVaryings.begin(), m_transformFeedbackVaryings.end(), "gl_PointSize") !=
m_transformFeedbackVaryings.end();
}
static bool isProgramSupported(const glw::Functions &gl, const ProgramSpec &spec, uint32_t tfMode)
{
int maxVertexAttribs = 0;
int maxTfInterleavedComponents = 0;
int maxTfSeparateAttribs = 0;
int maxTfSeparateComponents = 0;
gl.getIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs);
gl.getIntegerv(GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS, &maxTfInterleavedComponents);
gl.getIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS, &maxTfSeparateAttribs);
gl.getIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS, &maxTfSeparateComponents);
// Check vertex attribs.
int totalVertexAttribs = 1 /* a_position */ + (spec.isPointSizeUsed() ? 1 : 0);
for (vector<Varying>::const_iterator var = spec.getVaryings().begin(); var != spec.getVaryings().end(); var++)
{
for (glu::VectorTypeIterator vecIter = glu::VectorTypeIterator::begin(&var->type);
vecIter != glu::VectorTypeIterator::end(&var->type); vecIter++)
totalVertexAttribs += 1;
}
if (totalVertexAttribs > maxVertexAttribs)
return false; // Vertex attribute count exceeded.
// Check varyings.
int totalTfComponents = 0;
int totalTfAttribs = 0;
for (vector<string>::const_iterator iter = spec.getTransformFeedbackVaryings().begin();
iter != spec.getTransformFeedbackVaryings().end(); iter++)
{
const string &name = *iter;
int numComponents = 0;
if (name == "gl_Position")
numComponents = 4;
else if (name == "gl_PointSize")
numComponents = 1;
else
{
string varName = glu::parseVariableName(name.c_str());
const Varying &varying =
*std::find_if(spec.getVaryings().begin(), spec.getVaryings().end(), VaryingNameEquals(varName));
glu::TypeComponentVector varPath;
glu::parseTypePath(name.c_str(), varying.type, varPath);
numComponents = glu::getVarType(varying.type, varPath).getScalarSize();
}
if (tfMode == GL_SEPARATE_ATTRIBS && numComponents > maxTfSeparateComponents)
return false; // Per-attribute component count exceeded.
totalTfComponents += numComponents;
totalTfAttribs += 1;
}
if (tfMode == GL_SEPARATE_ATTRIBS && totalTfAttribs > maxTfSeparateAttribs)
return false;
if (tfMode == GL_INTERLEAVED_ATTRIBS && totalTfComponents > maxTfInterleavedComponents)
return false;
return true;
}
// Program
static std::string getAttributeName(const char *varyingName, const glu::TypeComponentVector &path)
{
std::ostringstream str;
str << "a_" << (deStringBeginsWith(varyingName, "v_") ? varyingName + 2 : varyingName);
for (glu::TypeComponentVector::const_iterator iter = path.begin(); iter != path.end(); iter++)
{
const char *prefix = DE_NULL;
switch (iter->type)
{
case glu::VarTypeComponent::STRUCT_MEMBER:
prefix = "_m";
break;
case glu::VarTypeComponent::ARRAY_ELEMENT:
prefix = "_e";
break;
case glu::VarTypeComponent::MATRIX_COLUMN:
prefix = "_c";
break;
case glu::VarTypeComponent::VECTOR_COMPONENT:
prefix = "_s";
break;
default:
DE_ASSERT(false);
}
str << prefix << iter->index;
}
return str.str();
}
static void genShaderSources(const ProgramSpec &spec, std::string &vertSource, std::string &fragSource,
bool pointSizeRequired)
{
std::ostringstream vtx;
std::ostringstream frag;
bool addPointSize = spec.isPointSizeUsed();
vtx << "#version 300 es\n"
<< "in highp vec4 a_position;\n";
frag << "#version 300 es\n"
<< "layout(location = 0) out mediump vec4 o_color;\n"
<< "uniform highp vec4 u_scale;\n"
<< "uniform highp vec4 u_bias;\n";
if (addPointSize)
vtx << "in highp float a_pointSize;\n";
// Declare attributes.
for (vector<Varying>::const_iterator var = spec.getVaryings().begin(); var != spec.getVaryings().end(); var++)
{
const char *name = var->name.c_str();
const glu::VarType &type = var->type;
for (glu::VectorTypeIterator vecIter = glu::VectorTypeIterator::begin(&type);
vecIter != glu::VectorTypeIterator::end(&type); vecIter++)
{
glu::VarType attribType = glu::getVarType(type, vecIter.getPath());
string attribName = getAttributeName(name, vecIter.getPath());
vtx << "in " << glu::declare(attribType, attribName.c_str()) << ";\n";
}
}
// Declare vayrings.
for (int ndx = 0; ndx < 2; ndx++)
{
const char *inout = ndx ? "in" : "out";
std::ostringstream &str = ndx ? frag : vtx;
// Declare structs that have type name.
for (vector<glu::StructType *>::const_iterator structIter = spec.getStructs().begin();
structIter != spec.getStructs().end(); structIter++)
{
const glu::StructType *structPtr = *structIter;
if (structPtr->hasTypeName())
str << glu::declare(structPtr) << ";\n";
}
for (vector<Varying>::const_iterator var = spec.getVaryings().begin(); var != spec.getVaryings().end(); var++)
str << getInterpolationName(var->interpolation) << " " << inout << " "
<< glu::declare(var->type, var->name.c_str()) << ";\n";
}
vtx << "\nvoid main (void)\n{\n"
<< "\tgl_Position = a_position;\n";
frag << "\nvoid main (void)\n{\n"
<< "\thighp vec4 res = vec4(0.0);\n";
if (addPointSize)
vtx << "\tgl_PointSize = a_pointSize;\n";
else if (pointSizeRequired)
vtx << "\tgl_PointSize = 1.0;\n";
// Generate assignments / usage.
for (vector<Varying>::const_iterator var = spec.getVaryings().begin(); var != spec.getVaryings().end(); var++)
{
const char *name = var->name.c_str();
const glu::VarType &type = var->type;
for (glu::VectorTypeIterator vecIter = glu::VectorTypeIterator::begin(&type);
vecIter != glu::VectorTypeIterator::end(&type); vecIter++)
{
glu::VarType subType = glu::getVarType(type, vecIter.getPath());
string attribName = getAttributeName(name, vecIter.getPath());
DE_ASSERT(subType.isBasicType() && glu::isDataTypeScalarOrVector(subType.getBasicType()));
// Vertex: assign from attribute.
vtx << "\t" << name << vecIter << " = " << attribName << ";\n";
// Fragment: add to res variable.
int scalarSize = glu::getDataTypeScalarSize(subType.getBasicType());
frag << "\tres += ";
if (scalarSize == 1)
frag << "vec4(" << name << vecIter << ")";
else if (scalarSize == 2)
frag << "vec2(" << name << vecIter << ").xxyy";
else if (scalarSize == 3)
frag << "vec3(" << name << vecIter << ").xyzx";
else if (scalarSize == 4)
frag << "vec4(" << name << vecIter << ")";
frag << ";\n";
}
}
frag << "\to_color = res * u_scale + u_bias;\n";
vtx << "}\n";
frag << "}\n";
vertSource = vtx.str();
fragSource = frag.str();
}
static glu::ShaderProgram *createVertexCaptureProgram(const glu::RenderContext &context, const ProgramSpec &spec,
uint32_t bufferMode, uint32_t primitiveType)
{
std::string vertSource, fragSource;
genShaderSources(spec, vertSource, fragSource, primitiveType == GL_POINTS /* Is point size required? */);
return new glu::ShaderProgram(context, glu::ProgramSources()
<< glu::VertexSource(vertSource) << glu::FragmentSource(fragSource)
<< glu::TransformFeedbackVaryings<vector<string>::const_iterator>(
spec.getTransformFeedbackVaryings().begin(),
spec.getTransformFeedbackVaryings().end())
<< glu::TransformFeedbackMode(bufferMode));
}
// Helpers.
static void computeInputLayout(vector<Attribute> &attributes, int &inputStride, const vector<Varying> &varyings,
bool usePointSize)
{
inputStride = 0;
// Add position.
attributes.push_back(
Attribute("a_position", glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP), inputStride));
inputStride += 4 * (int)sizeof(uint32_t);
if (usePointSize)
{
attributes.push_back(
Attribute("a_pointSize", glu::VarType(glu::TYPE_FLOAT, glu::PRECISION_HIGHP), inputStride));
inputStride += 1 * (int)sizeof(uint32_t);
}
// Compute attribute vector.
for (vector<Varying>::const_iterator var = varyings.begin(); var != varyings.end(); var++)
{
for (glu::VectorTypeIterator vecIter = glu::VectorTypeIterator::begin(&var->type);
vecIter != glu::VectorTypeIterator::end(&var->type); vecIter++)
{
glu::VarType type = vecIter.getType();
string name = getAttributeName(var->name.c_str(), vecIter.getPath());
attributes.push_back(Attribute(name, type, inputStride));
inputStride += glu::getDataTypeScalarSize(type.getBasicType()) * (int)sizeof(uint32_t);
}
}
}
static void computeTransformFeedbackOutputs(vector<Output> &transformFeedbackOutputs,
const vector<Attribute> &attributes, const vector<Varying> &varyings,
const vector<string> &transformFeedbackVaryings, uint32_t bufferMode)
{
int accumulatedSize = 0;
transformFeedbackOutputs.resize(transformFeedbackVaryings.size());
for (int varNdx = 0; varNdx < (int)transformFeedbackVaryings.size(); varNdx++)
{
const string &name = transformFeedbackVaryings[varNdx];
int bufNdx = (bufferMode == GL_SEPARATE_ATTRIBS ? varNdx : 0);
int offset = (bufferMode == GL_SEPARATE_ATTRIBS ? 0 : accumulatedSize);
Output &output = transformFeedbackOutputs[varNdx];
output.name = name;
output.bufferNdx = bufNdx;
output.offset = offset;
if (name == "gl_Position")
{
const Attribute *posIn =
&(*std::find_if(attributes.begin(), attributes.end(), AttributeNameEquals("a_position")));
output.type = posIn->type;
output.inputs.push_back(posIn);
}
else if (name == "gl_PointSize")
{
const Attribute *sizeIn =
&(*std::find_if(attributes.begin(), attributes.end(), AttributeNameEquals("a_pointSize")));
output.type = sizeIn->type;
output.inputs.push_back(sizeIn);
}
else
{
string varName = glu::parseVariableName(name.c_str());
const Varying &varying = *std::find_if(varyings.begin(), varyings.end(), VaryingNameEquals(varName));
glu::TypeComponentVector varPath;
glu::parseTypePath(name.c_str(), varying.type, varPath);
output.type = glu::getVarType(varying.type, varPath);
// Add all vectorized attributes as inputs.
for (glu::VectorTypeIterator iter = glu::VectorTypeIterator::begin(&output.type);
iter != glu::VectorTypeIterator::end(&output.type); iter++)
{
// Full path.
glu::TypeComponentVector fullPath(varPath.size() + iter.getPath().size());
std::copy(varPath.begin(), varPath.end(), fullPath.begin());
std::copy(iter.getPath().begin(), iter.getPath().end(), fullPath.begin() + varPath.size());
string attribName = getAttributeName(varName.c_str(), fullPath);
const Attribute *attrib =
&(*std::find_if(attributes.begin(), attributes.end(), AttributeNameEquals(attribName)));
output.inputs.push_back(attrib);
}
}
accumulatedSize += output.type.getScalarSize() * (int)sizeof(uint32_t);
}
}
static uint32_t signExtend(uint32_t value, uint32_t numBits)
{
DE_ASSERT(numBits >= 1u && numBits <= 32u);
if (numBits == 32u)
return value;
else if ((value & (1u << (numBits - 1u))) == 0u)
return value;
else
return value | ~((1u << numBits) - 1u);
}
static void genAttributeData(const Attribute &attrib, uint8_t *basePtr, int stride, int numElements, de::Random &rnd)
{
const int elementSize = (int)sizeof(uint32_t);
const bool isFloat = glu::isDataTypeFloatOrVec(attrib.type.getBasicType());
const bool isInt = glu::isDataTypeIntOrIVec(attrib.type.getBasicType());
const bool isUint = glu::isDataTypeUintOrUVec(attrib.type.getBasicType());
const glu::Precision precision = attrib.type.getPrecision();
const int numComps = glu::getDataTypeScalarSize(attrib.type.getBasicType());
for (int elemNdx = 0; elemNdx < numElements; elemNdx++)
{
for (int compNdx = 0; compNdx < numComps; compNdx++)
{
int offset = attrib.offset + elemNdx * stride + compNdx * elementSize;
if (isFloat)
{
float *comp = (float *)(basePtr + offset);
switch (precision)
{
case glu::PRECISION_LOWP:
*comp = 0.0f + 0.25f * (float)rnd.getInt(0, 4);
break;
case glu::PRECISION_MEDIUMP:
*comp = rnd.getFloat(-1e3f, 1e3f);
break;
case glu::PRECISION_HIGHP:
*comp = rnd.getFloat(-1e5f, 1e5f);
break;
default:
DE_ASSERT(false);
}
}
else if (isInt)
{
int *comp = (int *)(basePtr + offset);
switch (precision)
{
case glu::PRECISION_LOWP:
*comp = (int)signExtend(rnd.getUint32() & 0xff, 8);
break;
case glu::PRECISION_MEDIUMP:
*comp = (int)signExtend(rnd.getUint32() & 0xffff, 16);
break;
case glu::PRECISION_HIGHP:
*comp = (int)rnd.getUint32();
break;
default:
DE_ASSERT(false);
}
}
else if (isUint)
{
uint32_t *comp = (uint32_t *)(basePtr + offset);
switch (precision)
{
case glu::PRECISION_LOWP:
*comp = rnd.getUint32() & 0xff;
break;
case glu::PRECISION_MEDIUMP:
*comp = rnd.getUint32() & 0xffff;
break;
case glu::PRECISION_HIGHP:
*comp = rnd.getUint32();
break;
default:
DE_ASSERT(false);
}
}
else
DE_ASSERT(false);
}
}
}
static void genInputData(const vector<Attribute> &attributes, int numInputs, int inputStride, uint8_t *inputBasePtr,
de::Random &rnd)
{
// Random positions.
const Attribute &position = *std::find_if(attributes.begin(), attributes.end(), AttributeNameEquals("a_position"));
for (int ndx = 0; ndx < numInputs; ndx++)
{
uint8_t *ptr = inputBasePtr + position.offset + inputStride * ndx;
*((float *)(ptr + 0)) = rnd.getFloat(-1.2f, 1.2f);
*((float *)(ptr + 4)) = rnd.getFloat(-1.2f, 1.2f);
*((float *)(ptr + 8)) = rnd.getFloat(-1.2f, 1.2f);
*((float *)(ptr + 12)) = rnd.getFloat(0.1f, 2.0f);
}
// Point size.
vector<Attribute>::const_iterator pointSizePos =
std::find_if(attributes.begin(), attributes.end(), AttributeNameEquals("a_pointSize"));
if (pointSizePos != attributes.end())
{
for (int ndx = 0; ndx < numInputs; ndx++)
{
uint8_t *ptr = inputBasePtr + pointSizePos->offset + inputStride * ndx;
*((float *)ptr) = rnd.getFloat(1.0f, 8.0f);
}
}
// Random data for rest of components.
for (vector<Attribute>::const_iterator attrib = attributes.begin(); attrib != attributes.end(); attrib++)
{
if (attrib->name == "a_position" || attrib->name == "a_pointSize")
continue;
genAttributeData(*attrib, inputBasePtr, inputStride, numInputs, rnd);
}
}
static uint32_t getTransformFeedbackOutputCount(uint32_t primitiveType, int numElements)
{
switch (primitiveType)
{
case GL_TRIANGLES:
return numElements - numElements % 3;
case GL_TRIANGLE_STRIP:
return de::max(0, numElements - 2) * 3;
case GL_TRIANGLE_FAN:
return de::max(0, numElements - 2) * 3;
case GL_LINES:
return numElements - numElements % 2;
case GL_LINE_STRIP:
return de::max(0, numElements - 1) * 2;
case GL_LINE_LOOP:
return numElements > 1 ? numElements * 2 : 0;
case GL_POINTS:
return numElements;
default:
DE_ASSERT(false);
return 0;
}
}
static uint32_t getTransformFeedbackPrimitiveCount(uint32_t primitiveType, int numElements)
{
switch (primitiveType)
{
case GL_TRIANGLES:
return numElements / 3;
case GL_TRIANGLE_STRIP:
return de::max(0, numElements - 2);
case GL_TRIANGLE_FAN:
return de::max(0, numElements - 2);
case GL_LINES:
return numElements / 2;
case GL_LINE_STRIP:
return de::max(0, numElements - 1);
case GL_LINE_LOOP:
return numElements > 1 ? numElements : 0;
case GL_POINTS:
return numElements;
default:
DE_ASSERT(false);
return 0;
}
}
static uint32_t getTransformFeedbackPrimitiveMode(uint32_t primitiveType)
{
switch (primitiveType)
{
case GL_TRIANGLES:
case GL_TRIANGLE_STRIP:
case GL_TRIANGLE_FAN:
return GL_TRIANGLES;
case GL_LINES:
case GL_LINE_LOOP:
case GL_LINE_STRIP:
return GL_LINES;
case GL_POINTS:
return GL_POINTS;
default:
DE_ASSERT(false);
return 0;
}
}
static int getAttributeIndex(uint32_t primitiveType, int numInputs, int outNdx)
{
switch (primitiveType)
{
case GL_TRIANGLES:
return outNdx;
case GL_LINES:
return outNdx;
case GL_POINTS:
return outNdx;
case GL_TRIANGLE_STRIP:
{
int triNdx = outNdx / 3;
int vtxNdx = outNdx % 3;
return (triNdx % 2 != 0 && vtxNdx < 2) ? (triNdx + 1 - vtxNdx) : (triNdx + vtxNdx);
}
case GL_TRIANGLE_FAN:
return (outNdx % 3 != 0) ? (outNdx / 3 + outNdx % 3) : 0;
case GL_LINE_STRIP:
return outNdx / 2 + outNdx % 2;
case GL_LINE_LOOP:
{
int inNdx = outNdx / 2 + outNdx % 2;
return inNdx < numInputs ? inNdx : 0;
}
default:
DE_ASSERT(false);
return 0;
}
}
static bool compareTransformFeedbackOutput(tcu::TestLog &log, uint32_t primitiveType, const Output &output,
int numInputs, const uint8_t *inBasePtr, int inStride,
const uint8_t *outBasePtr, int outStride)
{
bool isOk = true;
int outOffset = output.offset;
for (int attrNdx = 0; attrNdx < (int)output.inputs.size(); attrNdx++)
{
const Attribute &attribute = *output.inputs[attrNdx];
glu::DataType type = attribute.type.getBasicType();
int numComponents = glu::getDataTypeScalarSize(type);
glu::Precision precision = attribute.type.getPrecision();
glu::DataType scalarType = glu::getDataTypeScalarType(type);
int numOutputs = getTransformFeedbackOutputCount(primitiveType, numInputs);
for (int outNdx = 0; outNdx < numOutputs; outNdx++)
{
int inNdx = getAttributeIndex(primitiveType, numInputs, outNdx);
for (int compNdx = 0; compNdx < numComponents; compNdx++)
{
const uint8_t *inPtr = inBasePtr + inStride * inNdx + attribute.offset + compNdx * sizeof(uint32_t);
const uint8_t *outPtr = outBasePtr + outStride * outNdx + outOffset + compNdx * sizeof(uint32_t);
uint32_t inVal = *(const uint32_t *)inPtr;
uint32_t outVal = *(const uint32_t *)outPtr;
bool isEqual = false;
if (scalarType == glu::TYPE_FLOAT)
{
// ULP comparison is used for highp and mediump. Lowp uses threshold-comparison.
switch (precision)
{
case glu::PRECISION_HIGHP:
isEqual = de::abs((int)inVal - (int)outVal) < 2;
break;
case glu::PRECISION_MEDIUMP:
isEqual = de::abs((int)inVal - (int)outVal) < 2 + (1 << 13);
break;
case glu::PRECISION_LOWP:
{
float inF = *(const float *)inPtr;
float outF = *(const float *)outPtr;
isEqual = de::abs(inF - outF) < 0.1f;
break;
}
default:
DE_ASSERT(false);
}
}
else
isEqual = (inVal == outVal); // Bit-exact match required for integer types.
if (!isEqual)
{
log << TestLog::Message << "Mismatch in " << output.name << " (" << attribute.name
<< "), output = " << outNdx << ", input = " << inNdx << ", component = " << compNdx
<< TestLog::EndMessage;
isOk = false;
break;
}
}
if (!isOk)
break;
}
if (!isOk)
break;
outOffset += numComponents * (int)sizeof(uint32_t);
}
return isOk;
}
static int computeTransformFeedbackPrimitiveCount(uint32_t primitiveType, const DrawCall *first, const DrawCall *end)
{
int primCount = 0;
for (const DrawCall *call = first; call != end; ++call)
{
if (call->transformFeedbackEnabled)
primCount += getTransformFeedbackPrimitiveCount(primitiveType, call->numElements);
}
return primCount;
}
static void writeBufferGuard(const glw::Functions &gl, uint32_t target, int bufferSize, int guardSize)
{
uint8_t *ptr = (uint8_t *)gl.mapBufferRange(target, bufferSize, guardSize, GL_MAP_WRITE_BIT);
if (ptr)
deMemset(ptr, 0xcd, guardSize);
gl.unmapBuffer(target);
GLU_EXPECT_NO_ERROR(gl.getError(), "guardband write");
}
static bool verifyGuard(const uint8_t *ptr, int guardSize)
{
for (int ndx = 0; ndx < guardSize; ndx++)
{
if (ptr[ndx] != 0xcd)
return false;
}
return true;
}
static void logTransformFeedbackVaryings(TestLog &log, const glw::Functions &gl, uint32_t program)
{
int numTfVaryings = 0;
int maxNameLen = 0;
gl.getProgramiv(program, GL_TRANSFORM_FEEDBACK_VARYINGS, &numTfVaryings);
gl.getProgramiv(program, GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH, &maxNameLen);
GLU_EXPECT_NO_ERROR(gl.getError(), "Query TF varyings");
log << TestLog::Message << "GL_TRANSFORM_FEEDBACK_VARYINGS = " << numTfVaryings << TestLog::EndMessage;
vector<char> nameBuf(maxNameLen + 1);
for (int ndx = 0; ndx < numTfVaryings; ndx++)
{
glw::GLsizei size = 0;
glw::GLenum type = 0;
gl.getTransformFeedbackVarying(program, ndx, (glw::GLsizei)nameBuf.size(), DE_NULL, &size, &type, &nameBuf[0]);
GLU_EXPECT_NO_ERROR(gl.getError(), "glGetTransformFeedbackVarying()");
const glu::DataType dataType = glu::getDataTypeFromGLType(type);
const std::string typeName = dataType != glu::TYPE_LAST ?
std::string(glu::getDataTypeName(dataType)) :
(std::string("unknown(") + tcu::toHex(type).toString() + ")");
log << TestLog::Message << (const char *)&nameBuf[0] << ": " << typeName << "[" << size << "]"
<< TestLog::EndMessage;
}
}
class TransformFeedbackCase : public TestCase
{
public:
TransformFeedbackCase(Context &context, const char *name, const char *desc, uint32_t bufferMode,
uint32_t primitiveType);
~TransformFeedbackCase(void);
void init(void);
void deinit(void);
IterateResult iterate(void);
protected:
ProgramSpec m_progSpec;
uint32_t m_bufferMode;
uint32_t m_primitiveType;
private:
TransformFeedbackCase(const TransformFeedbackCase &other);
TransformFeedbackCase &operator=(const TransformFeedbackCase &other);
bool runTest(const DrawCall *first, const DrawCall *end, uint32_t seed);
// Derived from ProgramSpec in init()
int m_inputStride;
vector<Attribute> m_attributes;
vector<Output> m_transformFeedbackOutputs;
vector<int> m_bufferStrides;
// GL state.
glu::ShaderProgram *m_program;
glu::TransformFeedback *m_transformFeedback;
vector<uint32_t> m_outputBuffers;
int m_iterNdx;
};
TransformFeedbackCase::TransformFeedbackCase(Context &context, const char *name, const char *desc, uint32_t bufferMode,
uint32_t primitiveType)
: TestCase(context, name, desc)
, m_bufferMode(bufferMode)
, m_primitiveType(primitiveType)
, m_inputStride(0)
, m_program(DE_NULL)
, m_transformFeedback(DE_NULL)
, m_iterNdx(0)
{
}
TransformFeedbackCase::~TransformFeedbackCase(void)
{
TransformFeedbackCase::deinit();
}
static bool hasArraysInTFVaryings(const ProgramSpec &spec)
{
for (vector<string>::const_iterator tfVar = spec.getTransformFeedbackVaryings().begin();
tfVar != spec.getTransformFeedbackVaryings().end(); ++tfVar)
{
string varName = glu::parseVariableName(tfVar->c_str());
vector<Varying>::const_iterator varIter =
std::find_if(spec.getVaryings().begin(), spec.getVaryings().end(), VaryingNameEquals(varName));
if (varName == "gl_Position" || varName == "gl_PointSize")
continue;
DE_ASSERT(varIter != spec.getVaryings().end());
if (varIter->type.isArrayType())
return true;
}
return false;
}
void TransformFeedbackCase::init(void)
{
TestLog &log = m_testCtx.getLog();
const glw::Functions &gl = m_context.getRenderContext().getFunctions();
DE_ASSERT(!m_program);
m_program = createVertexCaptureProgram(m_context.getRenderContext(), m_progSpec, m_bufferMode, m_primitiveType);
log << *m_program;
if (!m_program->isOk())
{
const bool linkFail = m_program->getShaderInfo(glu::SHADERTYPE_VERTEX).compileOk &&
m_program->getShaderInfo(glu::SHADERTYPE_FRAGMENT).compileOk &&
!m_program->getProgramInfo().linkOk;
if (linkFail)
{
if (!isProgramSupported(gl, m_progSpec, m_bufferMode))
throw tcu::NotSupportedError("Implementation limits execeeded", "", __FILE__, __LINE__);
else if (hasArraysInTFVaryings(m_progSpec))
throw tcu::NotSupportedError("Capturing arrays is not supported (undefined in specification)", "",
__FILE__, __LINE__);
else
throw tcu::TestError("Link failed", "", __FILE__, __LINE__);
}
else
throw tcu::TestError("Compile failed", "", __FILE__, __LINE__);
}
log << TestLog::Message << "Transform feedback varyings: "
<< tcu::formatArray(m_progSpec.getTransformFeedbackVaryings().begin(),
m_progSpec.getTransformFeedbackVaryings().end())
<< TestLog::EndMessage;
// Print out transform feedback points reported by GL.
log << TestLog::Message << "Transform feedback varyings reported by compiler:" << TestLog::EndMessage;
logTransformFeedbackVaryings(log, gl, m_program->getProgram());
// Compute input specification.
computeInputLayout(m_attributes, m_inputStride, m_progSpec.getVaryings(), m_progSpec.isPointSizeUsed());
// Build list of varyings used in transform feedback.
computeTransformFeedbackOutputs(m_transformFeedbackOutputs, m_attributes, m_progSpec.getVaryings(),
m_progSpec.getTransformFeedbackVaryings(), m_bufferMode);
DE_ASSERT(!m_transformFeedbackOutputs.empty());
// Buffer strides.
DE_ASSERT(m_bufferStrides.empty());
if (m_bufferMode == GL_SEPARATE_ATTRIBS)
{
for (vector<Output>::const_iterator outIter = m_transformFeedbackOutputs.begin();
outIter != m_transformFeedbackOutputs.end(); outIter++)
m_bufferStrides.push_back(outIter->type.getScalarSize() * (int)sizeof(uint32_t));
}
else
{
int totalSize = 0;
for (vector<Output>::const_iterator outIter = m_transformFeedbackOutputs.begin();
outIter != m_transformFeedbackOutputs.end(); outIter++)
totalSize += outIter->type.getScalarSize() * (int)sizeof(uint32_t);
m_bufferStrides.push_back(totalSize);
}
// \note Actual storage is allocated in iterate().
m_outputBuffers.resize(m_bufferStrides.size());
gl.genBuffers((glw::GLsizei)m_outputBuffers.size(), &m_outputBuffers[0]);
DE_ASSERT(!m_transformFeedback);
m_transformFeedback = new glu::TransformFeedback(m_context.getRenderContext());
GLU_EXPECT_NO_ERROR(gl.getError(), "init");
m_iterNdx = 0;
m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
}
void TransformFeedbackCase::deinit(void)
{
const glw::Functions &gl = m_context.getRenderContext().getFunctions();
if (!m_outputBuffers.empty())
{
gl.deleteBuffers((glw::GLsizei)m_outputBuffers.size(), &m_outputBuffers[0]);
m_outputBuffers.clear();
}
delete m_transformFeedback;
m_transformFeedback = DE_NULL;
delete m_program;
m_program = DE_NULL;
// Clean up state.
m_attributes.clear();
m_transformFeedbackOutputs.clear();
m_bufferStrides.clear();
m_inputStride = 0;
}
TransformFeedbackCase::IterateResult TransformFeedbackCase::iterate(void)
{
// Test cases.
static const DrawCall s_elemCount1[] = {DrawCall(1, true)};
static const DrawCall s_elemCount2[] = {DrawCall(2, true)};
static const DrawCall s_elemCount3[] = {DrawCall(3, true)};
static const DrawCall s_elemCount4[] = {DrawCall(4, true)};
static const DrawCall s_elemCount123[] = {DrawCall(123, true)};
static const DrawCall s_basicPause1[] = {DrawCall(64, true), DrawCall(64, false), DrawCall(64, true)};
static const DrawCall s_basicPause2[] = {DrawCall(13, true), DrawCall(5, true), DrawCall(17, false),
DrawCall(3, true), DrawCall(7, false)};
static const DrawCall s_startPaused[] = {DrawCall(123, false), DrawCall(123, true)};
static const DrawCall s_random1[] = {DrawCall(65, true), DrawCall(135, false), DrawCall(74, true),
DrawCall(16, false), DrawCall(226, false), DrawCall(9, true),
DrawCall(174, false)};
static const DrawCall s_random2[] = {DrawCall(217, true), DrawCall(171, true), DrawCall(147, true),
DrawCall(152, false), DrawCall(55, true)};
static const struct
{
const DrawCall *calls;
int numCalls;
} s_iterations[] = {
#define ITER(ARR) {ARR, DE_LENGTH_OF_ARRAY(ARR)}
ITER(s_elemCount1), ITER(s_elemCount2), ITER(s_elemCount3), ITER(s_elemCount4),
ITER(s_elemCount123), ITER(s_basicPause1), ITER(s_basicPause2), ITER(s_startPaused),
ITER(s_random1), ITER(s_random2)
#undef ITER
};
TestLog &log = m_testCtx.getLog();
bool isOk = true;
uint32_t seed = deStringHash(getName()) ^ deInt32Hash(m_iterNdx);
int numIterations = DE_LENGTH_OF_ARRAY(s_iterations);
const DrawCall *first = s_iterations[m_iterNdx].calls;
const DrawCall *end = s_iterations[m_iterNdx].calls + s_iterations[m_iterNdx].numCalls;
std::string sectionName = std::string("Iteration") + de::toString(m_iterNdx + 1);
std::string sectionDesc =
std::string("Iteration ") + de::toString(m_iterNdx + 1) + " / " + de::toString(numIterations);
tcu::ScopedLogSection section(log, sectionName, sectionDesc);
log << TestLog::Message << "Testing " << s_iterations[m_iterNdx].numCalls
<< " draw calls, (element count, TF state): " << tcu::formatArray(first, end) << TestLog::EndMessage;
isOk = runTest(first, end, seed);
if (!isOk)
m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Result comparison failed");
m_iterNdx += 1;
return (isOk && m_iterNdx < numIterations) ? CONTINUE : STOP;
}
bool TransformFeedbackCase::runTest(const DrawCall *first, const DrawCall *end, uint32_t seed)
{
TestLog &log = m_testCtx.getLog();
const glw::Functions &gl = m_context.getRenderContext().getFunctions();
de::Random rnd(seed);
int numInputs = 0; //!< Sum of element counts in calls.
int numOutputs = 0; //!< Sum of output counts for calls that have transform feedback enabled.
int width = m_context.getRenderContext().getRenderTarget().getWidth();
int height = m_context.getRenderContext().getRenderTarget().getHeight();
int viewportW = de::min((int)VIEWPORT_WIDTH, width);
int viewportH = de::min((int)VIEWPORT_HEIGHT, height);
int viewportX = rnd.getInt(0, width - viewportW);
int viewportY = rnd.getInt(0, height - viewportH);
tcu::Surface frameWithTf(viewportW, viewportH);
tcu::Surface frameWithoutTf(viewportW, viewportH);
glu::Query primitiveQuery(m_context.getRenderContext());
bool outputsOk = true;
bool imagesOk = true;
bool queryOk = true;
// Compute totals.
for (const DrawCall *call = first; call != end; call++)
{
numInputs += call->numElements;
numOutputs +=
call->transformFeedbackEnabled ? getTransformFeedbackOutputCount(m_primitiveType, call->numElements) : 0;
}
// Input data.
vector<uint8_t> inputData(m_inputStride * numInputs);
genInputData(m_attributes, numInputs, m_inputStride, &inputData[0], rnd);
gl.bindTransformFeedback(GL_TRANSFORM_FEEDBACK, m_transformFeedback->get());
GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTransformFeedback()");
// Allocate storage for transform feedback output buffers and bind to targets.
for (int bufNdx = 0; bufNdx < (int)m_outputBuffers.size(); bufNdx++)
{
uint32_t buffer = m_outputBuffers[bufNdx];
int stride = m_bufferStrides[bufNdx];
int target = bufNdx;
int size = stride * numOutputs;
int guardSize = stride * BUFFER_GUARD_MULTIPLIER;
const uint32_t usage = GL_DYNAMIC_READ;
gl.bindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, buffer);
gl.bufferData(GL_TRANSFORM_FEEDBACK_BUFFER, size + guardSize, DE_NULL, usage);
writeBufferGuard(gl, GL_TRANSFORM_FEEDBACK_BUFFER, size, guardSize);
// \todo [2012-07-30 pyry] glBindBufferRange()?
gl.bindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, target, buffer);
GLU_EXPECT_NO_ERROR(gl.getError(), "transform feedback buffer setup");
}
// Setup attributes.
for (vector<Attribute>::const_iterator attrib = m_attributes.begin(); attrib != m_attributes.end(); attrib++)
{
int loc = gl.getAttribLocation(m_program->getProgram(), attrib->name.c_str());
glu::DataType scalarType = glu::getDataTypeScalarType(attrib->type.getBasicType());
int numComponents = glu::getDataTypeScalarSize(attrib->type.getBasicType());
const void *ptr = &inputData[0] + attrib->offset;
if (loc >= 0)
{
gl.enableVertexAttribArray(loc);
if (scalarType == glu::TYPE_FLOAT)
gl.vertexAttribPointer(loc, numComponents, GL_FLOAT, GL_FALSE, m_inputStride, ptr);
else if (scalarType == glu::TYPE_INT)
gl.vertexAttribIPointer(loc, numComponents, GL_INT, m_inputStride, ptr);
else if (scalarType == glu::TYPE_UINT)
gl.vertexAttribIPointer(loc, numComponents, GL_UNSIGNED_INT, m_inputStride, ptr);
}
}
// Setup viewport.
gl.viewport(viewportX, viewportY, viewportW, viewportH);
// Setup program.
gl.useProgram(m_program->getProgram());
gl.uniform4fv(gl.getUniformLocation(m_program->getProgram(), "u_scale"), 1, tcu::Vec4(0.01f).getPtr());
gl.uniform4fv(gl.getUniformLocation(m_program->getProgram(), "u_bias"), 1, tcu::Vec4(0.5f).getPtr());
// Enable query.
gl.beginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, *primitiveQuery);
GLU_EXPECT_NO_ERROR(gl.getError(), "glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN)");
// Draw.
{
int offset = 0;
bool tfEnabled = true;
gl.clear(GL_COLOR_BUFFER_BIT);
gl.beginTransformFeedback(getTransformFeedbackPrimitiveMode(m_primitiveType));
for (const DrawCall *call = first; call != end; call++)
{
// Pause or resume transform feedback if necessary.
if (call->transformFeedbackEnabled != tfEnabled)
{
if (call->transformFeedbackEnabled)
gl.resumeTransformFeedback();
else
gl.pauseTransformFeedback();
tfEnabled = call->transformFeedbackEnabled;
}
gl.drawArrays(m_primitiveType, offset, call->numElements);
offset += call->numElements;
}
// Resume feedback before finishing it.
if (!tfEnabled)
gl.resumeTransformFeedback();
gl.endTransformFeedback();
GLU_EXPECT_NO_ERROR(gl.getError(), "render");
}
gl.endQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
GLU_EXPECT_NO_ERROR(gl.getError(), "glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN)");
// Check and log query status right after submit
{
uint32_t available = GL_FALSE;
gl.getQueryObjectuiv(*primitiveQuery, GL_QUERY_RESULT_AVAILABLE, &available);
GLU_EXPECT_NO_ERROR(gl.getError(), "glGetQueryObjectuiv()");
log << TestLog::Message << "GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN status after submit: "
<< (available != GL_FALSE ? "GL_TRUE" : "GL_FALSE") << TestLog::EndMessage;
}
// Compare result buffers.
for (int bufferNdx = 0; bufferNdx < (int)m_outputBuffers.size(); bufferNdx++)
{
uint32_t buffer = m_outputBuffers[bufferNdx];
int stride = m_bufferStrides[bufferNdx];
int size = stride * numOutputs;
int guardSize = stride * BUFFER_GUARD_MULTIPLIER;
const void *bufPtr = DE_NULL;
// Bind buffer for reading.
gl.bindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, buffer);
bufPtr = gl.mapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, size + guardSize, GL_MAP_READ_BIT);
GLU_EXPECT_NO_ERROR(gl.getError(), "mapping buffer");
// Verify all output variables that are written to this buffer.
for (vector<Output>::const_iterator out = m_transformFeedbackOutputs.begin();
out != m_transformFeedbackOutputs.end(); out++)
{
if (out->bufferNdx != bufferNdx)
continue;
int inputOffset = 0;
int outputOffset = 0;
// Process all draw calls and check ones with transform feedback enabled.
for (const DrawCall *call = first; call != end; call++)
{
if (call->transformFeedbackEnabled)
{
const uint8_t *inputPtr = &inputData[0] + inputOffset * m_inputStride;
const uint8_t *outputPtr = (const uint8_t *)bufPtr + outputOffset * stride;
if (!compareTransformFeedbackOutput(log, m_primitiveType, *out, call->numElements, inputPtr,
m_inputStride, outputPtr, stride))
{
outputsOk = false;
break;
}
}
inputOffset += call->numElements;
outputOffset += call->transformFeedbackEnabled ?
getTransformFeedbackOutputCount(m_primitiveType, call->numElements) :
0;
}
}
// Verify guardband.
if (!verifyGuard((const uint8_t *)bufPtr + size, guardSize))
{
log << TestLog::Message << "Error: Transform feedback buffer overrun detected" << TestLog::EndMessage;
outputsOk = false;
}
gl.unmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);
}
// Check status after mapping buffers.
{
const bool mustBeReady = !m_outputBuffers.empty(); // Mapping buffer forces synchronization.
const int expectedCount = computeTransformFeedbackPrimitiveCount(m_primitiveType, first, end);
uint32_t available = GL_FALSE;
uint32_t numPrimitives = 0;
gl.getQueryObjectuiv(*primitiveQuery, GL_QUERY_RESULT_AVAILABLE, &available);
gl.getQueryObjectuiv(*primitiveQuery, GL_QUERY_RESULT, &numPrimitives);
GLU_EXPECT_NO_ERROR(gl.getError(), "glGetQueryObjectuiv()");
if (!mustBeReady && available == GL_FALSE)
{
log << TestLog::Message
<< "ERROR: GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN result not available after mapping buffers!"
<< TestLog::EndMessage;
queryOk = false;
}
log << TestLog::Message << "GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN = " << numPrimitives
<< TestLog::EndMessage;
if ((int)numPrimitives != expectedCount)
{
log << TestLog::Message << "ERROR: Expected " << expectedCount << " primitives!" << TestLog::EndMessage;
queryOk = false;
}
}
// Clear transform feedback state.
gl.bindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0);
for (int bufNdx = 0; bufNdx < (int)m_outputBuffers.size(); bufNdx++)
{
gl.bindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, 0);
gl.bindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, bufNdx, 0);
}
// Read back rendered image.
glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, frameWithTf.getAccess());
// Render without transform feedback.
{
int offset = 0;
gl.clear(GL_COLOR_BUFFER_BIT);
for (const DrawCall *call = first; call != end; call++)
{
gl.drawArrays(m_primitiveType, offset, call->numElements);
offset += call->numElements;
}
GLU_EXPECT_NO_ERROR(gl.getError(), "render");
glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, frameWithoutTf.getAccess());
}
// Compare images with and without transform feedback.
imagesOk = tcu::pixelThresholdCompare(log, "Result", "Image comparison result", frameWithoutTf, frameWithTf,
tcu::RGBA(1, 1, 1, 1), tcu::COMPARE_LOG_ON_ERROR);
if (imagesOk)
m_testCtx.getLog() << TestLog::Message
<< "Rendering result comparison between TF enabled and TF disabled passed."
<< TestLog::EndMessage;
else
m_testCtx.getLog() << TestLog::Message
<< "ERROR: Rendering result comparison between TF enabled and TF disabled failed!"
<< TestLog::EndMessage;
return outputsOk && imagesOk && queryOk;
}
// Test cases.
class PositionCase : public TransformFeedbackCase
{
public:
PositionCase(Context &context, const char *name, const char *desc, uint32_t bufferType, uint32_t primitiveType)
: TransformFeedbackCase(context, name, desc, bufferType, primitiveType)
{
m_progSpec.addTransformFeedbackVarying("gl_Position");
}
};
class PointSizeCase : public TransformFeedbackCase
{
public:
PointSizeCase(Context &context, const char *name, const char *desc, uint32_t bufferType, uint32_t primitiveType)
: TransformFeedbackCase(context, name, desc, bufferType, primitiveType)
{
m_progSpec.addTransformFeedbackVarying("gl_PointSize");
}
};
class BasicTypeCase : public TransformFeedbackCase
{
public:
BasicTypeCase(Context &context, const char *name, const char *desc, uint32_t bufferType, uint32_t primitiveType,
glu::DataType type, glu::Precision precision, Interpolation interpolation)
: TransformFeedbackCase(context, name, desc, bufferType, primitiveType)
{
m_progSpec.addVarying("v_varA", glu::VarType(type, precision), interpolation);
m_progSpec.addVarying("v_varB", glu::VarType(type, precision), interpolation);
m_progSpec.addTransformFeedbackVarying("v_varA");
m_progSpec.addTransformFeedbackVarying("v_varB");
}
};
class BasicArrayCase : public TransformFeedbackCase
{
public:
BasicArrayCase(Context &context, const char *name, const char *desc, uint32_t bufferType, uint32_t primitiveType,
glu::DataType type, glu::Precision precision, Interpolation interpolation)
: TransformFeedbackCase(context, name, desc, bufferType, primitiveType)
{
if (glu::isDataTypeMatrix(type) || m_bufferMode == GL_SEPARATE_ATTRIBS)
{
// \note For matrix types we need to use reduced array sizes or otherwise we will exceed maximum attribute (16)
// or transform feedback component count (64).
// On separate attribs mode maximum component count per varying is 4.
m_progSpec.addVarying("v_varA", glu::VarType(glu::VarType(type, precision), 1), interpolation);
m_progSpec.addVarying("v_varB", glu::VarType(glu::VarType(type, precision), 2), interpolation);
}
else
{
m_progSpec.addVarying("v_varA", glu::VarType(glu::VarType(type, precision), 3), interpolation);
m_progSpec.addVarying("v_varB", glu::VarType(glu::VarType(type, precision), 4), interpolation);
}
m_progSpec.addTransformFeedbackVarying("v_varA");
m_progSpec.addTransformFeedbackVarying("v_varB");
}
};
class ArrayElementCase : public TransformFeedbackCase
{
public:
ArrayElementCase(Context &context, const char *name, const char *desc, uint32_t bufferType, uint32_t primitiveType,
glu::DataType type, glu::Precision precision, Interpolation interpolation)
: TransformFeedbackCase(context, name, desc, bufferType, primitiveType)
{
m_progSpec.addVarying("v_varA", glu::VarType(glu::VarType(type, precision), 3), interpolation);
m_progSpec.addVarying("v_varB", glu::VarType(glu::VarType(type, precision), 4), interpolation);
m_progSpec.addTransformFeedbackVarying("v_varA[1]");
m_progSpec.addTransformFeedbackVarying("v_varB[0]");
m_progSpec.addTransformFeedbackVarying("v_varB[3]");
}
};
class RandomCase : public TransformFeedbackCase
{
public:
RandomCase(Context &context, const char *name, const char *desc, uint32_t bufferType, uint32_t primitiveType,
uint32_t seed, bool elementCapture)
: TransformFeedbackCase(context, name, desc, bufferType, primitiveType)
, m_seed(seed)
, m_elementCapture(elementCapture)
{
}
void init(void)
{
// \note Hard-coded indices and hackery are used when indexing this, beware.
static const glu::DataType typeCandidates[] = {
glu::TYPE_FLOAT, glu::TYPE_FLOAT_VEC2, glu::TYPE_FLOAT_VEC3, glu::TYPE_FLOAT_VEC4,
glu::TYPE_INT, glu::TYPE_INT_VEC2, glu::TYPE_INT_VEC3, glu::TYPE_INT_VEC4,
glu::TYPE_UINT, glu::TYPE_UINT_VEC2, glu::TYPE_UINT_VEC3, glu::TYPE_UINT_VEC4,
glu::TYPE_FLOAT_MAT2, glu::TYPE_FLOAT_MAT2X3, glu::TYPE_FLOAT_MAT2X4,
glu::TYPE_FLOAT_MAT3X2, glu::TYPE_FLOAT_MAT3, glu::TYPE_FLOAT_MAT3X4,
glu::TYPE_FLOAT_MAT4X2, glu::TYPE_FLOAT_MAT4X3, glu::TYPE_FLOAT_MAT4};
static const glu::Precision precisions[] = {glu::PRECISION_LOWP, glu::PRECISION_MEDIUMP, glu::PRECISION_HIGHP};
static const Interpolation interpModes[] = {INTERPOLATION_FLAT, INTERPOLATION_SMOOTH, INTERPOLATION_CENTROID};
const int maxAttributeVectors = 16;
// const int maxTransformFeedbackComponents = 64; // \note It is enough to limit attribute set size.
bool isSeparateMode = m_bufferMode == GL_SEPARATE_ATTRIBS;
int maxTransformFeedbackVars = isSeparateMode ? 4 : maxAttributeVectors;
const float arrayWeight = 0.3f;
const float positionWeight = 0.7f;
const float pointSizeWeight = 0.1f;
const float captureFullArrayWeight = 0.5f;
de::Random rnd(m_seed);
bool usePosition = rnd.getFloat() < positionWeight;
bool usePointSize = rnd.getFloat() < pointSizeWeight;
int numAttribVectorsToUse = rnd.getInt(1, maxAttributeVectors - 1 /*position*/ - (usePointSize ? 1 : 0));
int numAttributeVectors = 0;
int varNdx = 0;
// Generate varyings.
while (numAttributeVectors < numAttribVectorsToUse)
{
int maxVecs = isSeparateMode ? de::min(2 /*at most 2*mat2*/, numAttribVectorsToUse - numAttributeVectors) :
numAttribVectorsToUse - numAttributeVectors;
const glu::DataType *begin = &typeCandidates[0];
const glu::DataType *end = begin + (maxVecs >= 4 ? 21 :
maxVecs >= 3 ? 18 :
maxVecs >= 2 ? (isSeparateMode ? 13 : 15) :
12);
glu::DataType type = rnd.choose<glu::DataType>(begin, end);
glu::Precision precision =
rnd.choose<glu::Precision>(&precisions[0], &precisions[0] + DE_LENGTH_OF_ARRAY(precisions));
Interpolation interp =
glu::getDataTypeScalarType(type) == glu::TYPE_FLOAT ?
rnd.choose<Interpolation>(&interpModes[0], &interpModes[0] + DE_LENGTH_OF_ARRAY(interpModes)) :
INTERPOLATION_FLAT;
int numVecs = glu::isDataTypeMatrix(type) ? glu::getDataTypeMatrixNumColumns(type) : 1;
int numComps = glu::getDataTypeScalarSize(type);
int maxArrayLen = de::max(1, isSeparateMode ? 4 / numComps : maxVecs / numVecs);
bool useArray = rnd.getFloat() < arrayWeight;
int arrayLen = useArray ? rnd.getInt(1, maxArrayLen) : 1;
std::string name = "v_var" + de::toString(varNdx);
if (useArray)
m_progSpec.addVarying(name.c_str(), glu::VarType(glu::VarType(type, precision), arrayLen), interp);
else
m_progSpec.addVarying(name.c_str(), glu::VarType(type, precision), interp);
numAttributeVectors += arrayLen * numVecs;
varNdx += 1;
}
// Generate transform feedback candidate set.
vector<string> tfCandidates;
if (usePosition)
tfCandidates.push_back("gl_Position");
if (usePointSize)
tfCandidates.push_back("gl_PointSize");
for (int ndx = 0; ndx < varNdx /* num varyings */; ndx++)
{
const Varying &var = m_progSpec.getVaryings()[ndx];
if (var.type.isArrayType())
{
const bool captureFull = m_elementCapture ? (rnd.getFloat() < captureFullArrayWeight) : true;
if (captureFull)
tfCandidates.push_back(var.name);
else
{
const int numElem = var.type.getArraySize();
for (int elemNdx = 0; elemNdx < numElem; elemNdx++)
tfCandidates.push_back(var.name + "[" + de::toString(elemNdx) + "]");
}
}
else
tfCandidates.push_back(var.name);
}
// Pick random selection.
vector<string> tfVaryings(de::min((int)tfCandidates.size(), maxTransformFeedbackVars));
rnd.choose(tfCandidates.begin(), tfCandidates.end(), tfVaryings.begin(), (int)tfVaryings.size());
rnd.shuffle(tfVaryings.begin(), tfVaryings.end());
for (vector<string>::const_iterator var = tfVaryings.begin(); var != tfVaryings.end(); var++)
m_progSpec.addTransformFeedbackVarying(var->c_str());
TransformFeedbackCase::init();
}
private:
uint32_t m_seed;
bool m_elementCapture;
};
} // namespace TransformFeedback
using namespace TransformFeedback;
TransformFeedbackTests::TransformFeedbackTests(Context &context)
: TestCaseGroup(context, "transform_feedback", "Transform feedback tests")
{
}
TransformFeedbackTests::~TransformFeedbackTests(void)
{
}
void TransformFeedbackTests::init(void)
{
static const struct
{
const char *name;
uint32_t mode;
} bufferModes[] = {{"separate", GL_SEPARATE_ATTRIBS}, {"interleaved", GL_INTERLEAVED_ATTRIBS}};
static const struct
{
const char *name;
uint32_t type;
} primitiveTypes[] = {
{"points", GL_POINTS}, {"lines", GL_LINES}, {"triangles", GL_TRIANGLES}
// Not supported by GLES3.
// { "line_strip", GL_LINE_STRIP },
// { "line_loop", GL_LINE_LOOP },
// { "triangle_fan", GL_TRIANGLE_FAN },
// { "triangle_strip", GL_TRIANGLE_STRIP }
};
static const glu::DataType basicTypes[] = {glu::TYPE_FLOAT, glu::TYPE_FLOAT_VEC2, glu::TYPE_FLOAT_VEC3,
glu::TYPE_FLOAT_VEC4, glu::TYPE_FLOAT_MAT2, glu::TYPE_FLOAT_MAT2X3,
glu::TYPE_FLOAT_MAT2X4, glu::TYPE_FLOAT_MAT3X2, glu::TYPE_FLOAT_MAT3,
glu::TYPE_FLOAT_MAT3X4, glu::TYPE_FLOAT_MAT4X2, glu::TYPE_FLOAT_MAT4X3,
glu::TYPE_FLOAT_MAT4, glu::TYPE_INT, glu::TYPE_INT_VEC2,
glu::TYPE_INT_VEC3, glu::TYPE_INT_VEC4, glu::TYPE_UINT,
glu::TYPE_UINT_VEC2, glu::TYPE_UINT_VEC3, glu::TYPE_UINT_VEC4};
static const glu::Precision precisions[] = {glu::PRECISION_LOWP, glu::PRECISION_MEDIUMP, glu::PRECISION_HIGHP};
static const struct
{
const char *name;
Interpolation interp;
} interpModes[] = {
{"smooth", INTERPOLATION_SMOOTH}, {"flat", INTERPOLATION_FLAT}, {"centroid", INTERPOLATION_CENTROID}};
// .position
{
tcu::TestCaseGroup *positionGroup =
new tcu::TestCaseGroup(m_testCtx, "position", "gl_Position capture using transform feedback");
addChild(positionGroup);
for (int primitiveType = 0; primitiveType < DE_LENGTH_OF_ARRAY(primitiveTypes); primitiveType++)
{
for (int bufferMode = 0; bufferMode < DE_LENGTH_OF_ARRAY(bufferModes); bufferMode++)
{
string name = string(primitiveTypes[primitiveType].name) + "_" + bufferModes[bufferMode].name;
positionGroup->addChild(new PositionCase(m_context, name.c_str(), "", bufferModes[bufferMode].mode,
primitiveTypes[primitiveType].type));
}
}
}
// .point_size
{
tcu::TestCaseGroup *pointSizeGroup =
new tcu::TestCaseGroup(m_testCtx, "point_size", "gl_PointSize capture using transform feedback");
addChild(pointSizeGroup);
for (int primitiveType = 0; primitiveType < DE_LENGTH_OF_ARRAY(primitiveTypes); primitiveType++)
{
for (int bufferMode = 0; bufferMode < DE_LENGTH_OF_ARRAY(bufferModes); bufferMode++)
{
string name = string(primitiveTypes[primitiveType].name) + "_" + bufferModes[bufferMode].name;
pointSizeGroup->addChild(new PointSizeCase(m_context, name.c_str(), "", bufferModes[bufferMode].mode,
primitiveTypes[primitiveType].type));
}
}
}
// .basic_type
{
tcu::TestCaseGroup *basicTypeGroup =
new tcu::TestCaseGroup(m_testCtx, "basic_types", "Basic types in transform feedback");
addChild(basicTypeGroup);
for (int bufferModeNdx = 0; bufferModeNdx < DE_LENGTH_OF_ARRAY(bufferModes); bufferModeNdx++)
{
tcu::TestCaseGroup *modeGroup = new tcu::TestCaseGroup(m_testCtx, bufferModes[bufferModeNdx].name, "");
uint32_t bufferMode = bufferModes[bufferModeNdx].mode;
basicTypeGroup->addChild(modeGroup);
for (int primitiveTypeNdx = 0; primitiveTypeNdx < DE_LENGTH_OF_ARRAY(primitiveTypes); primitiveTypeNdx++)
{
tcu::TestCaseGroup *primitiveGroup =
new tcu::TestCaseGroup(m_testCtx, primitiveTypes[primitiveTypeNdx].name, "");
uint32_t primitiveType = primitiveTypes[primitiveTypeNdx].type;
modeGroup->addChild(primitiveGroup);
for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(basicTypes); typeNdx++)
{
glu::DataType type = basicTypes[typeNdx];
bool isFloat = glu::getDataTypeScalarType(type) == glu::TYPE_FLOAT;
for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
{
glu::Precision precision = precisions[precNdx];
string name = string(glu::getPrecisionName(precision)) + "_" + glu::getDataTypeName(type);
primitiveGroup->addChild(
new BasicTypeCase(m_context, name.c_str(), "", bufferMode, primitiveType, type, precision,
isFloat ? INTERPOLATION_SMOOTH : INTERPOLATION_FLAT));
}
}
}
}
}
// .array
{
tcu::TestCaseGroup *arrayGroup = new tcu::TestCaseGroup(m_testCtx, "array", "Capturing whole array in TF");
addChild(arrayGroup);
for (int bufferModeNdx = 0; bufferModeNdx < DE_LENGTH_OF_ARRAY(bufferModes); bufferModeNdx++)
{
tcu::TestCaseGroup *modeGroup = new tcu::TestCaseGroup(m_testCtx, bufferModes[bufferModeNdx].name, "");
uint32_t bufferMode = bufferModes[bufferModeNdx].mode;
arrayGroup->addChild(modeGroup);
for (int primitiveTypeNdx = 0; primitiveTypeNdx < DE_LENGTH_OF_ARRAY(primitiveTypes); primitiveTypeNdx++)
{
tcu::TestCaseGroup *primitiveGroup =
new tcu::TestCaseGroup(m_testCtx, primitiveTypes[primitiveTypeNdx].name, "");
uint32_t primitiveType = primitiveTypes[primitiveTypeNdx].type;
modeGroup->addChild(primitiveGroup);
for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(basicTypes); typeNdx++)
{
glu::DataType type = basicTypes[typeNdx];
bool isFloat = glu::getDataTypeScalarType(type) == glu::TYPE_FLOAT;
for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
{
glu::Precision precision = precisions[precNdx];
string name = string(glu::getPrecisionName(precision)) + "_" + glu::getDataTypeName(type);
primitiveGroup->addChild(
new BasicArrayCase(m_context, name.c_str(), "", bufferMode, primitiveType, type, precision,
isFloat ? INTERPOLATION_SMOOTH : INTERPOLATION_FLAT));
}
}
}
}
}
// .array_element
{
tcu::TestCaseGroup *arrayElemGroup =
new tcu::TestCaseGroup(m_testCtx, "array_element", "Capturing single array element in TF");
addChild(arrayElemGroup);
for (int bufferModeNdx = 0; bufferModeNdx < DE_LENGTH_OF_ARRAY(bufferModes); bufferModeNdx++)
{
tcu::TestCaseGroup *modeGroup = new tcu::TestCaseGroup(m_testCtx, bufferModes[bufferModeNdx].name, "");
uint32_t bufferMode = bufferModes[bufferModeNdx].mode;
arrayElemGroup->addChild(modeGroup);
for (int primitiveTypeNdx = 0; primitiveTypeNdx < DE_LENGTH_OF_ARRAY(primitiveTypes); primitiveTypeNdx++)
{
tcu::TestCaseGroup *primitiveGroup =
new tcu::TestCaseGroup(m_testCtx, primitiveTypes[primitiveTypeNdx].name, "");
uint32_t primitiveType = primitiveTypes[primitiveTypeNdx].type;
modeGroup->addChild(primitiveGroup);
for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(basicTypes); typeNdx++)
{
glu::DataType type = basicTypes[typeNdx];
bool isFloat = glu::getDataTypeScalarType(type) == glu::TYPE_FLOAT;
for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
{
glu::Precision precision = precisions[precNdx];
string name = string(glu::getPrecisionName(precision)) + "_" + glu::getDataTypeName(type);
primitiveGroup->addChild(
new ArrayElementCase(m_context, name.c_str(), "", bufferMode, primitiveType, type,
precision, isFloat ? INTERPOLATION_SMOOTH : INTERPOLATION_FLAT));
}
}
}
}
}
// .interpolation
{
tcu::TestCaseGroup *interpolationGroup = new tcu::TestCaseGroup(
m_testCtx, "interpolation", "Different interpolation modes in transform feedback varyings");
addChild(interpolationGroup);
for (int modeNdx = 0; modeNdx < DE_LENGTH_OF_ARRAY(interpModes); modeNdx++)
{
Interpolation interp = interpModes[modeNdx].interp;
tcu::TestCaseGroup *modeGroup = new tcu::TestCaseGroup(m_testCtx, interpModes[modeNdx].name, "");
interpolationGroup->addChild(modeGroup);
for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
{
glu::Precision precision = precisions[precNdx];
for (int primitiveType = 0; primitiveType < DE_LENGTH_OF_ARRAY(primitiveTypes); primitiveType++)
{
for (int bufferMode = 0; bufferMode < DE_LENGTH_OF_ARRAY(bufferModes); bufferMode++)
{
string name = string(glu::getPrecisionName(precision)) + "_vec4_" +
primitiveTypes[primitiveType].name + "_" + bufferModes[bufferMode].name;
modeGroup->addChild(new BasicTypeCase(m_context, name.c_str(), "", bufferModes[bufferMode].mode,
primitiveTypes[primitiveType].type, glu::TYPE_FLOAT_VEC4,
precision, interp));
}
}
}
}
}
// .random
{
tcu::TestCaseGroup *randomGroup =
new tcu::TestCaseGroup(m_testCtx, "random", "Randomized transform feedback cases");
addChild(randomGroup);
for (int bufferModeNdx = 0; bufferModeNdx < DE_LENGTH_OF_ARRAY(bufferModes); bufferModeNdx++)
{
tcu::TestCaseGroup *modeGroup = new tcu::TestCaseGroup(m_testCtx, bufferModes[bufferModeNdx].name, "");
uint32_t bufferMode = bufferModes[bufferModeNdx].mode;
randomGroup->addChild(modeGroup);
for (int primitiveTypeNdx = 0; primitiveTypeNdx < DE_LENGTH_OF_ARRAY(primitiveTypes); primitiveTypeNdx++)
{
tcu::TestCaseGroup *primitiveGroup =
new tcu::TestCaseGroup(m_testCtx, primitiveTypes[primitiveTypeNdx].name, "");
uint32_t primitiveType = primitiveTypes[primitiveTypeNdx].type;
modeGroup->addChild(primitiveGroup);
for (int ndx = 0; ndx < 10; ndx++)
{
uint32_t seed = deInt32Hash(bufferMode) ^ deInt32Hash(primitiveType) ^ deInt32Hash(ndx);
primitiveGroup->addChild(new RandomCase(m_context, de::toString(ndx + 1).c_str(), "", bufferMode,
primitiveType, seed, true));
}
}
}
}
// .random_full_array_capture
{
tcu::TestCaseGroup *randomNecGroup =
new tcu::TestCaseGroup(m_testCtx, "random_full_array_capture",
"Randomized transform feedback cases without array element capture");
addChild(randomNecGroup);
for (int bufferModeNdx = 0; bufferModeNdx < DE_LENGTH_OF_ARRAY(bufferModes); bufferModeNdx++)
{
tcu::TestCaseGroup *modeGroup = new tcu::TestCaseGroup(m_testCtx, bufferModes[bufferModeNdx].name, "");
uint32_t bufferMode = bufferModes[bufferModeNdx].mode;
randomNecGroup->addChild(modeGroup);
for (int primitiveTypeNdx = 0; primitiveTypeNdx < DE_LENGTH_OF_ARRAY(primitiveTypes); primitiveTypeNdx++)
{
tcu::TestCaseGroup *primitiveGroup =
new tcu::TestCaseGroup(m_testCtx, primitiveTypes[primitiveTypeNdx].name, "");
uint32_t primitiveType = primitiveTypes[primitiveTypeNdx].type;
modeGroup->addChild(primitiveGroup);
for (int ndx = 0; ndx < 10; ndx++)
{
uint32_t seed = deInt32Hash(bufferMode) ^ deInt32Hash(primitiveType) ^ deInt32Hash(ndx);
primitiveGroup->addChild(new RandomCase(m_context, de::toString(ndx + 1).c_str(), "", bufferMode,
primitiveType, seed, false));
}
}
}
}
}
} // namespace Functional
} // namespace gles3
} // namespace deqp