/*------------------------------------------------------------------------- * 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 #include #include 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 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 &getStructs(void) const { return m_structs; } const vector &getVaryings(void) const { return m_varyings; } const vector &getTransformFeedbackVaryings(void) const { return m_transformFeedbackVaryings; } bool isPointSizeUsed(void) const; private: ProgramSpec(const ProgramSpec &other); ProgramSpec &operator=(const ProgramSpec &other); vector m_structs; vector m_varyings; vector m_transformFeedbackVaryings; }; // ProgramSpec ProgramSpec::ProgramSpec(void) { } ProgramSpec::~ProgramSpec(void) { for (vector::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::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::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::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::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::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::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::const_iterator>( spec.getTransformFeedbackVaryings().begin(), spec.getTransformFeedbackVaryings().end()) << glu::TransformFeedbackMode(bufferMode)); } // Helpers. static void computeInputLayout(vector &attributes, int &inputStride, const vector &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::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 &transformFeedbackOutputs, const vector &attributes, const vector &varyings, const vector &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 &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::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::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 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 m_attributes; vector m_transformFeedbackOutputs; vector m_bufferStrides; // GL state. glu::ShaderProgram *m_program; glu::TransformFeedback *m_transformFeedback; vector 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::const_iterator tfVar = spec.getTransformFeedbackVaryings().begin(); tfVar != spec.getTransformFeedbackVaryings().end(); ++tfVar) { string varName = glu::parseVariableName(tfVar->c_str()); vector::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::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::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 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::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::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(begin, end); glu::Precision precision = rnd.choose(&precisions[0], &precisions[0] + DE_LENGTH_OF_ARRAY(precisions)); Interpolation interp = glu::getDataTypeScalarType(type) == glu::TYPE_FLOAT ? rnd.choose(&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 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 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::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