// Amplify Texture - Massive Virtual Texturing for Unity Pro
// Copyright (c) Amplify Creations, Lda <info@amplify.pt>

#ifndef AMPLIFY_SHARED_INCLUDED
#define AMPLIFY_SHARED_INCLUDED

sampler2D _VTPageTable;
sampler2D _VTDiffuseCache;
sampler2D _VTNormalCache;
sampler2D _VTSpecularCache;

#ifndef _VT_SINGLE_MODE
sampler2D _VTPageTableDesc;
float4 _VTPageTableDesc_TexelSize;
#endif

float4 _VTRcpPagesPerLine;		// xy: rcp_pages_per_line, zw: bd_scale * rcp_pages_per_line
float4 _VTPhysTableInfo;		// xy: tabtex_rcp_size, z: singleVirtualSize, w: singleTableSize
float4 _VTBorderAndPrePass;		// x: bd_scale, y: bd_offs, z: pp_w_scale, w: pp_h_scale
float4 _VTMipInfo;				// x: anisoLevel, y: anisoLog2, z: mip_max, w: mip_bias
float4 _VTTableAddrRange;		// xyz: range, w: 1
float4 _VTInfoBlock;			// xy: gbl_scale, zw: gbl_offset

struct TextureDesc
{
#ifndef _VT_SINGLE_MODE
	float4 table;
	float4 tableTransform;
#endif
	float4 texelSize;
	float4 transform;
	bool tiled;
};

struct VirtualCoord
{
	TextureDesc tex;
	float mip;
	float mip1, mip2;
	float2 dx1, dy1;
	float2 dx2, dy2;
	float2 page1uv;
	float2 page2uv;
	float pageBlend;
};

struct VirtualUV
{
	float2 cont;
	float2 wrap;
};

float VTMipLevelAnisotropic( float2 vtc )
{
	float2 dx = ddx( vtc );
	float2 dy = ddy( vtc );
	float px = dot( dx, dx );
	float py = dot( dy, dy );
	float maxLod = 0.5 * log2( max( px, py ) );
	float minLod = 0.5 * log2( min( px, py ) );
	float lod = maxLod - min( maxLod - minLod, _VTMipInfo.y );
	return max( lod, 0 );
}

inline VirtualUV VTComputeVirtualUV( float2 uv, TextureDesc tex )
{
	VirtualUV virtual_uv;
	float4 transform = float4( tex.transform.xy, frac( tex.transform.zw ) );
	float2 clip_uv = tex.tiled ? frac( uv.xy ) : saturate( uv.xy );
	virtual_uv.cont = uv * transform.xy + transform.zw;
	virtual_uv.wrap = clip_uv * transform.xy + transform.zw;
	return virtual_uv;
}

inline float VTComputeVirtualMip( float2 uv, float2 mipUVScale, TextureDesc tex, float bias )
{
	return VTMipLevelAnisotropic( uv * tex.texelSize.zw * mipUVScale ) + bias + _VTMipInfo.w;
}

inline float VTComputeVirtualMip( float2 uv, float2 mipUVScale, TextureDesc tex )
{
	return VTComputeVirtualMip( uv, mipUVScale, tex, 0 );
}

inline float3 VTFetchPageTable( VirtualUV virtual_uv, float mip, TextureDesc tex )
{
#ifndef _VT_SINGLE_MODE
	float2 size = tex.table.yy / exp2( mip );
	float2 table_uv = floor( virtual_uv.wrap * size ) / size * tex.tableTransform.xy + tex.tableTransform.zw;
	return floor( tex2Dlod( _VTPageTable, float4( table_uv, 0, mip ) ).rgb * _VTTableAddrRange.xyz + 0.5f ).yxz;
#else
	float rmip2 = 1 / exp2( mip );
	float2 size = _VTPhysTableInfo.ww * rmip2;
	float2 scale = float2( 0.5, 1.0 ) * rmip2;
	float2 offset = float2( 1.0 - rmip2, 0.0 );
	float2 table_uv = floor( virtual_uv.wrap * size ) / size;
	return floor( tex2Dlod( _VTPageTable, float4( table_uv * scale + offset, 0, 0 ) ).rgb * _VTTableAddrRange.xyz + 0.5f ).yxz;
#endif
}

inline float2 VTComputePageCoords( VirtualUV virtual_uv, float3 page, TextureDesc tex )
{
#ifndef _VT_SINGLE_MODE
	float2 tableSize = tex.table.yy;
#else
	float2 tableSize = _VTPhysTableInfo.ww;
#endif
	float2 pageTC = virtual_uv.wrap * tableSize / exp2( page.z );
	float2 cacheTC = page.xy + frac( pageTC ) * _VTBorderAndPrePass.xx + _VTBorderAndPrePass.yy;
	return cacheTC * _VTRcpPagesPerLine.xy;
}

inline float2 VTComputePageCoords( VirtualUV virtual_uv, float virtual_mip, float3 page, TextureDesc tex, out float2 dx, out float2 dy )
{
#ifndef _VT_SINGLE_MODE
	float2 tableSize = tex.table.yy;
#else
	float2 tableSize = _VTPhysTableInfo.ww;
#endif
	float rcp_pageMip2 = 1.0f / exp2( page.z );
	float rcp_virtMip2 = 1.0f / exp2( virtual_mip );
	float2 wrap_pageTC = virtual_uv.wrap * tableSize * rcp_pageMip2;
	float2 cont_pageTC = virtual_uv.cont * tableSize * rcp_virtMip2 * _VTRcpPagesPerLine.zw;
	float2 cacheTC = page.xy + frac( wrap_pageTC ) * _VTBorderAndPrePass.xx + _VTBorderAndPrePass.yy;
	dx = ddx( cont_pageTC );
	dy = ddy( cont_pageTC );
	return cacheTC * _VTRcpPagesPerLine.xy;
}

inline TextureDesc FetchTextureDesc()
{
	TextureDesc desc;
	float4 transform = float4( _VTInfoBlock.xy, abs( _VTInfoBlock.zw ) );
#ifndef _VT_SINGLE_MODE
	float4 desc_uv = float4( trunc( transform.zz ) * _VTPageTableDesc_TexelSize.xy, 0, 0 );
	float4 packedDesc = floor( tex2Dlod( _VTPageTableDesc, desc_uv ) * float4( 255, 255, 256, 256 ) + float4( 0.5, 0.5, 0, 0 ) );
	desc.table = float4( exp2( packedDesc.xy ), packedDesc.zw / 128 );
	desc.tableTransform = float4( desc.table.yy * _VTPhysTableInfo.xy, frac( desc.table.zw ) );
	float2 size = transform.xy * desc.table.xx;
#else
	float2 size = transform.xy * _VTPhysTableInfo.zz;
#endif
	desc.texelSize = float4( 1 / size, size );
	desc.transform = transform;
	desc.tiled = ( _VTInfoBlock.w >= 0 );
	return desc;
}

inline VirtualCoord VTComputeVirtualCoordLod( float2 uv, float lod )
{
	VirtualCoord vcoord;
	vcoord.tex = FetchTextureDesc();
	vcoord.mip = clamp( lod, 0, _VTMipInfo.z );
	vcoord.mip1 = floor( vcoord.mip );
	vcoord.mip2 = vcoord.mip1 + 1;
	VirtualUV virtual_uv = VTComputeVirtualUV( uv, vcoord.tex );
	float3 page1 = VTFetchPageTable( virtual_uv, vcoord.mip1, vcoord.tex );
	float3 page2 = VTFetchPageTable( virtual_uv, vcoord.mip2, vcoord.tex );
	vcoord.page1uv = VTComputePageCoords( virtual_uv, vcoord.mip, page1, vcoord.tex, vcoord.dx1, vcoord.dy1 );
	vcoord.page2uv = VTComputePageCoords( virtual_uv, vcoord.mip, page2, vcoord.tex, vcoord.dx2, vcoord.dy2 );
	vcoord.pageBlend = vcoord.mip - vcoord.mip1;
	return vcoord;
}

inline VirtualCoord VTComputeVirtualCoordBias( float2 uv, float bias )
{
	VirtualCoord vcoord;
	vcoord.tex = FetchTextureDesc();
	vcoord.mip = clamp( VTComputeVirtualMip( uv, ( 1 ).xx, vcoord.tex, bias ), 0, _VTMipInfo.z );
	vcoord.mip1 = floor( vcoord.mip );
	vcoord.mip2 = vcoord.mip1 + 1;
	VirtualUV virtual_uv = VTComputeVirtualUV( uv, vcoord.tex );
	float3 page1 = VTFetchPageTable( virtual_uv, vcoord.mip1, vcoord.tex );
	float3 page2 = VTFetchPageTable( virtual_uv, vcoord.mip2, vcoord.tex );
	vcoord.page1uv = VTComputePageCoords( virtual_uv, vcoord.mip, page1, vcoord.tex, vcoord.dx1, vcoord.dy1 );
	vcoord.page2uv = VTComputePageCoords( virtual_uv, vcoord.mip, page2, vcoord.tex, vcoord.dx2, vcoord.dy2 );
	vcoord.pageBlend = vcoord.mip - vcoord.mip1;
	return vcoord;
}

inline VirtualCoord VTComputeVirtualCoord( float2 uv )
{
	return VTComputeVirtualCoordBias( uv, 0 );
}

inline VirtualCoord VTComputeVirtualCoord( float2 uv, float bias )
{
	return VTComputeVirtualCoordBias( uv, bias );
}

inline VirtualCoord VTUpdateVirtualCoord( VirtualCoord vcoord, float2 uv )
{
	VirtualUV virtual_uv = VTComputeVirtualUV( uv, vcoord.tex );
	float3 page1 = VTFetchPageTable( virtual_uv, vcoord.mip1, vcoord.tex );
	float3 page2 = VTFetchPageTable( virtual_uv, vcoord.mip2, vcoord.tex );
	vcoord.page1uv = VTComputePageCoords( virtual_uv, page1, vcoord.tex );
	vcoord.page2uv = VTComputePageCoords( virtual_uv, page2, vcoord.tex );
	vcoord.pageBlend = vcoord.mip - vcoord.mip1;
	return vcoord;
}

inline float4 VTComputeAndSampleCoverage( float2 uv )
{
	TextureDesc tex = FetchTextureDesc();
	VirtualUV virtual_uv = VTComputeVirtualUV( uv, tex );
	float3 page = VTFetchPageTable( virtual_uv, 0, tex );
	float2 dx, dy, pageuv = VTComputePageCoords( virtual_uv, 0, page, tex, dx, dy );
	return tex2Dgrad( _VTDiffuseCache, pageuv, dx, dy );
}

float3 VTDecodeLogLuv( in float4 logluv )
{
	float Le = dot( logluv.wz, float2( 65280, 255 ) ) - 32768;
	float L = max( 0, exp( Le * 0.00271 - 44.360065 ) );
	float2 uv = logluv.xy * 0.6222 + 0.00122;
	uv *= float2( 9.0, 4.0 ) / dot( float3( 6, -16, 12 ), float3( uv.xy, 1 ) );
	float rcp_yL = 1.0 / uv.y * L;
	uv *= rcp_yL;
	return float3( uv.x, L, rcp_yL - uv.x - uv.y );
}

inline float4 VTSampleAlbedo( VirtualCoord vcoord )
{
	float4 cache1 = tex2Dgrad( _VTDiffuseCache, vcoord.page1uv, vcoord.dx1, vcoord.dy1 );
	float4 cache2 = tex2Dgrad( _VTDiffuseCache, vcoord.page2uv, vcoord.dx2, vcoord.dy2 );
	float4 color = lerp( cache1, cache2, vcoord.pageBlend );
#ifdef _VT_HDR_ALBEDO
	color = float4( VTDecodeLogLuv( color ), 1 );
#endif
	return color;
}

inline float4 VTSampleNormal( VirtualCoord vcoord )
{
	float4 cache1 = tex2Dgrad( _VTNormalCache, vcoord.page1uv, vcoord.dx1, vcoord.dy1 );
	float4 cache2 = tex2Dgrad( _VTNormalCache, vcoord.page2uv, vcoord.dx2, vcoord.dy2 );
	return lerp( cache1, cache2, vcoord.pageBlend );
}

inline float4 VTSampleSpecular( VirtualCoord vcoord )
{
	float4 cache1 = tex2Dgrad( _VTSpecularCache, vcoord.page1uv, vcoord.dx1, vcoord.dy1 );
	float4 cache2 = tex2Dgrad( _VTSpecularCache, vcoord.page2uv, vcoord.dx2, vcoord.dy2 );
	return lerp( cache1, cache2, vcoord.pageBlend );
}

inline float4 VTSampleMaterial( VirtualCoord vcoord )
{
	return VTSampleSpecular( vcoord );
}

inline float4 VTSampleBase( VirtualCoord vcoord )
{
	return VTSampleAlbedo( vcoord );
}

inline float4 VTSampleBaseHDR( VirtualCoord vcoord )
{
	return float4( VTDecodeLogLuv( VTSampleAlbedo( vcoord ) ), 1 );
}

inline float VTVertexSampleDisplacement( float2 uv )
{
	TextureDesc tex = FetchTextureDesc();
	VirtualUV virtual_uv = VTComputeVirtualUV( uv, tex );
	float3 page = VTFetchPageTable( virtual_uv, 0, tex );
	float2 pageuv = VTComputePageCoords( virtual_uv, page, tex );
	return tex2Dlod( _VTNormalCache, float4( pageuv, 0, 0 ) ).b;
}

// Unity Version Compatibility
#if ( UNITY_VERSION < 540 )
	#define unity_ObjectToWorld _Object2World
	#define UnityObjectToClipPos( x ) mul( UNITY_MATRIX_MVP, x )
#endif

#endif // AMPLIFY_SHARED_INCLUDED
