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

  Program:   Visualization Toolkit
  Module:    vtkDualDepthPeelingPass.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   vtkDualDepthPeelingPass
 * @brief   Implements the dual depth peeling algorithm.
 *
 *
 * Dual depth peeling is an augmentatation of the standard depth peeling
 * algorithm that peels two layers (front and back) for each render pass. The
 * technique is described in "Order independent transparency with dual depth
 * peeling" (February 2008) by L. Bavoil, K. Myers.
 *
 * This algorithm has been extended to also peel volumetric data along with
 * translucent geometry. To use this feature, set VolumetricPass to an
 * appropriate RenderPass (usually vtkVolumetricPass).
 *
 * The pass occurs in several stages:
 *
 * 1. Copy the current (opaque geometry) depth buffer into a texture.
 * 2. Initialize the min-max depth buffer from the opaque depth texture and the
 *    translucent geometry.
 * 3. Peel the nearest and farthest fragments:
 * 3a. Blend fragments that match the nearest depth of the min-max depth buffer
 *     into the front buffer.
 * 3b. Write the far depth fragments into a temporary buffer.
 * 3c. Extract the next set of min/max depth values for the next peel.
 * 3d. Blend the temporary far fragment texture (3b) into an accumulation
 *     texture.
 * 3e. Go back to 3a and repeat until the maximum number of peels is met, or
 *     the desired occlusion ratio is satisfied.
 * 4. If the occlusion ratio != 0 (i.e. we hit the maximum number of peels
 *    before finishing), alpha blend the remaining fragments in-between the
 *    near and far accumulation textures.
 * 5. Blend all accumulation buffers over the opaque color buffer to produce the
 *    final image.
 */

#ifndef vtkDualDepthPeelingPass_h
#define vtkDualDepthPeelingPass_h

#include "vtkDepthPeelingPass.h"
#include "vtkRenderingOpenGL2Module.h" // For export macro

#include <array> // For std::array!

class vtkOpenGLFramebufferObject;
class vtkOpenGLQuadHelper;
class vtkOpenGLVertexArrayObject;
class vtkRenderTimerLog;
class vtkShaderProgram;
class vtkTextureObject;

class VTKRENDERINGOPENGL2_EXPORT vtkDualDepthPeelingPass : public vtkDepthPeelingPass
{
public:
  static vtkDualDepthPeelingPass* New();
  vtkTypeMacro(vtkDualDepthPeelingPass, vtkDepthPeelingPass);
  void PrintSelf(ostream& os, vtkIndent indent) override;

  void Render(const vtkRenderState* s) override;
  void ReleaseGraphicsResources(vtkWindow* w) override;

  //@{
  /**
   * Delegate for rendering the volumetric geometry, if needed.
   * It is usually set to a vtkVolumetricPass.
   * Initial value is a NULL pointer.
   */
  vtkGetObjectMacro(VolumetricPass, vtkRenderPass) virtual void SetVolumetricPass(
    vtkRenderPass* volumetricPass);
  //@}

  // vtkOpenGLRenderPass virtuals:
  bool PreReplaceShaderValues(std::string& vertexShader, std::string& geometryShader,
    std::string& fragmentShader, vtkAbstractMapper* mapper, vtkProp* prop) override;
  bool PostReplaceShaderValues(std::string& vertexShader, std::string& geometryShader,
    std::string& fragmentShader, vtkAbstractMapper* mapper, vtkProp* prop) override;
  bool SetShaderParameters(vtkShaderProgram* program, vtkAbstractMapper* mapper, vtkProp* prop,
    vtkOpenGLVertexArrayObject* VAO = nullptr) override;
  vtkMTimeType GetShaderStageMTime() override;

protected:
  bool PostReplaceTranslucentShaderValues(std::string& vertexShader, std::string& geometryShader,
    std::string& fragmentShader, vtkAbstractMapper* mapper, vtkProp* prop);
  bool PreReplaceVolumetricShaderValues(std::string& vertexShader, std::string& geometryShader,
    std::string& fragmentShader, vtkAbstractMapper* mapper, vtkProp* prop);
  bool SetTranslucentShaderParameters(vtkShaderProgram* program, vtkAbstractMapper* mapper,
    vtkProp* prop, vtkOpenGLVertexArrayObject* VAO);
  bool SetVolumetricShaderParameters(vtkShaderProgram* program, vtkAbstractMapper* mapper,
    vtkProp* prop, vtkOpenGLVertexArrayObject* VAO);

  // Name the textures used by this render pass. These are indexes into
  // this->Textures
  enum TextureName
  {
    BackTemp = 0, // RGBA8 back-to-front peeling buffer
    Back,         // RGBA8 back-to-front accumulation buffer
    FrontA,       // RGBA8 front-to-back accumulation buffer
    FrontB,       // RGBA8 front-to-back accumulation buffer
    DepthA,       // RG32F min-max depth buffer
    DepthB,       // RG32F min-max depth buffer
    OpaqueDepth,  // Stores the depth map from the opaque passes

    NumberOfTextures
  };

  // The stages of this multipass render pass:
  enum ShaderStage
  {
    InitializingDepth,
    Peeling,
    AlphaBlending,

    NumberOfPasses,
    Inactive = -1,
  };

  enum PeelType
  {
    TranslucentPeel,
    VolumetricPeel
  };

  vtkDualDepthPeelingPass();
  ~vtkDualDepthPeelingPass() override;

  void SetCurrentStage(ShaderStage stage);
  vtkSetMacro(CurrentPeelType, PeelType);

  /**
   * Release all FBOs and textures.
   */
  void FreeGLObjects();

  /**
   * Render the translucent pass geometry, counting number of render calls.
   */
  void RenderTranslucentPass();

  /**
   * Render any volumetric geometry.
   */
  void RenderVolumetricPass();

  bool IsRenderingVolumes();

  /**
   * Allocate and configure FBOs and textures.
   */
  void Initialize(const vtkRenderState* state);

  //@{
  /**
   * Initialize helpers.
   */
  void InitColorTexture(vtkTextureObject* tex, const vtkRenderState* s);
  void InitDepthTexture(vtkTextureObject* tex, const vtkRenderState* s);
  void InitOpaqueDepthTexture(vtkTextureObject* tex, const vtkRenderState* s);
  void InitFramebuffer(const vtkRenderState* s);
  //@}

  /**
   * Bind and activate draw buffers.
   * @{
   */
  void ActivateDrawBuffer(TextureName id) { this->ActivateDrawBuffers(&id, 1); }
  template <size_t NumTextures>
  void ActivateDrawBuffers(const std::array<TextureName, NumTextures>& a)
  {
    this->ActivateDrawBuffers(a.data(), a.size());
  }
  void ActivateDrawBuffers(const TextureName* ids, size_t numTextures);
  /**@}*/

  /**
   * Fill textures with initial values, bind the framebuffer.
   */
  void Prepare();

  void InitializeOcclusionQuery();
  void CopyOpaqueDepthBuffer();
  void InitializeDepth();

  void PeelVolumesOutsideTranslucentRange();

  bool PeelingDone();

  /**
   * Render the scene to produce the next set of peels.
   */
  void Peel();

  // Depending on whether we're handling volumes or not, we'll initialize the
  // front destination buffer by either clearing it or copying the last peel's
  // output into it.
  void PrepareFrontDestination();
  void ClearFrontDestination();
  void CopyFrontSourceToFrontDestination();

  void InitializeTargetsForTranslucentPass();
  void InitializeTargetsForVolumetricPass();

  void PeelTranslucentGeometry();
  void PeelVolumetricGeometry();

  void BlendBackBuffer();

  void StartTranslucentOcclusionQuery();
  void EndTranslucentOcclusionQuery();

  void StartVolumetricOcclusionQuery();
  void EndVolumetricOcclusionQuery();

  /**
   * Swap the src/dest render targets:
   */
  void SwapFrontBufferSourceDest();
  void SwapDepthBufferSourceDest();

  void Finalize();

  void AlphaBlendRender();

  void BlendFinalImage();
  void DeleteOcclusionQueryIds();

  vtkRenderTimerLog* Timer;
  vtkRenderPass* VolumetricPass;
  const vtkRenderState* RenderState;

  vtkOpenGLQuadHelper* CopyColorHelper;
  vtkOpenGLQuadHelper* CopyDepthHelper;
  vtkOpenGLQuadHelper* BackBlendHelper;
  vtkOpenGLQuadHelper* BlendHelper;

  vtkTextureObject* Textures[NumberOfTextures];

  TextureName FrontSource;      // The current front source buffer
  TextureName FrontDestination; // The current front destination buffer
  TextureName DepthSource;      // The current depth source buffer
  TextureName DepthDestination; // The current depth destination buffer

  ShaderStage CurrentStage;
  PeelType CurrentPeelType;
  vtkTimeStamp CurrentStageTimeStamp;

  bool LastPeelHadVolumes;
  int CurrentPeel;
  unsigned int TranslucentOcclusionQueryId;
  unsigned int TranslucentWrittenPixels;
  unsigned int VolumetricOcclusionQueryId;
  unsigned int VolumetricWrittenPixels;
  unsigned int OcclusionThreshold;

  int TranslucentRenderCount; // Debug info, counts number of geometry passes.
  int VolumetricRenderCount;  // Debug info, counts number of volumetric passes.

  // Cached state:
  bool SaveScissorTestState;
  int CullFaceMode;
  bool CullFaceEnabled;
  bool DepthTestEnabled;

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

#endif // vtkDualDepthPeelingPass_h
