;------------------------------------
; RULES FOR AUTHORING VERTEX SHADERS:
;------------------------------------
; - never use "def" . . .set constants in code instead. . our constant shadowing will break otherwise.
;	(same goes for pixel shaders)
; - use cN notation instead of c[N] notation. .makes grepping for registers easier.
;   The only exception is c[a0.x+blah] where you have no choice.
$g_NumRegisters = 12;

; NOTE: These must match the same values in vsh_prep.pl!
$vPos				= "v0";
$vBoneWeights		= "v1";
$vBoneIndices		= "v2";
$vNormal			= "v3";
$vColor				= "v5";
$vSpecular			= "v6";
$vTexCoord0			= "v7";
$vTexCoord1			= "v8";
$vTexCoord2			= "v9";
$vTexCoord3			= "v10";
$vTangentS			= "v11";
$vTangentT			= "v12";
$vUserData			= "v14";

if( $g_dx9 )
{
	if( $g_usesPos )
	{
		dcl_position $vPos;
	}
	
	if( $g_usesBoneWeights )
	{
		dcl_blendweight $vBoneWeights;
	}
	if( $g_usesBoneIndices )
	{
		dcl_blendindices $vBoneIndices;
	}
	if( $g_usesNormal )
	{
		dcl_normal $vNormal;
	}
	if( $g_usesColor )
	{
		dcl_color0 $vColor;
	}
	if( $g_usesSpecular )
	{
		dcl_color1 $vSpecular;
	}
	if( $g_usesTexCoord0 )
	{
		dcl_texcoord0 $vTexCoord0;
	}
	if( $g_usesTexCoord1 )
	{
		dcl_texcoord1 $vTexCoord1;
	}
	if( $g_usesTexCoord2 )
	{
		dcl_texcoord2 $vTexCoord2;
	}
	if( $g_usesTexCoord3 )
	{
		dcl_texcoord3 $vTexCoord3;
	}
	if( $g_usesTangentS )
	{
		dcl_tangent $vTangentS;
	}
	if( $g_usesTangentT )
	{
		dcl_binormal0 $vTangentT;
	}
	if( $g_usesUserData )
	{
		dcl_tangent $vUserData;
	}
}

# NOTE: These should match g_LightCombinations in vertexshaderdx8.cpp!
# NOTE: Leave this on single lines or shit might blow up.
@g_staticLightTypeArray = ( "none", "static", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "static", "static", "static", "static", "static", "static", "static", "static", "static", "static" );
@g_ambientLightTypeArray = ( "none", "none", "ambient", "ambient", "ambient", "ambient", "ambient", "ambient", 	"ambient", "ambient", "ambient", "ambient", "ambient", "ambient", "ambient", "ambient", "ambient", "ambient", "ambient", "ambient", "ambient", "ambient" );
@g_localLightType1Array = ( "none", "none", "none", "spot", "point", "directional", "spot", "spot", "spot", "point", "point", "directional", "none", "spot", "point", "directional", "spot", "spot", "spot", "point", "point", "directional" );
@g_localLightType2Array = ( "none", "none", "none", "none", "none", "none", "spot", "point", "directional", "point", "directional", "directional", "none", "none", "none", "none", "spot", "point", "directional", "point", "directional", "directional" );

$cConstants0		= "c0";
$cZero				= "c0.x";
$cOne				= "c0.y";
$cTwo				= "c0.z";
$cHalf				= "c0.w";

$cConstants1		    = "c1";
$cOOGamma			    = "c1.x"; # 1/2.2
$cOtherOverbrightFactor = "c1.y"; # overbright
$cOneThird			    = "c1.z"; # 1/3
$cOverbrightFactor      = "c1.w"; # 1/overbright

$cEyePos			= "c2";
$cWaterZ			= "c2.w";
$cEyePosWaterZ		= "c2";

$cLightIndex		= "c3";
$cLight0Offset		= "c3.x"; # 27
$cLight1Offset		= "c3.y"; # 32
$cColorToIntScale	= "c3.z"; # matrix array offset = 3.0f * 255.0f + 0.01 (epsilon ensures floor yields desired result)
$cModel0Index		= "c3.w"; # base for start of skinning matrices

; NOTE: These must match the same values in vsh_prep.pl!
$cModelViewProj0	= "c4";
$cModelViewProj1	= "c5";
$cModelViewProj2	= "c6";
$cModelViewProj3	= "c7";

$cViewProj0			= "c8";
$cViewProj1			= "c9";
$cViewProj2			= "c10";
$cViewProj3			= "c11";

; currently unused
; c12, c13

$SHADER_SPECIFIC_CONST_10 = "c14";
$SHADER_SPECIFIC_CONST_11 = "c15";

$cFogParams			= "c16";
$cFogEndOverFogRange = "c16.x";
$cFogOne			= "c16.y";
$cFogMaxDensity		= "c16.z";
$cOOFogRange		= "c16.w"; # (1/(fogEnd-fogStart))

$cViewModel0		= "c17";
$cViewModel1		= "c18";
$cViewModel2		= "c19";
$cViewModel3		= "c20";

$cAmbientColorPosX	= "c21";
$cAmbientColorNegX	= "c22";
$cAmbientColorPosY	= "c23";
$cAmbientColorNegY	= "c24";
$cAmbientColorPosZ	= "c25";
$cAmbientColorNegZ	= "c26";

$cAmbientColorPosXOffset = "21";
$cAmbientColorPosYOffset = "23";
$cAmbientColorPosZOffset = "25";

$cLight0DiffColor	= "c27";
$cLight0Dir			= "c28";
$cLight0Pos			= "c29";
$cLight0SpotParams  = "c30"; # [ exponent, stopdot, stopdot2, 1 / (stopdot - stopdot2)
$cLight0Atten		= "c31"; # [ constant, linear, quadratic, 0.0f ]

$cLight1DiffColor	= "c32";
$cLight1Dir			= "c33";
$cLight1Pos			= "c34";
$cLight1SpotParams  = "c35"; # [ exponent, stopdot, stopdot2, 1 / (stopdot - stopdot2)
$cLight1Atten		= "c36"; # [ constant, linear, quadratic, 0.0f ]

$cModulationColor	= "c37";

$SHADER_SPECIFIC_CONST_0  = "c38";
$SHADER_SPECIFIC_CONST_1  = "c39";
$SHADER_SPECIFIC_CONST_2  = "c40";
$SHADER_SPECIFIC_CONST_3  = "c41";
$SHADER_SPECIFIC_CONST_4  = "c42";
$SHADER_SPECIFIC_CONST_5  = "c43";
$SHADER_SPECIFIC_CONST_6  = "c44";
$SHADER_SPECIFIC_CONST_7  = "c45";
$SHADER_SPECIFIC_CONST_8  = "c46";
$SHADER_SPECIFIC_CONST_9  = "c47";
; $SHADER_SPECIFIC_CONST_10 is c14
; $SHADER_SPECIFIC_CONST_11 is c15

; There are 16 model matrices for skinning
; NOTE: These must match the same values in vsh_prep.pl!
$cModel0			= "c48";
$cModel1			= "c49";
$cModel2			= "c50";

sub OutputUsedRegisters
{
	local( $i );
	; USED REGISTERS
	for( $i = 0; $i < $g_NumRegisters; $i++ )
	{
		if( $g_allocated[$i] )
		{
			; $g_allocatedname[$i] = r$i
		}
	}
	;
}

sub AllocateRegister
{
	local( *reg ) = shift;
	local( $regname ) = shift;
	local( $i );
	for( $i = 0; $i < $g_NumRegisters; $i++ )
	{
		if( !$g_allocated[$i] )
		{
			$g_allocated[$i] = 1;
			$g_allocatedname[$i] = $regname;
			; AllocateRegister $regname = r$i
			$reg = "r$i";
			&OutputUsedRegisters();
			return;
		}
	}
	; Out of registers allocating $regname!
	$reg = "rERROR_OUT_OF_REGISTERS";
	&OutputUsedRegisters();
}

; pass in a reference to a var that contains a register. . ie \$var where var will constain "r1", etc
sub FreeRegister
{
	local( *reg ) = shift;
	local( $regname ) = shift;
	; FreeRegister $regname = $reg
	if( $reg =~ m/rERROR_DEALLOCATED/ )
	{
		; $regname already deallocated
		; $reg = "rALREADY_DEALLOCATED";
		&OutputUsedRegisters();
		return;
	}
;	if( $regname ne g_allocatedname[$reg] )
;	{
;		; Error freeing $reg
;		mov compileerror, freed unallocated register $regname
;	}

	if( ( $reg =~ m/r(.*)/ ) )
	{
		$g_allocated[$1] = 0;
	}
	$reg = "rERROR_DEALLOCATED";
	&OutputUsedRegisters();
}

sub CheckUnfreedRegisters()
{
	local( $i );
	for( $i = 0; $i < $g_NumRegisters; $i++ )
	{
		if( $g_allocated[$i] )
		{
			print "ERROR: r$i allocated to $g_allocatedname[$i] at end of program\n";
			$g_allocated[$i] = 0;
		}
	}
}

sub Normalize
{
	local( $r ) = shift;
	dp3 $r.w, $r, $r
	rsq $r.w, $r.w
	mul $r, $r, $r.w
}

sub Cross
{
	local( $result ) = shift;
	local( $a ) = shift;
	local( $b ) = shift;

	mul $result.xyz, $a.yzx, $b.zxy
	mad $result.xyz, -$b.yzx, $a.zxy, $result
}

sub RangeFog
{
	local( $projPos ) = shift;
	
	;------------------------------
	; Regular range fog
	;------------------------------

	; oFog.x = 1.0f = no fog
	; oFog.x = 0.0f = full fog
	; compute fog factor f = (fog_end - dist)*(1/(fog_end-fog_start))
	; this is == to: (fog_end/(fog_end-fog_start) - dist/(fog_end-fog_start)
	; which can be expressed with a single mad instruction!

	; Compute |projPos|
	local( $tmp );
	&AllocateRegister( \$tmp );
	dp3 $tmp.x, $projPos.xyw, $projPos.xyw
	rsq $tmp.x, $tmp.x
	rcp $tmp.x, $tmp.x

	if( $g_dx9 )
	{
		mad $tmp, -$tmp.x, $cOOFogRange, $cFogEndOverFogRange
		min $tmp, $tmp, $cOne
		max oFog, $tmp.x, $cFogMaxDensity
	}
	else
	{
		mad $tmp, -$tmp.x, $cOOFogRange, $cFogEndOverFogRange
		min $tmp, $tmp, $cOne
		max oFog.x, $tmp.x, $cFogMaxDensity
	}
	&FreeRegister( \$tmp );
}

sub DepthFog
{
	local( $projPos ) = shift;
	local( $dest ) = shift;

	if ( $dest eq "" )
	{
		$dest = "oFog";
	}

	;------------------------------
	; Regular range fog
	;------------------------------

	; oFog.x = 1.0f = no fog
	; oFog.x = 0.0f = full fog
	; compute fog factor f = (fog_end - dist)*(1/(fog_end-fog_start))
	; this is == to: (fog_end/(fog_end-fog_start) - dist/(fog_end-fog_start)
	; which can be expressed with a single mad instruction!

	; Compute |projPos|
	local( $tmp );
	&AllocateRegister( \$tmp );

	if( $g_dx9 )
	{
		mad $tmp, -$projPos.w, $cOOFogRange, $cFogEndOverFogRange
		min $tmp, $tmp, $cOne
		max $dest, $tmp.x, $cFogMaxDensity
	}
	else
	{
		mad $tmp, -$projPos.w, $cOOFogRange, $cFogEndOverFogRange
		min $tmp, $tmp, $cOne
		max $dest.x, $tmp.x, $cFogMaxDensity
	}

	&FreeRegister( \$tmp );
}

sub WaterRangeFog
{
	; oFog.x = 1.0f = no fog
	; oFog.x = 0.0f = full fog

	; only $worldPos.z is used out of worldPos
	local( $worldPos ) = shift;
	local( $projPos ) = shift;
	
	local( $tmp );
	&AllocateRegister( \$tmp );

	; This is simple similar triangles. Imagine a line passing from the point directly vertically
	; and another line passing from the point to the eye position.
	; Let d = total distance from point to the eye
	; Let h = vertical distance from the point to the eye
	; Let hw = vertical distance from the point to the water surface
	; Let dw = distance from the point to a point on the water surface that lies along the ray from point to eye
	; Therefore d/h = dw/hw by similar triangles, or dw = d * hw / h.
	; d = |projPos|, h = eyepos.z - worldPos.z, hw = waterheight.z - worldPos.z, dw = what we solve for

	; Now, tmp.x = hw, and tmp.y = h
	add $tmp.xy, $cEyePosWaterZ.wz, -$worldPos.z

	; if $tmp.x < 0, then set it to 0
	; This is the equivalent of moving the vert to the water surface if it's above the water surface
	max $tmp.x, $tmp.x, $cZero

	; Compute 1 / |projPos| = 1/d
	dp3 $tmp.z, $projPos.xyw, $projPos.xyw
	rsq $tmp.z, $tmp.z

	; Now we have h/d
	mul $tmp.z, $tmp.z, $tmp.y

	; Now we have d/h
	rcp $tmp.w, $tmp.z
	
	; We finally have d * hw / h
	; $tmp.w is now the distance that we see through water.
	mul $tmp.w, $tmp.x, $tmp.w

	if( $g_dx9 )
	{
		mad $tmp, -$tmp.w, $cOOFogRange, $cFogOne
		min $tmp, $tmp, $cOne
		max oFog, $tmp.x, $cFogMaxDensity
	}
	else
	{
		mad $tmp, -$tmp.w, $cOOFogRange, $cFogOne
		min $tmp, $tmp, $cOne
		max oFog.x, $tmp.x, $cFogMaxDensity
	}

	&FreeRegister( \$tmp );
}

sub WaterDepthFog
{
	; oFog.x = 1.0f = no fog
	; oFog.x = 0.0f = full fog

	; only $worldPos.z is used out of worldPos
	local( $worldPos ) = shift;
	local( $projPos ) = shift;
	local( $dest ) = shift;
	
	if ( $dest eq "" )
	{
		$dest = "oFog";
	}
	
	local( $tmp );
	&AllocateRegister( \$tmp );

	; This is simple similar triangles. Imagine a line passing from the point directly vertically
	; and another line passing from the point to the eye position.
	; Let d = total distance from point to the eye
	; Let h = vertical distance from the point to the eye
	; Let hw = vertical distance from the point to the water surface
	; Let dw = distance from the point to a point on the water surface that lies along the ray from point to eye
	; Therefore d/h = dw/hw by similar triangles, or dw = d * hw / h.
	; d = projPos.w, h = eyepos.z - worldPos.z, hw = waterheight.z - worldPos.z, dw = what we solve for

	; Now, tmp.x = hw, and tmp.y = h
	add $tmp.xy, $cEyePosWaterZ.wz, -$worldPos.z

	; if $tmp.x < 0, then set it to 0
	; This is the equivalent of moving the vert to the water surface if it's above the water surface
	max $tmp.x, $tmp.x, $cZero

	; Now we have 1/h
	rcp $tmp.z, $tmp.y

	; Now we have d/h
	mul $tmp.w, $projPos.w, $tmp.z

	; We finally have d * hw / h
	; $tmp.w is now the distance that we see through water.
	mul $tmp.w, $tmp.x, $tmp.w

	if( $g_dx9 )
	{
		mad $tmp, -$tmp.w, $cOOFogRange, $cFogOne
		min $tmp, $tmp, $cOne
		max $dest, $tmp.x, $cZero
	}
	else
	{
		mad $tmp, -$tmp.w, $cOOFogRange, $cFogOne
		min $tmp, $tmp, $cOne
		max $dest.x, $tmp.x, $cZero
	}

	&FreeRegister( \$tmp );
}


;------------------------------------------------------------------------------
; Main fogging routine
;------------------------------------------------------------------------------
sub CalcFog
{
	if( !defined $DOWATERFOG )
	{
		die "CalcFog called without using \$DOWATERFOG\n";
	}
	my $fogType;
	if( $DOWATERFOG == 0 )
	{
		$fogType = "rangefog";		
	}
	else
	{
		$fogType = "heightfog";
	}

#	print "\$fogType = $fogType\n";

	; CalcFog
	local( $worldPos ) = shift;
	local( $projPos ) = shift;
	local( $dest ) = shift;

	if ( $dest eq "" )
	{
		$dest = "oFog";
	}

	if( $fogType eq "rangefog" )
	{
		&DepthFog( $projPos, $dest );
	}
	elsif( $fogType eq "heightfog" )
	{
		&WaterDepthFog( $worldPos, $projPos, $dest );
	}
	else
	{
		die;
	}	
}

sub CalcRangeFog
{
	; CalcFog
	local( $worldPos ) = shift;
	local( $projPos ) = shift;

	if( $DOWATERFOG == 0 )
	{
		&RangeFog( $projPos );
	}
	elsif( $DOWATERFOG == 1 )
	{
		&WaterRangeFog( $worldPos, $projPos );
	}
	else
	{
		die;
	}	
}

sub GammaToLinear
{
	local( $gamma ) = shift;
	local( $linear ) = shift;

	local( $tmp );
	&AllocateRegister( \$tmp );

	; Is rcp more expensive than just storing 2.2 somewhere and doing a mov?
	rcp $gamma.w, $cOOGamma							; $gamma.w = 2.2
	lit $linear.z, $gamma.zzzw						; r0.z = linear blue
	lit $tmp.z, $gamma.yyyw							; r2.z = linear green
	mov $linear.y, $tmp.z							; r0.y = linear green
	lit $tmp.z, $gamma.xxxw							; r2.z = linear red
	mov $linear.x, $tmp.z							; r0.x = linear red

	&FreeRegister( \$tmp );
}

sub LinearToGamma
{
	local( $linear ) = shift;
	local( $gamma ) = shift;

	local( $tmp );
	&AllocateRegister( \$tmp );

	mov $linear.w, $cOOGamma						; $linear.w = 1.0/2.2
	lit $gamma.z, $linear.zzzw						; r0.z = gamma blue
	lit $tmp.z, $linear.yyyw						; r2.z = gamma green
	mov $gamma.y, $tmp.z							; r0.y = gamma green
	lit $tmp.z, $linear.xxxw						; r2.z = gamma red
	mov $gamma.x, $tmp.z							; r0.x = gamma red

	&FreeRegister( \$tmp );
}

sub ComputeReflectionVector
{
	local( $worldPos ) = shift;
	local( $worldNormal ) = shift;
	local( $reflectionVector ) = shift;

	local( $vertToEye ); &AllocateRegister( \$vertToEye );
	local( $tmp ); &AllocateRegister( \$tmp );

	; compute reflection vector r = 2 * (n dot v) n - v
	sub $vertToEye.xyz, $cEyePos.xyz, $worldPos  ; $tmp1 = v = c - p
	dp3 $tmp, $worldNormal, $vertToEye			; $tmp = n dot v
	mul $tmp.xyz, $tmp.xyz, $worldNormal	; $tmp = (n dot v ) n
	mad $reflectionVector.xyz, $tmp, $cTwo, -$vertToEye

	&FreeRegister( \$vertToEye );
	&FreeRegister( \$tmp );
}

sub ComputeSphereMapTexCoords
{
	local( $reflectionVector ) = shift;
	local( $sphereMapTexCoords ) = shift;

	local( $tmp ); &AllocateRegister( \$tmp );

	; transform reflection vector into view space
	dp3 $tmp.x, $reflectionVector, $cViewModel0
	dp3 $tmp.y, $reflectionVector, $cViewModel1
	dp3 $tmp.z, $reflectionVector, $cViewModel2

	; generate <rx ry rz+1>
	add $tmp.z, $tmp.z, $cOne

	; find 1 / the length of r2
	dp3 $tmp.w, $tmp, $tmp
	rsq $tmp.w, $tmp.w

	; r1 = r2/|r2| + 1
	mad $tmp.xy, $tmp.w, $tmp, $cOne
	mul $sphereMapTexCoords.xy, $tmp.xy, $cHalf
	
	&FreeRegister( \$tmp );
}

sub SkinPosition
{
#	print "\$SKINNING = $SKINNING\n";
	local( $worldPos ) = shift;
	
	if( !defined $SKINNING )
	{
		die "using \$SKINNING without defining.\n";
	}
		
	if( $SKINNING == 0 )
	{
		;
		; 0 bone skinning (4 instructions)
		;
		; Transform position into world space
		; position
		dp4 $worldPos.x, $vPos, $cModel0
		dp4 $worldPos.y, $vPos, $cModel1
		dp4 $worldPos.z, $vPos, $cModel2
		mov $worldPos.w, $cOne
	} 
	else
	{
		;
		; 3 bone skinning  (19 instructions)
		;
		local( $boneIndices );
		local( $blendedMatrix0 );
		local( $blendedMatrix1 );
		local( $blendedMatrix2 );
		local( $localPos );
		&AllocateRegister( \$boneIndices );
		&AllocateRegister( \$blendedMatrix0 );
		&AllocateRegister( \$blendedMatrix1 );
		&AllocateRegister( \$blendedMatrix2 );

		; Transform position into world space using all bones
		; denormalize d3dcolor to matrix index
		mad $boneIndices, $vBoneIndices, $cColorToIntScale, $cModel0Index
		if ( $g_x360 )
		{
			mov $boneIndices, $boneIndices.zyxw
		}
		
		; r11 = boneindices at this point
		; first matrix
		mov a0.x, $boneIndices.z
		mul $blendedMatrix0, $vBoneWeights.x, c[a0.x]
		mul $blendedMatrix1, $vBoneWeights.x, c[a0.x+1]
		mul $blendedMatrix2, $vBoneWeights.x, c[a0.x+2]
		; second matrix
		mov a0.x, $boneIndices.y
		mad $blendedMatrix0, $vBoneWeights.y, c[a0.x], $blendedMatrix0
		mad $blendedMatrix1, $vBoneWeights.y, c[a0.x+1], $blendedMatrix1
		mad $blendedMatrix2, $vBoneWeights.y, c[a0.x+2], $blendedMatrix2

		; Calculate third weight
		; compute 1-(weight1+weight2) to calculate weight2
		; Use $boneIndices.w as a temp since we aren't using it for anything.
		add $boneIndices.w, $vBoneWeights.x, $vBoneWeights.y
		sub $boneIndices.w, $cOne, $boneIndices.w

		; third matrix
		mov a0.x, $boneIndices.x
		mad $blendedMatrix0, $boneIndices.w, c[a0.x], $blendedMatrix0
		mad $blendedMatrix1, $boneIndices.w, c[a0.x+1], $blendedMatrix1
		mad $blendedMatrix2, $boneIndices.w, c[a0.x+2], $blendedMatrix2
		
		dp4 $worldPos.x, $vPos, $blendedMatrix0
		dp4 $worldPos.y, $vPos, $blendedMatrix1
		dp4 $worldPos.z, $vPos, $blendedMatrix2
		mov $worldPos.w, $cOne

		&FreeRegister( \$boneIndices );
		&FreeRegister( \$blendedMatrix0 );
		&FreeRegister( \$blendedMatrix1 );
		&FreeRegister( \$blendedMatrix2 );
	}
}


sub SkinPositionAndNormal
{
#	print "\$SKINNING = $SKINNING\n";
	local( $worldPos ) = shift;
	local( $worldNormal ) = shift;

	if( !defined $SKINNING )
	{
		die "using \$SKINNING without defining.\n";
	}

	if( $SKINNING == 0 )
	{
		;
		; 0 bone skinning (13 instructions)
		;
		; Transform position + normal + tangentS + tangentT into world space
		; position
		dp4 $worldPos.x, $vPos, $cModel0
		dp4 $worldPos.y, $vPos, $cModel1
		dp4 $worldPos.z, $vPos, $cModel2
		mov $worldPos.w, $cOne
		; normal
		dp3 $worldNormal.x, $vNormal, $cModel0
		dp3 $worldNormal.y, $vNormal, $cModel1
		dp3 $worldNormal.z, $vNormal, $cModel2
	}
	else
	{
		local( $boneIndices );
		local( $blendedMatrix0 );
		local( $blendedMatrix1 );
		local( $blendedMatrix2 );
		local( $localPos );
		local( $localNormal );
		local( $normalLength );
		local( $ooNormalLength );
		&AllocateRegister( \$boneIndices );
		&AllocateRegister( \$blendedMatrix0 );
		&AllocateRegister( \$blendedMatrix1 );
		&AllocateRegister( \$blendedMatrix2 );

		; Transform position into world space using all bones
		; denormalize d3dcolor to matrix index
		mad $boneIndices, $vBoneIndices, $cColorToIntScale, $cModel0Index
		if ( $g_x360 )
		{
			mov $boneIndices, $boneIndices.zyxw
		}

		; r11 = boneindices at this point
		; first matrix
		mov a0.x, $boneIndices.z
		mul $blendedMatrix0, $vBoneWeights.x, c[a0.x]
		mul $blendedMatrix1, $vBoneWeights.x, c[a0.x+1]
		mul $blendedMatrix2, $vBoneWeights.x, c[a0.x+2]
		; second matrix
		mov a0.x, $boneIndices.y
		mad $blendedMatrix0, $vBoneWeights.y, c[a0.x], $blendedMatrix0
		mad $blendedMatrix1, $vBoneWeights.y, c[a0.x+1], $blendedMatrix1
		mad $blendedMatrix2, $vBoneWeights.y, c[a0.x+2], $blendedMatrix2

		; Calculate third weight
		; compute 1-(weight1+weight2) to calculate weight2
		; Use $boneIndices.w as a temp since we aren't using it for anything.
		add $boneIndices.w, $vBoneWeights.x, $vBoneWeights.y
		sub $boneIndices.w, $cOne, $boneIndices.w

		; third matrix
		mov a0.x, $boneIndices.x
		mad $blendedMatrix0, $boneIndices.w, c[a0.x], $blendedMatrix0
		mad $blendedMatrix1, $boneIndices.w, c[a0.x+1], $blendedMatrix1
		mad $blendedMatrix2, $boneIndices.w, c[a0.x+2], $blendedMatrix2
		
		dp4 $worldPos.x, $vPos, $blendedMatrix0
		dp4 $worldPos.y, $vPos, $blendedMatrix1
		dp4 $worldPos.z, $vPos, $blendedMatrix2
		mov $worldPos.w, $cOne

		; normal
		dp3 $worldNormal.x, $vNormal, $blendedMatrix0
		dp3 $worldNormal.y, $vNormal, $blendedMatrix1
		dp3 $worldNormal.z, $vNormal, $blendedMatrix2

		&FreeRegister( \$boneIndices );
		&FreeRegister( \$blendedMatrix0 );
		&FreeRegister( \$blendedMatrix1 );
		&FreeRegister( \$blendedMatrix2 );
	}	
}

sub SkinPositionNormalAndTangentSpace
{
#	print "\$SKINNING = $SKINNING\n";
	local( $worldPos ) = shift;
	local( $worldNormal ) = shift;
	local( $worldTangentS ) = shift;
	local( $worldTangentT ) = shift;
	local( $userData );
	local( $localPos );
	local( $localNormal );
	local( $normalLength );
	local( $ooNormalLength );
	
	if( !defined $SKINNING )
	{
		die "using \$SKINNING without defining.\n";
	}

# X360TBD: needed for compressed vertex format
#	if ( $g_x360 )
#	{
#		&AllocateRegister( \$userData );
#		; remap compressed range [0..1] to [-1..1]
#		mad $userData, $vUserData, $cTwo, -$cOne
#	}

	if( $SKINNING == 0 )
	{
		;
		; 0 bone skinning (13 instructions)
		;
		; Transform position + normal + tangentS + tangentT into world space
		dp4 $worldPos.x, $vPos, $cModel0
		dp4 $worldPos.y, $vPos, $cModel1
		dp4 $worldPos.z, $vPos, $cModel2
		mov $worldPos.w, $cOne

		; normal
		dp3 $worldNormal.x, $vNormal, $cModel0
		dp3 $worldNormal.y, $vNormal, $cModel1
		dp3 $worldNormal.z, $vNormal, $cModel2

# X360TBD: needed for compressed vertex format
#		if ( $g_x360 )
#		{
#			; tangents
#			dp3 $worldTangentS.x, $userData, $cModel0
#			dp3 $worldTangentS.y, $userData, $cModel1
#			dp3 $worldTangentS.z, $userData, $cModel2
#
#			; calculate tangent t via cross( N, S ) * S[3]
#			&Cross( $worldTangentT, $worldNormal, $worldTangentS );
#			mul $worldTangentT.xyz, $userData.w, $worldTangentT.xyz
#		}
#		else
		{
			; tangents
			dp3 $worldTangentS.x, $vUserData, $cModel0
			dp3 $worldTangentS.y, $vUserData, $cModel1
			dp3 $worldTangentS.z, $vUserData, $cModel2

			; calculate tangent t via cross( N, S ) * S[3]
			&Cross( $worldTangentT, $worldNormal, $worldTangentS );
			mul $worldTangentT.xyz, $vUserData.w, $worldTangentT.xyz
		}
	}
	else
	{
		local( $boneIndices );
		local( $blendedMatrix0 );
		local( $blendedMatrix1 );
		local( $blendedMatrix2 );
		&AllocateRegister( \$boneIndices );
		&AllocateRegister( \$blendedMatrix0 );
		&AllocateRegister( \$blendedMatrix1 );
		&AllocateRegister( \$blendedMatrix2 );

		; Transform position into world space using all bones
		; denormalize d3dcolor to matrix index
		mad $boneIndices, $vBoneIndices, $cColorToIntScale, $cModel0Index
		if ( $g_x360 )
		{
			mov $boneIndices, $boneIndices.zyxw
		}

		; r11 = boneindices at this point
		; first matrix
		mov a0.x, $boneIndices.z
		mul $blendedMatrix0, $vBoneWeights.x, c[a0.x]
		mul $blendedMatrix1, $vBoneWeights.x, c[a0.x+1]
		mul $blendedMatrix2, $vBoneWeights.x, c[a0.x+2]
		; second matrix
		mov a0.x, $boneIndices.y
		mad $blendedMatrix0, $vBoneWeights.y, c[a0.x], $blendedMatrix0
		mad $blendedMatrix1, $vBoneWeights.y, c[a0.x+1], $blendedMatrix1
		mad $blendedMatrix2, $vBoneWeights.y, c[a0.x+2], $blendedMatrix2

		; Calculate third weight
		; compute 1-(weight1+weight2) to calculate weight2
		; Use $boneIndices.w as a temp since we aren't using it for anything.
		add $boneIndices.w, $vBoneWeights.x, $vBoneWeights.y
		sub $boneIndices.w, $cOne, $boneIndices.w

		; third matrix
		mov a0.x, $boneIndices.x
		mad $blendedMatrix0, $boneIndices.w, c[a0.x], $blendedMatrix0
		mad $blendedMatrix1, $boneIndices.w, c[a0.x+1], $blendedMatrix1
		mad $blendedMatrix2, $boneIndices.w, c[a0.x+2], $blendedMatrix2
		
		; position
		dp4 $worldPos.x, $vPos, $blendedMatrix0
		dp4 $worldPos.y, $vPos, $blendedMatrix1
		dp4 $worldPos.z, $vPos, $blendedMatrix2
		mov $worldPos.w, $cOne

		; normal
		dp3 $worldNormal.x, $vNormal, $blendedMatrix0
		dp3 $worldNormal.y, $vNormal, $blendedMatrix1
		dp3 $worldNormal.z, $vNormal, $blendedMatrix2

# X360TBD: needed for compressed vertex format
#		if ( $g_x360 )
#		{
#			; tangents
#			dp3 $worldTangentS.x, $userData, $blendedMatrix0
#			dp3 $worldTangentS.y, $userData, $blendedMatrix1
#			dp3 $worldTangentS.z, $userData, $blendedMatrix2
#
#			; calculate tangent t via cross( N, S ) * S[3]
#			&Cross( $worldTangentT, $worldNormal, $worldTangentS );
#			mul $worldTangentT.xyz, $userData.w, $worldTangentT.xyz
#		}
#		else
		{
			; tangents
			dp3 $worldTangentS.x, $vUserData, $blendedMatrix0
			dp3 $worldTangentS.y, $vUserData, $blendedMatrix1
			dp3 $worldTangentS.z, $vUserData, $blendedMatrix2

			; calculate tangent t via cross( N, S ) * S[3]
			&Cross( $worldTangentT, $worldNormal, $worldTangentS );
			mul $worldTangentT.xyz, $vUserData.w, $worldTangentT.xyz
		}

		&FreeRegister( \$boneIndices );
		&FreeRegister( \$blendedMatrix0 );
		&FreeRegister( \$blendedMatrix1 );
		&FreeRegister( \$blendedMatrix2 );
	}

# X360TBD: needed for compressed vertex format
#	if ( $g_x360 )
#	{
#		&FreeRegister( \$userData );
#	}
}

sub ColorClamp
{
	; ColorClamp; stomps $color.w
	local( $color ) = shift;
	local( $dst ) = shift;

	; Get the max of RGB and stick it in W
	max $color.w, $color.x, $color.y
	max $color.w, $color.w, $color.z

	; get the greater of one and the max color.
	max $color.w, $color.w, $cOne

	rcp $color.w, $color.w
	mul $dst.xyz, $color.w, $color.xyz
}

sub AmbientLight
{
	local( $worldNormal ) = shift;
	local( $linearColor ) = shift;
	local( $add ) = shift;

	; Ambient lighting
	&AllocateRegister( \$nSquared );
	&AllocateRegister( \$isNegative );

	mul $nSquared.xyz, $worldNormal.xyz, $worldNormal.xyz				; compute n times n
	slt $isNegative.xyz, $worldNormal.xyz, $cZero				; Figure out whether each component is >0
	mov a0.x, $isNegative.x
	if( $add )
	{
		mad $linearColor.xyz, $nSquared.x, c[a0.x + $cAmbientColorPosXOffset], $linearColor			; $linearColor = normal[0]*normal[0] * box color of appropriate x side
	}
	else
	{
		mul $linearColor.xyz, $nSquared.x, c[a0.x + $cAmbientColorPosXOffset]			; $linearColor = normal[0]*normal[0] * box color of appropriate x side
	}
	mov a0.x, $isNegative.y
	mad $linearColor.xyz, $nSquared.y, c[a0.x + $cAmbientColorPosYOffset], $linearColor
	mov a0.x, $isNegative.z
	mad $linearColor.xyz, $nSquared.z, c[a0.x + $cAmbientColorPosZOffset], $linearColor

	&FreeRegister( \$isNegative );
	&FreeRegister( \$nSquared );
}

sub DirectionalLight
{
	local( $worldNormal ) = shift;
	local( $linearColor ) = shift;
	local( $add ) = shift;

	&AllocateRegister( \$nDotL ); # FIXME: This only needs to be a scalar

	; NOTE: Gotta use -l here, since light direction = -l
	; DIRECTIONAL LIGHT
	; compute n dot l
	dp3 $nDotL.x, -c[a0.x + 1], $worldNormal
	
	if ( $HALF_LAMBERT == 0 )
	{
		; lambert
		max $nDotL.x, $nDotL.x, c0.x				; Clamp to zero
	}
	elsif ( $HALF_LAMBERT == 1 )
	{
		; half-lambert
		mad $nDotL.x, $nDotL.x, $cHalf, $cHalf		; dot = (dot * 0.5 + 0.5)^2
		mul $nDotL.x, $nDotL.x, $nDotL.x
	}
	else
	{
		die "\$HALF_LAMBERT is hosed\n";
	}
  
	if( $add )
	{
		mad $linearColor.xyz, c[a0.x], $nDotL.x, $linearColor
	}
	else
	{
		mul $linearColor.xyz, c[a0.x], $nDotL.x
	}

	&FreeRegister( \$nDotL );
}

sub PointLight
{
	local( $worldPos ) = shift;
	local( $worldNormal ) = shift;
	local( $linearColor ) = shift;
	local( $add ) = shift;

	local( $lightDir );
	&AllocateRegister( \$lightDir );
	
	; POINT LIGHT
	; compute light direction
	sub $lightDir, c[a0.x+2], $worldPos
	
	local( $lightDistSquared );
	local( $ooLightDist );
	&AllocateRegister( \$lightDistSquared );
	&AllocateRegister( \$ooLightDist );

	; normalize light direction, maintain temporaries for attenuation
	dp3 $lightDistSquared, $lightDir, $lightDir
	rsq $ooLightDist, $lightDistSquared.x
	mul $lightDir, $lightDir, $ooLightDist.x
	
	local( $attenuationFactors );
	&AllocateRegister( \$attenuationFactors );

	; compute attenuation amount (r2 = 'd*d d*d d*d d*d', r3 = '1/d 1/d 1/d 1/d')
	dst $attenuationFactors, $lightDistSquared, $ooLightDist						; r4 = ( 1, d, d*d, 1/d )
	&FreeRegister( \$lightDistSquared );
	&FreeRegister( \$ooLightDist );
	local( $attenuation );
	&AllocateRegister( \$attenuation );
	dp3 $attenuation, $attenuationFactors, c[a0.x+4]				; r3 = atten0 + d * atten1 + d*d * atten2

	rcp $lightDir.w, $attenuation						; $lightDir.w = 1 / (atten0 + d * atten1 + d*d * atten2)

	&FreeRegister( \$attenuationFactors );
	&FreeRegister( \$attenuation );
	
	local( $tmp );
	&AllocateRegister( \$tmp ); # FIXME : really only needs to be a scalar

	; compute n dot l, fold in distance attenutation
	dp3 $tmp.x, $lightDir, $worldNormal

	if ( $HALF_LAMBERT == 0 )
	{
		; lambert
		max $tmp.x, $tmp.x, c0.x				; Clamp to zero
	}
	elsif ( $HALF_LAMBERT == 1 )
	{
		; half-lambert
		mad $tmp.x, $tmp.x, $cHalf, $cHalf		; dot = (dot * 0.5 + 0.5)^2
		mul $tmp.x, $tmp.x, $tmp.x
	}
	else
	{
		die "\$HALF_LAMBERT is hosed\n";
	}
	
	mul $tmp.x, $tmp.x, $lightDir.w
	if( $add )
	{
		mad $linearColor.xyz, c[a0.x], $tmp.x, $linearColor
	}
	else
	{
		mul $linearColor.xyz, c[a0.x], $tmp.x
	}

	&FreeRegister( \$lightDir );
	&FreeRegister( \$tmp ); # FIXME : really only needs to be a scalar
}

sub SpotLight
{
	local( $worldPos ) = shift;
	local( $worldNormal ) = shift;
	local( $linearColor ) = shift;
	local( $add ) = shift;
	
	local( $lightDir );
	&AllocateRegister( \$lightDir );

	; SPOTLIGHT
	; compute light direction
	sub $lightDir, c[a0.x+2], $worldPos
	
	local( $lightDistSquared );
	local( $ooLightDist );
	&AllocateRegister( \$lightDistSquared );
	&AllocateRegister( \$ooLightDist );

	; normalize light direction, maintain temporaries for attenuation
	dp3 $lightDistSquared, $lightDir, $lightDir
	rsq $ooLightDist, $lightDistSquared.x
	mul $lightDir, $lightDir, $ooLightDist.x
	
	local( $attenuationFactors );
	&AllocateRegister( \$attenuationFactors );

	; compute attenuation amount (r2 = 'd*d d*d d*d d*d', r3 = '1/d 1/d 1/d 1/d')
	dst $attenuationFactors, $lightDistSquared, $ooLightDist						; r4 = ( 1, d, d*d, 1/d )

	&FreeRegister( \$lightDistSquared );
	&FreeRegister( \$ooLightDist );
	local( $attenuation );	&AllocateRegister( \$attenuation );

	dp3 $attenuation, $attenuationFactors, c[a0.x+4]				; r3 = atten0 + d * atten1 + d*d * atten2
	rcp $lightDir.w, $attenuation						; r1.w = 1 / (atten0 + d * atten1 + d*d * atten2)

	&FreeRegister( \$attenuationFactors );
	&FreeRegister( \$attenuation );
	
	local( $litSrc ); &AllocateRegister( \$litSrc );
	local( $tmp ); &AllocateRegister( \$tmp ); # FIXME - only needs to be scalar

	; compute n dot l
	dp3 $litSrc.x, $worldNormal, $lightDir
	
	if ( $HALF_LAMBERT == 0 )
	{
		; lambert
		max $litSrc.x, $litSrc.x, c0.x				; Clamp to zero
	}
	elsif ( $HALF_LAMBERT == 1 )
	{
		; half-lambert
		mad $litSrc.x, $litSrc.x, $cHalf, $cHalf	; dot = (dot * 0.5 + 0.5) ^ 2
		mul $litSrc.x, $litSrc.x, $litSrc.x
	}
	else
	{
		die "\$HALF_LAMBERT is hosed\n";
	}

	; compute angular attenuation
	dp3 $tmp.x, c[a0.x+1], -$lightDir				; dot = -delta * spot direction
	sub $litSrc.y, $tmp.x, c[a0.x+3].z				; r2.y = dot - stopdot2
	&FreeRegister( \$tmp );
	mul $litSrc.y, $litSrc.y, c[a0.x+3].w			; r2.y = (dot - stopdot2) / (stopdot - stopdot2)
	mov $litSrc.w, c[a0.x+3].x						; r2.w = exponent
	local( $litDst ); &AllocateRegister( \$litDst );
	lit $litDst, $litSrc							; r3.y = N dot L or 0, whichever is bigger
	&FreeRegister( \$litSrc );
													; r3.z = pow((dot - stopdot2) / (stopdot - stopdot2), exponent)
	min $litDst.z, $litDst.z, $cOne		 			; clamp pow() to 1
	
	local( $tmp1 ); &AllocateRegister( \$tmp1 );
	local( $tmp2 ); &AllocateRegister( \$tmp2 );  # FIXME - could be scalar

	; fold in distance attenutation with other factors
	mul $tmp1, c[a0.x], $lightDir.w
	mul $tmp2.x, $litDst.y, $litDst.z
	if( $add )
	{
		mad $linearColor.xyz, $tmp1, $tmp2.x, $linearColor
	}
	else
	{
		mul $linearColor.xyz, $tmp1, $tmp2.x
	}

	&FreeRegister( \$lightDir );
	&FreeRegister( \$litDst );
	&FreeRegister( \$tmp1 );
	&FreeRegister( \$tmp2 );
}

sub DoLight
{
	local( $lightType ) = shift;
	local( $worldPos ) = shift;
	local( $worldNormal ) = shift;
	local( $linearColor ) = shift;
	local( $add ) = shift;

	if( $lightType eq "spot" )
	{
		&SpotLight( $worldPos, $worldNormal, $linearColor, $add );
	}
	elsif( $lightType eq "point" )
	{
		&PointLight( $worldPos, $worldNormal, $linearColor, $add );
	}
	elsif( $lightType eq "directional" )
	{
		&DirectionalLight( $worldNormal, $linearColor, $add );
	}
	else
	{
		die "don't know about light type \"$lightType\"\n";
	}
}

sub DoLighting
{
	if( !defined $LIGHT_COMBO )
	{
		die "DoLighting called without using \$LIGHT_COMBO\n";
	}
	if ( !defined $HALF_LAMBERT )
	{
		die "DoLighting called without using \$HALF_LAMBERT\n";
	}

	my $staticLightType = $g_staticLightTypeArray[$LIGHT_COMBO];
	my $ambientLightType = $g_ambientLightTypeArray[$LIGHT_COMBO];
	my $localLightType1 = $g_localLightType1Array[$LIGHT_COMBO];
	my $localLightType2 = $g_localLightType2Array[$LIGHT_COMBO];

#	print "\$staticLightType = $staticLightType\n";
#	print "\$ambientLightType = $ambientLightType\n";
#	print "\$localLightType1 = $localLightType1\n";
#	print "\$localLightType2 = $localLightType2\n";

	local( $worldPos ) = shift;
	local( $worldNormal ) = shift;

	; special case for no lighting
	if( $staticLightType eq "none" && $ambientLightType eq "none" &&
		$localLightType1 eq "none" && $localLightType2 eq "none" )
	{
		; Have to write something here since debug d3d runtime will barf otherwise.
		mov oD0, $cOne
		return;
	}

	; special case for static lighting only
	; Don't need to bother converting to linear space in this case.
	if( $staticLightType eq "static" && $ambientLightType eq "none" &&
		$localLightType1 eq "none" && $localLightType2 eq "none" )
	{
		mov oD0, $vSpecular
		return;
	}

	alloc $linearColor
	alloc $gammaColor

	local( $add ) = 0;
	if( $staticLightType eq "static" )
	{
		; The static lighting comes in in gamma space and has also been premultiplied by $cOverbrightFactor
		; need to get it into
		; linear space so that we can do adds.
		rcp $gammaColor.w, $cOverbrightFactor
		mul $gammaColor.xyz, $vSpecular, $gammaColor.w
		&GammaToLinear( $gammaColor, $linearColor );
		$add = 1;
	}

	if( $ambientLightType eq "ambient" )
	{
		&AmbientLight( $worldNormal, $linearColor, $add );
		$add = 1;
	}

	if( $localLightType1 ne "none" )
	{
		mov a0.x, $cLight0Offset
		&DoLight( $localLightType1, $worldPos, $worldNormal, $linearColor, $add );
		$add = 1;
	}

	if( $localLightType2 ne "none" )
	{
		mov a0.x, $cLight1Offset
		&DoLight( $localLightType2, $worldPos, $worldNormal, $linearColor, $add );
		$add = 1;
	}

	;------------------------------------------------------------------------------
	; Output color (gamma correction)
	;------------------------------------------------------------------------------

	&LinearToGamma( $linearColor, $gammaColor );
	if( 0 )
	{
		mul oD0.xyz, $gammaColor.xyz, $cOverbrightFactor
	}
	else
	{
		mul $gammaColor.xyz, $gammaColor.xyz, $cOverbrightFactor
		&ColorClamp( $gammaColor, "oD0" );
	}

;	mov oD0.xyz, $linearColor
	mov oD0.w, $cOne				; make sure all components are defined

	free $linearColor
	free $gammaColor
}

sub DoDynamicLightingToLinear
{
	local( $worldPos ) = shift;
	local( $worldNormal ) = shift;
	local( $linearColor ) = shift;

	if( !defined $LIGHT_COMBO )
	{
		die "DoLighting called without using \$LIGHT_COMBO\n";
	}
	if ( !defined $HALF_LAMBERT )
	{
		die "DoLighting called without using \$HALF_LAMBERT\n";
	}

	my $staticLightType = $g_staticLightTypeArray[$LIGHT_COMBO];
	my $ambientLightType = $g_ambientLightTypeArray[$LIGHT_COMBO];
	my $localLightType1 = $g_localLightType1Array[$LIGHT_COMBO];
	my $localLightType2 = $g_localLightType2Array[$LIGHT_COMBO];

	# No lights at all. . note that we don't even consider static lighting here.
	if( $ambientLightType eq "none" &&
		$localLightType1 eq "none" && $localLightType2 eq "none" )
	{
		mov $linearColor, $cZero
		return;
	}

	local( $add ) = 0;
	if( $ambientLightType eq "ambient" )
	{
		&AmbientLight( $worldNormal, $linearColor, $add );
		$add = 1;
	}

	if( $localLightType1 ne "none" )
	{
		mov a0.x, $cLight0Offset
		&DoLight( $localLightType1, $worldPos, $worldNormal, $linearColor, $add );
		$add = 1;
	}

	if( $localLightType2 ne "none" )
	{
		mov a0.x, $cLight1Offset
		&DoLight( $localLightType2, $worldPos, $worldNormal, $linearColor, $add );
		$add = 1;
	}
}

sub NotImplementedYet
{
	&AllocateRegister( \$projPos );
	dp4 $projPos.x, $worldPos, $cViewProj0
	dp4 $projPos.y, $worldPos, $cViewProj1
	dp4 $projPos.z, $worldPos, $cViewProj2
	dp4 $projPos.w, $worldPos, $cViewProj3
	mov oPos, $projPos
	&FreeRegister( \$projPos );
	exit;
}
