/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkFrameBufferObject2.h

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
/**
 * @class   vtkFrameBufferObject2
 * @brief   Interface to OpenGL framebuffer object.
 *
 * A light and efficient interface to an OpenGL Frame Buffer Object.
 * Use is very simillalry to directly calling OpenGL, but as vtkObject
 * it may safely stored, shared, or passed around. It supports FBO Blit
 * and transfer to Pixel Buffer Object.
 *
 * Typical use case:
 *\code{.cpp}
 * vtkFrameBufferObject2 *fbo = this->Internals->FBO;
 * fbo->SaveCurrentBindings();
 * fbo->Bind(vtkgl::FRAMEBUFFER_EXT);
 * fbo->AddDepthAttachment(vtkgl::DRAW_FRAMEBUFFER_EXT, depthBuffer);
 * fbo->AddColorAttachment(vtkgl::DRAW_FRAMEBUFFER_EXT, 0U, colorTex1);
 * fbo->AddColorAttachment(vtkgl::DRAW_FRAMEBUFFER_EXT, 1U, colorTex2);
 * fbo->AddColorAttachment(vtkgl::DRAW_FRAMEBUFFER_EXT, 2U, colorTex3);
 * fbo->ActivateDrawBuffers(3);
 * vtkCheckFrameBufferStatusMacro(vtkgl::FRAMEBUFFER_EXT);
 *
 * ...
 *
 * fbo->UnBind(vtkgl::FRAMEBUFFER_EXT);
 *\endcode
 *
 * @sa
 * vtkRenderbuffer, vtkPixelBufferObject
*/

#ifndef vtkFrameBufferObject2_h
#define vtkFrameBufferObject2_h

#include "vtkObject.h"
#include "vtkRenderingOpenGLModule.h" // For export macro
#include "vtkSmartPointer.h" // needed for vtkSmartPointer.
#include "vtkWeakPointer.h" // needed for vtkWeakPointer.

/**
 * A variant of vtkErrorMacro that is used to verify framebuffer
 * object completeness. It's provided so that reporting may include
 * the file and line number of the offending code. In release mode
 * the macro does nothing.
 */
#ifdef NDEBUG
# define vtkCheckFrameBufferStatusMacro(mode)
# define vtkStaticCheckFrameBufferStatusMacro(mode)
#else
# define vtkCheckFrameBufferStatusMacroImpl(macro, mode)           \
{                                                                  \
const char *eStr;                                                  \
bool ok = vtkFrameBufferObject2::GetFrameBufferStatus(mode, eStr); \
if (!ok)                                                           \
{                                                                \
  macro(                                                           \
    << "OpenGL ERROR. The FBO is incomplete : " << eStr);          \
}                                                                \
 }
# define vtkCheckFrameBufferStatusMacro(mode) \
    vtkCheckFrameBufferStatusMacroImpl(vtkErrorMacro, mode)
# define vtkStaticCheckFrameBufferStatusMacro(mode) \
    vtkCheckFrameBufferStatusMacroImpl(vtkGenericWarningMacro, mode)
#endif

class vtkRenderWindow;
class vtkTextureObject;
class vtkRenderbuffer;
class vtkPixelBufferObject;
class vtkOpenGLExtensionManager;
class vtkOpenGLRenderWindow;

class VTKRENDERINGOPENGL_EXPORT vtkFrameBufferObject2 : public vtkObject
{
public:
  static vtkFrameBufferObject2* New();
  vtkTypeMacro(vtkFrameBufferObject2, vtkObject);
  void PrintSelf(ostream& os, vtkIndent indent) override;

  //@{
  /**
   * Get/Set the context. Context must be a vtkOpenGLRenderWindow.
   * This does not increase the reference count of the
   * context to avoid reference loops.
   * SetContext() may raise an error is the OpenGL context does not support the
   * required OpenGL extensions.
   */
  void SetContext(vtkRenderWindow *context);
  vtkRenderWindow *GetContext();
  //@}

  /**
   * Returns if the context supports the required extensions.
   * Extension will be loaded when the conetxt is set.
   */
  static bool IsSupported(vtkRenderWindow *renWin);

  /**
   * Bind FBO to FRAMEBUFFER,  DRAW_FRAMEBUFFER or READ_FRAMEBUFFER
   * The current binding is not saved, nor restored. (see glBindFramebuffer)
   * This method can be used to prepare for FBO Blit or buffer ping-pong.
   * Low level api.
   */
  void Bind(unsigned int mode);

  /**
   * Bind saved FBO (see SaveCurrentBindings) for DRAW or READ (see glBindFramebuffer)
   * If no bindings were saved bind to default FBO.
   * Low level api.
   */
  void UnBind(unsigned int mode);

  /**
   * Store the current framebuffer bindings. If this method
   * is called then UnBind will restore the saved value according
   * to its mode (DRAW_FRAMEBUFFER,READ_FRAMEBUFFER,FRAMEBUFFER)
   * Restoration occurs in UnBind.
   * Low level api
   */
  void SaveCurrentBindings();

  //@{
  /**
   * Store the current draw and read buffers. When restored
   * only the buffers matching mode are modified.
   * DRAW_FRAMEBUFFER -> glDrawBuffer
   * READ_FRAMEBUFFER -> glReadBuffer
   * FRAMEBUFFER -> both
   */
  void SaveCurrentBuffers();
  void RestorePreviousBuffers(unsigned int mode);
  //@}

  /**
   * Directly assign/remove a texture to color attachments.
   */
  void AddColorAttachment(
        unsigned int mode,
        unsigned int attId,
        vtkTextureObject* tex);

  void AddTexColorAttachment(
        unsigned int mode,
        unsigned int attId,
        unsigned int handle);

  void RemoveTexColorAttachments(unsigned int mode, unsigned int num);
  void RemoveTexColorAttachment(unsigned int mode, unsigned int attId)
    { this->AddTexColorAttachment(mode, attId, 0U); }

  /**
   * Directly assign/remove a renderbuffer to color attachments.
   */
  void AddColorAttachment(
        unsigned int mode,
        unsigned int attId,
        vtkRenderbuffer* tex);

  void AddRenColorAttachment(
        unsigned int mode,
        unsigned int attId,
        unsigned int handle);

  void RemoveRenColorAttachments(unsigned int mode, unsigned int num);
  void RemoveRenColorAttachment(unsigned int mode, unsigned int attId)
    { this->AddRenColorAttachment(mode, attId, 0U); }

  //@{
  /**
   * Directly assign/remove a texture/renderbuffer to depth attachments.
   */
  void AddDepthAttachment(unsigned int mode, vtkTextureObject* tex);
  void AddTexDepthAttachment(unsigned int mode, unsigned int handle);
  void RemoveTexDepthAttachment(unsigned int mode)
    { this->AddTexDepthAttachment(mode, 0U); }
  //@}

  //@{
  /**
   * Directly assign/remove a renderbuffer to depth attachments.
   */
  void AddDepthAttachment(unsigned int mode, vtkRenderbuffer* tex);
  void AddRenDepthAttachment(unsigned int mode, unsigned int handle);
  void RemoveRenDepthAttachment(unsigned int mode)
    { this->AddRenDepthAttachment(mode, 0U); }
  //@}

  //@{
  /**
   * Select a single specific draw or read buffer (zero based)
   */
  void ActivateDrawBuffer(unsigned int id);
  void ActivateReadBuffer(unsigned int id);
  void DeactivateReadBuffer();
  //@}

  //@{
  /**
   * Select n consecutive write attachments.
   * Low level api.
   */
  void ActivateDrawBuffers(unsigned int n);
  void ActivateDrawBuffers(unsigned int *ids, int n);
  void DeactivateDrawBuffers();
  //@}

  /**
   * Set up ortho viewport with scissor, lighting, blend, and depth
   * disabled. The method affects the current bound FBO. The method is
   * static so that it may be used on the default FBO without an instance.
   * Low level api.
   */
  static
  void InitializeViewport(int width, int height);

  /**
   * Validate the current FBO configuration (attachments, formats, etc)
   * prints detected errors to vtkErrorMacro.
   * Low level api.
   */
  int CheckFrameBufferStatus(unsigned int mode);

  /**
   * Validate the current FBO configuration (attachments, formats, etc)
   * return false if the FBO is incomplete. Assigns description a literal
   * containing a description of the status.
   * Low level api.
   */
  static
  bool GetFrameBufferStatus(
        unsigned int mode,
        const char *&desc);

  /**
   * Copy from the currently bound READ FBO to the currently
   * bound DRAW FBO. The method is static so that one doesn't
   * need to ccreate an instance when transferring between attachments
   * in the default FBO.
   */
  static
  int Blit(
        int srcExt[4],
        int destExt[4],
        unsigned int bits,
        unsigned int mapping);

  /**
   * Download data from the read color attachment of the currently
   * bound FBO into the retruned PBO. The PBO must be free'd when
   * you are finished with it. The number of components in the
   * PBO is the same as in the name of the specific download function.
   * When downloading a single color channel, the channel must be
   * identified by index, 1->red, 2->green, 3-> blue.
   */
  vtkPixelBufferObject *DownloadColor1(
        int extent[4],
        int vtkType,
        int channel);

  vtkPixelBufferObject *DownloadColor3(
        int extent[4],
        int vtkType);

  vtkPixelBufferObject *DownloadColor4(
        int extent[4],
        int vtkType);

  /**
   * Download data from the depth attachment of the currently
   * bound FBO. The returned PBO must be Delete'd by the caller.
   * The retruned PBO has one component.
   */
  vtkPixelBufferObject *DownloadDepth(
        int extent[4],
        int vtkType);

  /**
   * Download data from the read buffer of the current FBO. These
   * are low level meothds. In the static variant a PBO must be
   * passed in since we don't have access to a context. The static
   * method is provided so that one may download from the default
   * FBO.
   */
  vtkPixelBufferObject *Download(
        int extent[4],
        int vtkType,
        int nComps,
        int oglType,
        int oglFormat);

  static
  void Download(
        int extent[4],
        int vtkType,
        int nComps,
        int oglType,
        int oglFormat,
        vtkPixelBufferObject *pbo);

protected:
  /**
   * Load all necessary extensions.
   */
  static
  bool LoadRequiredExtensions(vtkRenderWindow *renWin);

  // gen buffer (occurs when context is set)
  void CreateFBO();

  // delete buffer (occurs during destruction or context swicth)
  void DestroyFBO();


  /**
   * Given a vtk type get a compatible open gl type.
   */
  int GetOpenGLType(int vtkType);

  vtkFrameBufferObject2();
  ~vtkFrameBufferObject2() override;

  vtkWeakPointer<vtkRenderWindow> Context;

  unsigned int FBOIndex;
  unsigned int PreviousDrawFBO;
  unsigned int PreviousReadFBO;
  unsigned int DepthBuffer;
  unsigned int PreviousDrawBuffer;
  unsigned int PreviousReadBuffer;

private:
  vtkFrameBufferObject2(const vtkFrameBufferObject2&) = delete;
  void operator=(const vtkFrameBufferObject2&) = delete;

  friend class vtkRenderbuffer; // needs access to LoadRequiredExtentsions

};

#endif
