/*------------------------------------------------------------------------- * drawElements Quality Program EGL 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 Memory object allocation stress tests *//*--------------------------------------------------------------------*/ #include "teglMemoryStressTests.hpp" #include "tcuTestLog.hpp" #include "tcuCommandLine.hpp" #include "deRandom.hpp" #include "deClock.h" #include "deString.h" #include "gluDefs.hpp" #include "glwFunctions.hpp" #include "glwDefs.hpp" #include "glwEnums.hpp" #include "egluUtil.hpp" #include "eglwLibrary.hpp" #include "eglwEnums.hpp" #include <vector> #include <string> using std::vector; using std::string; using tcu::TestLog; using namespace eglw; namespace deqp { namespace egl { namespace { enum ObjectType { OBJECTTYPE_PBUFFER = (1<<0), OBJECTTYPE_CONTEXT = (1<<1), // OBJECTTYPE_WINDOW, // OBJECTTYPE_PIXMAP, }; class MemoryAllocator { public: MemoryAllocator (EglTestContext& eglTestCtx, EGLDisplay display, EGLConfig config, int seed, ObjectType types, int minWidth, int minHeight, int maxWidth, int maxHeight, bool use); ~MemoryAllocator (void); bool allocateUntilFailure (void); int getAllocationCount (void) const { return (int)(m_pbuffers.size() + m_contexts.size()); } int getContextCount (void) const { return (int)m_contexts.size(); } int getPBufferCount (void) const { return (int)m_pbuffers.size(); } const string& getErrorString (void) const { return m_errorString; } private: void allocatePBuffer (void); void allocateContext (void); EglTestContext& m_eglTestCtx; EGLDisplay m_display; EGLConfig m_config; glw::Functions m_gl; de::Random m_rnd; bool m_failed; string m_errorString; ObjectType m_types; int m_minWidth; int m_minHeight; int m_maxWidth; int m_maxHeight; bool m_use; vector<EGLSurface> m_pbuffers; vector<EGLContext> m_contexts; }; MemoryAllocator::MemoryAllocator (EglTestContext& eglTestCtx, EGLDisplay display, EGLConfig config, int seed, ObjectType types, int minWidth, int minHeight, int maxWidth, int maxHeight, bool use) : m_eglTestCtx (eglTestCtx) , m_display (display) , m_config (config) , m_rnd (seed) , m_failed (false) , m_types (types) , m_minWidth (minWidth) , m_minHeight (minHeight) , m_maxWidth (maxWidth) , m_maxHeight (maxHeight) , m_use (use) { m_eglTestCtx.initGLFunctions(&m_gl, glu::ApiType::es(2,0)); } MemoryAllocator::~MemoryAllocator (void) { const Library& egl = m_eglTestCtx.getLibrary(); for (vector<EGLSurface>::const_iterator iter = m_pbuffers.begin(); iter != m_pbuffers.end(); ++iter) egl.destroySurface(m_display, *iter); m_pbuffers.clear(); for (vector<EGLContext>::const_iterator iter = m_contexts.begin(); iter != m_contexts.end(); ++iter) egl.destroyContext(m_display, *iter); m_contexts.clear(); } bool MemoryAllocator::allocateUntilFailure (void) { const deUint64 timeLimitUs = 10000000; // 10s deUint64 beginTimeUs = deGetMicroseconds(); vector<ObjectType> types; if ((m_types & OBJECTTYPE_CONTEXT) != 0) types.push_back(OBJECTTYPE_CONTEXT); if ((m_types & OBJECTTYPE_PBUFFER) != 0) types.push_back(OBJECTTYPE_PBUFFER); // If objects should be used. Create one of both at beginning to allow using them. if (m_contexts.size() == 0 && m_pbuffers.size() == 0 && m_use) { allocateContext(); allocatePBuffer(); } while (!m_failed) { ObjectType type = m_rnd.choose<ObjectType>(types.begin(), types.end()); switch (type) { case OBJECTTYPE_PBUFFER: allocatePBuffer(); break; case OBJECTTYPE_CONTEXT: allocateContext(); break; default: DE_ASSERT(false); } if (deGetMicroseconds() - beginTimeUs > timeLimitUs) return true; } return false; } void MemoryAllocator::allocatePBuffer (void) { // Reserve space for new allocations try { m_pbuffers.reserve(m_pbuffers.size() + 1); } catch (const std::bad_alloc&) { m_errorString = "std::bad_alloc when allocating more space for testcase. Out of host memory."; m_failed = true; return; } // Allocate pbuffer try { const Library& egl = m_eglTestCtx.getLibrary(); const EGLint width = m_rnd.getInt(m_minWidth, m_maxWidth); const EGLint height = m_rnd.getInt(m_minHeight, m_maxHeight); const EGLint attribList[] = { EGL_WIDTH, width, EGL_HEIGHT, height, EGL_NONE }; EGLSurface surface = egl.createPbufferSurface(m_display, m_config, attribList); EGLU_CHECK_MSG(egl, "eglCreatePbufferSurface"); DE_ASSERT(surface != EGL_NO_SURFACE); m_pbuffers.push_back(surface); if (m_use && m_contexts.size() > 0) { EGLContext context = m_rnd.choose<EGLContext>(m_contexts.begin(), m_contexts.end()); const float red = m_rnd.getFloat(); const float green = m_rnd.getFloat(); const float blue = m_rnd.getFloat(); const float alpha = m_rnd.getFloat(); EGLU_CHECK_CALL(egl, makeCurrent(m_display, surface, surface, context)); m_gl.clearColor(red, green, blue, alpha); GLU_EXPECT_NO_ERROR(m_gl.getError(), "glClearColor()"); m_gl.clear(GL_COLOR_BUFFER_BIT); GLU_EXPECT_NO_ERROR(m_gl.getError(), "glClear()"); EGLU_CHECK_CALL(egl, makeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); } } catch (const eglu::Error& error) { if (error.getError() == EGL_BAD_ALLOC) { m_errorString = "eglCreatePbufferSurface returned EGL_BAD_ALLOC"; m_failed = true; return; } else throw; } } void MemoryAllocator::allocateContext (void) { // Reserve space for new allocations try { m_contexts.reserve(m_contexts.size() + 1); } catch (const std::bad_alloc&) { m_errorString = "std::bad_alloc when allocating more space for testcase. Out of host memory."; m_failed = true; return; } // Allocate context try { const Library& egl = m_eglTestCtx.getLibrary(); const EGLint attribList[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; EGLU_CHECK_CALL(egl, bindAPI(EGL_OPENGL_ES_API)); EGLContext context = egl.createContext(m_display, m_config, EGL_NO_CONTEXT, attribList); EGLU_CHECK_MSG(egl, "eglCreateContext"); DE_ASSERT(context != EGL_NO_CONTEXT); m_contexts.push_back(context); if (m_use && m_pbuffers.size() > 0) { EGLSurface surface = m_rnd.choose<EGLSurface>(m_pbuffers.begin(), m_pbuffers.end()); const float red = m_rnd.getFloat(); const float green = m_rnd.getFloat(); const float blue = m_rnd.getFloat(); const float alpha = m_rnd.getFloat(); EGLU_CHECK_CALL(egl, makeCurrent(m_display, surface, surface, context)); m_gl.clearColor(red, green, blue, alpha); GLU_EXPECT_NO_ERROR(m_gl.getError(), "glClearColor()"); m_gl.clear(GL_COLOR_BUFFER_BIT); GLU_EXPECT_NO_ERROR(m_gl.getError(), "glClear()"); EGLU_CHECK_CALL(egl, makeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); } } catch (const eglu::Error& error) { if (error.getError() == EGL_BAD_ALLOC) { m_errorString = "eglCreateContext returned EGL_BAD_ALLOC"; m_failed = true; return; } else throw; } } } // anonymous class MemoryStressCase : public TestCase { public: struct Spec { ObjectType types; int minWidth; int minHeight; int maxWidth; int maxHeight; bool use; }; MemoryStressCase (EglTestContext& eglTestCtx, Spec spec, const char* name, const char* description); void init (void); void deinit (void); IterateResult iterate (void); private: Spec m_spec; vector<int> m_allocationCounts; MemoryAllocator* m_allocator; int m_iteration; int m_iterationCount; int m_seed; EGLDisplay m_display; EGLConfig m_config; }; MemoryStressCase::MemoryStressCase (EglTestContext& eglTestCtx, Spec spec, const char* name, const char* description) : TestCase (eglTestCtx, name, description) , m_spec (spec) , m_allocator (NULL) , m_iteration (0) , m_iterationCount (10) , m_seed (deStringHash(name)) , m_display (EGL_NO_DISPLAY) , m_config (DE_NULL) { } void MemoryStressCase::init (void) { const Library& egl = m_eglTestCtx.getLibrary(); EGLint configCount = 0; const EGLint attribList[] = { EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE }; if (!m_testCtx.getCommandLine().isOutOfMemoryTestEnabled()) { m_testCtx.getLog() << TestLog::Message << "Tests that exhaust memory are disabled, use --deqp-test-oom=enable command line option to enable." << TestLog::EndMessage; throw tcu::NotSupportedError("OOM tests disabled"); } m_display = eglu::getAndInitDisplay(m_eglTestCtx.getNativeDisplay()); EGLU_CHECK_CALL(egl, chooseConfig(m_display, attribList, &m_config, 1, &configCount)); TCU_CHECK(configCount != 0); } void MemoryStressCase::deinit (void) { delete m_allocator; m_allocator = DE_NULL; if (m_display != EGL_NO_DISPLAY) { m_eglTestCtx.getLibrary().terminate(m_display); m_display = EGL_NO_DISPLAY; } } TestCase::IterateResult MemoryStressCase::iterate (void) { TestLog& log = m_testCtx.getLog(); if (m_iteration < m_iterationCount) { try { if (!m_allocator) m_allocator = new MemoryAllocator(m_eglTestCtx, m_display, m_config, m_seed, m_spec.types, m_spec.minWidth, m_spec.minHeight, m_spec.maxWidth, m_spec.maxHeight, m_spec.use); if (m_allocator->allocateUntilFailure()) { log << TestLog::Message << "Couldn't exhaust memory before timeout. Allocated " << m_allocator->getAllocationCount() << " objects." << TestLog::EndMessage; m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); delete m_allocator; m_allocator = NULL; return STOP; } log << TestLog::Message << "Iteration " << m_iteration << ": Allocated " << m_allocator->getAllocationCount() << " objects; " << m_allocator->getContextCount() << " contexts, " << m_allocator->getPBufferCount() << " PBuffers." << TestLog::EndMessage; log << TestLog::Message << "Got expected error: " << m_allocator->getErrorString() << TestLog::EndMessage; m_allocationCounts.push_back(m_allocator->getAllocationCount()); delete m_allocator; m_allocator = NULL; m_iteration++; return CONTINUE; } catch (...) { log << TestLog::Message << "Iteration " << m_iteration << ": Allocated " << m_allocator->getAllocationCount() << " objects; " << m_allocator->getContextCount() << " contexts, " << m_allocator->getPBufferCount() << " PBuffers." << TestLog::EndMessage; log << TestLog::Message << "Unexpected error" << TestLog::EndMessage; throw; } } else { // Analyze number of passed allocations. int min = m_allocationCounts[0]; int max = m_allocationCounts[0]; float threshold = 50.0f; for (int allocNdx = 0; allocNdx < (int)m_allocationCounts.size(); allocNdx++) { min = deMin32(m_allocationCounts[allocNdx], min); max = deMax32(m_allocationCounts[allocNdx], max); } if (min == 0 && max != 0) { log << TestLog::Message << "Allocation count zero" << TestLog::EndMessage; m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail"); } else { float change = (float)(min - max) / ((float)(max)); if (change > threshold) { log << TestLog::Message << "Allocated objects max: " << max << ", min: " << min << ", difference: " << change << "% threshold: " << threshold << "%" << TestLog::EndMessage; m_testCtx.setTestResult(QP_TEST_RESULT_QUALITY_WARNING, "Allocation count variation"); } else m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); } return STOP; } } MemoryStressTests::MemoryStressTests (EglTestContext& eglTestCtx) : TestCaseGroup(eglTestCtx, "memory", "Memory allocation stress tests") { } void MemoryStressTests::init (void) { // Check small pbuffers 256x256 { MemoryStressCase::Spec spec; spec.types = OBJECTTYPE_PBUFFER; spec.minWidth = 256; spec.minHeight = 256; spec.maxWidth = 256; spec.maxHeight = 256; spec.use = false; addChild(new MemoryStressCase(m_eglTestCtx, spec, "pbuffer_256x256", "PBuffer allocation stress tests")); } // Check small pbuffers 256x256 and use them { MemoryStressCase::Spec spec; spec.types = OBJECTTYPE_PBUFFER; spec.minWidth = 256; spec.minHeight = 256; spec.maxWidth = 256; spec.maxHeight = 256; spec.use = true; addChild(new MemoryStressCase(m_eglTestCtx, spec, "pbuffer_256x256_use", "PBuffer allocation stress tests")); } // Check big pbuffers 1024x1024 { MemoryStressCase::Spec spec; spec.types = OBJECTTYPE_PBUFFER; spec.minWidth = 1024; spec.minHeight = 1024; spec.maxWidth = 1024; spec.maxHeight = 1024; spec.use = false; addChild(new MemoryStressCase(m_eglTestCtx, spec, "pbuffer_1024x1024", "PBuffer allocation stress tests")); } // Check big pbuffers 1024x1024 and use them { MemoryStressCase::Spec spec; spec.types = OBJECTTYPE_PBUFFER; spec.minWidth = 1024; spec.minHeight = 1024; spec.maxWidth = 1024; spec.maxHeight = 1024; spec.use = true; addChild(new MemoryStressCase(m_eglTestCtx, spec, "pbuffer_1024x1024_use", "PBuffer allocation stress tests")); } // Check different sized pbuffers { MemoryStressCase::Spec spec; spec.types = OBJECTTYPE_PBUFFER; spec.minWidth = 64; spec.minHeight = 64; spec.maxWidth = 1024; spec.maxHeight = 1024; spec.use = false; addChild(new MemoryStressCase(m_eglTestCtx, spec, "pbuffer", "PBuffer allocation stress tests")); } // Check different sized pbuffers and use them { MemoryStressCase::Spec spec; spec.types = OBJECTTYPE_PBUFFER; spec.minWidth = 64; spec.minHeight = 64; spec.maxWidth = 1024; spec.maxHeight = 1024; spec.use = true; addChild(new MemoryStressCase(m_eglTestCtx, spec, "pbuffer_use", "PBuffer allocation stress tests")); } // Check contexts { MemoryStressCase::Spec spec; spec.types = OBJECTTYPE_CONTEXT; spec.minWidth = 1024; spec.minHeight = 1024; spec.maxWidth = 1024; spec.maxHeight = 1024; spec.use = false; addChild(new MemoryStressCase(m_eglTestCtx, spec, "context", "Context allocation stress tests")); } // Check contexts and use them { MemoryStressCase::Spec spec; spec.types = OBJECTTYPE_CONTEXT; spec.minWidth = 1024; spec.minHeight = 1024; spec.maxWidth = 1024; spec.maxHeight = 1024; spec.use = true; addChild(new MemoryStressCase(m_eglTestCtx, spec, "context_use", "Context allocation stress tests")); } // Check contexts and pbuffers { MemoryStressCase::Spec spec; spec.types = (ObjectType)(OBJECTTYPE_PBUFFER|OBJECTTYPE_CONTEXT); spec.minWidth = 64; spec.minHeight = 64; spec.maxWidth = 1024; spec.maxHeight = 1024; spec.use = false; addChild(new MemoryStressCase(m_eglTestCtx, spec, "pbuffer_context", "PBuffer and context allocation stress tests")); } // Check contexts and pbuffers and use { MemoryStressCase::Spec spec; spec.types = (ObjectType)(OBJECTTYPE_PBUFFER|OBJECTTYPE_CONTEXT); spec.minWidth = 64; spec.minHeight = 64; spec.maxWidth = 1024; spec.maxHeight = 1024; spec.use = true; addChild(new MemoryStressCase(m_eglTestCtx, spec, "pbuffer_context_use", "PBuffer and context allocation stress tests")); } } } // egl } // deqp