/*
 * Copyright (C) 2009  VMware, Inc.  All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * VMWARE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
 * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/**
 * Measure VBO upload speed.
 * That is, measure glBufferDataARB() and glBufferSubDataARB().
 *
 * Brian Paul
 * 16 Sep 2009
 */

#include <string.h>
#include "common.h"
#include "piglit-util-gl.h"

#define WINDOW_SIZE 100


PIGLIT_GL_TEST_CONFIG_BEGIN

	config.supports_gl_compat_version = 10;
	config.window_width = WINDOW_SIZE;
	config.window_height = WINDOW_SIZE;
	config.window_visual = PIGLIT_GL_VISUAL_RGBA | PIGLIT_GL_VISUAL_DOUBLE;

PIGLIT_GL_TEST_CONFIG_END

/* Copy data out of a large array to avoid caching effects:
 */
#define DATA_SIZE (16*1024*1024)

static GLuint VBO;

static GLsizei VBOSize = 0;
static GLsizei SubSize = 0;
static GLubyte *VBOData = NULL;  /* array[DATA_SIZE] */

static const GLboolean DrawPoint = GL_TRUE;

static const GLfloat Vertex0[2] = { 0.0, 0.0 };


void
piglit_init(int argc, char **argv)
{
	/* setup VBO */
	glGenBuffersARB(1, &VBO);
	glBindBufferARB(GL_ARRAY_BUFFER_ARB, VBO);
	glVertexPointer(2, GL_FLOAT, sizeof(Vertex0), (void *) 0);
	glEnableClientState(GL_VERTEX_ARRAY);
}


static void
UploadVBO(unsigned count)
{
	unsigned i;
	unsigned total = 0;
	unsigned src = 0;

	for (i = 0; i < count; i++) {
		glBufferDataARB(GL_ARRAY_BUFFER, VBOSize, VBOData + src, GL_STREAM_DRAW_ARB);
		glDrawArrays(GL_POINTS, 0, 1);

		/* Throw in an occasional flush to work around a driver crash:
		 */
		total += VBOSize;
		if (total >= 16*1024*1024) {
			glFlush();
			total = 0;
		}

		src += VBOSize;
		src %= DATA_SIZE;
	}
	glFinish();
}


static void
UploadSubVBO(unsigned count)
{
	unsigned i;
	unsigned src = 0;

	for (i = 0; i < count; i++) {
		unsigned offset = (i * SubSize) % VBOSize;
		glBufferSubDataARB(GL_ARRAY_BUFFER, offset, SubSize, VBOData + src);

		if (DrawPoint) {
			glDrawArrays(GL_POINTS, offset / sizeof(Vertex0), 1);
		}

		src += SubSize;
		src %= DATA_SIZE;
	}
	glFinish();
}


/* Do multiple small SubData uploads, then call DrawArrays.  This may be a
 * fairer comparison to back-to-back BufferData calls:
 */
static void
BatchUploadSubVBO(unsigned count)
{
	unsigned i = 0, j;
	unsigned period = VBOSize / SubSize;
	unsigned src = 0;

	while (i < count) {
		for (j = 0; j < period && i < count; j++, i++) {
			unsigned offset = j * SubSize;
			glBufferSubDataARB(GL_ARRAY_BUFFER, offset, SubSize, VBOData + src);
		}

		glDrawArrays(GL_POINTS, 0, 1);

		src += SubSize;
		src %= DATA_SIZE;
	}
	glFinish();
}


/**
 * Test the sequence:
 *    create/load VBO
 *    draw
 *    destroy VBO
 */
static void
CreateDrawDestroyVBO(unsigned count)
{
	unsigned i;
	for (i = 0; i < count; i++) {
		GLuint vbo;
		/* create/load */
		glGenBuffersARB(1, &vbo);
		glBindBufferARB(GL_ARRAY_BUFFER_ARB, vbo);
		glBufferDataARB(GL_ARRAY_BUFFER, VBOSize, VBOData, GL_STREAM_DRAW_ARB);
		/* draw */
		glVertexPointer(2, GL_FLOAT, sizeof(Vertex0), (void *) 0);
		glDrawArrays(GL_POINTS, 0, 1);
		/* destroy */
		glDeleteBuffersARB(1, &vbo);
	}
	glFinish();
}


static const GLsizei Sizes[] = {
	64,
	1024,
	16*1024,
	256*1024,
	1024*1024,
	16*1024*1024,
	0 /* end of list */
};

enum piglit_result
piglit_display(void)
{
	double rate, mbPerSec;
	int i, sz;

	/* Load VBOData buffer with duplicated Vertex0.
	 */
	VBOData = calloc(DATA_SIZE, 1);

	for (i = 0; i < DATA_SIZE / sizeof(Vertex0); i++) {
		memcpy(VBOData + i * sizeof(Vertex0),
		       Vertex0,
		       sizeof(Vertex0));
	}

	/* glBufferDataARB()
	 */
	for (sz = 0; Sizes[sz]; sz++) {
		SubSize = VBOSize = Sizes[sz];
		rate = perf_measure_cpu_rate(UploadVBO, 1.0);
		mbPerSec = rate * VBOSize / (1024.0 * 1024.0);
		printf("  glBufferDataARB(size = %d): %.1f MB/sec\n",
		       VBOSize, mbPerSec);
	}

	/* glBufferSubDataARB()
	 */
	for (sz = 0; Sizes[sz]; sz++) {
		SubSize = VBOSize = Sizes[sz];
		rate = perf_measure_cpu_rate(UploadSubVBO, 1.0);
		mbPerSec = rate * VBOSize / (1024.0 * 1024.0);
		printf("  glBufferSubDataARB(size = %d): %.1f MB/sec\n",
		       VBOSize, mbPerSec);
	}

	/* Batch upload
	 */
	VBOSize = 1024 * 1024;
	glBufferDataARB(GL_ARRAY_BUFFER, VBOSize, VBOData, GL_STREAM_DRAW_ARB);

	for (sz = 0; Sizes[sz] < VBOSize; sz++) {
		SubSize = Sizes[sz];
		rate = perf_measure_cpu_rate(UploadSubVBO, 1.0);
		mbPerSec = rate * SubSize / (1024.0 * 1024.0);
		printf("  glBufferSubDataARB(size = %d, VBOSize = %d): %.1f MB/sec\n",
		       SubSize, VBOSize, mbPerSec);
	}

	for (sz = 0; Sizes[sz] < VBOSize; sz++) {
		SubSize = Sizes[sz];
		rate = perf_measure_cpu_rate(BatchUploadSubVBO, 1.0);
		mbPerSec = rate * SubSize / (1024.0 * 1024.0);
		printf("  glBufferSubDataARB(size = %d, VBOSize = %d), batched: %.1f MB/sec\n",
		       SubSize, VBOSize, mbPerSec);
	}

	/* Create/Draw/Destroy
	 */
	for (sz = 0; Sizes[sz]; sz++) {
		SubSize = VBOSize = Sizes[sz];
		rate = perf_measure_cpu_rate(CreateDrawDestroyVBO, 1.0);
		mbPerSec = rate * VBOSize / (1024.0 * 1024.0);
		printf("  VBO Create/Draw/Destroy(size = %d): %.1f MB/sec, %.1f draws/sec\n",
		       VBOSize, mbPerSec, rate);
	}

	exit(0);
	return PIGLIT_SKIP;
}
