From b59e3d9c35e5318966d999d0033ac27d76002126 Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Sun, 29 Sep 2013 21:24:19 -0300 Subject: [PATCH] Meshes now use vertex buffers for more efficient drawing. --HG-- branch : Mesh --- src/modules/graphics/opengl/Mesh.cpp | 159 ++++++++++++++---- src/modules/graphics/opengl/Mesh.h | 49 ++++-- src/modules/graphics/opengl/VertexBuffer.cpp | 53 +++--- src/modules/graphics/opengl/VertexBuffer.h | 52 ++++-- src/modules/graphics/opengl/wrap_Graphics.cpp | 8 + src/modules/graphics/opengl/wrap_Mesh.cpp | 69 +++----- src/modules/graphics/opengl/wrap_Mesh.h | 3 +- 7 files changed, 268 insertions(+), 125 deletions(-) diff --git a/src/modules/graphics/opengl/Mesh.cpp b/src/modules/graphics/opengl/Mesh.cpp index e1b77f733..deb54e9b4 100644 --- a/src/modules/graphics/opengl/Mesh.cpp +++ b/src/modules/graphics/opengl/Mesh.cpp @@ -31,64 +31,131 @@ namespace opengl { Mesh::Mesh(const std::vector &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 &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 &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 &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 &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()); } diff --git a/src/modules/graphics/opengl/Mesh.h b/src/modules/graphics/opengl/Mesh.h index 8e1983bc0..4515bd2c4 100644 --- a/src/modules/graphics/opengl/Mesh.h +++ b/src/modules/graphics/opengl/Mesh.h @@ -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 @@ -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 &verts); - /** - * Gets a reference to the vertices used in the Mesh. - **/ - const std::vector &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 &map); - const std::vector &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 vertices; - std::vector 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::Entry drawModeEntries[]; static StringMap drawModes; diff --git a/src/modules/graphics/opengl/VertexBuffer.cpp b/src/modules/graphics/opengl/VertexBuffer.cpp index bac1a8e13..fa45a8bbb 100644 --- a/src/modules/graphics/opengl/VertexBuffer.cpp +++ b/src/modules/graphics/opengl/VertexBuffer.cpp @@ -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(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); diff --git a/src/modules/graphics/opengl/VertexBuffer.h b/src/modules/graphics/opengl/VertexBuffer.h index 4e9c8f474..61b098172 100644 --- a/src/modules/graphics/opengl/VertexBuffer.h +++ b/src/modules/graphics/opengl/VertexBuffer.h @@ -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; diff --git a/src/modules/graphics/opengl/wrap_Graphics.cpp b/src/modules/graphics/opengl/wrap_Graphics.cpp index ad4b16eea..fa2c9bda8 100644 --- a/src/modules/graphics/opengl/wrap_Graphics.cpp +++ b/src/modules/graphics/opengl/wrap_Graphics.cpp @@ -453,6 +453,8 @@ int w_newMesh(lua_State *L) std::vector 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; } diff --git a/src/modules/graphics/opengl/wrap_Mesh.cpp b/src/modules/graphics/opengl/wrap_Mesh.cpp index 32ed7a8e0..41ebf0055 100644 --- a/src/modules/graphics/opengl/wrap_Mesh.cpp +++ b/src/modules/graphics/opengl/wrap_Mesh.cpp @@ -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 &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 &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 } }; diff --git a/src/modules/graphics/opengl/wrap_Mesh.h b/src/modules/graphics/opengl/wrap_Mesh.h index b6e09c924..7fa112a31 100644 --- a/src/modules/graphics/opengl/wrap_Mesh.h +++ b/src/modules/graphics/opengl/wrap_Mesh.h @@ -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);