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