/*------------------------------------------------------------------------- * 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