// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
/**
 * @class   vtkOpenGLFramebufferObject
 * @brief   Internal class which encapsulates OpenGL FramebufferObject
 *
 * Before delving into this class it is best to have some background
 * in some OpenGL terms. OpenGL has a notion of a currently
 * bound Framebuffers for drawing and reading. It can be the default
 * framebuffer such as created with a standard window/context or
 * it can be a user created Framebuffer objects. When draw and read
 * commands are invoked, they apply to the current draw and/or read
 * framebuffers.
 *
 * A framebuffer consists of color buffers and an optional depth buffer.
 * The FramebufferObject does not hold the memory for these buffers, it
 * just keeps track of what buffers are attached to it. The buffers themselves
 * hold the storage for the pixels/depths.
 *
 * In the context of this discussion a buffer can be either a
 * vtkTextureObject (both 2D or a slice of a 3D texture) or
 * a vtkRenderbuffer. In some cases a renderbuffer may be faster
 * or more lightweight but you cannot pass a renderbuffer into
 * a shader for sampling in a later pass like you can a texture.
 *
 * You attach these buffers to the Framebuffer using methods
 * such as AddColorAttachment or AddDepthAttachment
 * In normal usage a buffer is Attached to a FramebufferObject
 * and then some or all of the attached buffers are activated for drawing
 * or reading.
 *
 * When you have a framebuffer bound along with some buffers attached to it
 * you can then activate specific buffers for drawing or reading. So you
 * have draw and read framebuffer objects (bindings) and then for the currently
 * bound FramebufferObjects you have active draw and read buffers.
 *
 * A single FramebufferObject can be bound to both Draw and Read. You
 * cannot assign and activate a TextureObject for drawing on the FO and
 * at the same time pass it in as a Texture to the shader program. That
 * type of operation is very common and must be done in two steps.
 *  - Render to the FO with the Texture attached as an active buffer
 *  - deactivate the texture obj and then render with the texture obj
 *    as a texture passed into the shader
 *
 * Typical use cases:
 * The simplest example
 *\code{.cpp}
 * fbo->SetContext(renWin);
 * fbo->SaveCurrentBindingsAndBuffers();
 * fbo->PopulateFramebuffer(width, height);
 *
 * ...
 *
 * fbo->RestorePreviousBindingsAndBuffers();
 *\endcode
 *
 * If you wish to use a texture you created
 *
 *\code{.cpp}
 * fbo->SetContext(renWin);
 * fbo->SaveCurrentBindingsAndBuffers();
 * fbo->Bind();
 * fbo->AddColorAttachment(0, vtkTextureObj);
 * fbo->AddDepthAttachment(); // auto create depth buffer
 * fbo->ActivateBuffer(0);
 *
 * ...
 *
 * fbo->RestorePreviousBindingsAndBuffers();
 *\endcode
 *
 * If you will be using a FO repeatedly then it is best to create it
 * attach the buffers and then use as needed for example
 *
 * Typical use case:
 *\code{.cpp}
 * // setup the FBO once
 * fbo->SetContext(renWin);
 * fbo->SaveCurrentBindingsAndBuffers();
 * fbo->AddColorAttachment(0, vtkTextureObj);
 * fbo->AddDepthAttachment(); // auto create depth buffer
 * fbo->RestorePreviousBindingsAndBuffers();
 *
 * // use it many times
 * fbo->SaveCurrentBindingsAndBuffers();
 * fbo->Bind();
 * fbo->ActivateBuffer(0);
 * ... // render here etc
 * fbo->RestorePreviousBindingsAndBuffers();
 *\endcode
 *
 * If you with to only bind the framebuffer for drawing or reading there
 * are mode specific versions of some methods that only apply to the
 * mode specified Draw/Read/Both. The mode argument uses OpenGL constants
 * so this class provides convenience methods to return them named
 * GetDrawMode() GetReadMode() and GetBothMode() so that your code
 * does not need to be polluted with OpenGL headers/constants.
 *
 * This class replaces both vtkFrameBufferObject and vtkFrameBufferObject2
 * and contains methods from both of them. Most methods from FO2 should
 * work with this class. Just rename FBO2 to FBO and make sure to Save and
 * Restore the bindings and buffers.
 * If you have been using the old FO class, which had comments
 * in the header saying not to use it. Then you are in for a bit more of
 * a conversion but generally it should still be easy. Use the code
 * samples above (or any of the classes in OpenGL2 that currently use FBOs)
 * to guide you. They have all been converted to this class. Where previously
 * a DepthBuffer was automatically created for you, you now need to do it
 * explicitly using AddDepthAttachment().
 *
 * Note the capitalization of FramebufferObject
 *
 * @sa
 * vtkTextureObject, vtkRenderbufferObject
 */

#ifndef vtkOpenGLFramebufferObject_h
#define vtkOpenGLFramebufferObject_h

/* Dec 2018 this class has been cleaned up such that
 * AddColorAttachment and AddDepthAttachment no longer
 * take a mode argument. The mode is determined by how
 * the framebuffer is bound. If you are using these methods
 * and need to support both the old and new signatures you
 * can check for the following define in your code.
 */
#define VTK_UPDATED_FRAMEBUFFER

/**
 * 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 = vtkOpenGLFramebufferObject::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

#include "vtkFrameBufferObjectBase.h"
#include "vtkRenderingOpenGL2Module.h" // For export macro
#include <map>                         // for the maps
#include <vector>                      // for the lists of logical buffers.

VTK_ABI_NAMESPACE_BEGIN
class vtkFOInfo;
class vtkGenericOpenGLResourceFreeCallback;
class vtkOpenGLRenderWindow;
class vtkOpenGLVertexArrayObject;
class vtkPixelBufferObject;
class vtkRenderWindow;
class vtkRenderbuffer;
class vtkShaderProgram;
class vtkTextureObject;
class vtkWindow;

class VTKRENDERINGOPENGL2_EXPORT vtkOpenGLFramebufferObject : public vtkFrameBufferObjectBase
{
public:
  static vtkOpenGLFramebufferObject* New();
  vtkTypeMacro(vtkOpenGLFramebufferObject, vtkFrameBufferObjectBase);
  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 if the OpenGL context does not support the
   * required OpenGL extensions.
   */
  void SetContext(vtkRenderWindow* context);
  vtkOpenGLRenderWindow* GetContext();
  ///@}

  /**
   * Make the draw frame buffer active.
   */
  void Bind();
  void Bind(unsigned int mode);

  /**
   * Unbind this buffer
   */
  void UnBind();
  void UnBind(unsigned int mode);

  ///@{
  /**
   * Store/Restore the current framebuffer bindings and buffers.
   */
  void SaveCurrentBindingsAndBuffers();
  void SaveCurrentBindingsAndBuffers(unsigned int mode);
  void RestorePreviousBindingsAndBuffers();
  void RestorePreviousBindingsAndBuffers(unsigned int mode);
  ///@}

  ///@{
  /**
   * User must take care that width/height match the dimensions of
   * the user defined texture attachments.
   * This method makes the "active buffers" the buffers that will get drawn
   * into by subsequent drawing calls.
   * Note that this does not clear the render buffers i.e. no glClear() calls
   * are made by either of these methods. It's up to the caller to clear the
   * buffers if needed.
   */
  bool Start(int width, int height);
  bool StartNonOrtho(int width, int height);
  ///@}

  /**
   * Set up ortho viewport with scissor, lighting, blend, and depth
   * disabled. The method affects the current bound FBO.
   */
  void InitializeViewport(int width, int height);

  ///@{
  // activate deactivate draw/read buffers (color buffers)
  void ActivateDrawBuffers(unsigned int n);
  void ActivateDrawBuffers(unsigned int* ids, int n);
  void ActivateDrawBuffer(unsigned int id);
  void ActivateReadBuffer(unsigned int id);
  void ActivateBuffer(unsigned int id)
  {
    this->ActivateDrawBuffer(id);
    this->ActivateReadBuffer(id);
  }
  void DeactivateDrawBuffers();
  void DeactivateReadBuffer();
  ///@}

  vtkGetMacro(ActiveReadBuffer, unsigned int);
  unsigned int GetActiveDrawBuffer(unsigned int id);

  /**
   * Renders a quad at the given location with pixel coordinates. This method
   * is provided as a convenience, since we often render quads in a FBO.
   * \pre positive_minX: minX>=0
   * \pre increasing_x: minX<=maxX
   * \pre valid_maxX: maxX<LastSize[0]
   * \pre positive_minY: minY>=0
   * \pre increasing_y: minY<=maxY
   * \pre valid_maxY: maxY<LastSize[1]
   */
  void RenderQuad(int minX, int maxX, int minY, int maxY, vtkShaderProgram* program,
    vtkOpenGLVertexArrayObject* vao);

  ///@{
  /**
   * Directly assign/remove a texture to color attachments.
   */
  void AddColorAttachment(unsigned int attId, vtkTextureObject* tex, unsigned int zslice = 0,
    unsigned int format = 0, unsigned int mipmapLevel = 0);
  void AddColorAttachment(unsigned int attId, vtkRenderbuffer* tex);
  void RemoveColorAttachment(unsigned int index);
  void RemoveColorAttachments(unsigned int num);
  ///@}

  /**
   * Return the texture object bound to the passed attachment number.
   * In the case that a renderbuffer is used, this will return nullptr.
   */
  vtkTextureObject* GetColorAttachmentAsTextureObject(unsigned int num);

  /**
   * Return the number of color attachments for the given mode
   */
  int GetNumberOfColorAttachments();

  ///@{
  /**
   * Directly assign/remove a texture/renderbuffer to depth attachments.
   */
  void AddDepthAttachment();
  void AddDepthAttachment(vtkTextureObject* tex);
  void AddDepthAttachment(vtkRenderbuffer* tex);
  void RemoveDepthAttachment();
  vtkTextureObject* GetDepthAttachmentAsTextureObject();
  ///@}

  ///@{
  /**
   * Convenience method to populate a framebuffer with
   * attachments created as well. Returns true if a
   * complete valid Framebuffer was created
   */
  bool PopulateFramebuffer(int width, int height);
  bool PopulateFramebuffer(int width, int height, bool useTextures, int numberOfColorAttachments,
    int colorDataType, bool wantDepthAttachment, int depthBitplanes, int multisamples,
    bool wantStencilAttachment = false);
  ///@}

  /**
   * Returns the maximum number of targets that can be rendered to at one time.
   * This limits the active targets set by SetActiveTargets().
   * The return value is valid only if GetContext is non-null.
   */
  unsigned int GetMaximumNumberOfActiveTargets();

  /**
   * Returns the maximum number of render targets available. This limits the
   * available attachment points for SetColorAttachment().
   * The return value is valid only if GetContext is non-null.
   */
  unsigned int GetMaximumNumberOfRenderTargets();

  ///@{
  /**
   * Dimensions in pixels of the framebuffer.
   */
  int* GetLastSize() override
  {
    vtkDebugMacro(<< this->GetClassName() << " (" << this << "): returning LastSize pointer "
                  << this->LastSize);
    return this->LastSize;
  }
  void GetLastSize(int& _arg1, int& _arg2) override
  {
    _arg1 = this->LastSize[0];
    _arg2 = this->LastSize[1];
    vtkDebugMacro(<< this->GetClassName() << " (" << this << "): returning LastSize (" << _arg1
                  << "," << _arg2 << ")");
  }
  void GetLastSize(int _arg[2]) override { this->GetLastSize(_arg[0], _arg[1]); }
  ///@}

  /**
   * Returns if the context supports the required extensions.
   * Extension will be loaded when the context is set.
   */
  static bool IsSupported(vtkOpenGLRenderWindow*) { return true; }

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

  /**
   * Deactivate and UnBind
   */
  virtual void ReleaseGraphicsResources(vtkWindow* win);

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

  vtkGetMacro(FBOIndex, unsigned int);

  /**
   * 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(
    const int srcExt[4], const int destExt[4], unsigned int bits, unsigned int mapping);

  /**
   * Download data from the read color attachment of the currently
   * bound FBO into the returned 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 returned 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 methods. 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);

  // returns the mode values for draw/read/both
  // Can be used in cases where you do not
  // want to have OpenGL code mixed in.
  static unsigned int GetDrawMode();
  static unsigned int GetReadMode();
  static unsigned int GetBothMode();

  /**
   * Resize all FO attachments
   */
  void Resize(int width, int height);

  int GetMultiSamples();

protected:
  /**
   * Attach a specific buffer
   */
  void AttachColorBuffer(unsigned int index);
  void AttachDepthBuffer();

  /**
   * Load all necessary extensions.
   */
  static bool LoadRequiredExtensions(vtkOpenGLRenderWindow*) { return true; }

  vtkGenericOpenGLResourceFreeCallback* ResourceCallback;

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

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

  // detach and delete our reference(s)
  void DestroyDepthBuffer(vtkWindow* win);
  void DestroyColorBuffers(vtkWindow* win);

  // glDrawBuffers
  void ActivateBuffers();

  // examine attachments to see if they have the same size
  void UpdateSize();

  /**
   * Display all the attachments of the current framebuffer object.
   */
  void DisplayFrameBufferAttachments();

  /**
   * Display a given attachment for the current framebuffer object.
   */
  void DisplayFrameBufferAttachment(unsigned int uattachment);

  /**
   * Display the draw buffers.
   */
  void DisplayDrawBuffers();

  /**
   * Display the read buffer.
   */
  void DisplayReadBuffer();

  /**
   * Display any buffer (convert value into string).
   */
  void DisplayBuffer(int value);

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

  vtkOpenGLFramebufferObject();
  ~vtkOpenGLFramebufferObject() override;

  vtkOpenGLRenderWindow* Context;

  unsigned int FBOIndex;

  bool DrawBindingSaved;
  bool ReadBindingSaved;
  bool DrawBufferSaved;
  bool ReadBufferSaved;

  int LastSize[2];
  std::vector<unsigned int> ActiveBuffers;
  unsigned int ActiveReadBuffer;

  vtkFOInfo* DepthBuffer;
  std::map<unsigned int, vtkFOInfo*> ColorBuffers;

private:
  vtkOpenGLFramebufferObject(const vtkOpenGLFramebufferObject&) = delete;
  void operator=(const vtkOpenGLFramebufferObject&) = delete;
};

VTK_ABI_NAMESPACE_END
#endif
