Meshes now use vertex buffers for more efficient drawing.

--HG--
branch : Mesh
This commit is contained in:
Alex Szpakowski
2013-09-29 21:24:19 -03:00
parent c63e2ec624
commit b59e3d9c35
7 changed files with 268 additions and 125 deletions
+130 -29
View File
@@ -31,64 +31,131 @@ namespace opengl
{
Mesh::Mesh(const std::vector<Vertex> &verts, Mesh::DrawMode mode)
: draw_mode(mode)
: vbo(0)
, vertex_count(0)
, ibo(0)
, element_count(0)
, draw_mode(mode)
, image(0)
, colors_enabled(false)
{
setVertices(verts);
}
Mesh::~Mesh()
{
delete vbo;
delete ibo;
}
void Mesh::setVertices(const std::vector<Vertex> &verts)
{
if (verts.size() < 3)
throw love::Exception("At least 3 vertices are required.");
vertices = verts;
size_t size = sizeof(Vertex) * verts.size();
if (vbo && size > vbo->getSize())
{
delete vbo;
vbo = 0;
}
if (!vbo)
{
// Full memory backing because we might access the data at any time.
vbo = VertexBuffer::Create(size, GL_ARRAY_BUFFER, GL_DYNAMIC_DRAW, VertexBuffer::BACKING_FULL);
}
vertex_count = verts.size();
VertexBuffer::Bind vbo_bind(*vbo);
VertexBuffer::Mapper vbo_mapper(*vbo);
// Fill the buffer with the vertices.
memcpy(vbo_mapper.get(), &verts[0], size);
}
const std::vector<Vertex> &Mesh::getVertices() const
void Mesh::setVertex(size_t index, const Vertex &v)
{
return vertices;
if (index >= vertex_count)
throw love::Exception("Invalid vertex index: %ld", index);
VertexBuffer::Bind vbo_bind(*vbo);
// We unmap the vertex buffer in Mesh::draw. This lets us coalesce the
// buffer transfer calls.
Vertex *vertices = (Vertex *) vbo->map();
vertices[index] = v;
}
void Mesh::setVertex(size_t i, Vertex v)
Vertex Mesh::getVertex(size_t index) const
{
if (i >= vertices.size())
throw love::Exception("Invalid index.");
if (index >= vertex_count)
throw love::Exception("Invalid vertex index: %ld", index);
vertices[i] = v;
}
VertexBuffer::Bind vbo_bind(*vbo);
Vertex Mesh::getVertex(size_t i) const
{
if (i >= vertices.size())
throw love::Exception("Invalid index.");
return vertices[i];
// We unmap the vertex buffer in Mesh::draw.
Vertex *vertices = (Vertex *) vbo->map();
return vertices[index];
}
size_t Mesh::getVertexCount() const
{
return vertices.size();
return vertex_count;
}
void Mesh::setVertexMap(const std::vector<uint16> &map)
{
for (size_t i = 0; i < map.size(); i++)
{
if (map[i] >= vertices.size())
if (map[i] >= vertex_count)
throw love::Exception("Invalid vertex map value: %d", map[i]);
}
vertex_map = map;
size_t size = sizeof(uint16) * map.size();
if (ibo && size > ibo->getSize())
{
delete ibo;
ibo = 0;
}
if (!ibo)
{
// Full memory backing because we might access the data at any time.
ibo = VertexBuffer::Create(size, GL_ELEMENT_ARRAY_BUFFER, GL_DYNAMIC_DRAW, VertexBuffer::BACKING_FULL);
}
element_count = map.size();
if (element_count > 0)
{
VertexBuffer::Bind ibo_bind(*ibo);
VertexBuffer::Mapper ibo_map(*ibo);
// Fill the buffer.
memcpy(ibo_map.get(), &map[0], size);
}
}
const std::vector<uint16> &Mesh::getVertexMap() const
const uint16 *Mesh::getVertexMap() const
{
return vertex_map;
if (ibo && element_count > 0)
{
VertexBuffer::Bind ibo_bind(*ibo);
// We unmap the buffer in Mesh::draw and Mesh::setVertexMap.
return (uint16 *) ibo->map();
}
return 0;
}
Mesh::~Mesh()
size_t Mesh::getVertexMapCount() const
{
return element_count;
}
void Mesh::setImage(Image *img)
@@ -124,9 +191,23 @@ Mesh::DrawMode Mesh::getDrawMode() const
return draw_mode;
}
void Mesh::setVertexColors(bool enable)
{
colors_enabled = enable;
}
bool Mesh::hasVertexColors() const
{
return colors_enabled;
}
void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const
{
if (vertices.size() == 0)
const size_t pos_offset = offsetof(Vertex, x);
const size_t tex_offset = offsetof(Vertex, s);
const size_t color_offset = offsetof(Vertex, r);
if (vertex_count == 0)
return;
if (image)
@@ -140,29 +221,49 @@ void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, flo
glPushMatrix();
glMultMatrixf(m.getElements());
VertexBuffer::Bind vbo_bind(*vbo);
// Make sure the VBO isn't mapped when we draw (sends data to GPU if needed.)
vbo->unmap();
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glVertexPointer(2, GL_FLOAT, sizeof(Vertex), &(vertices[0].x));
glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), &(vertices[0].s));
glVertexPointer(2, GL_FLOAT, sizeof(Vertex), vbo->getPointer(pos_offset));
glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), vbo->getPointer(tex_offset));
if (hasVertexColors())
{
// Per-vertex colors.
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Vertex), &(vertices[0].r));
glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Vertex), vbo->getPointer(color_offset));
}
GLenum gl_draw_mode = getGLDrawMode(draw_mode);
GLenum mode = getGLDrawMode(draw_mode);
if (vertex_map.size() > 0)
glDrawElements(gl_draw_mode, vertex_map.size(), GL_UNSIGNED_SHORT, &vertex_map[0]);
if (element_count > 0)
{
VertexBuffer::Bind ibo_bind(*ibo);
// Make sure the index buffer isn't mapped (sends data to GPU if needed.)
ibo->unmap();
// Use the custom vertex map to draw the vertices.
glDrawElements(mode, element_count, GL_UNSIGNED_SHORT, ibo->getPointer(0));
}
else
glDrawArrays(gl_draw_mode, 0, vertices.size());
{
// Normal non-indexed drawing (no custom vertex map.)
glDrawArrays(mode, 0, vertex_count);
}
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
if (hasVertexColors())
{
glDisableClientState(GL_COLOR_ARRAY);
// Using the color array leaves the GL constant color undefined.
gl.setColor(gl.getColor());
}
+35 -14
View File
@@ -25,9 +25,9 @@
#include "common/int.h"
#include "common/math.h"
#include "common/StringMap.h"
#include "graphics/Volatile.h"
#include "graphics/Drawable.h"
#include "graphics/opengl/Image.h"
#include "Image.h"
#include "VertexBuffer.h"
// C++
#include <vector>
@@ -41,7 +41,7 @@ namespace opengl
/**
* Holds and draws arbitrary vertex geometry.
* Each vertex in a Mesh has a position, texture coordinate, and color.
* Each vertex in the Mesh has a position, texture coordinate, and color.
**/
class Mesh : public Drawable
{
@@ -71,18 +71,13 @@ public:
**/
void setVertices(const std::vector<Vertex> &verts);
/**
* Gets a reference to the vertices used in the Mesh.
**/
const std::vector<Vertex> &getVertices() const;
/**
* Sets an individual vertex in the Mesh.
* @param i The index into the list of vertices to use.
* @param index The index into the list of vertices to use.
* @param v The new vertex.
**/
void setVertex(size_t i, Vertex v);
Vertex getVertex(size_t i) const;
void setVertex(size_t index, const Vertex &v);
Vertex getVertex(size_t index) const;
/**
* Gets the total number of vertices in the Mesh.
@@ -96,7 +91,18 @@ public:
* {0, 1, 2, 3, 4, ...}
**/
void setVertexMap(const std::vector<uint16> &map);
const std::vector<uint16> &getVertexMap() const;
/**
* Gets a pointer to the vertex map array. The pointer is only valid until
* the next function call in the graphics module.
* May return null if the vertex map is empty.
**/
const uint16 *getVertexMap() const;
/**
* Gets the total number of elements in the vertex map array.
**/
size_t getVertexMapCount() const;
/**
* Sets the Image used when drawing the Mesh.
@@ -120,6 +126,13 @@ public:
void setDrawMode(DrawMode mode);
DrawMode getDrawMode() const;
/**
* Sets whether per-vertex colors are enabled. If this is disabled, the
* global color (love.graphics.setColor) will be used for the entire Mesh.
**/
void setVertexColors(bool enable);
bool hasVertexColors() const;
// Implements Drawable.
void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const;
@@ -130,13 +143,21 @@ private:
GLenum getGLDrawMode(DrawMode mode) const;
std::vector<Vertex> vertices;
std::vector<uint16> vertex_map;
// Vertex buffer.
VertexBuffer *vbo;
size_t vertex_count;
// Element (vertex index) buffer, for the vertex map.
VertexBuffer *ibo;
size_t element_count;
DrawMode draw_mode;
Image *image;
// Whether the per-vertex colors are used when drawing.
bool colors_enabled;
static StringMap<DrawMode, DRAW_MODE_MAX_ENUM>::Entry drawModeEntries[];
static StringMap<DrawMode, DRAW_MODE_MAX_ENUM> drawModes;
+32 -21
View File
@@ -42,25 +42,27 @@ namespace opengl
// VertexBuffer
VertexBuffer *VertexBuffer::Create(size_t size, GLenum target, GLenum usage)
VertexBuffer *VertexBuffer::Create(size_t size, GLenum target, GLenum usage, MemoryBacking backing)
{
try
{
// Try to create a VBO.
return new VBO(size, target, usage);
return new VBO(size, target, usage, backing);
}
catch(const love::Exception &)
{
// VBO not supported ... create regular array.
return new VertexArray(size, target, usage);
return new VertexArray(size, target, usage, backing);
}
}
VertexBuffer::VertexBuffer(size_t size, GLenum target, GLenum usage)
VertexBuffer::VertexBuffer(size_t size, GLenum target, GLenum usage, MemoryBacking backing)
: is_bound(false)
, is_mapped(false)
, size(size)
, target(target)
, usage(usage)
, backing(backing)
{
}
@@ -70,8 +72,8 @@ VertexBuffer::~VertexBuffer()
// VertexArray
VertexArray::VertexArray(size_t size, GLenum target, GLenum usage)
: VertexBuffer(size, target, usage)
VertexArray::VertexArray(size_t size, GLenum target, GLenum usage, MemoryBacking backing)
: VertexBuffer(size, target, usage, backing)
, buf(new char[size])
{
}
@@ -83,11 +85,13 @@ VertexArray::~VertexArray()
void *VertexArray::map()
{
is_mapped = true;
return buf;
}
void VertexArray::unmap()
{
is_mapped = false;
}
void VertexArray::bind()
@@ -112,20 +116,25 @@ const void *VertexArray::getPointer(size_t offset) const
// VBO
VBO::VBO(size_t size, GLenum target, GLenum usage)
: VertexBuffer(size, target, usage)
VBO::VBO(size_t size, GLenum target, GLenum usage, MemoryBacking backing)
: VertexBuffer(size, target, usage, backing)
, vbo(0)
, memory_map(0)
, is_mapped(false)
, is_dirty(true)
{
if (!(GLEE_ARB_vertex_buffer_object || GLEE_VERSION_1_5))
throw love::Exception("Not supported");
if (getMemoryBacking() == BACKING_FULL)
memory_map = malloc(getSize());
bool ok = load(false);
if (!ok)
{
free(memory_map);
throw love::Exception("Could not load VBO.");
}
}
VBO::~VBO()
@@ -150,10 +159,12 @@ void *VBO::map()
}
if (is_dirty)
{
glGetBufferSubDataARB(getTarget(), 0, getSize(), memory_map);
is_dirty = false;
}
is_mapped = true;
is_dirty = false;
return memory_map;
}
@@ -198,11 +209,10 @@ void VBO::unbind()
void VBO::fill(size_t offset, size_t size, const void *data)
{
if (is_mapped)
{
if (is_mapped || getMemoryBacking() == BACKING_FULL)
memcpy(static_cast<char *>(memory_map) + offset, data, size);
}
else
if (!is_mapped)
{
// Not all systems have access to some faster paths...
if (GLEE_APPLE_flush_buffer_range)
@@ -226,7 +236,8 @@ void VBO::fill(size_t offset, size_t size, const void *data)
glBufferSubDataARB(getTarget(), offset, size, data);
}
is_dirty = true;
if (getMemoryBacking() != BACKING_FULL)
is_dirty = true;
}
}
@@ -257,23 +268,23 @@ bool VBO::load(bool restore)
while (GL_NO_ERROR != glGetError())
/* clear error messages */;
// Note that if 'src' is '0', no data will be copied.
glBufferDataARB(getTarget(), getSize(), src, getUsage());
GLenum err = glGetError();
// We don't want to flush the entire buffer when we just modify a small
// portion of it (VBO::fill without VBO::map), so we'll handle the flushing
// ourselves when we can.
if (GLEE_APPLE_flush_buffer_range)
glBufferParameteriAPPLE(getTarget(), GL_BUFFER_FLUSHING_UNMAP_APPLE, GL_FALSE);
// Note that if 'src' is '0', no data will be copied.
glBufferDataARB(getTarget(), getSize(), src, getUsage());
GLenum err = glGetError();
return (GL_NO_ERROR == err);
}
void VBO::unload(bool save)
{
// Save data before unloading.
if (save)
// Save data before unloading, if we need to.
if (save && getMemoryBacking() == BACKING_PARTIAL)
{
VertexBuffer::Bind bind(*this);
+39 -13
View File
@@ -48,6 +48,18 @@ class VertexBuffer
{
public:
// Different guarantees for VertexBuffer data storage.
enum MemoryBacking
{
// The VertexBuffer is will have a valid copy of its data in main memory
// at all times.
BACKING_FULL,
// The VertexBuffer will have a valid copy of its data in main memory
// when it needs to be reloaded and when it's mapped.
BACKING_PARTIAL
};
/**
* Create a new VertexBuffer (either a plain vertex array, or a VBO),
* based on what's supported on the system.
@@ -58,9 +70,10 @@ public:
* @param size The size of the VertexBuffer (in bytes).
* @param target GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER.
* @param usage GL_DYNAMIC_DRAW, etc.
* @param backing Determines what guarantees are placed on the data.
* @return A new VertexBuffer.
*/
static VertexBuffer *Create(size_t size, GLenum target, GLenum usage);
static VertexBuffer *Create(size_t size, GLenum target, GLenum usage, MemoryBacking backing = BACKING_PARTIAL);
/**
* Constructor.
@@ -68,8 +81,9 @@ public:
* @param size The size of the VertexBuffer in bytes.
* @param target The target VertexBuffer object, e.g. GL_ARRAY_BUFFER.
* @param usage Usage hint, e.g. GL_DYNAMIC_DRAW.
* @param backing Determines what guarantees are placed on the data.
*/
VertexBuffer(size_t size, GLenum target, GLenum usage);
VertexBuffer(size_t size, GLenum target, GLenum usage, MemoryBacking backing = BACKING_PARTIAL);
/**
* Destructor. Does nothing, but must be declared virtual.
@@ -111,6 +125,16 @@ public:
return is_bound;
}
bool isMapped() const
{
return is_mapped;
}
MemoryBacking getMemoryBacking() const
{
return backing;
}
/**
* Map the VertexBuffer to client memory.
*
@@ -230,6 +254,9 @@ protected:
// Whether the buffer is currently bound.
bool is_bound;
// Whether the buffer is currently mapped to main memory.
bool is_mapped;
private:
// The size of the buffer, in bytes.
@@ -240,6 +267,9 @@ private:
// Usage hint. GL_[DYNAMIC, STATIC, STREAM]_DRAW.
GLenum usage;
//
MemoryBacking backing;
};
/**
@@ -253,9 +283,9 @@ class VertexArray : public VertexBuffer
public:
/**
* @copydoc VertexBuffer(int, GLenum, GLenum)
* @copydoc VertexBuffer(int, GLenum, GLenum, Backing)
*/
VertexArray(size_t size, GLenum target, GLenum usage);
VertexArray(size_t size, GLenum target, GLenum usage, MemoryBacking backing);
/**
* Frees the data we've allocated.
@@ -281,19 +311,19 @@ private:
* This will be used on all systems that support it. It's in general
* faster than vertex arrays, but especially in use-cases where there
* is no need to update the data every frame.
*/
**/
class VBO : public VertexBuffer, public Volatile
{
public:
/**
* @copydoc VertexBuffer(size_t, GLenum, GLenum)
*/
VBO(size_t size, GLenum target, GLenum usage);
* @copydoc VertexBuffer(size_t, GLenum, GLenum, Backing)
**/
VBO(size_t size, GLenum target, GLenum usage, MemoryBacking backing);
/**
* Deletes the VBOs from OpenGL.
*/
**/
virtual ~VBO();
// Implements VertexBuffer.
@@ -332,10 +362,6 @@ private:
// call to map().
void *memory_map;
// Set if the vbo currently operates on main instead of gpu
// memory.
bool is_mapped;
// Set if the buffer was modified while operating on gpu memory
// and needs to be synchronized.
bool is_dirty;
@@ -453,6 +453,8 @@ int w_newMesh(lua_State *L)
std::vector<Vertex> vertices;
vertices.reserve(vertex_count);
bool use_colors = false;
// Get the vertices from the table.
for (size_t i = 1; i <= vertex_count; i++)
{
@@ -477,6 +479,10 @@ int w_newMesh(lua_State *L)
v.b = (unsigned char) luaL_optinteger(L, -2, 255);
v.a = (unsigned char) luaL_optinteger(L, -1, 255);
// Enable per-vertex coloring if any color is not the default.
if (!use_colors && (v.r != 255 || v.g != 255 || v.b != 255 || v.a != 255))
use_colors = true;
lua_pop(L, 9);
vertices.push_back(v);
}
@@ -487,6 +493,8 @@ int w_newMesh(lua_State *L)
if (img)
t->setImage(img);
t->setVertexColors(use_colors);
luax_pushtype(L, "Mesh", GRAPHICS_MESH_T, t);
return 1;
}
+22 -47
View File
@@ -93,48 +93,6 @@ int w_Mesh_getVertex(lua_State *L)
return 8;
}
int w_Mesh_getVertices(lua_State *L)
{
Mesh *t = luax_checkmesh(L, 1);
const std::vector<Vertex> &vertices = t->getVertices();
lua_createtable(L, vertices.size(), 0);
for (size_t i = 0; i < vertices.size(); i++)
{
lua_createtable(L, 8, 0);
const Vertex &v = vertices[i];
lua_pushnumber(L, v.x);
lua_rawseti(L, -2, 1);
lua_pushnumber(L, v.y);
lua_rawseti(L, -2, 2);
lua_pushnumber(L, v.s);
lua_rawseti(L, -2, 3);
lua_pushnumber(L, v.t);
lua_rawseti(L, -2, 4);
lua_pushnumber(L, v.r);
lua_rawseti(L, -2, 5);
lua_pushnumber(L, v.g);
lua_rawseti(L, -2, 6);
lua_pushnumber(L, v.b);
lua_rawseti(L, -2, 7);
lua_pushnumber(L, v.a);
lua_rawseti(L, -2, 8);
lua_rawseti(L, -2, i + 1);
}
return 1;
}
int w_Mesh_getVertexCount(lua_State *L)
{
Mesh *t = luax_checkmesh(L, 1);
@@ -172,12 +130,14 @@ int w_Mesh_getVertexMap(lua_State *L)
{
Mesh *t = luax_checkmesh(L, 1);
const std::vector<uint16> &map = t->getVertexMap();
lua_createtable(L, map.size(), 0);
const uint16 *vertex_map = t->getVertexMap();
size_t elements = t->getVertexMapCount();
for (size_t i = 0; i < map.size(); i++)
lua_createtable(L, elements, 0);
for (size_t i = 0; i < elements; i++)
{
lua_pushinteger(L, map[i] + 1);
lua_pushinteger(L, lua_Integer(vertex_map[i]) + 1);
lua_rawseti(L, -2, i + 1);
}
@@ -238,11 +198,24 @@ int w_Mesh_getDrawMode(lua_State *L)
return 1;
}
int w_Mesh_setVertexColors(lua_State *L)
{
Mesh *t = luax_checkmesh(L, 1);
t->setVertexColors(luax_toboolean(L, 2));
return 0;
}
int w_Mesh_hasVertexColors(lua_State *L)
{
Mesh *t = luax_checkmesh(L, 1);
luax_pushboolean(L, t->hasVertexColors());
return 1;
}
static const luaL_Reg functions[] =
{
{ "setVertex", w_Mesh_setVertex },
{ "getVertex", w_Mesh_getVertex },
{ "getVertices", w_Mesh_getVertices },
{ "getVertexCount", w_Mesh_getVertexCount },
{ "setVertexMap", w_Mesh_setVertexMap },
{ "getVertexMap", w_Mesh_getVertexMap },
@@ -250,6 +223,8 @@ static const luaL_Reg functions[] =
{ "getImage", w_Mesh_getImage },
{ "setDrawMode", w_Mesh_setDrawMode },
{ "getDrawMode", w_Mesh_getDrawMode },
{ "setVertexColors", w_Mesh_setVertexColors },
{ "hasVertexColors", w_Mesh_hasVertexColors },
{ 0, 0 }
};
+2 -1
View File
@@ -36,7 +36,6 @@ Mesh *luax_checkmesh(lua_State *L, int idx);
int w_Mesh_setVertex(lua_State *L);
int w_Mesh_getVertex(lua_State *L);
int w_Mesh_getVertices(lua_State *L);
int w_Mesh_getVertexCount(lua_State *L);
int w_Mesh_setVertexMap(lua_State *L);
int w_Mesh_getVertexMap(lua_State *L);
@@ -44,6 +43,8 @@ int w_Mesh_setImage(lua_State *L);
int w_Mesh_getImage(lua_State *L);
int w_Mesh_setDrawMode(lua_State *L);
int w_Mesh_getDrawMode(lua_State *L);
int w_Mesh_setVertexColors(lua_State *L);
int w_Mesh_hasVertexColors(lua_State *L);
extern "C" int luaopen_mesh(lua_State *L);