/* osgCompute - Copyright (C) 2008-2009 SVT Group
 *                                                                     
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *                                                                     
 * 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
 * GNU Lesse General Public License for more details.
 *
 * The full license is in LICENSE file included with this distribution.
*/

#ifndef OSGCUDA_INTOPBUFFER
#define OSGCUDA_INTOPBUFFER 1

#include <string.h>
#include "osgCuda/Context"
#include <osg/GL>
#include <cuda_gl_interop.h>
#include <osg/Geometry>
#include <osg/Texture>
#include <osg/Texture1D>
#include <osg/Texture2D>
#include <osg/Texture3D>
#include <osg/Texture2DArray>
#include "osgCuda/Buffer"

namespace osgCuda
{
    template<class T>
    class IntOpBuffer;

    typedef IntOpBuffer<unsigned char>                                    UByteIntOpBuffer;
    typedef IntOpBuffer<osg::Vec4ub>                                      Vec4ubIntOpBuffer;
    typedef IntOpBuffer<char>                                             ByteIntOpBuffer;
    typedef IntOpBuffer<osg::Vec2b>                                       Vec2bIntOpBuffer;
    typedef IntOpBuffer<osg::Vec3b>                                       Vec3bIntOpBuffer;
    typedef IntOpBuffer<osg::Vec4b>                                       Vec4bIntOpBuffer;
    typedef IntOpBuffer<unsigned short>                                   UShortIntOpBuffer;
    typedef IntOpBuffer<short>                                            ShortIntOpBuffer;
    typedef IntOpBuffer<osg::Vec2s>                                       Vec2sIntOpBuffer;
    typedef IntOpBuffer<osg::Vec3s>                                       Vec3sIntOpBuffer;
    typedef IntOpBuffer<osg::Vec4s>                                       Vec4sIntOpBuffer;
    typedef IntOpBuffer<unsigned int>                                     UIntIntOpBuffer;
    typedef IntOpBuffer<int>                                              IntIntOpBuffer;
    typedef IntOpBuffer<unsigned long>                                    ULongIntOpBuffer;
    typedef IntOpBuffer<long>                                             LongIntOpBuffer;
    typedef IntOpBuffer<float>                                            FloatIntOpBuffer;
    typedef IntOpBuffer<osg::Vec2f>                                       Vec2fIntOpBuffer;
    typedef IntOpBuffer<osg::Vec3f>                                       Vec3fIntOpBuffer;
    typedef IntOpBuffer<osg::Vec4f>                                       Vec4fIntOpBuffer;
    typedef IntOpBuffer<double>                                           DoubleIntOpBuffer;
    typedef IntOpBuffer<osg::Vec2d>                                       Vec2dIntOpBuffer;
    typedef IntOpBuffer<osg::Vec3d>                                       Vec3dIntOpBuffer;
    typedef IntOpBuffer<osg::Vec4d>                                       Vec4dIntOpBuffer;

    typedef std::vector< osg::ref_ptr<osg::Object> >                      IntOpObjectList;
    typedef std::vector< osg::ref_ptr<osg::Object> >::iterator            IntOpObjectListItr;
    typedef std::vector< osg::ref_ptr<osg::Object> >::const_iterator      IntOpObjectListCnstItr;
    typedef std::vector< bool >                                           TexDynamicList;
    typedef std::vector< bool >::iterator                                 TexDynamicListItr;
    typedef std::vector< bool >::const_iterator                           TexDynamicListCnstItr;

    /**
    */
    template< class DATATYPE >
    struct IntOpStream : public BufferStream<DATATYPE>
    {
        GLuint                   _tex;
        GLenum                   _texType;
        GLenum                   _texFormat;

        GLenum                   _target;
        GLuint                   _bo;
        bool                     _boRegistered;

        IntOpStream();
        virtual ~IntOpStream();
    };

    /////////////////////////////////////////////////////////////////////////////////////////////////
    // PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////////////////////////
    //------------------------------------------------------------------------------
    template< class DATATYPE >
    IntOpStream<DATATYPE>::IntOpStream()
        : BufferStream<DATATYPE>(),
        _tex( UINT_MAX ),
        _texType( GL_NONE ),
        _texFormat( GL_NONE ),
        _target( GL_NONE ),
        _bo( UINT_MAX ),
        _boRegistered(false)
    {
    }

    //------------------------------------------------------------------------------
    template< class DATATYPE >
    IntOpStream<DATATYPE>::~IntOpStream()
    {
        if( _boRegistered )
            static_cast<Context*>( osgCompute::BufferStream<DATATYPE>::_context.get() )->freeBufferObject( _bo );
    }

    /**
    */
    template< class DATATYPE >
    class IntOpBuffer : public Buffer<DATATYPE>
    {
    public:
        IntOpBuffer();

        META_Object( osgCuda, IntOpBuffer )

        virtual bool init();
        virtual void clear();

        virtual void setIntOpObject( osg::Geometry& intOpGeometry, unsigned int streamIdx = 0 );
        virtual void setIntOpObject( osg::Texture& intOpTexture, unsigned int streamIdx = 0, bool isDynamic = false );
        virtual osg::Object* getIntOpObject( unsigned int streamIdx = 0 );
        virtual const osg::Object* getIntOpObject( unsigned int streamIdx = 0 ) const;

    protected:
        virtual ~IntOpBuffer();
        void clearLocal();

        virtual DATATYPE* mapStream( BufferStream<DATATYPE>& stream, unsigned int mapping ) const;
        virtual void unmapStream( BufferStream<DATATYPE>& stream ) const;

        virtual BufferStream<DATATYPE>* newStream( const osgCompute::Context& context, unsigned int streamIdx ) const;
        bool initGeometry( const osgCompute::Context& context, IntOpStream<DATATYPE>& stream ) const;
        bool initTexture( const osgCompute::Context& context, IntOpStream<DATATYPE>& stream ) const;
        void syncPBO( const osgCompute::Context& context, IntOpStream<DATATYPE>& stream ) const;
        void syncTexture( const osgCompute::Context& context, IntOpStream<DATATYPE>& stream ) const;

        TexDynamicList                                      _texDynamicList;
        IntOpObjectList                                     _intOpObjects;

    private:
        // copy constructor and operator should not be called
        IntOpBuffer( const IntOpBuffer& , const osg::CopyOp& ) {}
        IntOpBuffer& operator=(const IntOpBuffer&) { return (*this); }
    };

    /////////////////////////////////////////////////////////////////////////////////////////////////
    // PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////////////////////////
    //------------------------------------------------------------------------------
    template< class DATATYPE >
    IntOpBuffer<DATATYPE>::IntOpBuffer()
        : Buffer<DATATYPE>()
    {
        clearLocal();
    }

    //------------------------------------------------------------------------------
    template< class DATATYPE >
    IntOpBuffer<DATATYPE>::~IntOpBuffer()
    {
        clearLocal();
    }

    //------------------------------------------------------------------------------
    template< class DATATYPE >
    void IntOpBuffer<DATATYPE>::clear()
    {
        Buffer<DATATYPE>::clear();
        clearLocal();
    }

    //------------------------------------------------------------------------------
    template< class DATATYPE >
    bool IntOpBuffer<DATATYPE>::init()
    {
        if( !osgCompute::Param::isDirty() )
            return true;

        // resize the object list
        if( osgCuda::IntOpBuffer<DATATYPE>::_intOpObjects.size() < osgCompute::Buffer<DATATYPE>::getNumStreams() )
        {
            _intOpObjects.resize( osgCompute::Buffer<DATATYPE>::getNumStreams(), NULL );
            _texDynamicList.resize( osgCompute::Buffer<DATATYPE>::getNumStreams(), false );
        }

        unsigned int numElements = 1;
        for( unsigned int d=0; d<osgCompute::Buffer<DATATYPE>::_dimensions.size(); ++d )
            numElements *= osgCompute::Buffer<DATATYPE>::_dimensions[d];

        unsigned int _streamSize = sizeof(DATATYPE) * numElements;

        for( unsigned int t=0; t<_intOpObjects.size(); ++t )
        {
            if( !_intOpObjects[t].valid() )
                continue;

            if( osgCompute::Buffer<DATATYPE>::_dimensions.size() == 0 )
            {
                osg::notify(osg::FATAL)
                    << "CUDA::IntOpBuffer::init() for Buffer \""
                    << osg::Object::getName()<< "\": No dimensions defined for Buffer! Call setDimension() first."
                    << std::endl;

                clear();
                return false;
            }


            unsigned int _objectSize = 0;

            ///////////////////
            // PROOF TEXTURE //
            ///////////////////
            if( osg::Texture* curTex = dynamic_cast<osg::Texture*>( _intOpObjects[t].get() ) )
            {
                // MIPMAPS
                osg::Texture::FilterMode minfm = curTex->getFilter( osg::Texture::MIN_FILTER );
                osg::Texture::FilterMode magfm = curTex->getFilter( osg::Texture::MAG_FILTER );
                if( minfm == osg::Texture::LINEAR_MIPMAP_LINEAR ||
                    minfm == osg::Texture::LINEAR_MIPMAP_NEAREST ||
                    minfm == osg::Texture::NEAREST_MIPMAP_LINEAR ||
                    minfm == osg::Texture::NEAREST_MIPMAP_NEAREST ||
                    magfm == osg::Texture::LINEAR_MIPMAP_LINEAR ||
                    magfm == osg::Texture::LINEAR_MIPMAP_NEAREST ||
                    magfm == osg::Texture::NEAREST_MIPMAP_LINEAR ||
                    magfm == osg::Texture::NEAREST_MIPMAP_NEAREST
                    )
                {
                    osg::notify(osg::FATAL)
                        << "CUDA::IntOpBuffer::init() for Buffer \""
                        << osg::Object::getName()<< "\": MipMaps are activated for Texture is \""
                        << curTex->getName()<<"\" and Stream \""<<t<<"\" but MipMapping is not supported yet!"
                        << std::endl;

                    clear();
                    return false;
                }


                // PIXELSIZE
                // Setup SourceType and SourceFormat
                GLenum pixelFormat = (curTex->getSourceFormat() != GL_NONE)? curTex->getSourceFormat() : GL_RGBA;
                GLenum pixelType = (curTex->getSourceType() != GL_NONE)? curTex->getSourceType() : GL_UNSIGNED_BYTE;

                // Proof Component Information
                unsigned int pixelSizeInBits = osg::Image::computePixelSizeInBits( pixelFormat, pixelType );
                unsigned int pixelSizeInBytes = (pixelSizeInBits/8 + ((pixelSizeInBits%8)?1:0));


                // DIMENSION
                unsigned int numElements = 1;
                osg::Image* image = NULL;
                switch ( curTex->getTextureTarget() )
                {
                case GL_TEXTURE_1D:
                    {
                        image = curTex->getImage(0);
                        if( image != NULL )
                            numElements *= image->s();
                        else
                            numElements *= curTex->getTextureWidth();
                    }
                    break;
                case GL_TEXTURE_2D:
                    {
                        image = curTex->getImage(0);
                        if( image != NULL )
                        {
                            numElements *= image->s();
                            numElements *= image->t();
                        }
                        else
                        {
                            numElements *= curTex->getTextureWidth();
                            numElements *= curTex->getTextureHeight();
                        }
                    }
                    break;
                case GL_TEXTURE_2D_ARRAY_EXT:
                    {
                        image = curTex->getImage(0);
                        if( image != NULL )
                        {
                            numElements *= image->s();
                            numElements *= image->t();
                            numElements *= curTex->getTextureDepth();
                        }
                        else
                        {
                            numElements *= curTex->getTextureWidth();
                            numElements *= curTex->getTextureHeight();
                            numElements *= curTex->getTextureDepth();
                        }
                    }
                    break;
                case GL_TEXTURE_3D:
                    {
                        image = curTex->getImage(0);
                        if( image != NULL )
                        {
                            numElements *= image->s();
                            numElements *= image->t();
                            numElements *= image->r();
                        }
                        else
                        {
                            numElements *= curTex->getTextureWidth();
                            numElements *= curTex->getTextureHeight();
                            numElements *= curTex->getTextureDepth();
                        }
                    }
                    break;
                default:
                    {
                        numElements = 0;
                    }
                    break;
                }

                _objectSize = numElements * pixelSizeInBytes;
            }
            else if( osg::Geometry* curGeom = dynamic_cast<osg::Geometry*>( _intOpObjects[t].get() ) )
            {
                osg::Geometry::ArrayList arrayList;
                _objectSize = curGeom->getArrayList( arrayList );
                for( unsigned int a=0; a<arrayList.size(); ++a )
                {
                    // we assume that all arrays have the
                    // same number of elements
                    _objectSize += arrayList[a]->getTotalDataSize();
                }
            }
        }

        return Buffer<DATATYPE>::init();
    }

    //------------------------------------------------------------------------------
    template< class DATATYPE >
    void IntOpBuffer<DATATYPE>::setIntOpObject( osg::Texture& intOpTexture, unsigned int streamIdx, bool isDynamic )
    {
        if( !osgCompute::Param::isDirty() )
            return;

        if( _intOpObjects.size() < (streamIdx+1) )
        {
            _intOpObjects.resize( streamIdx + 1, NULL );
            _texDynamicList.resize( streamIdx + 1, false );
        }

        _intOpObjects[streamIdx] = &intOpTexture;
        _texDynamicList[streamIdx] = isDynamic;
    }

    //------------------------------------------------------------------------------
    template< class DATATYPE >
    void IntOpBuffer<DATATYPE>::setIntOpObject( osg::Geometry& intOpGeometry, unsigned int streamIdx )
    {
        if( !osgCompute::Param::isDirty() )
            return;

        if( _intOpObjects.size() < (streamIdx+1) )
            _intOpObjects.resize( streamIdx + 1, NULL );

        _intOpObjects[streamIdx] = &intOpGeometry;
    }

    //------------------------------------------------------------------------------
    template< class DATATYPE >
    osg::Object* IntOpBuffer<DATATYPE>::getIntOpObject( unsigned int streamIdx )
    {
        if( _intOpObjects.size() < (streamIdx+1) )
            return NULL;

        return _intOpObjects[streamIdx].get();
    }

    //------------------------------------------------------------------------------
    template< class DATATYPE >
    const osg::Object* IntOpBuffer<DATATYPE>::getIntOpObject( unsigned int streamIdx ) const
    {
        if( _intOpObjects.size() < (streamIdx+1) )
            return NULL;

        return _intOpObjects[streamIdx].get();
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////
    // PROTECTED FUNCTIONS //////////////////////////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////////////////////////
    //------------------------------------------------------------------------------
    template< class DATATYPE >
    void IntOpBuffer<DATATYPE>::clearLocal()
    {
        _texDynamicList.clear();
        _intOpObjects.clear();
    }

    //------------------------------------------------------------------------------
    template< class DATATYPE >
    DATATYPE* IntOpBuffer<DATATYPE>::mapStream( BufferStream<DATATYPE>& stream, unsigned int mapping ) const
    {
        IntOpStream<DATATYPE>* iopstream = static_cast<IntOpStream<DATATYPE>*>( &stream );
        if( NULL == iopstream )
        {
            osg::notify(osg::FATAL)
                << "CUDA::IntOpBuffer::mapStream() for Buffer \""
                << osg::Object::getName() <<"\": Could not get IntOpStream for Context \""
                << stream._context->getId() << "\" and stream \""<<stream._streamIdx<<"\"."
                << std::endl;

            return NULL;
        }

        if( stream._streamIdx < _intOpObjects.size() &&
            NULL != _intOpObjects[stream._streamIdx] &&
            iopstream->_mapping != mapping )
        {
            ////////////////
            // UPDATE PBO //
            ////////////////
            // if necessary sync dynamic texture with PBO
            if( ((mapping & osgCompute::MAP_DEVICE_SOURCE) ||
                (mapping & osgCompute::MAP_HOST_SOURCE) )&&
                !(iopstream->_target == GL_ARRAY_BUFFER_ARB) &&
                _texDynamicList[iopstream->_streamIdx] )
            {
                syncPBO( *stream._context, *iopstream );
                iopstream->_syncHost = true;
            }

            /////////////
            // MAP PBO //
            /////////////
            cudaError res = cudaGLMapBufferObject( reinterpret_cast<void**>(&iopstream->_devPtr), iopstream->_bo );
            if( cudaSuccess != res )
            {
                osg::notify(osg::WARN)
                    << "CUDA::Texture::mapStream() for Texture \""<< osg::Object::getName()
                    << "\": Something goes wrong on cudaGLMapBufferObject() for Context \""
                    << stream._context->getId()<<"\" and stream \""<<stream._streamIdx<<"\". Returned code is "<<std::hex<<res<<"."
                    << std::endl;

                return NULL;
            }
        }

        return Buffer<DATATYPE>::mapStream( stream, mapping );
    }

    //------------------------------------------------------------------------------
    template< class DATATYPE >
    void IntOpBuffer<DATATYPE>::unmapStream( BufferStream<DATATYPE>& stream ) const
    {
        IntOpStream<DATATYPE>* iopstream = static_cast<IntOpStream<DATATYPE>*>( &stream );
        if( NULL == iopstream )
        {
            osg::notify(osg::FATAL)
                << "CUDA::IntOpBuffer::unmapStream() for Buffer \""
                << osg::Object::getName() <<"\": Could not receive IntOpStream for context \""
                << stream._context->getId() << "\" and stream \""<<stream._streamIdx<<"\"."
                << std::endl;

            return;
        }

        if( stream._streamIdx < _intOpObjects.size() &&
            NULL != _intOpObjects[stream._streamIdx] )
        {
            ///////////
            // UNMAP //
            ///////////
            cudaError res = cudaGLUnmapBufferObject( iopstream->_bo );
            if( cudaSuccess != res )
            {
                osg::notify(osg::WARN)
                    << "CUDA::IntOpBuffer::unmapStream(): Something goes wrong on cudaGLUnmapBufferObject() for context \""
                    << stream._context->getId()<<"\" and stream \""<<stream._streamIdx<<"\". Returned code is "<<std::hex<<res<<"."
                    << std::endl;
                return;
            }
            iopstream->_devPtr = NULL;

            ////////////////////
            // UPDATE TEXTURE //
            ////////////////////
            if( iopstream->_mapping == osgCompute::MAP_DEVICE_TARGET &&
                iopstream->_target != GL_ARRAY_BUFFER_ARB )
            {
                // sync texture object as required
                syncTexture( *stream._context, *iopstream );
            }
        }

        Buffer<DATATYPE>::unmapStream( stream );
    }

    //------------------------------------------------------------------------------
    template< class DATATYPE >
    BufferStream<DATATYPE>* IntOpBuffer<DATATYPE>::newStream( const osgCompute::Context& context, unsigned int streamIdx ) const
    {
        IntOpStream<DATATYPE>* stream = new IntOpStream<DATATYPE>;
        stream->_streamIdx = streamIdx;
        stream->_context = const_cast<osgCompute::Context*>( &context );
        //stream->_allocHint = osgCompute::Buffer<DATATYPE>::getAllocHint();

        ////////////////////
        // PROOF SETTINGS //
        ////////////////////
        if( !context.isStateValid() )
        {
            // Context is invalid
            osg::notify(osg::FATAL)
                << "CUDA::IntOpBuffer::newStream() for Buffer \""
                << osg::Object::getName() << "\": No valid osg::State found within Context \""
                << context.getId()<<"\"."
                << std::endl;

            delete stream;
            return false;
        }

        if( !dynamic_cast<const Context*>( &context ) )
        {
            // Extensions not supported
            osg::notify(osg::FATAL)
                << "CUDA::IntOpBuffer::newStream() for Buffer \""
                << osg::Object::getName()<< "\": Context \""<<context.getId()
                << "\"  and stream \""<<streamIdx<<"\" is not a CUDA-based Context."
                << std::endl;

            delete stream;
            return NULL;
        }

        ////////////////////
        // INIT SYNC DATA //
        ////////////////////
        if( dynamic_cast<osg::Texture*>( _intOpObjects[streamIdx].get() ) )
        {
            if( !initTexture(context,*stream) )
            {
                delete stream;
                return NULL;
            }
        }
        else if( dynamic_cast<osg::Geometry*>( _intOpObjects[streamIdx].get() ) )
        {
            if( !initGeometry(context,*stream) )
            {
                delete stream;
                return NULL;
            }
        }
        // else cuda buffer

        return stream;
    }

    //------------------------------------------------------------------------------
    template< class DATATYPE >
    bool IntOpBuffer<DATATYPE>::initGeometry( const osgCompute::Context& context, IntOpStream<DATATYPE>& stream ) const
    {
        ////////////////////////
        // SETUP SOURCEOBJECT //
        ////////////////////////
        osg::Geometry* curGeom = dynamic_cast<osg::Geometry*>( _intOpObjects[stream._streamIdx].get() );
        if( !curGeom )
            return false;

        if( !curGeom->getUseVertexBufferObjects() )
        {
            // Object cannot be received
            osg::notify(osg::FATAL)
                << "CUDA::IntOpBuffer::initGeometry() for Buffer \""
                << osg::Object::getName() << "\": Source-Geometry \""
                << curGeom->getName()
                << "\" does not use VertexBufferObjects. Current context is \""<<context.getId()<<"\"  and current stream is \""<<stream._streamIdx<<"\"."
                << std::endl;

            return false;
        }

        osg::VertexBufferObject* vbo = curGeom->getOrCreateVertexBufferObject();

        //////////////
        // SETUP BO //
        //////////////
        // compile buffer object if necessary
        if( vbo->isDirty(context.getState()->getContextID()) )
            vbo->compileBuffer( const_cast<osg::State&>(*context.getState()) );

        // using vertex buffers
        stream._target = GL_ARRAY_BUFFER_ARB;
        stream._bo = vbo->buffer(context.getState()->getContextID());

        //////////////////
        // REGISTER PBO //
        //////////////////
        if( !stream._boRegistered )
        {
            if( !static_cast<const Context*>( &context )->registerBufferObject( stream._bo, osgCompute::Buffer<DATATYPE>::getStreamSize() ) )
            {
                osg::notify(osg::FATAL)
                    << "CUDA::IntOpBuffer::initGeometry() for Buffer \""
                    << osg::Object::getName()<< "\": Could not register BufferObject for Context \""
                    << context.getId()<<"\"  and stream \""<<stream._streamIdx<<"\"."
                    << std::endl;

                return false;
            }
        }
        stream._boRegistered = true;

        return true;
    }

    //------------------------------------------------------------------------------
    template< class DATATYPE >
    bool IntOpBuffer<DATATYPE>::initTexture( const osgCompute::Context& context, IntOpStream<DATATYPE>& stream ) const
    {
        osg::BufferObject::Extensions* bufferExt = osg::BufferObject::getExtensions( context.getState()->getContextID(),true );
        osg::Texture2DArray::Extensions* texArrayExt = osg::Texture2DArray::getExtensions( context.getState()->getContextID(),true );
        osg::Texture3D::Extensions* tex3DExt = osg::Texture3D::getExtensions( context.getState()->getContextID(),true );
        if( !bufferExt || !texArrayExt || !tex3DExt )
        {
            // Extensions are not supported
            osg::notify(osg::FATAL)
                << "CUDA::IntOpBuffer::initTexture() for Buffer \""
                << osg::Object::getName() << "\": Required Extensions are not found within Context \""
                << context.getId()<<"\" and stream \""<<stream._streamIdx<<"\"."
                << std::endl;

            return false;
        }

        // clear previous errors
        GLenum errorNo = glGetError();

        ////////////////////////
        // SETUP SOURCEOBJECT //
        ////////////////////////
        osg::Texture* curTex = dynamic_cast<osg::Texture*>( _intOpObjects[stream._streamIdx].get() );
        if( !curTex )
            return false;

        osg::Texture::TextureObject* curTexObject = NULL;

        // Compile GL objects if necessary
        if( !curTex->areAllTextureObjectsLoaded() )
            curTex->compileGLObjects( const_cast<osg::State&>(*context.getState()) );

        // Get the TextureObject
        curTexObject = curTex->getTextureObject( context.getState()->getContextID() );
        if( !curTexObject )
        {
            // Object cannot be received
            osg::notify(osg::FATAL)
                << "CUDA::IntOpBuffer::initTexture() for Buffer \""
                << osg::Object::getName() << "\": Could not receive SourceObject of Source-Texture \""
                << curTex->getName()
                << "\" for Context \""<<context.getId()<<"\"  and stream \""<<stream._streamIdx<<"\"."
                << std::endl;

            return false;
        }

        if( curTex->getTextureWidth() != curTexObject->_width ||
            curTex->getTextureHeight() != curTexObject->_height ||
            curTex->getTextureDepth() != curTexObject->_depth )
        {
            osg::notify(osg::FATAL)
                << "CUDA::IntOpBuffer::initTexture() for Buffer \""
                << osg::Object::getName() << "\": GLobject has different size as Texture \""
                << curTex->getName()
                << "\". For Context " << context.getId()<<" and stream \""<<stream._streamIdx<<"\"."
                << std::endl;

            return false;
        }

        // Test if texture is already present within
        // the current Context
        if( !glIsTexture( curTexObject->_id ) )
        {
            // object is not supported
            osg::notify(osg::FATAL)
                << "CUDA::IntOpBuffer::initTexture() for Buffer \""
                << osg::Object::getName() << "\": SourceObject of Source-Texture \""
                << curTex->getName()
                << "\" is not present within Context "
                << context.getId()<<" and stream \""<<stream._streamIdx<<"\"."
                << std::endl;

            return false;
        }

        ///////////////////
        // SETUP MEMBERS //
        ///////////////////
        if( curTex->getInternalFormatMode() == osg::Texture::USE_IMAGE_DATA_FORMAT )
        {
            osg::Image* image = curTex->getImage(0);
            if(!image)
            {
                osg::notify(osg::FATAL)
                << "CUDA::IntOpBuffer::initTexture() for Buffer \""
                << osg::Object::getName()<< "\": Could not find promised image data. Context is \""
                << context.getId()<<"\"  and stream is \""<<stream._streamIdx<<"\"."
                << std::endl;

                return false;
            }

            stream._texType = image->getDataType();
        }
        else
        {
            stream._texType = (curTex->getSourceType() != GL_NONE)? curTex->getSourceType() : GL_UNSIGNED_BYTE;
        }

        stream._texFormat = curTexObject->_internalFormat;
        stream._tex = curTexObject->_id;
        stream._target = curTexObject->_target;

        ///////////////
        // SETUP PBO //
        ///////////////
        stream._bo = static_cast<const Context*>( &context )->mallocBufferObject( osgCompute::Buffer<DATATYPE>::getStreamSize() );
        if( UINT_MAX == stream._bo )
        {
            osg::notify(osg::FATAL)
                << "CUDA::IntOpBuffer::initTexture() for Buffer \""
                << osg::Object::getName()<< "\": Could not generate BufferObject (glGenBuffers()) for Context \""
                << context.getId()<<"\"  and stream \""<<stream._streamIdx<<"\"."
                << std::endl;

            return false;
        }
        stream._boRegistered = true;


        if( curTexObject->isAllocated() )
        {
            //////////////
            // SYNC PBO //
            //////////////
            // Sync PBO with Texture-Data if Texture is allocated
            syncPBO( context, stream );
        }
        else
        {
            /////////////////////////////
            // ALLOCATE TEXTURE MEMORY //
            /////////////////////////////
            // else allocate the memory.
            glBindTexture( stream._target, stream._tex );

            // Allocate memory for texture if not done so far in order to allow slot
            // to call glTexSubImage() during runtime
            switch( curTexObject->_target )
            {
            case GL_TEXTURE_1D:
                {
                    glTexImage1D(
                        stream._target, 0,
                        curTexObject->_internalFormat,
                        curTexObject->_width,
                        curTexObject->_border,
                        stream._texFormat, stream._texType, NULL );
                }
                break;
            case GL_TEXTURE_2D:
                {
                    glTexImage2D(
                        stream._target, 0,
                        curTexObject->_internalFormat,
                        curTexObject->_width, curTexObject->_height,
                        curTexObject->_border,
                        stream._texFormat, stream._texType, NULL );
                }
                break;
            case GL_TEXTURE_2D_ARRAY_EXT:
                {
                    texArrayExt->glTexImage3D(
                        stream._target, 0,
                        curTexObject->_internalFormat,
                        curTexObject->_width, curTexObject->_height, curTexObject->_depth,
                        curTexObject->_border,
                        stream._texFormat, stream._texType, NULL );
                }
                break;
            case GL_TEXTURE_3D:
                {
                    tex3DExt->glTexImage3D(
                        stream._target, 0,
                        curTexObject->_internalFormat,
                        curTexObject->_width, curTexObject->_height, curTexObject->_depth,
                        curTexObject->_border,
                        stream._texFormat, stream._texType, NULL );
                }
                break;
            default:
                {
                    osg::notify(osg::FATAL)
                        << "CUDA::IntOpBuffer::initTexture() for Buffer \""
                        << osg::Object::getName()<< "\": Texture-Target "
                        << std::hex<<curTexObject->_target<<" is not supported for context \""
                        << context.getId()<<"\" and stream \""<<stream._streamIdx<<"\"."
                        << std::endl;

                    glBindTexture( stream._target, 0 );
                    bufferExt->glBindBuffer( GL_PIXEL_UNPACK_BUFFER_ARB, 0 );
                    return false;
                }
            }

            errorNo = glGetError();
            if( errorNo != GL_NO_ERROR )
            {
                osg::notify(osg::FATAL)
                    << "CUDA::IntOpBuffer::initTexture() for Buffer \""
                    << osg::Object::getName()<< "\": Something goes wrong on glTexImageXD() for context \""
                    << context.getId()<<"\" and stream \""<<stream._streamIdx<<"\". Returned code is "
                    << std::hex<<errorNo<<"."
                    << std::endl;

                glBindTexture( stream._target, 0 );
                bufferExt->glBindBuffer( GL_PIXEL_UNPACK_BUFFER_ARB, 0 );
                return false;
            }

            glBindTexture( stream._target, 0 );

            // Mark context based Texture-Object as allocated
            curTexObject->setAllocated(
                1,
                curTexObject->_internalFormat,
                curTexObject->_width,
                curTexObject->_height,
                curTexObject->_depth,
                0 );
        }

        return true;
    }

    //------------------------------------------------------------------------------
    template< class DATATYPE >
    void IntOpBuffer<DATATYPE>::syncTexture( const osgCompute::Context& context, IntOpStream<DATATYPE>& stream ) const
    {
        if( stream._bo == UINT_MAX || stream._tex == UINT_MAX )
            return;

        osg::BufferObject::Extensions* bufferExt = osg::BufferObject::getExtensions( context.getState()->getContextID(),true );
        osg::Texture2DArray::Extensions* texArrayExt = osg::Texture2DArray::getExtensions( context.getState()->getContextID(),true );
        osg::Texture3D::Extensions* tex3DExt = osg::Texture3D::getExtensions( context.getState()->getContextID(),true );

        bufferExt->glBindBuffer( GL_PIXEL_UNPACK_BUFFER_ARB,  stream._bo );
        glBindTexture( stream._target, stream._tex );

        // UNPACK the PBO data
        switch( stream._target )
        {
        case GL_TEXTURE_1D:
            {
                glTexSubImage1D(
                    stream._target, 0, 0, osgCompute::Buffer<DATATYPE>::getDimension(0), stream._texFormat, stream._texType, NULL );
            }
            break;
        case GL_TEXTURE_2D:
            {
                glTexSubImage2D(
                    stream._target, 0, 0, 0, osgCompute::Buffer<DATATYPE>::getDimension(0), osgCompute::Buffer<DATATYPE>::getDimension(1), stream._texFormat, stream._texType, NULL );
            }
            break;
        case GL_TEXTURE_2D_ARRAY_EXT:
            {
                texArrayExt->glTexSubImage3D(
                    stream._target, 0, 0, 0, 0, osgCompute::Buffer<DATATYPE>::getDimension(0), osgCompute::Buffer<DATATYPE>::getDimension(1), osgCompute::Buffer<DATATYPE>::getDimension(2), stream._texFormat, stream._texType, NULL );
            }
            break;
        case GL_TEXTURE_3D:
            {
                tex3DExt->glTexSubImage3D(
                    stream._target, 0, 0, 0, 0, osgCompute::Buffer<DATATYPE>::getDimension(0), osgCompute::Buffer<DATATYPE>::getDimension(1), osgCompute::Buffer<DATATYPE>::getDimension(2), stream._texFormat, stream._texType, NULL );
            }
            break;
        }

        GLenum errorStatus = glGetError();
        if( errorStatus != GL_NO_ERROR )
        {
            osg::notify(osg::FATAL)
                << "CUDA::IntOpBuffer::syncTexture() for Buffer \""
                << osg::Object::getName()<< "\": Something goes wrong on glTex(Sub)ImageXD() for context \""
                << context.getId()<<"\" and stream \""<<stream._streamIdx<<"\". Returned code is "
                << std::hex<<errorStatus<<"."
                << std::endl;
        }


        glBindTexture( stream._target, 0 );
        bufferExt->glBindBuffer( GL_PIXEL_UNPACK_BUFFER_ARB, 0 );
    }

    //------------------------------------------------------------------------------
    template< class DATATYPE >
    void IntOpBuffer<DATATYPE>::syncPBO( const osgCompute::Context& context, IntOpStream<DATATYPE>& stream ) const
    {
        if( stream._bo == UINT_MAX || stream._tex == UINT_MAX )
            return;

        osg::BufferObject::Extensions* bufferExt = osg::BufferObject::getExtensions( context.getState()->getContextID(),true );
        osg::Texture2DArray::Extensions* texArrayExt = osg::Texture2DArray::getExtensions( context.getState()->getContextID(),true );
        osg::Texture3D::Extensions* tex3DExt = osg::Texture3D::getExtensions( context.getState()->getContextID(),true );

        ////////////////////
        // UNREGISTER PBO //
        ////////////////////
        cudaError res = cudaGLUnregisterBufferObject( stream._bo );
        if( cudaSuccess != res )
        {
            osg::notify(osg::FATAL)
                << "CUDA::Texture::unregisterPBO() for Texture \""
                << osg::Object::getName()<< "\": Something goes wrong on cudaGLUnregisterBufferObject() for context \""
                << context.getId()<<"\" and stream \""<<stream._streamIdx<<"\". Returned code is "
                << std::hex<<res<<"."
                << std::endl;
        }

        ///////////////
        // COPY DATA //
        ///////////////
        glBindTexture( stream._target, stream._tex );
        bufferExt->glBindBuffer( GL_PIXEL_PACK_BUFFER_ARB,  stream._bo );

        // PACK the data for the PBO
        glGetTexImage(  stream._target, 0, stream._texFormat, stream._texType, NULL );

        GLenum errorStatus = glGetError();
        if( errorStatus != GL_NO_ERROR )
        {
            osg::notify(osg::FATAL)
                << "CUDA::IntOpBuffer::syncPBO() for Texture \""
                << osg::Object::getName()<< "\": Something goes wrong on glGetTexImage() for context \""
                << context.getId()<<"\" and stream \""<<stream._streamIdx<<"\". Returned code is "
                << std::hex<<errorStatus<<"."
                << std::endl;
        }

        bufferExt->glBindBuffer( GL_PIXEL_PACK_BUFFER_ARB, 0 );
        glBindTexture( stream._target, 0 );

        ////////////////////////
        // REGISTER PBO AGAIN //
        ////////////////////////
        res = cudaGLRegisterBufferObject( stream._bo );
        if( cudaSuccess != res )
        {
            osg::notify(osg::FATAL)
                << "CUDA::Texture::syncPBO() for Texture \""
                << osg::Object::getName()<< "\": Something goes wrong on cudaGLRegisterBufferObject() for context \""
                << context.getId()<<"\" and stream \""<<stream._streamIdx<<"\". Returned code is "
                << std::hex<<res<<"."
                << std::endl;
        }

        stream._syncHost = true;
    }
}


#endif //OSGCUDA_INTOPBUFFER
