/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2003 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 <osg/GLExtensions>
#include <osg/Image>
#include <osg/Texture>
#include <osg/State>
#include <osg/Notify>
#include <osg/GLU>
#include <osg/Timer>

#include <OpenThreads/ScopedLock>
#include <OpenThreads/Mutex>

using namespace osg;

#ifndef GL_TEXTURE_WRAP_R
#define GL_TEXTURE_WRAP_R                 0x8072
#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

unsigned int Texture::s_numberTextureReusedLastInLastFrame = 0;
unsigned int Texture::s_numberNewTextureInLastFrame = 0;
unsigned int Texture::s_numberDeletedTextureInLastFrame = 0;

unsigned int s_minimumNumberOfTextureObjectsToRetainInCache = 0;
void Texture::setMinimumNumberOfTextureObjectsToRetainInCache(unsigned int minimum)
{
    s_minimumNumberOfTextureObjectsToRetainInCache = minimum;
}

unsigned int Texture::getMinimumNumberOfTextureObjectsToRetainInCache()
{
    return s_minimumNumberOfTextureObjectsToRetainInCache;
}

class SG_EXPORT TextureObjectManager : public osg::Referenced
{
public:

    TextureObjectManager():
        _expiryDelay(0.0) {}

    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 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;
};

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 "<<s_number<<std::endl;

    // no useable texture object found so return 0
    GLuint id;
    glGenTextures( 1L, &id );

    return new Texture::TextureObject(id,target,numMipmapLevels,internalFormat,width,height,depth,border);
}

Texture::TextureObject* TextureObjectManager::reuseTextureObject(unsigned int contextID,
                                                                             GLenum    target,
                                                                             GLint     numMipmapLevels,
                                                                             GLenum    internalFormat,
                                                                             GLsizei   width,
                                                                             GLsizei   height,
                                                                             GLsizei   depth,
                                                                             GLint     border)
{
    OpenThreads::ScopedLock<OpenThreads::Mutex> 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 "<<std::endl;
            
            ++Texture::s_numberTextureReusedLastInLastFrame;

            return textureObject;
        }
    }
    
    return 0;
}

    

void TextureObjectManager::addTextureObjects(Texture::TextureObjectListMap& toblm)
{
    OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);

    for(Texture::TextureObjectListMap::iterator itr = toblm.begin();
        itr != toblm.end();
        ++itr)
    {
        Texture::TextureObjectList& tol = _textureObjectListMap[itr->first];
        tol.insert(tol.end(),itr->second.begin(),itr->second.end());
    }
}

void TextureObjectManager::addTextureObjectsFrom(Texture& texture)
{
    OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);

    texture.takeTextureObjects(_textureObjectListMap);
}

void TextureObjectManager::flushAllTextureObjects(unsigned int contextID)
{
    OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);

    Texture::TextureObjectListMap::iterator tmitr = _textureObjectListMap.find(contextID);
    if (tmitr!=_textureObjectListMap.end())
    {
        Texture::TextureObjectList& tol = tmitr->second;

        for(Texture::TextureObjectList::iterator itr=tol.begin();
            itr!=tol.end();
            ++itr)
        {
            glDeleteTextures( 1L, &((*itr)->_id));
        }
        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<OpenThreads::Mutex> lock(_mutex);

        Texture::TextureObjectListMap::iterator tmitr = _textureObjectListMap.find(contextID);
        if (tmitr!=_textureObjectListMap.end())
        {
            Texture::TextureObjectList& tol = tmitr->second;

            // 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() && elapsedTime<availableTime && tol.size()>s_minimumNumberOfTextureObjectsToRetainInCache && numObjectsDeleted<maxNumObjectsToDelete;
                )
            {
                if ((*itr)->_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 = "<<numTexturesDeleted<<" new total "<<s_number<<" elapsed time"<<elapsedTime<<std::endl;
        
    availableTime -= elapsedTime;
}


static TextureObjectManager* getTextureObjectManager()
{
    static ref_ptr<TextureObjectManager> s_textureObjectManager;
    if (!s_textureObjectManager) s_textureObjectManager = new TextureObjectManager;
    return s_textureObjectManager.get();
}


Texture::TextureObject* Texture::generateTextureObject(unsigned int contextID,GLenum target)
{
    return getTextureObjectManager()->generateTextureObject(contextID,target);
}

Texture::TextureObject* Texture::generateTextureObject(unsigned int contextID,
                                             GLenum    target,
                                             GLint     numMipmapLevels,
                                             GLenum    internalFormat,
                                             GLsizei   width,
                                             GLsizei   height,
                                             GLsizei   depth,
                                             GLint     border)
{
    return getTextureObjectManager()->reuseOrGenerateTextureObject(contextID,
                                             target,
                                             numMipmapLevels,
                                             internalFormat,
                                             width,
                                             height,
                                             depth,
                                             border);
}                                             

void Texture::flushAllDeletedTextureObjects(unsigned int contextID)
{
    getTextureObjectManager()->flushAllTextureObjects(contextID);
}

void Texture::flushDeletedTextureObjects(unsigned int contextID,double currentTime, double& availbleTime)
{
    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),
            _borderColor(0.0, 0.0, 0.0, 0.0),
            _borderWidth(0),
            _internalFormatMode(USE_IMAGE_DATA_FORMAT),
            _internalFormat(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),
            _borderColor(text._borderColor),
            _borderWidth(text._borderWidth),
            _internalFormatMode(text._internalFormatMode),
            _internalFormat(text._internalFormat),
            _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(_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)
    
    return 0;
}


void Texture::setWrap(WrapParameter which, WrapMode wrap)
{
    switch( which )
    {
        case WRAP_S : _wrap_s = wrap; dirtyTextureParameters(); break;
        case WRAP_T : _wrap_t = wrap; dirtyTextureParameters(); break;
        case WRAP_R : _wrap_r = wrap; dirtyTextureParameters(); break;
        default : notify(WARN)<<"Error: invalid 'which' passed Texture::setWrap("<<(unsigned int)which<<","<<(unsigned int)wrap<<")"<<std::endl; break;
    }
    
}


Texture::WrapMode Texture::getWrap(WrapParameter which) const
{
    switch( which )
    {
        case WRAP_S : return _wrap_s;
        case WRAP_T : return _wrap_t;
        case WRAP_R : return _wrap_r;
        default : notify(WARN)<<"Error: invalid 'which' passed Texture::getWrap(which)"<<std::endl; return _wrap_s;
    }
}


void Texture::setFilter(FilterParameter which, FilterMode filter)
{
    switch( which )
    {
        case MIN_FILTER : _min_filter = filter; dirtyTextureParameters(); break;
        case MAG_FILTER : _mag_filter = filter; dirtyTextureParameters(); break;
        default : notify(WARN)<<"Error: invalid 'which' passed Texture::setFilter("<<(unsigned int)which<<","<<(unsigned int)filter<<")"<<std::endl; break;
    }
}


Texture::FilterMode Texture::getFilter(FilterParameter which) const
{
    switch( which )
    {
        case MIN_FILTER : return _min_filter;
        case MAG_FILTER : return _mag_filter;
        default : notify(WARN)<<"Error: invalid 'which' passed Texture::getFilter(which)"<< std::endl; return _min_filter;
    }
}

void Texture::setMaxAnisotropy(float anis)
{
    if (_maxAnisotropy!=anis)
    {
        _maxAnisotropy = anis; 
        dirtyTextureParameters();
    }
}

/** Force a recompile on next apply() of associated OpenGL texture objects.*/
void Texture::dirtyTextureObject()
{
    getTextureObjectManager()->addTextureObjectsFrom(*this);
}

void Texture::takeTextureObjects(Texture::TextureObjectListMap& toblm)
{
    for(unsigned int i = 0; i<_textureObjectBuffer.size();++i)
    {
        if (_textureObjectBuffer[i].valid()) 
        {
            //notify(INFO) << "releasing texure "<<toblm[i].size()<<std::endl;
            toblm[i].push_back(_textureObjectBuffer[i]);
        }
    }
    _textureObjectBuffer.setAllElementsTo(0);
}

void Texture::dirtyTextureParameters()
{
    for(unsigned int i=0;i<_texParametersDirtyList.size();++i)
    {
        _texParametersDirtyList[i] = 1;
    }
}

void Texture::computeInternalFormatWithImage(const osg::Image& image) const
{
    GLint internalFormat = image.getInternalTextureFormat();

    if (_internalFormatMode==USE_IMAGE_DATA_FORMAT)
    {
        internalFormat = image.getInternalTextureFormat();
    }
    else if (_internalFormatMode==USE_USER_DEFINED_FORMAT)
    {
        internalFormat = _internalFormat;
    }
    else
    {

        const unsigned int contextID = 0; // state.getContextID();  // set to 0 right now, assume same paramters for each graphics context...
        const Extensions* extensions = getExtensions(contextID,true);

        switch(_internalFormatMode)
        {
        case(USE_ARB_COMPRESSION):
            if (extensions->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;
    
    //osg::notify(osg::NOTICE)<<"Internal format="<<std::hex<<internalFormat<<std::dec<<std::endl;
}

bool Texture::isCompressedInternalFormat() const
{
    return isCompressedInternalFormat(getInternalFormat());
}

bool Texture::isCompressedInternalFormat(GLint internalFormat) const
{
    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) const
{
    if (internalFormat == GL_COMPRESSED_RGB_S3TC_DXT1_EXT || internalFormat == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT)
        blockSize = 8;
    else
        blockSize = 16;
         
    size = ((width+3)/4)*((height+3)/4)*depth*blockSize;        
}

void Texture::applyTexParameters(GLenum target, State& state) const
{
    // get the contextID (user defined ID of 0 upwards) for the 
    // current OpenGL context.
    const unsigned int contextID = state.getContextID();
    const Extensions* extensions = getExtensions(contextID,true);

    WrapMode ws = _wrap_s, wt = _wrap_t, wr = _wrap_r;

    // GL_IBM_texture_mirrored_repeat, fall-back REPEAT
    if (!extensions->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;
    }

    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);

    if (extensions->isTextureFilterAnisotropicSupported())
    {
        // 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())
    {
        glTexParameterfv(target, GL_TEXTURE_BORDER_COLOR, _borderColor.ptr());
    }

    if (extensions->isShadowSupported() && target == GL_TEXTURE_2D)
      {
        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 = Image::computeNearestPowerOfTwo(image.s()-2*_borderWidth)+2*_borderWidth;
    int 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() && _useHardwareMipMapGeneration && extensions->isGenerateMipMapSupported();

    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;
        }    
    }
}

bool Texture::areAllTextureObjectsLoaded() const
{
    for(unsigned int i=0;i<DisplaySettings::instance()->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;

    // get the contextID (user defined ID of 0 upwards) for the 
    // current OpenGL context.
    const unsigned int contextID = state.getContextID();
    const Extensions* extensions = getExtensions(contextID,true);
    
    bool generateMipMapSupported = extensions->isGenerateMipMapSupported();

    // select the internalFormat required for the texture.
    bool compressed_image = isCompressedInternalFormat((GLenum)image->getPixelFormat());
    
    glPixelStorei(GL_UNPACK_ALIGNMENT,image->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();
 

    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."<<std::endl;
            return;
        }
        else if (compressed_image)
        {
            notify(WARN)<<"Warning:: Compressed osg::Image not a power of two, cannot apply to texture."<<std::endl;
            return;
        }
        
        unsigned int newTotalSize = osg::Image::computeRowWidthInBytes(inwidth,image->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."<<std::endl;
            return;
        }

        if (!image->getFileName().empty()) notify(NOTICE) << "Scaling image '"<<image->getFileName()<<"' from ("<<image->s()<<","<<image->t()<<") to ("<<inwidth<<","<<inheight<<")"<<std::endl;
        else notify(NOTICE) << "Scaling image from ("<<image->s()<<","<<image->t()<<") to ("<<inwidth<<","<<inheight<<")"<<std::endl;

        // rescale the image to the correct size.
        glPixelStorei(GL_PACK_ALIGNMENT,image->getPacking());
        gluScaleImage(image->getPixelFormat(),
                      image->s(),image->t(),image->getDataType(),image->data(),
                      inwidth,inheight,image->getDataType(),data);
        
    }    

    bool useHardwareMipMapGeneration = !image->isMipmap() && _useHardwareMipMapGeneration && generateMipMapSupported;

    if( _min_filter == LINEAR || _min_filter == NEAREST || useHardwareMipMapGeneration)
    {
        bool hardwareMipMapOn = false;
        if (_min_filter != LINEAR && _min_filter != NEAREST) 
        {
            if (useHardwareMipMapGeneration) glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS,GL_TRUE);
            hardwareMipMapOn = true;
        }

        if ( !compressed_image)
        {
            numMipmapLevels = 1;

            glTexImage2D( target, 0, _internalFormat,
                inwidth, inheight, _borderWidth,
                (GLenum)image->getPixelFormat(),
                (GLenum)image->getDataType(),
                data );

        }
        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);                
        }

        if (hardwareMipMapOn) glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS,GL_FALSE);
    }
    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));

                    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));                

                    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"<<std::endl;
            }

        }

    }

    if (needImageRescale)
    {
        // clean up the resized image.
        delete [] data;
    }
    
    if (useClientStorage)
    {
        glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE,GL_FALSE);
    }
}



void Texture::applyTexImage2D_subload(State& state, GLenum target, const Image* image, GLsizei inwidth, GLsizei inheight, GLint inInternalFormat, GLint numMipmapLevels) const
{
    // if we don't have a valid image we can't create a texture!
    if (!image || !image->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
    
   

    // get the contextID (user defined ID of 0 upwards) for the 
    // current OpenGL context.
    const unsigned int contextID = state.getContextID();
    const Extensions* extensions = getExtensions(contextID,true);
    
    bool generateMipMapSupported = extensions->isGenerateMipMapSupported();

    // select the internalFormat required for the texture.
    bool compressed_image = isCompressedInternalFormat((GLenum)image->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."<<std::endl;
            return;
        }
        else if (compressed_image)
        {
            notify(WARN)<<"Warning:: Compressed osg::Image not a power of two, cannot apply to texture."<<std::endl;
            return;
        }
        
        unsigned int newTotalSize = osg::Image::computeRowWidthInBytes(inwidth,image->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."<<std::endl;
            return;
        }

        if (!image->getFileName().empty()) notify(NOTICE) << "Scaling image '"<<image->getFileName()<<"' from ("<<image->s()<<","<<image->t()<<") to ("<<inwidth<<","<<inheight<<")"<<std::endl;
        else notify(NOTICE) << "Scaling image from ("<<image->s()<<","<<image->t()<<") to ("<<inwidth<<","<<inheight<<")"<<std::endl;

        // rescale the image to the correct size.
        glPixelStorei(GL_PACK_ALIGNMENT,image->getPacking());
        gluScaleImage(image->getPixelFormat(),
                      image->s(),image->t(),image->getDataType(),image->data(),
                      inwidth,inheight,image->getDataType(),data);
        
    }    

    bool useHardwareMipMapGeneration = !image->isMipmap() && _useHardwareMipMapGeneration && generateMipMapSupported;

    if( _min_filter == LINEAR || _min_filter == NEAREST || useHardwareMipMapGeneration)
    {
    
        bool hardwareMipMapOn = false;
        if (_min_filter != LINEAR && _min_filter != NEAREST) 
        {
            if (useHardwareMipMapGeneration) glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS,GL_TRUE);
            hardwareMipMapOn = true;
        }
        

        if (!compressed_image)
        {
            glTexSubImage2D( target, 0, 
                0, 0,
                inwidth, inheight,
                (GLenum)image->getPixelFormat(),
                (GLenum)image->getDataType(),
                data );

        }
        else if (extensions->isCompressedTexImage2DSupported())
        {        
            GLint blockSize,size;
            getCompressedSize(_internalFormat, inwidth, inheight, 1, blockSize,size);

            extensions->glCompressedTexSubImage2D(target, 0, 
                0,0,
                inwidth, inheight,
                (GLenum)image->getPixelFormat(),
                size,
                data );                
        }

        if (hardwareMipMapOn) glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS,GL_FALSE);
    }
    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));

                    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);

                    //state.checkGLErrors("before extensions->glCompressedTexSubImage2D(");

                    extensions->glCompressedTexSubImage2D(target, k,  
                                                       0, 0,
                                                       width, height, 
                                                       (GLenum)image->getPixelFormat(),
                                                       size,
                                                        image->getMipmapData(k));                

                    //state.checkGLErrors("after extensions->glCompressedTexSubImage2D(");

                    width >>= 1;
                    height >>= 1;
                }

            }
        }
        else
        {
            if (!compressed_image)
            {

                 numMipmapLevels = 0;

                int width  = inwidth;
                int height = inheight;

                glPixelStorei(GL_PACK_ALIGNMENT,image->getPacking());

                unsigned int newTotalSize = osg::Image::computeRowWidthInBytes(width,image->getPixelFormat(),image->getDataType(),image->getPacking())*height;
                unsigned char* copyData1 = new unsigned char [newTotalSize];
                unsigned char* copyData2 = new unsigned char [newTotalSize];
                unsigned char* tmpdata = data;

                int previous_width = width;
                int previous_height = height;

                for( GLsizei k = 0 ; (width || height) ;++k)
                {

                    if (width == 0)
                        width = 1;
                    if (height == 0)
                        height = 1;


                    if (k>0)
                    {
                        if (data!=copyData1)
                        {
                            gluScaleImage(image->getPixelFormat(),
                                          previous_width,previous_height,image->getDataType(),data,
                                          width,height,image->getDataType(),copyData1);
                            data = copyData1;
                        }
                        else
                        {
                            gluScaleImage(image->getPixelFormat(),
                                          previous_width,previous_height,image->getDataType(),data,
                                          width,height,image->getDataType(),copyData2);
                            data = copyData2;
                        }                                  
                    }

                    glTexSubImage2D( target, k, 
                        0, 0,
                        width, height,
                        (GLenum)image->getPixelFormat(),
                        (GLenum)image->getDataType(),
                        data);

                    previous_width = width;
                    previous_height = height;

                    width >>= 1;
                    height >>= 1;
                }

                delete [] copyData1;
                delete [] copyData2;
                
                // restore the data paramters so it can be deleted if it was allocated locally via needImageRescale
                data = tmpdata;
            }
            else 
            {
                notify(WARN)<<"Warning:: Compressed image cannot be mip mapped"<<std::endl;
            }
        }
    }
    
    if (needImageRescale)
    {
        // clean up the resized image.
        delete [] data;
    }
}


///////////////////////////////////////////////////////////////////////////////////////////////
//  Static map to manage the deletion of texture objects are the right time.
//////////////////////////////////////////////////////////////////////////////////////////////
#include <map>
#include <set>


void Texture::compileGLObjects(State& state) const
{
    apply(state);
}

void Texture::releaseGLObjects(State* state) const
{
    if (!state) const_cast<Texture*>(this)->dirtyTextureObject();
    else
    {
        unsigned int contextID = state->getContextID();
        if (_textureObjectBuffer[contextID].valid()) 
        {
            OpenThreads::ScopedLock<OpenThreads::Mutex> lock(getTextureObjectManager()->_mutex);
            
            getTextureObjectManager()->_textureObjectListMap[contextID].push_back(_textureObjectBuffer[contextID]);
            _textureObjectBuffer[contextID] = 0;
        }
    }
}

typedef buffered_value< ref_ptr<Texture::Extensions> > BufferedExtensions;
static BufferedExtensions s_extensions;

Texture::Extensions* Texture::getExtensions(unsigned int contextID,bool createIfNotInitalized)
{
    if (!s_extensions[contextID] && createIfNotInitalized) s_extensions[contextID] = new Extensions;
    return s_extensions[contextID].get();
}

void Texture::setExtensions(unsigned int contextID,Extensions* extensions)
{
    s_extensions[contextID] = extensions;
}

Texture::Extensions::Extensions()
{
    setupGLExtensions();
}

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;
}

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;
}

void Texture::Extensions::setupGLExtensions()
{
    float glVersion = atof( (const char *)glGetString( GL_VERSION ) );
    
    _isMultiTexturingSupported = ( glVersion >= 1.3 ) ||
                                 isGLExtensionSupported("GL_ARB_multitexture") ||
                                 isGLExtensionSupported("GL_EXT_multitexture");
    _isTextureFilterAnisotropicSupported = isGLExtensionSupported("GL_EXT_texture_filter_anisotropic");
    _isTextureCompressionARBSupported = ( glVersion >= 1.3 ) ||
                                        isGLExtensionSupported("GL_ARB_texture_compression");
    _isTextureCompressionS3TCSupported = isGLExtensionSupported("GL_EXT_texture_compression_s3tc");
    _isTextureMirroredRepeatSupported = ( glVersion >= 1.4 ) || 
                                    isGLExtensionSupported("GL_IBM_texture_mirrored_repeat") ||
                                    isGLExtensionSupported("GL_ARB_texture_mirrored_repeat");
    _isTextureEdgeClampSupported = ( glVersion >= 1.2 ) ||
                                    isGLExtensionSupported("GL_EXT_texture_edge_clamp") || 
                                    isGLExtensionSupported("GL_SGIS_texture_edge_clamp");
    _isTextureBorderClampSupported = ( glVersion >= 1.3 ) ||
                                    isGLExtensionSupported("GL_ARB_texture_border_clamp");
    _isGenerateMipMapSupported = (strncmp((const char*)glGetString(GL_VERSION),"1.4",3)>=0) ||
                                  isGLExtensionSupported("GL_SGIS_generate_mipmap");
    _isShadowSupported = isGLExtensionSupported("GL_ARB_shadow");
    _isShadowAmbientSupported = isGLExtensionSupported("GL_ARB_shadow_ambient");

    _isClientStorageSupported = isGLExtensionSupported("GL_APPLE_client_storage");

    glGetIntegerv(GL_MAX_TEXTURE_SIZE,&_maxTextureSize);

    char *ptr;
    if( (ptr = getenv("OSG_MAX_TEXTURE_SIZE")) != 0)
    {
        GLint osg_max_size = atoi(ptr);

        if (osg_max_size<_maxTextureSize)
        {

            _maxTextureSize = osg_max_size;
        }
    }

    if( _isMultiTexturingSupported )
    {
       glGetIntegerv(GL_MAX_TEXTURE_UNITS,&_numTextureUnits);
    }
    else
    {
       _numTextureUnits = 1;
    }

    _glCompressedTexImage2D = getGLExtensionFuncPtr("glCompressedTexImage2D","glCompressedTexImage2DARB");
    _glCompressedTexSubImage2D = getGLExtensionFuncPtr("glCompressedTexSubImage2D","glCompressedTexSubImage2DARB");
    _glGetCompressedTexImage = getGLExtensionFuncPtr("glGetCompressedTexImage","glGetCompressedTexImageARB");;
}

void Texture::Extensions::glCompressedTexImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data) const
{
    if (_glCompressedTexImage2D)
    {
        typedef void (APIENTRY * CompressedTexImage2DArbProc) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data);
        ((CompressedTexImage2DArbProc)_glCompressedTexImage2D)(target, level, internalformat, width, height, border, imageSize, data);
    }
    else
    {
        notify(WARN)<<"Error: glCompressedTexImage2D not supported by OpenGL driver"<<std::endl;
    }
    
}

void Texture::Extensions::glCompressedTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data) const
{
    if (_glCompressedTexSubImage2D)
    {
        typedef void (APIENTRY * CompressedTexSubImage2DArbProc) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data);
        ((CompressedTexSubImage2DArbProc)_glCompressedTexSubImage2D)(target, level, xoffset, yoffset, width, height, format, imageSize, data);
    }
    else
    {
        notify(WARN)<<"Error: glCompressedTexImage2D not supported by OpenGL driver"<<std::endl;
    }
    
}
void Texture::Extensions::glGetCompressedTexImage(GLenum target, GLint level, GLvoid *data) const
{
    if (_glGetCompressedTexImage)
    {
        typedef void (APIENTRY * GetCompressedTexImageArbProc) (GLenum target, GLint level, GLvoid *data);
        ((GetCompressedTexImageArbProc)_glGetCompressedTexImage)(target, level, data);
    }
    else
    {
        notify(WARN)<<"Error: glGetCompressedTexImage not supported by OpenGL driver"<<std::endl;
    }
}
