/* -*-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 using namespace osg; #ifndef GL_TEXTURE_WRAP_R #define GL_TEXTURE_WRAP_R 0x8072 #endif #ifndef GL_TEXTURE_MAX_LEVEL #define GL_TEXTURE_MAX_LEVEL 0x813D #endif #ifndef GL_UNPACK_CLIENT_STORAGE_APPLE #define GL_UNPACK_CLIENT_STORAGE_APPLE 0x85B2 #endif #ifndef GL_APPLE_vertex_array_range #define GL_VERTEX_ARRAY_RANGE_APPLE 0x851D #define GL_VERTEX_ARRAY_RANGE_LENGTH_APPLE 0x851E #define GL_VERTEX_ARRAY_STORAGE_HINT_APPLE 0x851F #define GL_VERTEX_ARRAY_RANGE_POINTER_APPLE 0x8521 #define GL_STORAGE_CACHED_APPLE 0x85BE #define GL_STORAGE_SHARED_APPLE 0x85BF #endif //#define DO_TIMING ApplicationUsageProxy Texture_e0(ApplicationUsage::ENVIRONMENTAL_VARIABLE,"OSG_MAX_TEXTURE_SIZE","Set the maximum size of textures."); class TextureObjectManager : public osg::Referenced { public: TextureObjectManager(): _expiryDelay(0.0) { // printf("Constructing TextureObjectManager\n"); } ~TextureObjectManager() { // printf("Destructing TextureObjectManager\n"); } virtual Texture::TextureObject* generateTextureObject(unsigned int contextID,GLenum target); virtual Texture::TextureObject* generateTextureObject(unsigned int contextID, GLenum target, GLint numMipmapLevels, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border); virtual Texture::TextureObject* reuseTextureObject(unsigned int contextID, GLenum target, GLint numMipmapLevels, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border); inline Texture::TextureObject* reuseOrGenerateTextureObject(unsigned int contextID, GLenum target, GLint numMipmapLevels, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border) { Texture::TextureObject* to = reuseTextureObject(contextID,target,numMipmapLevels,internalFormat,width,height,depth,border); if (to) return to; else return generateTextureObject(contextID,target,numMipmapLevels,internalFormat,width,height,depth,border); } void addTextureObjects(Texture::TextureObjectListMap& toblm); void addTextureObjectsFrom(Texture& texture); void flushAllTextureObjects(unsigned int contextID); void discardAllTextureObjects(unsigned int contextID); void flushTextureObjects(unsigned int contextID,double currentTime, double& availableTime); void setExpiryDelay(double expiryDelay) { _expiryDelay = expiryDelay; } double getExpiryDelay() const { return _expiryDelay; } /** How long to keep unused texture objects before deletion. */ double _expiryDelay; Texture::TextureObjectListMap _textureObjectListMap; // mutex to keep access serialized. OpenThreads::Mutex _mutex; }; unsigned int Texture::s_numberTextureReusedLastInLastFrame = 0; unsigned int Texture::s_numberNewTextureInLastFrame = 0; unsigned int Texture::s_numberDeletedTextureInLastFrame = 0; unsigned int s_minimumNumberOfTextureObjectsToRetainInCache = 0; typedef buffered_value< ref_ptr > BufferedExtensions; static BufferedExtensions s_extensions; static ref_ptr s_textureObjectManager = new TextureObjectManager; void Texture::setMinimumNumberOfTextureObjectsToRetainInCache(unsigned int minimum) { s_minimumNumberOfTextureObjectsToRetainInCache = minimum; } unsigned int Texture::getMinimumNumberOfTextureObjectsToRetainInCache() { return s_minimumNumberOfTextureObjectsToRetainInCache; } Texture::TextureObject* TextureObjectManager::generateTextureObject(unsigned int /*contextID*/,GLenum target) { GLuint id; glGenTextures( 1L, &id ); return new Texture::TextureObject(id,target); } static int s_number = 0; Texture::TextureObject* TextureObjectManager::generateTextureObject(unsigned int /*contextID*/, GLenum target, GLint numMipmapLevels, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border) { ++s_number; ++Texture::s_numberNewTextureInLastFrame; // notify(NOTICE)<<"creating new texture object "< lock(_mutex); Texture::TextureObjectList& tol = _textureObjectListMap[contextID]; for(Texture::TextureObjectList::iterator itr = tol.begin(); itr != tol.end(); ++itr) { if ((*itr)->match(target,numMipmapLevels,internalFormat,width,height,depth,border)) { // found usable texture object. Texture::TextureObject* textureObject = (*itr).release(); tol.erase(itr); // notify(NOTICE)<<"reusing texture object "< lock(_mutex); for(unsigned int i=0; i< toblm.size(); ++i) { Texture::TextureObjectList& tol = _textureObjectListMap[i]; tol.insert(tol.end(),toblm[i].begin(),toblm[i].end()); } } void TextureObjectManager::addTextureObjectsFrom(Texture& texture) { OpenThreads::ScopedLock lock(_mutex); texture.takeTextureObjects(_textureObjectListMap); } void TextureObjectManager::flushAllTextureObjects(unsigned int contextID) { OpenThreads::ScopedLock lock(_mutex); Texture::TextureObjectList& tol = _textureObjectListMap[contextID]; // osg::notify(osg::INFO)<<"Flushing texture objects num="<_id<_id)); } tol.clear(); } void TextureObjectManager::discardAllTextureObjects(unsigned int contextID) { OpenThreads::ScopedLock lock(_mutex); Texture::TextureObjectList& tol = _textureObjectListMap[contextID]; tol.clear(); } void TextureObjectManager::flushTextureObjects(unsigned int contextID,double currentTime, double& availableTime) { // if no time available don't try to flush objects. if (availableTime<=0.0) return; unsigned int numObjectsDeleted = 0; unsigned int maxNumObjectsToDelete = 4; const osg::Timer& timer = *osg::Timer::instance(); osg::Timer_t start_tick = timer.tick(); double elapsedTime = 0.0; unsigned int numTexturesDeleted = 0; { OpenThreads::ScopedLock lock(_mutex); Texture::TextureObjectList& tol = _textureObjectListMap[contextID]; // reset the time of any uninitialized objects. Texture::TextureObjectList::iterator itr; for(itr=tol.begin(); itr!=tol.end(); ++itr) { if ((*itr)->_timeStamp==0.0) (*itr)->_timeStamp=currentTime; } double expiryTime = currentTime-_expiryDelay; for(itr=tol.begin(); itr!=tol.end() && elapsedTimes_minimumNumberOfTextureObjectsToRetainInCache && numObjectsDeleted_timeStamp<=expiryTime) { --s_number; ++Texture::s_numberDeletedTextureInLastFrame; glDeleteTextures( 1L, &((*itr)->_id)); itr = tol.erase(itr); ++numTexturesDeleted; ++numObjectsDeleted; } else { ++itr; } elapsedTime = timer.delta_s(start_tick,timer.tick()); } } elapsedTime = timer.delta_s(start_tick,timer.tick()); // if (numTexturesDeleted) notify(osg::NOTICE)<<"Number of Texture's deleted = "<generateTextureObject(contextID,target); else return 0; } Texture::TextureObject* Texture::generateTextureObject(unsigned int contextID, GLenum target, GLint numMipmapLevels, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border) { if (getTextureObjectManager()) return getTextureObjectManager()->reuseOrGenerateTextureObject(contextID, target, numMipmapLevels, internalFormat, width, height, depth, border); else return 0; } void Texture::flushAllDeletedTextureObjects(unsigned int contextID) { if (getTextureObjectManager()) getTextureObjectManager()->flushAllTextureObjects(contextID); } void Texture::discardAllDeletedTextureObjects(unsigned int contextID) { if (getTextureObjectManager()) getTextureObjectManager()->discardAllTextureObjects(contextID); } void Texture::flushDeletedTextureObjects(unsigned int contextID,double currentTime, double& availbleTime) { if (getTextureObjectManager()) getTextureObjectManager()->flushTextureObjects(contextID, currentTime, availbleTime); } Texture::Texture(): _wrap_s(CLAMP), _wrap_t(CLAMP), _wrap_r(CLAMP), _min_filter(LINEAR_MIPMAP_LINEAR), // trilinear _mag_filter(LINEAR), _maxAnisotropy(1.0f), _useHardwareMipMapGeneration(true), _unrefImageDataAfterApply(false), _clientStorageHint(false), _resizeNonPowerOfTwoHint(true), _borderColor(0.0, 0.0, 0.0, 0.0), _borderWidth(0), _internalFormatMode(USE_IMAGE_DATA_FORMAT), _internalFormatType(NORMALIZED), _internalFormat(0), _sourceFormat(0), _sourceType(0), _use_shadow_comparison(false), _shadow_compare_func(LEQUAL), _shadow_texture_mode(LUMINANCE), _shadow_ambient(0) { } Texture::Texture(const Texture& text,const CopyOp& copyop): StateAttribute(text,copyop), _wrap_s(text._wrap_s), _wrap_t(text._wrap_t), _wrap_r(text._wrap_r), _min_filter(text._min_filter), _mag_filter(text._mag_filter), _maxAnisotropy(text._maxAnisotropy), _useHardwareMipMapGeneration(text._useHardwareMipMapGeneration), _unrefImageDataAfterApply(text._unrefImageDataAfterApply), _clientStorageHint(text._clientStorageHint), _resizeNonPowerOfTwoHint(text._resizeNonPowerOfTwoHint), _borderColor(text._borderColor), _borderWidth(text._borderWidth), _internalFormatMode(text._internalFormatMode), _internalFormatType(text._internalFormatType), _internalFormat(text._internalFormat), _sourceFormat(text._sourceFormat), _sourceType(text._sourceType), _use_shadow_comparison(text._use_shadow_comparison), _shadow_compare_func(text._shadow_compare_func), _shadow_texture_mode(text._shadow_texture_mode), _shadow_ambient(text._shadow_ambient) { } Texture::~Texture() { // delete old texture objects. dirtyTextureObject(); } int Texture::compareTexture(const Texture& rhs) const { COMPARE_StateAttribute_Parameter(_wrap_s) COMPARE_StateAttribute_Parameter(_wrap_t) COMPARE_StateAttribute_Parameter(_wrap_r) COMPARE_StateAttribute_Parameter(_min_filter) COMPARE_StateAttribute_Parameter(_mag_filter) COMPARE_StateAttribute_Parameter(_maxAnisotropy) COMPARE_StateAttribute_Parameter(_useHardwareMipMapGeneration) COMPARE_StateAttribute_Parameter(_internalFormatMode) // only compare _internalFomat is it has alrady been set in both lhs, and rhs if (_internalFormat!=0 && rhs._internalFormat!=0) { COMPARE_StateAttribute_Parameter(_internalFormat) } COMPARE_StateAttribute_Parameter(_sourceFormat) COMPARE_StateAttribute_Parameter(_sourceType) COMPARE_StateAttribute_Parameter(_use_shadow_comparison) COMPARE_StateAttribute_Parameter(_shadow_compare_func) COMPARE_StateAttribute_Parameter(_shadow_texture_mode) COMPARE_StateAttribute_Parameter(_shadow_ambient) COMPARE_StateAttribute_Parameter(_unrefImageDataAfterApply) COMPARE_StateAttribute_Parameter(_clientStorageHint) COMPARE_StateAttribute_Parameter(_resizeNonPowerOfTwoHint) COMPARE_StateAttribute_Parameter(_internalFormatType); return 0; } int Texture::compareTextureObjects(const Texture& rhs) const { if (_textureObjectBuffer.size()addTextureObjectsFrom(*this); } void Texture::takeTextureObjects(Texture::TextureObjectListMap& toblm) { for(unsigned int i = 0; i<_textureObjectBuffer.size();++i) { if (_textureObjectBuffer[i].valid()) { //notify(INFO) << "releasing texture "<isTextureCompressionARBSupported()) { switch(image.getPixelFormat()) { case(1): internalFormat = GL_COMPRESSED_ALPHA_ARB; break; case(2): internalFormat = GL_COMPRESSED_LUMINANCE_ALPHA_ARB; break; case(3): internalFormat = GL_COMPRESSED_RGB_ARB; break; case(4): internalFormat = GL_COMPRESSED_RGBA_ARB; break; case(GL_RGB): internalFormat = GL_COMPRESSED_RGB_ARB; break; case(GL_RGBA): internalFormat = GL_COMPRESSED_RGBA_ARB; break; case(GL_ALPHA): internalFormat = GL_COMPRESSED_ALPHA_ARB; break; case(GL_LUMINANCE): internalFormat = GL_COMPRESSED_LUMINANCE_ARB; break; case(GL_LUMINANCE_ALPHA): internalFormat = GL_COMPRESSED_LUMINANCE_ALPHA_ARB; break; case(GL_INTENSITY): internalFormat = GL_COMPRESSED_INTENSITY_ARB; break; } } else internalFormat = image.getInternalTextureFormat(); break; case(USE_S3TC_DXT1_COMPRESSION): if (extensions->isTextureCompressionS3TCSupported()) { switch(image.getPixelFormat()) { case(3): internalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; break; case(4): internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; break; case(GL_RGB): internalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; break; case(GL_RGBA): internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; break; default: internalFormat = image.getInternalTextureFormat(); break; } } else internalFormat = image.getInternalTextureFormat(); break; case(USE_S3TC_DXT3_COMPRESSION): if (extensions->isTextureCompressionS3TCSupported()) { switch(image.getPixelFormat()) { case(3): case(GL_RGB): internalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; break; case(4): case(GL_RGBA): internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break; default: internalFormat = image.getInternalTextureFormat(); break; } } else internalFormat = image.getInternalTextureFormat(); break; case(USE_S3TC_DXT5_COMPRESSION): if (extensions->isTextureCompressionS3TCSupported()) { switch(image.getPixelFormat()) { case(3): case(GL_RGB): internalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; break; case(4): case(GL_RGBA): internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; default: internalFormat = image.getInternalTextureFormat(); break; } } else internalFormat = image.getInternalTextureFormat(); break; default: break; } } _internalFormat = internalFormat; computeInternalFormatType(); //osg::notify(osg::NOTICE)<<"Internal format="< _sourceFormat = GL_RGBA_INTEGER_EXT) // Should we do this? ( Art, 09. Sept. 2007) // compute internal format type based on the internal format switch(_internalFormat) { case GL_RGBA32UI_EXT: case GL_RGBA16UI_EXT: case GL_RGBA8UI_EXT: case GL_RGB32UI_EXT: case GL_RGB16UI_EXT: case GL_RGB8UI_EXT: case GL_LUMINANCE32UI_EXT: case GL_LUMINANCE16UI_EXT: case GL_LUMINANCE8UI_EXT: case GL_INTENSITY32UI_EXT: case GL_INTENSITY16UI_EXT: case GL_INTENSITY8UI_EXT: case GL_LUMINANCE_ALPHA32UI_EXT: case GL_LUMINANCE_ALPHA16UI_EXT: case GL_LUMINANCE_ALPHA8UI_EXT : _internalFormatType = UNSIGNED_INTEGER; break; case GL_RGBA32I_EXT: case GL_RGBA16I_EXT: case GL_RGBA8I_EXT: case GL_RGB32I_EXT: case GL_RGB16I_EXT: case GL_RGB8I_EXT: case GL_LUMINANCE32I_EXT: case GL_LUMINANCE16I_EXT: case GL_LUMINANCE8I_EXT: case GL_INTENSITY32I_EXT: case GL_INTENSITY16I_EXT: case GL_INTENSITY8I_EXT: case GL_LUMINANCE_ALPHA32I_EXT: case GL_LUMINANCE_ALPHA16I_EXT: case GL_LUMINANCE_ALPHA8I_EXT: _internalFormatType = SIGNED_INTEGER; break; case GL_RGBA32F_ARB: case GL_RGBA16F_ARB: case GL_RGB32F_ARB: case GL_RGB16F_ARB: case GL_LUMINANCE32F_ARB: case GL_LUMINANCE16F_ARB: case GL_INTENSITY32F_ARB: case GL_INTENSITY16F_ARB: case GL_LUMINANCE_ALPHA32F_ARB: case GL_LUMINANCE_ALPHA16F_ARB: _internalFormatType = FLOAT; break; default: _internalFormatType = NORMALIZED; break; }; } bool Texture::isCompressedInternalFormat() const { return isCompressedInternalFormat(getInternalFormat()); } bool Texture::isCompressedInternalFormat(GLint internalFormat) { switch(internalFormat) { case(GL_COMPRESSED_ALPHA_ARB): case(GL_COMPRESSED_INTENSITY_ARB): case(GL_COMPRESSED_LUMINANCE_ALPHA_ARB): case(GL_COMPRESSED_LUMINANCE_ARB): case(GL_COMPRESSED_RGBA_ARB): case(GL_COMPRESSED_RGB_ARB): case(GL_COMPRESSED_RGB_S3TC_DXT1_EXT): case(GL_COMPRESSED_RGBA_S3TC_DXT1_EXT): case(GL_COMPRESSED_RGBA_S3TC_DXT3_EXT): case(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT): return true; default: return false; } } void Texture::getCompressedSize(GLenum internalFormat, GLint width, GLint height, GLint depth, GLint& blockSize, GLint& size) { if (internalFormat == GL_COMPRESSED_RGB_S3TC_DXT1_EXT || internalFormat == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) blockSize = 8; else if (internalFormat == GL_COMPRESSED_RGBA_S3TC_DXT3_EXT || internalFormat == GL_COMPRESSED_RGBA_S3TC_DXT5_EXT) blockSize = 16; else { notify(WARN)<<"Texture::getCompressedSize(...) : cannot compute correct size of compressed format ("<isTextureMirroredRepeatSupported()) { if (ws == MIRROR) ws = REPEAT; if (wt == MIRROR) wt = REPEAT; if (wr == MIRROR) wr = REPEAT; } // GL_EXT_texture_edge_clamp, fall-back CLAMP if (!extensions->isTextureEdgeClampSupported()) { if (ws == CLAMP_TO_EDGE) ws = CLAMP; if (wt == CLAMP_TO_EDGE) wt = CLAMP; if (wr == CLAMP_TO_EDGE) wr = CLAMP; } if(!extensions->isTextureBorderClampSupported()) { if(ws == CLAMP_TO_BORDER) ws = CLAMP; if(wt == CLAMP_TO_BORDER) wt = CLAMP; if(wr == CLAMP_TO_BORDER) wr = CLAMP; } const Image * image = getImage(0); if( image && image->isMipmap() && extensions->isTextureMaxLevelSupported() && int( image->getNumMipmapLevels() ) < Image::computeNumberOfMipmapLevels( image->s(), image->t(), image->r() ) ) glTexParameteri( target, GL_TEXTURE_MAX_LEVEL, image->getNumMipmapLevels() - 1 ); glTexParameteri( target, GL_TEXTURE_WRAP_S, ws ); if (target!=GL_TEXTURE_1D) glTexParameteri( target, GL_TEXTURE_WRAP_T, wt ); if (target==GL_TEXTURE_3D) glTexParameteri( target, GL_TEXTURE_WRAP_R, wr ); glTexParameteri( target, GL_TEXTURE_MIN_FILTER, _min_filter); glTexParameteri( target, GL_TEXTURE_MAG_FILTER, _mag_filter); // Art: I think anisotropic filtering is not supported by the integer textures if (extensions->isTextureFilterAnisotropicSupported() && _internalFormatType != SIGNED_INTEGER && _internalFormatType != UNSIGNED_INTEGER) { // note, GL_TEXTURE_MAX_ANISOTROPY_EXT will either be defined // by gl.h (or via glext.h) or by include/osg/Texture. glTexParameterf(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, _maxAnisotropy); } if (extensions->isTextureBorderClampSupported()) { if (_internalFormatType == SIGNED_INTEGER) { GLint color[4] = {(GLint)_borderColor.r(), (GLint)_borderColor.g(), (GLint)_borderColor.b(), (GLint)_borderColor.a()}; extensions->glTexParameterIiv(target, GL_TEXTURE_BORDER_COLOR, color); }else if (_internalFormatType == UNSIGNED_INTEGER) { GLuint color[4] = {(GLuint)_borderColor.r(), (GLuint)_borderColor.g(), (GLuint)_borderColor.b(), (GLuint)_borderColor.a()}; extensions->glTexParameterIuiv(target, GL_TEXTURE_BORDER_COLOR, color); }else{ GLfloat color[4] = {(GLfloat)_borderColor.r(), (GLfloat)_borderColor.g(), (GLfloat)_borderColor.b(), (GLfloat)_borderColor.a()}; glTexParameterfv(target, GL_TEXTURE_BORDER_COLOR, color); } } // integer texture are not supported by the shadow if (extensions->isShadowSupported() && (target == GL_TEXTURE_2D || target == GL_TEXTURE_RECTANGLE) && _internalFormatType != SIGNED_INTEGER && _internalFormatType != UNSIGNED_INTEGER) { if (_use_shadow_comparison) { glTexParameteri(target, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB); glTexParameteri(target, GL_TEXTURE_COMPARE_FUNC_ARB, _shadow_compare_func); glTexParameteri(target, GL_DEPTH_TEXTURE_MODE_ARB, _shadow_texture_mode); // if ambient value is 0 - it is default behaviour of GL_ARB_shadow // no need for GL_ARB_shadow_ambient in this case if (extensions->isShadowAmbientSupported() && _shadow_ambient > 0) { glTexParameterf(target, TEXTURE_COMPARE_FAIL_VALUE_ARB, _shadow_ambient); } } else { glTexParameteri(target, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE); } } getTextureParameterDirty(state.getContextID()) = false; } void Texture::computeRequiredTextureDimensions(State& state, const osg::Image& image,GLsizei& inwidth, GLsizei& inheight,GLsizei& numMipmapLevels) const { const unsigned int contextID = state.getContextID(); const Extensions* extensions = getExtensions(contextID,true); int width,height; if( !_resizeNonPowerOfTwoHint && extensions->isNonPowerOfTwoTextureSupported(_min_filter) ) { width = image.s(); height = image.t(); } else { width = Image::computeNearestPowerOfTwo(image.s()-2*_borderWidth)+2*_borderWidth; height = Image::computeNearestPowerOfTwo(image.t()-2*_borderWidth)+2*_borderWidth; } // cap the size to what the graphics hardware can handle. if (width>extensions->maxTextureSize()) width = extensions->maxTextureSize(); if (height>extensions->maxTextureSize()) height = extensions->maxTextureSize(); inwidth = width; inheight = height; bool useHardwareMipMapGeneration = !image.isMipmap() && isHardwareMipmapGenerationEnabled(state); if( _min_filter == LINEAR || _min_filter == NEAREST || useHardwareMipMapGeneration ) { numMipmapLevels = 1; } else if( image.isMipmap() ) { numMipmapLevels = image.getNumMipmapLevels(); } else { numMipmapLevels = 0; for( ; (width || height) ;++numMipmapLevels) { if (width == 0) width = 1; if (height == 0) height = 1; width >>= 1; height >>= 1; } } // osg::notify(osg::NOTICE)<<"Texture::computeRequiredTextureDimensions() image.s() "<isNonPowerOfTwoTextureSupported(_min_filter)="<isNonPowerOfTwoTextureSupported(_min_filter) <getMaxNumberOfGraphicsContexts();++i) { if (_textureObjectBuffer[i]==0) return false; } return true; } void Texture::applyTexImage2D_load(State& state, GLenum target, const Image* image, GLsizei inwidth, GLsizei inheight,GLsizei numMipmapLevels) const { // if we don't have a valid image we can't create a texture! if (!image || !image->data()) return; #ifdef DO_TIMING osg::Timer_t start_tick = osg::Timer::instance()->tick(); osg::notify(osg::NOTICE)<<"glTexImage2D pixelFormat = "<getPixelFormat()<getPixelFormat()); // If the texture's internal format is a compressed type, then the // user is requesting that the graphics card compress the image if it's // not already compressed. However, if the image is not a multiple of // four in each dimension the subsequent calls to glTexSubImage* will // fail. Revert to uncompressed format in this case. if (isCompressedInternalFormat(_internalFormat) && (((inwidth >> 2) << 2) != inwidth || ((inheight >> 2) << 2) != inheight)) { osg::notify(osg::NOTICE)<<"Received a request to compress an image, but image size is not a multiple of four ("<getPacking()); bool useClientStorage = extensions->isClientStorageSupported() && getClientStorageHint(); if (useClientStorage) { glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE,GL_TRUE); glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_PRIORITY,0.0f); #ifdef GL_TEXTURE_STORAGE_HINT_APPLE glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_STORAGE_HINT_APPLE , GL_STORAGE_CACHED_APPLE); #endif } unsigned char* data = (unsigned char*)image->data(); // osg::notify(osg::NOTICE)<<"inwidth="<getFileName()"<getFileName()<s() || inheight!=image->t(); if (needImageRescale) { // resize the image to power of two. if (image->isMipmap()) { notify(WARN)<<"Warning:: Mipmapped osg::Image not a power of two, cannot apply to texture."<getPixelFormat(),image->getDataType(),image->getPacking())*inheight; data = new unsigned char [newTotalSize]; if (!data) { notify(WARN)<<"Warning:: Not enough memory to resize image, cannot apply to texture."<getFileName().empty()) notify(NOTICE) << "Scaling image '"<getFileName()<<"' from ("<s()<<","<t()<<") to ("<s()<<","<t()<<") to ("<getPacking()); gluScaleImage(image->getPixelFormat(), image->s(),image->t(),image->getDataType(),image->data(), inwidth,inheight,image->getDataType(),data); } bool mipmappingRequired = _min_filter != LINEAR && _min_filter != NEAREST; bool useHardwareMipMapGeneration = mipmappingRequired && (!image->isMipmap() && isHardwareMipmapGenerationEnabled(state)); bool useGluBuildMipMaps = mipmappingRequired && (!useHardwareMipMapGeneration && !image->isMipmap()); unsigned char* dataMinusOffset = 0; unsigned char* dataPlusOffset = 0; const PixelBufferObject* pbo = image->getPixelBufferObject(); if (pbo && pbo->isPBOSupported(contextID) && !needImageRescale && !useGluBuildMipMaps) { state.bindPixelBufferObject(pbo); dataMinusOffset = data; dataPlusOffset = reinterpret_cast(pbo->offset()); #ifdef DO_TIMING osg::notify(osg::NOTICE)<<"after PBO "<delta_m(start_tick,osg::Timer::instance()->tick())<<"ms"<getPixelFormat(), (GLenum)image->getDataType(), data -dataMinusOffset+dataPlusOffset); } else if (extensions->isCompressedTexImage2DSupported()) { numMipmapLevels = 1; GLint blockSize, size; getCompressedSize(_internalFormat, inwidth, inheight, 1, blockSize,size); extensions->glCompressedTexImage2D(target, 0, _internalFormat, inwidth, inheight,0, size, data-dataMinusOffset+dataPlusOffset); } mipmapAfterTexImage(state, mipmapResult); } else { // we require mip mapping. if(image->isMipmap()) { // image is mip mapped so we take the mip map levels from the image. numMipmapLevels = image->getNumMipmapLevels(); int width = inwidth; int height = inheight; if( !compressed_image ) { for( GLsizei k = 0 ; k < numMipmapLevels && (width || height) ;k++) { if (width == 0) width = 1; if (height == 0) height = 1; glTexImage2D( target, k, _internalFormat, width, height, _borderWidth, (GLenum)image->getPixelFormat(), (GLenum)image->getDataType(), image->getMipmapData(k)-dataMinusOffset+dataPlusOffset); width >>= 1; height >>= 1; } } else if (extensions->isCompressedTexImage2DSupported()) { GLint blockSize, size; for( GLsizei k = 0 ; k < numMipmapLevels && (width || height) ;k++) { if (width == 0) width = 1; if (height == 0) height = 1; getCompressedSize(_internalFormat, width, height, 1, blockSize,size); extensions->glCompressedTexImage2D(target, k, _internalFormat, width, height, _borderWidth, size, image->getMipmapData(k)-dataMinusOffset+dataPlusOffset); width >>= 1; height >>= 1; } } } else { if ( !compressed_image) { numMipmapLevels = 0; gluBuild2DMipmaps( target, _internalFormat, inwidth,inheight, (GLenum)image->getPixelFormat(), (GLenum)image->getDataType(), data); int width = image->s(); int height = image->t(); for( numMipmapLevels = 0 ; (width || height) ; ++numMipmapLevels) { width >>= 1; height >>= 1; } } else { notify(WARN)<<"Warning:: Compressed image cannot be mip mapped"<delta_m(start_tick,osg::Timer::instance()->tick()); s_total_time += delta_time; osg::notify(osg::NOTICE)<<"glTexImage2D "<data()) return; // image size has changed so we have to re-load the image from scratch. if (image->s()!=inwidth || image->t()!=inheight || image->getInternalTextureFormat()!=inInternalFormat ) { applyTexImage2D_load(state, target, image, inwidth, inheight,numMipmapLevels); return; } // else image size the same as when loaded so we can go ahead and subload // If the texture's internal format is a compressed type, then the // user is requesting that the graphics card compress the image if it's // not already compressed. However, if the image is not a multiple of // four in each dimension the subsequent calls to glTexSubImage* will // fail. Revert to uncompressed format in this case. if (isCompressedInternalFormat(_internalFormat) && (((inwidth >> 2) << 2) != inwidth || ((inheight >> 2) << 2) != inheight)) { applyTexImage2D_load(state, target, image, inwidth, inheight, numMipmapLevels); return; } #ifdef DO_TIMING osg::Timer_t start_tick = osg::Timer::instance()->tick(); osg::notify(osg::NOTICE)<<"glTexSubImage2D pixelFormat = "<getPixelFormat()<getPixelFormat()); glPixelStorei(GL_UNPACK_ALIGNMENT,image->getPacking()); unsigned char* data = (unsigned char*)image->data(); bool needImageRescale = inwidth!=image->s() || inheight!=image->t(); if (needImageRescale) { // resize the image to power of two. if (image->isMipmap()) { notify(WARN)<<"Warning:: Mipmapped osg::Image not a power of two, cannot apply to texture."<getPixelFormat(),image->getDataType(),image->getPacking())*inheight; data = new unsigned char [newTotalSize]; if (!data) { notify(WARN)<<"Warning:: Not enough memory to resize image, cannot apply to texture."<getFileName().empty()) notify(NOTICE) << "Scaling image '"<getFileName()<<"' from ("<s()<<","<t()<<") to ("<s()<<","<t()<<") to ("<getPacking()); gluScaleImage(image->getPixelFormat(), image->s(),image->t(),image->getDataType(),image->data(), inwidth,inheight,image->getDataType(),data); } bool mipmappingRequired = _min_filter != LINEAR && _min_filter != NEAREST; bool useHardwareMipMapGeneration = mipmappingRequired && (!image->isMipmap() && isHardwareMipmapGenerationEnabled(state)); bool useGluBuildMipMaps = mipmappingRequired && (!useHardwareMipMapGeneration && !image->isMipmap()); unsigned char* dataMinusOffset = 0; unsigned char* dataPlusOffset = 0; const PixelBufferObject* pbo = image->getPixelBufferObject(); if (pbo && pbo->isPBOSupported(contextID) && !needImageRescale && !useGluBuildMipMaps) { state.bindPixelBufferObject(pbo); dataMinusOffset = data; dataPlusOffset = reinterpret_cast(pbo->offset()); #ifdef DO_TIMING osg::notify(osg::NOTICE)<<"after PBO "<delta_m(start_tick,osg::Timer::instance()->tick())<<"ms"<getPixelFormat(), (GLenum)image->getDataType(), data - dataMinusOffset + dataPlusOffset); } else if (extensions->isCompressedTexImage2DSupported()) { GLint blockSize,size; getCompressedSize(image->getInternalTextureFormat(), inwidth, inheight, 1, blockSize,size); extensions->glCompressedTexSubImage2D(target, 0, 0,0, inwidth, inheight, (GLenum)image->getPixelFormat(), size, data - dataMinusOffset + dataPlusOffset ); } mipmapAfterTexImage(state, mipmapResult); } else { if (image->isMipmap()) { numMipmapLevels = image->getNumMipmapLevels(); int width = inwidth; int height = inheight; if( !compressed_image ) { for( GLsizei k = 0 ; k < numMipmapLevels && (width || height) ;k++) { if (width == 0) width = 1; if (height == 0) height = 1; glTexSubImage2D( target, k, 0, 0, width, height, (GLenum)image->getPixelFormat(), (GLenum)image->getDataType(), image->getMipmapData(k) - dataMinusOffset + dataPlusOffset); width >>= 1; height >>= 1; } } else if (extensions->isCompressedTexImage2DSupported()) { GLint blockSize,size; for( GLsizei k = 0 ; k < numMipmapLevels && (width || height) ;k++) { if (width == 0) width = 1; if (height == 0) height = 1; getCompressedSize(image->getInternalTextureFormat(), width, height, 1, blockSize,size); //state.checkGLErrors("before extensions->glCompressedTexSubImage2D("); extensions->glCompressedTexSubImage2D(target, k, 0, 0, width, height, (GLenum)image->getPixelFormat(), size, image->getMipmapData(k) - dataMinusOffset + dataPlusOffset); //state.checkGLErrors("after extensions->glCompressedTexSubImage2D("); width >>= 1; height >>= 1; } } } else { //notify(WARN)<<"Warning:: cannot subload mip mapped texture from non mipmapped image."<delta_m(start_tick,osg::Timer::instance()->tick())<<"ms"<isGenerateMipMapSupported()) { return true; } const FBOExtensions* fbo_ext = FBOExtensions::instance(contextID,true); if (fbo_ext->glGenerateMipmapEXT) { return true; } } return false; } Texture::GenerateMipmapMode Texture::mipmapBeforeTexImage(const State& state, bool hardwareMipmapOn) const { if (hardwareMipmapOn) { int width = getTextureWidth(); int height = getTextureHeight(); //quick bithack to determine whether width or height are non-power-of-two if ((width & (width - 1)) || (height & (height - 1))) { //GL_GENERATE_MIPMAP_SGIS with non-power-of-two textures on NVIDIA hardware //is extremely slow. Use glGenerateMipmapEXT() instead if supported. if (_internalFormatType != SIGNED_INTEGER && _internalFormatType != UNSIGNED_INTEGER) { if (FBOExtensions::instance(state.getContextID(), true)->glGenerateMipmapEXT) { return GENERATE_MIPMAP; } } } glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE); return GENERATE_MIPMAP_TEX_PARAMETER; } return GENERATE_MIPMAP_NONE; } void Texture::mipmapAfterTexImage(State& state, GenerateMipmapMode beforeResult) const { switch (beforeResult) { case GENERATE_MIPMAP: { unsigned int contextID = state.getContextID(); TextureObject* textureObject = getTextureObject(contextID); if (textureObject) { osg::FBOExtensions* fbo_ext = osg::FBOExtensions::instance(contextID, true); fbo_ext->glGenerateMipmapEXT(textureObject->_target); } } break; case GENERATE_MIPMAP_TEX_PARAMETER: glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_FALSE); break; case GENERATE_MIPMAP_NONE: break; } } void Texture::generateMipmap(State& state) const { const unsigned int contextID = state.getContextID(); // get the texture object for the current contextID. TextureObject* textureObject = getTextureObject(contextID); // if not initialized before, then do nothing if (textureObject == NULL) return; _texMipmapGenerationDirtyList[contextID] = 0; // if internal format does not provide automatic mipmap generation, then do manual allocation if (_internalFormatType == SIGNED_INTEGER || _internalFormatType == UNSIGNED_INTEGER) { allocateMipmap(state); return; } // get fbo extension which provides us with the glGenerateMipmapEXT function osg::FBOExtensions* fbo_ext = osg::FBOExtensions::instance(state.getContextID(), true); // check if the function is supported if (fbo_ext->glGenerateMipmapEXT) { textureObject->bind(); fbo_ext->glGenerateMipmapEXT(textureObject->_target); // inform state that this texture is the current one bound. state.haveAppliedTextureAttribute(state.getActiveTextureUnit(), this); // if the function is not supported, then do manual allocation }else { allocateMipmap(state); } } /////////////////////////////////////////////////////////////////////////////////////////////// // Static map to manage the deletion of texture objects are the right time. ////////////////////////////////////////////////////////////////////////////////////////////// #include #include void Texture::compileGLObjects(State& state) const { apply(state); } void Texture::resizeGLObjectBuffers(unsigned int maxSize) { _textureObjectBuffer.resize(maxSize); } void Texture::releaseGLObjects(State* state) const { // if (state) osg::notify(osg::NOTICE)<<"Texture::releaseGLObjects contextID="<getContextID()<(this)->dirtyTextureObject(); else { unsigned int contextID = state->getContextID(); if (_textureObjectBuffer[contextID].valid() && getTextureObjectManager()) { OpenThreads::ScopedLock lock(getTextureObjectManager()->_mutex); getTextureObjectManager()->_textureObjectListMap[contextID].push_back(_textureObjectBuffer[contextID]); _textureObjectBuffer[contextID] = 0; } } } Texture::Extensions* Texture::getExtensions(unsigned int contextID,bool createIfNotInitalized) { if (!s_extensions[contextID] && createIfNotInitalized) s_extensions[contextID] = new Extensions(contextID); return s_extensions[contextID].get(); } void Texture::setExtensions(unsigned int contextID,Extensions* extensions) { s_extensions[contextID] = extensions; } Texture::Extensions::Extensions(unsigned int contextID) { setupGLExtensions(contextID); } Texture::Extensions::Extensions(const Extensions& rhs): Referenced() { _isMultiTexturingSupported = rhs._isMultiTexturingSupported; _isTextureFilterAnisotropicSupported = rhs._isTextureFilterAnisotropicSupported; _isTextureCompressionARBSupported = rhs._isTextureCompressionARBSupported; _isTextureCompressionS3TCSupported = rhs._isTextureCompressionS3TCSupported; _isTextureMirroredRepeatSupported = rhs._isTextureMirroredRepeatSupported; _isTextureEdgeClampSupported = rhs._isTextureEdgeClampSupported; _isTextureBorderClampSupported = rhs._isTextureBorderClampSupported; _isGenerateMipMapSupported = rhs._isGenerateMipMapSupported; _maxTextureSize = rhs._maxTextureSize; _glCompressedTexImage2D = rhs._glCompressedTexImage2D; _isShadowSupported = rhs._isShadowSupported; _isShadowAmbientSupported = rhs._isShadowAmbientSupported; _isClientStorageSupported = rhs._isClientStorageSupported; _isNonPowerOfTwoTextureMipMappedSupported = rhs._isNonPowerOfTwoTextureMipMappedSupported; _isNonPowerOfTwoTextureNonMipMappedSupported = rhs._isNonPowerOfTwoTextureNonMipMappedSupported; _isTextureIntegerEXTSupported = rhs._isTextureIntegerEXTSupported; _isTextureMaxLevelSupported = rhs._isTextureMaxLevelSupported; } void Texture::Extensions::lowestCommonDenominator(const Extensions& rhs) { if (!rhs._isMultiTexturingSupported) _isMultiTexturingSupported = false; if (!rhs._isTextureFilterAnisotropicSupported) _isTextureFilterAnisotropicSupported = false; if (!rhs._isTextureMirroredRepeatSupported) _isTextureMirroredRepeatSupported = false; if (!rhs._isTextureEdgeClampSupported) _isTextureEdgeClampSupported = false; if (!rhs._isTextureBorderClampSupported) _isTextureBorderClampSupported = false; if (!rhs._isTextureCompressionARBSupported) _isTextureCompressionARBSupported = false; if (!rhs._isTextureCompressionS3TCSupported) _isTextureCompressionS3TCSupported = false; if (!rhs._isGenerateMipMapSupported) _isGenerateMipMapSupported = false; if (rhs._maxTextureSize<_maxTextureSize) _maxTextureSize = rhs._maxTextureSize; if (rhs._numTextureUnits<_numTextureUnits) _numTextureUnits = rhs._numTextureUnits; if (!rhs._glCompressedTexImage2D) _glCompressedTexImage2D = 0; if (!rhs._glCompressedTexSubImage2D) _glCompressedTexSubImage2D = 0; if (!rhs._glGetCompressedTexImage) _glGetCompressedTexImage = 0; if (!rhs._isShadowSupported) _isShadowSupported = false; if (!rhs._isShadowAmbientSupported) _isShadowAmbientSupported = false; if (!rhs._isClientStorageSupported) _isClientStorageSupported = false; if (!rhs._isNonPowerOfTwoTextureMipMappedSupported) _isNonPowerOfTwoTextureMipMappedSupported = false; if (!rhs._isNonPowerOfTwoTextureNonMipMappedSupported) _isNonPowerOfTwoTextureNonMipMappedSupported = false; if (!rhs._isTextureIntegerEXTSupported) _isTextureIntegerEXTSupported = false; if (!rhs._isTextureMaxLevelSupported) _isTextureMaxLevelSupported = false; } void Texture::Extensions::setupGLExtensions(unsigned int contextID) { const char* version = (const char*) glGetString( GL_VERSION ); if (!version) { osg::notify(osg::FATAL)<<"Error: In Texture::Extensions::setupGLExtensions(..) OpenGL version test failed, requires valid graphics context."<= 1.2f ); } void Texture::Extensions::glTexParameterIiv(GLenum target, GLenum pname, const GLint* data) const { if (_glTexParameterIiv) { _glTexParameterIiv(target, pname, data); } else { notify(WARN)<<"Error: glTexParameterIiv not supported by OpenGL driver"<