/// <summary>
				/// 
				/// </summary>
				/// <param name="prof"></param>
				/// <param name="terrain"></param>
				/// <param name="tt"></param>
				/// <param name="source"></param>
				protected override void GenerateVpHeader( SM2Profile prof, Terrain terrain, TechniqueType tt, ref string source )
				{
					source += "void main_vp(\n" + "float4 pos : POSITION,\n" + "float2 uv  : TEXCOORD0,\n";
					if ( tt != TechniqueType.RenderCompositeMap )
					{
						source += "float2 delta  : TEXCOORD1,\n"; // lodDelta, lodThreshold
					}

					source += "uniform float4x4 worldMatrix,\n" + "uniform float4x4 viewProjMatrix,\n" + "uniform float2   lodMorph,\n";
					// morph amount, morph LOD target

					// uv multipliers
					uint maxLayers = prof.GetMaxLayers( terrain );
					uint numLayers = Utility.Min( maxLayers, (uint)terrain.LayerCount );
					uint numUVMutipliers = ( numLayers/4 );
					if ( numLayers%4 != 0 )
					{
						++numUVMutipliers;
					}
					for ( uint i = 0; i < numUVMutipliers; ++i )
					{
						source += "uniform float4 uvMul" + i + ", \n";
					}


					source += "out float4 oPos : POSITION,\n" + "out float2 oUV : TEXCOORD0, \n" + "out float4 oPosObj : TEXCOORD1 \n";

					// layer UV's premultiplied, packed as xy/zw
					uint numUVSets = numLayers/2;
					if ( numLayers%2 != 0 )
					{
						++numUVSets;
					}
					uint texCoordSet = 2;
					if ( tt != TechniqueType.LowLod )
					{
						for ( uint i = 0; i < numUVSets; ++i )
						{
							source += ", out float4 oUV" + i + " : TEXCOORD" + texCoordSet++ + "\n";
						}
					}

					if ( prof.Parent.DebugLevel != 0 && tt != TechniqueType.RenderCompositeMap )
					{
						source += ", out float2 lodInfo : TEXCOORD" + texCoordSet++ + "\n";
					}

					source += ")\n" + "{\n" + "	float4 worldPos = mul(worldMatrix, pos);\n" + "	oPosObj = pos;\n";

					if ( tt != TechniqueType.RenderCompositeMap )
					{
						// determine whether to apply the LOD morph to this vertex
						// we store the deltas against all vertices so we only want to apply 
						// the morph to the ones which would disappear. The target LOD which is
						// being morphed to is stored in lodMorph.y, and the LOD at which 
						// the vertex should be morphed is stored in uv.w. If we subtract
						// the former from the latter, and arrange to only morph if the
						// result is negative (it will only be -1 in fact, since after that
						// the vertex will never be indexed), we will achieve our aim.
						// sign(vertexLOD - targetLOD) == -1 is to morph
						source += "	float toMorph = -min(0, sign(delta.y - lodMorph.y));\n";
						// this will either be 1 (morph) or 0 (don't morph)
						if ( prof.Parent.DebugLevel != 0 )
						{
							// x == LOD level (-1 since value is target level, we want to display actual)
							source += "lodInfo.x = (lodMorph.y - 1) / " + terrain.NumLodLevels + ";\n";
							// y == LOD morph
							source += "lodInfo.y = toMorph * lodMorph.x;\n";
						}

						//morph
						switch ( terrain.Alignment )
						{
							case Alignment.Align_X_Y:
								break;
							case Alignment.Align_X_Z:
								source += "	worldPos.y += delta.x * toMorph * lodMorph.x;\n";
								break;
							case Alignment.Align_Y_Z:
								break;
						}
					}

					// generate UVs
					if ( tt == TechniqueType.LowLod )
					{
						//passtrough
						source += "	oUV = uv;\n";
					}
					else
					{
						for ( uint i = 0; i < numUVSets; ++i )
						{
							uint layer = i*2;
							uint uvMulIdx = layer/4;

							source += "	oUV" + i + ".xy = " + " uv.xy * uvMul" + uvMulIdx + "." + GetChannel( layer ) + ";\n";
							source += "	oUV" + i + ".zw = " + " uv.xy * uvMul" + uvMulIdx + "." + GetChannel( layer + 1 ) + ";\n";
						}
					}
				}
				/// <summary>
				/// 
				/// </summary>
				/// <param name="prof"></param>
				/// <param name="terrain"></param>
				/// <param name="tt"></param>
				/// <param name="source"></param>
				protected virtual void GenerateFragmetProgramSource( SM2Profile prof, Terrain terrain, TechniqueType tt,
				                                                     ref string source )
				{
					GenerateFpHeader( prof, terrain, tt, ref source );

					if ( tt != TechniqueType.LowLod )
					{
						uint maxLayers = prof.GetMaxLayers( terrain );
						uint numLayers = Utility.Min( maxLayers, (uint)terrain.LayerCount );

						for ( uint i = 0; i < numLayers; ++i )
						{
							GenerateFpLayer( prof, terrain, tt, i, ref source );
						}
					}

					GenerateFpFooter( prof, terrain, tt, ref source );
				}
				/// <summary>
				/// 
				/// </summary>
				/// <param name="prof"></param>
				/// <param name="terrain"></param>
				/// <param name="tt"></param>
				/// <param name="gpuparams"></param>
				public virtual void UpdateVpParams( SM2Profile prof, Terrain terrain, TechniqueType tt,
				                                    GpuProgramParameters gpuparams )
				{
					gpuparams.IgnoreMissingParameters = true;
					uint maxLayers = prof.GetMaxLayers( terrain );
					uint numLayers = Utility.Min( maxLayers, (uint)terrain.LayerCount );
					uint numUVMul = numLayers/4;
					if ( numUVMul%4 == 0 )
					{
						++numUVMul;
					}
					for ( uint i = 0; i < numUVMul; ++i )
					{
						var uvMul = new Vector4( terrain.GetLayerUVMultiplier( (byte)( i*4 ) ),
						                         terrain.GetLayerUVMultiplier( (byte)( i*4 + 1 ) ),
						                         terrain.GetLayerUVMultiplier( (byte)( i*4 + 2 ) ),
						                         terrain.GetLayerUVMultiplier( (byte)( i*4 + 3 ) ) );
#if true
						gpuparams.SetNamedConstant( "uvMul" + i.ToString(), uvMul );
#endif
					}
				}
				/// <summary>
				/// 
				/// </summary>
				/// <param name="prof"></param>
				/// <param name="terrain"></param>
				/// <param name="tt"></param>
				/// <param name="source"></param>
				protected override void GenerateFpHeader( SM2Profile prof, Terrain terrain, TechniqueType tt, ref string source )
				{
					// Main header
					source += // helpers
						"float4 expand(float4 v)\n" + "{ \n" + "	return v * 2 - 1;\n" + "}\n\n\n" + "float4 main_fp(\n" +
						"float2 uv : TEXCOORD0,\n" + "float4 position : TEXCOORD1,\n";

					// UV's premultiplied, packed as xy/zw
					uint maxLayers = prof.GetMaxLayers( terrain );
					uint numBlendTextures = Utility.Min( terrain.GetBlendTextureCount( (byte)maxLayers ),
					                                     terrain.GetBlendTextureCount() );
					uint numLayers = Utility.Min( maxLayers, (uint)terrain.LayerCount );
					uint numUVSets = numLayers/2;
					if ( numLayers%2 != 0 )
					{
						++numUVSets;
					}

					uint texCoordSet = 2;
					if ( tt != TechniqueType.LowLod )
					{
						for ( uint i = 0; i < numUVSets; ++i )
						{
							source += "float4 layerUV" + i + " : TEXCOORD" + texCoordSet++ + ", \n";
						}
					}
					if ( prof.Parent.DebugLevel != 0 && tt != TechniqueType.RenderCompositeMap )
					{
						source += "float2 lodInfo : TEXCOORD" + texCoordSet++ + ", \n";
					}

					source += // Only 1 light supported in this version
						// deferred shading profile / generator later, ok? :)
						"uniform float4 ambient,\n" + "uniform float4 lightPosObjSpace,\n" + "uniform float3 lightDiffuseColor,\n" +
						"uniform float3 lightSpecularColor,\n" + "uniform float3 eyePosObjSpace,\n" + // pack scale, bias and specular
						"uniform float4 scaleBiasSpecular,\n";

					if ( tt == TechniqueType.LowLod )
					{
						// single composite map covers all the others below
						source += "uniform sampler2D compositeMap : register(s0)\n";
					}
					else
					{
						source += "uniform sampler2D globalNormal : register(s0)\n";

						uint currentSamplerIdx = 1;
						if ( terrain.IsGlobalColorMapEnabled && prof.IsGlobalColorMapEnabled )
						{
							source += ", uniform sampler2D globalColorMap : register(s" + currentSamplerIdx++ + ")\n";
						}
						if ( prof.IsLightMapEnabled )
						{
							source += ", uniform sampler2D lightMap : register(s" + currentSamplerIdx++ + ")\n";
						}
						// Blend textures - sampler definitions
						for ( uint i = 0; i < numBlendTextures; ++i )
						{
							source += ", uniform sampler2D blendTex" + i + " : register(s" + currentSamplerIdx++ + ")\n";
						}

						// Layer textures - sampler definitions & UV multipliers
						for ( uint i = 0; i < numLayers; ++i )
						{
							source += ", uniform sampler2D difftex" + i + " : register(s" + currentSamplerIdx++ + ")\n";
							source += ", uniform sampler2D normtex" + i + " : register(s" + currentSamplerIdx++ + ")\n";
						}
					}

					source += ") : COLOR\n" + "{\n" + "	float4 outputCol;\n" + "	float shadow = 1.0;\n" + // base colour
					          "	outputCol = float4(0,0,0,1);\n";

					if ( tt != TechniqueType.LowLod )
					{
						source += // global normal
							"	float3 normal = expand(tex2D(globalNormal, uv)).rgb;\n";
					}

					source += "	float3 lightDir = \n" + "		lightPosObjSpace.xyz -  (position.xyz * lightPosObjSpace.w);\n" +
					          "	float3 eyeDir = eyePosObjSpace - position.xyz;\n" + // set up accumulation areas
					          "	float3 diffuse = float3(0,0,0);\n" + "	float specular = 0;\n";

					if ( tt == TechniqueType.LowLod )
					{
						// we just do a single calculation from composite map
						source += "	float4 composite = tex2D(compositeMap, uv);\n" + "	diffuse = composite.rgb;\n";
						// TODO - specular; we'll need normals for this!
					}
					else
					{
						// set up the blend values
						for ( uint i = 0; i < numBlendTextures; ++i )
						{
							source += "	float4 blendTexVal" + i + " = tex2D(blendTex" + i + ", uv);\n";
						}

						if ( prof.IsLayerNormalMappingEnabled )
						{
							// derive the tangent space basis
							// we do this in the pixel shader because we don't have per-vertex normals
							// because of the LOD, we use a normal map
							// tangent is always +x or -z in object space depending on alignment
							switch ( terrain.Alignment )
							{
								case Alignment.Align_X_Y:
								case Alignment.Align_X_Z:
									source += "	float3 tangent = float3(1, 0, 0);\n";
									break;
								case Alignment.Align_Y_Z:
									source += "	float3 tangent = float3(0, 0, -1);\n";
									break;
							}

							source += "	float3 binormal = normalize(cross(tangent, normal));\n";
							// note, now we need to re-cross to derive tangent again because it wasn't orthonormal
							source += "	tangent = normalize(cross(normal, binormal));\n";
							// derive final matrix
							source += "	float3x3 TBN = float3x3(tangent, binormal, normal);\n";

							// set up lighting result placeholders for interpolation
							source += "	float4 litRes, litResLayer;\n";
							source += "	float3 TSlightDir, TSeyeDir, TShalfAngle, TSnormal;\n";
							if ( prof.IsLayerParallaxMappingEnabled )
							{
								source += "	float displacement;\n";
							}
							// move 
							source += "	TSlightDir = normalize(mul(TBN, lightDir));\n";
							source += "	TSeyeDir = normalize(mul(TBN, eyeDir));\n";
						}
						else
						{
							// simple per-pixel lighting with no normal mapping
							source += "	lightDir = normalize(lightDir);\n";
							source += "	eyeDir = normalize(eyeDir);\n";
							source += "	float3 halfAngle = normalize(lightDir + eyeDir);\n";
							source += "	float4 litRes = lit(dot(lightDir, normal), dot(halfAngle, normal), scaleBiasSpecular.z);\n";
						}
					}
				}