VBO Software Emulation Layer

Discuss programming topics that involve the OpenGL API.
Post Reply
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

VBO Software Emulation Layer

Post by mh »

I wrote this for RMQ because I wanted to maintain a single code-path throughout the engine. Almost all that's needed is - if the VBO entry points are not found - to assign your function pointers to the -RMQ versions below, and you can just write code as if VBOs were there (there's a little bit more, I'll get to that later).

It's intentionally not very robust. If a program crashes using these functions it will probably also crash if using real VBOs, and I think you'll want to know about that. Note that it's quite possible to write code using these functions that performs well enough, only to have it be sucked down into the morass of performance loss if using real VBOs. The reason is that real VBO code can do things like stall the pipeline if you use it badly, whereas this code won't. It's not intended as something that you can use for a quick, easy and fun way of writing VBO code if your hardware doesn't support VBOs and you must therefore test your original code on hardware that does support them. Once you've got that working you can drop this in and give the player a fallback if their hardware doesn't. Because that's what it's for.

On to the code.

Code: Select all

// software emulation layer for VBOs so that i can maintain a single code path in the program
// this is intentionally not very robust - if it crashes, your code will probably crash with real VBOs too
qboolean gl_swbuffers = false;

typedef struct gl_swvbo_s
{
	GLint access;
	GLint mapped;
	GLint size;
	GLint usage;
	GLvoid *data;
} gl_swvbo_t;


#define MAX_SWVBO	65536

gl_swvbo_t *gl_swvbos[MAX_SWVBO];

GLuint gl_currentvbo[2] = {0, 0};


void GL_ResetAllBuffers (void)
{
	// to be called when the video mode changes
	int i;

	for (i = 0; i < MAX_SWVBO; i++)
	{
		if (gl_swvbos[i])
		{
			if (gl_swvbos[i]->data)
				free (gl_swvbos[i]->data);

			free (gl_swvbos[i]);
			gl_swvbos[i] = NULL;
		}
	}

	gl_currentvbo[0] = 0;
	gl_currentvbo[1] = 0;
}


gl_swvbo_t *GL_GetBufferForTarget (GLenum target)
{
	int buffer = -1;

	switch (target)
	{
	case GL_ARRAY_BUFFER:
		buffer = gl_currentvbo[0];
		break;

	case GL_ELEMENT_ARRAY_BUFFER:
		buffer = gl_currentvbo[1];
		break;

	default:
		return NULL;
	}

	return gl_swvbos[buffer];
}


void *GL_FixupPointer (GLenum target, void *pointer)
{
	if (gl_swbuffers)
	{
		gl_swvbo_t *buffer = GL_GetBufferForTarget (target);

		if (buffer)
			return ((byte *) buffer->data) + ((intptr_t) pointer);
		else return pointer;
	}
	else return pointer;
}


void APIENTRY glBindBufferRMQ (GLenum target, GLuint buffer)
{
	switch (target)
	{
	case GL_ARRAY_BUFFER:
		gl_currentvbo[0] = buffer;
		break;

	case GL_ELEMENT_ARRAY_BUFFER:
		gl_currentvbo[1] = buffer;
		break;

	default:
		break;
	}
}


void APIENTRY glDeleteBuffersRMQ (GLsizei n, const GLuint *buffers)
{
	int i;

	for (i = 0; i < n; i++)
	{
		if (gl_swvbos[buffers[i]])
		{
			if (gl_swvbos[buffers[i]]->data)
				free (gl_swvbos[buffers[i]]->data);

			free (gl_swvbos[buffers[i]]);
			gl_swvbos[buffers[i]] = NULL;
		}
	}
}


void APIENTRY glGenBuffersRMQ (GLsizei n, GLuint *buffers)
{
	int i, b;

	for (i = 0; i < n; i++)
	{
		// buffer object 0 is reserved
		for (b = 1; b < MAX_SWVBO; b++)
		{
			if (!gl_swvbos[b])
			{
				gl_swvbos[b] = (gl_swvbo_t *) malloc (sizeof (gl_swvbo_t));

				// defaults per http://www.opengl.org/sdk/docs/man/xhtml/glGetBufferParameteriv.xml
				gl_swvbos[b]->access = GL_READ_WRITE;
				gl_swvbos[b]->data = NULL;
				gl_swvbos[b]->mapped = GL_FALSE;
				gl_swvbos[b]->size = 0;
				gl_swvbos[b]->usage = GL_STATIC_DRAW;

				buffers[i] = b;
				break;
			}
		}
	}
}


GLboolean APIENTRY glIsBufferRMQ (GLuint buffer)
{
	if (gl_swvbos[buffer])
		return GL_TRUE;
	else return GL_FALSE;
}


void APIENTRY glBufferDataRMQ (GLenum target, GLsizeiptrARB size, const GLvoid *data, GLenum usage)
{
	gl_swvbo_t *buffer = GL_GetBufferForTarget (target);

	if (size > buffer->size)
	{
		if (buffer->data)
		{
			free (buffer->data);
			buffer->data = NULL;
		}
	}

	buffer->size = size;
	buffer->data = malloc (size);

	if (data)
		memcpy (buffer->data, data, size);
}


void APIENTRY glBufferSubDataRMQ (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid *data)
{
	gl_swvbo_t *buffer = GL_GetBufferForTarget (target);

	memcpy (((byte *) buffer->data) + offset, data, size);
}


void APIENTRY glGetBufferSubDataRMQ (GLenum target, GLintptrARB offset, GLsizeiptrARB size, GLvoid *data)
{
	gl_swvbo_t *buffer = GL_GetBufferForTarget (target);

	memcpy (data, ((byte *) buffer->data) + offset, size);
}


GLvoid *APIENTRY glMapBufferRMQ (GLenum target, GLenum access)
{
	gl_swvbo_t *buffer = GL_GetBufferForTarget (target);

	buffer->mapped = GL_TRUE;
	buffer->access = access;

	return buffer->data;
}


GLboolean APIENTRY glUnmapBufferRMQ (GLenum target)
{
	gl_swvbo_t *buffer = GL_GetBufferForTarget (target);

	buffer->mapped = GL_FALSE;
	return GL_TRUE;
}


void APIENTRY glGetBufferParameterivRMQ (GLenum target, GLenum value, GLint *data)
{
	gl_swvbo_t *buffer = GL_GetBufferForTarget (target);

	switch (value)
	{
	case GL_BUFFER_ACCESS: data[0] = buffer->access; break;
	case GL_BUFFER_MAPPED: data[0] = buffer->mapped; break;
	case GL_BUFFER_SIZE: data[0] = buffer->size; break;
	case GL_BUFFER_USAGE: data[0] = buffer->usage; break;
	default: break;
	}
}


void APIENTRY glGetBufferPointervRMQ (GLenum target, GLenum name, GLvoid **params)
{
	gl_swvbo_t *buffer = GL_GetBufferForTarget (target);

	if (name == GL_BUFFER_MAP_POINTER_ARB)
	{
		if (buffer->mapped)
			params[0] = buffer->data;
		else params[0] = NULL;
	}
	else params[0] = NULL;
}
I mentioned that there was a little more work to do. OK, here it is. Because the ARB - in their infinite wisdom - decided to overload the regular vertex array functions rather than provide new entry points for VBOs, we need to deal with this too.

Note the GL_FixupPointer function above. This part is where that comes in.

A gl*Pointer function is now changed to look like this:

Code: Select all

glWhateverPointer (size, type, stride, GL_FixupPointer (GL_ARRAY_BUFFER, pointer));
And glDrawElements becomes this:

Code: Select all

glDrawElements (mode, count, type, GL_FixupPointer (GL_ELEMENT_ARRAY_BUFFER, indices));
(I'm sure you can figure out glDrawRangeElements yourself...)
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Post Reply