/**************************************************************************************/
/*                                                                                    */
/*  Visualization Library                                                             */
/*  http://visualizationlibrary.org                                                   */
/*                                                                                    */
/*  Copyright (c) 2005-2020, Michele Bosi                                             */
/*  All rights reserved.                                                              */
/*                                                                                    */
/*  Redistribution and use in source and binary forms, with or without modification,  */
/*  are permitted provided that the following conditions are met:                     */
/*                                                                                    */
/*  - Redistributions of source code must retain the above copyright notice, this     */
/*  list of conditions and the following disclaimer.                                  */
/*                                                                                    */
/*  - Redistributions in binary form must reproduce the above copyright notice, this  */
/*  list of conditions and the following disclaimer in the documentation and/or       */
/*  other materials provided with the distribution.                                   */
/*                                                                                    */
/*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND   */
/*  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED     */
/*  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE            */
/*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR  */
/*  ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES    */
/*  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;      */
/*  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON    */
/*  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT           */
/*  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS     */
/*  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                      */
/*                                                                                    */
/**************************************************************************************/

#ifndef FramebufferObject_INCLUDE_ONCE
#define FramebufferObject_INCLUDE_ONCE

#include <vlGraphics/Camera.hpp>
#include <vlGraphics/Texture.hpp>
#include <set>
#include <map>

namespace vl
{
  class FramebufferObject;
//-----------------------------------------------------------------------------
// FBOAbstractAttachment
//-----------------------------------------------------------------------------
  /**
   * Abstract class that represents a framebuffer object attachment to be used with FramebufferObject.
   *
   * \sa
   * - https://www.khronos.org/opengl/wiki/Framebuffer
   * - https://www.khronos.org/opengl/wiki/Default_Framebuffer
   * - https://www.khronos.org/opengl/wiki/Framebuffer_Object
   * - OpenGLContext::framebuffer() and FramebufferObject
   */
  class VLGRAPHICS_EXPORT FBOAbstractAttachment: public Object
  {
    VL_INSTRUMENT_ABSTRACT_CLASS(vl::FBOAbstractAttachment, Object)

    friend class FramebufferObject;

  private:
    // no copy constructor and no assignment operator
    FBOAbstractAttachment( const FBOAbstractAttachment& other ): Object( other ) {}
    void operator=( const FBOAbstractAttachment& ) {}

  public:
    /** Constructor  */
    FBOAbstractAttachment() {}

    /** Destructor. */
    virtual ~FBOAbstractAttachment() { VL_CHECK(fboFramebuffers().size() == 0); }

    /** Removes the FBO attachment from all bound FBO render targets. */
    virtual void unbindFromAllFBO();

    /** Returns an std::set containing the FramebufferObject that use this FBO attachment. */
    const std::set< ref<FramebufferObject> >& fboFramebuffers() const { return mFramebufferObjects; }

  protected:
    virtual void bindAttachment( FramebufferObject* fbo, EAttachmentPoint attach_point ) = 0;

  protected:
    std::set< ref<FramebufferObject> > mFramebufferObjects;
  };
  //-----------------------------------------------------------------------------
  // FBORenderbufferAttachment
  //-----------------------------------------------------------------------------
  /**
   * Abstract class that represents a framebuffer renderbuffer attachment, that is, a non-texture fbo attachment (wraps \p glFramebufferRenderbuffer()).
   * \sa FramebufferObject.
   * \sa http://www.opengl.org/sdk/docs/man3/xhtml/glFramebufferRenderbuffer.xml
   */
  class VLGRAPHICS_EXPORT FBORenderbufferAttachment: public FBOAbstractAttachment
  {
    VL_INSTRUMENT_ABSTRACT_CLASS(vl::FBORenderbufferAttachment, FBOAbstractAttachment)

    friend class FramebufferObject;

  public:
    /** Constructor. */
    FBORenderbufferAttachment(): mHandle( 0 ), mWidth( 0 ), mHeight( 0 ), mSamples( 0 ), mReallocateRenderbuffer( true ) {}

    /** Destructor. */
    ~FBORenderbufferAttachment() { deleteRenderBuffer(); }

    /**
     * Creates a renderbuffer object calling glGenRenderbuffers().
     * The identifier returned by glGenRenderbuffers() can be queried calling the handle() method.
     */
    void createRenderBuffer();

    /** Deletes the renderbuffer object created with the createRenderBuffer() method. */
    void deleteRenderBuffer();

    /**
     * Sets the handle for this attachment, the handle must have been created by glGenRenderbuffers().
     * Normally you don't need to call this. See also: createRenderBuffer(), handle().
     */
    void setHandle( GLuint  handle ) { if ( mHandle != handle ) { mHandle = handle; mReallocateRenderbuffer = false; } }

    /** Returns the handle obtained by createRenderBuffer() using glGenRenderbuffers() */
    GLuint handle() const { return mHandle; }

    /**
     * Initializes the storage of the renderbuffer with the given sample count and dimensions.
     * This method does nothing if the the width, height and sample count has not changed since last time it was called.
     * Note that the renderbuffer storage type is defined by the setType() method of
     * FBOColorBufferAttachment, FBODepthBufferAttachment, FBOStencilBufferAttachment,
     * FBODepthStencilBufferAttachment. See also setSamples().
     */
    void initStorage( int w, int h, int samples );

    /** The same as calling initStorage( width(), height() ) */
    void initStorage() { initStorage( width(), height(), samples() ); }

    /**
     * Returns the with of the renderbuffer storage.
     * Note that if renderbufferStorageReady() returns \p false it means that
     * the renderbuffer has not been allocated yet.
     */
    int width() const { return mWidth; }

    /**
     * Returns the height of the renderbuffer storage.
     * Note that if renderbufferStorageReady() returns \p false it means that
     * the renderbuffer has not been allocated yet.
     */
    int height() const { return mHeight; }

    /**
     * Returns the number of samples to be used when allocating the renderbuffer's storage.
     * Note that if renderbufferStorageReady() returns \p false it means that
     * the renderbuffer has not been allocated yet.
     */
    int samples() const { return mSamples; }

    /**
     * The width of the renderbuffer storage to be allocated.
     * If 'w' is set to 0 the renderbuffer storage allocation will use the dimensions of the next FramebufferObject bound.
     */
    void setWidth( int w ) { if ( w != mWidth ) /* schedules recreation */ mReallocateRenderbuffer = true; mWidth = w; }

    /**
     * The height of the renderbuffer storage to be allocated.
     * If 'h' is set to 0 the renderbuffer storage allocation will use the dimensions of the next FramebufferObject bound.
     */
    void setHeight( int h ) { if ( h != mHeight ) /* schedules recreation */ mReallocateRenderbuffer = true; mHeight = h; }

    /**
     * Sets the number of samples to be specified when allocating the renderbuffer's storage.
     */
    void setSamples( int samples ) { if ( samples != mSamples ) /* schedules recreation */ mReallocateRenderbuffer = true; mSamples = samples; }

    /** Returns \p false if the renderbuffer storage needs to be created or reallocated due to a change of the sample count, renderbuffer type or dimension. */
    bool renderbufferStorageReady() const { return !mReallocateRenderbuffer; }

  protected:
    void bindAttachment( FramebufferObject* fbo, EAttachmentPoint attach_point );
    virtual int internalType() = 0;

  protected:
    GLuint mHandle;
    int mWidth;
    int mHeight;
    int mSamples;
    bool mReallocateRenderbuffer;
  };
  //-----------------------------------------------------------------------------
  // FBOColorBufferAttachment
  //-----------------------------------------------------------------------------
  /**
   * A color renderbuffer to be attached to a FramebufferObject.
   */
  class VLGRAPHICS_EXPORT FBOColorBufferAttachment: public FBORenderbufferAttachment
  {
    VL_INSTRUMENT_CLASS(vl::FBOColorBufferAttachment, FBOColorBufferAttachment)

  public:
    /** Constructor */
    FBOColorBufferAttachment( EColorBufferFormat type = CBF_RGBA )
    {
      VL_DEBUG_SET_OBJECT_NAME()
      mType = type;
    }

    /** The type to specify when allocating the renderbuffer storage. */
    void setType( EColorBufferFormat type ) { if ( type != mType ) /* schedules recreation */ mReallocateRenderbuffer = true; mType = type; }

    /** The type to specify when allocating the renderbuffer storage. */
    EColorBufferFormat type() const { return mType; }

  protected:
    virtual int internalType() { return type(); }

  protected:
    EColorBufferFormat mType;
  };
  //-----------------------------------------------------------------------------
  // FBODepthBufferAttachment
  //-----------------------------------------------------------------------------
  /**
   * A depth renderbuffer to be attached to a FramebufferObject.
   */
  class VLGRAPHICS_EXPORT FBODepthBufferAttachment: public FBORenderbufferAttachment
  {
    VL_INSTRUMENT_CLASS(vl::FBODepthBufferAttachment, FBORenderbufferAttachment)

  public:
    /** Constructor */
    FBODepthBufferAttachment( EDepthBufferFormat type = DBF_DEPTH_COMPONENT )
    {
      VL_DEBUG_SET_OBJECT_NAME()
      mType = type;
    }

    /** The type to specify when allocating the renderbuffer storage. */
    void setType( EDepthBufferFormat type ) { if ( type != mType ) /* schedules recreation */ mReallocateRenderbuffer = true; mType = type; }

    /** The type to specify when allocating the renderbuffer storage. */
    EDepthBufferFormat type() const { return mType; }

  protected:
    virtual int internalType() { return type(); }

  protected:
    EDepthBufferFormat mType;
  };
  //-----------------------------------------------------------------------------
  // FBOStencilBufferAttachment
  //-----------------------------------------------------------------------------
  /**
   * A stencil renderbuffer to be attached to a FramebufferObject.
   */
  class VLGRAPHICS_EXPORT FBOStencilBufferAttachment: public FBORenderbufferAttachment
  {
    VL_INSTRUMENT_CLASS(vl::FBOStencilBufferAttachment, FBORenderbufferAttachment)

  public:
    /** Constructor */
    FBOStencilBufferAttachment( EStencilBufferFormat type = SBF_STENCIL_INDEX8 )
    {
      VL_DEBUG_SET_OBJECT_NAME()
      mType = type;
    }

    /** The type to specify when allocating the renderbuffer storage. */
    void setType( EStencilBufferFormat type ) { if ( type != mType ) /* schedules recreation */ mReallocateRenderbuffer = true; mType = type; }

    /** The type to specify when allocating the renderbuffer storage. */
    EStencilBufferFormat type() const { return mType; }

  protected:
    virtual int internalType() { return type(); }

  protected:
    EStencilBufferFormat mType;
  };
  //-----------------------------------------------------------------------------
  // FBODepthStencilBufferAttachment
  //-----------------------------------------------------------------------------
  /**
   * A depth+stencil renderbuffer to be attached to a FramebufferObject.
   */
  class VLGRAPHICS_EXPORT FBODepthStencilBufferAttachment: public FBORenderbufferAttachment
  {
    VL_INSTRUMENT_CLASS(vl::FBODepthStencilBufferAttachment, FBORenderbufferAttachment)

  public:
    /** Constructor */
    FBODepthStencilBufferAttachment( EDepthStencilBufferFormat type = DSBT_DEPTH_STENCIL )
    {
      VL_DEBUG_SET_OBJECT_NAME()
      mType = type;
    }

    /** The type to specify when allocating the renderbuffer storage. */
    void setType( EDepthStencilBufferFormat type ) { if ( type != mType ) /* schedules recreation */ mReallocateRenderbuffer = true; mType = type; }

    /** The type to specify when allocating the renderbuffer storage. */
    EDepthStencilBufferFormat type() const { return mType; }

  protected:
    virtual int internalType() { return type(); }

  protected:
    EDepthStencilBufferFormat mType;
  };
  //-----------------------------------------------------------------------------
  // FBOAbstractTextureAttachment
  //-----------------------------------------------------------------------------
  /**
   * Base class for all the framebuffer texture attachments (see also FramebufferObject).
   */
  class VLGRAPHICS_EXPORT FBOAbstractTextureAttachment: public FBOAbstractAttachment
  {
    VL_INSTRUMENT_ABSTRACT_CLASS(vl::FBOAbstractTextureAttachment, FBOAbstractAttachment)

  public:
    /** Constructor. */
    FBOAbstractTextureAttachment( Texture* texture, int mipmap_level ): mTexture(texture), mMipmapLevel(mipmap_level)
    {
      VL_DEBUG_SET_OBJECT_NAME()
    }

    /** The texture bound to this attachment. */
    void setTexture( Texture* texture ) { mTexture = texture; }

    /** The texture bound to this attachment. */
    Texture* texture() { return mTexture.get(); }

    /** The texture bound to this attachment. */
    const Texture* texture() const { return mTexture.get(); }

    /** The mipmap level of the texture to attach. */
    void setMipmapLevel( int mipmap_level ) { mMipmapLevel = mipmap_level; }

    /** The mipmap level of the texture to attach. */
    int mipmapLevel() const { return mMipmapLevel; }

  protected:
    ref<Texture> mTexture;
    int mMipmapLevel;
  };
  //-----------------------------------------------------------------------------
  // FBOTexture1DAttachment
  //-----------------------------------------------------------------------------
  /**
   * A 1D texture renderbuffer to be attached to a FramebufferObject (wraps \p glFramebufferTexture1D()).
   * \sa http://www.opengl.org/sdk/docs/man3/xhtml/glFramebufferTexture.xml
   */
  class VLGRAPHICS_EXPORT FBOTexture1DAttachment: public FBOAbstractTextureAttachment
  {
    VL_INSTRUMENT_CLASS(vl::FBOTexture1DAttachment, FBOAbstractTextureAttachment)

  public:
    /** Constructor. */
    FBOTexture1DAttachment(): FBOAbstractTextureAttachment( NULL, 0 )
    {
      VL_DEBUG_SET_OBJECT_NAME()
    }

    /** Constructor. */
    FBOTexture1DAttachment( Texture* texture, int mipmap_level ): FBOAbstractTextureAttachment( texture, mipmap_level )
    {
      VL_DEBUG_SET_OBJECT_NAME()
    }

  protected:
    virtual void bindAttachment( FramebufferObject* fbo, EAttachmentPoint attach_point );
  };
  //-----------------------------------------------------------------------------
  // FBOTexture2DAttachment
  //-----------------------------------------------------------------------------
  /**
   * A 2D texture renderbuffer to be attached to a FramebufferObject (wraps \p glFramebufferTexture2D()).
   * \sa http://www.opengl.org/sdk/docs/man3/xhtml/glFramebufferTexture.xml
   */
  class VLGRAPHICS_EXPORT FBOTexture2DAttachment: public FBOAbstractTextureAttachment
  {
    VL_INSTRUMENT_CLASS(vl::FBOTexture2DAttachment, FBOAbstractTextureAttachment)

  public:
    /** Constructor. */
    FBOTexture2DAttachment( ): FBOAbstractTextureAttachment( NULL, 0 )
    {
      VL_DEBUG_SET_OBJECT_NAME()
      mTextureTarget = T2DT_TEXTURE_2D;
    }

    /** Constructor. */
    FBOTexture2DAttachment( Texture* texture, int mipmap_level, ETex2DTarget target ): FBOAbstractTextureAttachment( texture, mipmap_level )
    {
      VL_DEBUG_SET_OBJECT_NAME()
      mTextureTarget = target;
    }

    /** What type of texture is expected, or for cube map textures, which face is to be attached. */
    void setTextureTarget( ETex2DTarget target ) { mTextureTarget = target; }

    /** What type of texture is expected, or for cube map textures, which face is to be attached. */
    ETex2DTarget textureTarget() const { return mTextureTarget; }

  protected:
    virtual void bindAttachment( FramebufferObject* fbo, EAttachmentPoint attach_point );

  protected:
    ETex2DTarget mTextureTarget;
  };
  //-----------------------------------------------------------------------------
  // FBOTextureAttachment
  //-----------------------------------------------------------------------------
  /**
   * A texture renderbuffer to be attached to a FramebufferObject (wraps \p glFramebufferTexture()).
   * \sa http://www.opengl.org/sdk/docs/man3/xhtml/glFramebufferTexture.xml
   */
  class VLGRAPHICS_EXPORT FBOTextureAttachment: public FBOAbstractTextureAttachment
  {
    VL_INSTRUMENT_CLASS(vl::FBOTextureAttachment, FBOAbstractTextureAttachment)

  public:
    /** Constructor. */
    FBOTextureAttachment(): FBOAbstractTextureAttachment( NULL, 0 )
    {
      VL_DEBUG_SET_OBJECT_NAME()
    }

    /** Constructor. */
    FBOTextureAttachment( Texture* texture, int mipmap_level ): FBOAbstractTextureAttachment( texture, mipmap_level )
    {
      VL_DEBUG_SET_OBJECT_NAME()
    }

  protected:
    virtual void bindAttachment( FramebufferObject* fbo, EAttachmentPoint attach_point );

  };
  //-----------------------------------------------------------------------------
  // FBOTexture3DAttachment
  //-----------------------------------------------------------------------------
  /**
   * A 3D texture renderbuffer to be attached to a FramebufferObject (wraps \p glFramebufferTexture3D()).
   * \sa http://www.opengl.org/sdk/docs/man3/xhtml/glFramebufferTexture.xml
   */
  class VLGRAPHICS_EXPORT FBOTexture3DAttachment: public FBOAbstractTextureAttachment
  {
    VL_INSTRUMENT_CLASS(vl::FBOTexture3DAttachment, FBOAbstractTextureAttachment)

  public:
    FBOTexture3DAttachment(): FBOAbstractTextureAttachment( NULL, 0 )
    {
      VL_DEBUG_SET_OBJECT_NAME()
      mLayer = 0;
    }

    FBOTexture3DAttachment( Texture* texture, int mipmap_level, int layer ): FBOAbstractTextureAttachment( texture, mipmap_level )
    {
      VL_DEBUG_SET_OBJECT_NAME()
      mLayer   = layer;
    }

    /** The layer of a 2-dimensional image within a 3-dimensional texture. */
    void setLayer( int layer ) { mLayer = layer; }

    /** The layer of a 2-dimensional image within a 3-dimensional texture. */
    int layer() const { return mLayer; }

  protected:
    virtual void bindAttachment( FramebufferObject* fbo, EAttachmentPoint attach_point );

  protected:
    int mLayer;
  };
  //-----------------------------------------------------------------------------
  // FBOTextureLayerAttachment
  //-----------------------------------------------------------------------------
  /**
   * A texture layer renderbuffer to be attached to a FramebufferObject (wraps \p glFramebufferTextureLayer()).
   * \sa http://www.opengl.org/sdk/docs/man3/xhtml/glFramebufferTextureLayer.xml
   */
  class VLGRAPHICS_EXPORT FBOTextureLayerAttachment: public FBOAbstractTextureAttachment
  {
    VL_INSTRUMENT_CLASS(vl::FBOTextureLayerAttachment, FBOAbstractTextureAttachment)

  public:
    /** Constructor. */
    FBOTextureLayerAttachment(): FBOAbstractTextureAttachment( NULL, 0 )
    {
      VL_DEBUG_SET_OBJECT_NAME()
      mLayer = 0;
    }

    /** Constructor. */
    FBOTextureLayerAttachment( Texture* texture, int mipmap_level, int layer ): FBOAbstractTextureAttachment( texture, mipmap_level )
    {
      VL_DEBUG_SET_OBJECT_NAME()
      mLayer   = layer;
    }

    /** The layer of a 2-dimensional image within a 3-dimensional texture or texture array. */
    int layer() const { return mLayer; }

    /** The layer of a 2-dimensional image within a 3-dimensional texture or texture array. */
    void setLayer( int layer ) { mLayer = layer; }

  protected:
    virtual void bindAttachment( FramebufferObject* fbo, EAttachmentPoint attach_point );

  protected:
    ref<Texture> mTexture;
    int mMipmapLevel;
    int mLayer;
  };
  //-----------------------------------------------------------------------------
  // FramebufferObject
  //-----------------------------------------------------------------------------
  /**
   * Implements a framebuffer object to be used as a rendering target as specified by
   * the \p ARB_framebuffer_object extension.
   * An FramebufferObject belongs to one and only one OpenGLContext and can be created
   * using the OpenGLContext::createFramebufferObject() method.
   * To render to a FramebufferObject use the Rendering::setFramebuffer() function.
   *
   * \remarks
   * Before using any method from FramebufferObject make sure that the appropriate
   * OpenGL rendering context is active using FramebufferObject::openglContext()->makeCurrent()
   *
   * \remarks
   * All the renderbuffer attachments must specify the same number of samples.
   *
   * \sa
   * - \ref pagGuideFramebufferObject
   * - \p ARB_framebuffer_object specifications http://www.opengl.org/registry/specs/ARB/framebuffer_object.txt
   * - FBORenderbufferAttachment
   * - FBOColorBufferAttachment
   * - FBODepthBufferAttachment
   * - FBODepthStencilBufferAttachment
   * - FBOStencilBufferAttachment
   * - FBOTexture1DAttachment
   * - FBOTexture2DAttachment
   * - FBOTexture3DAttachment
   * - FBOTextureAttachment
   * - FBOTextureLayerAttachment
   */
  class VLGRAPHICS_EXPORT FramebufferObject: public Object
  {
    VL_INSTRUMENT_CLASS(vl::FramebufferObject, Object)

    friend class OpenGLContext;

  private:
    void operator=( const FramebufferObject& ) {}

    FramebufferObject( const FramebufferObject& other ): 
      Object( other ),
      mOpenGLContext(0), 
      mWidth(0), 
      mHeight(0),
      mHandle( 0 ), 
      mExternallyManaged(false) 
    {
      // setDrawBuffer(draw_buffer);
      // setReadBuffer(read_buffer);
    }

    FramebufferObject():
      mOpenGLContext(NULL), 
      mWidth(0), 
      mHeight(0),
      mHandle( 0 ), 
      mExternallyManaged( false )
    {
      VL_DEBUG_SET_OBJECT_NAME()
      setDrawBuffer(RDB_COLOR_ATTACHMENT0);
      setReadBuffer(RDB_COLOR_ATTACHMENT0);
    }

    FramebufferObject( OpenGLContext* ctx, 
                       int w, 
                       int h,
                       EReadDrawBuffer draw_buffer = RDB_COLOR_ATTACHMENT0,
                       EReadDrawBuffer read_buffer = RDB_COLOR_ATTACHMENT0 ):
      mOpenGLContext(ctx), 
      mWidth(w), 
      mHeight(h),
      mHandle( 0 ), 
      mExternallyManaged( false )
    {
      VL_DEBUG_SET_OBJECT_NAME()
      setDrawBuffer(draw_buffer);
      setReadBuffer(read_buffer);
    }

  public:
    /** Destructor. */
    ~FramebufferObject() { if (openglContext()) deleteFBO(); }

    /** The OpenGLContext bound to a render target. */
    OpenGLContext* openglContext() { return mOpenGLContext; }
    
    /** The OpenGLContext bound to a render target. */
    const OpenGLContext* openglContext() const { return mOpenGLContext; }

    /** The width of a render target. */
    int width() const { return mWidth; }
    
    /** The height of a render target. */
    int height() const { return mHeight; }
    
    /** The width of a render target. */
    void setWidth(int width) { mWidth = width; }
    
    /** The height of a render target. */
    void setHeight(int height) { mHeight = height; }

    /** Activates the FramebufferObject by calling bindFramebuffer() and bindDrawBuffers() */
    void activate(EFramebufferBind target = FBB_FRAMEBUFFER)  { bindFramebuffer(target); }

    /** Binds to the currently active framebuffer object (including the 0 one) the read buffer specified by setReadBuffer(). */
    void bindReadBuffer();

    /** Binds to the currently active framebuffer object (including the 0 one) the draw buffers specified by setDrawBuffers(). */
    void bindDrawBuffers() const;

    /** Returns \p true if the draw buffers bound to this render target are legal for this render target type. */
    bool checkDrawBuffers() const;

    /** Specifies the color buffer to be drawn into. */
    void setDrawBuffer(EReadDrawBuffer draw_buffer)
    {
      mDrawBuffers.clear();
      mDrawBuffers.push_back(draw_buffer);
    }

    /** Specifies a list of color buffers to be drawn into. */
    void setDrawBuffers(EReadDrawBuffer draw_buffer1, EReadDrawBuffer draw_buffer2)
    {
      mDrawBuffers.clear();
      mDrawBuffers.push_back(draw_buffer1);
      mDrawBuffers.push_back(draw_buffer2);
    }

    /** Specifies a list of color buffers to be drawn into. */
    void setDrawBuffers(EReadDrawBuffer draw_buffer1, EReadDrawBuffer draw_buffer2, EReadDrawBuffer draw_buffer3)
    {
      mDrawBuffers.clear();
      mDrawBuffers.push_back(draw_buffer1);
      mDrawBuffers.push_back(draw_buffer2);
      mDrawBuffers.push_back(draw_buffer3);
    }

    /** Specifies a list of color buffers to be drawn into. */
    void setDrawBuffers(EReadDrawBuffer draw_buffer1, EReadDrawBuffer draw_buffer2, EReadDrawBuffer draw_buffer3, EReadDrawBuffer draw_buffer4)
    {
      mDrawBuffers.clear();
      mDrawBuffers.push_back(draw_buffer1);
      mDrawBuffers.push_back(draw_buffer2);
      mDrawBuffers.push_back(draw_buffer3);
      mDrawBuffers.push_back(draw_buffer4);
    }

    /** Specifies a list of color buffers to be drawn into. */
    void setDrawBuffers(const std::vector< EReadDrawBuffer >& draw_buffers) { mDrawBuffers = draw_buffers; }

    /** The color buffers to be drawn into. */
    const std::vector< EReadDrawBuffer >& drawBuffers() { return mDrawBuffers; }

    /** The read-buffer bound when the render target is activated. */
    EReadDrawBuffer readBuffer() const { return mReadBuffer; }
    
    /** The read-buffer bound when the render target is activated. */
    void setReadBuffer(EReadDrawBuffer read_buffer) { mReadBuffer = read_buffer; }

    // ---------- FramebufferObject proper functions ----------

    /**
     * Creates a framebuffer object by calling glGenFramebuffers().
     * \sa http://www.opengl.org/sdk/docs/man3/xhtml/glGenFramebuffers.xml
     */
    void createFBO();

    /**
     * Deletes a framebuffer object by calling glDeleteFramebuffers().
     * \sa http://www.opengl.org/sdk/docs/man3/xhtml/glDeleteFramebuffers.xml
     */
    void deleteFBO();

    /** The handle of the framebuffer object as returned by glGenFramebuffers. */
    void setHandle( GLuint handle ) { mHandle = handle; }

    /** The handle of the framebuffer object as returned by glGenFramebuffers. */
    virtual GLuint handle() const { return mHandle; }

    /**
     * Makes the framebuffer the current rendering target calling glBindFramebuffer( GL_FRAMEBUFFER, FramebufferObject::handle() )
     * and initializes all the previously defined attachment points.
     * 
     * Wen externallyManaged() == true then this function does nothing. This is used when external logic is handling the framebuffer
     * for example for compositing and off-screen rendering (eg Qt 6 QOpenGLWidget).
     * 
     * \sa 
     * - externallyManaged()
     * - http://www.opengl.org/sdk/docs/man3/xhtml/glBindFramebuffer.xml
     */
    virtual void bindFramebuffer( EFramebufferBind target = FBB_FRAMEBUFFER );

    /** Checks the framebuffer status and returns the value of glCheckFramebufferStatus() */
    GLenum checkFramebufferStatus();

    /** Prints a human readable description of the error code as returned by glCheckFramebufferStatus() */
    void printFramebufferError( GLenum status ) const;

    /** Binds a color attachment to a framebuffer object. */
    void addColorAttachment( EAttachmentPoint attach_point, FBOColorBufferAttachment* attachment );

    /** Binds a texture attachment to a framebuffer object. */
    void addTextureAttachment( EAttachmentPoint attach_point, FBOAbstractTextureAttachment* attachment );

    /**
     * Binds a depth attachment to a framebuffer object.
     * The \p attachment parameter must point to either a FBODepthBufferAttachment or FBODepthStencilBufferAttachment.
     */
    void addDepthAttachment( FBOAbstractAttachment* attachment );

    /**
     * Binds a stencil attachment to a framebuffer object.
     * The \p attachment parameter must point to either a FBOStencilBufferAttachment or FBODepthStencilBufferAttachment.
     */
    void addStencilAttachment( FBOAbstractAttachment* attachment );

    /** Binds a depth-stencil attachment to a framebuffer object. */
    void addDepthStencilAttachment( FBOAbstractAttachment* attachment );

    /** Unbinds the given attachments from a framebuffer object. */
    void removeAttachment( FBOAbstractAttachment* attachment );

    /** Unbinds the attachment associated to the given attachment point from a framebuffer object. */
    void removeAttachment( EAttachmentPoint attach_point );

    /** Unbinds all attachments bound to a framebuffer object. */
    void removeAllAttachments();

    /** A map associating which fbo-attachment belongs to which attachment point in a framebuffer object. */
    const std::map< EAttachmentPoint, ref<FBOAbstractAttachment> >& fboAttachments() const { return mFBOAttachments; }

    /** Wen externallyManaged() == true then this function does nothing. This is used when external logic is handling the framebuffer
     * for example for compositing and off-screen rendering (eg Qt 6 QOpenGLWidget). */
    bool externallyManaged() const { return mExternallyManaged; }
    /** Wen externallyManaged() == true then this function does nothing. This is used when external logic is handling the framebuffer
     * for example for compositing and off-screen rendering (eg Qt 6 QOpenGLWidget). */
    void setExternallyManaged( bool em ) { mExternallyManaged = em; }

  public:
    std::vector< EReadDrawBuffer > mDrawBuffers;
    EReadDrawBuffer mReadBuffer;
    OpenGLContext* mOpenGLContext;
    int mWidth;
    int mHeight;

    std::map< EAttachmentPoint, ref<FBOAbstractAttachment> > mFBOAttachments;
    GLuint mHandle;
    bool mExternallyManaged;
  };
}

#endif
