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