/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
 * Copyright (C) 2012 David Callu
 *
 * 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.
*/

#ifndef OSG_BUFFEROBJECT
#define OSG_BUFFEROBJECT 1

#include <osg/GL>
#include <osg/Object>
#include <osg/buffered_value>
#include <osg/FrameStamp>

#include <iosfwd>
#include <list>
#include <map>

// identify GLES 1.1
#if (defined(GL_VERSION_ES_CM_1_0) && GL_VERSION_ES_CM_1_0 > 0) || \
    (defined(GL_VERSION_ES_CM_1_1) && GL_VERSION_ES_CM_1_1 > 0)

    #define OPENGLES_1_1_FOUND 1

#endif

// for compatibility with gl.h headers that don't support VBO,
//GL_VERSION_1_5 and GL_ARB_vertex_buffer_object provide these types for OpenGL
//all ES versions except GL_OES_VERSION_1_0 provide these types for OpenGL ES
#if !defined(GL_VERSION_1_5) && !defined(GL_ARB_vertex_buffer_object) \
  && !defined(GL_ES_VERSION_2_0) && !defined(OPENGLES_1_1_FOUND)
    #if defined(_WIN64)
        typedef __int64 GLintptr;
        typedef __int64 GLsizeiptr;
    #elif defined(__ia64__) || defined(__x86_64__) || defined(ANDROID)
        typedef long int GLintptr;
        typedef long int GLsizeiptr;
    #else
        typedef int GLintptr;
        typedef int GLsizeiptr;
    #endif
#endif

#ifndef GL_ARB_vertex_buffer_object
    #define GL_ARB_vertex_buffer_object

    typedef GLintptr GLintptrARB;
    typedef GLsizeiptr GLsizeiptrARB;

    #define GL_ARRAY_BUFFER_ARB               0x8892
    #define GL_ELEMENT_ARRAY_BUFFER_ARB       0x8893
    #define GL_ARRAY_BUFFER_BINDING_ARB       0x8894
    #define GL_ELEMENT_ARRAY_BUFFER_BINDING_ARB 0x8895
    #define GL_VERTEX_ARRAY_BUFFER_BINDING_ARB 0x8896
    #define GL_NORMAL_ARRAY_BUFFER_BINDING_ARB 0x8897
    #define GL_COLOR_ARRAY_BUFFER_BINDING_ARB 0x8898
    #define GL_INDEX_ARRAY_BUFFER_BINDING_ARB 0x8899
    #define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING_ARB 0x889A
    #define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING_ARB 0x889B
    #define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING_ARB 0x889C
    #define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING_ARB 0x889D
    #define GL_WEIGHT_ARRAY_BUFFER_BINDING_ARB 0x889E
    #define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB 0x889F
    #define GL_STREAM_DRAW_ARB                0x88E0
    #define GL_STREAM_READ_ARB                0x88E1
    #define GL_STREAM_COPY_ARB                0x88E2
    #define GL_STATIC_DRAW_ARB                0x88E4
    #define GL_STATIC_READ_ARB                0x88E5
    #define GL_STATIC_COPY_ARB                0x88E6
    #define GL_DYNAMIC_DRAW_ARB               0x88E8
    #define GL_DYNAMIC_READ_ARB               0x88E9
    #define GL_DYNAMIC_COPY_ARB               0x88EA
    #define GL_READ_ONLY_ARB                  0x88B8
    #define GL_WRITE_ONLY_ARB                 0x88B9
    #define GL_READ_WRITE_ARB                 0x88BA
    #define GL_BUFFER_SIZE_ARB                0x8764
    #define GL_BUFFER_USAGE_ARB               0x8765
    #define GL_BUFFER_ACCESS_ARB              0x88BB
    #define GL_BUFFER_MAPPED_ARB              0x88BC
    #define GL_BUFFER_MAP_POINTER_ARB         0x88BD
#endif

#ifndef GL_VERSION_1_5
    #define GL_STREAM_DRAW                    0x88E0
    #define GL_STREAM_READ                    0x88E1
    #define GL_STREAM_COPY                    0x88E2
    #define GL_STATIC_DRAW                    0x88E4
    #define GL_STATIC_READ                    0x88E5
    #define GL_STATIC_COPY                    0x88E6
    #define GL_DYNAMIC_DRAW                   0x88E8
    #define GL_DYNAMIC_READ                   0x88E9
    #define GL_DYNAMIC_COPY                   0x88EA
#endif

#ifndef GL_VERSION_2_1
    #define GL_PIXEL_PACK_BUFFER              0x88EB
    #define GL_PIXEL_UNPACK_BUFFER            0x88EC
    #define GL_PIXEL_PACK_BUFFER_BINDING      0x88ED
    #define GL_PIXEL_UNPACK_BUFFER_BINDING    0x88EF
#endif


#ifndef GL_ARB_pixel_buffer_object
    #define GL_PIXEL_PACK_BUFFER_ARB            0x88EB
    #define GL_PIXEL_UNPACK_BUFFER_ARB          0x88EC
    #define GL_PIXEL_PACK_BUFFER_BINDING_ARB    0x88ED
    #define GL_PIXEL_UNPACK_BUFFER_BINDING_ARB  0x88EF
#endif

namespace osg
{

class State;
class BufferData;
class BufferObject;

class BufferObjectProfile
{
    public:
        BufferObjectProfile():
            _target(0),
            _usage(0),
            _size(0) {}

        BufferObjectProfile(GLenum target, GLenum usage, unsigned int size):
            _target(target),
            _usage(usage),
            _size(size) {}

        BufferObjectProfile(const BufferObjectProfile& bpo):
            _target(bpo._target),
            _usage(bpo._usage),
            _size(bpo._size) {}

        bool operator < (const BufferObjectProfile& rhs) const
        {
            if (_target < rhs._target) return true;
            else if (_target > rhs._target) return false;
            if (_usage < rhs._usage) return true;
            else if (_usage > rhs._usage) return false;
            return _size < rhs._size;
        }

        bool operator == (const BufferObjectProfile& rhs) const
        {
            return (_target == rhs._target) &&
                   (_usage == rhs._usage) &&
                   (_size == rhs._size);
        }

        void setProfile(GLenum target, GLenum usage, unsigned int size)
        {
            _target = target;
            _usage = usage;
            _size = size;
        }

        BufferObjectProfile& operator = (const BufferObjectProfile& rhs)
        {
            _target = rhs._target;
            _usage = rhs._usage;
            _size = rhs._size;
            return *this;
        }

        GLenum _target;
        GLenum _usage;
        GLenum _size;
};

// forward declare
class GLBufferObjectSet;
class GLBufferObjectManager;

class OSG_EXPORT GLBufferObject : public Referenced
{
    public:

        GLBufferObject(unsigned int contextID, BufferObject* bufferObject, unsigned int glObjectID=0);

        void setProfile(const BufferObjectProfile& profile) { _profile = profile; }
        const BufferObjectProfile& getProfile() const { return _profile; }

        void setBufferObject(BufferObject* bufferObject);
        BufferObject* getBufferObject() { return _bufferObject; }

        struct BufferEntry
        {
            BufferEntry(): numRead(0), modifiedCount(0),dataSize(0),offset(0),dataSource(0) {}

            BufferEntry(const BufferEntry& rhs):
                numRead(rhs.numRead),
                modifiedCount(rhs.modifiedCount),
                dataSize(rhs.dataSize),
                offset(rhs.offset),
                dataSource(rhs.dataSource) {}

            BufferEntry& operator = (const BufferEntry& rhs)
            {
                if (&rhs==this) return *this;
                numRead = rhs.numRead;
                modifiedCount = rhs.modifiedCount;
                dataSize = rhs.dataSize;
                offset = rhs.offset;
                dataSource = rhs.dataSource;
                return *this;
            }

            unsigned int getNumClients() const;

            unsigned int        numRead;
            unsigned int        modifiedCount;
            unsigned int        dataSize;
            unsigned int        offset;
            BufferData*         dataSource;
        };

        inline unsigned int getContextID() const { return _contextID; }

        inline GLuint& getGLObjectID() { return _glObjectID; }
        inline GLuint getGLObjectID() const { return _glObjectID; }
        inline GLsizeiptrARB getOffset(unsigned int i) const { return _bufferEntries[i].offset; }

        inline void bindBuffer();

        inline void unbindBuffer()
        {
            _extensions->glBindBuffer(_profile._target,0);
        }

        inline bool isDirty() const { return _dirty; }

        void dirty() { _dirty = true; }

        void clear();

        void compileBuffer();

        void deleteGLObject();

        void assign(BufferObject* bufferObject);

        bool isPBOSupported() const { return _extensions->isPBOSupported(); }

        static GLBufferObject* createGLBufferObject(unsigned int contextID, const BufferObject* bufferObject);

        static void deleteAllBufferObjects(unsigned int contextID);
        static void discardAllBufferObjects(unsigned int contextID);
        static void flushAllDeletedBufferObjects(unsigned int contextID);
        static void discardAllDeletedBufferObjects(unsigned int contextID);
        static void flushDeletedBufferObjects(unsigned int contextID,double currentTime, double& availbleTime);
        static void releaseGLBufferObject(unsigned int contextID, GLBufferObject* to);

        /** Extensions class which encapsulates the querying of extensions and
        * associated function pointers, and provide convenience wrappers to
        * check for the extensions or use the associated functions.*/
        class OSG_EXPORT Extensions : public osg::Referenced
        {
        public:
            Extensions(unsigned int contextID);

            Extensions(const Extensions& rhs);

            void lowestCommonDenominator(const Extensions& rhs);

            void setupGLExtensions(unsigned int contextID);

            bool isBufferObjectSupported() const { return _glGenBuffers!=0; }
            bool isPBOSupported() const { return _isPBOSupported; }
            bool isUniformBufferObjectSupported() const { return _isUniformBufferObjectSupported; }
            bool isTBOSupported() const { return _isTBOSupported; }

            void glGenBuffers (GLsizei n, GLuint *buffers) const;
            void glBindBuffer (GLenum target, GLuint buffer) const;
            void glBufferData (GLenum target, GLsizeiptrARB size, const GLvoid *data, GLenum usage) const;
            void glBufferSubData (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid *data) const;
            void glDeleteBuffers (GLsizei n, const GLuint *buffers) const;
            GLboolean glIsBuffer (GLuint buffer) const;
            void glGetBufferSubData (GLenum target, GLintptrARB offset, GLsizeiptrARB size, GLvoid *data) const;
            GLvoid* glMapBuffer (GLenum target, GLenum access) const;
            GLboolean glUnmapBuffer (GLenum target) const;
            void glGetBufferParameteriv (GLenum target, GLenum pname, GLint *params) const;
            void glGetBufferPointerv (GLenum target, GLenum pname, GLvoid* *params) const;
            void glBindBufferRange (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size);
            void glBindBufferBase (GLenum target, GLuint index, GLuint buffer);
            void glTexBuffer( GLenum target, GLenum internalFormat, GLuint buffer ) const;

        protected:

            typedef void (GL_APIENTRY * GenBuffersProc) (GLsizei n, GLuint *buffers);
            typedef void (GL_APIENTRY * BindBufferProc) (GLenum target, GLuint buffer);
            typedef void (GL_APIENTRY * BufferDataProc) (GLenum target, GLsizeiptrARB size, const GLvoid *data, GLenum usage);
            typedef void (GL_APIENTRY * BufferSubDataProc) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid *data);
            typedef void (GL_APIENTRY * DeleteBuffersProc) (GLsizei n, const GLuint *buffers);
            typedef GLboolean (GL_APIENTRY * IsBufferProc) (GLuint buffer);
            typedef void (GL_APIENTRY * GetBufferSubDataProc) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, GLvoid *data);
            typedef GLvoid* (GL_APIENTRY * MapBufferProc) (GLenum target, GLenum access);
            typedef GLboolean (GL_APIENTRY * UnmapBufferProc) (GLenum target);
            typedef void (GL_APIENTRY * GetBufferParameterivProc) (GLenum target, GLenum pname, GLint *params);
            typedef void (GL_APIENTRY * GetBufferPointervProc) (GLenum target, GLenum pname, GLvoid* *params);
            typedef void (GL_APIENTRY * BindBufferRangeProc) (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size);
            typedef void (GL_APIENTRY * BindBufferBaseProc) (GLenum target, GLuint index, GLuint buffer);
            typedef void (GL_APIENTRY *TexBufferProc ) ( GLenum target, GLenum internalFormat, GLuint buffer );


            GenBuffersProc          _glGenBuffers;
            BindBufferProc          _glBindBuffer;
            BufferDataProc          _glBufferData;
            BufferSubDataProc       _glBufferSubData;
            DeleteBuffersProc       _glDeleteBuffers;
            IsBufferProc            _glIsBuffer;
            GetBufferSubDataProc    _glGetBufferSubData;
            MapBufferProc           _glMapBuffer;
            UnmapBufferProc         _glUnmapBuffer;
            GetBufferParameterivProc _glGetBufferParameteriv;
            GetBufferPointervProc   _glGetBufferPointerv;
            BindBufferRangeProc     _glBindBufferRange;
            BindBufferBaseProc      _glBindBufferBase;
            TexBufferProc           _glTexBuffer;

            bool _isPBOSupported;
            bool _isUniformBufferObjectSupported;
            bool _isTBOSupported;
        };

        /** Function to call to get the extension of a specified context.
        * If the Extension object for that context has not yet been created
        * and the 'createIfNotInitalized' flag been set to false then returns NULL.
        * If 'createIfNotInitalized' is true then the Extensions object is
        * automatically created.  However, in this case the extension object is
        * only created with the graphics context associated with ContextID..*/
        static Extensions* getExtensions(unsigned int contextID,bool createIfNotInitalized);

        /** setExtensions allows users to override the extensions across graphics contexts.
        * typically used when you have different extensions supported across graphics pipes
        * but need to ensure that they all use the same low common denominator extensions.*/
        static void setExtensions(unsigned int contextID,Extensions* extensions);

        bool hasAllBufferDataBeenRead() const;

        void setBufferDataHasBeenRead(const osg::BufferData* bd);

    protected:

        virtual ~GLBufferObject();

        unsigned int computeBufferAlignment(unsigned int pos, unsigned int bufferAlignment) const
        {
            if (bufferAlignment<2) return pos;
            if ((pos%bufferAlignment)==0) return pos;
            return ((pos/bufferAlignment)+1)*bufferAlignment;
        }

        unsigned int            _contextID;
        GLuint                  _glObjectID;

        BufferObjectProfile     _profile;
        unsigned int            _allocatedSize;

        bool                    _dirty;

        typedef std::vector<BufferEntry> BufferEntries;
        BufferEntries           _bufferEntries;

        BufferObject*           _bufferObject;

    public:

        GLBufferObjectSet*      _set;
        GLBufferObject*         _previous;
        GLBufferObject*         _next;
        unsigned int            _frameLastUsed;

    public:
        Extensions*             _extensions;

};

typedef std::list< ref_ptr<GLBufferObject> > GLBufferObjectList;

class OSG_EXPORT GLBufferObjectSet : public Referenced
{
    public:
        GLBufferObjectSet(GLBufferObjectManager* parent, const BufferObjectProfile& profile);

        const BufferObjectProfile& getProfile() const { return _profile; }

        void handlePendingOrphandedGLBufferObjects();

        void deleteAllGLBufferObjects();
        void discardAllGLBufferObjects();
        void flushAllDeletedGLBufferObjects();
        void discardAllDeletedGLBufferObjects();
        void flushDeletedGLBufferObjects(double currentTime, double& availableTime);

        GLBufferObject* takeFromOrphans(BufferObject* bufferObject);
        GLBufferObject* takeOrGenerate(BufferObject* bufferObject);

        void moveToBack(GLBufferObject* to);
        void addToBack(GLBufferObject* to);
        void orphan(GLBufferObject* to);
        void remove(GLBufferObject* to);
        void moveToSet(GLBufferObject* to, GLBufferObjectSet* set);

        unsigned int size() const { return _profile._size * _numOfGLBufferObjects; }

        bool makeSpace(unsigned int& size);

        bool checkConsistency() const;

        GLBufferObjectManager* getParent() { return _parent; }

        unsigned int computeNumGLBufferObjectsInList() const;
        unsigned int getNumOfGLBufferObjects() const { return _numOfGLBufferObjects; }
        unsigned int getNumOrphans() const { return static_cast<unsigned int>(_orphanedGLBufferObjects.size()); }
        unsigned int getNumPendingOrphans() const { return static_cast<unsigned int>(_pendingOrphanedGLBufferObjects.size()); }


    protected:

        virtual ~GLBufferObjectSet();

        OpenThreads::Mutex  _mutex;

        GLBufferObjectManager*  _parent;
        unsigned int            _contextID;
        BufferObjectProfile     _profile;
        unsigned int            _numOfGLBufferObjects;
        GLBufferObjectList      _orphanedGLBufferObjects;
        GLBufferObjectList      _pendingOrphanedGLBufferObjects;

        GLBufferObject*         _head;
        GLBufferObject*         _tail;
};

class OSG_EXPORT GLBufferObjectManager : public osg::Referenced
{
    public:
        GLBufferObjectManager(unsigned int contextID);

        unsigned int getContextID() const { return _contextID; }


        void setNumberActiveGLBufferObjects(unsigned int size) { _numActiveGLBufferObjects = size; }
        unsigned int& getNumberActiveGLBufferObjects() { return _numActiveGLBufferObjects; }
        unsigned int getNumberActiveGLBufferObjects() const { return _numActiveGLBufferObjects; }

        void setNumberOrphanedGLBufferObjects(unsigned int size) { _numOrphanedGLBufferObjects = size; }
        unsigned int& getNumberOrphanedGLBufferObjects() { return _numOrphanedGLBufferObjects; }
        unsigned int getNumberOrphanedGLBufferObjects() const { return _numOrphanedGLBufferObjects; }

        void setCurrGLBufferObjectPoolSize(unsigned int size) { _currGLBufferObjectPoolSize = size; }
        unsigned int& getCurrGLBufferObjectPoolSize() { return _currGLBufferObjectPoolSize; }
        unsigned int getCurrGLBufferObjectPoolSize() const { return _currGLBufferObjectPoolSize; }

        void setMaxGLBufferObjectPoolSize(unsigned int size);
        unsigned int getMaxGLBufferObjectPoolSize() const { return _maxGLBufferObjectPoolSize; }

        bool hasSpace(unsigned int size) const { return (_currGLBufferObjectPoolSize+size)<=_maxGLBufferObjectPoolSize; }
        bool makeSpace(unsigned int size);

        GLBufferObject* generateGLBufferObject(const osg::BufferObject* bufferObject);

        void handlePendingOrphandedGLBufferObjects();

        void deleteAllGLBufferObjects();
        void discardAllGLBufferObjects();
        void flushAllDeletedGLBufferObjects();
        void discardAllDeletedGLBufferObjects();
        void flushDeletedGLBufferObjects(double currentTime, double& availableTime);
        void releaseGLBufferObject(GLBufferObject* to);

        GLBufferObjectSet* getGLBufferObjectSet(const BufferObjectProfile& profile);

        void newFrame(osg::FrameStamp* fs);
        void resetStats();
        void reportStats(std::ostream& out);
        void recomputeStats(std::ostream& out);

        unsigned int& getFrameNumber() { return _frameNumber; }
        unsigned int& getNumberFrames() { return _numFrames; }

        unsigned int& getNumberDeleted() { return _numDeleted; }
        double& getDeleteTime() { return _deleteTime; }

        unsigned int& getNumberGenerated() { return _numGenerated; }
        double& getGenerateTime() { return _generateTime; }

        unsigned int& getNumberApplied() { return _numApplied; }
        double& getApplyTime() { return _applyTime; }

        static osg::ref_ptr<GLBufferObjectManager>& getGLBufferObjectManager(unsigned int contextID);

    protected:

        typedef std::map< BufferObjectProfile, osg::ref_ptr<GLBufferObjectSet> > GLBufferObjectSetMap;
        unsigned int            _contextID;
        unsigned int            _numActiveGLBufferObjects;
        unsigned int            _numOrphanedGLBufferObjects;
        unsigned int            _currGLBufferObjectPoolSize;
        unsigned int            _maxGLBufferObjectPoolSize;
        GLBufferObjectSetMap    _glBufferObjectSetMap;

        unsigned int            _frameNumber;

        unsigned int            _numFrames;
        unsigned int            _numDeleted;
        double                  _deleteTime;

        unsigned int            _numGenerated;
        double                  _generateTime;

        unsigned int            _numApplied;
        double                  _applyTime;

};


class OSG_EXPORT BufferObject : public Object
{
    public:

        BufferObject();

        /** Copy constructor using CopyOp to manage deep vs shallow copy.*/
        BufferObject(const BufferObject& bo,const CopyOp& copyop=CopyOp::SHALLOW_COPY);

        virtual bool isSameKindAs(const Object* obj) const { return dynamic_cast<const BufferObject*>(obj)!=NULL; }
        virtual const char* libraryName() const { return "osg"; }
        virtual const char* className() const { return "BufferObject"; }

        void setTarget(GLenum target) { _profile._target = target; }
        GLenum getTarget() const { return _profile._target; }

        /** Set what type of usage the buffer object will have. Options are:
          *          GL_STREAM_DRAW, GL_STREAM_READ, GL_STREAM_COPY,
          *          GL_STATIC_DRAW, GL_STATIC_READ, GL_STATIC_COPY,
          *          GL_DYNAMIC_DRAW, GL_DYNAMIC_READ, or GL_DYNAMIC_COPY.
          */
        void setUsage(GLenum usage) { _profile._usage = usage; }

        /** Get the type of usage the buffer object has been set up for.*/
        GLenum getUsage() const { return _profile._usage; }

        BufferObjectProfile& getProfile() { return _profile; }
        const BufferObjectProfile& getProfile() const { return _profile; }


        /** Set whether the BufferObject should use a GLBufferObject just for copying the BufferData and release it immmediately so that it may be reused.*/
        void setCopyDataAndReleaseGLBufferObject(bool copyAndRelease) { _copyDataAndReleaseGLBufferObject = copyAndRelease; }

        /** Get whether the BufferObject should use a GLBufferObject just for copying the BufferData and release it immmediately.*/
        bool getCopyDataAndReleaseGLBufferObject() const { return _copyDataAndReleaseGLBufferObject; }


        void dirty();

        /** Resize any per context GLObject buffers to specified size. */
        virtual void resizeGLObjectBuffers(unsigned int maxSize);

        /** If State is non-zero, this function releases OpenGL objects for
          * the specified graphics context. Otherwise, releases OpenGL objects
          * for all graphics contexts. */
        void releaseGLObjects(State* state=0) const;

        unsigned int addBufferData(BufferData* bd);
        void removeBufferData(unsigned int index);
        void removeBufferData(BufferData* bd);

        void setBufferData(unsigned int index, BufferData* bd);
        BufferData* getBufferData(unsigned int index) { return _bufferDataList[index]; }
        const BufferData* getBufferData(unsigned int index) const { return _bufferDataList[index]; }

        unsigned int getNumBufferData() const { return static_cast<unsigned int>(_bufferDataList.size()); }

        void setGLBufferObject(unsigned int contextID, GLBufferObject* glbo) { _glBufferObjects[contextID] = glbo; }

        GLBufferObject* getGLBufferObject(unsigned int contextID) const { return _glBufferObjects[contextID].get(); }

        GLBufferObject* getOrCreateGLBufferObject(unsigned int contextID) const
        {
            if (!_glBufferObjects[contextID]) _glBufferObjects[contextID] = GLBufferObject::createGLBufferObject(contextID, this);
            return _glBufferObjects[contextID].get();
        }

        unsigned int computeRequiredBufferSize() const;

        /** deprecated, provided for backwards compatibility.*/
        static void deleteBufferObject(unsigned int contextID,GLuint globj);

    protected:

        ~BufferObject();

        typedef std::vector< BufferData* > BufferDataList;
        typedef osg::buffered_object< osg::ref_ptr<GLBufferObject> > GLBufferObjects;

        BufferObjectProfile     _profile;

        bool                    _copyDataAndReleaseGLBufferObject;

        BufferDataList          _bufferDataList;

        mutable GLBufferObjects _glBufferObjects;
};

class OSG_EXPORT BufferData : public Object
{
    public:

        BufferData():
            Object(true),
            _modifiedCount(0),
            _bufferIndex(0),
            _numClients(0) {}

        /** Copy constructor using CopyOp to manage deep vs shallow copy.*/
        BufferData(const BufferData& bd,const CopyOp& copyop=CopyOp::SHALLOW_COPY):
            osg::Object(bd,copyop),
            _modifiedCount(0),
            _bufferIndex(0),
            _modifiedCallback(bd._modifiedCallback),
            _numClients(0) {}

        virtual bool isSameKindAs(const Object* obj) const { return dynamic_cast<const BufferData*>(obj)!=NULL; }
        virtual const char* libraryName() const { return "osg"; }
        virtual const char* className() const { return "BufferData"; }

        virtual const GLvoid*   getDataPointer() const = 0;
        virtual unsigned int    getTotalDataSize() const = 0;

        virtual osg::Array* asArray() { return 0; }
        virtual const osg::Array* asArray() const { return 0; }

        virtual osg::PrimitiveSet* asPrimitiveSet() { return 0; }
        virtual const osg::PrimitiveSet* asPrimitiveSet() const { return 0; }

        virtual osg::Image* asImage() { return 0; }
        virtual const osg::Image* asImage() const { return 0; }

        void setBufferObject(BufferObject* bufferObject);
        BufferObject* getBufferObject() { return _bufferObject.get(); }
        const BufferObject* getBufferObject() const { return _bufferObject.get(); }

        void setBufferIndex(unsigned int index) { _bufferIndex = index; }
        unsigned int getBufferIndex() const { return _bufferIndex; }

        GLBufferObject* getGLBufferObject(unsigned int contextID) const { return _bufferObject.valid() ? _bufferObject->getGLBufferObject(contextID) : 0; }
        GLBufferObject* getOrCreateGLBufferObject(unsigned int contextID) const { return _bufferObject.valid() ? _bufferObject->getOrCreateGLBufferObject(contextID) : 0; }

        struct ModifiedCallback : public virtual osg::Object
        {
            ModifiedCallback() {}

            ModifiedCallback(const ModifiedCallback&,const CopyOp&) {}

            META_Object(osg,ModifiedCallback);

            virtual void modified(BufferData* /*bufferData*/) const {}
        };

        void setModifiedCallback(ModifiedCallback* md) { _modifiedCallback = md; }
        ModifiedCallback* getModifiedCallback() { return _modifiedCallback.get(); }
        const ModifiedCallback* getModifiedCallback() const { return _modifiedCallback.get(); }

        /** Dirty the primitive, which increments the modified count, to force buffer objects to update.
          * If a ModifiedCallback is attached to this BufferData then the callback is called prior to the bufferObject's dirty is called. */
        inline void dirty()
        {
            ++_modifiedCount;
            if (_modifiedCallback.valid()) _modifiedCallback->modified(this);
            if (_bufferObject.valid()) _bufferObject->dirty();
        }

        /** Set the modified count value.*/
        inline void setModifiedCount(unsigned int value) { _modifiedCount=value; }

        /** Get modified count value.*/
        inline unsigned int getModifiedCount() const { return _modifiedCount; }

        /** Resize any per context GLObject buffers to specified size. */
        virtual void resizeGLObjectBuffers(unsigned int maxSize);

        /** If State is non-zero, this function releases OpenGL objects for
          * the specified graphics context. Otherwise, releases OpenGL objects
          * for all graphics contexts. */
        void releaseGLObjects(State* state=0) const;

        unsigned int getNumClients() const { return _numClients; }

        void addClient(osg::Object * /*client*/) { ++_numClients; }

        void removeClient(osg::Object * /*client*/) { --_numClients; }

protected:

        virtual ~BufferData();

        unsigned int                    _modifiedCount;

        unsigned int                    _bufferIndex;
        osg::ref_ptr<BufferObject>      _bufferObject;
        osg::ref_ptr<ModifiedCallback>  _modifiedCallback;

        unsigned int _numClients;
};


class Array;
class OSG_EXPORT VertexBufferObject : public BufferObject
{
    public:

        VertexBufferObject();

        /** Copy constructor using CopyOp to manage deep vs shallow copy.*/
        VertexBufferObject(const VertexBufferObject& vbo,const CopyOp& copyop=CopyOp::SHALLOW_COPY);

        META_Object(osg,VertexBufferObject);

        unsigned int addArray(osg::Array* array);
        void removeArray(osg::Array* array);

        void setArray(unsigned int i, Array* array);
        Array* getArray(unsigned int i);
        const Array* getArray(unsigned int i) const;

    protected:
        virtual ~VertexBufferObject();
};

class DrawElements;
class OSG_EXPORT ElementBufferObject : public BufferObject
{
    public:

        ElementBufferObject();

        /** Copy constructor using CopyOp to manage deep vs shallow copy.*/
        ElementBufferObject(const ElementBufferObject& pbo,const CopyOp& copyop=CopyOp::SHALLOW_COPY);

        META_Object(osg,ElementBufferObject);

        unsigned int addDrawElements(osg::DrawElements* PrimitiveSet);
        void removeDrawElements(osg::DrawElements* PrimitiveSet);

        void setDrawElements(unsigned int i, DrawElements* PrimitiveSet);
        DrawElements* getDrawElements(unsigned int i);
        const DrawElements* getDrawElements(unsigned int i) const;

    protected:

        virtual ~ElementBufferObject();
};

class Image;
class OSG_EXPORT PixelBufferObject : public BufferObject
{
    public:

        PixelBufferObject(osg::Image* image=0);

        /** Copy constructor using CopyOp to manage deep vs shallow copy.*/
        PixelBufferObject(const PixelBufferObject& pbo,const CopyOp& copyop=CopyOp::SHALLOW_COPY);

        META_Object(osg,PixelBufferObject);

        void setImage(osg::Image* image);

        Image* getImage();
        const Image* getImage() const;

        bool isPBOSupported(unsigned int contextID) const { return _glBufferObjects[contextID]->isPBOSupported(); }

    protected:

        virtual ~PixelBufferObject();
};

/**
 * This object represent a general class of pixel buffer objects,
 * which are capable of allocating buffer object (memory)
 * on the GPU. The memory can then be used either for CPU-GPU
 * pixel transfer or directly for GPU-GPU transfer, without CPU intervention.
 **/
class OSG_EXPORT PixelDataBufferObject : public BufferObject
{
    public:
        PixelDataBufferObject();
        PixelDataBufferObject(const PixelDataBufferObject& pbo, const CopyOp& copyop=CopyOp::SHALLOW_COPY);

        META_Object(osg, PixelDataBufferObject);

        //! Set new size of the buffer object. This will reallocate the memory on the next compile
        inline void setDataSize(unsigned int size) { _profile._size = size; dirty(); }

        //! Get data size of the used buffer
        inline unsigned int getDataSize() const { return _profile._size; }

        //! Compile the buffer (reallocate the memory if buffer is dirty)
        virtual void compileBuffer(State& state) const;

        //! Bind the buffer in read mode, which means that data can be downloaded from the buffer (note: GL_PIXEL_UNPACK_BUFFER_ARB)
        virtual void bindBufferInReadMode(State& state);

        //! Bind the buffer in write mode, which means following OpenGL instructions will write data into the buffer (note: GL_PIXEL_PACK_BUFFER_ARB)
        virtual void bindBufferInWriteMode(State& state);

        //! Unbind the buffer
        virtual void unbindBuffer(unsigned int contextID) const;

        /** Resize any per context GLObject buffers to specified size. */
        virtual void resizeGLObjectBuffers(unsigned int maxSize);

        enum Mode
        {
            //! A normal mode of this data buffer
            NONE = 0,

            //! Buffer is in read mode (@see bindBufferInReadMode)
            READ = 1,

            //! Buffer is in write mode (@see bindBufferInWriteMode)
            WRITE = 2
        };

        Mode getMode(unsigned int contextID) const { return (Mode)_mode[contextID]; }

    protected:

        virtual ~PixelDataBufferObject();

        typedef osg::buffered_value<unsigned int> ModeList;

        mutable ModeList _mode;

};

class OSG_EXPORT UniformBufferObject : public BufferObject
{
 public:
    UniformBufferObject();
    UniformBufferObject(const UniformBufferObject& ubo, const CopyOp& copyop=CopyOp::SHALLOW_COPY);
    META_Object(osg, UniformBufferObject);
 protected:
    virtual ~UniformBufferObject();
};

class OSG_EXPORT AtomicCounterBufferObject : public BufferObject
{
 public:
    AtomicCounterBufferObject();
    AtomicCounterBufferObject(const AtomicCounterBufferObject& ubo, const CopyOp& copyop=CopyOp::SHALLOW_COPY);
    META_Object(osg, AtomicCounterBufferObject);

 protected:
    virtual ~AtomicCounterBufferObject();
};

inline void GLBufferObject::bindBuffer()
{
    _extensions->glBindBuffer(_profile._target,_glObjectID);
    if (_set) _set->moveToBack(this);
}

}

#endif
