text: /*
  -----------------------------------------------------------------------------
  This source file is part of OGRE-Next
      (Object-oriented Graphics Rendering Engine)
  For the latest info, see http://www.ogre3d.org/
  
  Copyright (c) 2000-2014 Torus Knot Software Ltd
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:
  
  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.
  
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
  -----------------------------------------------------------------------------
  */
  
  #include "Terra/Hlms/OgreHlmsTerra.h"
  #include "Terra/Hlms/OgreHlmsTerraDatablock.h"
  #include "OgreHlmsManager.h"
  #include "OgreHlmsListener.h"
  #include "OgreLwString.h"
  
  #if !OGRE_NO_JSON
      #include "Terra/Hlms/OgreHlmsJsonTerra.h"
  #endif
  
  #include "OgreViewport.h"
  #include "OgreHighLevelGpuProgramManager.h"
  #include "OgreHighLevelGpuProgram.h"
  #include "OgreForward3D.h"
  #include "Cubemaps/OgreParallaxCorrectedCubemap.h"
  #include "OgreIrradianceVolume.h"
  #include "OgreCamera.h"
  
  #include "OgreSceneManager.h"
  #include "OgreRenderQueue.h"
  #include "Compositor/OgreCompositorShadowNode.h"
  #include "Vao/OgreVaoManager.h"
  #include "Vao/OgreConstBufferPacked.h"
  
  #include "CommandBuffer/OgreCommandBuffer.h"
  #include "CommandBuffer/OgreCbTexture.h"
  #include "CommandBuffer/OgreCbShaderBuffer.h"
  
  #ifdef OGRE_BUILD_COMPONENT_PLANAR_REFLECTIONS
      #include "OgrePlanarReflections.h"
  #endif
  
  #include "Terra/Terra.h"
  
  namespace Ogre
  {
      const IdString TerraProperty::UseSkirts         = IdString( "use_skirts" );
      const IdString TerraProperty::ZUp               = IdString( "z_up" );
  
      const char *TerraProperty::DiffuseMap           = "diffuse_map";
      const char *TerraProperty::EnvProbeMap          = "envprobe_map";
      const char *TerraProperty::DetailWeightMap      = "detail_weight_map";
      const char *TerraProperty::DetailMapN           = "detail_map";     //detail_map0-4
      const char *TerraProperty::DetailMapNmN         = "detail_map_nm";  //detail_map_nm0-4
      const char *TerraProperty::RoughnessMap         = "roughness_map";
      const char *TerraProperty::MetalnessMap         = "metalness_map";
  
      HlmsTerra::HlmsTerra( Archive *dataFolder, ArchiveVec *libraryFolders ) :
          HlmsPbs( dataFolder, libraryFolders ),
          mLastMovableObject( 0 )
      {
          //Override defaults
          mType = HLMS_USER3;
          mTypeName = "Terra";
          mTypeNameStr = "Terra";
  
          mBytesPerSlot = HlmsTerraDatablock::MaterialSizeInGpuAligned;
          mOptimizationStrategy = LowerGpuOverhead;
          mSetupWorldMatBuf = false;
          mReservedTexBufferSlots = 0u;
          mReservedTexSlots = 3u; //heightMap, terrainNormals & terrainShadows
  
          mSkipRequestSlotInChangeRS = true;
      }
      //-----------------------------------------------------------------------------------
      HlmsTerra::~HlmsTerra()
      {
          destroyAllBuffers();
      }
      //-----------------------------------------------------------------------------------
      void HlmsTerra::_linkTerra( Terra *terra )
      {
          OGRE_ASSERT_LOW( terra->mHlmsTerraIndex == std::numeric_limits<uint32>::max() &&
                           "Terra instance must be unlinked before being linked again!" );
  
          terra->mHlmsTerraIndex = static_cast<uint32>( mLinkedTerras.size() );
          mLinkedTerras.push_back( terra );
      }
      //-----------------------------------------------------------------------------------
      void HlmsTerra::_unlinkTerra( Terra *terra )
      {
          if( terra->mHlmsTerraIndex >= mLinkedTerras.size() ||
              terra != *( mLinkedTerras.begin() + terra->mHlmsTerraIndex ) )
          {
              OGRE_EXCEPT( Exception::ERR_INTERNAL_ERROR,
                           "A Terra instance had it's mHlmsTerraIndex out of date!!! "
                           "(or the instance wasn't being tracked by this HlmsTerra)",
                           "HlmsTerra::_unlinkTerra" );
          }
  
          FastArray<Terra *>::iterator itor = mLinkedTerras.begin() + terra->mHlmsTerraIndex;
          itor = efficientVectorRemove( mLinkedTerras, itor );
  
          // The Renderable that was at the end got swapped and has now a different index
          if( itor != mLinkedTerras.end() )
              ( *itor )->mHlmsTerraIndex = static_cast<uint32>( itor - mLinkedTerras.begin() );
  
          terra->mHlmsTerraIndex = std::numeric_limits<uint32>::max();
      }
      //-----------------------------------------------------------------------------------
      void HlmsTerra::_changeRenderSystem( RenderSystem *newRs )
      {
          HlmsPbs::_changeRenderSystem( newRs );
  
          if( newRs )
          {
              HlmsDatablockMap::const_iterator itor = mDatablocks.begin();
              HlmsDatablockMap::const_iterator end  = mDatablocks.end();
  
              while( itor != end )
              {
                  assert( dynamic_cast<HlmsTerraDatablock*>( itor->second.datablock ) );
                  HlmsTerraDatablock *datablock =
                          static_cast<HlmsTerraDatablock*>( itor->second.datablock );
  
                  requestSlot( datablock->mTextureHash, datablock, false );
                  ++itor;
              }
          }
      }
      //-----------------------------------------------------------------------------------
      void HlmsTerra::setDetailMapProperties( HlmsTerraDatablock *datablock, PiecesMap *inOutPieces )
      {
          int32 minNormalMap = 4;
          bool hasDiffuseMaps = false;
          bool hasNormalMaps = false;
          for( uint8 i=0; i<4u; ++i )
          {
              setDetailTextureProperty( TerraProperty::DetailMapN,   datablock,
                                        TERRA_DETAIL0, i );
              setDetailTextureProperty( TerraProperty::DetailMapNmN, datablock,
                                        TERRA_DETAIL0_NM, i );
              setDetailTextureProperty( TerraProperty::RoughnessMap, datablock,
                                        TERRA_DETAIL_ROUGHNESS0, i );
              setDetailTextureProperty( TerraProperty::MetalnessMap, datablock,
                                        TERRA_DETAIL_METALNESS0, i );
  
              if( datablock->getTexture( TERRA_DETAIL0 + i ) )
                  hasDiffuseMaps = true;
  
              if( datablock->getTexture( TERRA_DETAIL0_NM + i ) )
              {
                  minNormalMap = std::min<int32>( minNormalMap, i );
                  hasNormalMaps = true;
              }
  
              if( datablock->mDetailsOffsetScale[i] != Vector4( 0, 0, 1, 1 ) )
                  setProperty( *PbsProperty::DetailOffsetsPtrs[i], 1 );
          }
  
          if( hasDiffuseMaps )
              setProperty( PbsProperty::DetailMapsDiffuse, 4 );
  
          if( hasNormalMaps )
              setProperty( PbsProperty::DetailMapsNormal, 4 );
  
          setProperty( PbsProperty::FirstValidDetailMapNm, minNormalMap );
      }
      //-----------------------------------------------------------------------------------
      void HlmsTerra::setTextureProperty( const char *propertyName, HlmsTerraDatablock *datablock,
                                          TerraTextureTypes texType )
      {
          uint8 idx = datablock->getIndexToDescriptorTexture( texType );
          if( idx != NUM_TERRA_TEXTURE_TYPES )
          {
              char tmpData[64];
              LwString propName = LwString::FromEmptyPointer( tmpData, sizeof(tmpData) );
  
              propName = propertyName; //diffuse_map
  
              const size_t basePropSize = propName.size(); // diffuse_map
  
              //In the template the we subtract the "+1" for the index.
              //We need to increment it now otherwise @property( diffuse_map )
              //can translate to @property( 0 ) which is not what we want.
              setProperty( propertyName, idx + 1 );
  
              propName.resize( basePropSize );
              propName.a( "_idx" ); //diffuse_map_idx
              setProperty( propName.c_str(), idx );
  
              if( mHasSeparateSamplers )
              {
                  const uint8 samplerIdx = datablock->getIndexToDescriptorSampler( texType );
                  propName.resize( basePropSize );
                  propName.a( "_sampler" );           //diffuse_map_sampler
                  setProperty( propName.c_str(), samplerIdx );
              }
          }
      }
      //-----------------------------------------------------------------------------------
      void HlmsTerra::setDetailTextureProperty( const char *propertyName, HlmsTerraDatablock *datablock,
                                                TerraTextureTypes baseTexType, uint8 detailIdx )
      {
          const TerraTextureTypes texType = static_cast<TerraTextureTypes>( baseTexType + detailIdx );
          const uint8 idx = datablock->getIndexToDescriptorTexture( texType );
          if( idx != NUM_TERRA_TEXTURE_TYPES )
          {
              char tmpData[64];
              LwString propName = LwString::FromEmptyPointer( tmpData, sizeof(tmpData) );
  
              propName.a( propertyName, detailIdx ); //detail_map0
  
              const size_t basePropSize = propName.size(); // diffuse_map
  
              //In the template the we subtract the "+1" for the index.
              //We need to increment it now otherwise @property( diffuse_map )
              //can translate to @property( 0 ) which is not what we want.
              setProperty( propName.c_str(), idx + 1 );
  
              propName.resize( basePropSize );
              propName.a( "_idx" ); //detail_map0_idx
              setProperty( propName.c_str(), idx );
  
              if( mHasSeparateSamplers )
              {
                  const uint8 samplerIdx = datablock->getIndexToDescriptorSampler( texType );
                  propName.resize( basePropSize );
                  propName.a( "_sampler" );           //detail_map0_sampler
                  setProperty( propName.c_str(), samplerIdx );
              }
          }
      }
      //-----------------------------------------------------------------------------------
      void HlmsTerra::calculateHashFor( Renderable *renderable, uint32 &outHash, uint32 &outCasterHash )
      {
          assert( dynamic_cast<HlmsTerraDatablock*>( renderable->getDatablock() ) );
          HlmsTerraDatablock *datablock = static_cast<HlmsTerraDatablock*>( renderable->getDatablock() );
          if( datablock->getDirtyFlags() & (DirtyTextures|DirtySamplers) )
          {
              //Delay hash generation for later, when we have the final (or temporary) descriptor sets.
              outHash = 0;
              outCasterHash = 0;
          }
          else
          {
              Hlms::calculateHashFor( renderable, outHash, outCasterHash );
          }
  
          datablock->loadAllTextures();
      }
      //-----------------------------------------------------------------------------------
      void HlmsTerra::calculateHashForPreCreate( Renderable *renderable, PiecesMap *inOutPieces )
      {
          assert( dynamic_cast<TerrainCell*>(renderable) &&
                  "This Hlms can only be used on a Terra object!" );
  
          // Disable normal offset bias because the world normals are not available in the
          // vertex shader. We could fetch the normals, but tessellation is constantly changing in
          // Terra, and besides we don't care because Terra doesn't cast shadow maps, thus it
          // has no self occlussion artifacts.
          setProperty( "skip_normal_offset_bias_vs", 1 );
  
          TerrainCell *terrainCell = static_cast<TerrainCell*>(renderable);
          setProperty( TerraProperty::UseSkirts, terrainCell->getUseSkirts() );
          setProperty( TerraProperty::ZUp, terrainCell->isZUp() );
  
          assert( dynamic_cast<HlmsTerraDatablock*>( renderable->getDatablock() ) );
          HlmsTerraDatablock *datablock = static_cast<HlmsTerraDatablock*>( renderable->getDatablock() );
  
          setProperty( PbsProperty::FresnelScalar, 1 );
          setProperty( PbsProperty::FresnelWorkflow, 0 );
          setProperty( PbsProperty::MetallicWorkflow, 1 );
  
          setProperty( PbsProperty::ReceiveShadows, 1 );
  
          uint32 brdf = datablock->getBrdf();
          if( (brdf & TerraBrdf::BRDF_MASK) == TerraBrdf::Default )
          {
              setProperty( PbsProperty::BrdfDefault, 1 );
  
              if( !(brdf & TerraBrdf::FLAG_UNCORRELATED) )
                  setProperty( PbsProperty::GgxHeightCorrelated, 1 );
          }
          else if( (brdf & TerraBrdf::BRDF_MASK) == TerraBrdf::CookTorrance )
              setProperty( PbsProperty::BrdfCookTorrance, 1 );
          else if( (brdf & TerraBrdf::BRDF_MASK) == TerraBrdf::BlinnPhong )
              setProperty( PbsProperty::BrdfBlinnPhong, 1 );
  
          if( brdf & TerraBrdf::FLAG_SPERATE_DIFFUSE_FRESNEL )
              setProperty( PbsProperty::FresnelSeparateDiffuse, 1 );
  
          if( brdf & TerraBrdf::FLAG_LEGACY_MATH )
              setProperty( PbsProperty::LegacyMathBrdf, 1 );
          if( brdf & TerraBrdf::FLAG_FULL_LEGACY )
              setProperty( PbsProperty::RoughnessIsShininess, 1 );
  
          if( datablock->mTexturesDescSet )
              setDetailMapProperties( datablock, inOutPieces );
          else
              setProperty( PbsProperty::FirstValidDetailMapNm, 4 );
  
          if( datablock->mSamplersDescSet )
              setProperty( PbsProperty::NumSamplers, datablock->mSamplersDescSet->mSamplers.size() );
  
          if( terrainCell->getParentTerra()->getHeightMapTex()->getPixelFormat() == PFG_R16_UINT )
              setProperty( "terra_use_uint", 1 );
  
          if( datablock->mTexturesDescSet )
          {
              bool envMap = datablock->getTexture( TERRA_REFLECTION ) != 0;
              setProperty( PbsProperty::NumTextures,
                           datablock->mTexturesDescSet->mTextures.size() - envMap );
  
              setTextureProperty( PbsProperty::DiffuseMap,    datablock,  TERRA_DIFFUSE );
              setTextureProperty( PbsProperty::EnvProbeMap,   datablock,  TERRA_REFLECTION );
              setTextureProperty( PbsProperty::DetailWeightMap,datablock, TERRA_DETAIL_WEIGHT );
  
              //Save the name of the cubemap for hazard prevention
              //(don't sample the cubemap and render to it at the same time).
              const TextureGpu *reflectionTexture = datablock->getTexture( TERRA_REFLECTION );
              if( reflectionTexture )
              {
                  //Manual reflection texture
  //                if( datablock->getCubemapProbe() )
  //                    setProperty( PbsProperty::UseParallaxCorrectCubemaps, 1 );
                  setProperty( PbsProperty::EnvProbeMap,
                               static_cast<int32>( reflectionTexture->getName().mHash ) );
              }
          }
  
          bool usesNormalMap = false;
          for( uint8 i=TERRA_DETAIL0_NM; i<=TERRA_DETAIL3_NM; ++i )
              usesNormalMap |= datablock->getTexture( i ) != 0;
          setProperty( PbsProperty::NormalMap, usesNormalMap );
  
          if( usesNormalMap )
          {
  //            TextureGpu *normalMapTex = datablock->getTexture( TERRA_DETAIL0_NM );
  //            if( PixelFormatGpuUtils::isSigned( normalMapTex->getPixelFormat() ) )
              {
                  setProperty( PbsProperty::NormalSamplingFormat, PbsProperty::NormalRgSnorm.mHash );
                  setProperty( PbsProperty::NormalRgSnorm, PbsProperty::NormalRgSnorm.mHash );
              }
  //            else
  //            {
  //                setProperty( PbsProperty::NormalSamplingFormat, PbsProperty::NormalRgUnorm.mHash );
  //                setProperty( PbsProperty::NormalRgUnorm, PbsProperty::NormalRgUnorm.mHash );
  //            }
              //Reserved for supporting LA textures in GLES2.
  //            else
  //            {
  //                setProperty( PbsProperty::NormalSamplingFormat, PbsProperty::NormalLa.mHash );
  //                setProperty( PbsProperty::NormalLa, PbsProperty::NormalLa.mHash );
  //            }
          }
  
  #ifdef OGRE_BUILD_COMPONENT_PLANAR_REFLECTIONS
          if( mPlanarReflections && mPlanarReflections->hasPlanarReflections( renderable ) )
          {
              if( !mPlanarReflections->_isUpdatingRenderablesHlms() )
                  mPlanarReflections->_notifyRenderableFlushedHlmsDatablock( renderable );
              else
                  setProperty( PbsProperty::UsePlanarReflections, 1 );
          }
  #endif
      }
      //-----------------------------------------------------------------------------------
      void HlmsTerra::calculateHashForPreCaster( Renderable *renderable, PiecesMap *inOutPieces )
      {
          // Override, since shadow casting is very basic
          mSetProperties.clear();
          setProperty( "hlms_no_shadowConstantBias_decl", 1 );
  
          TerrainCell *terrainCell = static_cast<TerrainCell *>( renderable );
          setProperty( TerraProperty::ZUp, terrainCell->isZUp() );
      }
      //-----------------------------------------------------------------------------------
      void HlmsTerra::notifyPropertiesMergedPreGenerationStep()
      {
          HlmsPbs::notifyPropertiesMergedPreGenerationStep();
  
          int32 texSlotsStart = 0;
          if( getProperty( HlmsBaseProp::ForwardPlus ) )
              texSlotsStart = getProperty( "f3dGrid" ) + 1;
          setTextureReg( VertexShader, "heightMap", texSlotsStart + 0 );
          if( !getProperty( HlmsBaseProp::ShadowCaster ) )
          {
                  setTextureReg( PixelShader, "terrainNormals", texSlotsStart + 1 );
                  setTextureReg( PixelShader, "terrainShadows", texSlotsStart + 2 );
          }
      }
      //-----------------------------------------------------------------------------------
      void HlmsTerra::analyzeBarriers( BarrierSolver &barrierSolver,
                                       ResourceTransitionArray &resourceTransitions,
                                       Camera *renderingCamera, const bool bCasterPass )
      {
          HlmsPbs::analyzeBarriers( barrierSolver, resourceTransitions, renderingCamera, bCasterPass );
  
          if( bCasterPass )
              return;
  
          FastArray<Terra *>::const_iterator itor = mLinkedTerras.begin();
          FastArray<Terra *>::const_iterator endt = mLinkedTerras.end();
  
          while( itor != endt )
          {
              const Terra *terraObj = static_cast<const Terra *>( *itor );
  
              if( terraObj->getHeightMapTex()->isRenderToTexture() ||
                  terraObj->getHeightMapTex()->isUav() )
              {
                  barrierSolver.resolveTransition( resourceTransitions, terraObj->getHeightMapTex(),
                                                   ResourceLayout::Texture, ResourceAccess::Read,
                                                   1u << VertexShader );
              }
              if( terraObj->getNormalMapTex()->isRenderToTexture() ||
                  terraObj->getNormalMapTex()->isUav() )
              {
                  barrierSolver.resolveTransition( resourceTransitions, terraObj->getNormalMapTex(),
                                                   ResourceLayout::Texture, ResourceAccess::Read,
                                                   1u << PixelShader );
              }
              // Shadow map texture is used in the vertex shader in regular objects.
              // Terra uses it in pixel shader.
              barrierSolver.resolveTransition( resourceTransitions, terraObj->_getShadowMapTex(),
                                               ResourceLayout::Texture, ResourceAccess::Read,
                                               ( 1u << VertexShader ) | ( 1u << PixelShader ) );
              ++itor;
          }
      }
      //-----------------------------------------------------------------------------------
      uint32 HlmsTerra::fillBuffersFor( const HlmsCache *cache, const QueuedRenderable &queuedRenderable,
                                        bool casterPass, uint32 lastCacheHash,
                                        uint32 lastTextureHash )
      {
          OGRE_EXCEPT( Exception::ERR_NOT_IMPLEMENTED,
                       "Trying to use slow-path on a desktop implementation. "
                       "Change the RenderQueue settings.",
                       "HlmsTerra::fillBuffersFor" );
      }
      //-----------------------------------------------------------------------------------
      uint32 HlmsTerra::fillBuffersForV1( const HlmsCache *cache,
                                          const QueuedRenderable &queuedRenderable,
                                          bool casterPass, uint32 lastCacheHash,
                                          CommandBuffer *commandBuffer )
      {
          return fillBuffersFor( cache, queuedRenderable, casterPass,
                                 lastCacheHash, commandBuffer, true );
      }
      //-----------------------------------------------------------------------------------
      uint32 HlmsTerra::fillBuffersForV2( const HlmsCache *cache,
                                          const QueuedRenderable &queuedRenderable,
                                          bool casterPass, uint32 lastCacheHash,
                                          CommandBuffer *commandBuffer )
      {
          return fillBuffersFor( cache, queuedRenderable, casterPass,
                                 lastCacheHash, commandBuffer, false );
      }
      //-----------------------------------------------------------------------------------
      uint32 HlmsTerra::fillBuffersFor( const HlmsCache *cache, const QueuedRenderable &queuedRenderable,
                                        bool casterPass, uint32 lastCacheHash,
                                        CommandBuffer *commandBuffer, bool isV1 )
      {
          assert( dynamic_cast<const HlmsTerraDatablock*>( queuedRenderable.renderable->getDatablock() ) );
          const HlmsTerraDatablock *datablock = static_cast<const HlmsTerraDatablock*>(
                                                  queuedRenderable.renderable->getDatablock() );
  
          if( OGRE_EXTRACT_HLMS_TYPE_FROM_CACHE_HASH( lastCacheHash ) != mType )
          {
              //layout(binding = 0) uniform PassBuffer {} pass
              ConstBufferPacked *passBuffer = mPassBuffers[mCurrentPassBuffer-1];
              *commandBuffer->addCommand<CbShaderBuffer>() = CbShaderBuffer( VertexShader,
                                                                             0, passBuffer, 0,
                                                                             passBuffer->
                                                                             getTotalSizeBytes() );
              *commandBuffer->addCommand<CbShaderBuffer>() = CbShaderBuffer( PixelShader,
                                                                             0, passBuffer, 0,
                                                                             passBuffer->
                                                                             getTotalSizeBytes() );
  
              size_t texUnit = mReservedTexBufferSlots;
  
              if( !casterPass )
              {
                  if( mGridBuffer )
                  {
                      *commandBuffer->addCommand<CbShaderBuffer>() =
                              CbShaderBuffer( PixelShader, texUnit++, mGlobalLightListBuffer, 0, 0 );
                      *commandBuffer->addCommand<CbShaderBuffer>() =
                              CbShaderBuffer( PixelShader, texUnit++, mGridBuffer, 0, 0 );
                  }
  
                  texUnit += mReservedTexSlots;
  
                  if( !mPrePassTextures->empty() )
                  {
                      *commandBuffer->addCommand<CbTexture>() =
                              CbTexture( texUnit++, (*mPrePassTextures)[0], 0 );
                      *commandBuffer->addCommand<CbTexture>() =
                              CbTexture( texUnit++, (*mPrePassTextures)[1], 0 );
                  }
  
                  if( mPrePassMsaaDepthTexture )
                  {
                      *commandBuffer->addCommand<CbTexture>() =
                              CbTexture( texUnit++, mPrePassMsaaDepthTexture, 0 );
                  }
  
                  if( mSsrTexture )
                  {
                      *commandBuffer->addCommand<CbTexture>() =
                              CbTexture( texUnit++, mSsrTexture, 0 );
                  }
  
                  if( mIrradianceVolume )
                  {
                      TextureGpu *irradianceTex = mIrradianceVolume->getIrradianceVolumeTexture();
                      const HlmsSamplerblock *samplerblock = mIrradianceVolume->getIrradSamplerblock();
  
                      *commandBuffer->addCommand<CbTexture>() = CbTexture( texUnit,
                                                                           irradianceTex,
                                                                           samplerblock );
                      ++texUnit;
                  }
  
                  if( mUsingAreaLightMasks )
                  {
                      *commandBuffer->addCommand<CbTexture>() = CbTexture( texUnit,
                                                                           mAreaLightMasks,
                                                                           mAreaLightMasksSamplerblock );
                      ++texUnit;
                  }
  
                  if( mLtcMatrixTexture )
                  {
                      *commandBuffer->addCommand<CbTexture>() = CbTexture( texUnit,
                                                                           mLtcMatrixTexture,
                                                                           mAreaLightMasksSamplerblock );
                      ++texUnit;
                  }
  
                  for( size_t i=0; i<3u; ++i )
                  {
                      if( mDecalsTextures[i] &&
                          (i != 2u || !mDecalsDiffuseMergedEmissive) )
                      {
                          *commandBuffer->addCommand<CbTexture>() = CbTexture( texUnit,
                                                                               mDecalsTextures[i],
                                                                               mDecalsSamplerblock );
                          ++texUnit;
                      }
                  }
  
                  //We changed HlmsType, rebind the shared textures.
                  FastArray<TextureGpu*>::const_iterator itor = mPreparedPass.shadowMaps.begin();
                  FastArray<TextureGpu*>::const_iterator end  = mPreparedPass.shadowMaps.end();
                  while( itor != end )
                  {
                      *commandBuffer->addCommand<CbTexture>() = CbTexture( texUnit, *itor,
                                                                           mCurrentShadowmapSamplerblock );
                      ++texUnit;
                      ++itor;
                  }
  
                  if( mParallaxCorrectedCubemap && !mParallaxCorrectedCubemap->isRendering() )
                  {
                      TextureGpu *pccTexture = mParallaxCorrectedCubemap->getBindTexture();
                      const HlmsSamplerblock *samplerblock =
                              mParallaxCorrectedCubemap->getBindTrilinearSamplerblock();
                      *commandBuffer->addCommand<CbTexture>() = CbTexture( texUnit, pccTexture,
                                                                           samplerblock );
                      ++texUnit;
                  }
              }
  
              mLastDescTexture = 0;
              mLastDescSampler = 0;
              mLastMovableObject = 0;
              mLastBoundPool = 0;
  
              //layout(binding = 2) uniform InstanceBuffer {} instance
              if( mCurrentConstBuffer < mConstBuffers.size() &&
                  (size_t)((mCurrentMappedConstBuffer - mStartMappedConstBuffer) + 4) <=
                      mCurrentConstBufferSize )
              {
                  *commandBuffer->addCommand<CbShaderBuffer>() =
                          CbShaderBuffer( VertexShader, 2, mConstBuffers[mCurrentConstBuffer], 0, 0 );
                  *commandBuffer->addCommand<CbShaderBuffer>() =
                          CbShaderBuffer( PixelShader, 2, mConstBuffers[mCurrentConstBuffer], 0, 0 );
              }
  
              //rebindTexBuffer( commandBuffer );
  
  #ifdef OGRE_BUILD_COMPONENT_PLANAR_REFLECTIONS
              mLastBoundPlanarReflection = 0u;
  #endif
              mListener->hlmsTypeChanged( casterPass, commandBuffer, datablock, 0u );
          }
  
          //Don't bind the material buffer on caster passes (important to keep
          //MDI & auto-instancing running on shadow map passes)
          if( mLastBoundPool != datablock->getAssignedPool() )
          {
              //layout(binding = 1) uniform MaterialBuf {} materialArray
              const ConstBufferPool::BufferPool *newPool = datablock->getAssignedPool();
              *commandBuffer->addCommand<CbShaderBuffer>() = CbShaderBuffer( VertexShader,
                                                                             1, newPool->materialBuffer, 0,
                                                                             newPool->materialBuffer->
                                                                             getTotalSizeBytes() );
              *commandBuffer->addCommand<CbShaderBuffer>() = CbShaderBuffer( PixelShader,
                                                                             1, newPool->materialBuffer, 0,
                                                                             newPool->materialBuffer->
                                                                             getTotalSizeBytes() );
              mLastBoundPool = newPool;
          }
  
          if( mLastMovableObject != queuedRenderable.movableObject )
          {
              //Different Terra? Must change textures then.
              const Terra *terraObj = static_cast<const Terra*>( queuedRenderable.movableObject );
              *commandBuffer->addCommand<CbTexture>() =
                  CbTexture( mTexBufUnitSlotEnd + 0u, terraObj->getHeightMapTex() );
              if( !casterPass )
              {
                  // Do not bind these textures during caster pass:
                  //  1. They're not actually used/needed
                  //  2. We haven't transitioned them to Texture yet
  
                  // We need one for terrainNormals & terrainShadows. Reuse an existing samplerblock
                  *commandBuffer->addCommand<CbTexture>() = CbTexture(
                      mTexBufUnitSlotEnd + 1u, terraObj->getNormalMapTex(), mAreaLightMasksSamplerblock );
                  *commandBuffer->addCommand<CbTexture>() = CbTexture(
                      mTexBufUnitSlotEnd + 2u, terraObj->_getShadowMapTex(), mAreaLightMasksSamplerblock );
              }
              mLastMovableObject = queuedRenderable.movableObject;
          }
  
          uint32 * RESTRICT_ALIAS currentMappedConstBuffer    = mCurrentMappedConstBuffer;
  
          //---------------------------------------------------------------------------
          //                          ---- VERTEX SHADER ----
          //---------------------------------------------------------------------------
          //We need to correct currentMappedConstBuffer to point to the right texture buffer's
          //offset, which may not be in sync if the previous draw had skeletal animation.
          bool exceedsConstBuffer = (size_t)((currentMappedConstBuffer - mStartMappedConstBuffer) + 12)
                                      > mCurrentConstBufferSize;
  
          if( exceedsConstBuffer )
              currentMappedConstBuffer = mapNextConstBuffer( commandBuffer );
  
          const TerrainCell *terrainCell = static_cast<const TerrainCell*>( queuedRenderable.renderable );
  
          terrainCell->uploadToGpu( currentMappedConstBuffer );
          currentMappedConstBuffer += 16u;
  
          //---------------------------------------------------------------------------
          //                          ---- PIXEL SHADER ----
          //---------------------------------------------------------------------------
  
          if( !casterPass || datablock->getAlphaTest() != CMPF_ALWAYS_PASS )
          {
  #ifdef OGRE_BUILD_COMPONENT_PLANAR_REFLECTIONS
              if( mHasPlanarReflections &&
                  (queuedRenderable.renderable->mCustomParameter & 0x80) &&
                  mLastBoundPlanarReflection != queuedRenderable.renderable->mCustomParameter )
              {
                  const uint8 activeActorIdx = queuedRenderable.renderable->mCustomParameter & 0x7F;
                  TextureGpu *planarReflTex = mPlanarReflections->getTexture( activeActorIdx );
                  *commandBuffer->addCommand<CbTexture>() =
                          CbTexture( mTexUnitSlotStart - 1u, planarReflTex,
                                     mPlanarReflectionsSamplerblock );
                  mLastBoundPlanarReflection = queuedRenderable.renderable->mCustomParameter;
              }
  #endif
              if( datablock->mTexturesDescSet != mLastDescTexture )
              {
                  if( datablock->mTexturesDescSet )
                  {
                      //Rebind textures
                      size_t texUnit = mTexUnitSlotStart;
  
                      *commandBuffer->addCommand<CbTextures>() =
                              CbTextures( texUnit, std::numeric_limits<uint16>::max(),
                                          datablock->mTexturesDescSet );
  
                      if( !mHasSeparateSamplers )
                      {
                          *commandBuffer->addCommand<CbSamplers>() =
                                  CbSamplers( texUnit, datablock->mSamplersDescSet );
                      }
                      //texUnit += datablock->mTexturesDescSet->mTextures.size();
                  }
  
                  mLastDescTexture = datablock->mTexturesDescSet;
              }
  
              if( datablock->mSamplersDescSet != mLastDescSampler && mHasSeparateSamplers )
              {
                  if( datablock->mSamplersDescSet )
                  {
                      //Bind samplers
                      size_t texUnit = mTexUnitSlotStart;
                      *commandBuffer->addCommand<CbSamplers>() =
                              CbSamplers( texUnit, datablock->mSamplersDescSet );
                      mLastDescSampler = datablock->mSamplersDescSet;
                  }
              }
          }
  
          mCurrentMappedConstBuffer   = currentMappedConstBuffer;
  
          return ((mCurrentMappedConstBuffer - mStartMappedConstBuffer) >> 4) - 1;
      }
      //-----------------------------------------------------------------------------------
      void HlmsTerra::getDefaultPaths( String &outDataFolderPath, StringVector &outLibraryFoldersPaths )
      {
          //We need to know what RenderSystem is currently in use, as the
          //name of the compatible shading language is part of the path
          Ogre::RenderSystem *renderSystem = Ogre::Root::getSingleton().getRenderSystem();
          Ogre::String shaderSyntax = "GLSL";
          if (renderSystem->getName() == "Direct3D11 Rendering Subsystem")
              shaderSyntax = "HLSL";
          else if (renderSystem->getName() == "Metal Rendering Subsystem")
              shaderSyntax = "Metal";
  
          //Fill the library folder paths with the relevant folders
          outLibraryFoldersPaths.clear();
          outLibraryFoldersPaths.push_back( "Hlms/Common/" + shaderSyntax );
          outLibraryFoldersPaths.push_back( "Hlms/Common/Any" );
          outLibraryFoldersPaths.push_back( "Hlms/Pbs/Any" );
          outLibraryFoldersPaths.push_back( "Hlms/Pbs/Any/Main" );
          outLibraryFoldersPaths.push_back( "Hlms/Terra/Any" );
  
          //Fill the data folder path
          outDataFolderPath = "Hlms/Terra/" + shaderSyntax;
      }
  #if !OGRE_NO_JSON
      //-----------------------------------------------------------------------------------
      void HlmsTerra::_loadJson( const rapidjson::Value &jsonValue, const HlmsJson::NamedBlocks &blocks,
                                 HlmsDatablock *datablock, const String &resourceGroup,
                                 HlmsJsonListener *listener,
                                 const String &additionalTextureExtension ) const
      {
          HlmsJsonTerra jsonTerra( mHlmsManager, mRenderSystem->getTextureGpuManager() );
          jsonTerra.loadMaterial( jsonValue, blocks, datablock, resourceGroup );
      }
      //-----------------------------------------------------------------------------------
      void HlmsTerra::_saveJson( const HlmsDatablock *datablock, String &outString,
                                 HlmsJsonListener *listener,
                                 const String &additionalTextureExtension ) const
      {
  //        HlmsJsonTerra jsonTerra( mHlmsManager );
  //        jsonTerra.saveMaterial( datablock, outString );
      }
      //-----------------------------------------------------------------------------------
      void HlmsTerra::_collectSamplerblocks( set<const HlmsSamplerblock*>::type &outSamplerblocks,
                                           const HlmsDatablock *datablock ) const
      {
          HlmsJsonTerra::collectSamplerblocks( datablock, outSamplerblocks );
      }
  #endif
      //-----------------------------------------------------------------------------------
      HlmsDatablock* HlmsTerra::createDatablockImpl( IdString datablockName,
                                                     const HlmsMacroblock *macroblock,
                                                     const HlmsBlendblock *blendblock,
                                                     const HlmsParamVec &paramVec )
      {
          return OGRE_NEW HlmsTerraDatablock( datablockName, this, macroblock, blendblock, paramVec );
      }
  }
  