Source

love-experiments / src / modules / graphics / opengl / VertexBuffer.cpp

The branch 'vbostrategy' does not exist.
/**
 * Copyright (c) 2006-2012 LOVE Development Team
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 **/

#include "VertexBuffer.h"

#include "common/Exception.h"
#include "common/config.h"

#include <cstdlib>
#include <cstring>

namespace love
{
namespace graphics
{
namespace opengl
{

// VertexBuffer

VertexBuffer *VertexBuffer::Create(size_t size, GLenum target, GLenum usage)
{
	try
	{
		// Try to create a VBO.
		return new VBO(size, target, usage);
	}
	catch(const love::Exception &)
	{
		// VBO not supported ... create regular array.
		return new VertexArray(size, target, usage);
	}
}

VertexBuffer::VertexBuffer(size_t size, GLenum target, GLenum usage)
	: size(size)
	, target(target)
	, usage(usage)
{
}

VertexBuffer::~VertexBuffer()
{
}

// VertexArray

VertexArray::VertexArray(size_t size, GLenum target, GLenum usage)
	: VertexBuffer(size, target, usage)
	, buf(new char[size])
{
}

VertexArray::~VertexArray()
{
	delete [] buf;
}

void *VertexArray::map()
{
	return buf;
}

void VertexArray::unmap()
{
}

void VertexArray::bind()
{
}

void VertexArray::unbind()
{
}

void VertexArray::fill(size_t offset, size_t size, const void *data)
{
	memcpy(buf + offset, data, size);
}

const void *VertexArray::getPointer(size_t offset) const
{
	return buf + offset;
}


// Functions for VBO usage, selectable at runtime:
// opengl >= 1.5, ARB
struct VboStrategy
{
	virtual void genBuffers(GLsizei n, GLuint *buffers) = 0;
	virtual void deleteBuffers(GLsizei n, const GLuint *buffers) = 0;
	virtual void bindBuffer(GLenum target, GLuint buffer) = 0;

	virtual void bufferData(GLenum target, ptrdiff_t size, const void *data, GLenum usage) = 0;
	virtual void bufferSubData(GLenum target, ptrdiff_t offset, ptrdiff_t size, const void *data) = 0;
	virtual void getBufferSubData(GLenum target, ptrdiff_t offset, ptrdiff_t size, void *data) = 0;

	virtual void *mapBuffer(GLenum target, GLenum access) = 0;
	virtual GLboolean unmapBuffer(GLenum target) = 0;

	virtual void getBufferParameteriv(GLenum target, GLenum pname, GLint *params) = 0;
	virtual void getBufferPointerv(GLenum target, GLenum pname, void **params) = 0;
};

struct VboStrategyGL1_5 : public VboStrategy
{
	virtual void genBuffers(GLsizei n, GLuint *buffers) { glGenBuffers(n, buffers); };
	virtual void deleteBuffers(GLsizei n, const GLuint *buffers) { glDeleteBuffers(n, buffers); };
	virtual void bindBuffer(GLenum target, GLuint buffer) { glBindBuffer(target, buffer); };

	virtual void bufferData(GLenum target, ptrdiff_t size, const void *data, GLenum usage) { glBufferData(target, size, data, usage); };
	virtual void bufferSubData(GLenum target, ptrdiff_t offset, ptrdiff_t size, const void *data) { glBufferSubData(target, offset, size, data); };
	virtual void getBufferSubData(GLenum target, ptrdiff_t offset, ptrdiff_t size, void *data) { glGetBufferSubData(target, offset, size, data); };

	virtual void *mapBuffer(GLenum target, GLenum access) { return glMapBuffer(target, access); };
	virtual GLboolean unmapBuffer(GLenum target) { return glUnmapBuffer(target); };

	virtual void getBufferParameteriv(GLenum target, GLenum pname, GLint *params) { glGetBufferParameteriv(target, pname, params); };
	virtual void getBufferPointerv(GLenum target, GLenum pname, void **params) { glGetBufferPointerv(target, pname, params); };
};

struct VboStrategyARB : public VboStrategy
{
	virtual void genBuffers(GLsizei n, GLuint *buffers) { glGenBuffersARB(n, buffers); };
	virtual void deleteBuffers(GLsizei n, const GLuint *buffers) { glDeleteBuffersARB(n, buffers); };
	virtual void bindBuffer(GLenum target, GLuint buffer) { glBindBufferARB(target, buffer); };

	virtual void bufferData(GLenum target, ptrdiff_t size, const void *data, GLenum usage) { glBufferDataARB(target, size, data, usage); };
	virtual void bufferSubData(GLenum target, ptrdiff_t offset, ptrdiff_t size, const void *data) { glBufferSubDataARB(target, offset, size, data); };
	virtual void getBufferSubData(GLenum target, ptrdiff_t offset, ptrdiff_t size, void *data) { glGetBufferSubDataARB(target, offset, size, data); };

	virtual void *mapBuffer(GLenum target, GLenum access) { return glMapBufferARB(target, access); };
	virtual GLboolean unmapBuffer(GLenum target) { return glUnmapBufferARB(target); };

	virtual void getBufferParameteriv(GLenum target, GLenum pname, GLint *params) { glGetBufferParameterivARB(target, pname, params); };
	virtual void getBufferPointerv(GLenum target, GLenum pname, void **params) { glGetBufferPointervARB(target, pname, params); };
};

static bool vboStrategyLoaded = false;
static VboStrategy *vboStrategy;
static VboStrategyGL1_5 vboStrategyGL1_5;
static VboStrategyARB vboStrategyARB;

static void loadVboStrategy()
{
	if (!vboStrategyLoaded)
	{
		if (GLEE_VERSION_1_5)
			vboStrategy = &vboStrategyGL1_5;
		else if (GLEE_ARB_vertex_buffer_object)
			vboStrategy = &vboStrategyARB;
		else
			throw love::Exception("VBO not supported");
		vboStrategyLoaded = true;
	}
};

// VBO

VBO::VBO(size_t size, GLenum target, GLenum usage)
	: VertexBuffer(size, target, usage)
	, vbo(0)
	, memory_map(0)
	, is_mapped(false)
	, is_dirty(true)
{
	loadVboStrategy();

	bool ok = load(false);

	if (!ok)
		throw love::Exception("Could not load VBO.");
}

VBO::~VBO()
{
	if (vbo != 0)
		unload(false);

	if (memory_map)
		free(memory_map);
}

void *VBO::map()
{
	if (is_mapped)
		return memory_map;

	if (!memory_map)
	{
		memory_map = malloc(getSize());
		if (!memory_map)
			throw love::Exception("Out of memory (oh the humanity!)");
	}

	if (is_dirty)
		vboStrategy->getBufferSubData(getTarget(), 0, getSize(), memory_map);

	is_mapped = true;
	is_dirty = false;

	return memory_map;
}

void VBO::unmap()
{
	if (!is_mapped)
		return;

	// "orphan" current buffer to avoid implicit synchronisation on the gpu:
	// http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf
	vboStrategy->bufferData(getTarget(), getSize(), NULL,       getUsage());
	vboStrategy->bufferData(getTarget(), getSize(), memory_map, getUsage());
	is_mapped = false;
}

void VBO::bind()
{
	if (!is_mapped)
		vboStrategy->bindBuffer(getTarget(), vbo);
}

void VBO::unbind()
{
	if (!is_mapped)
		vboStrategy->bindBuffer(getTarget(), 0);
}

void VBO::fill(size_t offset, size_t size, const void *data)
{
	if (is_mapped)
	{
		memcpy((char *) memory_map + offset, data, size);
	}
	else
	{
		vboStrategy->bufferSubData(getTarget(), offset, size, data);
		is_dirty = true;
	}
}

const void *VBO::getPointer(size_t offset) const
{
	return reinterpret_cast<const void *>(offset);
}

bool VBO::loadVolatile()
{
	return load(true);
}

void VBO::unloadVolatile()
{
	unload(true);
}

bool VBO::load(bool restore)
{
	vboStrategy->genBuffers(1, &vbo);

	VertexBuffer::Bind bind(*this);

	// Copy the old buffer only if 'restore' was requested.
	const GLvoid *src = restore ? memory_map : 0;

	while (GL_NO_ERROR != glGetError())
		/* clear error messages */;

	// Note that if 'src' is '0', no data will be copied.
	vboStrategy->bufferData(getTarget(), getSize(), src, getUsage());
	GLenum err = glGetError();

	return (GL_NO_ERROR == err);
}

void VBO::unload(bool save)
{
	// Save data before unloading.
	if (save)
	{
		VertexBuffer::Bind bind(*this);

		GLint size;
		vboStrategy->getBufferParameteriv(getTarget(), GL_BUFFER_SIZE, &size);

		map(); // saves buffer content to memory_map.
		unmap();
	}

	vboStrategy->deleteBuffers(1, &vbo);
	vbo = 0;
}

} // opengl
} // graphics
} // love