/*-------------------------------------------------------------------------
* 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 Long shader compilation stress tests
*//*--------------------------------------------------------------------*/
#include "es3sLongShaderTests.hpp"
#include "deRandom.hpp"
#include "deStringUtil.hpp"
#include "deString.h"
#include "tcuTestLog.hpp"
#include "gluRenderContext.hpp"
#include "gluShaderProgram.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"
#include <string>
#include <set>
#include <map>
#include <cmath>
using tcu::TestLog;
namespace deqp
{
namespace gles3
{
namespace Stress
{
namespace
{
enum LongShaderCaseFlags
{
CASE_REQUIRE_LINK_STATUS_OK = 1
};
const char* getConstVertShaderSource (void)
{
const char* const src =
"#version 300 es\n"
"void main ()\n"
"{\n"
" gl_Position = vec4(0.0);\n"
"}\n";
return src;
}
const char* getConstFragShaderSource (void)
{
const char* const src =
"#version 300 es\n"
"layout(location = 0) out mediump vec4 o_fragColor;\n"
"void main ()\n"
"{\n"
" o_fragColor = vec4(0.0);\n"
"}\n";
return src;
}
const char* getConstShaderSource (const glu::ShaderType shaderType)
{
DE_ASSERT(shaderType == glu::SHADERTYPE_VERTEX || shaderType == glu::SHADERTYPE_FRAGMENT);
if (shaderType == glu::SHADERTYPE_VERTEX)
return getConstVertShaderSource();
else
return getConstFragShaderSource();
}
typedef std::set<std::string> ShaderScope;
const char variableNamePrefixChars[] = "abcdefghijklmnopqrstuvwxyz";
class NameGenerator
{
public:
NameGenerator (void)
: m_scopeIndices (1, 0)
, m_currentScopeDepth (1)
, m_variableIndex (0)
{
}
void beginScope (void)
{
m_currentScopeDepth++;
if (m_scopeIndices.size() < (size_t)m_currentScopeDepth)
m_scopeIndices.push_back(0);
else
m_scopeIndices[m_currentScopeDepth-1]++;
m_variableIndex = 0;
}
void endScope (void)
{
DE_ASSERT(m_currentScopeDepth > 1);
m_currentScopeDepth--;
}
std::string makePrefix (void)
{
std::string prefix;
for (int ndx = 0; ndx < m_currentScopeDepth; ndx++)
{
const int scopeIndex = m_scopeIndices[m_currentScopeDepth-1];
DE_ASSERT(scopeIndex < DE_LENGTH_OF_ARRAY(variableNamePrefixChars));
prefix += variableNamePrefixChars[scopeIndex];
}
return prefix;
}
std::string next (void)
{
m_variableIndex++;
return makePrefix() + de::toString(m_variableIndex);
}
void makeNames (ShaderScope& scope, const deUint32 count)
{
for (deUint32 ndx = 0; ndx < count; ndx++)
scope.insert(next());
}
private:
std::vector<int> m_scopeIndices;
int m_currentScopeDepth;
int m_variableIndex;
};
struct LongShaderSpec
{
glu::ShaderType shaderType;
deUint32 opsTotal;
deUint32 variablesPerBlock;
deUint32 opsPerExpression;
LongShaderSpec (const glu::ShaderType shaderTypeInit, const deUint32 opsTotalInit)
: shaderType (shaderTypeInit)
, opsTotal (opsTotalInit)
, variablesPerBlock (deMaxu32(10, (deUint32)std::floor(std::sqrt((double)opsTotal))))
, opsPerExpression (deMinu32(10, variablesPerBlock / 2))
{
}
};
// Generator for long test shaders
class LongShaderGenerator
{
public:
LongShaderGenerator (de::Random& rnd, const LongShaderSpec& spec);
glu::ShaderSource getSource (void);
private:
de::Random m_rnd;
const LongShaderSpec m_spec;
NameGenerator m_nameGen;
std::vector<std::string> m_varNames;
std::vector<ShaderScope> m_scopes;
std::string m_source;
void generateSource (void);
std::string getRandomVariableName (void);
std::string getShaderOutputName (void);
std::string makeExpression (const std::vector<std::string>& varNames, const int numOps);
void addIndent (void);
void addLine (const std::string& text);
void beginBlock (void);
void endBlock (void);
};
LongShaderGenerator::LongShaderGenerator (de::Random& rnd, const LongShaderSpec& spec)
: m_rnd (rnd)
, m_spec (spec)
{
DE_ASSERT(m_spec.shaderType == glu::SHADERTYPE_VERTEX || m_spec.shaderType == glu::SHADERTYPE_FRAGMENT);
}
glu::ShaderSource LongShaderGenerator::getSource (void)
{
if (m_source.empty())
generateSource();
return glu::ShaderSource(m_spec.shaderType, m_source);
}
void LongShaderGenerator::generateSource (void)
{
deUint32 currentOpsTotal = 0;
m_source.clear();
addLine("#version 300 es");
if (m_spec.shaderType == glu::SHADERTYPE_FRAGMENT)
addLine("layout(location = 0) out mediump vec4 o_fragColor;");
addLine("void main (void)");
beginBlock();
while (currentOpsTotal < m_spec.opsTotal)
{
const bool isLast = (m_spec.opsTotal <= (currentOpsTotal + m_spec.opsPerExpression));
const int numOps = isLast ? (m_spec.opsTotal - currentOpsTotal) : m_spec.opsPerExpression;
const size_t numVars = numOps + 1;
const std::string outName = isLast ? getShaderOutputName() : getRandomVariableName();
std::vector<std::string> inNames (numVars);
DE_ASSERT(numVars < m_varNames.size());
m_rnd.choose(m_varNames.begin(), m_varNames.end(), inNames.begin(), (int)numVars);
{
std::string expr = makeExpression(inNames, numOps);
if (isLast)
addLine(outName + " = vec4(" + expr + ");");
else
addLine(outName + " = " + expr + ";");
}
currentOpsTotal += numOps;
}
while (!m_scopes.empty())
endBlock();
}
std::string LongShaderGenerator::getRandomVariableName (void)
{
return m_rnd.choose<std::string>(m_varNames.begin(), m_varNames.end());
}
std::string LongShaderGenerator::getShaderOutputName (void)
{
return (m_spec.shaderType == glu::SHADERTYPE_VERTEX) ? "gl_Position" : "o_fragColor";
}
std::string LongShaderGenerator::makeExpression (const std::vector<std::string>& varNames, const int numOps)
{
const std::string operators = "+-*/";
std::string expr;
DE_ASSERT(varNames.size() > (size_t)numOps);
expr = varNames[0];
for (int ndx = 1; ndx <= numOps; ndx++)
{
const std::string op = std::string("") + m_rnd.choose<char>(operators.begin(), operators.end());
const std::string varName = varNames[ndx];
expr += " " + op + " " + varName;
}
return expr;
}
void LongShaderGenerator::addIndent (void)
{
m_source += std::string(m_scopes.size(), '\t');
}
void LongShaderGenerator::addLine (const std::string& text)
{
addIndent();
m_source += text + "\n";
}
void LongShaderGenerator::beginBlock (void)
{
ShaderScope scope;
addLine("{");
m_nameGen.beginScope();
m_nameGen.makeNames(scope, m_spec.variablesPerBlock);
m_scopes.push_back(scope);
for (ShaderScope::const_iterator nameIter = scope.begin(); nameIter != scope.end(); nameIter++)
{
const std::string varName = *nameIter;
const float varValue = m_rnd.getFloat();
addLine("mediump float " + varName + " = " + de::floatToString(varValue, 5) + "f;");
m_varNames.push_back(varName);
}
}
void LongShaderGenerator::endBlock (void)
{
ShaderScope& scope = *(m_scopes.end()-1);
DE_ASSERT(!m_scopes.empty());
m_varNames.erase((m_varNames.begin() + (m_varNames.size() - scope.size())), m_varNames.end());
m_nameGen.endScope();
m_scopes.pop_back();
addLine("}");
}
} // anonymous
// Stress test case for compilation of large shaders
class LongShaderCompileStressCase : public TestCase
{
public:
LongShaderCompileStressCase (Context& context, const char* name, const char* desc, const LongShaderSpec& caseSpec, const deUint32 flags);
virtual ~LongShaderCompileStressCase (void);
void init (void);
IterateResult iterate (void);
void verify (const glu::ShaderProgram& program);
private:
const glu::ShaderType m_shaderType;
const deUint32 m_flags;
de::Random m_rnd;
LongShaderGenerator m_gen;
};
LongShaderCompileStressCase::LongShaderCompileStressCase (Context& context, const char* name, const char* desc, const LongShaderSpec& caseSpec, const deUint32 flags)
: TestCase (context, name, desc)
, m_shaderType (caseSpec.shaderType)
, m_flags (flags)
, m_rnd (deStringHash(name) ^ 0xac9c91d)
, m_gen (m_rnd, caseSpec)
{
DE_ASSERT(m_shaderType == glu::SHADERTYPE_VERTEX || m_shaderType == glu::SHADERTYPE_FRAGMENT);
}
LongShaderCompileStressCase::~LongShaderCompileStressCase (void)
{
}
void LongShaderCompileStressCase::init (void)
{
m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
}
tcu::TestCase::IterateResult LongShaderCompileStressCase::iterate (void)
{
tcu::TestLog& log = m_testCtx.getLog();
const glu::ShaderType otherShader = (m_shaderType == glu::SHADERTYPE_VERTEX) ? glu::SHADERTYPE_FRAGMENT : glu::SHADERTYPE_VERTEX;
glu::ProgramSources sources;
sources << m_gen.getSource();
sources << glu::ShaderSource(otherShader, getConstShaderSource(otherShader));
{
glu::ShaderProgram program(m_context.getRenderContext(), sources);
verify(program);
log << program;
}
return STOP;
}
void LongShaderCompileStressCase::verify (const glu::ShaderProgram& program)
{
tcu::TestLog& log = m_testCtx.getLog();
const glw::Functions& gl = m_context.getRenderContext().getFunctions();
const bool isStrict = (m_flags & CASE_REQUIRE_LINK_STATUS_OK) != 0;
const glw::GLenum errorCode = gl.getError();
if (isStrict && !program.isOk())
{
log << TestLog::Message << "Fail, expected program to compile and link successfully." << TestLog::EndMessage;
m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Linking failed");
}
if (program.isOk() && (errorCode != GL_NO_ERROR))
{
log << TestLog::Message << "Fail, program status OK but a GL error was received (" << errorCode << ")." << TestLog::EndMessage;
m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Conflicting status");
}
else if ((errorCode != GL_NO_ERROR) && (errorCode != GL_OUT_OF_MEMORY))
{
log << TestLog::Message << "Fail, expected GL_NO_ERROR or GL_OUT_OF_MEMORY, received " << errorCode << "." << TestLog::EndMessage;
m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Unexpected GL error");
}
}
LongShaderTests::LongShaderTests (Context& testCtx)
: TestCaseGroup(testCtx, "long_shaders", "Long shader compilation stress tests")
{
}
LongShaderTests::~LongShaderTests(void)
{
}
void LongShaderTests::init (void)
{
const deUint32 requireLinkOkMaxOps = 1000;
const deUint32 caseOpCounts[] =
{
100,
1000,
10000,
100000
};
for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(caseOpCounts); caseNdx++)
{
for (int shaderTypeInt = 0; shaderTypeInt < 2; shaderTypeInt++)
{
const glu::ShaderType shaderType = (shaderTypeInt == 0) ? glu::SHADERTYPE_VERTEX : glu::SHADERTYPE_FRAGMENT;
const deUint32 opCount = caseOpCounts[caseNdx];
const deUint32 flags = (opCount <= requireLinkOkMaxOps) ? CASE_REQUIRE_LINK_STATUS_OK : 0;
const std::string name = de::toString(opCount) + "_operations_" + glu::getShaderTypeName(shaderType);
const std::string desc = std::string("Compile ") + glu::getShaderTypeName(shaderType) + " shader with " + de::toString(opCount) + " operations";
LongShaderSpec caseSpec (shaderType, opCount);
addChild(new LongShaderCompileStressCase(m_context, name.c_str(), desc.c_str(), caseSpec, flags));
}
}
}
} // Stress
} // gles3
} // deqp