/*-------------------------------------------------------------------------
* drawElements Quality Program OpenGL ES 3.1 Module
* -------------------------------------------------
*
* Copyright 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*//*!
* \file
* \brief Tests for separate shader objects
*//*--------------------------------------------------------------------*/
#include "es31fSeparateShaderTests.hpp"
#include "deInt32.h"
#include "deString.h"
#include "deStringUtil.hpp"
#include "deUniquePtr.hpp"
#include "deRandom.hpp"
#include "deSTLUtil.hpp"
#include "tcuCommandLine.hpp"
#include "tcuImageCompare.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuResultCollector.hpp"
#include "tcuRGBA.hpp"
#include "tcuSurface.hpp"
#include "tcuStringTemplate.hpp"
#include "gluCallLogWrapper.hpp"
#include "gluPixelTransfer.hpp"
#include "gluRenderContext.hpp"
#include "gluShaderProgram.hpp"
#include "gluVarType.hpp"
#include "glsShaderLibrary.hpp"
#include "glwFunctions.hpp"
#include "glwDefs.hpp"
#include "glwEnums.hpp"
#include <cstdarg>
#include <algorithm>
#include <map>
#include <sstream>
#include <string>
#include <set>
#include <vector>
namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{
using std::map;
using std::set;
using std::ostringstream;
using std::string;
using std::vector;
using de::MovePtr;
using de::Random;
using de::UniquePtr;
using tcu::MessageBuilder;
using tcu::RenderTarget;
using tcu::StringTemplate;
using tcu::Surface;
using tcu::TestLog;
using tcu::ResultCollector;
using glu::CallLogWrapper;
using glu::DataType;
using glu::VariableDeclaration;
using glu::Precision;
using glu::Program;
using glu::ProgramPipeline;
using glu::ProgramSources;
using glu::RenderContext;
using glu::ShaderProgram;
using glu::ShaderType;
using glu::Storage;
using glu::VarType;
using glu::VertexSource;
using glu::FragmentSource;
using glu::ProgramSeparable;
using namespace glw;
#define LOG_CALL(CALL) do \
{ \
enableLogging(true); \
CALL; \
enableLogging(false); \
} while (deGetFalse())
enum
{
VIEWPORT_SIZE = 128
};
enum VaryingInterpolation
{
VARYINGINTERPOLATION_SMOOTH = 0,
VARYINGINTERPOLATION_FLAT,
VARYINGINTERPOLATION_CENTROID,
VARYINGINTERPOLATION_DEFAULT,
VARYINGINTERPOLATION_RANDOM,
VARYINGINTERPOLATION_LAST
};
DataType randomType (Random& rnd)
{
using namespace glu;
if (rnd.getInt(0, 7) == 0)
{
const int numCols = rnd.getInt(2, 4), numRows = rnd.getInt(2, 4);
return getDataTypeMatrix(numCols, numRows);
}
else
{
static const DataType s_types[] = { TYPE_FLOAT, TYPE_INT, TYPE_UINT };
static const float s_weights[] = { 3.0, 1.0, 1.0 };
const int size = rnd.getInt(1, 4);
const DataType scalarType = rnd.chooseWeighted<DataType>(
DE_ARRAY_BEGIN(s_types), DE_ARRAY_END(s_types), DE_ARRAY_BEGIN(s_weights));
return getDataTypeVector(scalarType, size);
}
DE_ASSERT(!"Impossible");
return TYPE_INVALID;
}
VaryingInterpolation randomInterpolation (Random& rnd)
{
static const VaryingInterpolation s_validInterpolations[] =
{
VARYINGINTERPOLATION_SMOOTH,
VARYINGINTERPOLATION_FLAT,
VARYINGINTERPOLATION_CENTROID,
VARYINGINTERPOLATION_DEFAULT,
};
return s_validInterpolations[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_validInterpolations)-1)];
}
glu::Interpolation getGluInterpolation (VaryingInterpolation interpolation)
{
switch (interpolation)
{
case VARYINGINTERPOLATION_SMOOTH: return glu::INTERPOLATION_SMOOTH;
case VARYINGINTERPOLATION_FLAT: return glu::INTERPOLATION_FLAT;
case VARYINGINTERPOLATION_CENTROID: return glu::INTERPOLATION_CENTROID;
case VARYINGINTERPOLATION_DEFAULT: return glu::INTERPOLATION_LAST; //!< Last means no qualifier, i.e. default
default:
DE_ASSERT(!"Invalid interpolation");
return glu::INTERPOLATION_LAST;
}
}
// used only for debug sanity checks
#if defined(DE_DEBUG)
VaryingInterpolation getVaryingInterpolation (glu::Interpolation interpolation)
{
switch (interpolation)
{
case glu::INTERPOLATION_SMOOTH: return VARYINGINTERPOLATION_SMOOTH;
case glu::INTERPOLATION_FLAT: return VARYINGINTERPOLATION_FLAT;
case glu::INTERPOLATION_CENTROID: return VARYINGINTERPOLATION_CENTROID;
case glu::INTERPOLATION_LAST: return VARYINGINTERPOLATION_DEFAULT; //!< Last means no qualifier, i.e. default
default:
DE_ASSERT(!"Invalid interpolation");
return VARYINGINTERPOLATION_LAST;
}
}
#endif
enum BindingKind
{
BINDING_NAME,
BINDING_LOCATION,
BINDING_LAST
};
BindingKind randomBinding (Random& rnd)
{
return rnd.getBool() ? BINDING_LOCATION : BINDING_NAME;
}
void printInputColor (ostringstream& oss, const VariableDeclaration& input)
{
using namespace glu;
const DataType basicType = input.varType.getBasicType();
string exp = input.name;
switch (getDataTypeScalarType(basicType))
{
case TYPE_FLOAT:
break;
case TYPE_INT:
case TYPE_UINT:
{
DataType floatType = getDataTypeFloatScalars(basicType);
exp = string() + "(" + getDataTypeName(floatType) + "(" + exp + ") / 255.0" + ")";
break;
}
default:
DE_ASSERT(!"Impossible");
}
if (isDataTypeScalarOrVector(basicType))
{
switch (getDataTypeScalarSize(basicType))
{
case 1:
oss << "hsv(vec3(" << exp << ", 1.0, 1.0))";
break;
case 2:
oss << "hsv(vec3(" << exp << ", 1.0))";
break;
case 3:
oss << "vec4(" << exp << ", 1.0)";
break;
case 4:
oss << exp;
break;
default:
DE_ASSERT(!"Impossible");
}
}
else if (isDataTypeMatrix(basicType))
{
int rows = getDataTypeMatrixNumRows(basicType);
int columns = getDataTypeMatrixNumColumns(basicType);
if (rows == columns)
oss << "hsv(vec3(determinant(" << exp << ")))";
else
{
if (rows != 3 && columns >= 3)
{
exp = "transpose(" + exp + ")";
std::swap(rows, columns);
}
exp = exp + "[0]";
if (rows > 3)
exp = exp + ".xyz";
oss << "hsv(" << exp << ")";
}
}
else
DE_ASSERT(!"Impossible");
}
// Representation for the varyings between vertex and fragment shaders
struct VaryingParams
{
VaryingParams (void)
: count (0)
, type (glu::TYPE_LAST)
, binding (BINDING_LAST)
, vtxInterp (VARYINGINTERPOLATION_LAST)
, frgInterp (VARYINGINTERPOLATION_LAST) {}
int count;
DataType type;
BindingKind binding;
VaryingInterpolation vtxInterp;
VaryingInterpolation frgInterp;
};
struct VaryingInterface
{
vector<VariableDeclaration> vtxOutputs;
vector<VariableDeclaration> frgInputs;
};
// Generate corresponding input and output variable declarations that may vary
// in compatible ways.
VaryingInterpolation chooseInterpolation (VaryingInterpolation param, DataType type, Random& rnd)
{
if (glu::getDataTypeScalarType(type) != glu::TYPE_FLOAT)
return VARYINGINTERPOLATION_FLAT;
if (param == VARYINGINTERPOLATION_RANDOM)
return randomInterpolation(rnd);
return param;
}
bool isSSOCompatibleInterpolation (VaryingInterpolation vertexInterpolation, VaryingInterpolation fragmentInterpolation)
{
// interpolations must be fully specified
DE_ASSERT(vertexInterpolation != VARYINGINTERPOLATION_RANDOM);
DE_ASSERT(vertexInterpolation < VARYINGINTERPOLATION_LAST);
DE_ASSERT(fragmentInterpolation != VARYINGINTERPOLATION_RANDOM);
DE_ASSERT(fragmentInterpolation < VARYINGINTERPOLATION_LAST);
// interpolation can only be either smooth or flat. Auxiliary storage does not matter.
const bool isSmoothVtx = (vertexInterpolation == VARYINGINTERPOLATION_SMOOTH) || //!< trivial
(vertexInterpolation == VARYINGINTERPOLATION_DEFAULT) || //!< default to smooth
(vertexInterpolation == VARYINGINTERPOLATION_CENTROID); //!< default to smooth, ignore storage
const bool isSmoothFrag = (fragmentInterpolation == VARYINGINTERPOLATION_SMOOTH) || //!< trivial
(fragmentInterpolation == VARYINGINTERPOLATION_DEFAULT) || //!< default to smooth
(fragmentInterpolation == VARYINGINTERPOLATION_CENTROID); //!< default to smooth, ignore storage
// Khronos bug #12630: flat / smooth qualifiers must match in SSO
return isSmoothVtx == isSmoothFrag;
}
VaryingInterface genVaryingInterface (const VaryingParams& params,
Random& rnd)
{
using namespace glu;
VaryingInterface ret;
int offset = 0;
for (int varNdx = 0; varNdx < params.count; ++varNdx)
{
const BindingKind binding = ((params.binding == BINDING_LAST)
? randomBinding(rnd) : params.binding);
const DataType type = ((params.type == TYPE_LAST)
? randomType(rnd) : params.type);
const VaryingInterpolation vtxInterp = chooseInterpolation(params.vtxInterp, type, rnd);
const VaryingInterpolation frgInterp = chooseInterpolation(params.frgInterp, type, rnd);
const VaryingInterpolation vtxCompatInterp = (isSSOCompatibleInterpolation(vtxInterp, frgInterp))
? (vtxInterp) : (frgInterp);
const int loc = ((binding == BINDING_LOCATION) ? offset : -1);
const string ndxStr = de::toString(varNdx);
const string vtxName = ((binding == BINDING_NAME)
? "var" + ndxStr : "vtxVar" + ndxStr);
const string frgName = ((binding == BINDING_NAME)
? "var" + ndxStr : "frgVar" + ndxStr);
const VarType varType (type, PRECISION_HIGHP);
offset += getDataTypeNumLocations(type);
// Over 16 locations aren't necessarily supported, so halt here.
if (offset > 16)
break;
ret.vtxOutputs.push_back(
VariableDeclaration(varType, vtxName, STORAGE_OUT, getGluInterpolation(vtxCompatInterp), loc));
ret.frgInputs.push_back(
VariableDeclaration(varType, frgName, STORAGE_IN, getGluInterpolation(frgInterp), loc));
}
return ret;
}
// Create vertex output variable declarations that are maximally compatible
// with the fragment input variables.
vector<VariableDeclaration> varyingCompatVtxOutputs (const VaryingInterface& varyings)
{
vector<VariableDeclaration> outputs = varyings.vtxOutputs;
for (size_t i = 0; i < outputs.size(); ++i)
{
outputs[i].interpolation = varyings.frgInputs[i].interpolation;
outputs[i].name = varyings.frgInputs[i].name;
}
return outputs;
}
// Shader source generation
void printFloat (ostringstream& oss, double d)
{
oss.setf(oss.fixed | oss.internal);
oss.precision(4);
oss.width(7);
oss << d;
}
void printFloatDeclaration (ostringstream& oss,
const string& varName,
bool uniform,
GLfloat value = 0.0)
{
using namespace glu;
const VarType varType (TYPE_FLOAT, PRECISION_HIGHP);
if (uniform)
oss << VariableDeclaration(varType, varName, STORAGE_UNIFORM) << ";\n";
else
oss << VariableDeclaration(varType, varName, STORAGE_CONST)
<< " = " << de::floatToString(value, 6) << ";\n";
}
void printRandomInitializer (ostringstream& oss, DataType type, Random& rnd)
{
using namespace glu;
const int size = getDataTypeScalarSize(type);
if (size > 0)
oss << getDataTypeName(type) << "(";
for (int i = 0; i < size; ++i)
{
oss << (i == 0 ? "" : ", ");
switch (getDataTypeScalarType(type))
{
case TYPE_FLOAT:
printFloat(oss, rnd.getInt(0, 16) / 16.0);
break;
case TYPE_INT:
case TYPE_UINT:
oss << rnd.getInt(0, 255);
break;
case TYPE_BOOL:
oss << (rnd.getBool() ? "true" : "false");
break;
default:
DE_ASSERT(!"Impossible");
}
}
if (size > 0)
oss << ")";
}
string genVtxShaderSrc (deUint32 seed,
const vector<VariableDeclaration>& outputs,
const string& varName,
bool uniform,
float value = 0.0)
{
ostringstream oss;
Random rnd (seed);
enum { NUM_COMPONENTS = 2 };
static const int s_quadrants[][NUM_COMPONENTS] = { {1, 1}, {-1, 1}, {1, -1} };
oss << "#version 310 es\n";
printFloatDeclaration(oss, varName, uniform, value);
for (vector<VariableDeclaration>::const_iterator it = outputs.begin();
it != outputs.end(); ++it)
oss << *it << ";\n";
oss << "const vec2 triangle[3] = vec2[3](\n";
for (int vertexNdx = 0; vertexNdx < DE_LENGTH_OF_ARRAY(s_quadrants); ++vertexNdx)
{
oss << "\tvec2(";
for (int componentNdx = 0; componentNdx < NUM_COMPONENTS; ++componentNdx)
{
printFloat(oss, s_quadrants[vertexNdx][componentNdx] * rnd.getInt(4,16) / 16.0);
oss << (componentNdx < 1 ? ", " : "");
}
oss << ")" << (vertexNdx < 2 ? "," : "") << "\n";
}
oss << ");\n";
for (vector<VariableDeclaration>::const_iterator it = outputs.begin();
it != outputs.end(); ++it)
{
const DataType type = it->varType.getBasicType();
const string typeName = glu::getDataTypeName(type);
oss << "const " << typeName << " " << it->name << "Inits[3] = "
<< typeName << "[3](\n";
for (int i = 0; i < 3; ++i)
{
oss << (i == 0 ? "\t" : ",\n\t");
printRandomInitializer(oss, type, rnd);
}
oss << ");\n";
}
oss << "void main (void)\n"
<< "{\n"
<< "\tgl_Position = vec4(" << varName << " * triangle[gl_VertexID], 0.0, 1.0);\n";
for (vector<VariableDeclaration>::const_iterator it = outputs.begin();
it != outputs.end(); ++it)
oss << "\t" << it->name << " = " << it->name << "Inits[gl_VertexID];\n";
oss << "}\n";
return oss.str();
}
string genFrgShaderSrc (deUint32 seed,
const vector<VariableDeclaration>& inputs,
const string& varName,
bool uniform,
float value = 0.0)
{
Random rnd (seed);
ostringstream oss;
oss.precision(4);
oss.width(7);
oss << "#version 310 es\n";
oss << "precision highp float;\n";
oss << "out vec4 fragColor;\n";
printFloatDeclaration(oss, varName, uniform, value);
for (vector<VariableDeclaration>::const_iterator it = inputs.begin();
it != inputs.end(); ++it)
oss << *it << ";\n";
// glsl % isn't defined for negative numbers
oss << "int imod (int n, int d)" << "\n"
<< "{" << "\n"
<< "\t" << "return (n < 0 ? d - 1 - (-1 - n) % d : n % d);" << "\n"
<< "}" << "\n";
oss << "vec4 hsv (vec3 hsv)"
<< "{" << "\n"
<< "\tfloat h = hsv.x * 3.0;\n"
<< "\tfloat r = max(0.0, 1.0 - h) + max(0.0, h - 2.0);\n"
<< "\tfloat g = max(0.0, 1.0 - abs(h - 1.0));\n"
<< "\tfloat b = max(0.0, 1.0 - abs(h - 2.0));\n"
<< "\tvec3 hs = mix(vec3(1.0), vec3(r, g, b), hsv.y);\n"
<< "\treturn vec4(hsv.z * hs, 1.0);\n"
<< "}\n";
oss << "void main (void)\n"
<< "{\n";
oss << "\t" << "fragColor = vec4(vec3(" << varName << "), 1.0);" << "\n";
if (inputs.size() > 0)
{
oss << "\t"
<< "switch (imod(int(0.5 * (";
printFloat(oss, rnd.getFloat(0.5f, 2.0f));
oss << " * gl_FragCoord.x - ";
printFloat(oss, rnd.getFloat(0.5f, 2.0f));
oss << " * gl_FragCoord.y)), "
<< inputs.size() << "))" << "\n"
<< "\t" << "{" << "\n";
for (size_t i = 0; i < inputs.size(); ++i)
{
oss << "\t\t" << "case " << i << ":" << "\n"
<< "\t\t\t" << "fragColor *= ";
printInputColor(oss, inputs[i]);
oss << ";" << "\n"
<< "\t\t\t" << "break;" << "\n";
}
oss << "\t\t" << "case " << inputs.size() << ":\n"
<< "\t\t\t" << "fragColor = vec4(1.0, 0.0, 1.0, 1.0);" << "\n";
oss << "\t\t\t" << "break;" << "\n";
oss << "\t\t" << "case -1:\n"
<< "\t\t\t" << "fragColor = vec4(1.0, 1.0, 0.0, 1.0);" << "\n";
oss << "\t\t\t" << "break;" << "\n";
oss << "\t\t" << "default:" << "\n"
<< "\t\t\t" << "fragColor = vec4(1.0, 1.0, 0.0, 1.0);" << "\n";
oss << "\t" << "}\n";
}
oss << "}\n";
return oss.str();
}
// ProgramWrapper
class ProgramWrapper
{
public:
virtual ~ProgramWrapper (void) {}
virtual GLuint getProgramName (void) = 0;
virtual void writeToLog (TestLog& log) = 0;
};
class ShaderProgramWrapper : public ProgramWrapper
{
public:
ShaderProgramWrapper (const RenderContext& renderCtx,
const ProgramSources& sources)
: m_shaderProgram (renderCtx, sources) {}
~ShaderProgramWrapper (void) {}
GLuint getProgramName (void) { return m_shaderProgram.getProgram(); }
ShaderProgram& getShaderProgram (void) { return m_shaderProgram; }
void writeToLog (TestLog& log) { log << m_shaderProgram; }
private:
ShaderProgram m_shaderProgram;
};
class RawProgramWrapper : public ProgramWrapper
{
public:
RawProgramWrapper (const RenderContext& renderCtx,
GLuint programName,
ShaderType shaderType,
const string& source)
: m_program (renderCtx, programName)
, m_shaderType (shaderType)
, m_source (source) {}
~RawProgramWrapper (void) {}
GLuint getProgramName (void) { return m_program.getProgram(); }
Program& getProgram (void) { return m_program; }
void writeToLog (TestLog& log);
private:
Program m_program;
ShaderType m_shaderType;
const string m_source;
};
void RawProgramWrapper::writeToLog (TestLog& log)
{
const string info = m_program.getInfoLog();
qpShaderType qpType = glu::getLogShaderType(m_shaderType);
log << TestLog::ShaderProgram(true, info)
<< TestLog::Shader(qpType, m_source,
true, "[Shader created by glCreateShaderProgramv()]")
<< TestLog::EndShaderProgram;
}
// ProgramParams
struct ProgramParams
{
ProgramParams (deUint32 vtxSeed_, GLfloat vtxScale_, deUint32 frgSeed_, GLfloat frgScale_)
: vtxSeed (vtxSeed_)
, vtxScale (vtxScale_)
, frgSeed (frgSeed_)
, frgScale (frgScale_) {}
deUint32 vtxSeed;
GLfloat vtxScale;
deUint32 frgSeed;
GLfloat frgScale;
};
ProgramParams genProgramParams (Random& rnd)
{
const deUint32 vtxSeed = rnd.getUint32();
const GLfloat vtxScale = rnd.getInt(8, 16) / 16.0f;
const deUint32 frgSeed = rnd.getUint32();
const GLfloat frgScale = rnd.getInt(0, 16) / 16.0f;
return ProgramParams(vtxSeed, vtxScale, frgSeed, frgScale);
}
// TestParams
struct TestParams
{
bool initSingle;
bool switchVtx;
bool switchFrg;
bool useUniform;
bool useSameName;
bool useCreateHelper;
bool useProgramUniform;
VaryingParams varyings;
};
deUint32 paramsSeed (const TestParams& params)
{
deUint32 paramCode = (params.initSingle << 0 |
params.switchVtx << 1 |
params.switchFrg << 2 |
params.useUniform << 3 |
params.useSameName << 4 |
params.useCreateHelper << 5 |
params.useProgramUniform << 6);
paramCode = deUint32Hash(paramCode) + params.varyings.count;
paramCode = deUint32Hash(paramCode) + params.varyings.type;
paramCode = deUint32Hash(paramCode) + params.varyings.binding;
paramCode = deUint32Hash(paramCode) + params.varyings.vtxInterp;
paramCode = deUint32Hash(paramCode) + params.varyings.frgInterp;
return deUint32Hash(paramCode);
}
string paramsCode (const TestParams& params)
{
using namespace glu;
ostringstream oss;
oss << (params.initSingle ? "1" : "2")
<< (params.switchVtx ? "v" : "")
<< (params.switchFrg ? "f" : "")
<< (params.useProgramUniform ? "p" : "")
<< (params.useUniform ? "u" : "")
<< (params.useSameName ? "s" : "")
<< (params.useCreateHelper ? "c" : "")
<< de::toString(params.varyings.count)
<< (params.varyings.binding == BINDING_NAME ? "n" :
params.varyings.binding == BINDING_LOCATION ? "l" :
params.varyings.binding == BINDING_LAST ? "r" :
"")
<< (params.varyings.vtxInterp == VARYINGINTERPOLATION_SMOOTH ? "m" :
params.varyings.vtxInterp == VARYINGINTERPOLATION_CENTROID ? "e" :
params.varyings.vtxInterp == VARYINGINTERPOLATION_FLAT ? "a" :
params.varyings.vtxInterp == VARYINGINTERPOLATION_RANDOM ? "r" :
"o")
<< (params.varyings.frgInterp == VARYINGINTERPOLATION_SMOOTH ? "m" :
params.varyings.frgInterp == VARYINGINTERPOLATION_CENTROID ? "e" :
params.varyings.frgInterp == VARYINGINTERPOLATION_FLAT ? "a" :
params.varyings.frgInterp == VARYINGINTERPOLATION_RANDOM ? "r" :
"o");
return oss.str();
}
bool paramsValid (const TestParams& params)
{
using namespace glu;
// Final pipeline has a single program?
if (params.initSingle)
{
// Cannot have conflicting names for uniforms or constants
if (params.useSameName)
return false;
// CreateShaderProgram would never get called
if (!params.switchVtx && !params.switchFrg && params.useCreateHelper)
return false;
// Must switch either all or nothing
if (params.switchVtx != params.switchFrg)
return false;
}
// ProgramUniform would never get called
if (params.useProgramUniform && !params.useUniform)
return false;
// Interpolation is meaningless if we don't use an in/out variable.
if (params.varyings.count == 0 &&
!(params.varyings.vtxInterp == VARYINGINTERPOLATION_LAST &&
params.varyings.frgInterp == VARYINGINTERPOLATION_LAST))
return false;
// Mismatch by flat / smooth is not allowed. See Khronos bug #12630
// \note: iterpolations might be RANDOM, causing generated varyings potentially match / mismatch anyway.
// This is checked later on. Here, we just make sure that we don't force the generator to generate
// only invalid varying configurations, i.e. there exists a valid varying configuration for this
// test param config.
if ((params.varyings.vtxInterp != VARYINGINTERPOLATION_RANDOM) &&
(params.varyings.frgInterp != VARYINGINTERPOLATION_RANDOM) &&
(params.varyings.vtxInterp == VARYINGINTERPOLATION_FLAT) != (params.varyings.frgInterp == VARYINGINTERPOLATION_FLAT))
return false;
return true;
}
// used only for debug sanity checks
#if defined(DE_DEBUG)
bool varyingsValid (const VaryingInterface& varyings)
{
for (int ndx = 0; ndx < (int)varyings.vtxOutputs.size(); ++ndx)
{
const VaryingInterpolation vertexInterpolation = getVaryingInterpolation(varyings.vtxOutputs[ndx].interpolation);
const VaryingInterpolation fragmentInterpolation = getVaryingInterpolation(varyings.frgInputs[ndx].interpolation);
if (!isSSOCompatibleInterpolation(vertexInterpolation, fragmentInterpolation))
return false;
}
return true;
}
#endif
void logParams (TestLog& log, const TestParams& params)
{
// We don't log operational details here since those are shown
// in the log messages during execution.
MessageBuilder msg = log.message();
msg << "Pipeline configuration:\n";
msg << "Vertex and fragment shaders have "
<< (params.useUniform ? "uniform" : "constant") << "s with "
<< (params.useSameName ? "the same name" : "different names") << ".\n";
if (params.varyings.count == 0)
msg << "There are no varyings.\n";
else
{
if (params.varyings.count == 1)
msg << "There is one varying.\n";
else
msg << "There are " << params.varyings.count << " varyings.\n";
if (params.varyings.type == glu::TYPE_LAST)
msg << "Varyings are of random types.\n";
else
msg << "Varyings are of type '"
<< glu::getDataTypeName(params.varyings.type) << "'.\n";
msg << "Varying outputs and inputs correspond ";
switch (params.varyings.binding)
{
case BINDING_NAME:
msg << "by name.\n";
break;
case BINDING_LOCATION:
msg << "by location.\n";
break;
case BINDING_LAST:
msg << "randomly either by name or by location.\n";
break;
default:
DE_ASSERT(!"Impossible");
}
msg << "In the vertex shader the varyings are qualified ";
if (params.varyings.vtxInterp == VARYINGINTERPOLATION_DEFAULT)
msg << "with no interpolation qualifiers.\n";
else if (params.varyings.vtxInterp == VARYINGINTERPOLATION_RANDOM)
msg << "with a random interpolation qualifier.\n";
else
msg << "'" << glu::getInterpolationName(getGluInterpolation(params.varyings.vtxInterp)) << "'.\n";
msg << "In the fragment shader the varyings are qualified ";
if (params.varyings.frgInterp == VARYINGINTERPOLATION_DEFAULT)
msg << "with no interpolation qualifiers.\n";
else if (params.varyings.frgInterp == VARYINGINTERPOLATION_RANDOM)
msg << "with a random interpolation qualifier.\n";
else
msg << "'" << glu::getInterpolationName(getGluInterpolation(params.varyings.frgInterp)) << "'.\n";
}
msg << TestLog::EndMessage;
log.writeMessage("");
}
TestParams genParams (deUint32 seed)
{
Random rnd (seed);
TestParams params;
int tryNdx = 0;
do
{
params.initSingle = rnd.getBool();
params.switchVtx = rnd.getBool();
params.switchFrg = rnd.getBool();
params.useUniform = rnd.getBool();
params.useProgramUniform = params.useUniform && rnd.getBool();
params.useCreateHelper = rnd.getBool();
params.useSameName = rnd.getBool();
{
int i = rnd.getInt(-1, 3);
params.varyings.count = (i == -1 ? 0 : 1 << i);
}
if (params.varyings.count > 0)
{
params.varyings.type = glu::TYPE_LAST;
params.varyings.binding = BINDING_LAST;
params.varyings.vtxInterp = VARYINGINTERPOLATION_RANDOM;
params.varyings.frgInterp = VARYINGINTERPOLATION_RANDOM;
}
else
{
params.varyings.type = glu::TYPE_INVALID;
params.varyings.binding = BINDING_LAST;
params.varyings.vtxInterp = VARYINGINTERPOLATION_LAST;
params.varyings.frgInterp = VARYINGINTERPOLATION_LAST;
}
tryNdx += 1;
} while (!paramsValid(params) && tryNdx < 16);
DE_ASSERT(paramsValid(params));
return params;
}
// Program pipeline wrapper that retains references to component programs.
struct Pipeline
{
Pipeline (MovePtr<ProgramPipeline> pipeline_,
MovePtr<ProgramWrapper> fullProg_,
MovePtr<ProgramWrapper> vtxProg_,
MovePtr<ProgramWrapper> frgProg_)
: pipeline (pipeline_)
, fullProg (fullProg_)
, vtxProg (vtxProg_)
, frgProg (frgProg_) {}
ProgramWrapper& getVertexProgram (void) const
{
return vtxProg ? *vtxProg : *fullProg;
}
ProgramWrapper& getFragmentProgram (void) const
{
return frgProg ? *frgProg : *fullProg;
}
UniquePtr<ProgramPipeline> pipeline;
UniquePtr<ProgramWrapper> fullProg;
UniquePtr<ProgramWrapper> vtxProg;
UniquePtr<ProgramWrapper> frgProg;
};
void logPipeline(TestLog& log, const Pipeline& pipeline)
{
ProgramWrapper& vtxProg = pipeline.getVertexProgram();
ProgramWrapper& frgProg = pipeline.getFragmentProgram();
log.writeMessage("// Failed program pipeline:");
if (&vtxProg == &frgProg)
{
log.writeMessage("// Common program for both vertex and fragment stages:");
vtxProg.writeToLog(log);
}
else
{
log.writeMessage("// Vertex stage program:");
vtxProg.writeToLog(log);
log.writeMessage("// Fragment stage program:");
frgProg.writeToLog(log);
}
}
// Rectangle
struct Rectangle
{
Rectangle (int x_, int y_, int width_, int height_)
: x (x_)
, y (y_)
, width (width_)
, height (height_) {}
int x;
int y;
int width;
int height;
};
void setViewport (const RenderContext& renderCtx, const Rectangle& rect)
{
renderCtx.getFunctions().viewport(rect.x, rect.y, rect.width, rect.height);
}
void readRectangle (const RenderContext& renderCtx, const Rectangle& rect, Surface& dst)
{
dst.setSize(rect.width, rect.height);
glu::readPixels(renderCtx, rect.x, rect.y, dst.getAccess());
}
Rectangle randomViewport (const RenderContext& ctx, Random& rnd,
GLint maxWidth, GLint maxHeight)
{
const RenderTarget& target = ctx.getRenderTarget();
GLint width = de::min(target.getWidth(), maxWidth);
GLint xOff = rnd.getInt(0, target.getWidth() - width);
GLint height = de::min(target.getHeight(), maxHeight);
GLint yOff = rnd.getInt(0, target.getHeight() - height);
return Rectangle(xOff, yOff, width, height);
}
// SeparateShaderTest
class SeparateShaderTest : public TestCase, private CallLogWrapper
{
public:
typedef void (SeparateShaderTest::*TestFunc)
(MovePtr<Pipeline>& pipeOut);
SeparateShaderTest (Context& ctx,
const string& name,
const string& description,
int iterations,
const TestParams& params,
TestFunc testFunc);
IterateResult iterate (void);
void testPipelineRendering (MovePtr<Pipeline>& pipeOut);
void testCurrentProgPriority (MovePtr<Pipeline>& pipeOut);
void testActiveProgramUniform (MovePtr<Pipeline>& pipeOut);
void testPipelineQueryActive (MovePtr<Pipeline>& pipeOut);
void testPipelineQueryPrograms (MovePtr<Pipeline>& pipeOut);
private:
TestLog& log (void);
const RenderContext& getRenderContext (void);
void setUniform (ProgramWrapper& program,
const string& uniformName,
GLfloat value,
bool useProgramUni);
void drawSurface (Surface& dst,
deUint32 seed = 0);
MovePtr<ProgramWrapper> createShaderProgram (const string* vtxSource,
const string* frgSource,
bool separable);
MovePtr<ProgramWrapper> createSingleShaderProgram (ShaderType shaderType,
const string& src);
MovePtr<Pipeline> createPipeline (const ProgramParams& pp);
MovePtr<ProgramWrapper> createReferenceProgram (const ProgramParams& pp);
int m_iterations;
int m_currentIteration;
TestParams m_params;
TestFunc m_testFunc;
Random m_rnd;
ResultCollector m_status;
VaryingInterface m_varyings;
// Per-iteration state required for logging on exception
MovePtr<ProgramWrapper> m_fullProg;
MovePtr<ProgramWrapper> m_vtxProg;
MovePtr<ProgramWrapper> m_frgProg;
};
const RenderContext& SeparateShaderTest::getRenderContext (void)
{
return m_context.getRenderContext();
}
TestLog& SeparateShaderTest::log (void)
{
return m_testCtx.getLog();
}
SeparateShaderTest::SeparateShaderTest (Context& ctx,
const string& name,
const string& description,
int iterations,
const TestParams& params,
TestFunc testFunc)
: TestCase (ctx, name.c_str(), description.c_str())
, CallLogWrapper (ctx.getRenderContext().getFunctions(), log())
, m_iterations (iterations)
, m_currentIteration(0)
, m_params (params)
, m_testFunc (testFunc)
, m_rnd (paramsSeed(params))
, m_status (log(), "// ")
, m_varyings (genVaryingInterface(params.varyings, m_rnd))
{
DE_ASSERT(paramsValid(params));
DE_ASSERT(varyingsValid(m_varyings));
}
MovePtr<ProgramWrapper> SeparateShaderTest::createShaderProgram (const string* vtxSource,
const string* frgSource,
bool separable)
{
ProgramSources sources;
if (vtxSource != DE_NULL)
sources << VertexSource(*vtxSource);
if (frgSource != DE_NULL)
sources << FragmentSource(*frgSource);
sources << ProgramSeparable(separable);
MovePtr<ShaderProgramWrapper> wrapper (new ShaderProgramWrapper(getRenderContext(),
sources));
if (!wrapper->getShaderProgram().isOk())
{
log().writeMessage("Couldn't create shader program");
wrapper->writeToLog(log());
TCU_FAIL("Couldn't create shader program");
}
return MovePtr<ProgramWrapper>(wrapper.release());
}
MovePtr<ProgramWrapper> SeparateShaderTest::createSingleShaderProgram (ShaderType shaderType,
const string& src)
{
const RenderContext& renderCtx = getRenderContext();
if (m_params.useCreateHelper)
{
const char* const srcStr = src.c_str();
const GLenum glType = glu::getGLShaderType(shaderType);
const GLuint programName = glCreateShaderProgramv(glType, 1, &srcStr);
if (glGetError() != GL_NO_ERROR || programName == 0)
{
qpShaderType qpType = glu::getLogShaderType(shaderType);
log() << TestLog::Message << "glCreateShaderProgramv() failed"
<< TestLog::EndMessage
<< TestLog::ShaderProgram(false, "[glCreateShaderProgramv() failed]")
<< TestLog::Shader(qpType, src,
false, "[glCreateShaderProgramv() failed]")
<< TestLog::EndShaderProgram;
TCU_FAIL("glCreateShaderProgramv() failed");
}
RawProgramWrapper* const wrapper = new RawProgramWrapper(renderCtx, programName,
shaderType, src);
MovePtr<ProgramWrapper> wrapperPtr(wrapper);
Program& program = wrapper->getProgram();
if (!program.getLinkStatus())
{
log().writeMessage("glCreateShaderProgramv() failed at linking");
wrapper->writeToLog(log());
TCU_FAIL("glCreateShaderProgram() failed at linking");
}
return wrapperPtr;
}
else
{
switch (shaderType)
{
case glu::SHADERTYPE_VERTEX:
return createShaderProgram(&src, DE_NULL, true);
case glu::SHADERTYPE_FRAGMENT:
return createShaderProgram(DE_NULL, &src, true);
default:
DE_ASSERT(!"Impossible case");
}
}
return MovePtr<ProgramWrapper>(); // Shut up compiler warnings.
}
void SeparateShaderTest::setUniform (ProgramWrapper& program,
const string& uniformName,
GLfloat value,
bool useProgramUniform)
{
const GLuint progName = program.getProgramName();
const GLint location = glGetUniformLocation(progName, uniformName.c_str());
MessageBuilder msg = log().message();
msg << "// Set program " << progName << "'s uniform '" << uniformName << "' to " << value;
if (useProgramUniform)
{
msg << " using glProgramUniform1f";
glProgramUniform1f(progName, location, value);
}
else
{
msg << " using glUseProgram and glUniform1f";
glUseProgram(progName);
glUniform1f(location, value);
glUseProgram(0);
}
msg << TestLog::EndMessage;
}
MovePtr<Pipeline> SeparateShaderTest::createPipeline (const ProgramParams& pp)
{
const bool useUniform = m_params.useUniform;
const string vtxName = m_params.useSameName ? "scale" : "vtxScale";
const deUint32 initVtxSeed = m_params.switchVtx ? m_rnd.getUint32() : pp.vtxSeed;
const string frgName = m_params.useSameName ? "scale" : "frgScale";
const deUint32 initFrgSeed = m_params.switchFrg ? m_rnd.getUint32() : pp.frgSeed;
const string frgSource = genFrgShaderSrc(initFrgSeed, m_varyings.frgInputs,
frgName, useUniform, pp.frgScale);
const RenderContext& renderCtx = getRenderContext();
MovePtr<ProgramPipeline> pipeline (new ProgramPipeline(renderCtx));
MovePtr<ProgramWrapper> fullProg;
MovePtr<ProgramWrapper> vtxProg;
MovePtr<ProgramWrapper> frgProg;
// We cannot allow a situation where we have a single program with a
// single uniform, because then the vertex and fragment shader uniforms
// would not be distinct in the final pipeline, and we are going to test
// that altering one uniform will not affect the other.
DE_ASSERT(!(m_params.initSingle && m_params.useSameName &&
!m_params.switchVtx && !m_params.switchFrg));
if (m_params.initSingle)
{
string vtxSource = genVtxShaderSrc(initVtxSeed,
varyingCompatVtxOutputs(m_varyings),
vtxName, useUniform, pp.vtxScale);
fullProg = createShaderProgram(&vtxSource, &frgSource, true);
pipeline->useProgramStages(GL_VERTEX_SHADER_BIT | GL_FRAGMENT_SHADER_BIT,
fullProg->getProgramName());
log() << TestLog::Message
<< "// Created pipeline " << pipeline->getPipeline()
<< " with two-shader program " << fullProg->getProgramName()
<< TestLog::EndMessage;
}
else
{
string vtxSource = genVtxShaderSrc(initVtxSeed, m_varyings.vtxOutputs,
vtxName, useUniform, pp.vtxScale);
vtxProg = createSingleShaderProgram(glu::SHADERTYPE_VERTEX, vtxSource);
pipeline->useProgramStages(GL_VERTEX_SHADER_BIT, vtxProg->getProgramName());
frgProg = createSingleShaderProgram(glu::SHADERTYPE_FRAGMENT, frgSource);
pipeline->useProgramStages(GL_FRAGMENT_SHADER_BIT, frgProg->getProgramName());
log() << TestLog::Message
<< "// Created pipeline " << pipeline->getPipeline()
<< " with vertex program " << vtxProg->getProgramName()
<< " and fragment program " << frgProg->getProgramName()
<< TestLog::EndMessage;
}
m_status.check(pipeline->isValid(),
"Pipeline is invalid after initialization");
if (m_params.switchVtx)
{
string newSource = genVtxShaderSrc(pp.vtxSeed, m_varyings.vtxOutputs,
vtxName, useUniform, pp.vtxScale);
vtxProg = createSingleShaderProgram(glu::SHADERTYPE_VERTEX, newSource);
pipeline->useProgramStages(GL_VERTEX_SHADER_BIT, vtxProg->getProgramName());
log() << TestLog::Message
<< "// Switched pipeline " << pipeline->getPipeline()
<< "'s vertex stage to single-shader program " << vtxProg->getProgramName()
<< TestLog::EndMessage;
}
if (m_params.switchFrg)
{
string newSource = genFrgShaderSrc(pp.frgSeed, m_varyings.frgInputs,
frgName, useUniform, pp.frgScale);
frgProg = createSingleShaderProgram(glu::SHADERTYPE_FRAGMENT, newSource);
pipeline->useProgramStages(GL_FRAGMENT_SHADER_BIT, frgProg->getProgramName());
log() << TestLog::Message
<< "// Switched pipeline " << pipeline->getPipeline()
<< "'s fragment stage to single-shader program " << frgProg->getProgramName()
<< TestLog::EndMessage;
}
if (m_params.switchVtx || m_params.switchFrg)
m_status.check(pipeline->isValid(),
"Pipeline became invalid after changing a stage's program");
if (m_params.useUniform)
{
ProgramWrapper& vtxStage = *(vtxProg ? vtxProg : fullProg);
ProgramWrapper& frgStage = *(frgProg ? frgProg : fullProg);
setUniform(vtxStage, vtxName, pp.vtxScale, m_params.useProgramUniform);
setUniform(frgStage, frgName, pp.frgScale, m_params.useProgramUniform);
}
else
log().writeMessage("// Programs use constants instead of uniforms");
return MovePtr<Pipeline>(new Pipeline(pipeline, fullProg, vtxProg, frgProg));
}
MovePtr<ProgramWrapper> SeparateShaderTest::createReferenceProgram (const ProgramParams& pp)
{
bool useUniform = m_params.useUniform;
const string vtxSrc = genVtxShaderSrc(pp.vtxSeed,
varyingCompatVtxOutputs(m_varyings),
"vtxScale", useUniform, pp.vtxScale);
const string frgSrc = genFrgShaderSrc(pp.frgSeed, m_varyings.frgInputs,
"frgScale", useUniform, pp.frgScale);
MovePtr<ProgramWrapper> program = createShaderProgram(&vtxSrc, &frgSrc, false);
GLuint progName = program->getProgramName();
log() << TestLog::Message
<< "// Created monolithic shader program " << progName
<< TestLog::EndMessage;
if (useUniform)
{
setUniform(*program, "vtxScale", pp.vtxScale, false);
setUniform(*program, "frgScale", pp.frgScale, false);
}
return program;
}
void SeparateShaderTest::drawSurface (Surface& dst, deUint32 seed)
{
const RenderContext& renderCtx = getRenderContext();
Random rnd (seed > 0 ? seed : m_rnd.getUint32());
Rectangle viewport = randomViewport(renderCtx, rnd,
VIEWPORT_SIZE, VIEWPORT_SIZE);
glClearColor(0.125f, 0.25f, 0.5f, 1.f);
setViewport(renderCtx, viewport);
glClear(GL_COLOR_BUFFER_BIT);
GLU_CHECK_CALL(glDrawArrays(GL_TRIANGLES, 0, 3));
readRectangle(renderCtx, viewport, dst);
log().writeMessage("// Drew a triangle");
}
void SeparateShaderTest::testPipelineRendering (MovePtr<Pipeline>& pipeOut)
{
ProgramParams pp = genProgramParams(m_rnd);
Pipeline& pipeline = *(pipeOut = createPipeline(pp));
GLuint pipeName = pipeline.pipeline->getPipeline();
UniquePtr<ProgramWrapper> refProgram (createReferenceProgram(pp));
GLuint refProgName = refProgram->getProgramName();
Surface refSurface;
Surface pipelineSurface;
GLuint drawSeed = m_rnd.getUint32();
glUseProgram(refProgName);
log() << TestLog::Message << "// Use program " << refProgName << TestLog::EndMessage;
drawSurface(refSurface, drawSeed);
glUseProgram(0);
glBindProgramPipeline(pipeName);
log() << TestLog::Message << "// Bind pipeline " << pipeName << TestLog::EndMessage;
drawSurface(pipelineSurface, drawSeed);
glBindProgramPipeline(0);
{
const bool result = tcu::fuzzyCompare(
m_testCtx.getLog(), "Program pipeline result",
"Result of comparing a program pipeline with a monolithic program",
refSurface, pipelineSurface, 0.05f, tcu::COMPARE_LOG_RESULT);
m_status.check(result, "Pipeline rendering differs from equivalent monolithic program");
}
}
void SeparateShaderTest::testCurrentProgPriority (MovePtr<Pipeline>& pipeOut)
{
ProgramParams pipePp = genProgramParams(m_rnd);
ProgramParams programPp = genProgramParams(m_rnd);
Pipeline& pipeline = *(pipeOut = createPipeline(pipePp));
GLuint pipeName = pipeline.pipeline->getPipeline();
UniquePtr<ProgramWrapper> program (createReferenceProgram(programPp));
Surface pipelineSurface;
Surface refSurface;
Surface resultSurface;
deUint32 drawSeed = m_rnd.getUint32();
LOG_CALL(glBindProgramPipeline(pipeName));
drawSurface(pipelineSurface, drawSeed);
LOG_CALL(glBindProgramPipeline(0));
LOG_CALL(glUseProgram(program->getProgramName()));
drawSurface(refSurface, drawSeed);
LOG_CALL(glUseProgram(0));
LOG_CALL(glUseProgram(program->getProgramName()));
LOG_CALL(glBindProgramPipeline(pipeName));
drawSurface(resultSurface, drawSeed);
LOG_CALL(glBindProgramPipeline(0));
LOG_CALL(glUseProgram(0));
bool result = tcu::pixelThresholdCompare(
m_testCtx.getLog(), "Active program rendering result",
"Active program rendering result",
refSurface, resultSurface, tcu::RGBA(0, 0, 0, 0), tcu::COMPARE_LOG_RESULT);
m_status.check(result, "glBindProgramPipeline() affects glUseProgram()");
if (!result)
log() << TestLog::Image("Pipeline image", "Image produced by pipeline",
pipelineSurface);
}
void SeparateShaderTest::testActiveProgramUniform (MovePtr<Pipeline>& pipeOut)
{
ProgramParams refPp = genProgramParams(m_rnd);
Surface refSurface;
Surface resultSurface;
deUint32 drawSeed = m_rnd.getUint32();
DE_UNREF(pipeOut);
{
UniquePtr<ProgramWrapper> refProg (createReferenceProgram(refPp));
GLuint refProgName = refProg->getProgramName();
glUseProgram(refProgName);
log() << TestLog::Message << "// Use reference program " << refProgName
<< TestLog::EndMessage;
drawSurface(refSurface, drawSeed);
glUseProgram(0);
}
{
ProgramParams changePp = genProgramParams(m_rnd);
changePp.vtxSeed = refPp.vtxSeed;
changePp.frgSeed = refPp.frgSeed;
UniquePtr<ProgramWrapper> changeProg (createReferenceProgram(changePp));
GLuint changeName = changeProg->getProgramName();
ProgramPipeline pipeline (getRenderContext());
GLint vtxLoc = glGetUniformLocation(changeName, "vtxScale");
GLint frgLoc = glGetUniformLocation(changeName, "frgScale");
LOG_CALL(glBindProgramPipeline(pipeline.getPipeline()));
pipeline.activeShaderProgram(changeName);
log() << TestLog::Message << "// Set active shader program to " << changeName
<< TestLog::EndMessage;
glUniform1f(vtxLoc, refPp.vtxScale);
log() << TestLog::Message
<< "// Set uniform 'vtxScale' to " << refPp.vtxScale << " using glUniform1f"
<< TestLog::EndMessage;
glUniform1f(frgLoc, refPp.frgScale);
log() << TestLog::Message
<< "// Set uniform 'frgScale' to " << refPp.frgScale << " using glUniform1f"
<< TestLog::EndMessage;
pipeline.activeShaderProgram(0);
LOG_CALL(glBindProgramPipeline(0));
LOG_CALL(glUseProgram(changeName));
drawSurface(resultSurface, drawSeed);
LOG_CALL(glUseProgram(0));
}
bool result = tcu::fuzzyCompare(
m_testCtx.getLog(), "Active program uniform result",
"Active program uniform result",
refSurface, resultSurface, 0.05f, tcu::COMPARE_LOG_RESULT);
m_status.check(result,
"glUniform() did not correctly modify "
"the active program of the bound pipeline");
}
void SeparateShaderTest::testPipelineQueryPrograms (MovePtr<Pipeline>& pipeOut)
{
ProgramParams pipePp = genProgramParams(m_rnd);
Pipeline& pipeline = *(pipeOut = createPipeline(pipePp));
GLuint pipeName = pipeline.pipeline->getPipeline();
GLint queryVtx = 0;
GLint queryFrg = 0;
LOG_CALL(GLU_CHECK_CALL(glGetProgramPipelineiv(pipeName, GL_VERTEX_SHADER, &queryVtx)));
m_status.check(GLuint(queryVtx) == pipeline.getVertexProgram().getProgramName(),
"Program pipeline query reported wrong vertex shader program");
LOG_CALL(GLU_CHECK_CALL(glGetProgramPipelineiv(pipeName, GL_FRAGMENT_SHADER, &queryFrg)));
m_status.check(GLuint(queryFrg) == pipeline.getFragmentProgram().getProgramName(),
"Program pipeline query reported wrong fragment shader program");
}
void SeparateShaderTest::testPipelineQueryActive (MovePtr<Pipeline>& pipeOut)
{
ProgramParams pipePp = genProgramParams(m_rnd);
Pipeline& pipeline = *(pipeOut = createPipeline(pipePp));
GLuint pipeName = pipeline.pipeline->getPipeline();
GLuint newActive = pipeline.getVertexProgram().getProgramName();
GLint queryActive = 0;
LOG_CALL(GLU_CHECK_CALL(glGetProgramPipelineiv(pipeName, GL_ACTIVE_PROGRAM, &queryActive)));
m_status.check(queryActive == 0,
"Program pipeline query reported non-zero initial active program");
pipeline.pipeline->activeShaderProgram(newActive);
log() << TestLog::Message
<< "Set pipeline " << pipeName << "'s active shader program to " << newActive
<< TestLog::EndMessage;
LOG_CALL(GLU_CHECK_CALL(glGetProgramPipelineiv(pipeName, GL_ACTIVE_PROGRAM, &queryActive)));
m_status.check(GLuint(queryActive) == newActive,
"Program pipeline query reported incorrect active program");
pipeline.pipeline->activeShaderProgram(0);
}
TestCase::IterateResult SeparateShaderTest::iterate (void)
{
MovePtr<Pipeline> pipeline;
DE_ASSERT(m_iterations > 0);
if (m_currentIteration == 0)
logParams(log(), m_params);
++m_currentIteration;
try
{
(this->*m_testFunc)(pipeline);
log().writeMessage("");
}
catch (const tcu::Exception&)
{
if (pipeline)
logPipeline(log(), *pipeline);
throw;
}
if (m_status.getResult() != QP_TEST_RESULT_PASS)
{
if (pipeline)
logPipeline(log(), *pipeline);
}
else if (m_currentIteration < m_iterations)
return CONTINUE;
m_status.setTestContextResult(m_testCtx);
return STOP;
}
// Group construction utilities
enum ParamFlags
{
PARAMFLAGS_SWITCH_FRAGMENT = 1 << 0,
PARAMFLAGS_SWITCH_VERTEX = 1 << 1,
PARAMFLAGS_INIT_SINGLE = 1 << 2,
PARAMFLAGS_LAST = 1 << 3,
PARAMFLAGS_MASK = PARAMFLAGS_LAST - 1
};
bool areCaseParamFlagsValid (ParamFlags flags)
{
const ParamFlags switchAll = ParamFlags(PARAMFLAGS_SWITCH_VERTEX|PARAMFLAGS_SWITCH_FRAGMENT);
if ((flags & PARAMFLAGS_INIT_SINGLE) != 0)
return (flags & switchAll) == 0 ||
(flags & switchAll) == switchAll;
else
return true;
}
bool addRenderTest (TestCaseGroup& group, const string& namePrefix, const string& descPrefix,
int numIterations, ParamFlags flags, TestParams params)
{
ostringstream name;
ostringstream desc;
DE_ASSERT(areCaseParamFlagsValid(flags));
name << namePrefix;
desc << descPrefix;
params.initSingle = (flags & PARAMFLAGS_INIT_SINGLE) != 0;
params.switchVtx = (flags & PARAMFLAGS_SWITCH_VERTEX) != 0;
params.switchFrg = (flags & PARAMFLAGS_SWITCH_FRAGMENT) != 0;
name << (flags & PARAMFLAGS_INIT_SINGLE ? "single_program" : "separate_programs");
desc << (flags & PARAMFLAGS_INIT_SINGLE
? "Single program with two shaders"
: "Separate programs for each shader");
switch (flags & (PARAMFLAGS_SWITCH_FRAGMENT | PARAMFLAGS_SWITCH_VERTEX))
{
case 0:
break;
case PARAMFLAGS_SWITCH_FRAGMENT:
name << "_add_fragment";
desc << ", then add a fragment program";
break;
case PARAMFLAGS_SWITCH_VERTEX:
name << "_add_vertex";
desc << ", then add a vertex program";
break;
case PARAMFLAGS_SWITCH_FRAGMENT | PARAMFLAGS_SWITCH_VERTEX:
name << "_add_both";
desc << ", then add both vertex and shader programs";
break;
}
if (!paramsValid(params))
return false;
group.addChild(new SeparateShaderTest(group.getContext(), name.str(), desc.str(),
numIterations, params,
&SeparateShaderTest::testPipelineRendering));
return true;
}
void describeInterpolation (const string& stage, VaryingInterpolation qual,
ostringstream& name, ostringstream& desc)
{
DE_ASSERT(qual < VARYINGINTERPOLATION_RANDOM);
if (qual == VARYINGINTERPOLATION_DEFAULT)
{
desc << ", unqualified in " << stage << " shader";
return;
}
else
{
const string qualName = glu::getInterpolationName(getGluInterpolation(qual));
name << "_" << stage << "_" << qualName;
desc << ", qualified '" << qualName << "' in " << stage << " shader";
}
}
} // anonymous
TestCaseGroup* createSeparateShaderTests (Context& ctx)
{
TestParams defaultParams;
int numIterations = 4;
TestCaseGroup* group =
new TestCaseGroup(ctx, "separate_shader", "Separate shader tests");
defaultParams.useUniform = false;
defaultParams.initSingle = false;
defaultParams.switchVtx = false;
defaultParams.switchFrg = false;
defaultParams.useCreateHelper = false;
defaultParams.useProgramUniform = false;
defaultParams.useSameName = false;
defaultParams.varyings.count = 0;
defaultParams.varyings.type = glu::TYPE_INVALID;
defaultParams.varyings.binding = BINDING_NAME;
defaultParams.varyings.vtxInterp = VARYINGINTERPOLATION_LAST;
defaultParams.varyings.frgInterp = VARYINGINTERPOLATION_LAST;
TestCaseGroup* stagesGroup =
new TestCaseGroup(ctx, "pipeline", "Pipeline configuration tests");
group->addChild(stagesGroup);
for (deUint32 flags = 0; flags < PARAMFLAGS_LAST << 2; ++flags)
{
TestParams params = defaultParams;
ostringstream name;
ostringstream desc;
if (!areCaseParamFlagsValid(ParamFlags(flags & PARAMFLAGS_MASK)))
continue;
if (flags & (PARAMFLAGS_LAST << 1))
{
params.useSameName = true;
name << "same_";
desc << "Identically named ";
}
else
{
name << "different_";
desc << "Differently named ";
}
if (flags & PARAMFLAGS_LAST)
{
params.useUniform = true;
name << "uniform_";
desc << "uniforms, ";
}
else
{
name << "constant_";
desc << "constants, ";
}
addRenderTest(*stagesGroup, name.str(), desc.str(), numIterations,
ParamFlags(flags & PARAMFLAGS_MASK), params);
}
TestCaseGroup* programUniformGroup =
new TestCaseGroup(ctx, "program_uniform", "ProgramUniform tests");
group->addChild(programUniformGroup);
for (deUint32 flags = 0; flags < PARAMFLAGS_LAST; ++flags)
{
TestParams params = defaultParams;
if (!areCaseParamFlagsValid(ParamFlags(flags)))
continue;
params.useUniform = true;
params.useProgramUniform = true;
addRenderTest(*programUniformGroup, "", "", numIterations, ParamFlags(flags), params);
}
TestCaseGroup* createShaderProgramGroup =
new TestCaseGroup(ctx, "create_shader_program", "CreateShaderProgram tests");
group->addChild(createShaderProgramGroup);
for (deUint32 flags = 0; flags < PARAMFLAGS_LAST; ++flags)
{
TestParams params = defaultParams;
if (!areCaseParamFlagsValid(ParamFlags(flags)))
continue;
params.useCreateHelper = true;
addRenderTest(*createShaderProgramGroup, "", "", numIterations,
ParamFlags(flags), params);
}
TestCaseGroup* interfaceGroup =
new TestCaseGroup(ctx, "interface", "Shader interface compatibility tests");
group->addChild(interfaceGroup);
enum
{
NUM_INTERPOLATIONS = VARYINGINTERPOLATION_RANDOM, // VARYINGINTERPOLATION_RANDOM is one after last fully specified interpolation
INTERFACEFLAGS_LAST = BINDING_LAST * NUM_INTERPOLATIONS * NUM_INTERPOLATIONS
};
for (deUint32 flags = 0; flags < INTERFACEFLAGS_LAST; ++flags)
{
deUint32 tmpFlags = flags;
VaryingInterpolation frgInterp = VaryingInterpolation(tmpFlags % NUM_INTERPOLATIONS);
VaryingInterpolation vtxInterp = VaryingInterpolation((tmpFlags /= NUM_INTERPOLATIONS)
% NUM_INTERPOLATIONS);
BindingKind binding = BindingKind((tmpFlags /= NUM_INTERPOLATIONS)
% BINDING_LAST);
TestParams params = defaultParams;
ostringstream name;
ostringstream desc;
params.varyings.count = 1;
params.varyings.type = glu::TYPE_FLOAT;
params.varyings.binding = binding;
params.varyings.vtxInterp = vtxInterp;
params.varyings.frgInterp = frgInterp;
switch (binding)
{
case BINDING_LOCATION:
name << "same_location";
desc << "Varyings have same location, ";
break;
case BINDING_NAME:
name << "same_name";
desc << "Varyings have same name, ";
break;
default:
DE_ASSERT(!"Impossible");
}
describeInterpolation("vertex", vtxInterp, name, desc);
describeInterpolation("fragment", frgInterp, name, desc);
if (!paramsValid(params))
continue;
interfaceGroup->addChild(
new SeparateShaderTest(ctx, name.str(), desc.str(), numIterations, params,
&SeparateShaderTest::testPipelineRendering));
}
deUint32 baseSeed = ctx.getTestContext().getCommandLine().getBaseSeed();
Random rnd (deStringHash("separate_shader.random") + baseSeed);
set<string> seen;
TestCaseGroup* randomGroup = new TestCaseGroup(
ctx, "random", "Random pipeline configuration tests");
group->addChild(randomGroup);
for (deUint32 i = 0; i < 128; ++i)
{
TestParams params;
string code;
deUint32 genIterations = 4096;
do
{
params = genParams(rnd.getUint32());
code = paramsCode(params);
} while (de::contains(seen, code) && --genIterations > 0);
seen.insert(code);
string name = de::toString(i); // Would be code but baseSeed can change
randomGroup->addChild(new SeparateShaderTest(
ctx, name, name, numIterations, params,
&SeparateShaderTest::testPipelineRendering));
}
TestCaseGroup* apiGroup =
new TestCaseGroup(ctx, "api", "Program pipeline API tests");
group->addChild(apiGroup);
{
// More or less random parameters. These shouldn't have much effect, so just
// do a single sample.
TestParams params = defaultParams;
params.useUniform = true;
apiGroup->addChild(new SeparateShaderTest(
ctx,
"current_program_priority",
"Test priority between current program and pipeline binding",
1, params, &SeparateShaderTest::testCurrentProgPriority));
apiGroup->addChild(new SeparateShaderTest(
ctx,
"active_program_uniform",
"Test that glUniform() affects a pipeline's active program",
1, params, &SeparateShaderTest::testActiveProgramUniform));
apiGroup->addChild(new SeparateShaderTest(
ctx,
"pipeline_programs",
"Test queries for programs in program pipeline stages",
1, params, &SeparateShaderTest::testPipelineQueryPrograms));
apiGroup->addChild(new SeparateShaderTest(
ctx,
"pipeline_active",
"Test query for active programs in a program pipeline",
1, params, &SeparateShaderTest::testPipelineQueryActive));
}
TestCaseGroup* interfaceMismatchGroup =
new TestCaseGroup(ctx, "validation", "Negative program pipeline interface matching");
group->addChild(interfaceMismatchGroup);
{
gls::ShaderLibrary shaderLibrary (ctx.getTestContext(), ctx.getRenderContext(), ctx.getContextInfo());
const std::vector<tcu::TestNode*> children = shaderLibrary.loadShaderFile("shaders/separate_shader_validation.test");
for (int i = 0; i < (int)children.size(); i++)
interfaceMismatchGroup->addChild(children[i]);
}
return group;
}
} // Functional
} // gles31
} // deqp