| /*------------------------------------------------------------------------- |
| * 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::string; |
| using std::vector; |
| 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 uint64_t timeLimitUs = 10000000; // 10s |
| uint64_t 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; |
| } |
| } |
| |
| } // namespace |
| |
| 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(nullptr) |
| { |
| } |
| |
| 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 = nullptr; |
| |
| 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")); |
| } |
| } |
| |
| } // namespace egl |
| } // namespace deqp |