/* -*-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. */ // initial FBO support written by Marco Jez, June 2005. #include #include #include #include #include #include #include #include #include #include #include using namespace osg; static buffered_object< ref_ptr > s_extensions; FBOExtensions* FBOExtensions::instance(unsigned contextID, bool createIfNotInitalized) { if (!s_extensions[contextID] && createIfNotInitalized) s_extensions[contextID] = new FBOExtensions(contextID); return s_extensions[contextID].get(); } /************************************************************************** * FBOExtensions **************************************************************************/ #define LOAD_FBO_EXT(name) setGLExtensionFuncPtr(name, (#name)) FBOExtensions::FBOExtensions(unsigned int contextID) : glBindRenderbufferEXT(0), glGenRenderbuffersEXT(0), glDeleteRenderbuffersEXT(0), glRenderbufferStorageEXT(0), glRenderbufferStorageMultisampleEXT(0), glRenderbufferStorageMultisampleCoverageNV(0), glBindFramebufferEXT(0), glDeleteFramebuffersEXT(0), glGenFramebuffersEXT(0), glCheckFramebufferStatusEXT(0), glFramebufferTexture1DEXT(0), glFramebufferTexture2DEXT(0), glFramebufferTexture3DEXT(0), glFramebufferTextureLayerEXT(0), glFramebufferRenderbufferEXT(0), glGenerateMipmapEXT(0), glBlitFramebufferEXT(0), _supported(false), _packed_depth_stencil_supported(false) { if (!isGLExtensionSupported(contextID, "GL_EXT_framebuffer_object")) return; LOAD_FBO_EXT(glBindRenderbufferEXT); LOAD_FBO_EXT(glGenRenderbuffersEXT); LOAD_FBO_EXT(glDeleteRenderbuffersEXT); LOAD_FBO_EXT(glRenderbufferStorageEXT); LOAD_FBO_EXT(glBindFramebufferEXT); LOAD_FBO_EXT(glDeleteFramebuffersEXT); LOAD_FBO_EXT(glGenFramebuffersEXT); LOAD_FBO_EXT(glCheckFramebufferStatusEXT); LOAD_FBO_EXT(glFramebufferTexture1DEXT); LOAD_FBO_EXT(glFramebufferTexture2DEXT); LOAD_FBO_EXT(glFramebufferTexture3DEXT); LOAD_FBO_EXT(glFramebufferTextureLayerEXT); LOAD_FBO_EXT(glFramebufferRenderbufferEXT); LOAD_FBO_EXT(glGenerateMipmapEXT); _supported = glBindRenderbufferEXT != 0 && glDeleteRenderbuffersEXT != 0 && glGenRenderbuffersEXT != 0 && glRenderbufferStorageEXT != 0 && glBindFramebufferEXT != 0 && glDeleteFramebuffersEXT != 0 && glGenFramebuffersEXT != 0 && glCheckFramebufferStatusEXT != 0 && glFramebufferTexture1DEXT != 0 && glFramebufferTexture2DEXT != 0 && glFramebufferTexture3DEXT != 0 && glFramebufferRenderbufferEXT != 0 && glGenerateMipmapEXT != 0; if (!isGLExtensionSupported(contextID, "GL_EXT_framebuffer_blit")) return; LOAD_FBO_EXT(glBlitFramebufferEXT); if (isGLExtensionSupported(contextID, "GL_EXT_framebuffer_multisample")) { LOAD_FBO_EXT(glRenderbufferStorageMultisampleEXT); } if (isGLExtensionSupported(contextID, "GL_NV_framebuffer_multisample_coverage")) { LOAD_FBO_EXT(glRenderbufferStorageMultisampleCoverageNV); } if (isGLExtensionSupported(contextID, "GL_EXT_packed_depth_stencil")) { _packed_depth_stencil_supported = true; } } /************************************************************************** * RenderBuffer **************************************************************************/ /////////////////////////////////////////////////////////////////////////// // static cache of glRenderbuffers flagged for deletion, which will actually // be deleted in the correct GL context. typedef std::list RenderBufferHandleList; typedef osg::buffered_object DeletedRenderBufferCache; static OpenThreads::Mutex s_mutex_deletedRenderBufferCache; static DeletedRenderBufferCache s_deletedRenderBufferCache; void RenderBuffer::deleteRenderBuffer(unsigned int contextID, GLuint rb) { if( rb ) { OpenThreads::ScopedLock lock(s_mutex_deletedRenderBufferCache); // add glProgram to the cache for the appropriate context. s_deletedRenderBufferCache[contextID].push_back(rb); } } void RenderBuffer::flushDeletedRenderBuffers(unsigned int contextID,double /*currentTime*/, double& availableTime) { // if no time available don't try to flush objects. if (availableTime<=0.0) return; const FBOExtensions* extensions = FBOExtensions::instance(contextID,true); if(!extensions || !extensions->isSupported() ) return; const osg::Timer& timer = *osg::Timer::instance(); osg::Timer_t start_tick = timer.tick(); double elapsedTime = 0.0; { OpenThreads::ScopedLock lock(s_mutex_deletedRenderBufferCache); RenderBufferHandleList& pList = s_deletedRenderBufferCache[contextID]; for(RenderBufferHandleList::iterator titr=pList.begin(); titr!=pList.end() && elapsedTimeglDeleteRenderbuffersEXT(1, &(*titr) ); titr = pList.erase( titr ); elapsedTime = timer.delta_s(start_tick,timer.tick()); } } availableTime -= elapsedTime; } void RenderBuffer::discardDeletedRenderBuffers(unsigned int contextID) { OpenThreads::ScopedLock lock(s_mutex_deletedRenderBufferCache); RenderBufferHandleList& pList = s_deletedRenderBufferCache[contextID]; pList.clear(); } RenderBuffer::RenderBuffer() : Object(), _internalFormat(GL_DEPTH_COMPONENT24), _width(512), _height(512), _samples(0), _colorSamples(0) { } RenderBuffer::RenderBuffer(int width, int height, GLenum internalFormat, int samples, int colorSamples) : Object(), _internalFormat(internalFormat), _width(width), _height(height), _samples(samples), _colorSamples(colorSamples) { } RenderBuffer::RenderBuffer(const RenderBuffer ©, const CopyOp ©op) : Object(copy, copyop), _internalFormat(copy._internalFormat), _width(copy._width), _height(copy._height), _samples(copy._samples), _colorSamples(copy._colorSamples) { } RenderBuffer::~RenderBuffer() { for(unsigned i=0; i<_objectID.size(); ++i) { if (_objectID[i]) deleteRenderBuffer(i, _objectID[i]); } } int RenderBuffer::getMaxSamples(unsigned int contextID, const FBOExtensions *ext) { static osg::buffered_value maxSamplesList; GLint& maxSamples = maxSamplesList[contextID]; if (!maxSamples && ext->isMultisampleSupported()) { glGetIntegerv(GL_MAX_SAMPLES_EXT, &maxSamples); } return maxSamples; } GLuint RenderBuffer::getObjectID(unsigned int contextID, const FBOExtensions *ext) const { GLuint &objectID = _objectID[contextID]; int &dirty = _dirty[contextID]; if (objectID == 0) { ext->glGenRenderbuffersEXT(1, &objectID); if (objectID == 0) return 0; dirty = 1; } if (dirty) { // bind and configure ext->glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, objectID); // framebuffer_multisample_coverage specification requires that coverage // samples must be >= color samples. if (_samples < _colorSamples) { notify(WARN) << "Coverage samples must be greater than or equal to color samples." " Setting coverage samples equal to color samples." << std::endl; const_cast(this)->setSamples(_colorSamples); } if (_samples > 0 && ext->isMultisampleCoverageSupported()) { int samples = minimum(_samples, getMaxSamples(contextID, ext)); int colorSamples = minimum(_colorSamples, samples); ext->glRenderbufferStorageMultisampleCoverageNV(GL_RENDERBUFFER_EXT, samples, colorSamples, _internalFormat, _width, _height); } else if (_samples > 0 && ext->isMultisampleSupported()) { int samples = minimum(_samples, getMaxSamples(contextID, ext)); ext->glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, samples, _internalFormat, _width, _height); } else { ext->glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, _internalFormat, _width, _height); } dirty = 0; } return objectID; } /************************************************************************** * FrameBufferAttachment **************************************************************************/ #ifndef GL_TEXTURE_CUBE_MAP_POSITIVE_X #define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 #endif struct FrameBufferAttachment::Pimpl { enum TargetType { RENDERBUFFER, TEXTURE1D, TEXTURE2D, TEXTURE3D, TEXTURECUBE, TEXTURERECT, TEXTURE2DARRAY }; TargetType targetType; ref_ptr renderbufferTarget; ref_ptr textureTarget; int cubeMapFace; int level; int zoffset; explicit Pimpl(TargetType ttype = RENDERBUFFER, int lev = 0) : targetType(ttype), cubeMapFace(0), level(lev), zoffset(0) { } Pimpl(const Pimpl ©) : targetType(copy.targetType), renderbufferTarget(copy.renderbufferTarget), textureTarget(copy.textureTarget), cubeMapFace(copy.cubeMapFace), level(copy.level), zoffset(copy.zoffset) { } }; FrameBufferAttachment::FrameBufferAttachment() { _ximpl = new Pimpl; } FrameBufferAttachment::FrameBufferAttachment(const FrameBufferAttachment ©) { _ximpl = new Pimpl(*copy._ximpl); } FrameBufferAttachment::FrameBufferAttachment(RenderBuffer* target) { _ximpl = new Pimpl(Pimpl::RENDERBUFFER); _ximpl->renderbufferTarget = target; } FrameBufferAttachment::FrameBufferAttachment(Texture1D* target, int level) { _ximpl = new Pimpl(Pimpl::TEXTURE1D, level); _ximpl->textureTarget = target; } FrameBufferAttachment::FrameBufferAttachment(Texture2D* target, int level) { _ximpl = new Pimpl(Pimpl::TEXTURE2D, level); _ximpl->textureTarget = target; } FrameBufferAttachment::FrameBufferAttachment(Texture3D* target, int zoffset, int level) { _ximpl = new Pimpl(Pimpl::TEXTURE3D, level); _ximpl->textureTarget = target; _ximpl->zoffset = zoffset; } FrameBufferAttachment::FrameBufferAttachment(Texture2DArray* target, int layer, int level) { _ximpl = new Pimpl(Pimpl::TEXTURE2DARRAY, level); _ximpl->textureTarget = target; _ximpl->zoffset = layer; } FrameBufferAttachment::FrameBufferAttachment(TextureCubeMap* target, int face, int level) { _ximpl = new Pimpl(Pimpl::TEXTURECUBE, level); _ximpl->textureTarget = target; _ximpl->cubeMapFace = face; } FrameBufferAttachment::FrameBufferAttachment(TextureRectangle* target) { _ximpl = new Pimpl(Pimpl::TEXTURERECT); _ximpl->textureTarget = target; } FrameBufferAttachment::FrameBufferAttachment(Camera::Attachment& attachment) { osg::Texture* texture = attachment._texture.get(); if (texture) { osg::Texture1D* texture1D = dynamic_cast(texture); if (texture1D) { _ximpl = new Pimpl(Pimpl::TEXTURE1D, attachment._level); _ximpl->textureTarget = texture1D; return; } osg::Texture2D* texture2D = dynamic_cast(texture); if (texture2D) { _ximpl = new Pimpl(Pimpl::TEXTURE2D, attachment._level); _ximpl->textureTarget = texture2D; return; } osg::Texture3D* texture3D = dynamic_cast(texture); if (texture3D) { _ximpl = new Pimpl(Pimpl::TEXTURE3D, attachment._level); _ximpl->textureTarget = texture3D; _ximpl->zoffset = attachment._face; return; } osg::Texture2DArray* texture2DArray = dynamic_cast(texture); if (texture2DArray) { _ximpl = new Pimpl(Pimpl::TEXTURE2DARRAY, attachment._level); _ximpl->textureTarget = texture2DArray; _ximpl->zoffset = attachment._face; return; } osg::TextureCubeMap* textureCubeMap = dynamic_cast(texture); if (textureCubeMap) { _ximpl = new Pimpl(Pimpl::TEXTURECUBE, attachment._level); _ximpl->textureTarget = textureCubeMap; _ximpl->cubeMapFace = attachment._face; return; } osg::TextureRectangle* textureRectangle = dynamic_cast(texture); if (textureRectangle) { _ximpl = new Pimpl(Pimpl::TEXTURERECT); _ximpl->textureTarget = textureRectangle; return; } } osg::Image* image = attachment._image.get(); if (image) { if (image->s()>0 && image->t()>0) { GLenum format = attachment._image->getInternalTextureFormat(); if (format == 0) format = attachment._internalFormat; _ximpl = new Pimpl(Pimpl::RENDERBUFFER); _ximpl->renderbufferTarget = new osg::RenderBuffer(image->s(), image->t(), format); } else { osg::notify(osg::WARN)<<"Error: FrameBufferAttachment::FrameBufferAttachment(Camera::Attachment&) passed an empty osg::Image, image must be allocated first."<renderbufferTarget.valid()) { return _ximpl->renderbufferTarget->getSamples() > 0; } return false; } void FrameBufferAttachment::createRequiredTexturesAndApplyGenerateMipMap(State &state, const FBOExtensions* ext) const { unsigned int contextID = state.getContextID(); // force compile texture if necessary Texture::TextureObject *tobj = 0; if (_ximpl->textureTarget.valid()) { tobj = _ximpl->textureTarget->getTextureObject(contextID); if (!tobj || tobj->_id == 0) { _ximpl->textureTarget->compileGLObjects(state); tobj = _ximpl->textureTarget->getTextureObject(contextID); } if (!tobj || tobj->_id == 0) return; Texture::FilterMode minFilter = _ximpl->textureTarget->getFilter(Texture::MIN_FILTER); if (minFilter==Texture::LINEAR_MIPMAP_LINEAR || minFilter==Texture::LINEAR_MIPMAP_NEAREST || minFilter==Texture::NEAREST_MIPMAP_LINEAR || minFilter==Texture::NEAREST_MIPMAP_NEAREST) { state.setActiveTextureUnit(0); state.applyTextureAttribute(0, _ximpl->textureTarget.get()); ext->glGenerateMipmapEXT(_ximpl->textureTarget->getTextureTarget()); } } } void FrameBufferAttachment::attach(State &state, GLenum target, GLenum attachment_point, const FBOExtensions* ext) const { unsigned int contextID = state.getContextID(); Texture::TextureObject *tobj = 0; if (_ximpl->textureTarget.valid()) { tobj = _ximpl->textureTarget->getTextureObject(contextID); if (!tobj || tobj->_id == 0) { _ximpl->textureTarget->compileGLObjects(state); tobj = _ximpl->textureTarget->getTextureObject(contextID); } if (!tobj || tobj->_id == 0) return; } switch (_ximpl->targetType) { default: case Pimpl::RENDERBUFFER: ext->glFramebufferRenderbufferEXT(target, attachment_point, GL_RENDERBUFFER_EXT, _ximpl->renderbufferTarget->getObjectID(contextID, ext)); break; case Pimpl::TEXTURE1D: ext->glFramebufferTexture1DEXT(target, attachment_point, GL_TEXTURE_1D, tobj->_id, _ximpl->level); break; case Pimpl::TEXTURE2D: ext->glFramebufferTexture2DEXT(target, attachment_point, GL_TEXTURE_2D, tobj->_id, _ximpl->level); break; case Pimpl::TEXTURE3D: ext->glFramebufferTexture3DEXT(target, attachment_point, GL_TEXTURE_3D, tobj->_id, _ximpl->level, _ximpl->zoffset); break; case Pimpl::TEXTURE2DARRAY: ext->glFramebufferTextureLayerEXT(target, attachment_point, tobj->_id, _ximpl->level, _ximpl->zoffset); break; case Pimpl::TEXTURERECT: ext->glFramebufferTexture2DEXT(target, attachment_point, GL_TEXTURE_RECTANGLE, tobj->_id, 0); break; case Pimpl::TEXTURECUBE: ext->glFramebufferTexture2DEXT(target, attachment_point, GL_TEXTURE_CUBE_MAP_POSITIVE_X + _ximpl->cubeMapFace, tobj->_id, _ximpl->level); break; } } int FrameBufferAttachment::compare(const FrameBufferAttachment &fa) const { if (&fa == this) return 0; if (_ximpl->targetType < fa._ximpl->targetType) return -1; if (_ximpl->targetType > fa._ximpl->targetType) return 1; if (_ximpl->renderbufferTarget.get() < fa._ximpl->renderbufferTarget.get()) return -1; if (_ximpl->renderbufferTarget.get() > fa._ximpl->renderbufferTarget.get()) return 1; if (_ximpl->textureTarget.get() < fa._ximpl->textureTarget.get()) return -1; if (_ximpl->textureTarget.get() > fa._ximpl->textureTarget.get()) return 1; if (_ximpl->cubeMapFace < fa._ximpl->cubeMapFace) return -1; if (_ximpl->cubeMapFace > fa._ximpl->cubeMapFace) return 1; if (_ximpl->level < fa._ximpl->level) return -1; if (_ximpl->level > fa._ximpl->level) return 1; if (_ximpl->zoffset < fa._ximpl->zoffset) return -1; if (_ximpl->zoffset > fa._ximpl->zoffset) return 1; return 0; } RenderBuffer* FrameBufferAttachment::getRenderBuffer() { return _ximpl->renderbufferTarget.get(); } Texture* FrameBufferAttachment::getTexture() { return _ximpl->textureTarget.get(); } const RenderBuffer* FrameBufferAttachment::getRenderBuffer() const { return _ximpl->renderbufferTarget.get(); } const Texture* FrameBufferAttachment::getTexture() const { return _ximpl->textureTarget.get(); } int FrameBufferAttachment::getCubeMapFace() const { return _ximpl->cubeMapFace; } int FrameBufferAttachment::getTextureLevel() const { return _ximpl->level; } int FrameBufferAttachment::getTexture3DZOffset() const { return _ximpl->zoffset; } int FrameBufferAttachment::getTextureArrayLayer() const { return _ximpl->zoffset; } /************************************************************************** * FrameBufferObject **************************************************************************/ /////////////////////////////////////////////////////////////////////////// // static cache of glRenderbuffers flagged for deletion, which will actually // be deleted in the correct GL context. typedef std::list FrameBufferObjectHandleList; typedef osg::buffered_object DeletedFrameBufferObjectCache; static OpenThreads::Mutex s_mutex_deletedFrameBufferObjectCache; static DeletedFrameBufferObjectCache s_deletedFrameBufferObjectCache; void FrameBufferObject::deleteFrameBufferObject(unsigned int contextID, GLuint rb) { if( rb ) { OpenThreads::ScopedLock lock(s_mutex_deletedFrameBufferObjectCache); // add glProgram to the cache for the appropriate context. s_deletedFrameBufferObjectCache[contextID].push_back(rb); } } void FrameBufferObject::flushDeletedFrameBufferObjects(unsigned int contextID,double /*currentTime*/, double& availableTime) { // if no time available don't try to flush objects. if (availableTime<=0.0) return; const FBOExtensions* extensions = FBOExtensions::instance(contextID,true); if(!extensions || !extensions->isSupported() ) return; const osg::Timer& timer = *osg::Timer::instance(); osg::Timer_t start_tick = timer.tick(); double elapsedTime = 0.0; { OpenThreads::ScopedLock lock(s_mutex_deletedFrameBufferObjectCache); FrameBufferObjectHandleList& pList = s_deletedFrameBufferObjectCache[contextID]; for(FrameBufferObjectHandleList::iterator titr=pList.begin(); titr!=pList.end() && elapsedTimeglDeleteFramebuffersEXT(1, &(*titr) ); titr = pList.erase( titr ); elapsedTime = timer.delta_s(start_tick,timer.tick()); } } availableTime -= elapsedTime; } void FrameBufferObject::discardDeletedFrameBufferObjects(unsigned int contextID) { OpenThreads::ScopedLock lock(s_mutex_deletedFrameBufferObjectCache); FrameBufferObjectHandleList& pList = s_deletedFrameBufferObjectCache[contextID]; pList.clear(); } FrameBufferObject::FrameBufferObject() : StateAttribute() { } FrameBufferObject::FrameBufferObject(const FrameBufferObject ©, const CopyOp ©op) : StateAttribute(copy, copyop), _attachments(copy._attachments), _drawBuffers(copy._drawBuffers) { } FrameBufferObject::~FrameBufferObject() { for(unsigned i=0; i<_fboID.size(); ++i) { if (_fboID[i]) deleteFrameBufferObject(i, _fboID[i]); } } void FrameBufferObject::setAttachment(BufferComponent attachment_point, const FrameBufferAttachment &attachment) { _attachments[attachment_point] = attachment; updateDrawBuffers(); dirtyAll(); } GLenum FrameBufferObject::convertBufferComponentToGLenum(BufferComponent attachment_point) const { switch(attachment_point) { case(Camera::DEPTH_BUFFER): return GL_DEPTH_ATTACHMENT_EXT; case(Camera::STENCIL_BUFFER): return GL_STENCIL_ATTACHMENT_EXT; case(Camera::COLOR_BUFFER): return GL_COLOR_ATTACHMENT0_EXT; default: return GLenum(GL_COLOR_ATTACHMENT0_EXT + (attachment_point-Camera::COLOR_BUFFER0)); } } void FrameBufferObject::updateDrawBuffers() { _drawBuffers.clear(); // create textures and mipmaps before we bind the frame buffer object for (AttachmentMap::const_iterator i=_attachments.begin(); i!=_attachments.end(); ++i) { // setup draw buffers based on the attachment definition if (i->first >= Camera::COLOR_BUFFER0 && i->first <= Camera::COLOR_BUFFER15) _drawBuffers.push_back(convertBufferComponentToGLenum(i->first)); } } void FrameBufferObject::apply(State &state) const { apply(state, READ_DRAW_FRAMEBUFFER); } void FrameBufferObject::apply(State &state, BindTarget target) const { unsigned int contextID = state.getContextID(); if (_unsupported[contextID]) return; FBOExtensions* ext = FBOExtensions::instance(contextID,true); if (!ext->isSupported()) { _unsupported[contextID] = 1; notify(WARN) << "Warning: EXT_framebuffer_object is not supported" << std::endl; return; } if (_attachments.empty()) { ext->glBindFramebufferEXT(target, 0); return; } int &dirtyAttachmentList = _dirtyAttachmentList[contextID]; GLuint &fboID = _fboID[contextID]; if (fboID == 0) { ext->glGenFramebuffersEXT(1, &fboID); if (fboID == 0) { notify(WARN) << "Warning: FrameBufferObject: could not create the FBO" << std::endl; return; } dirtyAttachmentList = 1; } if (dirtyAttachmentList) { // the set of of attachments appears to be thread sensitive, it shouldn't be because // OpenGL FBO handles osg::FrameBufferObject has are multi-buffered... // so as a temporary fix will stick in a mutex to ensure that only one thread passes through here // at one time. static OpenThreads::Mutex s_mutex; OpenThreads::ScopedLock lock(s_mutex); // create textures and mipmaps before we bind the frame buffer object for (AttachmentMap::const_iterator i=_attachments.begin(); i!=_attachments.end(); ++i) { const FrameBufferAttachment &fa = i->second; fa.createRequiredTexturesAndApplyGenerateMipMap(state, ext); } } ext->glBindFramebufferEXT(target, fboID); // enable drawing buffers to render the result to fbo if (_drawBuffers.size() > 0) { GL2Extensions *gl2e = GL2Extensions::Get(state.getContextID(), true ); if (gl2e) { gl2e->glDrawBuffers(_drawBuffers.size(), &(_drawBuffers[0])); } } if (dirtyAttachmentList) { for (AttachmentMap::const_iterator i=_attachments.begin(); i!=_attachments.end(); ++i) { const FrameBufferAttachment &fa = i->second; switch(i->first) { case(Camera::PACKED_DEPTH_STENCIL_BUFFER): if (ext->isPackedDepthStencilSupported()) { fa.attach(state, target, GL_DEPTH_ATTACHMENT_EXT, ext); fa.attach(state, target, GL_STENCIL_ATTACHMENT_EXT, ext); } else { notify(WARN) << "Warning: FrameBufferObject: could not attach PACKED_DEPTH_STENCIL_BUFFER, " "EXT_packed_depth_stencil is not supported !" << std::endl; } break; default: fa.attach(state, target, convertBufferComponentToGLenum(i->first), ext); break; } } dirtyAttachmentList = 0; } } bool FrameBufferObject::isMultisample() const { if (_attachments.size()) { // If the FBO is correctly set up then all attachments will be either // multisampled or single sampled. Therefore we can just return the // result of the first attachment. return _attachments.begin()->second.isMultisample(); } return false; } int FrameBufferObject::compare(const StateAttribute &sa) const { COMPARE_StateAttribute_Types(FrameBufferObject, sa); COMPARE_StateAttribute_Parameter(_attachments.size()); AttachmentMap::const_iterator i = _attachments.begin(); AttachmentMap::const_iterator j = rhs._attachments.begin(); for (; i!=_attachments.end(); ++i, ++j) { int cmp = i->second.compare(j->second); if (cmp != 0) return cmp; } return 0; }