// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
/**
 * @class   vtkLineIntegralConvolution2D
 * @brief   GPU-based implementation of Line
 *  Integral Convolution (LIC)
 *
 *
 *  This class resorts to GLSL to implement GPU-based Line Integral Convolution
 *  (LIC) for visualizing a 2D vector field that may be obtained by projecting
 *  an original 3D vector field onto a surface (such that the resulting 2D
 *  vector at each grid point on the surface is tangential to the local normal,
 *  as done in vtkSurfaceLICPainter).
 *
 *  As an image-based technique, 2D LIC works by (1) integrating a bidirectional
 *  streamline from the center of each pixel (of the LIC output image), (2)
 *  locating the pixels along / hit by this streamline as the correlated pixels
 *  of the starting pixel (seed point / pixel), (3) indexing a (usually white)
 *  noise texture (another input to LIC, in addition to the 2D vector field,
 *  usually with the same size as that of the 2D vector field) to determine the
 *  values (colors) of these pixels (the starting and the correlated pixels),
 *  typically through bi-linear interpolation, and (4) performing convolution
 *  (weighted averaging) on these values, by adopting a low-pass filter (such
 *  as box, ramp, and Hanning kernels), to obtain the result value (color) that
 *  is then assigned to the seed pixel.
 *
 *  The GLSL-based GPU implementation herein maps the aforementioned pipeline to
 *  fragment shaders and a box kernel is employed. Both the white noise and the
 *  vector field are provided to the GPU as texture objects (supported by the
 *  multi-texturing capability). In addition, there are four texture objects
 *  (color buffers) allocated to constitute two pairs that work in a ping-pong
 *  fashion, with one as the read buffers and the other as the write / render
 *  targets. Maintained by a frame buffer object (GL_EXT_framebuffer_object),
 *  each pair employs one buffer to store the current (dynamically updated)
 *  position (by means of the texture coordinate that keeps being warped by the
 *  underlying vector) of the (virtual) particle initially released from each
 *  fragment while using the bother buffer to store the current (dynamically
 *  updated too) accumulated texture value that each seed fragment (before the
 *  'mesh' is warped) collects. Given NumberOfSteps integration steps in each
 *  direction, there are a total of (2 * NumberOfSteps + 1) fragments (including
 *  the seed fragment) are convolved and each contributes 1 / (2 * NumberOfSteps
 *  + 1) of the associated texture value to fulfill the box filter.
 *
 *  One pass of LIC (basic LIC) tends to produce low-contrast / blurred images and
 *  vtkLineIntegralConvolution2D provides an option for creating enhanced LIC
 *  images. Enhanced LIC improves image quality by increasing inter-streamline
 *  contrast while suppressing artifacts. It performs two passes of LIC, with a
 *  3x3 Laplacian high-pass filter in between that processes the output of pass
 *  #1 LIC and forwards the result as the input 'noise' to pass #2 LIC.
 *
 *  vtkLineIntegralConvolution2D applies masking to zero-vector fragments so
 *  that un-filtered white noise areas are made totally transparent by class
 *  vtkSurfaceLICPainter to show the underlying geometry surface.
 *
 *  The convolution process tends to decrease both contrast and dynamic range,
 *  sometimes leading to dull dark images. In order to counteract this, optional
 *  contrast ehnancement stages have been added. These increase the dynamic range and
 *  contrast and sharpen streaking patterns that emerge from the LIC process.
 *
 *  Under some circumstances, typically depending on the contrast and dynamic
 *  range and graininess of the noise texture, jagged or pixelated patterns emerge
 *  in the LIC. These can be reduced by enabling the optional anti-aliasing pass.
 *
 *  The internal pipeline is as follows, with optional stages denoted by ()
 *  nested optional stages depend on their parent stage.
 *  <pre>
 *   noise texture
 *           |
 *           [ LIC ((CE) HPF LIC) (AA) (CE) ]
 *           |                              |
 *  vector field                       LIC'd image
 * </pre>
 *  where LIC is the LIC stage, HPF is the high-pass filter stage, CE is the
 *  contrast ehnacement stage, and AA is the antialias stage.
 *
 * @sa
 *  vtkImageDataLIC2D vtkStructuredGridLIC2D
 */

#ifndef vtkLineIntegralConvolution2D_h
#define vtkLineIntegralConvolution2D_h

#include "vtkObject.h"
#include "vtkRenderingLICOpenGL2Module.h" // for export macro
#include "vtkWeakPointer.h"               // for ren context
#include <deque>                          // for deque

VTK_ABI_NAMESPACE_BEGIN
class vtkOpenGLFramebufferObject;
class vtkOpenGLHelper;
class vtkOpenGLRenderWindow;
class vtkPainterCommunicator;
class vtkPixelExtent;
class vtkRenderWindow;
class vtkShaderProgram;
class vtkTextureObject;

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

  /**
   * Returns if the context supports the required extensions.
   */
  static bool IsSupported(vtkRenderWindow* renWin);

  ///@{
  /**
   * Set/Get the rendering context. A reference is not explicitly held,
   * thus reference to the context must be held externally.
   */
  void SetContext(vtkOpenGLRenderWindow* context);
  vtkOpenGLRenderWindow* GetContext();
  ///@}

  ///@{
  /**
   * EnhancedLIC mean compute the LIC twice with the second pass using
   * the edge-enhanced result of the first pass as a noise texture. Edge
   * enhancedment is made by a simple Laplace convolution.
   */
  vtkSetClampMacro(EnhancedLIC, int, 0, 1);
  vtkGetMacro(EnhancedLIC, int);
  vtkBooleanMacro(EnhancedLIC, int);
  ///@}

  ///@{
  /**
   * Enable/Disable contrast and dynamic range correction stages. Stage 1 is applied
   * on the input to the high-pass filter when the high-pass filter is enabled and
   * skipped otherwise. Stage 2, when enabled is the final stage in the internal
   * pipeline. Both stages are implemented by a histogram stretching of the gray scale
   * colors in the LIC'd image as follows:

   * c = (c-m)/(M-m)

   * where, c is the fragment color, m is the color value to map to 0, M is the
   * color value to map to 1. The default values of m and M are the min and max
   * over all fragments.

   * This increase the dynamic range and contrast in the LIC'd image, both of which
   * are naturally attenuated by the LI convolution process.

   * ENHANCE_CONTRAST_OFF  -- don't enhance contrast
   * ENHANCE_CONTRAST_ON   -- enhance high-pass input and final stage output

   * This feature is disabled by default.
   */
  enum
  {
    ENHANCE_CONTRAST_OFF = 0,
    ENHANCE_CONTRAST_ON = 1
  };
  vtkSetClampMacro(EnhanceContrast, int, 0, 2);
  vtkGetMacro(EnhanceContrast, int);
  vtkBooleanMacro(EnhanceContrast, int);
  ///@}

  ///@{
  /**
   * This feature is used to fine tune the contrast enhancement. Values are provided
   * indicating the fraction of the range to adjust m and M by during contrast enahncement
   * histogram stretching.  M and m are the intensity/lightness values that map to 1 and 0.
   * (see EnhanceContrast for an explanation of the mapping procedure). m and M are computed
   * using the factors as follows:

   * m = min(C) - mFactor * (max(C) - min(C))
   * M = max(C) - MFactor * (max(C) - min(C))

   * the default values for mFactor and MFactor are 0 which result in
   * m = min(C), M = max(C), where C is all of the colors in the image. Adjusting
   * mFactor and MFactor above zero provide a means to control the saturation of
   * normalization. These settings only affect the final normalization, the
   * normalization that occurs on the input to the high-pass filter always uses
   * the min and max.
   */
  vtkSetClampMacro(LowContrastEnhancementFactor, double, 0.0, 1.0);
  vtkGetMacro(LowContrastEnhancementFactor, double);
  vtkSetClampMacro(HighContrastEnhancementFactor, double, 0.0, 1.0);
  vtkGetMacro(HighContrastEnhancementFactor, double);
  ///@}

  ///@{
  /**
   * Enable/Disable the anti-aliasing pass. This optional pass (disabled by
   * default) can be enabled to reduce jagged patterns in the final LIC image.
   * Values greater than 0 control the number of iterations, one is typically
   * sufficient.
   */
  vtkSetClampMacro(AntiAlias, int, 0, VTK_INT_MAX);
  vtkGetMacro(AntiAlias, int);
  vtkBooleanMacro(AntiAlias, int);
  ///@}

  ///@{
  /**
   * Number of streamline integration steps (initial value is 1).
   * In term of visual quality, the greater (within some range) the better.
   */
  vtkSetClampMacro(NumberOfSteps, int, 0, VTK_INT_MAX);
  vtkGetMacro(NumberOfSteps, int);
  ///@}

  ///@{
  /**
   * Get/Set the streamline integration step size (0.01 by default). This is
   * the length of each step in normalized image space i.e. in range [0, FLOAT_MAX].
   * In term of visual quality, the smaller the better. The type for the
   * interface is double as VTK interface is, but GPU only supports float.
   * Thus it will be converted to float in the execution of the algorithm.
   */
  vtkSetClampMacro(StepSize, double, 0.0, VTK_FLOAT_MAX);
  vtkGetMacro(StepSize, double);
  ///@}

  ///@{
  /**
   * If VectorField has >= 3 components, we must choose which 2 components
   * form the (X, Y) components for the vector field. Must be in the range
   * [0, 3].
   */
  void SetComponentIds(int c0, int c1);
  void SetComponentIds(int c[2]) { this->SetComponentIds(c[0], c[1]); }
  vtkGetVector2Macro(ComponentIds, int);
  ///@}

  ///@{
  /**
   * Set the max noise value for use during LIC integration normalization.
   * The integration normalization factor is the max noise value times the
   * number of steps taken. The default value is 1.
   */
  vtkSetClampMacro(MaxNoiseValue, double, 0.0, 1.0);
  vtkGetMacro(MaxNoiseValue, double);
  ///@}

  ///@{
  /**
   * This class performs LIC in the normalized image space. Hence, by default
   * it transforms the input vectors to the normalized image space (using the
   * GridSpacings and input vector field dimensions). Set this to 0 to disable
   * transformation if the vectors are already transformed.
   */
  void SetTransformVectors(int val);
  vtkGetMacro(TransformVectors, int);
  ///@}

  /**
   * Set/Get the spacing in each dimension of the plane on which the vector
   * field is defined. This class performs LIC in the normalized image space
   * and hence generally it needs to transform the input vector field (given
   * in physical space) to the normalized image space. The Spacing is needed
   * to determine the transform. Default is (1.0, 1.0). It is possible to
   * disable vector transformation by setting TransformVectors to 0.
   * vtkSetVector2Macro(GridSpacings, double);
   * vtkGetVector2Macro(GridSpacings, double);
   */

  ///@{
  /**
   * Normalize vectors during integration. When set(the default) the input vector field
   * is normalized during integration, and each integration occurs over the same arclength.
   * When not set each integration occurs over an arc length proportional to the field
   * magnitude as is customary in traditional numerical methods. See, "Imaging Vector
   * Fields Using Line Integral Convolution" for an axample where normalization is used.
   * See, "Image Space Based Visualization of Unsteady Flow on Surfaces" for an example
   * of where no normalization is used.
   */
  void SetNormalizeVectors(int val);
  vtkGetMacro(NormalizeVectors, int);
  ///@}

  ///@{
  /**
   * The MaskThreshold controls blanking of the LIC texture. For fragments with
   * |V|<threshold the LIC fragment is not rendered. The default value is 0.0.

   * For surface LIC MaskThreshold units are in the original vector space. For image LIC
   * be aware that while the vector field is transformed to image space while the mask
   * threshold is not. Therefore the mask threshold must be specified in image space
   * units.
   */
  vtkSetClampMacro(MaskThreshold, double, -1.0, VTK_FLOAT_MAX);
  vtkGetMacro(MaskThreshold, double);
  ///@}

  /**
   * Compute the lic on the entire vector field texture.
   */
  vtkTextureObject* Execute(vtkTextureObject* vectorTex, vtkTextureObject* noiseTex);

  /**
   * Compute the lic on the indicated subset of the vector field
   * texture.
   */
  vtkTextureObject* Execute(
    const int extent[4], vtkTextureObject* vectorTex, vtkTextureObject* noiseTex);

  /**
   * Compute LIC over the desired subset of the input texture. The
   * result is copied into the desired subset of the provided output
   * texture.

   * inputTexExtent  : screen space extent of the input texture
   * vectorExtent    : part of the inpute extent that has valid vectors
   * licExtent       : part of the inpute extent to compute on
   * outputTexExtent : screen space extent of the output texture
   * outputExtent    : part of the output texture to store the result
   */
  vtkTextureObject* Execute(const vtkPixelExtent& inputTexExtent,
    const std::deque<vtkPixelExtent>& vectorExtent, const std::deque<vtkPixelExtent>& licExtent,
    vtkTextureObject* vectorTex, vtkTextureObject* maskVectorTex, vtkTextureObject* noiseTex);

  /**
   * Convenience functions to ensure that the input textures are
   * configured correctly.
   */
  static void SetVectorTexParameters(vtkTextureObject* vectors);

  static void SetNoiseTexParameters(vtkTextureObject* noise);

  /**
   * Set the communicator to use during parallel operation
   * The communicator will not be duplicated or reference
   * counted for performance reasons thus caller should
   * hold/manage reference to the communicator during use
   * of the LIC object.
   */
  virtual void SetCommunicator(vtkPainterCommunicator*) {}
  virtual vtkPainterCommunicator* GetCommunicator();

  /**
   * For parallel operation, find global min/max
   * min/max are in/out.
   */
  virtual void GetGlobalMinMax(vtkPainterCommunicator*, float&, float&) {}

  /**
   * Methods used for parallel benchmarks. Use cmake to define
   * vtkLineIntegralConviolution2DTIME to enable benchmarks.
   * During each update timing information is stored, it can
   * be written to disk by calling WriteLog.
   */
  virtual void WriteTimerLog(const char*) {}

protected:
  vtkLineIntegralConvolution2D();
  ~vtkLineIntegralConvolution2D() override;

  vtkPainterCommunicator* Comm;

  void SetVTShader(vtkShaderProgram* prog);
  void SetLIC0Shader(vtkShaderProgram* prog);
  void SetLICIShader(vtkShaderProgram* prog);
  void SetLICNShader(vtkShaderProgram* prog);
  void SetEEShader(vtkShaderProgram* prog);
  void SetCEShader(vtkShaderProgram* prog);
  void SetAAHShader(vtkShaderProgram* prog);
  void SetAAVShader(vtkShaderProgram* prog);

  void BuildShaders();

  void RenderQuad(float computeBounds[4], vtkPixelExtent computeExtent);

  vtkTextureObject* AllocateBuffer(unsigned int texSize[2]);

  /**
   * Convenience functions to ensure that the input textures are
   * configured correctly.
   */
  void SetNoise2TexParameters(vtkTextureObject* noise);

  /**
   * Methods used for parallel benchmarks. Use cmake to define
   * vtkSurfaceLICPainterTIME to enable benchmarks. During each
   * update timing information is stored, it can be written to
   * disk by calling WriteLog (defined in vtkSurfaceLICPainter).
   */
  virtual void StartTimerEvent(const char*) {}
  virtual void EndTimerEvent(const char*) {}

  vtkWeakPointer<vtkOpenGLRenderWindow> Context;
  vtkOpenGLFramebufferObject* FBO;

  int ShadersNeedBuild;
  vtkOpenGLHelper* FinalBlendProgram;
  vtkOpenGLHelper* IntermediateBlendProgram;
  vtkOpenGLHelper* VTShader;
  vtkOpenGLHelper* LIC0Shader;
  vtkOpenGLHelper* LICIShader;
  vtkOpenGLHelper* LICNShader;
  vtkOpenGLHelper* EEShader;
  vtkOpenGLHelper* CEShader;
  vtkOpenGLHelper* AAHShader;
  vtkOpenGLHelper* AAVShader;

  int NumberOfSteps;
  double StepSize;
  int EnhancedLIC;
  int EnhanceContrast;
  double LowContrastEnhancementFactor;
  double HighContrastEnhancementFactor;
  int AntiAlias;
  int NoiseTextureLookupCompatibilityMode;
  double MaskThreshold;
  int TransformVectors;
  int NormalizeVectors;
  int ComponentIds[2];
  double MaxNoiseValue;

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

VTK_ABI_NAMESPACE_END
#endif
