/*------------------------------------------------------------------------- * drawElements Quality Program OpenGL (ES) 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 Parametrized, long-running stress case. * * \todo [2013-06-27 nuutti] Do certain things in a cleaner and less * confusing way, such as the "redundant buffer * factor" thing in LongStressCase. *//*--------------------------------------------------------------------*/ #include "glsLongStressCase.hpp" #include "tcuTestLog.hpp" #include "tcuCommandLine.hpp" #include "tcuTextureUtil.hpp" #include "tcuVector.hpp" #include "tcuVectorUtil.hpp" #include "glsTextureTestUtil.hpp" #include "gluPixelTransfer.hpp" #include "gluTextureUtil.hpp" #include "tcuStringTemplate.hpp" #include "gluStrUtil.hpp" #include "gluShaderProgram.hpp" #include "deRandom.hpp" #include "deStringUtil.hpp" #include "deString.h" #include "deSharedPtr.hpp" #include "deClock.h" #include "glw.h" #include <limits> #include <vector> #include <iomanip> #include <map> #include <iomanip> using tcu::TestLog; using tcu::Vec2; using tcu::Vec3; using tcu::Vec4; using tcu::IVec2; using tcu::IVec3; using tcu::IVec4; using tcu::TextureLevel; using tcu::TextureFormat; using tcu::ConstPixelBufferAccess; using tcu::CubeFace; using de::SharedPtr; using de::Random; using de::toString; using std::vector; using std::string; using std::map; namespace deqp { namespace gls { using glu::TextureTestUtil::TextureType; using glu::TextureTestUtil::TEXTURETYPE_2D; using glu::TextureTestUtil::TEXTURETYPE_CUBE; static const float Mi = (float)(1<<20); static const deUint32 bufferUsages[] = { GL_STATIC_DRAW, GL_STREAM_DRAW, GL_DYNAMIC_DRAW, GL_STATIC_READ, GL_STREAM_READ, GL_DYNAMIC_READ, GL_STATIC_COPY, GL_STREAM_COPY, GL_DYNAMIC_COPY }; static const deUint32 bufferUsagesGLES2[] = { GL_STATIC_DRAW, GL_DYNAMIC_DRAW, GL_STREAM_DRAW }; static const deUint32 bufferTargets[] = { GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER, GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, GL_PIXEL_PACK_BUFFER, GL_PIXEL_UNPACK_BUFFER, GL_TRANSFORM_FEEDBACK_BUFFER, GL_UNIFORM_BUFFER }; static const deUint32 bufferTargetsGLES2[] = { GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER }; static inline int computePixelStore (const TextureFormat& format) { const int pixelSize = format.getPixelSize(); if (deIsPowerOfTwo32(pixelSize)) return de::min(pixelSize, 8); else return 1; } static inline int getNumIterations (const tcu::TestContext& testCtx, const int defaultNumIterations) { const int cmdLineVal = testCtx.getCommandLine().getTestIterationCount(); return cmdLineVal == 0 ? defaultNumIterations : cmdLineVal; } static inline float triangleArea (const Vec2& a, const Vec2& b, const Vec2& c) { const Vec2 ab = b-a; const Vec2 ac = c-a; return 0.5f * tcu::length(ab.x()*ac.y() - ab.y()*ac.x()); } static inline string mangleShaderNames (const string& source, const string& manglingSuffix) { map<string, string> m; m["NS"] = manglingSuffix; return tcu::StringTemplate(source.c_str()).specialize(m); } template <typename T, int N> static inline T randomChoose (Random& rnd, const T (&arr)[N]) { return rnd.choose<T>(DE_ARRAY_BEGIN(arr), DE_ARRAY_END(arr)); } static inline int nextDivisible (const int x, const int div) { DE_ASSERT(x >= 0); DE_ASSERT(div >= 1); return x == 0 ? 0 : x-1 + div - (x-1) % div; } static inline string getTimeStr (const deUint64 seconds) { const deUint64 m = seconds / 60; const deUint64 h = m / 60; const deUint64 d = h / 24; std::ostringstream res; res << d << "d " << h%24 << "h " << m%60 << "m " << seconds%60 << "s"; return res.str(); } static inline string probabilityStr (const float prob) { return prob == 0.0f ? "never" : prob == 1.0f ? "ALWAYS" : de::floatToString(prob*100.0f, 0) + "%"; } static inline deUint32 randomBufferTarget (Random& rnd, const bool isGLES3) { return isGLES3 ? randomChoose(rnd, bufferTargets) : randomChoose(rnd, bufferTargetsGLES2); } static inline deUint32 randomBufferUsage (Random& rnd, const bool isGLES3) { return isGLES3 ? randomChoose(rnd, bufferUsages) : randomChoose(rnd, bufferUsagesGLES2); } static inline deUint32 cubeFaceToGLFace (tcu::CubeFace face) { switch (face) { case tcu::CUBEFACE_NEGATIVE_X: return GL_TEXTURE_CUBE_MAP_NEGATIVE_X; case tcu::CUBEFACE_POSITIVE_X: return GL_TEXTURE_CUBE_MAP_POSITIVE_X; case tcu::CUBEFACE_NEGATIVE_Y: return GL_TEXTURE_CUBE_MAP_NEGATIVE_Y; case tcu::CUBEFACE_POSITIVE_Y: return GL_TEXTURE_CUBE_MAP_POSITIVE_Y; case tcu::CUBEFACE_NEGATIVE_Z: return GL_TEXTURE_CUBE_MAP_NEGATIVE_Z; case tcu::CUBEFACE_POSITIVE_Z: return GL_TEXTURE_CUBE_MAP_POSITIVE_Z; default: DE_ASSERT(false); return GL_NONE; } } #if defined(DE_DEBUG) static inline bool isMatchingGLInternalFormat (const deUint32 internalFormat, const TextureFormat& texFormat) { switch (internalFormat) { // Unsized formats. case GL_RGBA: return texFormat.order == TextureFormat::RGBA && (texFormat.type == TextureFormat::UNORM_INT8 || texFormat.type == TextureFormat::UNORM_SHORT_4444 || texFormat.type == TextureFormat::UNORM_SHORT_5551); case GL_RGB: return texFormat.order == TextureFormat::RGB && (texFormat.type == TextureFormat::UNORM_INT8 || texFormat.type == TextureFormat::UNORM_SHORT_565); case GL_LUMINANCE_ALPHA: return texFormat.order == TextureFormat::LA && texFormat.type == TextureFormat::UNORM_INT8; case GL_LUMINANCE: return texFormat.order == TextureFormat::L && texFormat.type == TextureFormat::UNORM_INT8; case GL_ALPHA: return texFormat.order == TextureFormat::A && texFormat.type == TextureFormat::UNORM_INT8; // Sized formats. default: return glu::mapGLInternalFormat(internalFormat) == texFormat; } } #endif // DE_DEBUG static inline bool compileShader (const deUint32 shaderGL) { glCompileShader(shaderGL); int success = GL_FALSE; glGetShaderiv(shaderGL, GL_COMPILE_STATUS, &success); return success == GL_TRUE; } static inline bool linkProgram (const deUint32 programGL) { glLinkProgram(programGL); int success = GL_FALSE; glGetProgramiv(programGL, GL_LINK_STATUS, &success); return success == GL_TRUE; } static inline string getShaderInfoLog (const deUint32 shaderGL) { int infoLogLen = 0; vector<char> infoLog; glGetShaderiv(shaderGL, GL_INFO_LOG_LENGTH, &infoLogLen); infoLog.resize(infoLogLen+1); glGetShaderInfoLog(shaderGL, (int)infoLog.size(), DE_NULL, &infoLog[0]); return &infoLog[0]; } static inline string getProgramInfoLog (const deUint32 programGL) { int infoLogLen = 0; vector<char> infoLog; glGetProgramiv(programGL, GL_INFO_LOG_LENGTH, &infoLogLen); infoLog.resize(infoLogLen+1); glGetProgramInfoLog(programGL, (int)infoLog.size(), DE_NULL, &infoLog[0]); return &infoLog[0]; } namespace LongStressCaseInternal { // A hacky-ish class for drawing text on screen as GL quads. class DebugInfoRenderer { public: DebugInfoRenderer (const glu::RenderContext& ctx); ~DebugInfoRenderer (void) { delete m_prog; } void drawInfo (deUint64 secondsElapsed, int texMem, int maxTexMem, int bufMem, int maxBufMem, int iterNdx); private: DebugInfoRenderer (const DebugInfoRenderer&); DebugInfoRenderer& operator= (const DebugInfoRenderer&); void render (void); void addTextToBuffer (const string& text, int yOffset); const glu::RenderContext& m_ctx; const glu::ShaderProgram* m_prog; vector<float> m_posBuf; vector<deUint16> m_ndxBuf; }; void DebugInfoRenderer::drawInfo (const deUint64 secondsElapsed, const int texMem, const int maxTexMem, const int bufMem, const int maxBufMem, const int iterNdx) { const deUint64 m = secondsElapsed / 60; const deUint64 h = m / 60; const deUint64 d = h / 24; { std::ostringstream text; text << std::setw(2) << std::setfill('0') << d << ":" << std::setw(2) << std::setfill('0') << h % 24 << ":" << std::setw(2) << std::setfill('0') << m % 60 << ":" << std::setw(2) << std::setfill('0') << secondsElapsed % 60; addTextToBuffer(text.str(), 0); text.str(""); text << std::fixed << std::setprecision(2) << (float)texMem/Mi << "/" << (float)maxTexMem/Mi; addTextToBuffer(text.str(), 1); text.str(""); text << std::fixed << std::setprecision(2) << (float)bufMem/Mi << "/" << (float)maxBufMem/Mi; addTextToBuffer(text.str(), 2); text.str(""); text << std::setw(0) << iterNdx; addTextToBuffer(text.str(), 3); } render(); } DebugInfoRenderer::DebugInfoRenderer (const glu::RenderContext& ctx) : m_ctx (ctx) , m_prog (DE_NULL) { DE_ASSERT(!m_prog); m_prog = new glu::ShaderProgram(ctx, glu::makeVtxFragSources( "attribute highp vec2 a_pos;\n" "void main (void)\n" "{\n" " gl_Position = vec4(a_pos, -1.0, 1.0);\n" "}\n", "void main(void)\n" "{\n" " gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n" "}\n")); } void DebugInfoRenderer::addTextToBuffer (const string& text, const int yOffset) { static const char characters[] = "0123456789.:/"; const int numCharacters = DE_LENGTH_OF_ARRAY(characters)-1; // \note -1 for null byte. const int charWid = 6; const int charHei = 6; static const string charsStr (characters); static const char font[numCharacters*charWid*charHei + 1]= " #### "" # "" #### ""##### "" # ""######"" #####""######"" #### "" #### "" "" ## "" #" "# #"" ## ""# #"" #"" # ""# ""# "" # ""# #""# #"" "" ## "" # " "# #"" # "" # "" ### "" # # "" #### ""# ### "" # "" #### "" #####"" "" "" # " "# #"" # "" # "" #""######"" #""## #"" # ""# #"" #"" "" ## "" # " "# #"" # "" # ""# #"" # ""# #""# #"" # ""# #"" ## "" ## "" ## "" # " " #### "" ### ""######"" #### "" # "" #### "" #### ""# "" #### ""### "" ## "" ""# "; for (int ndxInText = 0; ndxInText < (int)text.size(); ndxInText++) { const int ndxInCharset = (int)charsStr.find(text[ndxInText]); DE_ASSERT(ndxInCharset < numCharacters); const int fontXStart = ndxInCharset*charWid; for (int y = 0; y < charHei; y++) { float ay = -1.0f + (float)(y + 0 + yOffset*(charHei+2))*0.1f/(float)(charHei+2); float by = -1.0f + (float)(y + 1 + yOffset*(charHei+2))*0.1f/(float)(charHei+2); for (int x = 0; x < charWid; x++) { // \note Text is mirrored in x direction since on most(?) mobile devices the image is mirrored(?). float ax = 1.0f - (float)(x + 0 + ndxInText*(charWid+2))*0.1f/(float)(charWid+2); float bx = 1.0f - (float)(x + 1 + ndxInText*(charWid+2))*0.1f/(float)(charWid+2); if (font[y*numCharacters*charWid + fontXStart + x] != ' ') { const int vtxNdx = (int)m_posBuf.size()/2; m_ndxBuf.push_back(deUint16(vtxNdx+0)); m_ndxBuf.push_back(deUint16(vtxNdx+1)); m_ndxBuf.push_back(deUint16(vtxNdx+2)); m_ndxBuf.push_back(deUint16(vtxNdx+2)); m_ndxBuf.push_back(deUint16(vtxNdx+1)); m_ndxBuf.push_back(deUint16(vtxNdx+3)); m_posBuf.push_back(ax); m_posBuf.push_back(ay); m_posBuf.push_back(bx); m_posBuf.push_back(ay); m_posBuf.push_back(ax); m_posBuf.push_back(by); m_posBuf.push_back(bx); m_posBuf.push_back(by); } } } } } void DebugInfoRenderer::render (void) { const int prog = m_prog->getProgram(); const int posloc = glGetAttribLocation(prog, "a_pos"); glUseProgram(prog); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glEnableVertexAttribArray(posloc); glVertexAttribPointer(posloc, 2, GL_FLOAT, 0, 0, &m_posBuf[0]); glDrawElements(GL_TRIANGLES, (int)m_ndxBuf.size(), GL_UNSIGNED_SHORT, &m_ndxBuf[0]); glDisableVertexAttribArray(posloc); m_posBuf.clear(); m_ndxBuf.clear(); } /*--------------------------------------------------------------------*//*! * \brief Texture object helper class * * Each Texture owns a GL texture object that is created when the Texture * is constructed and deleted when it's destructed. The class provides some * convenience interface functions to e.g. upload texture data to the GL. * * In addition, the class tracks the approximate amount of GL memory likely * used by the corresponding GL texture object; get this with * getApproxMemUsage(). Also, getApproxMemUsageDiff() returns N-M, where N * is the value that getApproxMemUsage() would return after a call to * setData() with arguments corresponding to those given to * getApproxMemUsageDiff(), and M is the value currently returned by * getApproxMemUsage(). This can be used to check if we need to free some * other memory before performing the setData() call, in case we have an * upper limit on the amount of memory we want to use. *//*--------------------------------------------------------------------*/ class Texture { public: Texture (TextureType type); ~Texture (void); // Functions that may change the value returned by getApproxMemUsage(). void setData (const ConstPixelBufferAccess& src, int width, int height, deUint32 internalFormat, bool useMipmap); // Functions that don't change the value returned by getApproxMemUsage(). void setSubData (const ConstPixelBufferAccess& src, int xOff, int yOff, int width, int height) const; void toUnit (int unit) const; void setFilter (deUint32 min, deUint32 mag) const; void setWrap (deUint32 s, deUint32 t) const; int getApproxMemUsage (void) const { return m_dataSizeApprox; } int getApproxMemUsageDiff (int width, int height, deUint32 internalFormat, bool useMipmap) const; private: Texture (const Texture&); // Not allowed. Texture& operator= (const Texture&); // Not allowed. static deUint32 genTexture (void) { deUint32 tex = 0; glGenTextures(1, &tex); return tex; } deUint32 getGLBindTarget (void) const { DE_ASSERT(m_type == TEXTURETYPE_2D || m_type == TEXTURETYPE_CUBE); return m_type == TEXTURETYPE_2D ? GL_TEXTURE_2D : GL_TEXTURE_CUBE_MAP; } const TextureType m_type; const deUint32 m_textureGL; int m_numMipLevels; deUint32 m_internalFormat; int m_dataSizeApprox; }; Texture::Texture (const TextureType type) : m_type (type) , m_textureGL (genTexture()) , m_numMipLevels (0) , m_internalFormat (0) , m_dataSizeApprox (0) { } Texture::~Texture (void) { glDeleteTextures(1, &m_textureGL); } int Texture::getApproxMemUsageDiff (const int width, const int height, const deUint32 internalFormat, const bool useMipmap) const { const int numLevels = useMipmap ? deLog2Floor32(de::max(width, height))+1 : 1; const int pixelSize = internalFormat == GL_RGBA ? 4 : internalFormat == GL_RGB ? 3 : internalFormat == GL_ALPHA ? 1 : glu::mapGLInternalFormat(internalFormat).getPixelSize(); int memUsageApproxAfter = 0; for (int level = 0; level < numLevels; level++) memUsageApproxAfter += de::max(1, width>>level) * de::max(1, height>>level) * pixelSize * (m_type == TEXTURETYPE_CUBE ? 6 : 1); return memUsageApproxAfter - getApproxMemUsage(); } void Texture::setData (const ConstPixelBufferAccess& src, const int width, const int height, const deUint32 internalFormat, const bool useMipmap) { DE_ASSERT(m_type != TEXTURETYPE_CUBE || width == height); DE_ASSERT(!useMipmap || (deIsPowerOfTwo32(width) && deIsPowerOfTwo32(height))); const TextureFormat& format = src.getFormat(); const glu::TransferFormat transfer = glu::getTransferFormat(format); m_numMipLevels = useMipmap ? deLog2Floor32(de::max(width, height))+1 : 1; m_internalFormat = internalFormat; m_dataSizeApprox = width * height * format.getPixelSize() * (m_type == TEXTURETYPE_CUBE ? 6 : 1); DE_ASSERT(src.getRowPitch() == format.getPixelSize()*src.getWidth()); DE_ASSERT(isMatchingGLInternalFormat(internalFormat, format)); DE_ASSERT(width <= src.getWidth() && height <= src.getHeight()); glPixelStorei(GL_UNPACK_ALIGNMENT, computePixelStore(format)); if (m_type == TEXTURETYPE_2D) { m_dataSizeApprox = 0; glBindTexture(GL_TEXTURE_2D, m_textureGL); for (int level = 0; level < m_numMipLevels; level++) { const int levelWid = de::max(1, width>>level); const int levelHei = de::max(1, height>>level); m_dataSizeApprox += levelWid * levelHei * format.getPixelSize(); glTexImage2D(GL_TEXTURE_2D, level, internalFormat, levelWid, levelHei, 0, transfer.format, transfer.dataType, src.getDataPtr()); } } else if (m_type == TEXTURETYPE_CUBE) { m_dataSizeApprox = 0; glBindTexture(GL_TEXTURE_CUBE_MAP, m_textureGL); for (int level = 0; level < m_numMipLevels; level++) { const int levelWid = de::max(1, width>>level); const int levelHei = de::max(1, height>>level); m_dataSizeApprox += 6 * levelWid * levelHei * format.getPixelSize(); for (int face = 0; face < tcu::CUBEFACE_LAST; face++) glTexImage2D(cubeFaceToGLFace((CubeFace)face), level, internalFormat, levelWid, levelHei, 0, transfer.format, transfer.dataType, src.getDataPtr()); } } else DE_ASSERT(false); } void Texture::setSubData (const ConstPixelBufferAccess& src, const int xOff, const int yOff, const int width, const int height) const { const TextureFormat& format = src.getFormat(); const glu::TransferFormat transfer = glu::getTransferFormat(format); DE_ASSERT(src.getRowPitch() == format.getPixelSize()*src.getWidth()); DE_ASSERT(isMatchingGLInternalFormat(m_internalFormat, format)); DE_ASSERT(width <= src.getWidth() && height <= src.getHeight()); glPixelStorei(GL_UNPACK_ALIGNMENT, computePixelStore(format)); if (m_type == TEXTURETYPE_2D) { glBindTexture(GL_TEXTURE_2D, m_textureGL); for (int level = 0; level < m_numMipLevels; level++) glTexSubImage2D(GL_TEXTURE_2D, level, xOff>>level, yOff>>level, de::max(1, width>>level), de::max(1, height>>level), transfer.format, transfer.dataType, src.getDataPtr()); } else if (m_type == TEXTURETYPE_CUBE) { glBindTexture(GL_TEXTURE_CUBE_MAP, m_textureGL); for (int level = 0; level < m_numMipLevels; level++) { for (int face = 0; face < tcu::CUBEFACE_LAST; face++) glTexSubImage2D(cubeFaceToGLFace((CubeFace)face), level, xOff>>level, yOff>>level, de::max(1, width>>level), de::max(1, height>>level), transfer.format, transfer.dataType, src.getDataPtr()); } } else DE_ASSERT(false); } void Texture::setFilter (const deUint32 min, const deUint32 mag) const { glBindTexture(getGLBindTarget(), m_textureGL); glTexParameteri(getGLBindTarget(), GL_TEXTURE_MIN_FILTER, min); glTexParameteri(getGLBindTarget(), GL_TEXTURE_MAG_FILTER, mag); } void Texture::setWrap (const deUint32 s, const deUint32 t) const { glBindTexture(getGLBindTarget(), m_textureGL); glTexParameteri(getGLBindTarget(), GL_TEXTURE_WRAP_S, s); glTexParameteri(getGLBindTarget(), GL_TEXTURE_WRAP_T, t); } void Texture::toUnit (const int unit) const { glActiveTexture(GL_TEXTURE0 + unit); glBindTexture(getGLBindTarget(), m_textureGL); } /*--------------------------------------------------------------------*//*! * \brief Buffer object helper class * * Each Buffer owns a GL buffer object that is created when the Buffer * is constructed and deleted when it's destructed. The class provides some * convenience interface functions to e.g. upload buffer data to the GL. * * In addition, the class tracks the approximate amount of GL memory, * similarly to the Texture class (see above). The getApproxMemUsageDiff() * is also analoguous. *//*--------------------------------------------------------------------*/ class Buffer { public: Buffer (void); ~Buffer (void); // Functions that may change the value returned by getApproxMemUsage(). template <typename T> void setData (const vector<T>& src, const deUint32 target, const deUint32 usage) { setData(&src[0], (int)(src.size()*sizeof(T)), target, usage); } void setData (const void* src, int size, deUint32 target, deUint32 usage); // Functions that don't change the value returned by getApproxMemUsage(). template <typename T> void setSubData (const vector<T>& src, const int offsetElems, const int numElems, const deUint32 target) { setSubData(&src[offsetElems], offsetElems*(int)sizeof(T), numElems*(int)sizeof(T), target); } void setSubData (const void* src, int offsetBytes, int sizeBytes, deUint32 target) const; void bind (const deUint32 target) const { glBindBuffer(target, m_bufferGL); } int getApproxMemUsage (void) const { return m_dataSizeApprox; } template <typename T> int getApproxMemUsageDiff (const vector<T>& src) const { return getApproxMemUsageDiff((int)(src.size()*sizeof(T))); } int getApproxMemUsageDiff (const int sizeBytes) const { return sizeBytes - getApproxMemUsage(); } private: Buffer (const Buffer&); // Not allowed. Buffer& operator= (const Buffer&); // Not allowed. static deUint32 genBuffer (void) { deUint32 buf = 0; glGenBuffers(1, &buf); return buf; } const deUint32 m_bufferGL; int m_dataSizeApprox; }; Buffer::Buffer (void) : m_bufferGL (genBuffer()) , m_dataSizeApprox (0) { } Buffer::~Buffer (void) { glDeleteBuffers(1, &m_bufferGL); } void Buffer::setData (const void* const src, const int size, const deUint32 target, const deUint32 usage) { bind(target); glBufferData(target, size, src, usage); glBindBuffer(target, 0); m_dataSizeApprox = size; } void Buffer::setSubData (const void* const src, const int offsetBytes, const int sizeBytes, const deUint32 target) const { bind(target); glBufferSubData(target, offsetBytes, sizeBytes, src); glBindBuffer(target, 0); } class Program { public: Program (void); ~Program (void); void setSources (const string& vertSource, const string& fragSource); void build (TestLog& log); void use (void) const { DE_ASSERT(m_isBuilt); glUseProgram(m_programGL); } void setRandomUniforms (const vector<VarSpec>& uniforms, const string& shaderNameManglingSuffix, Random& rnd) const; void setAttribute (const Buffer& attrBuf, int attrBufOffset, const VarSpec& attrSpec, const string& shaderNameManglingSuffix) const; void setAttributeClientMem (const void* attrData, const VarSpec& attrSpec, const string& shaderNameManglingSuffix) const; void disableAttributeArray (const VarSpec& attrSpec, const string& shaderNameManglingSuffix) const; private: Program (const Program&); // Not allowed. Program& operator= (const Program&); // Not allowed. string m_vertSource; string m_fragSource; const deUint32 m_vertShaderGL; const deUint32 m_fragShaderGL; const deUint32 m_programGL; bool m_hasSources; bool m_isBuilt; }; Program::Program (void) : m_vertShaderGL (glCreateShader(GL_VERTEX_SHADER)) , m_fragShaderGL (glCreateShader(GL_FRAGMENT_SHADER)) , m_programGL (glCreateProgram()) , m_hasSources (false) , m_isBuilt (false) { glAttachShader(m_programGL, m_vertShaderGL); glAttachShader(m_programGL, m_fragShaderGL); } Program::~Program (void) { glDeleteShader(m_vertShaderGL); glDeleteShader(m_fragShaderGL); glDeleteProgram(m_programGL); } void Program::setSources (const string& vertSource, const string& fragSource) { const char* const vertSourceCstr = vertSource.c_str(); const char* const fragSourceCstr = fragSource.c_str(); m_vertSource = vertSource; m_fragSource = fragSource; // \note In GLES2 api the source parameter type lacks one const. glShaderSource(m_vertShaderGL, 1, (const char**)&vertSourceCstr, DE_NULL); glShaderSource(m_fragShaderGL, 1, (const char**)&fragSourceCstr, DE_NULL); m_hasSources = true; } void Program::build (TestLog& log) { DE_ASSERT(m_hasSources); const bool vertCompileOk = compileShader(m_vertShaderGL); const bool fragCompileOk = compileShader(m_fragShaderGL); const bool attemptLink = vertCompileOk && fragCompileOk; const bool linkOk = attemptLink && linkProgram(m_programGL); if (!(vertCompileOk && fragCompileOk && linkOk)) { log << TestLog::ShaderProgram(linkOk, attemptLink ? getProgramInfoLog(m_programGL) : string("")) << TestLog::Shader(QP_SHADER_TYPE_VERTEX, m_vertSource, vertCompileOk, getShaderInfoLog(m_vertShaderGL)) << TestLog::Shader(QP_SHADER_TYPE_FRAGMENT, m_fragSource, fragCompileOk, getShaderInfoLog(m_fragShaderGL)) << TestLog::EndShaderProgram; throw tcu::TestError("Program build failed"); } m_isBuilt = true; } void Program::setRandomUniforms (const vector<VarSpec>& uniforms, const string& shaderNameManglingSuffix, Random& rnd) const { use(); for (int unifNdx = 0; unifNdx < (int)uniforms.size(); unifNdx++) { const VarSpec& spec = uniforms[unifNdx]; const int typeScalarSize = glu::getDataTypeScalarSize(spec.type); const int location = glGetUniformLocation(m_programGL, mangleShaderNames(spec.name, shaderNameManglingSuffix).c_str()); if (location < 0) continue; if (glu::isDataTypeFloatOrVec(spec.type)) { float val[4]; for (int i = 0; i < typeScalarSize; i++) val[i] = rnd.getFloat(spec.minValue.f[i], spec.maxValue.f[i]); switch (spec.type) { case glu::TYPE_FLOAT: glUniform1f(location, val[0]); break; case glu::TYPE_FLOAT_VEC2: glUniform2f(location, val[0], val[1]); break; case glu::TYPE_FLOAT_VEC3: glUniform3f(location, val[0], val[1], val[2]); break; case glu::TYPE_FLOAT_VEC4: glUniform4f(location, val[0], val[1], val[2], val[3]); break; default: DE_ASSERT(false); } } else if (glu::isDataTypeMatrix(spec.type)) { float val[4*4]; for (int i = 0; i < typeScalarSize; i++) val[i] = rnd.getFloat(spec.minValue.f[i], spec.maxValue.f[i]); switch (spec.type) { case glu::TYPE_FLOAT_MAT2: glUniformMatrix2fv (location, 1, GL_FALSE, &val[0]); break; case glu::TYPE_FLOAT_MAT3: glUniformMatrix3fv (location, 1, GL_FALSE, &val[0]); break; case glu::TYPE_FLOAT_MAT4: glUniformMatrix4fv (location, 1, GL_FALSE, &val[0]); break; case glu::TYPE_FLOAT_MAT2X3: glUniformMatrix2x3fv (location, 1, GL_FALSE, &val[0]); break; case glu::TYPE_FLOAT_MAT2X4: glUniformMatrix2x4fv (location, 1, GL_FALSE, &val[0]); break; case glu::TYPE_FLOAT_MAT3X2: glUniformMatrix3x2fv (location, 1, GL_FALSE, &val[0]); break; case glu::TYPE_FLOAT_MAT3X4: glUniformMatrix3x4fv (location, 1, GL_FALSE, &val[0]); break; case glu::TYPE_FLOAT_MAT4X2: glUniformMatrix4x2fv (location, 1, GL_FALSE, &val[0]); break; case glu::TYPE_FLOAT_MAT4X3: glUniformMatrix4x3fv (location, 1, GL_FALSE, &val[0]); break; default: DE_ASSERT(false); } } else if (glu::isDataTypeIntOrIVec(spec.type)) { int val[4]; for (int i = 0; i < typeScalarSize; i++) val[i] = rnd.getInt(spec.minValue.i[i], spec.maxValue.i[i]); switch (spec.type) { case glu::TYPE_INT: glUniform1i(location, val[0]); break; case glu::TYPE_INT_VEC2: glUniform2i(location, val[0], val[1]); break; case glu::TYPE_INT_VEC3: glUniform3i(location, val[0], val[1], val[2]); break; case glu::TYPE_INT_VEC4: glUniform4i(location, val[0], val[1], val[2], val[3]); break; default: DE_ASSERT(false); } } else if (glu::isDataTypeUintOrUVec(spec.type)) { deUint32 val[4]; for (int i = 0; i < typeScalarSize; i++) { DE_ASSERT(spec.minValue.i[i] >= 0 && spec.maxValue.i[i] >= 0); val[i] = (deUint32)rnd.getInt(spec.minValue.i[i], spec.maxValue.i[i]); } switch (spec.type) { case glu::TYPE_UINT: glUniform1ui(location, val[0]); break; case glu::TYPE_UINT_VEC2: glUniform2ui(location, val[0], val[1]); break; case glu::TYPE_UINT_VEC3: glUniform3ui(location, val[0], val[1], val[2]); break; case glu::TYPE_UINT_VEC4: glUniform4ui(location, val[0], val[1], val[2], val[3]); break; default: DE_ASSERT(false); } } else DE_ASSERT(false); } } void Program::setAttribute (const Buffer& attrBuf, const int attrBufOffset, const VarSpec& attrSpec, const string& shaderNameManglingSuffix) const { const int attrLoc = glGetAttribLocation(m_programGL, mangleShaderNames(attrSpec.name, shaderNameManglingSuffix).c_str()); glEnableVertexAttribArray(attrLoc); attrBuf.bind(GL_ARRAY_BUFFER); if (glu::isDataTypeFloatOrVec(attrSpec.type)) glVertexAttribPointer(attrLoc, glu::getDataTypeScalarSize(attrSpec.type), GL_FLOAT, GL_FALSE, 0, (GLvoid*)(deIntptr)attrBufOffset); else DE_ASSERT(false); } void Program::setAttributeClientMem (const void* const attrData, const VarSpec& attrSpec, const string& shaderNameManglingSuffix) const { const int attrLoc = glGetAttribLocation(m_programGL, mangleShaderNames(attrSpec.name, shaderNameManglingSuffix).c_str()); glEnableVertexAttribArray(attrLoc); glBindBuffer(GL_ARRAY_BUFFER, 0); if (glu::isDataTypeFloatOrVec(attrSpec.type)) glVertexAttribPointer(attrLoc, glu::getDataTypeScalarSize(attrSpec.type), GL_FLOAT, GL_FALSE, 0, attrData); else DE_ASSERT(false); } void Program::disableAttributeArray (const VarSpec& attrSpec, const string& shaderNameManglingSuffix) const { const int attrLoc = glGetAttribLocation(m_programGL, mangleShaderNames(attrSpec.name, shaderNameManglingSuffix).c_str()); glDisableVertexAttribArray(attrLoc); } /*--------------------------------------------------------------------*//*! * \brief Container class for managing GL objects * * GLObjectManager can be used for objects of class Program, Buffer or * Texture. In the manager, each such object is associated with a name that * is used to access it. * * In addition to the making, getting and removing functions, the manager * supports marking objects as "garbage", meaning they're not yet * destroyed, but can be later destroyed with removeRandomGarbage(). The * idea is that if we want to stress test with high memory usage, we can * continuously move objects to garbage after using them, and when a memory * limit is reached, we can call removeGarbageUntilUnder(limit, rnd). This * way we can approximately keep our memory usage at just under the wanted * limit. * * The manager also supports querying the approximate amount of GL memory * used by its objects. * * \note The memory usage related functions are not currently supported * for Program objects. *//*--------------------------------------------------------------------*/ template <typename T> class GLObjectManager { public: void make (const string& name) { DE_ASSERT(!has(name)); m_objects[name] = SharedPtr<T>(new T); } void make (const string& name, gls::TextureType texType) { DE_ASSERT(!has(name)); m_objects[name] = SharedPtr<T>(new T(texType)); } bool has (const string& name) const { return m_objects.find(name) != m_objects.end(); } const T& get (const string& name) const; T& get (const string& name) { return const_cast<T&>(((const GLObjectManager<T>*)this)->get(name)); } void remove (const string& name) { const int removed = (int)m_objects.erase(name); DE_ASSERT(removed); DE_UNREF(removed); } int computeApproxMemUsage (void) const; void markAsGarbage (const string& name); int removeRandomGarbage (Random& rnd); void removeGarbageUntilUnder (int limit, Random& rnd); private: static const char* objTypeName (void); map<string, SharedPtr<T> > m_objects; vector<SharedPtr<T> > m_garbageObjects; }; template <> const char* GLObjectManager<Buffer>::objTypeName (void) { return "buffer"; } template <> const char* GLObjectManager<Texture>::objTypeName (void) { return "texture"; } template <> const char* GLObjectManager<Program>::objTypeName (void) { return "program"; } template <typename T> const T& GLObjectManager<T>::get (const string& name) const { const typename map<string, SharedPtr<T> >::const_iterator it = m_objects.find(name); DE_ASSERT(it != m_objects.end()); return *it->second; } template <typename T> int GLObjectManager<T>::computeApproxMemUsage (void) const { int result = 0; for (typename map<string, SharedPtr<T> >::const_iterator it = m_objects.begin(); it != m_objects.end(); ++it) result += it->second->getApproxMemUsage(); for (typename vector<SharedPtr<T> >::const_iterator it = m_garbageObjects.begin(); it != m_garbageObjects.end(); ++it) result += (*it)->getApproxMemUsage(); return result; } template <typename T> void GLObjectManager<T>::markAsGarbage (const string& name) { const typename map<string, SharedPtr<T> >::iterator it = m_objects.find(name); DE_ASSERT(it != m_objects.end()); m_garbageObjects.push_back(it->second); m_objects.erase(it); } template <typename T> int GLObjectManager<T>::removeRandomGarbage (Random& rnd) { if (m_garbageObjects.empty()) return -1; const int removeNdx = rnd.getInt(0, (int)m_garbageObjects.size()-1); const int memoryFreed = m_garbageObjects[removeNdx]->getApproxMemUsage(); m_garbageObjects.erase(m_garbageObjects.begin() + removeNdx); return memoryFreed; } template <typename T> void GLObjectManager<T>::removeGarbageUntilUnder (const int limit, Random& rnd) { int memUsage = computeApproxMemUsage(); while (memUsage > limit) { const int memReleased = removeRandomGarbage(rnd); if (memReleased < 0) throw tcu::InternalError(string("") + "Given " + objTypeName() + " memory usage limit exceeded, and no unneeded " + objTypeName() + " resources available to release"); memUsage -= memReleased; DE_ASSERT(memUsage == computeApproxMemUsage()); } } } // LongStressCaseInternal using namespace LongStressCaseInternal; static int generateRandomAttribData (vector<deUint8>& attrDataBuf, int& dataSizeBytesDst, const VarSpec& attrSpec, const int numVertices, Random& rnd) { const bool isFloat = glu::isDataTypeFloatOrVec(attrSpec.type); const int numComponents = glu::getDataTypeScalarSize(attrSpec.type); const int componentSize = (int)(isFloat ? sizeof(GLfloat) : sizeof(GLint)); const int offsetInBuf = nextDivisible((int)attrDataBuf.size(), componentSize); // Round up for alignment. DE_STATIC_ASSERT(sizeof(GLint) == sizeof(int)); DE_STATIC_ASSERT(sizeof(GLfloat) == sizeof(float)); dataSizeBytesDst = numComponents*componentSize*numVertices; attrDataBuf.resize(offsetInBuf + dataSizeBytesDst); if (isFloat) { float* const data = (float*)&attrDataBuf[offsetInBuf]; for (int vtxNdx = 0; vtxNdx < numVertices; vtxNdx++) for (int compNdx = 0; compNdx < numComponents; compNdx++) data[vtxNdx*numComponents + compNdx] = rnd.getFloat(attrSpec.minValue.f[compNdx], attrSpec.maxValue.f[compNdx]); } else { DE_ASSERT(glu::isDataTypeIntOrIVec(attrSpec.type)); int* const data = (int*)&attrDataBuf[offsetInBuf]; for (int vtxNdx = 0; vtxNdx < numVertices; vtxNdx++) for (int compNdx = 0; compNdx < numComponents; compNdx++) data[vtxNdx*numComponents + compNdx] = rnd.getInt(attrSpec.minValue.i[compNdx], attrSpec.maxValue.i[compNdx]); } return offsetInBuf; } static int generateRandomPositionAttribData (vector<deUint8>& attrDataBuf, int& dataSizeBytesDst, const VarSpec& attrSpec, const int numVertices, Random& rnd) { DE_ASSERT(glu::isDataTypeFloatOrVec(attrSpec.type)); const int numComponents = glu::getDataTypeScalarSize(attrSpec.type); DE_ASSERT(numComponents >= 2); const int offsetInBuf = generateRandomAttribData(attrDataBuf, dataSizeBytesDst, attrSpec, numVertices, rnd); if (numComponents > 2) { float* const data = (float*)&attrDataBuf[offsetInBuf]; for (int vtxNdx = 0; vtxNdx < numVertices; vtxNdx++) data[vtxNdx*numComponents + 2] = -1.0f; for (int triNdx = 0; triNdx < numVertices-2; triNdx++) { float* const vtxAComps = &data[(triNdx+0)*numComponents]; float* const vtxBComps = &data[(triNdx+1)*numComponents]; float* const vtxCComps = &data[(triNdx+2)*numComponents]; const float triArea = triangleArea(Vec2(vtxAComps[0], vtxAComps[1]), Vec2(vtxBComps[0], vtxBComps[1]), Vec2(vtxCComps[0], vtxCComps[1])); const float t = triArea / (triArea + 1.0f); const float z = (1.0f-t)*attrSpec.minValue.f[2] + t*attrSpec.maxValue.f[2]; vtxAComps[2] = de::max(vtxAComps[2], z); vtxBComps[2] = de::max(vtxBComps[2], z); vtxCComps[2] = de::max(vtxCComps[2], z); } } return offsetInBuf; } static void generateAttribs (vector<deUint8>& attrDataBuf, vector<int>& attrDataOffsets, vector<int>& attrDataSizes, const vector<VarSpec>& attrSpecs, const string& posAttrName, const int numVertices, Random& rnd) { attrDataBuf.clear(); attrDataOffsets.clear(); attrDataSizes.resize(attrSpecs.size()); for (int i = 0; i < (int)attrSpecs.size(); i++) { if (attrSpecs[i].name == posAttrName) attrDataOffsets.push_back(generateRandomPositionAttribData(attrDataBuf, attrDataSizes[i], attrSpecs[i], numVertices, rnd)); else attrDataOffsets.push_back(generateRandomAttribData(attrDataBuf, attrDataSizes[i], attrSpecs[i], numVertices, rnd)); } } LongStressCase::LongStressCase (tcu::TestContext& testCtx, const glu::RenderContext& renderCtx, const char* const name, const char* const desc, const int maxTexMemoryUsageBytes, const int maxBufMemoryUsageBytes, const int numDrawCallsPerIteration, const int numTrianglesPerDrawCall, const vector<ProgramContext>& programContexts, const FeatureProbabilities& probabilities, const deUint32 indexBufferUsage, const deUint32 attrBufferUsage, const int redundantBufferFactor, const bool showDebugInfo) : tcu::TestCase (testCtx, name, desc) , m_renderCtx (renderCtx) , m_maxTexMemoryUsageBytes (maxTexMemoryUsageBytes) , m_maxBufMemoryUsageBytes (maxBufMemoryUsageBytes) , m_numDrawCallsPerIteration (numDrawCallsPerIteration) , m_numTrianglesPerDrawCall (numTrianglesPerDrawCall) , m_numVerticesPerDrawCall (numTrianglesPerDrawCall+2) // \note Triangle strips are used. , m_programContexts (programContexts) , m_probabilities (probabilities) , m_indexBufferUsage (indexBufferUsage) , m_attrBufferUsage (attrBufferUsage) , m_redundantBufferFactor (redundantBufferFactor) , m_showDebugInfo (showDebugInfo) , m_numIterations (getNumIterations(testCtx, 5)) , m_isGLES3 (contextSupports(renderCtx.getType(), glu::ApiType::es(3,0))) , m_currentIteration (0) , m_startTimeSeconds ((deUint64)-1) , m_lastLogTime ((deUint64)-1) , m_lastLogIteration (0) , m_currentLogEntryNdx (0) , m_rnd (deStringHash(getName()) ^ testCtx.getCommandLine().getBaseSeed()) , m_programs (DE_NULL) , m_buffers (DE_NULL) , m_textures (DE_NULL) , m_debugInfoRenderer (DE_NULL) { DE_ASSERT(m_numVerticesPerDrawCall <= (int)std::numeric_limits<deUint16>::max()+1); // \note Vertices are referred to with 16-bit indices. DE_ASSERT(m_redundantBufferFactor > 0); } LongStressCase::~LongStressCase (void) { LongStressCase::deinit(); } void LongStressCase::init (void) { // Generate dummy texture data for each texture spec in m_programContexts. DE_ASSERT(!m_programContexts.empty()); DE_ASSERT(m_programResources.empty()); m_programResources.resize(m_programContexts.size()); for (int progCtxNdx = 0; progCtxNdx < (int)m_programContexts.size(); progCtxNdx++) { const ProgramContext& progCtx = m_programContexts[progCtxNdx]; ProgramResources& progRes = m_programResources[progCtxNdx]; for (int texSpecNdx = 0; texSpecNdx < (int)progCtx.textureSpecs.size(); texSpecNdx++) { const TextureSpec& spec = progCtx.textureSpecs[texSpecNdx]; const TextureFormat format = glu::mapGLTransferFormat(spec.format, spec.dataType); // If texture data with the same format has already been generated, re-use that (don't care much about contents). SharedPtr<TextureLevel> dummyTex; for (int prevProgCtxNdx = 0; prevProgCtxNdx < (int)m_programResources.size(); prevProgCtxNdx++) { const vector<SharedPtr<TextureLevel> >& prevProgCtxTextures = m_programResources[prevProgCtxNdx].dummyTextures; for (int texNdx = 0; texNdx < (int)prevProgCtxTextures.size(); texNdx++) { if (prevProgCtxTextures[texNdx]->getFormat() == format) { dummyTex = prevProgCtxTextures[texNdx]; break; } } } if (!dummyTex) dummyTex = SharedPtr<TextureLevel>(new TextureLevel(format)); if (dummyTex->getWidth() < spec.width || dummyTex->getHeight() < spec.height) { dummyTex->setSize(spec.width, spec.height); tcu::fillWithComponentGradients(dummyTex->getAccess(), spec.minValue, spec.maxValue); } progRes.dummyTextures.push_back(dummyTex); } } m_vertexIndices.clear(); for (int i = 0; i < m_numVerticesPerDrawCall; i++) m_vertexIndices.push_back((deUint16)i); m_rnd.shuffle(m_vertexIndices.begin(), m_vertexIndices.end()); DE_ASSERT(!m_programs && !m_buffers && !m_textures); m_programs = new GLObjectManager<Program>; m_buffers = new GLObjectManager<Buffer>; m_textures = new GLObjectManager<Texture>; m_currentIteration = 0; { TestLog& log = m_testCtx.getLog(); log << TestLog::Message << "Number of iterations: " << (m_numIterations > 0 ? toString(m_numIterations) : "infinite") << TestLog::EndMessage << TestLog::Message << "Number of draw calls per iteration: " << m_numDrawCallsPerIteration << TestLog::EndMessage << TestLog::Message << "Number of triangles per draw call: " << m_numTrianglesPerDrawCall << TestLog::EndMessage << TestLog::Message << "Using triangle strips" << TestLog::EndMessage << TestLog::Message << "Approximate texture memory usage limit: " << de::floatToString((float)m_maxTexMemoryUsageBytes / Mi, 2) << " MiB" << TestLog::EndMessage << TestLog::Message << "Approximate buffer memory usage limit: " << de::floatToString((float)m_maxBufMemoryUsageBytes / Mi, 2) << " MiB" << TestLog::EndMessage << TestLog::Message << "Default vertex attribute data buffer usage parameter: " << glu::getUsageName(m_attrBufferUsage) << TestLog::EndMessage << TestLog::Message << "Default vertex index data buffer usage parameter: " << glu::getUsageName(m_indexBufferUsage) << TestLog::EndMessage << TestLog::Section("ProbabilityParams", "Per-iteration probability parameters") << TestLog::Message << "Program re-build: " << probabilityStr(m_probabilities.rebuildProgram) << TestLog::EndMessage << TestLog::Message << "Texture re-upload: " << probabilityStr(m_probabilities.reuploadTexture) << TestLog::EndMessage << TestLog::Message << "Buffer re-upload: " << probabilityStr(m_probabilities.reuploadBuffer) << TestLog::EndMessage << TestLog::Message << "Use glTexImage* instead of glTexSubImage* when uploading texture: " << probabilityStr(m_probabilities.reuploadWithTexImage) << TestLog::EndMessage << TestLog::Message << "Use glBufferData* instead of glBufferSubData* when uploading buffer: " << probabilityStr(m_probabilities.reuploadWithBufferData) << TestLog::EndMessage << TestLog::Message << "Delete texture after using it, even if could re-use it: " << probabilityStr(m_probabilities.deleteTexture) << TestLog::EndMessage << TestLog::Message << "Delete buffer after using it, even if could re-use it: " << probabilityStr(m_probabilities.deleteBuffer) << TestLog::EndMessage << TestLog::Message << "Don't re-use texture, and only delete if memory limit is hit: " << probabilityStr(m_probabilities.wastefulTextureMemoryUsage) << TestLog::EndMessage << TestLog::Message << "Don't re-use buffer, and only delete if memory limit is hit: " << probabilityStr(m_probabilities.wastefulBufferMemoryUsage) << TestLog::EndMessage << TestLog::Message << "Use client memory (instead of GL buffers) for vertex attribute data: " << probabilityStr(m_probabilities.clientMemoryAttributeData) << TestLog::EndMessage << TestLog::Message << "Use client memory (instead of GL buffers) for vertex index data: " << probabilityStr(m_probabilities.clientMemoryIndexData) << TestLog::EndMessage << TestLog::Message << "Use random target parameter when uploading buffer data: " << probabilityStr(m_probabilities.randomBufferUploadTarget) << TestLog::EndMessage << TestLog::Message << "Use random usage parameter when uploading buffer data: " << probabilityStr(m_probabilities.randomBufferUsage) << TestLog::EndMessage << TestLog::Message << "Use glDrawArrays instead of glDrawElements: " << probabilityStr(m_probabilities.useDrawArrays) << TestLog::EndMessage << TestLog::Message << "Use separate buffers for each attribute, instead of one array for all: " << probabilityStr(m_probabilities.separateAttributeBuffers) << TestLog::EndMessage << TestLog::EndSection << TestLog::Message << "Using " << m_programContexts.size() << " program(s)" << TestLog::EndMessage; bool anyProgramsFailed = false; for (int progCtxNdx = 0; progCtxNdx < (int)m_programContexts.size(); progCtxNdx++) { const ProgramContext& progCtx = m_programContexts[progCtxNdx]; glu::ShaderProgram prog(m_renderCtx, glu::makeVtxFragSources(mangleShaderNames(progCtx.vertexSource, ""), mangleShaderNames(progCtx.fragmentSource, ""))); log << TestLog::Section("ShaderProgram" + toString(progCtxNdx), "Shader program " + toString(progCtxNdx)) << prog << TestLog::EndSection; if (!prog.isOk()) anyProgramsFailed = true; } if (anyProgramsFailed) throw tcu::TestError("One or more shader programs failed to compile"); } DE_ASSERT(!m_debugInfoRenderer); if (m_showDebugInfo) m_debugInfoRenderer = new DebugInfoRenderer(m_renderCtx); } void LongStressCase::deinit (void) { m_programResources.clear(); delete m_programs; m_programs = DE_NULL; delete m_buffers; m_buffers = DE_NULL; delete m_textures; m_textures = DE_NULL; delete m_debugInfoRenderer; m_debugInfoRenderer = DE_NULL; } LongStressCase::IterateResult LongStressCase::iterate (void) { TestLog& log = m_testCtx.getLog(); const int renderWidth = m_renderCtx.getRenderTarget().getWidth(); const int renderHeight = m_renderCtx.getRenderTarget().getHeight(); const bool useClientMemoryIndexData = m_rnd.getFloat() < m_probabilities.clientMemoryIndexData; const bool useDrawArrays = m_rnd.getFloat() < m_probabilities.useDrawArrays; const bool separateAttributeBuffers = m_rnd.getFloat() < m_probabilities.separateAttributeBuffers; const int progContextNdx = m_rnd.getInt(0, (int)m_programContexts.size()-1); const ProgramContext& programContext = m_programContexts[progContextNdx]; ProgramResources& programResources = m_programResources[progContextNdx]; const string programName = "prog" + toString(progContextNdx); const string textureNamePrefix = "tex" + toString(progContextNdx) + "_"; const string unitedAttrBufferNamePrefix = "attrBuf" + toString(progContextNdx) + "_"; const string indexBufferName = "indexBuf" + toString(progContextNdx); const string separateAttrBufNamePrefix = "attrBuf" + toString(progContextNdx) + "_"; if (m_currentIteration == 0) m_lastLogTime = m_startTimeSeconds = deGetTime(); // Make or re-compile programs. { const bool hadProgram = m_programs->has(programName); if (!hadProgram) m_programs->make(programName); Program& prog = m_programs->get(programName); if (!hadProgram || m_rnd.getFloat() < m_probabilities.rebuildProgram) { programResources.shaderNameManglingSuffix = toString((deUint16)deUint64Hash((deUint64)m_currentIteration ^ deGetTime())); prog.setSources(mangleShaderNames(programContext.vertexSource, programResources.shaderNameManglingSuffix), mangleShaderNames(programContext.fragmentSource, programResources.shaderNameManglingSuffix)); prog.build(log); } prog.use(); } Program& program = m_programs->get(programName); // Make or re-upload textures. for (int texNdx = 0; texNdx < (int)programContext.textureSpecs.size(); texNdx++) { const string texName = textureNamePrefix + toString(texNdx); const bool hadTexture = m_textures->has(texName); const TextureSpec& spec = programContext.textureSpecs[texNdx]; if (!hadTexture) m_textures->make(texName, spec.textureType); if (!hadTexture || m_rnd.getFloat() < m_probabilities.reuploadTexture) { Texture& texture = m_textures->get(texName); m_textures->removeGarbageUntilUnder(m_maxTexMemoryUsageBytes - texture.getApproxMemUsageDiff(spec.width, spec.height, spec.internalFormat, spec.useMipmap), m_rnd); if (!hadTexture || m_rnd.getFloat() < m_probabilities.reuploadWithTexImage) texture.setData(programResources.dummyTextures[texNdx]->getAccess(), spec.width, spec.height, spec.internalFormat, spec.useMipmap); else texture.setSubData(programResources.dummyTextures[texNdx]->getAccess(), 0, 0, spec.width, spec.height); texture.toUnit(0); texture.setWrap(spec.sWrap, spec.tWrap); texture.setFilter(spec.minFilter, spec.magFilter); } } // Bind textures to units, in random order (because when multiple texture specs have same unit, we want to pick one randomly). { vector<int> texSpecIndices(programContext.textureSpecs.size()); for (int i = 0; i < (int)texSpecIndices.size(); i++) texSpecIndices[i] = i; m_rnd.shuffle(texSpecIndices.begin(), texSpecIndices.end()); for (int i = 0; i < (int)texSpecIndices.size(); i++) m_textures->get(textureNamePrefix + toString(texSpecIndices[i])).toUnit(programContext.textureSpecs[i].textureUnit); } // Make or re-upload index buffer. if (!useDrawArrays) { m_rnd.shuffle(m_vertexIndices.begin(), m_vertexIndices.end()); if (!useClientMemoryIndexData) { const bool hadIndexBuffer = m_buffers->has(indexBufferName); if (!hadIndexBuffer) m_buffers->make(indexBufferName); Buffer& indexBuf = m_buffers->get(indexBufferName); if (!hadIndexBuffer || m_rnd.getFloat() < m_probabilities.reuploadBuffer) { m_buffers->removeGarbageUntilUnder(m_maxBufMemoryUsageBytes - indexBuf.getApproxMemUsageDiff(m_vertexIndices), m_rnd); const deUint32 target = m_rnd.getFloat() < m_probabilities.randomBufferUploadTarget ? randomBufferTarget(m_rnd, m_isGLES3) : GL_ELEMENT_ARRAY_BUFFER; if (!hadIndexBuffer || m_rnd.getFloat() < m_probabilities.reuploadWithBufferData) indexBuf.setData(m_vertexIndices, target, m_rnd.getFloat() < m_probabilities.randomBufferUsage ? randomBufferUsage(m_rnd, m_isGLES3) : m_indexBufferUsage); else indexBuf.setSubData(m_vertexIndices, 0, m_numVerticesPerDrawCall, target); } } } // Set vertex attributes. If not using client-memory data, make or re-upload attribute buffers. generateAttribs(programResources.attrDataBuf, programResources.attrDataOffsets, programResources.attrDataSizes, programContext.attributes, programContext.positionAttrName, m_numVerticesPerDrawCall, m_rnd); if (!(m_rnd.getFloat() < m_probabilities.clientMemoryAttributeData)) { if (separateAttributeBuffers) { for (int attrNdx = 0; attrNdx < (int)programContext.attributes.size(); attrNdx++) { const int usedRedundantBufferNdx = m_rnd.getInt(0, m_redundantBufferFactor-1); for (int redundantBufferNdx = 0; redundantBufferNdx < m_redundantBufferFactor; redundantBufferNdx++) { const string curAttrBufName = separateAttrBufNamePrefix + toString(attrNdx) + "_" + toString(redundantBufferNdx); const bool hadCurAttrBuffer = m_buffers->has(curAttrBufName); if (!hadCurAttrBuffer) m_buffers->make(curAttrBufName); Buffer& curAttrBuf = m_buffers->get(curAttrBufName); if (!hadCurAttrBuffer || m_rnd.getFloat() < m_probabilities.reuploadBuffer) { m_buffers->removeGarbageUntilUnder(m_maxBufMemoryUsageBytes - curAttrBuf.getApproxMemUsageDiff(programResources.attrDataSizes[attrNdx]), m_rnd); const deUint32 target = m_rnd.getFloat() < m_probabilities.randomBufferUploadTarget ? randomBufferTarget(m_rnd, m_isGLES3) : GL_ARRAY_BUFFER; if (!hadCurAttrBuffer || m_rnd.getFloat() < m_probabilities.reuploadWithBufferData) curAttrBuf.setData(&programResources.attrDataBuf[programResources.attrDataOffsets[attrNdx]], programResources.attrDataSizes[attrNdx], target, m_rnd.getFloat() < m_probabilities.randomBufferUsage ? randomBufferUsage(m_rnd, m_isGLES3) : m_attrBufferUsage); else curAttrBuf.setSubData(&programResources.attrDataBuf[programResources.attrDataOffsets[attrNdx]], 0, programResources.attrDataSizes[attrNdx], target); } if (redundantBufferNdx == usedRedundantBufferNdx) program.setAttribute(curAttrBuf, 0, programContext.attributes[attrNdx], programResources.shaderNameManglingSuffix); } } } else { const int usedRedundantBufferNdx = m_rnd.getInt(0, m_redundantBufferFactor-1); for (int redundantBufferNdx = 0; redundantBufferNdx < m_redundantBufferFactor; redundantBufferNdx++) { const string attrBufName = unitedAttrBufferNamePrefix + toString(redundantBufferNdx); const bool hadAttrBuffer = m_buffers->has(attrBufName); if (!hadAttrBuffer) m_buffers->make(attrBufName); Buffer& attrBuf = m_buffers->get(attrBufName); if (!hadAttrBuffer || m_rnd.getFloat() < m_probabilities.reuploadBuffer) { m_buffers->removeGarbageUntilUnder(m_maxBufMemoryUsageBytes - attrBuf.getApproxMemUsageDiff(programResources.attrDataBuf), m_rnd); const deUint32 target = m_rnd.getFloat() < m_probabilities.randomBufferUploadTarget ? randomBufferTarget(m_rnd, m_isGLES3) : GL_ARRAY_BUFFER; if (!hadAttrBuffer || m_rnd.getFloat() < m_probabilities.reuploadWithBufferData) attrBuf.setData(programResources.attrDataBuf, target, m_rnd.getFloat() < m_probabilities.randomBufferUsage ? randomBufferUsage(m_rnd, m_isGLES3) : m_attrBufferUsage); else attrBuf.setSubData(programResources.attrDataBuf, 0, (int)programResources.attrDataBuf.size(), target); } if (redundantBufferNdx == usedRedundantBufferNdx) { for (int i = 0; i < (int)programContext.attributes.size(); i++) program.setAttribute(attrBuf, programResources.attrDataOffsets[i], programContext.attributes[i], programResources.shaderNameManglingSuffix); } } } } else { for (int i = 0; i < (int)programContext.attributes.size(); i++) program.setAttributeClientMem(&programResources.attrDataBuf[programResources.attrDataOffsets[i]], programContext.attributes[i], programResources.shaderNameManglingSuffix); } // Draw. glViewport(0, 0, renderWidth, renderHeight); glClearDepthf(1.0f); glClear(GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); for (int i = 0; i < m_numDrawCallsPerIteration; i++) { program.use(); program.setRandomUniforms(programContext.uniforms, programResources.shaderNameManglingSuffix, m_rnd); if (useDrawArrays) glDrawArrays(GL_TRIANGLE_STRIP, 0, m_numVerticesPerDrawCall); else { if (useClientMemoryIndexData) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glDrawElements(GL_TRIANGLE_STRIP, m_numVerticesPerDrawCall, GL_UNSIGNED_SHORT, &m_vertexIndices[0]); } else { m_buffers->get(indexBufferName).bind(GL_ELEMENT_ARRAY_BUFFER); glDrawElements(GL_TRIANGLE_STRIP, m_numVerticesPerDrawCall, GL_UNSIGNED_SHORT, DE_NULL); } } } for(int i = 0; i < (int)programContext.attributes.size(); i++) program.disableAttributeArray(programContext.attributes[i], programResources.shaderNameManglingSuffix); if (m_showDebugInfo) m_debugInfoRenderer->drawInfo(deGetTime()-m_startTimeSeconds, m_textures->computeApproxMemUsage(), m_maxTexMemoryUsageBytes, m_buffers->computeApproxMemUsage(), m_maxBufMemoryUsageBytes, m_currentIteration); if (m_currentIteration > 0) { // Log if a certain amount of time has passed since last log entry (or if this is the last iteration). const deUint64 loggingIntervalSeconds = 10; const deUint64 time = deGetTime(); const deUint64 timeDiff = time - m_lastLogTime; const int iterDiff = m_currentIteration - m_lastLogIteration; if (timeDiff >= loggingIntervalSeconds || m_currentIteration == m_numIterations-1) { log << TestLog::Section("LogEntry" + toString(m_currentLogEntryNdx), "Log entry " + toString(m_currentLogEntryNdx)) << TestLog::Message << "Time elapsed: " << getTimeStr(time - m_startTimeSeconds) << TestLog::EndMessage << TestLog::Message << "Frame number: " << m_currentIteration << TestLog::EndMessage << TestLog::Message << "Time since last log entry: " << timeDiff << "s" << TestLog::EndMessage << TestLog::Message << "Frames since last log entry: " << iterDiff << TestLog::EndMessage << TestLog::Message << "Average frame time since last log entry: " << de::floatToString((float)timeDiff / (float)iterDiff, 2) << "s" << TestLog::EndMessage << TestLog::Message << "Approximate texture memory usage: " << de::floatToString((float)m_textures->computeApproxMemUsage() / Mi, 2) << " MiB / " << de::floatToString((float)m_maxTexMemoryUsageBytes / Mi, 2) << " MiB" << TestLog::EndMessage << TestLog::Message << "Approximate buffer memory usage: " << de::floatToString((float)m_buffers->computeApproxMemUsage() / Mi, 2) << " MiB / " << de::floatToString((float)m_maxBufMemoryUsageBytes / Mi, 2) << " MiB" << TestLog::EndMessage << TestLog::EndSection; m_lastLogTime = time; m_lastLogIteration = m_currentIteration; m_currentLogEntryNdx++; } } // Possibly remove or set-as-garbage some objects, depending on given probabilities. for (int texNdx = 0; texNdx < (int)programContext.textureSpecs.size(); texNdx++) { const string texName = textureNamePrefix + toString(texNdx); if (m_rnd.getFloat() < m_probabilities.deleteTexture) m_textures->remove(texName); else if (m_rnd.getFloat() < m_probabilities.wastefulTextureMemoryUsage) m_textures->markAsGarbage(texName); } if (m_buffers->has(indexBufferName)) { if (m_rnd.getFloat() < m_probabilities.deleteBuffer) m_buffers->remove(indexBufferName); else if (m_rnd.getFloat() < m_probabilities.wastefulBufferMemoryUsage) m_buffers->markAsGarbage(indexBufferName); } if (separateAttributeBuffers) { for (int attrNdx = 0; attrNdx < (int)programContext.attributes.size(); attrNdx++) { const string curAttrBufNamePrefix = separateAttrBufNamePrefix + toString(attrNdx) + "_"; if (m_buffers->has(curAttrBufNamePrefix + "0")) { if (m_rnd.getFloat() < m_probabilities.deleteBuffer) { for (int i = 0; i < m_redundantBufferFactor; i++) m_buffers->remove(curAttrBufNamePrefix + toString(i)); } else if (m_rnd.getFloat() < m_probabilities.wastefulBufferMemoryUsage) { for (int i = 0; i < m_redundantBufferFactor; i++) m_buffers->markAsGarbage(curAttrBufNamePrefix + toString(i)); } } } } else { if (m_buffers->has(unitedAttrBufferNamePrefix + "0")) { if (m_rnd.getFloat() < m_probabilities.deleteBuffer) { for (int i = 0; i < m_redundantBufferFactor; i++) m_buffers->remove(unitedAttrBufferNamePrefix + toString(i)); } else if (m_rnd.getFloat() < m_probabilities.wastefulBufferMemoryUsage) { for (int i = 0; i < m_redundantBufferFactor; i++) m_buffers->markAsGarbage(unitedAttrBufferNamePrefix + toString(i)); } } } GLU_CHECK_MSG("End of LongStressCase::iterate()"); m_currentIteration++; if (m_currentIteration == m_numIterations) { m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Passed"); return STOP; } else return CONTINUE; } } // gls } // deqp