/*------------------------------------------------------------------------- * 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 #include #include #include #include using de::Random; using de::SharedPtr; using de::toString; using tcu::ConstPixelBufferAccess; using tcu::CubeFace; using tcu::IVec2; using tcu::IVec3; using tcu::IVec4; using tcu::TestLog; using tcu::TextureFormat; using tcu::TextureLevel; using tcu::Vec2; using tcu::Vec3; using tcu::Vec4; using std::map; using std::string; using std::vector; 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 uint32_t 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 uint32_t bufferUsagesGLES2[] = {GL_STATIC_DRAW, GL_DYNAMIC_DRAW, GL_STREAM_DRAW}; static const uint32_t 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 uint32_t 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 m; m["NS"] = manglingSuffix; return tcu::StringTemplate(source.c_str()).specialize(m); } template static inline T randomChoose(Random &rnd, const T (&arr)[N]) { return rnd.choose(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 uint64_t seconds) { const uint64_t m = seconds / 60; const uint64_t h = m / 60; const uint64_t 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 uint32_t randomBufferTarget(Random &rnd, const bool isGLES3) { return isGLES3 ? randomChoose(rnd, bufferTargets) : randomChoose(rnd, bufferTargetsGLES2); } static inline uint32_t randomBufferUsage(Random &rnd, const bool isGLES3) { return isGLES3 ? randomChoose(rnd, bufferUsages) : randomChoose(rnd, bufferUsagesGLES2); } static inline uint32_t 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 uint32_t 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 uint32_t shaderGL) { glCompileShader(shaderGL); int success = GL_FALSE; glGetShaderiv(shaderGL, GL_COMPILE_STATUS, &success); return success == GL_TRUE; } static inline bool linkProgram(const uint32_t programGL) { glLinkProgram(programGL); int success = GL_FALSE; glGetProgramiv(programGL, GL_LINK_STATUS, &success); return success == GL_TRUE; } static inline string getShaderInfoLog(const uint32_t shaderGL) { int infoLogLen = 0; vector 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 uint32_t programGL) { int infoLogLen = 0; vector 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(uint64_t 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 m_posBuf; vector m_ndxBuf; }; void DebugInfoRenderer::drawInfo(const uint64_t secondsElapsed, const int texMem, const int maxTexMem, const int bufMem, const int maxBufMem, const int iterNdx) { const uint64_t m = secondsElapsed / 60; const uint64_t h = m / 60; const uint64_t 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(uint16_t(vtxNdx + 0)); m_ndxBuf.push_back(uint16_t(vtxNdx + 1)); m_ndxBuf.push_back(uint16_t(vtxNdx + 2)); m_ndxBuf.push_back(uint16_t(vtxNdx + 2)); m_ndxBuf.push_back(uint16_t(vtxNdx + 1)); m_ndxBuf.push_back(uint16_t(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, uint32_t 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(uint32_t min, uint32_t mag) const; void setWrap(uint32_t s, uint32_t t) const; int getApproxMemUsage(void) const { return m_dataSizeApprox; } int getApproxMemUsageDiff(int width, int height, uint32_t internalFormat, bool useMipmap) const; private: Texture(const Texture &); // Not allowed. Texture &operator=(const Texture &); // Not allowed. static uint32_t genTexture(void) { uint32_t tex = 0; glGenTextures(1, &tex); return tex; } uint32_t 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 uint32_t m_textureGL; int m_numMipLevels; uint32_t 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 uint32_t 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 uint32_t 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 uint32_t min, const uint32_t mag) const { glBindTexture(getGLBindTarget(), m_textureGL); glTexParameteri(getGLBindTarget(), GL_TEXTURE_MIN_FILTER, min); glTexParameteri(getGLBindTarget(), GL_TEXTURE_MAG_FILTER, mag); } void Texture::setWrap(const uint32_t s, const uint32_t 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 void setData(const vector &src, const uint32_t target, const uint32_t usage) { setData(&src[0], (int)(src.size() * sizeof(T)), target, usage); } void setData(const void *src, int size, uint32_t target, uint32_t usage); // Functions that don't change the value returned by getApproxMemUsage(). template void setSubData(const vector &src, const int offsetElems, const int numElems, const uint32_t target) { setSubData(&src[offsetElems], offsetElems * (int)sizeof(T), numElems * (int)sizeof(T), target); } void setSubData(const void *src, int offsetBytes, int sizeBytes, uint32_t target) const; void bind(const uint32_t target) const { glBindBuffer(target, m_bufferGL); } int getApproxMemUsage(void) const { return m_dataSizeApprox; } template int getApproxMemUsageDiff(const vector &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 uint32_t genBuffer(void) { uint32_t buf = 0; glGenBuffers(1, &buf); return buf; } const uint32_t 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 uint32_t target, const uint32_t 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 uint32_t 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 &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 uint32_t m_vertShaderGL; const uint32_t m_fragShaderGL; const uint32_t 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 &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)) { uint32_t val[4]; for (int i = 0; i < typeScalarSize; i++) { DE_ASSERT(spec.minValue.i[i] >= 0 && spec.maxValue.i[i] >= 0); val[i] = (uint32_t)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 *)(intptr_t)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 class GLObjectManager { public: void make(const string &name) { DE_ASSERT(!has(name)); m_objects[name] = SharedPtr(new T); } void make(const string &name, gls::TextureType texType) { DE_ASSERT(!has(name)); m_objects[name] = SharedPtr(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(((const GLObjectManager *)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> m_objects; vector> m_garbageObjects; }; template <> const char *GLObjectManager::objTypeName(void) { return "buffer"; } template <> const char *GLObjectManager::objTypeName(void) { return "texture"; } template <> const char *GLObjectManager::objTypeName(void) { return "program"; } template const T &GLObjectManager::get(const string &name) const { const typename map>::const_iterator it = m_objects.find(name); DE_ASSERT(it != m_objects.end()); return *it->second; } template int GLObjectManager::computeApproxMemUsage(void) const { int result = 0; for (typename map>::const_iterator it = m_objects.begin(); it != m_objects.end(); ++it) result += it->second->getApproxMemUsage(); for (typename vector>::const_iterator it = m_garbageObjects.begin(); it != m_garbageObjects.end(); ++it) result += (*it)->getApproxMemUsage(); return result; } template void GLObjectManager::markAsGarbage(const string &name) { const typename map>::iterator it = m_objects.find(name); DE_ASSERT(it != m_objects.end()); m_garbageObjects.push_back(it->second); m_objects.erase(it); } template int GLObjectManager::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 void GLObjectManager::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()); } } } // namespace LongStressCaseInternal using namespace LongStressCaseInternal; static int generateRandomAttribData(vector &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 &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 &attrDataBuf, vector &attrDataOffsets, vector &attrDataSizes, const vector &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 &programContexts, const FeatureProbabilities &probabilities, const uint32_t indexBufferUsage, const uint32_t 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((uint64_t)-1) , m_lastLogTime((uint64_t)-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::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 unused 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 unusedTex; for (int prevProgCtxNdx = 0; prevProgCtxNdx < (int)m_programResources.size(); prevProgCtxNdx++) { const vector> &prevProgCtxTextures = m_programResources[prevProgCtxNdx].unusedTextures; for (int texNdx = 0; texNdx < (int)prevProgCtxTextures.size(); texNdx++) { if (prevProgCtxTextures[texNdx]->getFormat() == format) { unusedTex = prevProgCtxTextures[texNdx]; break; } } } if (!unusedTex) unusedTex = SharedPtr(new TextureLevel(format)); if (unusedTex->getWidth() < spec.width || unusedTex->getHeight() < spec.height) { unusedTex->setSize(spec.width, spec.height); tcu::fillWithComponentGradients(unusedTex->getAccess(), spec.minValue, spec.maxValue); } progRes.unusedTextures.push_back(unusedTex); } } m_vertexIndices.clear(); for (int i = 0; i < m_numVerticesPerDrawCall; i++) m_vertexIndices.push_back((uint16_t)i); m_rnd.shuffle(m_vertexIndices.begin(), m_vertexIndices.end()); DE_ASSERT(!m_programs && !m_buffers && !m_textures); m_programs = new GLObjectManager; m_buffers = new GLObjectManager; m_textures = new GLObjectManager; 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((uint16_t)deUint64Hash((uint64_t)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.unusedTextures[texNdx]->getAccess(), spec.width, spec.height, spec.internalFormat, spec.useMipmap); else texture.setSubData(programResources.unusedTextures[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 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 uint32_t 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 uint32_t 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 uint32_t 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 uint64_t loggingIntervalSeconds = 10; const uint64_t time = deGetTime(); const uint64_t 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; } } // namespace gls } // namespace deqp