#ifndef __OPENGLPLAYGROUND_VBOMANAGER_HH__ #define __OPENGLPLAYGROUND_VBOMANAGER_HH__ #include #include #include #include #include using namespace gl; class VBOManager { public: VBOManager() { } ~VBOManager() { } private: class VBO; static const size_t default_size = 1048576; static const size_t alignment = 8; class AllocFailed {}; public: class VBOAlloc { public: ~VBOAlloc() { if(_vbo) _vbo->free(*this); _vbo = nullptr; } VBOAlloc(VBOAlloc&& move) : _vbo(move._vbo), _ofs(move._ofs), _size(move._size) { move._vbo = nullptr; } VBOAlloc& operator=(VBOAlloc&& move) { _vbo = move._vbo; _ofs = move._ofs; _size = move._size; move._vbo = nullptr; return *this; } VBOAlloc() : _vbo(nullptr), _ofs(0), _size(0) { } size_t getOfs() const { assert(_vbo); return _ofs; } size_t getSize() const { assert(_vbo); return _size; } GLuint getVBOId() const { assert(_vbo); return _vbo->getID(); } private: VBOAlloc(VBO &vbo, size_t ofs, size_t size) : _vbo(&vbo), _ofs(ofs), _size(size) { } friend class VBOManager; friend class VBO; VBO *_vbo; size_t _ofs, _size; }; VBOAlloc alloc(size_t size, GLenum type = GL_STATIC_DRAW) { for(auto& vbo : _vbos[type]) { try { return vbo.alloc(size); } catch (AllocFailed &ex) { continue; } } _vbos[type].emplace_back((size>default_size)?size:default_size, type); return _vbos[type].back().alloc(size); } private: class VBO { public: VBO(size_t size = default_size, GLenum type = GL_STATIC_DRAW) : _bufID(0) { glGenBuffers(1, &_bufID); glBindBuffer(GL_ARRAY_BUFFER, _bufID); glBufferData(GL_ARRAY_BUFFER, size, NULL, type); _allocs.emplace_back(_Entry{size, false}); } VBO(VBO const& copy) = delete; VBO(VBO&& move) : _bufID(move._bufID), _allocs(move._allocs) { move._bufID = 0; } ~VBO() { for (auto ent : _allocs) { assert(!ent.used); } if (_bufID) glDeleteBuffers(1, &_bufID); _bufID = 0; } VBOAlloc alloc(size_t size) { if (size%alignment != 0) size += (alignment - (size%alignment)); size_t pos = 0; for (auto it = _allocs.begin();it != _allocs.end();++it) { if (!it->used && (it->size >= size)) { size_t leftover = it->size - size; it->used = true; it->size = size; if (leftover > 0) _allocs.insert(++it, _Entry{leftover, false}); printf("DEBUG: VBO: Allocated %lu @ %lu in %u\n", size, pos, _bufID); return VBOAlloc(*this, pos, size); } pos += it->size; } throw AllocFailed(); } void free(VBOAlloc& alloc) { size_t pos = 0; for (auto it = _allocs.begin();it != _allocs.end();++it) { if (pos == alloc._ofs) { assert(it->size == alloc._size); printf("DEBUG: VBO: Freed %lu @ %lu in %u\n", alloc._size, pos, _bufID); it->used = false; if ((std::next(it) != _allocs.end()) && !std::next(it)->used) { it->size += std::next(it)->size; _allocs.erase(std::next(it)); } if ((it != _allocs.begin()) && !std::prev(it)->used) { it->size += std::prev(it)->size; _allocs.erase(std::prev(it)); } } pos += it->size; } } GLuint getID() const { return _bufID; } private: GLuint _bufID; struct _Entry { size_t size; bool used; }; std::list<_Entry> _allocs; }; std::map > _vbos; }; #endif