/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield * * This library is open source and may be redistributed and/or modified under * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or * (at your option) any later version. The full license is in LICENSE file * included with this distribution, and on the openscenegraph.org website. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * OpenSceneGraph Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace osg; // static cache of deleted buffer object lists which can only // by completely deleted once the appropriate OpenGL context // is set. Used osg::BufferObject::deleteBufferObject(..) and flushDeletedBufferObjects(..) below. typedef std::multimap BufferObjectMap; typedef osg::buffered_object DeletedBufferObjectCache; static OpenThreads::Mutex s_mutex_deletedBufferObjectCache; static DeletedBufferObjectCache s_deletedBufferObjectCache; void BufferObject::deleteBufferObject(unsigned int contextID,GLuint globj) { if (globj!=0) { OpenThreads::ScopedLock lock(s_mutex_deletedBufferObjectCache); // insert the globj into the cache for the appropriate context. s_deletedBufferObjectCache[contextID].insert(BufferObjectMap::value_type(0,globj)); } } void BufferObject::flushDeletedBufferObjects(unsigned int contextID,double /*currentTime*/, double& availableTime) { // if no time available don't try to flush objects. if (availableTime<=0.0) return; const osg::Timer& timer = *osg::Timer::instance(); osg::Timer_t start_tick = timer.tick(); double elapsedTime = 0.0; { OpenThreads::ScopedLock lock(s_mutex_deletedBufferObjectCache); const Extensions* extensions = getExtensions(contextID,true); unsigned int noDeleted = 0; BufferObjectMap& dll = s_deletedBufferObjectCache[contextID]; BufferObjectMap::iterator ditr=dll.begin(); for(; ditr!=dll.end() && elapsedTimeglDeleteBuffers(1,&(ditr->second)); elapsedTime = timer.delta_s(start_tick,timer.tick()); ++noDeleted; } if (ditr!=dll.begin()) dll.erase(dll.begin(),ditr); // if (noDeleted!=0) notify(osg::NOTICE)<<"Number VBOs deleted = "< lock(s_mutex_deletedBufferObjectCache); BufferObjectMap& dll = s_deletedBufferObjectCache[contextID]; dll.clear(); } BufferObject::BufferObject(): _target(0), _usage(0), _totalSize(0) { } BufferObject::BufferObject(const BufferObject& bo,const CopyOp& copyop): Object(bo,copyop) { } BufferObject::~BufferObject() { releaseGLObjects(0); } void BufferObject::resizeGLObjectBuffers(unsigned int maxSize) { _bufferObjectList.resize(maxSize); } void BufferObject::releaseGLObjects(State* state) const { if (state) { unsigned int contextID = state->getContextID(); if (_bufferObjectList[contextID]) { deleteBufferObject(contextID,_bufferObjectList[contextID]); _bufferObjectList[contextID] = 0; } } else { for(unsigned int contextID=0;contextID<_bufferObjectList.size();++contextID) { if (_bufferObjectList[contextID]) { deleteBufferObject(contextID,_bufferObjectList[contextID]); _bufferObjectList[contextID] = 0; } } } } ////////////////////////////////////////////////////////////////////////////// // // Extension support // typedef buffered_value< ref_ptr > BufferedExtensions; static BufferedExtensions s_extensions; BufferObject::Extensions* BufferObject::getExtensions(unsigned int contextID,bool createIfNotInitalized) { if (!s_extensions[contextID] && createIfNotInitalized) s_extensions[contextID] = new BufferObject::Extensions(contextID); return s_extensions[contextID].get(); } void BufferObject::setExtensions(unsigned int contextID,Extensions* extensions) { s_extensions[contextID] = extensions; } BufferObject::Extensions::Extensions(unsigned int contextID) { setupGLExtensions(contextID); } BufferObject::Extensions::Extensions(const Extensions& rhs): Referenced() { _glGenBuffers = rhs._glGenBuffers; _glBindBuffer = rhs._glBindBuffer; _glBufferData = rhs._glBufferData; _glBufferSubData = rhs._glBufferSubData; _glDeleteBuffers = rhs._glDeleteBuffers; _glIsBuffer = rhs._glIsBuffer; _glGetBufferSubData = rhs._glGetBufferSubData; _glMapBuffer = rhs._glMapBuffer; _glUnmapBuffer = rhs._glUnmapBuffer; _glGetBufferParameteriv = rhs._glGetBufferParameteriv; _glGetBufferPointerv = rhs._glGetBufferPointerv; } void BufferObject::Extensions::lowestCommonDenominator(const Extensions& rhs) { if (!rhs._glGenBuffers) _glGenBuffers = rhs._glGenBuffers; if (!rhs._glBindBuffer) _glBindBuffer = rhs._glBindBuffer; if (!rhs._glBufferData) _glBufferData = rhs._glBufferData; if (!rhs._glBufferSubData) _glBufferSubData = rhs._glBufferSubData; if (!rhs._glDeleteBuffers) _glDeleteBuffers = rhs._glDeleteBuffers; if (!rhs._glIsBuffer) _glIsBuffer = rhs._glIsBuffer; if (!rhs._glGetBufferSubData) _glGetBufferSubData = rhs._glGetBufferSubData; if (!rhs._glMapBuffer) _glMapBuffer = rhs._glMapBuffer; if (!rhs._glUnmapBuffer) _glUnmapBuffer = rhs._glUnmapBuffer; if (!rhs._glGetBufferParameteriv) _glGetBufferParameteriv = rhs._glGetBufferParameteriv; if (!rhs._glGetBufferParameteriv) _glGetBufferPointerv = rhs._glGetBufferPointerv; } void BufferObject::Extensions::setupGLExtensions(unsigned int contextID) { setGLExtensionFuncPtr(_glGenBuffers, "glGenBuffers","glGenBuffersARB"); setGLExtensionFuncPtr(_glBindBuffer, "glBindBuffer","glBindBufferARB"); setGLExtensionFuncPtr(_glBufferData, "glBufferData","glBufferDataARB"); setGLExtensionFuncPtr(_glBufferSubData, "glBufferSubData","glBufferSubDataARB"); setGLExtensionFuncPtr(_glDeleteBuffers, "glDeleteBuffers","glDeleteBuffersARB"); setGLExtensionFuncPtr(_glIsBuffer, "glIsBuffer","glIsBufferARB"); setGLExtensionFuncPtr(_glGetBufferSubData, "glGetBufferSubData","glGetBufferSubDataARB"); setGLExtensionFuncPtr(_glMapBuffer, "glMapBuffer","glMapBufferARB"); setGLExtensionFuncPtr(_glUnmapBuffer, "glUnmapBuffer","glUnmapBufferARB"); setGLExtensionFuncPtr(_glGetBufferParameteriv, "glGetBufferParameteriv","glGetBufferParameterivARB"); setGLExtensionFuncPtr(_glGetBufferPointerv, "glGetBufferPointerv","glGetBufferPointervARB"); _isPBOSupported = osg::isGLExtensionSupported(contextID,"GL_ARB_pixel_buffer_object"); } void BufferObject::Extensions::glGenBuffers(GLsizei n, GLuint *buffers) const { if (_glGenBuffers) _glGenBuffers(n, buffers); else notify(WARN)<<"Error: glGenBuffers not supported by OpenGL driver"<second == array) break; } if (itr != _bufferEntryArrayPairs.end()) _bufferEntryArrayPairs.erase(itr); } void VertexBufferObject::setArray(unsigned int i, Array* array) { if (i+1>=_bufferEntryArrayPairs.size()) _bufferEntryArrayPairs.resize(i+1); _bufferEntryArrayPairs[i].second = array; _bufferEntryArrayPairs[i].first.modifiedCount.setAllElementsTo(0xffffffff); _bufferEntryArrayPairs[i].first.offset = 0; dirty(); } void VertexBufferObject::compileBuffer(State& state) const { unsigned int contextID = state.getContextID(); _compiledList[contextID] = 1; Extensions* extensions = getExtensions(contextID,true); // osg::notify(osg::NOTICE)<<"VertexBufferObject::compileBuffer frameNumber="<getFrameNumber()<getTotalDataSize(); if (bep.first.dataSize == 0) ++numNewArrays; } } bool copyAll = false; GLuint& vbo = buffer(contextID); if (vbo==0) { // building for the first time. _totalSize = totalSizeRequired; // don't generate buffer if size is zero. if (_totalSize==0) return; extensions->glGenBuffers(1, &vbo); extensions->glBindBuffer(_target, vbo); extensions->glBufferData(_target, _totalSize, NULL, _usage); copyAll = true; } else { extensions->glBindBuffer(_target, vbo); if (_totalSize != totalSizeRequired) { // resize vbo. _totalSize = totalSizeRequired; extensions->glBufferData(_target, _totalSize, NULL, _usage); copyAll = true; } } typedef std::map > SizePosMap_t; SizePosMap_t freeList; if (copyAll == false && numNewArrays > 0) { std::map usedList; for(BufferEntryArrayPairs::const_iterator itr = _bufferEntryArrayPairs.begin(); itr != _bufferEntryArrayPairs.end(); ++itr) { const BufferEntryArrayPair& bep = *itr; if (bep.second==NULL) continue; if (bep.first.dataSize == 0) continue; usedList[bep.first.offset] = bep.first.dataSize; } unsigned int numFreeBlocks = 0; unsigned int pos=0; for (std::map::const_iterator it=usedList.begin(); it!=usedList.end(); ++it) { unsigned int start = it->first; unsigned int length = it->second; if (pos < start) { freeList[start-pos].push_back(pos); ++numFreeBlocks; } pos = start+length; } if (pos < totalSizeRequired) { freeList[totalSizeRequired-pos].push_back(pos); ++numFreeBlocks; } if (numNewArrays < numFreeBlocks) { copyAll = true; // too fragmented, fallback to copyAll freeList.clear(); } } // osg::Timer_t start_tick = osg::Timer::instance()->tick(); void* vboMemory = 0; #if 0 vboMemory = extensions->glMapBuffer(_target, GL_WRITE_ONLY_ARB); #endif unsigned int offset = 0; for(BufferEntryArrayPairs::const_iterator itr = _bufferEntryArrayPairs.begin(); itr != _bufferEntryArrayPairs.end(); ++itr) { const BufferEntryArrayPair& bep = *itr; const Array* de = bep.second; if (de) { const unsigned int arraySize = de->getTotalDataSize(); if (copyAll || bep.first.modifiedCount[contextID] != bep.second->getModifiedCount() || bep.first.dataSize != arraySize) { // copy data across unsigned int newOffset = bep.first.offset; if (copyAll) { newOffset = offset; offset += arraySize; } else if (bep.first.dataSize == 0) { SizePosMap_t::iterator findIt = freeList.lower_bound(arraySize); if (findIt==freeList.end()) { osg::notify(osg::FATAL)<<"No suitable Memory in VBO found!"<second.back(); newOffset = oldOffset; if (findIt->first > arraySize) // using larger block { freeList[findIt->first-arraySize].push_back(oldOffset+arraySize); } findIt->second.pop_back(); if (findIt->second.empty()) { freeList.erase(findIt); } } bep.first.dataSize = arraySize; bep.first.modifiedCount[contextID] = de->getModifiedCount(); bep.first.offset = newOffset; de->setVertexBufferObjectOffset((GLvoid*)newOffset); // osg::notify(osg::NOTICE)<<" copying vertex buffer data "<getDataPointer(), bep.first.dataSize); else extensions->glBufferSubData(_target, bep.first.offset, bep.first.dataSize, de->getDataPointer()); } } } // Unmap the texture image buffer if (vboMemory) extensions->glUnmapBuffer(_target); // osg::notify(osg::NOTICE)<<"pbo _totalSize="<<_totalSize<delta_m(start_tick,osg::Timer::instance()->tick())<<"ms"<first.modifiedCount.resize(maxSize); } } ////////////////////////////////////////////////////////////////////////////////// // // ElementBufferObject // ElementBufferObject::ElementBufferObject() { _target = GL_ELEMENT_ARRAY_BUFFER_ARB; _usage = GL_STATIC_DRAW_ARB; } ElementBufferObject::ElementBufferObject(const ElementBufferObject& vbo,const CopyOp& copyop): BufferObject(vbo,copyop) { } ElementBufferObject::~ElementBufferObject() { } unsigned int ElementBufferObject::addDrawElements(osg::DrawElements* drawElements) { unsigned int i = _bufferEntryDrawElementsPairs.size(); _bufferEntryDrawElementsPairs.resize(i+1); _bufferEntryDrawElementsPairs[i].second = drawElements; _bufferEntryDrawElementsPairs[i].first.modifiedCount.setAllElementsTo(0xffffffff); _bufferEntryDrawElementsPairs[i].first.dataSize = 0; return i; } void ElementBufferObject::removeDrawElements(osg::DrawElements* drawElements) { BufferEntryDrawElementsPairs::iterator itr; for(itr = _bufferEntryDrawElementsPairs.begin(); itr != _bufferEntryDrawElementsPairs.end(); ++itr) { if (itr->second == drawElements) break; } if (itr != _bufferEntryDrawElementsPairs.end()) _bufferEntryDrawElementsPairs.erase(itr); } void ElementBufferObject::setDrawElements(unsigned int i, DrawElements* drawElements) { if (i+1>=_bufferEntryDrawElementsPairs.size()) _bufferEntryDrawElementsPairs.resize(i+1); _bufferEntryDrawElementsPairs[i].second = drawElements; _bufferEntryDrawElementsPairs[i].first.modifiedCount.setAllElementsTo(0xffffffff); _bufferEntryDrawElementsPairs[i].first.dataSize = 0; } void ElementBufferObject::compileBuffer(State& state) const { unsigned int contextID = state.getContextID(); _compiledList[contextID] = 1; // osg::notify(osg::NOTICE)<<"ElementBufferObject::compile"<getTotalDataSize(); } } bool copyAll = false; GLuint& ebo = buffer(contextID); if (ebo==0) { // building for the first time. _totalSize = totalSizeRequired; // don't generate buffer if size is zero. if (_totalSize==0) return; extensions->glGenBuffers(1, &ebo); extensions->glBindBuffer(_target, ebo); extensions->glBufferData(_target, _totalSize, NULL, _usage); copyAll = true; } else { extensions->glBindBuffer(_target, ebo); if (_totalSize != totalSizeRequired) { // resize EBO. _totalSize = totalSizeRequired; extensions->glBufferData(_target, _totalSize, NULL, _usage); copyAll = true; } } // osg::Timer_t start_tick = osg::Timer::instance()->tick(); void* eboMemory = 0; #if 0 eboMemory = extensions->glMapBuffer(_target, GL_WRITE_ONLY_ARB); #endif size_t offset = 0; for(BufferEntryDrawElementsPairs::const_iterator itr = _bufferEntryDrawElementsPairs.begin(); itr != _bufferEntryDrawElementsPairs.end(); ++itr) { const BufferEntryDrawElementsPair& bep = *itr; const DrawElements* de = bep.second; if (de) { if (copyAll || bep.first.modifiedCount[contextID] != bep.second->getModifiedCount() || bep.first.dataSize != bep.second->getTotalDataSize()) { // copy data across bep.first.dataSize = bep.second->getTotalDataSize(); bep.first.modifiedCount[contextID] = de->getModifiedCount(); if (copyAll) { bep.first.offset = offset; de->setElementBufferObjectOffset((GLvoid*)offset); offset += bep.first.dataSize; } if (eboMemory) memcpy((char*)eboMemory + bep.first.offset, de->getDataPointer(), bep.first.dataSize); else extensions->glBufferSubData(_target, bep.first.offset, bep.first.dataSize, de->getDataPointer()); } } } // Unmap the texture image buffer if (eboMemory) extensions->glUnmapBuffer(_target); // osg::notify(osg::NOTICE)<<"pbo _totalSize="<<_totalSize<delta_m(start_tick,osg::Timer::instance()->tick())<<"ms"<first.modifiedCount.resize(maxSize); } } ////////////////////////////////////////////////////////////////////////////////// // // PixelBufferObject // PixelBufferObject::PixelBufferObject(osg::Image* image): BufferObject() { _target = GL_PIXEL_UNPACK_BUFFER_ARB; _usage = GL_STREAM_DRAW_ARB; _bufferEntryImagePair.second = image; } /** Copy constructor using CopyOp to manage deep vs shallow copy.*/ PixelBufferObject::PixelBufferObject(const PixelBufferObject& buffer,const CopyOp& copyop): BufferObject(buffer,copyop), _bufferEntryImagePair(buffer._bufferEntryImagePair) { } PixelBufferObject::~PixelBufferObject() { } void PixelBufferObject::setImage(osg::Image* image) { if (_bufferEntryImagePair.second == image) return; _bufferEntryImagePair.second = image; dirty(); } void PixelBufferObject::compileBuffer(State& state) const { unsigned int contextID = state.getContextID(); _compiledList[contextID] = 1; osg::Image* image = _bufferEntryImagePair.second; _bufferEntryImagePair.first.modifiedCount[contextID] = image->getModifiedCount(); if (!image->valid()) return; Extensions* extensions = getExtensions(contextID,true); GLuint& pbo = buffer(contextID); if (pbo==0) { // building for the first time. _totalSize = image->getTotalSizeInBytes(); // don't generate buffer if size is zero. if (_totalSize==0) return; extensions->glGenBuffers(1, &pbo); extensions->glBindBuffer(_target, pbo); extensions->glBufferData(_target, _totalSize, NULL, _usage); } else { extensions->glBindBuffer(_target, pbo); if (_totalSize != image->getTotalSizeInBytes()) { // resize PBO. _totalSize = image->getTotalSizeInBytes(); extensions->glBufferData(_target, _totalSize, NULL, _usage); } } // osg::Timer_t start_tick = osg::Timer::instance()->tick(); void* pboMemory = extensions->glMapBuffer(_target, GL_WRITE_ONLY_ARB); // copy data across memcpy(pboMemory, image->data(), _totalSize); // Unmap the texture image buffer extensions->glUnmapBuffer(_target); _bufferEntryImagePair.first.modifiedCount[contextID] = image->getModifiedCount(); // osg::notify(osg::NOTICE)<<"pbo _totalSize="<<_totalSize<delta_m(start_tick,osg::Timer::instance()->tick())<<"ms"<glGenBuffers(1, &pbo); } extensions->glBindBuffer(_target, pbo); extensions->glBufferData(_target, _bufferData.dataSize, NULL, _usage); extensions->glBindBuffer(_target, 0); _compiledList[contextID] = 1; } //-------------------------------------------------------------------------------- void PixelDataBufferObject::bindBufferInReadMode(State& state) { unsigned int contextID = state.getContextID(); if (isDirty(contextID)) compileBuffer(state); Extensions* extensions = getExtensions(contextID,true); extensions->glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, buffer(contextID)); _mode[contextID] = READ; } //-------------------------------------------------------------------------------- void PixelDataBufferObject::bindBufferInWriteMode(State& state) { unsigned int contextID = state.getContextID(); if (isDirty(contextID)) compileBuffer(state); Extensions* extensions = getExtensions(contextID,true); extensions->glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, buffer(contextID)); _mode[contextID] = WRITE; } //-------------------------------------------------------------------------------- void PixelDataBufferObject::unbindBuffer(unsigned int contextID) const { Extensions* extensions = getExtensions(contextID,true); switch(_mode[contextID]) { case READ: extensions->glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB,0); break; case WRITE: extensions->glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB,0); break; default: extensions->glBindBuffer(_target,0); break; } _mode[contextID] = NONE; } //-------------------------------------------------------------------------------- void PixelDataBufferObject::resizeGLObjectBuffers(unsigned int maxSize) { BufferObject::resizeGLObjectBuffers(maxSize); _mode.resize(maxSize); }