blob: 821c278d87adaa571456a9cc621acd2f9a0cbcea [file] [log] [blame]
/*
* Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <stdio.h>
#include <math.h>
#include <sys/time.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#define ALLOC_COUNT_MAX 256
#define ALLOC_SIZE_MAX 8000000
#define REALLOCS_COUNT 10000
#define PINNING_PERCENTAGE 0
#define DEFAULT_LOOP_COUNT 100
#define DEFAULT_CARVEOUT_AMT 80
#define SYS_ROOT "/sys/devices/platform/tegra-nvmap/misc/nvmap"
#define WINDOW_WIDTH 1024
#define WINDOW_HEIGHT 768
/* GART is 32mb, but is limited to 24mb per process */
#define GART_FILL_SIZE 24
static int verbose = 0;
static const char space[] = "************************************************";
static GLuint vsObj, fsObj, programObj;
static Display *x_display;
static Window win;
static EGLDisplay egl_display;
static EGLContext egl_context;
static EGLSurface egl_surface;
const char vertex_src [] =
" \
uniform mat4 transformMatrix; \
attribute vec4 position; \
attribute vec4 tcoord; \
varying vec2 st; \
\
void main() \
{ \
gl_Position = transformMatrix * position; \
st = tcoord.st; \
} \
";
const char fragment_src [] =
" \
precision highp float; \
uniform sampler2D tex; \
varying vec2 st; \
\
void main() \
{ \
gl_FragColor = texture2D(tex, st); \
} \
";
static const GLfloat sVertData[] = {
-1, -1, 0, 1,
1, -1, 0, 1,
-1, 1, 0, 1,
1, 1, 0, 1
};
/*
* This function gets the available amount of carveout from sysfs.
*/
int GetCarveoutTotalSize(unsigned int *total)
{
FILE* f;
char buffer[256];
sprintf(buffer, "%s/heap-generic-0/total_size", SYS_ROOT);
f = fopen(buffer, "r");
if(!f) {
printf("Failed to open %s", buffer);
return -1;
}
fscanf(f, "%d", total);
fclose(f);
return 0;
}
/*
* This function gets the free amount of carveout from sysfs.
*/
int GetCarveoutFreeSize(unsigned int *free)
{
FILE* f;
char buffer[256];
sprintf(buffer, "%s/heap-generic-0/free_size", SYS_ROOT);
/*
* Make sure all previous rendering calls have completed so we
* can query free carveout size
*/
glFinish();
f = fopen(buffer, "r");
if(!f) {
printf("Failed to open %s", buffer);
return -1;
}
fscanf(f, "%d", free);
fclose(f);
return 0;
}
/*
* This function creates an RGBA texture with a given width and height.
* Return value: handle to texture
*/
static GLuint CreateTexture(int width, int height, int number)
{
char *data = NULL;
int x, y, bytesPerPixel;
GLuint tex;
assert(number == (number & 0xF));
/* There are 4 bytes per pixel for GL_RGBA & GL_UNSIGNED_BYTE */
bytesPerPixel = 4;
data = (char *)malloc((size_t)(width*height*bytesPerPixel));
if (!data)
return -1;
for (x = 0; x < width; x++) {
for (y = 0 ; y < height; y++) {
int idx = (y*width + x)*bytesPerPixel;
data[idx] = (number * 0xF) & 0xFF;
data[idx+1] = (number * 0xF) & 0xFF;
data[idx+2] = 0xFF;
data[idx+3] = 0xFF;
}
}
/* create texture */
glGenTextures(1, &tex);
if (glGetError() != GL_NO_ERROR)
goto fail;
glActiveTexture(GL_TEXTURE0);
if (glGetError() != GL_NO_ERROR)
goto fail;
glBindTexture(GL_TEXTURE_2D, tex);
if (glGetError() != GL_NO_ERROR)
goto fail;
glTexImage2D(
/* target */ GL_TEXTURE_2D,
/* level */ 0,
/* internalformat */ (GLint)GL_RGBA,
/* width */ width,
/* height */ height,
/* border */ 0,
/* format */ GL_RGBA,
/* type */ GL_UNSIGNED_BYTE,
/* pixels */ data);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
goto done;
fail:
tex = -1;
done:
free(data);
return tex;
}
/*
* Fill in the result array with an identity matrix.
*/
static void LoadIdentity(GLfloat *result)
{
memset(result, 0x0, 16*4);
result[0] = 1;
result[5] = 1;
result[10] = 1;
result[15] = 1;
}
/*
* Fill in the result array with a scaling matrix.
*/
static void Scale(GLfloat *result, GLfloat sx, GLfloat sy, GLfloat sz)
{
result[0] *= sx;
result[1] *= sx;
result[2] *= sx;
result[3] *= sx;
result[4] *= sy;
result[5] *= sy;
result[6] *= sy;
result[7] *= sy;
result[8] *= sz;
result[9] *= sz;
result[10] *= sz;
result[11] *= sz;
}
/*
* Fill in the result array with a transformation matrix.
*/
static void Translate(GLfloat *result, GLfloat tx, GLfloat ty, GLfloat tz)
{
result[12] += (result[0] * tx + result[4] * ty + result[8] * tz);
result[13] += (result[1] * tx + result[5] * ty + result[9] * tz);
result[14] += (result[2] * tx + result[6] * ty + result[10] * tz);
result[15] += (result[3] * tx + result[7] * ty + result[11] * tz);
}
/*
* This function takes a given texture array and displays the textures in it.
* All textures need to be the same size, width x height.
*/
int ShowTextures(GLuint *tex, int count, int width, int height)
{
GLint texSampler;
GLint transformMatrixUniform;
GLfloat vertSTData[8];
int i;
GLfloat transformMatrix[16];
int cols = (int)sqrtf(count);
struct timeval tv;
int rnd;
gettimeofday(&tv, NULL);
rnd = tv.tv_sec * 1000;
/* Texture coords */
vertSTData[0] = 0;
vertSTData[1] = 0;
vertSTData[2] = width;
vertSTData[3] = 0;
vertSTData[4] = 0;
vertSTData[5] = height;
vertSTData[6] = width;
vertSTData[7] = height;
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, vertSTData);
texSampler = glGetUniformLocation(programObj, "tex");
transformMatrixUniform = glGetUniformLocation(programObj,
"transformMatrix");
glUniform1i(texSampler, 0);
/* Draw texture rectangles */
LoadIdentity(transformMatrix);
Scale(transformMatrix, 4.0f/cols, 4.0f/cols, 4.0f/cols);
Translate(transformMatrix, -cols - 1.0f, cols - 1.0f, 0.0f);
for (i = 0; i < count; i++) {
rnd = rnd * 69069 + 69069;
if(((rnd / 1217) & 255) > 128) {
Translate(transformMatrix, 2.0f, 0.0f, 0.0f);
glUniformMatrix4fv(transformMatrixUniform, 1, GL_FALSE,
transformMatrix);
glBindTexture(GL_TEXTURE_2D, tex[i]);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
if (((i+1) % cols) == 0) {
Translate(transformMatrix, -2.0f*cols, -2.0f,
0.0f);
}
}
}
/* Issue flush to ensure all gl commands are sent to processing */
glFlush();
return 0;
}
/*
* This function runs a single phase of the test.
* Return value: 0 on success
*/
int RunPhase(int phase, int phase_width, int phase_height, int texture_count,
int texture_size, GLuint *tex)
{
unsigned int phase_size = texture_count * texture_size;
unsigned int freeBefore, freeAfter;
GLenum errCode = GL_NO_ERROR;
int brokeAt = 0;
unsigned int displacement;
int i;
printf("%.*s\n", 48, space);
printf("Phase %d: Allocating small %d (%dMB) [%dMB] textures\n",
phase, texture_count, texture_size,
texture_count * texture_size);
printf("%.*s\n", 48, space);
/* Create texture */
printf("allocating textures.. (%d,%d)\n", phase_width, phase_height);
GetCarveoutFreeSize(&freeBefore);
for (i = 0; i < texture_count; i++) {
tex[i] = CreateTexture(phase_width - 1, phase_height - 1,
(i % 16));
if (tex[i] < 0) {
printf("Failed to create texture.\n");
brokeAt = i;
goto done;
}
errCode = glGetError();
if (errCode != GL_NO_ERROR) {
printf("GL Error Occured : %d\n", errCode);
brokeAt = i;
goto done;
}
}
GetCarveoutFreeSize(&freeAfter);
/* Calculate difference in MB */
displacement = (freeBefore - freeAfter) / (1024 * 1024);
if (displacement < phase_size) {
fprintf(stderr, "FAIL to alloc required mem from carveout.\n");
fprintf(stderr, "Allocated %dMB instead of desired %dMB\n",
displacement, phase_size);
brokeAt = texture_count - 1;
goto done;
}
if (verbose) {
unsigned int free;
GetCarveoutFreeSize(&free);
printf("CarveOut free after phase %d allocation: %d\n",
phase, free);
}
done:
return brokeAt; /* brokeAt is 0 unless and error happened */
}
/*
* This function runs the actual test, drawing the texture rects.
* Return value: 0 on success
*/
int RunTest(int carveout_percent)
{
GLuint phase1_tex[ALLOC_COUNT_MAX];
GLuint phase2_tex[ALLOC_COUNT_MAX / 4];
GLuint phase3_tex[ALLOC_COUNT_MAX / 8];
int i;
unsigned int allocCount = 0;
unsigned int carveoutTotal;
unsigned int carveoutFree;
int allocatedMemoryLimit;
GLenum errCode = GL_NO_ERROR;
int phase = 0;
int brokeAt = 0;
if(GetCarveoutTotalSize(&carveoutTotal) ||
GetCarveoutFreeSize(&carveoutFree)) {
printf("Failed to read carveout stats\n");
return -1;
}
printf("CarveOut total before cleanup: %d [%dMB]\n", carveoutTotal,
carveoutTotal / (1024*1024));
printf("CarveOut free before cleanup: %d [%dMB]\n", carveoutFree,
carveoutFree / (1024*1024));
/* Memory is in units of bytes */
allocatedMemoryLimit = (int)((carveoutFree / 100) * carveout_percent);
allocCount = allocatedMemoryLimit / 1048576; /* 1 mb */
allocCount = (allocCount / 4) * 4; /* always a multiple of 4. */
phase = 1;
errCode = RunPhase(phase, 512, 512, allocCount, 1, phase1_tex);
if (errCode) {
brokeAt = errCode;
goto cleanup_phase1;
}
printf("freeing first 3 of every 4 textures from phase 1 [%dMB]\n",
(allocCount * 3 / 4) * 1);
for (i = 0; i < allocCount; i++) {
if ((i + 1) % 4 == 0) continue;
glDeleteTextures(1, &phase1_tex[i]);
}
/*
* Make sure all previous rendering calls have completed so we
* can query free carveout size
*/
glFinish();
if (verbose) {
unsigned int free;
GetCarveoutFreeSize(&free);
printf("CarveOut free after phase 1 freeing: %d\n", free);
}
/*
* prepare for phase 2
* we free'd up 3/4 of the phase 1 handles, and then these will be
* 4x as large, so the number will further shrink so 3/16
*/
allocCount = (allocCount * 3) / 16;
phase = 2;
errCode = RunPhase(phase, 1024, 1024, allocCount, 4, phase2_tex);
if (errCode) {
brokeAt = errCode;
goto cleanup_phase2;
}
printf("freeing every other texture from phase 2 [%dMB]\n",
(allocCount / 2) * 4 );
for (i = 0; i < allocCount; i++) {
if (i % 2 != 0) continue;
glDeleteTextures(1, &phase2_tex[i]);
}
/*
* Make sure all previous rendering calls have completed so we
* can query free carveout size
*/
glFinish();
if (verbose) {
unsigned int free;
GetCarveoutFreeSize(&free);
printf("CarveOut free after phase 2 freeing: %d\n", free);
}
/*
* prepare for phase 3
* we free'd up 1/2 of the phase 2 handles, and then these will be
* 2x as large, so the number will further shrink so 1/4
*/
allocCount = (allocCount / 4);
phase = 3;
errCode = RunPhase(phase, 2048, 1024, allocCount, 8, phase3_tex);
if (errCode) {
brokeAt = errCode;
goto cleanup_phase3;
}
printf("%.*s\n", 48, space);
printf("Test Complete \n");
printf("%.*s\n", 48, space);
cleanup_phase3:
printf("freeing last textures from phase 3\n");
for (i = 0; i < (brokeAt ? brokeAt : allocCount); i++)
glDeleteTextures(1, &phase3_tex[i]);
cleanup_phase2:
printf("freeing last textures from phase 2\n");
if (phase == 2 && errCode != GL_NO_ERROR)
for (i = 0; i < (brokeAt ? brokeAt : allocCount); i++)
glDeleteTextures(1, &phase2_tex[i]);
else
for (i = 0; i < allocCount; i += 2)
glDeleteTextures(1, &phase2_tex[i]);
cleanup_phase1:
printf("freeing last textures from phase 1\n");
if (phase == 1 && errCode != GL_NO_ERROR)
for (i = 0; i < (brokeAt ? brokeAt : allocCount); i++)
glDeleteTextures(1, &phase1_tex[i]);
else
for (i = 3; i < allocCount; i += 4)
glDeleteTextures(1, &phase1_tex[i]);
return (errCode == GL_NO_ERROR ? 0 : -1);
}
/*
* This function prints the info log for a given shader (from handle).
*/
void PrintShaderInfoLog(GLuint shader)
{
GLint length;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);
if (length) {
char buffer[length];
glGetShaderInfoLog(shader, length, NULL, buffer);
printf("shader info: %s\n", buffer);
}
}
GLuint LoadShader(const char *shader_src, GLenum type)
{
GLuint shader = glCreateShader(type);
GLint success;
glShaderSource(shader, 1, &shader_src, NULL);
glCompileShader(shader);
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (success != GL_TRUE) {
fprintf(stderr, "FAILED to compile shader. %d\n", success);
return success;
}
if (verbose)
PrintShaderInfoLog(shader);
return shader;
}
static void InitGraphicsState()
{
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, sVertData);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
vsObj = LoadShader(vertex_src, GL_VERTEX_SHADER);
fsObj = LoadShader(fragment_src, GL_FRAGMENT_SHADER);
programObj = glCreateProgram();
glAttachShader(programObj, vsObj);
glAttachShader(programObj, fsObj);
glBindAttribLocation(programObj, 0, "position");
glBindAttribLocation(programObj, 1, "tcoord");
glLinkProgram(programObj);
glUseProgram(programObj);
/* so that odd-sized RGB textures will work nicely */
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glDisable(GL_DEPTH_TEST);
}
void CleanupX()
{
XDestroyWindow(x_display, win);
XCloseDisplay(x_display);
}
void CleanupEgl()
{
eglDestroyContext(egl_display, egl_context);
eglDestroySurface(egl_display, egl_surface);
eglTerminate(egl_display);
}
int XInitialize(int x, int y, int width, int height)
{
Window root;
XSetWindowAttributes swa;
XSetWindowAttributes xattr;
Atom atom;
XWMHints hints;
int xres;
x_display = XOpenDisplay(NULL);
if (x_display == NULL) {
printf("Cannot connect to X server. Exiting...\n");
return -1;
}
root = DefaultRootWindow(x_display);
swa.event_mask = ExposureMask | PointerMotionMask | KeyPressMask;
if (verbose)
printf("Creating window at (%d,%d) with w=%d, h=%d\n",
x, y, width, height);
win = XCreateWindow(
/* connection to x server */ x_display,
/* parent window */ root,
/* x coord, top left corner */ x,
/* y coord, top left corner */ y,
/* width of window */ width,
/* height of window */ height,
/* border width */ 0,
/* depth of window */ CopyFromParent,
/* window's class */ InputOutput,
/* visual type */ CopyFromParent,
/* valid attribute mask */ CWEventMask,
/* attributes */ &swa);
if (win == BadAlloc ||
win == BadColor ||
win == BadCursor ||
win == BadMatch ||
win == BadPixmap ||
win == BadValue ||
win == BadWindow) {
fprintf(stderr, "FAILED to create X window\n");
return -1;
}
xattr.override_redirect = false;
xres = XChangeWindowAttributes(x_display, win, CWOverrideRedirect,
&xattr);
if (xres == BadAccess ||
xres == BadColor ||
xres == BadCursor ||
xres == BadMatch ||
xres == BadPixmap ||
xres == BadValue ||
xres == BadWindow) {
fprintf(stderr, "FAIL changing X win attribs: %d\n", xres);
goto fail;
}
atom = XInternAtom(x_display, "_NET_WM_STATE_FULLSCREEN", true);
hints.input = true;
hints.flags = InputHint;
xres = XSetWMHints(x_display, win, &hints);
if (xres == BadAlloc || xres == BadWindow) {
fprintf(stderr, "FAIL setting X WM hints: %d\n", xres);
goto fail;
}
xres = XMapWindow(x_display, win);
if (xres == BadAlloc || xres == BadWindow ) {
fprintf(stderr, "FAIL mapping X window: %d\n", xres);
goto fail;
}
xres = XStoreName(x_display, win, "GLES2 Texture Test");
if (xres == BadAlloc || xres == BadWindow) {
fprintf(stderr, "FAIL storing X window name: %d\n", xres);
goto fail;
}
return 0;
fail:
CleanupX();
return -1;
}
int EglInitialize()
{
EGLConfig config;
EGLint numConfig;
EGLint attr[] = {
EGL_BUFFER_SIZE, 16,
EGL_RENDERABLE_TYPE,
EGL_OPENGL_ES2_BIT,
EGL_NONE
};
EGLint ctxattr[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
egl_display = eglGetDisplay((EGLNativeDisplayType)x_display);
if (egl_display == EGL_NO_DISPLAY) {
fprintf(stderr, "EGL failed to obtain display. Exiting...\n");
return -1;
}
if ( !eglInitialize(egl_display, NULL, NULL)) {
fprintf(stderr, "EGL failed to initialize. Exiting...\n");
return -1;
}
if ( !eglChooseConfig(egl_display, attr, &config, 1, &numConfig)) {
fprintf(stderr, "EGL failed to choose config. Exiting...\n");
return -1;
}
if (numConfig != 1) {
fprintf(stderr, "EGL failed got %d > 1 Exiting...\n",
numConfig);
return -1;
}
egl_surface = eglCreateWindowSurface(egl_display, config, win, NULL);
if (egl_surface == EGL_NO_SURFACE) {
fprintf(stderr, "EGL failed create window surface. Exiting\n");
return -1;
}
egl_context = eglCreateContext(egl_display, config, EGL_NO_CONTEXT,
ctxattr);
if (egl_context == EGL_NO_CONTEXT) {
fprintf(stderr, "EGL failed to create context. Exiting...\n");
return -1;
}
if ( !eglMakeCurrent(egl_display, egl_surface, egl_surface,
egl_context)) {
fprintf(stderr, "EGL failed make context current. Exiting\n");
return -1;
}
return 0;
}
void PrintUsage()
{
printf("--------------------------------------------\n");
printf("nvmap_carveout_compactor [options]\n");
printf(" -h | --help - Show this help screen\n");
printf(" -v | --verbose - Enables verbose prints\n");
printf(" -l | --loop_count - # of times to loop [def: %d]\n",
DEFAULT_LOOP_COUNT);
printf(" -c | --carveout_percent - %% of free carveout [def : %d].\n",
DEFAULT_CARVEOUT_AMT);
}
int main(int argc, char *argv[])
{
GLuint fill_tex[GART_FILL_SIZE];
int failure = 0;
int failIndex = 0;
int i;
int loop_count = 1;
int option_index = 0;
GLenum errCode;
int x = 0, y = 0, width = WINDOW_WIDTH, height = WINDOW_HEIGHT;
int carveout_percent = 80; /* 80 percent of free carveout */
static struct option long_options[] = {
{"help", no_argument, 0, 'h'},
{"verbose", no_argument, 0, 'v'},
{"loop_count", required_argument, 0, 'l'},
{"carveout_percent", required_argument, 0, 'c'},
{NULL, 0, NULL, 0}
};
if (!getenv("DISPLAY")) {
fprintf(stderr, "FAIL: DISPLAY env variable not set.\n");
failure = -1;
goto done;
}
while ((i = getopt_long(argc, argv, "hvl:c:", long_options,
&option_index)) != -1)
switch (i) {
case 'h':
PrintUsage();
return 0;
case 'v':
verbose = 1;
break;
case 'l':
loop_count = atoi(optarg);
break;
case 'c':
carveout_percent = atoi(optarg);
break;
case '?':
printf("unknown option `\\x%x`.\n", optopt);
return 1;
default:
goto done;
}
failure = XInitialize(x, y, width, height);
if (failure)
goto done;
failure = EglInitialize();
if (failure)
goto clean_x;
InitGraphicsState();
glViewport(0, 0, width, height);
glClear(GL_COLOR_BUFFER_BIT);
printf("Test started pid = %d.\n", getpid());
if (verbose) {
printf("Looping for %d iterations.\n", loop_count);
printf("Going to try to use %d%% of carveout during test.\n",
carveout_percent);
}
/* first need to allocate 24mb of textures to fill GART */
printf("allocating textures to fill GART.. (%d,%d)\n",
512, 512);
/* Each process gets 24mb of GART */
for (i = 0; i < GART_FILL_SIZE; i++) {
fill_tex[i] = CreateTexture(511, 511, (i % 16));
errCode = glGetError();
if (fill_tex[i] >= 0 && errCode == GL_NO_ERROR)
continue;
/* Some error occured when creating textures */
printf("Failed to create texture.\n");
if (errCode != GL_NO_ERROR)
printf("GL Error Occured : %d\n", errCode);
failIndex = i;
failure = -1;
goto done;
}
ShowTextures(fill_tex, GART_FILL_SIZE, 512, 512);
/* predefined resolutions to account for size */
for(i = 0; i < loop_count; i++) {
failure |= RunTest(carveout_percent);
eglSwapBuffers(egl_display, egl_surface);
}
if (!failure) {
errCode = glGetError();
if (errCode == GL_NO_ERROR)
failure = false;
else {
fprintf(stderr, "FAIL: GL Error Occured : %d\n",
errCode);
failure = 1;
}
}
CleanupEgl();
clean_x:
CleanupX();
done:
for (i = 0; i < (failIndex ? failIndex : GART_FILL_SIZE); i++)
glDeleteTextures(1, &fill_tex[i]);
if (!failure)
printf("Test completed [SUCCESS]: pid = %d\n", getpid());
else
fprintf(stderr, "Test completed [FAIL]: pid = %d\n", getpid());
return failure ? -1 : 0;
}