// Author: Ilya Khramov
// Copyright (c) 2019 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.

#ifndef _OpenGl_PBREnvironment_HeaderFile
#define _OpenGl_PBREnvironment_HeaderFile

#include <OpenGl_Texture.hxx>
#include <OpenGl_VertexBuffer.hxx>

//! This class contains specular and diffuse maps required for Image Base Lighting (IBL) in PBR shading model with it's generation methods.
class OpenGl_PBREnvironment : public OpenGl_NamedResource
{
  DEFINE_STANDARD_RTTIEXT(OpenGl_PBREnvironment, OpenGl_NamedResource)
public:

  //! Creates and initializes new PBR environment. It is the only way to create OpenGl_PBREnvironment.
  //! @param theCtx OpenGL context where environment will be created
  //! @param thePow2Size final size of texture's sides can be calculated as 2^thePow2Size;
  //!                    if thePow2Size less than 1 it will be set to 1
  //! @param theSpecMapLevelsNum number of mipmap levels used in specular IBL map;
  //!                            if theSpecMapLevelsNum less than 2 or less than Pow2Size + 1 it will be set to the corresponding values.
  //! @param theId OpenGl_Resource name
  //! @return handle to created PBR environment or NULL handle in case of fail
  Standard_EXPORT static Handle(OpenGl_PBREnvironment) Create (const Handle(OpenGl_Context)&  theCtx,
                                                               unsigned int                   thePow2Size = 9,
                                                               unsigned int                   theSpecMapLevelsNum = 6,
                                                               const TCollection_AsciiString& theId = "PBREnvironment");

public:

  //! Binds diffuse and specular IBL maps to the corresponding texture units.
  Standard_EXPORT void Bind (const Handle(OpenGl_Context)& theCtx);

  //! Unbinds diffuse and specular IBL maps.
  Standard_EXPORT void Unbind (const Handle(OpenGl_Context)& theCtx);

  //! Fills all mipmaps of specular IBL map and diffuse IBL map with one color.
  //! So that environment illumination will be constant.
  Standard_EXPORT void Clear (const Handle(OpenGl_Context)& theCtx,
                              const Graphic3d_Vec3&         theColor = Graphic3d_Vec3 (1.f));

  //! Generates specular and diffuse (irradiance) IBL maps.
  //! @param theCtx OpenGL context
  //! @param theEnvMap source environment map
  //! @param theZIsInverted flags indicates whether environment cubemap has inverted Z axis or not
  //! @param theIsTopDown flags indicates whether environment cubemap has top-down memory layout or not
  //! @param theDiffMapNbSamples number of samples in Monte-Carlo integration for diffuse IBL spherical harmonics calculation
  //! @param theSpecMapNbSamples number of samples in Monte-Carlo integration for specular IBL map generation
  //! @param theProbability controls strength of samples number reducing strategy during specular IBL map baking (see 'SpecIBLMapSamplesFactor' for details)
  //! theZIsInverted and theIsTopDown can be taken from Graphic3d_CubeMap source of environment cubemap.
  //! theDiffMapNbSamples and theSpecMapNbSamples is the main parameter directly affected to performance.
  Standard_EXPORT void Bake (const Handle(OpenGl_Context)& theCtx,
                             const Handle(OpenGl_Texture)& theEnvMap,
                             Standard_Boolean              theZIsInverted = Standard_False,
                             Standard_Boolean              theIsTopDown = Standard_True,
                             Standard_Size                 theDiffMapNbSamples = 1024,
                             Standard_Size                 theSpecMapNbSamples = 256,
                             Standard_ShortReal            theProbability = 0.99f);

  //! Returns number of mipmap levels used in specular IBL map.
  //! It can be different from value passed to creation method.
  unsigned int SpecMapLevelsNumber() const { return mySpecMapLevelsNumber; }

  //! Returns size of IBL maps sides as power of 2.
  //! So that the real size can be calculated as 2^Pow2Size()
  unsigned int Pow2Size() const { return myPow2Size; }

  //! Checks whether the given sizes affects to the current ones.
  //! It can be imagined as creation of new PBR environment.
  //! If creation method with this values returns the PBR environment having real sizes which are equals to current ones
  //! then this method will return false.
  //! It is handful when sizes are required to be changed.
  //! If this method returns false there is no reason to recreate PBR environment in order to change sizes.
  Standard_EXPORT bool SizesAreDifferent (unsigned int thePow2Size,
                                          unsigned int theSpecMapLevelsNumber) const;

  //! Indicates whether IBL map's textures have to be bound or it is not obligate.
  bool IsNeededToBeBound() const
  {
    return myIsNeededToBeBound;
  }

  //! Releases all OpenGL resources.
  //! It must be called before destruction.
  Standard_EXPORT virtual void Release (OpenGl_Context* theCtx) Standard_OVERRIDE;

  //! Returns estimated GPU memory usage for holding data without considering overheads and allocation alignment rules.
  virtual Standard_Size EstimatedDataSize() const Standard_OVERRIDE
  {
    unsigned int aDiffIBLMapSidePixelsCount = 1 << myPow2Size;
    aDiffIBLMapSidePixelsCount *= aDiffIBLMapSidePixelsCount;
    Standard_Size anEstimatedDataSize = aDiffIBLMapSidePixelsCount;
    for (unsigned int i = 0; i < mySpecMapLevelsNumber; ++i)
    {
      anEstimatedDataSize += aDiffIBLMapSidePixelsCount >> (2 * i);
    }
    anEstimatedDataSize *= 6; // cubemap sides
    anEstimatedDataSize *= 3; // channels
    return anEstimatedDataSize;
  }

  //! Checks completeness of PBR environment.
  //! Creation method returns only completed objects or null handles otherwise.
  Standard_Boolean IsComplete() const { return myIsComplete; }

  //! Destructor.
  //! Warning! 'Release' method must be called before destruction.
  //! Otherwise unhandled critical error will be generated.
  Standard_EXPORT virtual ~OpenGl_PBREnvironment();

private:

  //! Creates new PBR environment.
  //! Parameters and logic are described in 'Create' method documentation.
  Standard_EXPORT OpenGl_PBREnvironment (const Handle(OpenGl_Context)&  theCtx,
                                         unsigned int                   thePowOf2Size = 9,
                                         unsigned int                   theSpecMapLevelsNumber = 6,
                                         const TCollection_AsciiString& theId = "PBREnvironment");

private:

  //! Enum classified the type of IBL map
  enum OpenGl_TypeOfIBLMap
  {
    OpenGl_TypeOfIBLMap_DiffuseSH,
    OpenGl_TypeOfIBLMap_Specular
  };

  //! Initializes all textures.
  //! @return false in case of failed texture initialization
  //! Warning! Requires using of OpenGl_PBREnvironmentSentry.
  bool initTextures (const Handle(OpenGl_Context)& theCtx);

  //! Creates frame buffer object for IBL maps generation.
  //! @return false in case of failed FBO initialization
  //! Warning! Requires using of OpenGl_PBREnvironmentSentry.
  bool initFBO (const Handle(OpenGl_Context)& theCtx);

  //! Initializes vertex buffer object of screen rectangle.
  //! @return false in case of failed creation
  //! Warning! Requires using of OpenGl_PBREnvironmentSentry.
  bool initVAO (const Handle(OpenGl_Context)& theCtx);

  //! Responses for diffuse (irradiance) IBL map processing.
  //! @return false in case of failed baking or clearing
  //! Warning! Requires using of OpenGl_PBREnvironmentSentry.
  bool processDiffIBLMap (const Handle(OpenGl_Context)& theCtx,
                          Standard_Boolean      theIsDrawAction,
                          Standard_Size         theNbSamples = 0);

  //! Responses for specular IBL map processing.
  //! @return false in case of failed baking or clearing
  //! Warning! Requires using of OpenGl_PBREnvironmentSentry.
  bool processSpecIBLMap (const Handle(OpenGl_Context)& theCtx,
                          Standard_Boolean   theIsDrawAction,
                          Standard_Integer   theEnvMapSize = 1024,
                          Standard_Size      theNbSamples = 0,
                          Standard_ShortReal theProbability = 1.f);

  //! Checks completeness of frame buffer object for all targets
  //! (all cube map sides and levels of IBL maps).
  //! @return false in case of uncompleted frame buffer object.
  //! Warning! Requires using of OpenGl_PBREnvironmentSentry.
  bool checkFBOComplentess (const Handle(OpenGl_Context)& theCtx) const;

  //! Version of 'Bake' without OpenGl_PBREnvironmentSetnry.
  //! Warning! Requires using of OpenGl_PBREnvironmentSentry.
  void bake (const Handle(OpenGl_Context)& theCtx,
             const Handle(OpenGl_Texture)& theEnvMap,
             Standard_Boolean              theZIsInverted = Standard_False,
             Standard_Boolean              theIsTopDown = Standard_True,
             Standard_Size                 theDiffMapNbSamples = 1024,
             Standard_Size                 theSpecMapNbSamples = 256,
             Standard_ShortReal            theProbability = 1.f);

  //! Version of 'Clear' without OpenGl_PBREnvironmentSetnry.
  //! Warning! Requires using of OpenGl_PBREnvironmentSentry.
  void clear (const Handle(OpenGl_Context)& theCtx,
              const Graphic3d_Vec3&         theColor = Graphic3d_Vec3 (1.f));

private:

  unsigned int        myPow2Size;            //!< size of IBL maps sides (real size can be calculated as 2^myPow2Size)
  unsigned int        mySpecMapLevelsNumber; //!< number of mipmap levels used in specular IBL map

  OpenGl_Texture      myIBLMaps[2];          //!< IBL maps
  OpenGl_VertexBuffer myVBO;                 //!< vertex buffer object of screen rectangular
  GLuint              myFBO;                 //!< frame buffer object to generate or clear IBL maps

  Standard_Boolean    myIsComplete;          //!< completeness of PBR environment
  Standard_Boolean    myIsNeededToBeBound;   //!< indicates whether IBL map's textures have to be bound or it is not obligate

};

#endif // _OpenGl_PBREnvironment_HeaderFile
