/** * Copyright (c) 2006-2013 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 #include #include #include // Conflicts with std::numeric_limits::max() (Windows). #ifdef max # undef max #endif 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; } // 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) { if (!(GLEE_ARB_vertex_buffer_object || GLEE_VERSION_1_5)) throw love::Exception("Not supported"); 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) glGetBufferSubDataARB(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 glBufferDataARB(getTarget(), getSize(), NULL, getUsage()); glBufferDataARB(getTarget(), getSize(), memory_map, getUsage()); is_mapped = false; } void VBO::bind() { if (!is_mapped) glBindBufferARB(getTarget(), vbo); } void VBO::unbind() { if (!is_mapped) glBindBufferARB(getTarget(), 0); } void VBO::fill(size_t offset, size_t size, const void *data) { if (is_mapped) { memcpy(static_cast(memory_map) + offset, data, size); } else { glBufferSubDataARB(getTarget(), offset, size, data); is_dirty = true; } } const void *VBO::getPointer(size_t offset) const { return reinterpret_cast(offset); } bool VBO::loadVolatile() { return load(true); } void VBO::unloadVolatile() { unload(true); } bool VBO::load(bool restore) { glGenBuffersARB(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. glBufferDataARB(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; glGetBufferParameterivARB(getTarget(), GL_BUFFER_SIZE, &size); map(); // saves buffer content to memory_map. unmap(); } glDeleteBuffersARB(1, &vbo); vbo = 0; } // VertexIndex size_t VertexIndex::maxSize = 0; size_t VertexIndex::elementSize = 0; std::list VertexIndex::sizeRefs; VertexBuffer *VertexIndex::element_array = NULL; VertexIndex::VertexIndex(size_t size) : size(size) { // The upper limit is the maximum of GLuint divided by six (the number // of indices per size) and divided by the size of GLuint. This guarantees // no overflows when calculating the array size in bytes. // Memory issues will be handled by other exceptions. if (size == 0 || size > ((GLuint) -1) / 6 / sizeof(GLuint)) throw love::Exception("Invalid size."); addSize(size); } VertexIndex::~VertexIndex() { removeSize(size); } size_t VertexIndex::getSize() const { return size; } size_t VertexIndex::getIndexCount(size_t elements) const { return elements * 6; } GLenum VertexIndex::getType(size_t s) const { // Calculates if unsigned short is big enough to hold all the vertex indices. static const GLenum type_table[] = {GL_UNSIGNED_SHORT, GL_UNSIGNED_INT}; return type_table[s * 4 > std::numeric_limits::max()]; // if buffer-size > max(GLushort) then GL_UNSIGNED_INT else GL_UNSIGNED_SHORT } size_t VertexIndex::getElementSize() { return elementSize; } VertexBuffer *VertexIndex::getVertexBuffer() const { return element_array; } const void *VertexIndex::getPointer(size_t offset) const { return element_array->getPointer(offset); } void VertexIndex::addSize(size_t newSize) { if (newSize <= maxSize) { // Current size is bigger. Append the size to list and sort. sizeRefs.push_back(newSize); sizeRefs.sort(); return; } // Try to resize before adding it to the list because resize may throw. resize(newSize); sizeRefs.push_back(newSize); } void VertexIndex::removeSize(size_t oldSize) { // TODO: For debugging purposes, this should check if the size was actually found. sizeRefs.erase(std::find(sizeRefs.begin(), sizeRefs.end(), oldSize)); if (sizeRefs.size() == 0) { resize(0); return; } if (oldSize == maxSize) { // Shrink if there's a smaller size. size_t newSize = sizeRefs.back(); if (newSize < maxSize) resize(newSize); } } void VertexIndex::resize(size_t size) { if (size == 0) { delete element_array; element_array = NULL; maxSize = 0; return; } VertexBuffer *new_element_array; // Depending on the size, a switch to int and more memory is needed. GLenum target_type = getType(size); size_t elem_size = (target_type == GL_UNSIGNED_SHORT) ? sizeof(GLushort) : sizeof(GLuint); size_t array_size = elem_size * 6 * size; // Create may throw out-of-memory exceptions. // VertexIndex will propagate the exception and keep the old VertexBuffer. try { new_element_array = VertexBuffer::Create(array_size, GL_ELEMENT_ARRAY_BUFFER, GL_STATIC_DRAW); } catch (std::bad_alloc &) { throw love::Exception("Out of memory."); } // Allocation of the new VertexBuffer succeeded. // The old VertexBuffer can now be deleted. delete element_array; element_array = new_element_array; maxSize = size; elementSize = elem_size; switch (target_type) { case GL_UNSIGNED_SHORT: fill(); break; case GL_UNSIGNED_INT: fill(); break; } } template void VertexIndex::fill() { VertexBuffer::Bind bind(*element_array); VertexBuffer::Mapper mapper(*element_array); T *indices = (T *) mapper.get(); for (size_t i = 0; i < maxSize; ++i) { indices[i*6+0] = i * 4 + 0; indices[i*6+1] = i * 4 + 1; indices[i*6+2] = i * 4 + 2; indices[i*6+3] = i * 4 + 0; indices[i*6+4] = i * 4 + 2; indices[i*6+5] = i * 4 + 3; } } } // opengl } // graphics } // love