//---------------------------------------------------------------------------
// Prepar3d - Shader Effect Files
// Copyright (c) 2017, Lockheed Martin Corporation
//---------------------------------------------------------------------------

#ifndef _FUNCLIBRARY_FXH_
#define _FUNCLIBRARY_FXH_

#include "ConstantBuffers.fxh"

//Fesnel factor using schlick's approximation.
float3 ApproxFresnel(float3 vSpecularColor, float3 vHalfVector, float3 vLightDir)
{
    float fDot = saturate(dot(vLightDir, vHalfVector));
    float3 fresnel = vSpecularColor + (1.0f - vSpecularColor) * pow((1.0f - fDot), 5.0f);

    //Disable the fresnel if color is pure black.
    fresnel *= dot(vSpecularColor, 1.0f) > 0.0f;

    return fresnel;
}

//Attempt to convert specular power to roughness.
float SpecularPowerToRoughness(float specularPower)
{
    return sqrt(2.0f / (specularPower + 2.0f));
}

//Compute GGX specular vis term.
float GGXVisTerm(float specularPowerSqr, float nDotX)
{
    return 1.0f / (nDotX + sqrt(specularPowerSqr + (1 - specularPowerSqr) * nDotX * nDotX));
}

//Get GGX specular contribution.
float GGXSpecular(float specularPower, float3 vNormal, float3 vHalfVector, float3 vViewDir, float3 vLightDir)
{

    float roughness = SpecularPowerToRoughness(specularPower);

    float fNDotH = saturate(dot(vNormal, vHalfVector));
    float fNDotV = saturate(dot(vNormal, vViewDir));
    float fNDotL = saturate(dot(vNormal, vLightDir));

    float fNDotHSqr = fNDotH * fNDotH;
    float froughnessSqr = roughness * roughness;

    float dTerm = froughnessSqr / (3.141592654f * pow(fNDotH * fNDotH * (froughnessSqr - 1) + 1, 2.0f));

    float fVLI = GGXVisTerm(froughnessSqr, fNDotL);
    float fVLO = GGXVisTerm(froughnessSqr, fNDotV);
    float vis = fVLI * fVLO;

    return dTerm * vis;
}

float3 	CalculateSpecular(
    const float3 EyeDir,
    const float3 LightDir,
    const float3 NormalWS,
    const float3 SpecularColor,
    const float3 DiffuseColor,
    const float  SpecularPower,
    const float  HdrSpecularScale)
{

#define HALF_ANGLE_SPECULAR
//#define GGX_SPECULAR //Experimental.

#ifdef HALF_ANGLE_SPECULAR

    //Calculate half normal vector and specular intensity.
    float3 halfNormalVector = normalize(EyeDir + LightDir);

#if defined(SHD_BUMP) || !defined(SHD_VERTICAL_NORMAL)
    float specularIntensity = pow(saturate(dot(NormalWS, halfNormalVector)), SpecularPower);
#else

    float specularIntensity = pow(saturate(halfNormalVector.y), SpecularPower);
#endif

#elif defined(GGX_SPECULAR)
    float3 halfNormalVector = normalize(EyeDir + LightDir);

    float specularIntensity = GGXSpecular(SpecularPower, NormalWS, halfNormalVector, EyeDir, LightDir);

    //float3 fresnel = ApproxFresnel(SpecularColor, halfNormalVector, LightDir);

    

    //return specularIntensity * fresnel * SpecularColor * DiffuseColor;
#else

    //Calculate the reflection vector and specular intensity.
    //float3 vReflect = normalize(2.0 * dot(NormalWS, LightDir) * NormalWS - LightDir);
    float3 vReflect = reflect(-LightDir, NormalWS);
    float specularIntensity = pow(saturate(dot(vReflect, EyeDir)), SpecularPower);

#endif

    //Multiple in the HDR.
    specularIntensity = specularIntensity * HdrSpecularScale;

    //Final specular = specularIntensity * SpecularColor * Diffuse of directional light.
    return specularIntensity * SpecularColor * DiffuseColor;
}

float GetAttenuation(float k, float distance, float radius, float exponent)
{
    //-(1 / k)*(1 - (k + 1) / (1 + k*x ^ 2))
    float x = abs(distance / radius);
    return -(1 / k) * (1 - (k + 1) / (1 + k * pow(x, exponent)));
}

void PointLighting(const float3 vPositionWS,
    const float3 vNormalWS, const float3 vEyeDirWS,
    const PointLight pointLight,
#if defined(SHD_DYNAMIC_LIGHTING_SPECULAR)
    const float3 vSpecularColor,
    const float fSpecularPower,
#endif
    out float3 vDiffuse,
    out float3 vSpecular)
{
    const float3 vPointLightWS = pointLight.mPosition;
    const float dist = distance(vPositionWS, vPointLightWS);

    vDiffuse = float3(0, 0, 0);
    vSpecular = float3(0, 0, 0);

    if (dist < pointLight.mRange)
    {
        const float3 vPointLightDirection = normalize(vPointLightWS - vPositionWS);
        float fAttenuation = GetAttenuation(2, dist, pointLight.mRange, pointLight.mFalloffExponent);

        float fDot = 0;
        #if defined(SHD_VERTICAL_NORMAL)
            fDot = max(vPointLightDirection.y, 0);
        #else
            fDot = saturate(dot(vNormalWS, vPointLightDirection));
        #endif

        vDiffuse += pointLight.mDiffuse.rgb * fAttenuation * fDot * pointLight.mDiffuse.a;
#if defined(SHD_DYNAMIC_LIGHTING_SPECULAR)
        vSpecular += CalculateSpecular(vEyeDirWS, vPointLightDirection, vNormalWS, vSpecularColor, pointLight.mDiffuse.rgb, fSpecularPower, 1.0f) * fAttenuation;
#endif
    }
}

void SpotLighting(const float3 vPositionWS,
    const float3 vNormalWS, const float3 vEyeDirWS,
    const SpotLight spotLight,
#if defined(SHD_DYNAMIC_LIGHTING_SPECULAR)
    const float3 vSpecularColor,
    const float fSpecularPower,
#endif
    out float3 vDiffuse, out float3 vSpecular)
{
    const float3 vSpotLightWS = spotLight.mPosition;
    const float3 vSpotLightDirection = normalize(vSpotLightWS - vPositionWS);
    const float dist = distance(vPositionWS, vSpotLightWS);
    float fCosineOfCurrentConeAngle = dot(-vSpotLightDirection, normalize(spotLight.mDirection));

    vDiffuse = float3(0, 0, 0);
    vSpecular = float3(0, 0, 0);

    if (dist < spotLight.mRange && fCosineOfCurrentConeAngle > spotLight.mPhi)
    {
        float fRadialAttenuation =  (fCosineOfCurrentConeAngle - spotLight.mPhi) / (spotLight.mTheta - spotLight.mPhi);
        fRadialAttenuation = fRadialAttenuation * fRadialAttenuation;

        float fAttenuation = GetAttenuation(2, dist, spotLight.mRange, spotLight.mFalloffExponent);
        fAttenuation = fAttenuation * fRadialAttenuation;

        float fDot = 0;
        #if defined(SHD_VERTICAL_NORMAL)
            fDot = max(vSpotLightDirection.y, 0);
        #else
            fDot = saturate(dot(vNormalWS, vSpotLightDirection));
        #endif

        vDiffuse += spotLight.mDiffuse.rgb * fAttenuation * fDot * spotLight.mDiffuse.a;
#if defined(SHD_DYNAMIC_LIGHTING_SPECULAR)
        vSpecular += CalculateSpecular(vEyeDirWS, vSpotLightDirection, vNormalWS, vSpecularColor, spotLight.mDiffuse.rgb, fSpecularPower, 1.0f) * fAttenuation;
#endif
    }
}

void DirectionalLighting(const float3 vNormalWS, const float shadowContrib,
    out float3 diffuseAndAmbient)
{
#if defined(SHD_VERTICAL_NORMAL)
    const float fDotSun = max(cb_mSun.mDirection.y, 0);
    const float fDotMoon = max(cb_mMoon.mDirection.y, 0);
#else
    const float fDotSun = saturate(dot(vNormalWS, normalize(cb_mSun.mDirection)));
    const float fDotMoon = saturate(dot(vNormalWS, normalize(cb_mMoon.mDirection)));
#endif

    diffuseAndAmbient = saturate((cb_mSun.mAmbient.xyz + (shadowContrib * (cb_mSun.mDiffuse.xyz * fDotSun))) +
                        (cb_mMoon.mAmbient.xyz + (shadowContrib * (cb_mMoon.mDiffuse.xyz * fDotMoon))));
}

void DoubleSidedDirectionalLighting(const float3 vNormalWS, const float shadowContrib,
    out float3 frontFaceDiffuseAndAmbient,
    out float3 backFaceDiffuseAndAmbient)
{
    const float3 sunDirection = normalize(cb_mSun.mDirection);
    const float3 moonDirection = normalize(cb_mMoon.mDirection);

    const float fDotSun = saturate(dot(vNormalWS, sunDirection));
    const float fDotMoon = saturate(dot(vNormalWS, moonDirection));

    const float fBackFaceDotSun = saturate(dot(-vNormalWS, sunDirection));
    const float fBackFaceDotMoon = saturate(dot(-vNormalWS, moonDirection));

    frontFaceDiffuseAndAmbient = saturate((cb_mSun.mAmbient.xyz + (shadowContrib * (cb_mSun.mDiffuse.xyz * fDotSun))) +
                                 (cb_mMoon.mAmbient.xyz + (shadowContrib * (cb_mMoon.mDiffuse.xyz * fDotMoon))));

    backFaceDiffuseAndAmbient = saturate((cb_mSun.mAmbient.xyz + (shadowContrib * (cb_mSun.mDiffuse.xyz * fBackFaceDotSun))) +
                                (cb_mMoon.mAmbient.xyz + (shadowContrib * (cb_mMoon.mDiffuse.xyz * fBackFaceDotMoon))));
}

//
// Fog Helper Functions
// FogVS will fog the vertex and output a distance that must be passed to the pixel shader
// FogPS will take a "base" color and fog it based on the linearly interpolated distance brought in from the vertex shader
float FogVS(const float4 vPosition,
            const matrix mWorld,
            const float4 vEyePoint)
{
    return distance(vEyePoint, mul(vPosition, mWorld));
}

float3 FogPS(const float3 cColor,
             const float  fDistance,
             const float  fFogDensitySquared,
             const float3 cFog)
{
    #if defined(SHD_ADDITIVE) || defined(SHD_MULTIPLICATIVE)
        return lerp(float3( 0, 0, 0 ), cColor, saturate(exp(-fDistance*fDistance*fFogDensitySquared))); 
    #else
		return lerp(cFog, cColor, saturate(exp(-fDistance*fDistance*fFogDensitySquared))); 
    #endif
}

float3 FogPSLightScatter(const float3 cColor,
    const float  fDistance,
    const float  fFogDensitySquared,
    const float3 cFog,
    const float3 vEyeDirWS,
    const float3 vLightDir)
{
    float fogAmount = saturate(exp(-fDistance*fDistance*fFogDensitySquared));
    float lightFogAmount = max(dot(vEyeDirWS, vLightDir), 0.0f);

    float3 finalFogColor = lerp(cFog, float3(1.0, 0.9, 0.7), pow(lightFogAmount, 8.0));
    return lerp(finalFogColor, cColor, fogAmount);
}

float3 FogPSSquaredDist(const float3 cColor,
             const float  fDistanceSquared,
             const float  fFogDensitySquared,
             const float3 cFog)
{
    return lerp(cFog, cColor, saturate(exp(-fDistanceSquared*fFogDensitySquared))); 
}

float4 VolumetricFogPS(const float fPositionY,
                       const float4 cColor,
                       const float  fDistance,
                       const float  fFogDensity,
                       const float3 cFog)
{
    const float distQuared = fDistance*fDistance;
    const float signOfY =  ceil(saturate(fPositionY)) - ceil(saturate(-fPositionY));
    const float oneOverY = 1/fPositionY;
    float4 FinalColor = cColor;
	float horizonFogDensity = fFogDensity;

    //float cameraAlt = cb_mEyePoint.y -fPositionY;
	//const float belowTopLayer = saturate( max( max( cb_mFog[0].cb_mFogTop, cb_mFog[1].cb_mFogTop ), cb_mFog[2].cb_mFogTop ));
	//debug colorizing code
    //float3 colors[3]; colors[0] = float3(1,0,0); colors[1] = float3(0,1,0); colors[2] = float3(0,0,1);

	//Don't fade or override layers by default.
	float3 layerEnableFade = float3(1, 1, 1);

	//If no horizon fog we also want turn off fog layers as we get over the layer.
	#if defined(SHD_NO_HORIZON_FOG)
		horizonFogDensity = 0;

		//The horizon fog layer enabling is based on the camera and layers.
		layerEnableFade = float3(cb_mFog[0].cb_mFogNoHorizonLayerEnable,
			cb_mFog[1].cb_mFogNoHorizonLayerEnable, 
			cb_mFog[2].cb_mFogNoHorizonLayerEnable);

	#endif

    const float layer[3] = { layerEnableFade[0] * cb_mFog[0].cb_mFogEnable, 
					         layerEnableFade[1] * cb_mFog[1].cb_mFogEnable, 
                             layerEnableFade[2] * cb_mFog[2].cb_mFogEnable };

    const uint belowBottomLayer = ceil(saturate(-fPositionY - cb_mFog[0].cb_mFogBase));

	#if defined(SHD_ADDITIVE) || defined(SHD_MULTIPLICATIVE)
		float3 horizonFog = float3(0.0f, 0.0f, 0.0f);
	#else
		float3 horizonFog = cFog;
	#endif
    
	#if !defined(SHD_NO_HORIZON_FOG)
		FinalColor.xyz = (FinalColor.xyz * belowBottomLayer) + 
			(FogPSSquaredDist(FinalColor.xyz, distQuared, horizonFogDensity, horizonFog) * !belowBottomLayer);
	#endif

    //Loop through the fog layers to calculate the layer colors.
    [unroll]for(int i = 0; i < NUM_FOG_LAYERS; i++)
    {
        [branch]if(layer[i] > 0.0f)
        {
            const Fog fog = cb_mFog[i];
            const float yMinusBase = fPositionY - fog.cb_mFogBase;
            const float yMinusTop = fPositionY - fog.cb_mFogTop;

            const float signOfBase = ceil(saturate(fog.cb_mFogBase)) - ceil(saturate(-fog.cb_mFogBase));
            const float signOfTop = ceil(saturate(fog.cb_mFogTop)) - ceil(saturate(-fog.cb_mFogTop));
            const float cameraInLayer = saturate(-signOfBase*signOfTop);
            const float pixelInLayer = saturate(-1 + ceil(saturate(yMinusBase)) + ceil(saturate(-yMinusTop)));

            float ratio = abs(saturate(oneOverY * saturate(signOfY * signOfBase) * yMinusBase) -
                              saturate(oneOverY * saturate(signOfY * signOfTop) * yMinusTop));

            ratio = saturate(ratio + saturate(cameraInLayer + pixelInLayer - 1));
            ratio = lerp(ratio, 1 - ratio, cameraInLayer * (1 - pixelInLayer));
            
			#if defined(SHD_ADDITIVE) || defined(SHD_MULTIPLICATIVE)
				float3 fogColor = float3(0.0f, 0.0f, 0.0f);
			#else
				float3 fogColor = fog.cb_mFogColor.xyz;
				//float3 fogColor = colors[i];
			#endif

			FinalColor.xyz = FogPS(FinalColor.xyz, ratio * fDistance, fog.cb_mFogDensity, fogColor);

            #if !defined(SHD_ADDITIVE) && !defined(SHD_MULTIPLICATIVE)
			    horizonFog = lerp(horizonFog, fog.cb_mFogColor.xyz, cameraInLayer);
			#endif
        }
    }

	#if !defined(SHD_NO_HORIZON_FOG)
		FinalColor.xyz = (FinalColor.xyz * !belowBottomLayer) + 
			(FogPSSquaredDist(FinalColor.xyz, distQuared, horizonFogDensity, horizonFog) * belowBottomLayer);
	#endif

    return FinalColor;
}

float ComputeFog(const float w, 
                 const float fFogEnd, 
                 const float fFogRecip, 
                 const float fFogSelectVertex, 
                 const float fFogSelectTable)
{
    // Compute vertex fog
    // fog = (fog_end - dist) * (1 / (fog_end-fog_start))
    float fFog = (fFogEnd - w) * fFogRecip;
    fFog = max(fFog, 0.0);
    fFog = min(fFog, 1.0);

    // Now select whether we're using vertex fog or table fog
    fFog = (fFog * fFogSelectVertex) + (w * fFogSelectTable);

    return (fFog);
}

float3 TransformVectorByColumnMatrix(const float3 vIn, const float3 column1, const float3 column2, const float3 column3)
{
    float3x3 xformMatrix = float3x3(column1.x, column2.x, column3.x,
                                    column1.y, column2.y, column3.y,
                                    column1.z, column2.z, column3.z);
    return (mul(xformMatrix, vIn));
}

float4 SkinPosition(const float4 vIn, 
                    const float4 vWeight,                     
                    const float4x4 mBone1,
                    const float4x4 mBone2,
                    const float4x4 mBone3,
                    const float4x4 mBone4)
{
    float4 res;   
    res =  vWeight.x * mul(vIn,mBone1);
    res += vWeight.y * mul(vIn,mBone2);
    res += vWeight.z * mul(vIn,mBone3);
    res += vWeight.w * mul(vIn,mBone4);

    return float4(res.xyz,1);
}

float3 SkinNormal(const float4 vIn, 
                  const float4 vWeight, 
                  const float4x4 mBone1, 
                  const float4x4 mBone2,
                  const float4x4 mBone3,
                  const float4x4 mBone4)
{
    float4 res;

    res =  vWeight.x * mul(vIn, mBone1);
    res += vWeight.y * mul(vIn, mBone2);
    res += vWeight.z * mul(vIn, mBone3);
    res += vWeight.w * mul(vIn, mBone4);

    return (res.xyz);
}

#if !defined(SHD_VERTICAL_NORMAL)

float CalculateSpecularBloom(const float3 vEyeDir, 
                             const float3 vLight, 
                             const float3 vNormal, 
                             const float fPower,
                             const float fMatSpecBloomFloor)
{
    float3 vHN = normalize(vEyeDir + vLight);

	// need .0001 bias to prevent artifacts
    float fSpec = pow(saturate(dot(vNormal, vHN)) + 0.0001, 64 * fPower);

    if (fSpec < fMatSpecBloomFloor)
    {
         fSpec = 0;
    }

    return (fSpec);
}

#else

float CalculateSpecularBloom(const float3 vEyeDir, 
                             const float3 vLight, 
                             const float fPower,
                             const float fMatSpecBloomFloor)
{
    float3 vHN = normalize(vEyeDir + vLight);

#if defined(SHD_HDR)
    float fSpec = pow(saturate(vHN.y + 0.0001), 64 * fPower);
#else
    float fSpec = pow(saturate(vHN.y), 64 * fPower);
#endif

    if (fSpec < fMatSpecBloomFloor)
    {
         fSpec = 0;
    }

    return (fSpec);
}

#endif

// Calculate the specular color using a specular map.
float3 	CalculateSpecularFromMap(
	const float3 EyeDir,
	const float3 LightDir,
	const float3 NormalWS, 
	const float4 SpecularMap,
    const float3 DiffuseColor,
	const float  MatSpecMapPowerScale,
	const float  HdrSpecularScale)
{
	#if defined(SHD_HDR)
		float fPow = (SpecularMap.a * MatSpecMapPowerScale) + 0.0001;
	#else
		// biasing specular power scale
		float fPow = SpecularMap.a * MatSpecMapPowerScale + 0.0001;
	#endif
   
    //Clamp specular power to 1.
    fPow = max(fPow, 1);

    return CalculateSpecular(
        EyeDir,
        LightDir,
        NormalWS,
        SpecularMap.rgb,
        DiffuseColor,
        fPow,
        HdrSpecularScale);
}

float3 InvertRGB( const float3 RGB )
{
    return 1 - RGB;
}

float GrayscaleRGB( const float3 RGB )
{
    return dot( RGB, float3(.30, .59, .11) );
}

float DebugThisVar( const float4 var, float modMe )
{
    float modVal = length( normalize( var ) );
    return modMe * modVal;
}

/**
 * @note Expects positionSV to have already been multiplied by world view projection.
**/
float LinearizeDepthZ(const float4 positionSV)
{
	return positionSV.z;

    //return positionSV.z * positionSV.w / cb_FarClip;

	//Logarithmic depth writes which give a nice distribution overall, but not as good up close.
	//float c = 1.01f; //Handles the resolution up close to the camera.
	//return (log(1.0f + c * positionSV.w) / log(c * cb_FarClip + 1.0f)) * positionSV.w;
	//return (log(positionSV.w/cb_NearClip)/log(cb_FarClip/cb_NearClip)) * positionSV.w;
}

/**
 * Creates a new projection matrix based on the per object z bias.
**/
row_major float4x4 PerObjectProjectionZBias(const row_major float4x4 projection)
{
	row_major float4x4 perObjectProjection = projection;

    float bias = 1;

	//Pull the object forward.
    float newFarClip = max(cb_FarClip, 0);
    float newNearClip = max(cb_NearClip + bias, 0);

    float Q = newFarClip / (newFarClip - newNearClip);

    perObjectProjection[2][2] = Q;
	perObjectProjection[3][2] = -Q*newNearClip;
	
	return perObjectProjection;
}

/**
* Monitor resolution gama correction.
* @note, Normally done on base sample textures to get a linear represenation of the image. Unless using sRGB textures.
* @note, If not using sRGB framebuffers, may need to pow(color, 1.0/2.2) during tone mapping/custom resolve.
**/
float3 GammaCorrection(float3 color)
{
	return pow(color,2.2);
}

/**
* Calculate the far clip fade
* @note, Apply fading at the far clip. This prevents objects (such as clouds) from popping in/out at the far clip
* @note, 10000 meters is when we start fading out
**/
float FarClipAlphaFade( in float fDistance, in float fAlpha )
{
    /// Get the distance to the far clip
    float fFarFadeDistance = cb_FarClip - fDistance;
    fFarFadeDistance = fFarFadeDistance * 0.0001f;
    fFarFadeDistance = fAlpha * saturate(fFarFadeDistance);

    return fFarFadeDistance;
}

/**
* Lin step function.
**/
float Linstep(float min, float max, float v)
{
	return clamp((v - min) / (max - min), 0, 1);
}

/**
* Map a value to range.
**/
float MapToRange(float x, float range)
{
    return x / (2.0f*range) + 0.5f;
}

/**
* Random number.
* @note Not very cheap.
* @param Takes in texture coordinates.
* @return Number between 0.0 and 0.9999.. inclusive.
**/
float Random( float2 texCoords )
{
  float2 r = float2(23.1406926327792690,  // e^pi (Gelfond's constant)
				    2.6651441426902251); // 2^sqrt(2) (Gelfond–Schneider constant) 
  return frac( cos( 123456789.0f % (1e-7 + 256.0f * dot(texCoords,r)) ) );  
}

/**
* Creates an ortho matrix off center left hand.
**/
float4x4 CreateOrthoMatrix(float l, float r, float b, float t, float zn, float zf)
{
	float4x4 orthoMatrix;

	orthoMatrix[0][0] = 2/(r-l);
	orthoMatrix[0][1] = 0;
	orthoMatrix[0][2] = 0;
	orthoMatrix[0][3] = 0;

	orthoMatrix[1][0] = 0;
	orthoMatrix[1][1] = 2/(t-b);
	orthoMatrix[1][2] = 0;
	orthoMatrix[1][3] = 0;

	orthoMatrix[2][0] = 0;
	orthoMatrix[2][1] = 0;
	orthoMatrix[2][2] = 1/(zf-zn);
	orthoMatrix[2][3] = 0;

	orthoMatrix[3][0] = (l+r)/(l-r);
	orthoMatrix[3][1] = (t+b)/(b-t);
	orthoMatrix[3][2] = zn/(zn-zf);
	orthoMatrix[3][3] = 1;

	return orthoMatrix;
}

/**
* Take point from projection back to view space.
**/
float4 ProjectionToView(float4 vPosition)
{
    vPosition = mul(vPosition, cb_mInverseProj);
    vPosition /= vPosition.w;
    return vPosition;
}

/**
* Get the light tile index based on screen position.
* @note Expects vPosition to have already been multiplied by world view projection.
**/
uint LightTileIndex(float2 vPosition)
{
    float tileResolution = (float)TILE_RESOLUTION;
    uint xTiles = cb_mLightTileParams.x;
    uint tileIndex = floor(vPosition.x / tileResolution) + floor(vPosition.y / tileResolution) * xTiles;
    return tileIndex;
}

/**
* Perform dynamic lighting for both point and spot lights.
**/
void DynamicLighting( in StructuredBuffer<PointLight> pointLightBuffer,
    in StructuredBuffer<SpotLight> spotLightBuffer,
    in Buffer<uint> lightTileIndexBuffer,
    in const float2 vPositionXY,
    in const float3 vPositionWS,
    in const float3 vNormalWS,
    in const float3 vEyeDirWS,
#if defined(SHD_DYNAMIC_LIGHTING_SPECULAR)
    in const float3 vSpecularColor,
    in const float  fSpecularPower,
#endif
    out float3 vAccumDiffuseLighting,
    out float3 vAccumSpecLighting
    )
{

    vAccumDiffuseLighting = float3(0, 0, 0);
    vAccumSpecLighting = float3(0, 0, 0);

#define TILE_LIGHTING

    #if defined(TILE_LIGHTING)

    uint lightTileIndex = LightTileIndex(vPositionXY) * MAX_LIGHTS_PER_TILE;
    uint nextLightIndex = lightTileIndexBuffer[lightTileIndex];

    [loop]
    while (nextLightIndex != LIGHT_TILE_INDEX_END)
    #else
    uint lightIndex = 0;
    [loop]
    for (lightIndex = 0; lightIndex < cb_mPointLightCount; ++lightIndex)
    #endif
    {
    #if defined(TILE_LIGHTING)

        uint currentLightIndex = nextLightIndex;
        lightTileIndex++;
        nextLightIndex = lightTileIndexBuffer[lightTileIndex];
    #else
        uint currentLightIndex = lightIndex;
    #endif

        float3 lightDiffuse;
        float3 lightSpecular;

        PointLighting(vPositionWS, vNormalWS, vEyeDirWS,
            pointLightBuffer[currentLightIndex],
        #if defined(SHD_DYNAMIC_LIGHTING_SPECULAR)
            vSpecularColor,
            fSpecularPower,
        #endif
            lightDiffuse, lightSpecular);

        vAccumDiffuseLighting += lightDiffuse;
        vAccumSpecLighting += lightSpecular;
    }

    #if defined(TILE_LIGHTING)
    //Move to the spot lights.
    lightTileIndex++;
    nextLightIndex = lightTileIndexBuffer[lightTileIndex];

    [loop]
    while (nextLightIndex != LIGHT_TILE_INDEX_END)
    #else
    [loop]
    for (lightIndex = 0; lightIndex < cb_mSpotLightCount; ++lightIndex)
    #endif
    {

    #if defined(TILE_LIGHTING)

        uint currentLightIndex = nextLightIndex;
        lightTileIndex++;
        nextLightIndex = lightTileIndexBuffer[lightTileIndex];
    #else
        uint currentLightIndex = lightIndex;
    #endif
        float3 lightDiffuse;
        float3 lightSpecular;

        SpotLighting(vPositionWS, vNormalWS, vEyeDirWS,
            spotLightBuffer[currentLightIndex],
        #if defined(SHD_DYNAMIC_LIGHTING_SPECULAR)
            vSpecularColor,
            fSpecularPower,
        #endif
            lightDiffuse, lightSpecular);

        vAccumDiffuseLighting += lightDiffuse;
        vAccumSpecLighting += lightSpecular;
    }
}

#endif // _FUNCLIBRARY_FXH_
