Kaynağa Gözat

Merge hxsl3

Nicolas Cannasse 11 yıl önce
ebeveyn
işleme
ddb6107547
26 değiştirilmiş dosya ile 4979 ekleme ve 40 silme
  1. 3 10
      .gitignore
  2. 20 0
      LICENSE
  3. 6 30
      README.md
  4. 91 0
      doc/Render.hx
  5. 306 0
      doc/Shaders.hx
  6. 6 0
      hxsl.html
  7. 6 0
      hxsl.hxml
  8. 56 0
      hxsl.hxproj
  9. 396 0
      hxsl/Ast.hx
  10. 206 0
      hxsl/Cache.hx
  11. 841 0
      hxsl/Checker.hx
  12. 79 0
      hxsl/Clone.hx
  13. 361 0
      hxsl/Eval.hx
  14. 250 0
      hxsl/Flatten.hx
  15. 48 0
      hxsl/Globals.hx
  16. 317 0
      hxsl/GlslOut.hx
  17. 412 0
      hxsl/Linker.hx
  18. 205 0
      hxsl/MacroParser.hx
  19. 276 0
      hxsl/Macros.hx
  20. 260 0
      hxsl/Printer.hx
  21. 39 0
      hxsl/Serializer.hx
  22. 46 0
      hxsl/Shader.hx
  23. 134 0
      hxsl/SharedShader.hx
  24. 192 0
      hxsl/Splitter.hx
  25. 23 0
      hxsl/Types.hx
  26. 400 0
      test/Test.hx

+ 3 - 10
.gitignore

@@ -1,12 +1,5 @@
-
-/engine.swf
-/samples/*.swf
 *.n
 *.swf
-*.n
-
-/samples/comps/arial.ttf
-/engine.js
-/engine.js.map
-/samples/2d/demo.js
-/bin
+*.js
+*.js.map
+/bin

+ 20 - 0
LICENSE

@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 Nicolas Cannasse
+
+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.

+ 6 - 30
README.md

@@ -1,30 +1,6 @@
-Haxe 3D Engine
-=========
-
-A lightweight 3D Engine for Haxe.
-
-Currently only supports Flash/Stage3D, but is abstracted to support other backends in the near future.
-
-In order to setup the engine, you can do :
-
-> var engine = new h3d.Engine();
-> engine.onReady = startMyApp;
-> engine.init();
-
-Then in your render loop you can do :
-
-> engine.begin();
-> ... render objects ...
-> engine.end()
-
-Objects can be created using a combination of a `h3d.mat.Material` (shader and blendmode) and `h3d.prim.Primitive` (geometry).
-
-You can look at available examples in `samples` directory.
-
-2D GPU Engine
--------------
-
-The `h2d` package contains classes that provides a complete 2D API that is built on top of `h3d`, and is then GPU accelerated.
-
-It contains an object hierarchy which base class is `h2d.Sprite` and root is `h2d.Scene`
-
+Heaps
+=====
+
+High Performance Game Framework
+
+http://heaps.io

+ 91 - 0
doc/Render.hx

@@ -0,0 +1,91 @@
+// shader setup:
+	
+if( globals.constModified || shader.constModified ) {
+	shader.syncGlobalConsts(globals);
+	// will get/build an instance based on globals + const bits
+	// the instance is the eval'd version of the shader and has an unique ID
+}
+instances.push(shader.instance);
+
+// get/build a linked shader based on the unique instance identifiers and outputs
+// make sure that it's unique (use md5(toString) to prevent dups)
+var linked = getLinkedVersion(instances, ["output.pos", "output.color"]);
+
+shaders.sort(sortByLinkedId)
+for( s in linked ) {
+	if( s != prevId ) {
+		selectShader(s);
+		uploadParams(s.buildGlobals(globals));
+	}
+	// handle params and per-object globals (modelView, modelViewInv)
+	uploadParams(s.buildParams(globals));
+	render();g
+}
+
+// CHECK HOW RENDER PASS ARE DONE
+
+var passes : Map< String, Array<ObjectPass> > = collecObjectPasses();
+for( m in allPasses ) {
+	var objs = passes.get(m.name);
+	m.render(objs);
+}
+
+function renderPass() {
+	for( op in objectPasses ) {
+		op.pass.setup(...);
+		op.pass.assemble();
+	}
+	objectPass.sort(sortByShaderID);
+	beginPass();
+	for( op in objectPasses ) {
+		engine.selectPass(op.pass);
+		op.primitive.render();
+	}
+	endPass();
+}
+
+// ----------------------------
+// on mesh, passes are referenced by their name, not by a concrete object (decouple local setup from global one)
+
+var m = new Mesh(prim).material;
+
+(color white by default)
+m.texture = mytexture; // add the TextureShader on pass[0] if not already added
+m.addShader(new TextureMap(tex2)).additive = true; // second texture with additive
+m.castShadows = true; // add/remove the "shadowMap" pass with DepthShader
+m.receiveShadows = false; //add/remove the ShadowShader on pass[0]
+
+m.blendMode = Normal | Alpha | Additive
+	will set m.pass[0].name to "default" | "alpha" | "additive" and blendSrc/blendDst as well
+	-> blend / depth / culling / color are now in Pass properties, not in Material
+	
+mesh.addPass("outline",true/*inherit*/).addShader(new OutlineShader({ params }));
+mesh.removePass("outline");
+
+// on Scene, global setup as Array of passes
+
+var passes = scene.passes;
+passes.push(new ShadowMapPass("shadowMap",2048,...));
+
+Pass :
+	name : String; // a quel pass il s'execute
+	parentPass : inherit des modifiers (null si on ne veux pas garder les transforms par exemple)
+	shaders : Array<Shader> // this pass modifier
+	blendSrc/Dst
+	depthTest/Write
+	culling
+	colorMask
+
+ObjectPass:
+	pass : Pass (params already set)
+	prim : Primitive
+	obj : Object (for absPos, invAbsPos and error reporting)
+
+how are "globals" injected ?
+	before a pass is rendered, each pass is setup() using the current pass globals + the object specific globals
+	--> each pass can reinterpret the globals (eg : ShadowMap uses Light instead of Camera)
+	it is then assembled at pass rendering time (not before)
+	--> we need to know the pass output variable, global list and output selection to do so
+
+
+	

+ 306 - 0
doc/Shaders.hx

@@ -0,0 +1,306 @@
+
+//
+//  Resolve dependencies :
+// 		Given a Proto + X shader modifiers, build a complete shader and optimize it
+//		TODO : where to insert changes made by modifiers ?
+//		it seems necessary to build a code tree with dependencies in order to allow code permutation
+//		it is absolutely necessary to treat each struct field as a single variable (was not in HxSL)
+	
+Steps :
+	A) apply conditionals / reduce
+	B) calculate per-function dependencies
+	C) resolve order
+	D) eliminate code based on final pass requirement (output.color or another var)
+	E) calculate used vars and classify them
+
+	
+Variable types:
+
+	@global : no per-shader but per-pass (read-only)
+	@param : per-object parameter (read - only)
+	@input : vertex input
+	@var (default) : variable available between shaders (tracked for dependency)
+
+	Qualifiers:
+		
+	@private : for a @var, hide it from other shaders
+	@const : qualifier for param (default) or global. Mean that the value is eliminated at compilation
+
+Proto:
+	
+	// globals are injected by passes, they are not local to the per-shader-object. They are thus accessible in sub passes as well
+	@global var camera : {
+		var view : Matrix;
+		var proj : Matrix;
+		var projDiag : Float3; // [_11,_22,_33]
+		var viewProj : Matrix;
+		var inverseViewProj : Matrix;
+		@var var dir : Float3; // allow mix of variable types in structure (each variable is independent anyway)
+	};
+
+	@global var global : {
+		var time : Float;
+		var modelView : Matrix;
+		// ... other available globals in BasePass
+	};
+	
+	@input var input : {
+		var position : Float3;
+		var normal : Float3;
+	};
+	
+	var output : {
+		var position : Float4; // written in vertex
+		var color : Float4; // written in fragment
+	};
+	
+	// vars are always exported
+	
+	var transformedPosition : Float3;
+	var transformedNormal : Float3;
+	var projectedPosition : Float4;
+	var pixelColor : Float4;
+	
+	// each __init__ expr is out of order dependency-based
+	function __init__() {
+		transformedPosition = input.position * global.modelView;
+		projectedPosition = float4(transformedPosition, 1) * camera.viewProj;
+		transformedNormal = input.normal * global.modelView.m33;
+		camera.dir = (camera.position - transformedPosition).normalize();
+		pixelColor = color;
+	}
+	
+	function vertex() {
+		output.position = projectedPosition;
+	}
+	
+	function fragment() {
+		output.color = pixelColor;
+	}
+	
+	
+VertexColor
+	
+	@input var input : {
+		var color : Float3;
+	};
+	
+	var pixelColor : Float4;
+	
+	function vertex() {
+		vertexColor = input.color;
+	}
+	
+	function fragment() {
+		pixelColor.rgb *= vertexColor;
+	}
+
+
+TextureMaterial
+
+	// will add extra required fields to input
+	@input var input : {
+		var uv : Float2;
+	};
+	
+	@const var additive : Bool;
+	@const var killAlpha : Bool;
+	@param var killAlphaThreshold : Float;
+	
+	@param var texture : Texture;
+	var calculatedUV : Float2;
+	var pixelColor : Float4;
+	
+	function vertex() {
+		calculatedUV = input.uv;
+	}
+	
+	function fragment() {
+		var c = texture.get(calculatedUV);
+		if( killAlpha ) kill(c.a - killAlphaThreshold); // in multipass, we will have to specify if we want to keep the kill's or not
+		if( additive )
+			pixelColor += c;
+		else
+			pixelColor *= c;
+	}
+
+	// ajouter plusieurs TextureMaterial permet de faire du multiTexturing (à partir des même UV)
+
+AnimatedUV
+
+	var calculatedUV : Float2;
+	@global("global.time") var globalTime : Float;
+	@param var animationUV : Float2;
+	
+	function vertex() {
+		calculatedUV += animationUV * globalTime;
+	}
+
+	
+LightSystem:
+
+	// the light system is set by the pass setup, even if it's potentially per-object, it's still a global
+	@global var light : {
+		@const var perPixel : Bool;
+		var ambient : Float3;
+		var dirs : Array<{ dir : Float3, color : Float3 }>;
+		var points : Array<{ pos : Float3, color : Float3, att : Float3 }>;
+	};
+	
+	var transformedNormal : Float3; // will be tagged as read in either vertex or fragment depending on conditional
+	
+	@private var color : Float3; // will be tagged as written in vertex and read in fragment if !perPixel, or unused either
+	
+	var pixelColor : Float4; // will be tagged as read+written in fragment
+
+	function calcLight() {
+		var col = light.ambient;
+		var tn = transformedNormal.normalize();
+		for( d in light.dirs )
+			col += d.color * tn.dot(-d.dir).max(0);
+		for( p in light.points ) {
+			var d = transformedPosition - p.pos;
+			var dist2 = d.dot(d);
+			var dist = dist2.sqt();
+			col += p.color * (tn.dot(d).max(0) / (p.att.x * dist + p.att.y * dist2 + p.att.z * dist2 * dist));
+		}
+		return col;
+	}
+	
+	function vertex() {
+		if( !light.perPixel ) color = calcLight();
+	}
+	
+	function fragment() {
+		if( light.perPixel )
+			pixelColor.rgb *= calcLight();
+		else
+			pixelColor.rgb *= color;
+	}
+	
+	// DEPENDENCY:
+		Vertex:
+		READ transformedNormal   |-> must be inserted after all vertex that write transformNormals have been done
+		READ transformedPosition |
+		WRITE lightColor -> must be inserted before any vertex that reads light color
+		Fragment:
+		READ lightColor -> must be inserted after all fragments that write these two
+		READ+WRITE pixelColor -> must be inserted after it's been written first, but then keep the shader order
+		
+	CONFLICT if a program read lightColor and write transformNormal (unresolved cycle)
+	
+	CONFLICT if a program inserted after WRITE both lightColor and pixelColor:
+		writing lightColor will put it before, but since it also write pixelColor it should be after to enforce evaluation order
+		the user should then put it before
+
+	
+Outline
+
+	// Param are always local
+	@param var color : Float4;
+	@param var power : Float;
+	@param var size : Float;
+	
+	var camera : {
+		var projDiag : Float3;
+		var dir : Float3;
+	};
+	var transformedNormal : Float3;
+
+	function vertex() {
+		transformedPosition.xy += transformedNormal.xy * camera.projDiag.xy * size;
+	}
+	
+	function fragment() {
+		var e = 1 - transformedNormal.normalize().dot(camera.dir.normalize());
+		output.color = color * e.pow(power);
+	}
+	
+Skin
+
+	@input var input : {
+		pos : Float3,
+		normal : Float3,
+		weights : Float3,
+		indexes : Int,
+	};
+	
+	var transformedPosition : Float3;
+	var transformedNormal : Float3;
+	
+	function vertex() {
+		var p = input.pos, n = input.normal;
+		transformedPosition = p * input.weights.x * skinMatrixes[input.indexes.x * (255 * 3)] + p * input.weights.y * skinMatrixes[input.indexes.y * (255 * 3)] + p * input.weights.z * skinMatrixes[input.indexes.z * (255 * 3)];
+		transformedNormal = n * input.weights.x * skinMatrixes[input.indexes.x * (255 * 3)].m33 + n * input.weights.y * skinMatrixes[input.indexes.y * (255 * 3)].m33 + n * input.weights.z * skinMatrixes[input.indexes.z * (255 * 3)].m33;
+	}
+	
+	// will write TP/TN, hence disabling proto init
+
+ApplyShadow
+
+	@global var shadow : {
+		var map : Texture;
+		var color : Float3;
+		var power : Float;
+		var lightProj : Matrix;
+		var lightCenter : Matrix;
+	};
+
+	// Nullable params, allow to check them in shaders. Will create a isNull Const automatically
+	// actually use the global values unless object-specific local values are defined
+	var shadowColor : Param<Null<Float3>>;
+	var shadowPower : Param<Null<Float>>;
+
+	var pixelColor : Float4;
+
+	@:private var tshadowPos : Float3;
+	
+	function vertex() {
+		tshadowPos = float4(transformedPosition,1) * shadow.lightProj * shadow.lightCenter;
+	}
+	
+	function fragment() {
+		var s = exp( (shadowPower == null ? shadow.power : shadowPower) * (tshadowPos.z - shadow.map.get(tshadowPos.xy).dot([1, 1 / 255, 1 / (255 * 255), 1 / (255 * 255 * 255)]))).sat();
+		pixelColor.rgb *= (1 - s) * (shadowColor == null ? shadow.color : shadowColor) + s.xxx;
+	}
+
+DepthMap
+
+	@global var depthDelta : Float;
+	@:private var distance : Float;
+	
+	// the pass setup will have to declare that it will write distanceColor to its rendertarget
+	var depthColor : Float4;
+
+	function vertex() {
+		distance = projectedPosition.z / projectedPosition.w + depthDelta;
+	}
+	
+	function fragment() {
+		var color : Float4 = (distance.xxxx * [1,255,255*255,255*255*255]).frac();
+		depthColor = color - color.yzww * [1 / 255, 1 / 255, 1 / 255, 0];
+	}
+
+DistanceMap
+
+	@global var distance : {
+		var center : Float3;
+		var scale : Float;
+		var power : Float;
+	};
+
+	@private var dist : Float3;
+	
+	// the pass will have to declare that it will write distanceColor to its rendertarget
+	var distanceColor : Float4;
+
+	function vertex() {
+		dist = (transformedPosition - center).abs();
+	}
+	
+	function fragment() {
+		var d = (dist.length() * scale).pow(power);
+		var color : Float4 = (d.xxxx * [1,255,255*255,255*255*255]).frac();
+		distanceColor = color - color.yzww * [1 / 255, 1 / 255, 1 / 255, 0];
+	}
+

+ 6 - 0
hxsl.html

@@ -0,0 +1,6 @@
+<html>
+	<body>
+		<pre id="haxe:trace"></pre>
+		<script src="hxsl.js"></script>
+	</body>
+</html>

+ 6 - 0
hxsl.hxml

@@ -0,0 +1,6 @@
+-cp test
+-js hxsl.js
+-main Test
+-dce full
+-lib h3d
+-cp .

+ 56 - 0
hxsl.hxproj

@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<project version="2">
+  <!-- Output SWF options -->
+  <output>
+    <movie outputType="Application" />
+    <movie input="" />
+    <movie path="hxsl.js" />
+    <movie fps="60" />
+    <movie width="800" />
+    <movie height="1000" />
+    <movie version="1" />
+    <movie minorVersion="0" />
+    <movie platform="JavaScript" />
+    <movie background="#FFFFFF" />
+  </output>
+  <!-- Other classes to be compiled into your SWF -->
+  <classpaths>
+    <class path="test" />
+  </classpaths>
+  <!-- Build options -->
+  <build>
+    <option directives="" />
+    <option flashStrict="True" />
+    <option mainClass="Test" />
+    <option enabledebug="False" />
+    <option additional="-dce full&#xA;-lib h3d&#xA;-cp ." />
+  </build>
+  <!-- haxelib libraries -->
+  <haxelib>
+    <!-- example: <library name="..." /> -->
+  </haxelib>
+  <!-- Class files to compile (other referenced classes will automatically be included) -->
+  <compileTargets>
+    <!-- example: <compile path="..." /> -->
+  </compileTargets>
+  <!-- Paths to exclude from the Project Explorer tree -->
+  <hiddenPaths>
+    <hidden path="obj" />
+    <hidden path="hxsl.js.map" />
+    <hidden path="hxsl.swf" />
+    <hidden path="hxsl.js" />
+    <hidden path="hxsl.hxml" />
+  </hiddenPaths>
+  <!-- Executed before build -->
+  <preBuildCommand />
+  <!-- Executed after build -->
+  <postBuildCommand alwaysRun="False" />
+  <!-- Other project options -->
+  <options>
+    <option showHiddenPaths="False" />
+    <option testMovie="OpenDocument" />
+    <option testMovieCommand="hxsl.html" />
+  </options>
+  <!-- Plugin storage -->
+  <storage />
+</project>

+ 396 - 0
hxsl/Ast.hx

@@ -0,0 +1,396 @@
+package hxsl;
+
+enum Type {
+	TVoid;
+	TInt;
+	TBool;
+	TFloat;
+	TString;
+	TVec( size : Int, t : VecType );
+	TMat3;
+	TMat4;
+	TMat3x4;
+	TSampler2D;
+	TSamplerCube;
+	TStruct( vl : Array<TVar> );
+	TFun( variants : Array<FunType> );
+	TArray( t : Type, size : SizeDecl );
+}
+
+enum VecType {
+	VInt;
+	VFloat;
+	VBool;
+}
+
+enum SizeDecl {
+	SConst( v : Int );
+	SVar( v : TVar );
+}
+
+typedef FunType = { args : Array<{ name : String, type : Type }>, ret : Type };
+
+class Error {
+	
+	public var msg : String;
+	public var pos : Position;
+	
+	public function new( msg, pos ) {
+		this.msg = msg;
+		this.pos = pos;
+	}
+	
+	public function toString() {
+		return "Error(" + msg + ")@" + pos;
+	}
+	
+	public static function t( msg : String, pos : Position ) : Dynamic {
+		throw new Error(msg, pos);
+		return null;
+	}
+}
+
+typedef Position = haxe.macro.Expr.Position;
+
+typedef Expr = { expr : ExprDef, pos : Position };
+
+typedef Binop = haxe.macro.Expr.Binop;
+typedef Unop = haxe.macro.Expr.Unop;
+
+enum VarKind {
+	Global;
+	Input;
+	Param;
+	Var;
+	Local;
+	Output;
+	Function;
+}
+
+enum VarQualifier {
+	Const( ?max : Int );
+	Private;
+	Nullable;
+	PerObject;
+	Name( n : String );
+}
+
+typedef VarDecl = {
+	var name : String;
+	var type : Null<Type>;
+	var kind : Null<VarKind>;
+	var qualifiers : Array<VarQualifier>;
+	var expr : Null<Expr>;
+}
+
+typedef FunDecl = {
+	var name : String;
+	var args : Array<VarDecl>;
+	var ret : Null<Type>;
+	var expr : Expr;
+}
+
+enum Const {
+	CNull;
+	CBool( b : Bool );
+	CInt( v : Int );
+	CFloat( v : Float );
+	CString( v : String );
+}
+
+enum ExprDef {
+ 	EConst( c : Const );
+	EIdent( i : String );
+	EParenthesis( e : Expr );
+	EField( e : Expr, f : String );
+	EBinop( op : Binop, e1 : Expr, e2 : Expr );
+	EUnop( op : Unop, e1 : Expr );
+	ECall( e : Expr, args : Array<Expr> );
+	EBlock( el : Array<Expr> );
+	EVars( v : Array<VarDecl> );
+	EFunction( f : FunDecl );
+	EIf( econd : Expr, eif : Expr, eelse : Null<Expr> );
+	EDiscard;
+	EFor( v : String, loop : Expr, block : Expr );
+	EReturn( ?e : Expr );
+	EBreak;
+	EContinue;
+	EArray( e : Expr, eindex : Expr );
+	EArrayDecl( el : Array<Expr> );
+}
+
+typedef TVar = {
+	var id : Int;
+	var name : String;
+	var type : Type;
+	var kind : VarKind;
+	@:optional var parent : TVar;
+	@:optional var qualifiers : Null<Array<VarQualifier>>;
+}
+
+typedef TFunction = {
+	var kind : FunctionKind;
+	var ref : TVar;
+	var args : Array<TVar>;
+	var ret : Type;
+	var expr : TExpr;
+}
+
+enum FunctionKind {
+	Vertex;
+	Fragment;
+	Init;
+	Helper;
+}
+
+enum TGlobal {
+	Radians;
+	Degrees;
+	Sin;
+	Cos;
+	Tan;
+	Asin;
+	Acos;
+	Atan;
+	Pow;
+	Exp;
+	Log;
+	Exp2;
+	Log2;
+	Sqrt;
+	Inversesqrt;
+	Abs;
+	Sign;
+	Floor;
+	Ceil;
+	Fract;
+	Mod;
+	Min;
+	Max;
+	//Clamp;
+	//Mix;
+	//Step;
+	//SmoothStep;
+	Length;
+	Distance;
+	Dot;
+	Cross;
+	Normalize;
+	//Faceforward;
+	//Reflect;
+	//Refract;
+	//MatrixCompMult;
+	//Any;
+	//All;
+	Texture2D;
+	TextureCube;
+	// ...other texture* operations
+	// constructors
+	Vec2;
+	Vec3;
+	Vec4;
+	IVec2;
+	IVec3;
+	IVec4;
+	BVec2;
+	BVec3;
+	BVec4;
+	Mat2;
+	Mat3;
+	Mat4;
+	// extra (not in GLSL ES)
+	Mat3x4;
+	Saturate;
+}
+
+enum Component {
+	X;
+	Y;
+	Z;
+	W;
+}
+
+enum TExprDef {
+	TConst( c : Const );
+	TVar( v : TVar );
+	TGlobal( g : TGlobal );
+	TParenthesis( e : TExpr );
+	TBlock( el : Array<TExpr> );
+	TBinop( op : Binop, e1 : TExpr, e2 : TExpr );
+	TUnop( op : Unop, e1 : TExpr );
+	TVarDecl( v : TVar, ?init : TExpr );
+	TCall( e : TExpr, args : Array<TExpr> );
+	TSwiz( e : TExpr, regs : Array<Component> );
+	TIf( econd : TExpr, eif : TExpr, eelse : Null<TExpr> );
+	TDiscard;
+	TReturn( ?e : TExpr );
+	TFor( v : TVar, it : TExpr, loop : TExpr );
+	TContinue;
+	TBreak;
+	TArray( e : TExpr, index : TExpr );
+	TArrayDecl( el : Array<TExpr> );
+}
+
+typedef TExpr = { e : TExprDef, t : Type, p : Position }
+
+typedef ShaderData = {
+	var name : String;
+	var vars : Array<TVar>;
+	var funs : Array<TFunction>;
+}
+
+class Tools {
+	
+	static var UID = 0;
+	
+	public static var SWIZ = Component.createAll();
+	
+	public static function allocVarId() {
+		return ++UID;
+	}
+	
+	public static function getName( v : TVar ) {
+		if( v.qualifiers == null )
+			return v.name;
+		for( q in v.qualifiers )
+			switch( q ) {
+			case Name(n): return n;
+			default:
+			}
+		return v.name;
+	}
+	
+	public static function getConstBits( v : TVar ) {
+		switch( v.type ) {
+		case TBool:
+			return 1;
+		case TInt:
+			for( q in v.qualifiers )
+				switch( q ) {
+				case Const(n):
+					if( n != null ) {
+						var bits = 0;
+						while( n >= 1 << bits )
+							bits++;
+						return bits;
+					}
+					return 8;
+				default:
+				}
+		default:
+		}
+		return 0;
+	}
+	
+	public static function isConst( v : TVar ) {
+		if( v.qualifiers != null )
+			for( q in v.qualifiers )
+				switch( q ) {
+				case Const(_): return true;
+				default:
+				}
+		return false;
+	}
+
+	public static function isStruct( v : TVar ) {
+		return switch( v.type ) { case TStruct(_): true; default: false; }
+	}
+
+	public static function isArray( v : TVar ) {
+		return switch( v.type ) { case TArray(_): true; default: false; }
+	}
+
+	public static function hasQualifier( v : TVar, q ) {
+		if( v.qualifiers != null )
+			for( q2 in v.qualifiers )
+				if( q2 == q )
+					return true;
+		return false;
+	}
+
+	public static function toString( t : Type ) {
+		return switch( t ) {
+		case TVec(size, t):
+			var prefix = switch( t ) {
+			case VFloat: "";
+			case VInt: "I";
+			case VBool: "B";
+			}
+			prefix + "Vec" + size;
+		case TStruct(vl):"{" + [for( v in vl ) v.name + " : " + toString(v.type)].join(",") + "}";
+		case TArray(t, s): toString(t) + "[" + (switch( s ) { case SConst(i): "" + i; case SVar(v): v.name; } ) + "]";
+		default: t.getName().substr(1);
+		}
+	}
+
+	public static function toType( t : VecType ) {
+		return switch( t ) {
+		case VFloat: TFloat;
+		case VBool: TBool;
+		case VInt: TInt;
+		};
+	}
+	
+	public static function iter( e : TExpr, f : TExpr -> Void ) {
+		switch( e.e ) {
+		case TParenthesis(e): f(e);
+		case TBlock(el): for( e in el ) f(e);
+		case TBinop(_, e1, e2): f(e1); f(e2);
+		case TUnop(_, e1): f(e1);
+		case TVarDecl(_,init): if( init != null ) f(init);
+		case TCall(e, args): f(e); for( a in args ) f(a);
+		case TSwiz(e, _): f(e);
+		case TIf(econd, eif, eelse): f(econd); f(eif); if( eelse != null ) f(eelse);
+		case TReturn(e): if( e != null ) f(e);
+		case TFor(_, it, loop): f(it); f(loop);
+		case TArray(e, index): f(e); f(index);
+		case TArrayDecl(el): for( e in el ) f(e);
+		case TConst(_),TVar(_),TGlobal(_), TDiscard, TContinue, TBreak:
+		}
+	}
+
+	public static function map( e : TExpr, f : TExpr -> TExpr ) : TExpr {
+		var ed = switch( e.e ) {
+		case TParenthesis(e): TParenthesis(f(e));
+		case TBlock(el): TBlock([for( e in el ) f(e)]);
+		case TBinop(op, e1, e2): TBinop(op, f(e1), f(e2));
+		case TUnop(op, e1): TUnop(op, f(e1));
+		case TVarDecl(v,init): TVarDecl(v, if( init != null ) f(init) else null);
+		case TCall(e, args): TCall(f(e),[for( a in args ) f(a)]);
+		case TSwiz(e, c): TSwiz(f(e), c);
+		case TIf(econd, eif, eelse): TIf(f(econd),f(eif),if( eelse != null ) f(eelse) else null);
+		case TReturn(e): TReturn(if( e != null ) f(e) else null);
+		case TFor(v, it, loop): TFor(v, f(it), f(loop));
+		case TArray(e, index): TArray(f(e), f(index));
+		case TArrayDecl(el): TArrayDecl([for( e in el ) f(e)]);
+		case TConst(_), TVar(_), TGlobal(_), TDiscard, TContinue, TBreak: e.e;
+		}
+		return { e : ed, t : e.t, p : e.p };
+	}
+
+}
+
+class Tools2 {
+
+	public static function toString( g : TGlobal ) {
+		var n = g.getName();
+		return n.charAt(0).toLowerCase() + n.substr(1);
+	}
+
+}
+
+class Tools3 {
+
+	public static function toString( s : ShaderData ) {
+		return Printer.shaderToString(s);
+	}
+
+}
+
+class Tools4 {
+
+	public static function toString( e : TExpr ) {
+		return Printer.toString(e);
+	}
+
+}

+ 206 - 0
hxsl/Cache.hx

@@ -0,0 +1,206 @@
+package hxsl;
+using hxsl.Ast;
+
+class AllocParam {
+	public var pos : Int;
+	public var instance : Int;
+	public var index : Int;
+	public var type : Type;
+	public var perObjectGlobal : AllocGlobal;
+	public function new(pos, instance, index, type) {
+		this.pos = pos;
+		this.instance = instance;
+		this.index = index;
+		this.type = type;
+	}
+}
+
+class AllocGlobal {
+	public var pos : Int;
+	public var gid : Int;
+	public var path : String;
+	public var type : Type;
+	public function new(pos, path, type) {
+		this.pos = pos;
+		this.path = path;
+		this.gid = Globals.allocID(path);
+		this.type = type;
+	}
+}
+
+class CompleteShader {
+	static var UID = 0;
+	public var id : Int;
+	public var data : ShaderData;
+	public var params : Array<AllocParam>;
+	public var paramsSize : Int;
+	public var globals : Array<AllocGlobal>;
+	public var globalsSize : Int;
+	public var textures : Array<AllocParam>;
+	public function new() {
+		id = UID++;
+	}
+}
+
+class ShaderBuffers {
+	public var globals : haxe.ds.Vector<Float>;
+	public var params : haxe.ds.Vector<Float>;
+	public var tex : haxe.ds.Vector<Types.Texture>;
+	public function new( c : CompleteShader ) {
+		globals = new haxe.ds.Vector(c.globalsSize);
+		params = new haxe.ds.Vector(c.paramsSize);
+		tex = new haxe.ds.Vector(c.textures.length);
+	}
+}
+
+class SearchMap {
+	public var linked : { vertex : CompleteShader, fragment : CompleteShader };
+	public var next : Map<Int,SearchMap>;
+	public function new() {
+	}
+}
+
+class Cache {
+
+	var linkCache : Map<Int,SearchMap>;
+	var outVarsMap : Map<String, Int>;
+	var outVars : Array<Array<String>>;
+	
+	function new() {
+		linkCache = new Map();
+		outVarsMap = new Map();
+		outVars = [];
+	}
+	
+	public function allocOutputVars( vars : Array<String> ) {
+		var key = vars.join(",");
+		var id = outVarsMap.get(key);
+		if( id != null )
+			return id;
+		vars = vars.copy();
+		vars.sort(Reflect.compare);
+		id = outVarsMap.get(vars.join(","));
+		if( id != null ) {
+			outVarsMap.set(key, id);
+			return id;
+		}
+		id = outVars.length;
+		outVars.push(vars);
+		outVarsMap.set(key, id);
+		return id;
+	}
+	
+	public function link( instances : Array<SharedShader.ShaderInstance>, outVars : Int ) {
+		var c = linkCache.get(outVars);
+		if( c == null ) {
+			c = new SearchMap();
+			linkCache.set(outVars, c);
+		}
+		for( i in instances ) {
+			if( c.next == null ) c.next = new Map();
+			var cs = c.next.get(i.id);
+			if( cs == null ) {
+				cs = new SearchMap();
+				c.next.set(i.id, cs);
+			}
+			c = cs;
+		}
+		if( c.linked != null )
+			return c.linked;
+			
+		var linker = new hxsl.Linker();
+		var s = linker.link([for( s in instances ) s.shader], this.outVars[outVars]);
+		
+		// params tracking
+		var paramVars = new Map();
+		for( v in linker.allVars )
+			if( v.v.kind == Param ) {
+				switch( v.v.type ) {
+				case TStruct(_): continue;
+				default:
+				}
+				var i = instances[v.instanceIndex];
+				paramVars.set(v.id, { instance : v.instanceIndex, index : i.params.get(v.merged[0].id) } );
+			}
+		
+		var s = new hxsl.Splitter().split(s);
+		c.linked = {
+			vertex : flattenShader(s.vertex, Vertex, paramVars),
+			fragment : flattenShader(s.fragment, Fragment, paramVars),
+		};
+		return c.linked;
+	}
+	
+	function getPath( v : TVar ) {
+		if( v.parent == null )
+			return v.name;
+		return getPath(v.parent) + "." + v.name;
+	}
+	
+	function flattenShader( s : ShaderData, kind : FunctionKind, params : Map < Int, { instance:Int, index:Int } > ) {
+		var flat = new Flatten();
+		var c = new CompleteShader();
+		var data = flat.flatten(s, kind);
+		for( g in flat.allocData.keys() ) {
+			var alloc = flat.allocData.get(g);
+			switch( g.kind ) {
+			case Param:
+				var out = [];
+				for( a in alloc ) {
+					if( a.v == null ) continue; // padding
+					var p = params.get(a.v.id);
+					if( p == null ) {
+						var ap = new AllocParam(a.pos, -1, -1, a.v.type);
+						ap.perObjectGlobal = new AllocGlobal( -1, getPath(a.v), a.v.type);
+						out.push(ap);
+						continue;
+					}
+					out.push(new AllocParam(a.pos, p.instance, p.index, a.v.type));
+				}
+				switch( g.type ) {
+				case TArray(TSampler2D, _):
+					c.textures = out;
+				case TArray(TVec(4, VFloat),SConst(size)):
+					c.params = out;
+					c.paramsSize = size;
+				default: throw "assert";
+				}
+			case Global:
+				var out = [for( a in alloc ) if( a.v != null ) new AllocGlobal(a.pos, getPath(a.v), a.v.type)];
+				switch( g.type ) {
+				case TArray(TVec(4, VFloat),SConst(size)):
+					c.globals = out;
+					c.globalsSize = size;
+				default:
+					throw "assert";
+				}
+			default: throw "assert";
+			}
+		}
+		if( c.globals == null ) {
+			c.globals = [];
+			c.globalsSize = 0;
+		}
+		if( c.params == null ) {
+			c.params = [];
+			c.paramsSize = 0;
+		}
+		if( c.textures == null )
+			c.textures = [];
+		c.data = data;
+		return c;
+	}
+	
+	static var INST : Cache;
+	public static function get() : Cache {
+		var c = INST;
+		if( c == null )
+			INST = c = new Cache();
+		return c;
+	}
+	
+	public static function clear() {
+		INST = null;
+	}
+	
+}

+ 841 - 0
hxsl/Checker.hx

@@ -0,0 +1,841 @@
+package hxsl;
+
+using hxsl.Ast;
+
+private enum FieldAccess {
+	FField( e : TExpr );
+	FGlobal( g : TGlobal, arg : TExpr, variants : Array<FunType> );
+}
+
+private enum WithType {
+	NoValue;
+	Value;
+	InBlock;
+	With( t : Type );
+}
+
+/**
+	Type Checker : will take an untyped Expr and turn it into a typed TExpr, resolving identifiers and ensuring type safety.
+**/
+class Checker {
+	
+	static var vec2 = TVec(2, VFloat);
+	static var vec3 = TVec(3, VFloat);
+	static var vec4 = TVec(4, VFloat);
+	
+	var vars : Map<String,TVar>;
+	var globals : Map<String,{ g : TGlobal, t : Type }>;
+	var curFun : TFunction;
+	var inLoop : Bool;
+
+	public function new() {
+		globals = new Map();
+		inline function g(gl:TGlobal, vars) {
+		}
+		var genType = [TFloat, vec2, vec3, vec4];
+		var genFloat = [for( t in genType ) { args : [ { name : "value", type : t } ], ret : t } ];
+		var genFloat2 = [for( t in genType ) { args : [ { name : "a", type : t }, { name : "b", type : t } ], ret : t } ];
+		var genWithFloat = [for( t in genType ) { args : [ { name : "a", type : t }, { name : "b", type : TFloat } ], ret : t } ];
+		for( g in Ast.TGlobal.createAll() ) {
+			var def = switch( g ) {
+			case Vec2, Vec3, Vec4, Mat2, Mat3, Mat3x4, Mat4, IVec2, IVec3, IVec4, BVec2, BVec3, BVec4: [];
+			case Radians, Degrees, Cos, Sin, Tan, Asin, Acos, Exp, Log, Exp2, Log2, Sqrt, Inversesqrt, Abs, Sign, Floor, Ceil, Fract: genFloat;
+			case Atan: genFloat.concat(genFloat2);
+			case Pow: genFloat2;
+			case Mod, Min, Max:
+				genFloat2.concat(genWithFloat);
+			case Saturate:
+				[ { args : [ { name : "value", type : TFloat } ], ret : TFloat } ];
+			case Length:
+				[for( t in genType ) { args : [ { name : "value", type : t } ], ret : TFloat } ];
+			case Distance, Dot:
+				[for( t in genType ) { args : [ { name : "a", type : t }, { name : "b", type : t } ], ret : TFloat } ];
+			case Normalize:
+				genFloat;
+			case Cross:
+				[ { args : [ { name : "a", type : vec3 }, { name : "b", type : vec3 } ], ret : vec3 } ];
+			case Texture2D:
+				[ { args : [ { name : "tex", type : TSampler2D }, { name : "b", type : vec2 } ], ret : vec4 } ];
+			case TextureCube:
+				[ { args : [ { name : "tex", type : TSamplerCube }, { name : "b", type : vec3 } ], ret : vec4 } ];
+			}
+			if( def != null )
+				globals.set(g.toString(), { t : TFun(def), g : g } );
+		}
+	}
+	
+	function error( msg : String, pos : Position ) : Dynamic {
+		return Ast.Error.t(msg,pos);
+	}
+
+	public function check( name : String, shader : Expr ) : ShaderData {
+		vars = new Map();
+		inLoop = false;
+		
+		var funs = [];
+		checkExpr(shader, funs);
+		var tfuns = [];
+		for( f in funs ) {
+			var pos = f.p, f = f.f;
+			var args : Array<TVar> = [for( a in f.args ) {
+				if( a.type == null ) error("Argument type required", pos);
+				if( a.expr != null ) error("Optional argument not supported", pos);
+				if( a.kind == null ) a.kind = Local;
+				if( a.kind != Local ) error("Argument should be local", pos);
+				if( a.qualifiers.length != 0 ) error("No qualifier allowed for argument", pos);
+				{ id : 0, name : a.name, kind : Local, type : a.type };
+			}];
+			var kind = switch( f.name ) {
+			case "vertex":  Vertex;
+			case "fragment": Fragment;
+			case "__init__": Init;
+			default: Helper;
+			}
+			if( args.length != 0 && kind != Helper )
+				error(kind+" function should have no argument", pos);
+			var fv : TVar = {
+				id : 0,
+				name : f.name,
+				kind : Function,
+				type : TFun([{ args : [for( a in args ) { type : a.type, name : a.name }], ret : f.ret == null ? TVoid : f.ret }]),
+			};
+			var f : TFunction = {
+				kind : kind,
+				ref : fv,
+				args : args,
+				ret : f.ret == null ? TVoid : f.ret,
+				expr : null,
+			};
+			if( vars.exists(fv.name) )
+				error("Duplicate function name", pos);
+			vars.set(fv.name,fv);
+			tfuns.push(f);
+		}
+		for( i in 0...tfuns.length )
+			typeFun(tfuns[i], funs[i].f.expr);
+		return {
+			name : name,
+			vars : Lambda.array(vars),
+			funs : tfuns,
+		};
+	}
+	
+	function saveVars() {
+		var old = new Map();
+		for( v in vars.keys() )
+			old.set(v, vars.get(v));
+		return old;
+	}
+
+	function typeFun( f : TFunction, e : Expr ) {
+		var old = saveVars();
+		for( a in f.args )
+			vars.set(a.name, a);
+		curFun = f;
+		f.expr = typeExpr(e,NoValue);
+		vars = old;
+	}
+	
+	function tryUnify( t1 : Type, t2 : Type ) {
+		if( t1 == t2 )
+			return true;
+		switch( [t1, t2] ) {
+		case [TVec(s1, t1), TVec(s2, t2)] if( s1 == s2 && t1 == t2 ):
+			return true;
+		default:
+		}
+		return false;
+	}
+	
+	function unify( t1 : Type, t2 : Type, p : Position ) {
+		if( !tryUnify(t1,t2) )
+			error(t1.toString() + " should be " + t2.toString(), p);
+	}
+	
+	function unifyExpr( e : TExpr, t : Type ) {
+		if( !tryUnify(e.t, t) ) {
+			switch( e.e ) {
+			case TConst(CInt(v)) if( t == TFloat ):
+				e.e = TConst(CFloat(v));
+				e.t = TFloat;
+			default:
+				error(e.t.toString() + " should be " + t.toString(), e.p);
+			}
+		}
+	}
+	
+	function checkWrite( e : TExpr ) {
+		switch( e.e ) {
+		case TVar(v):
+			switch( v.kind ) {
+			case Local, Var, Output:
+				return;
+			default:
+			}
+		case TSwiz(e, _):
+			checkWrite(e);
+			return;
+		default:
+		}
+		error("This expression cannot be assigned", e.p);
+	}
+	
+	function typeWith( e : Expr, ?t : Type ) {
+		if( t == null )
+			return typeExpr(e, Value);
+		var e = typeExpr(e, With(t));
+		unify(e.t, t, e.p);
+		return e;
+	}
+
+	function typeExpr( e : Expr, with : WithType ) : TExpr {
+		var type;
+		var ed = switch( e.expr ) {
+		case EConst(c):
+			type = switch( c ) {
+			case CInt(_): TInt;
+			case CString(_): TString;
+			case CNull: TVoid;
+			case CBool(_): TBool;
+			case CFloat(_): TFloat;
+			};
+			TConst(c);
+		case EBlock(el):
+			var old = saveVars();
+			var el = el.copy(), tl = [];
+			with = propagate(with);
+			if( el.length == 0 && with != NoValue ) error("Value expected", e.pos);
+			while( true ) {
+				var e = el.shift();
+				if( e == null ) break;
+				// split vars decls
+				switch( e.expr ) {
+				case EVars(vl) if( vl.length > 1 ):
+					var v0 = vl.shift();
+					el.unshift(e);
+					e = { expr : EVars([v0]), pos : e.pos };
+				default:
+				}
+				var ew = switch( e.expr ) {
+				case EVars(_): InBlock;
+				default: if( el.length == 0 ) with else NoValue;
+				}
+				tl.push(typeExpr(e, ew));
+			}
+			vars = old;
+			type = with == NoValue ? TVoid : tl[tl.length - 1].t;
+			TBlock(tl);
+		case EBinop(op, e1, e2):
+			var e1 = typeExpr(e1, Value);
+			var e2 = typeExpr(e2, With(e1.t));
+			switch( op ) {
+			case OpAssign:
+				checkWrite(e1);
+				unify(e2.t, e1.t, e2.p);
+				type = e1.t;
+			case OpAssignOp(op):
+				checkWrite(e1);
+				unify(typeBinop(op, e1, e2, e.pos), e1.t, e2.p);
+				type = e1.t;
+			default:
+				type = typeBinop(op, e1, e2, e.pos);
+			}
+			TBinop(op, e1, e2);
+		case EIdent(name):
+			var v = vars.get(name);
+			if( v != null ) {
+				switch( name ) {
+				case "vertex", "fragment", "__init__":
+					error("Function cannot be accessed", e.pos);
+				default:
+				}
+				type = v.type;
+				TVar(v);
+			} else {
+				var g = globals.get(name);
+				if( g != null ) {
+					type = g.t;
+					TGlobal(g.g);
+				} else
+					error("Unknown identifier '" + name + "'", e.pos);
+			}
+		case EField(e1, f):
+			var e1 = typeExpr(e1, Value);
+			var ef = fieldAccess(e1, f, with, e.pos);
+			if( ef == null ) error(e1.t.toString() + " has no field '" + f + "'", e.pos);
+			switch( ef ) {
+			case FField(ef):
+				type = ef.t;
+				ef.e;
+			case FGlobal(_):
+				// not closure support
+				error("Global function must be called immediately", e.pos);
+			}
+		case ECall(e1, args):
+			function makeCall(e1) {
+				return switch( e1.t ) {
+				case TFun(variants):
+					var e = unifyCallParams(e1, args, variants, e.pos);
+					type = e.t;
+					e.e;
+				default:
+					error(e1.t.toString() + " cannot be called", e.pos);
+				}
+			}
+			switch( e1.expr ) {
+			case EField(e1, f):
+				var e1 = typeExpr(e1, Value);
+				var ef = fieldAccess(e1, f, with, e.pos);
+				if( ef == null ) error(e1.t.toString() + " has no field '" + f + "'", e.pos);
+				switch( ef ) {
+				case FField(ef):
+					makeCall(ef);
+				case FGlobal(g, arg, variants):
+					var eg = { e : TGlobal(g), t : TFun(variants), p : e1.p };
+					if( variants.length == 0 ) {
+						var args = [for( a in args ) typeExpr(a, Value)];
+						args.unshift(arg);
+						var e = specialGlobal(g, eg, args, e.pos);
+						type = e.t;
+						e.e;
+					} else {
+						var e = unifyCallParams(eg, args, variants, e.pos);
+						switch( [e.e, eg.t] ) {
+						case [TCall(_, args), TFun([f])]:
+							args.unshift(arg);
+							f.args.unshift({ name : "_", type : arg.t });
+						default:
+							throw "assert";
+						}
+						type = e.t;
+						e.e;
+					}
+				}
+			default:
+				makeCall(typeExpr(e1, Value));
+			}
+		case EParenthesis(e):
+			var e = typeExpr(e, with);
+			type = e.t;
+			TParenthesis(e);
+		case EFunction(_):
+			throw "assert";
+		case EVars(vl):
+			if( with != InBlock )
+				error("Cannot declare a variable outside of a block", e.pos);
+			if( vl.length != 1 ) throw "assert";
+			var v = vl[0];
+			if( v.kind == null ) v.kind = Local;
+			if( v.kind != Local ) error("Should be local var", e.pos);
+			if( v.qualifiers.length != 0 ) error("Unexpected qualifier", e.pos);
+			var tv = makeVar(vl[0],e.pos);
+			var init = v.expr == null ? null : typeWith(v.expr, tv.type);
+			if( tv.type == null ) {
+				if( init == null ) error("Type required for unitialized local var", e.pos);
+				tv.type = init.t;
+			}
+			vars.set(tv.name, tv);
+			type = TVoid;
+			TVarDecl(tv, init);
+		case EUnop(op,e1):
+			var e1 = typeExpr(e1, Value);
+			switch( op ) {
+			case OpNot:
+				unifyExpr(e1, TBool);
+				type = TBool;
+				TUnop(op, e1);
+			case OpNeg:
+				switch( e1.t ) {
+				case TFloat, TInt, TVec(_,VFloat|VInt):
+				default: error("Cannot negate " + e1.t.toString(), e.pos);
+				}
+				type = e1.t;
+				TUnop(op, e1);
+			default:
+				error("Operation non supported", e.pos);
+			}
+		case EIf(cond, e1, e2):
+			with = propagate(with);
+			var cond = typeWith(cond, TBool);
+			var e1 = typeExpr(e1, with);
+			var e2 = e2 == null ? null : typeExpr(e2, with);
+			if( with == NoValue ) {
+				type = TVoid;
+				TIf(cond, e1, e2);
+			} else {
+				if( e2 == null ) error("Missing else", e.pos);
+				if( tryUnify(e1.t, e2.t) )
+					type = e1.t;
+				else {
+					unifyExpr(e2, e1.t);
+					type = e2.t;
+				}
+				TIf(cond, e1, e2);
+			}
+		case EDiscard:
+			type = TVoid;
+			TDiscard;
+		case EReturn(e):
+			if( (e == null) != (curFun.ret == TVoid) )
+				error("This function should return " + curFun.ret.toString(), e.pos);
+			var e = e == null ? null : typeWith(e, curFun.ret);
+			TReturn(e);
+		case EFor(v, it, block):
+			var it = typeExpr(it, Value);
+			switch( it.t ) {
+			case TArray(t, _):
+				var v : TVar = {
+					id : 0,
+					name : v,
+					type : t,
+					kind : Local,
+				};
+				var old = vars.get(v.name);
+				vars.set(v.name, v);
+				var block = typeExpr(block, NoValue);
+				if( old == null ) vars.remove(v.name) else vars.set(v.name, old);
+				TFor(v, it, block);
+			default:
+				error("Cannot iterate on " + it.t.toString(), it.p);
+			}
+		case EContinue:
+			if( !inLoop ) error("Continue outside loop", e.pos);
+			TContinue;
+		case EBreak:
+			if( !inLoop ) error("Break outside loop", e.pos);
+			TBreak;
+		case EArray(e1, e2):
+			var e1 = typeExpr(e1, Value);
+			var e2 = typeWith(e2, TInt);
+			switch( e1.t ) {
+			case TArray(t, size):
+				switch( [size, e2.e] ) {
+				case [SConst(v), TConst(CInt(i))] if( i >= v ):
+					error("Indexing outside array bounds", e.pos);
+				case [_, TConst(CInt(i))] if( i < 0 ):
+					error("Cannot index with negative value", e.pos);
+				default:
+				}
+				type = t;
+				TExprDef.TArray(e1, e2);
+			default:
+				error("Cannot index " + e1.t.toString() + " : should be an array", e.pos);
+			}
+		case EArrayDecl(el):
+			if( el.length == 0 ) error("Empty array not supported", e.pos);
+			var el = [for( e in el ) typeExpr(e, Value)];
+			var t = el[0].t;
+			for( i in 1...el.length )
+				unifyExpr(el[i], t);
+			type = TArray(t, SConst(el.length));
+			TArrayDecl(el);
+		}
+		return { e : ed, t : type, p : e.pos };
+	}
+	
+	function propagate( with : WithType ) {
+		return switch( with ) {
+		case InBlock: NoValue;
+		default: with;
+		}
+	}
+	
+	function checkExpr( e : Expr, funs : Array<{ f : FunDecl, p : Position }> ) {
+		switch( e.expr ) {
+		case EBlock(el):
+			for( e in el )
+				checkExpr(e,funs);
+		case EFunction(f):
+			funs.push({ f : f, p : e.pos });
+		case EVars(vl):
+			for( v in vl ) {
+				if( v.kind == null ) {
+					if( v.name == "output" ) v.kind = Output else v.kind = Local;
+					for( q in v.qualifiers )
+						switch( q ) {
+						case Const(_): v.kind = Param;
+						case Private: v.kind = Var;
+						default:
+						}
+				}
+				if( v.expr != null ) error("Cannot initialize variable declaration", v.expr.pos);
+				if( v.type == null ) error("Type required for variable declaration", e.pos);
+				if( vars.exists(v.name) ) error("Duplicate var decl '" + v.name + "'", e.pos);
+				vars.set(v.name, makeVar(v, e.pos));
+			}
+		default:
+			error("This expression is not allowed at shader declaration level", e.pos);
+		}
+	}
+	
+	function makeVar( v : VarDecl, pos : Position, ?parent : TVar ) {
+		var tv : TVar = {
+			id : 0,
+			name : v.name,
+			kind : v.kind,
+			type : v.type,
+		};
+		if( parent != null )
+			tv.parent = parent;
+		if( tv.kind == null ) {
+			if( parent == null )
+				tv.kind = Local;
+			else
+				tv.kind = parent.kind;
+		} else if( parent != null && tv.kind != parent.kind ) {
+			switch( [parent.kind, tv.kind] ) {
+			case [Global, Var]:
+				// allow declaring Vars inside globals (pseudo globals built by shader)
+			default:
+				error("Variable " + parent.kind + " cannot be changed to " + tv.kind, pos);
+			}
+		}
+		if( v.qualifiers.length > 0 ) {
+			tv.qualifiers = v.qualifiers;
+			for( q in v.qualifiers )
+				switch( q ) {
+				case Private: if( tv.kind != Var ) error("@private only allowed on varying", pos);
+				case Const(_):
+					var p = parent;
+					while( p != null ) {
+						if( !p.isStruct() ) error("@const only allowed in structure", pos);
+						p = p.parent;
+					}
+					if( tv.kind != Global && tv.kind != Param ) error("@const only allowed on parameter or global", pos);
+				case PerObject: if( tv.kind != Global ) error("@perObject only allowed on global", pos);
+				case Nullable: if( tv.kind != Param ) error("@nullable only allowed on parameter or global", pos);
+				case Name(_):
+					if( parent != null ) error("Cannot have an explicit name for a structure variable", pos);
+					if( tv.kind != Global ) error("Explicit name is only allowed for global var", pos);
+				}
+		}
+		if( tv.type != null )
+			tv.type = makeVarType(tv.type, tv, pos);
+		return tv;
+	}
+	
+	function makeVarType( t : Type, parent : TVar, pos : Position ) {
+		switch( t ) {
+		case TStruct(vl):
+			// mutate to allow TArray to access previously declared vars
+			var vl = vl.copy();
+			parent.type = TStruct(vl);
+			for( i in 0...vl.length ) {
+				var v = vl[i];
+				vl[i] = makeVar( { type : v.type, qualifiers : v.qualifiers, name : v.name, kind : v.kind, expr : null }, pos, parent);
+			}
+			return parent.type;
+		case TArray(t, size):
+			switch( t ) {
+			case TArray(_):
+				error("Multidimentional arrays are not allowed", pos);
+			case TStruct(_):
+				error("Array of structures are not allowed", pos);
+			default:
+			}
+			var s = switch( size ) {
+			case SConst(_): size;
+			case SVar(v):
+				var path = v.name.split(".");
+				var v2 = null;
+				for( n in path ) {
+					if( v2 == null ) {
+						v2 = vars.get(n);
+						// special handling when we reference our own variable which is being currently declared
+						if( v2 == null && parent != null ) {
+							var p = parent;
+							while( p.parent != null )
+								p = p.parent;
+							if( p.name == n )
+								v2 = p;
+						}
+					} else {
+						v2 = switch( v2.type ) {
+						case TStruct(vl):
+							var f = null;
+							for( v in vl )
+								if( v.name == n ) {
+									f = v;
+									break;
+								}
+							f;
+						default:
+							null;
+						}
+					}
+					if( v2 == null ) break;
+				}
+				if( v2 == null ) error("Array size variable '" + v.name + "'not found", pos);
+				if( !v2.isConst() ) error("Array size variable '" + v.name + "'should be a constant", pos);
+				SVar(v2);
+			}
+			return TArray(makeVarType(t,parent,pos), s);
+		default:
+			return t;
+		}
+	}
+	
+	function fieldAccess( e : TExpr, f : String, with : WithType, pos : Position ) : FieldAccess {
+		var ef = switch( e.t ) {
+		case TStruct(vl):
+			var found = null;
+			for( v in vl )
+				if( v.name == f ) {
+					found = v;
+					break;
+				}
+			if( found == null )
+				null;
+			else
+				{ e : TVar(found), t : found.type, p : pos };
+		default:
+			null;
+		}
+		if( ef != null )
+			return FField(ef);
+		var g = globals.get(f);
+		if( g == null ) {
+			var gl : TGlobal = switch( [f, e.t] ) {
+			case ["get", TSampler2D]: Texture2D;
+			case ["get", TSamplerCube]: TextureCube;
+			default: null;
+			}
+			if( gl != null )
+				g = globals.get(gl.toString());
+		}
+		if( g != null ) {
+			switch( g.t ) {
+			case TFun(variants):
+				var sel = [];
+				for( v in variants ) {
+					if( v.args.length == 0 || !tryUnify(e.t, v.args[0].type) ) continue;
+					var args = v.args.copy();
+					args.shift();
+					sel.push({ args : args, ret : v.ret });
+				}
+				if( sel.length > 0 || variants.length == 0 )
+					return FGlobal(g.g, e, sel);
+			default:
+			}
+		}
+		// swizzle ?
+		var stype;
+		var ncomps = switch( e.t ) {
+		case TFloat: stype = VFloat; 1;
+		case TInt: stype = VInt; 1;
+		case TBool: stype = VBool; 1;
+		case TVec(size, t): stype = t; size;
+		default: 0;
+		}
+		if( ncomps > 0 && f.length <= 4 ) {
+			var str = "xrsygtzbpwaq";
+			var comps = [X, Y, Z, W];
+			var cat = -1;
+			var out = [];
+			for( i in 0...f.length ) {
+				var idx = str.indexOf(f.charAt(i));
+				if( idx < 0 ) return null;
+				var icat = idx % 3;
+				if( cat < 0 ) cat = icat else if( icat != cat ) return null; // down't allow .ryz
+				var cid = Std.int(idx / 3);
+				if( cid >= ncomps )
+					error(e.t.toString() + " does not have component " + f.charAt(i), pos);
+				out.push(comps[cid]);
+			}
+			return FField( { e : TSwiz(e, out), t: out.length == 1 ? stype.toType() : TVec(out.length,stype), p:pos } );
+		}
+		return null;
+	}
+
+	function specialGlobal( g : TGlobal, e : TExpr, args : Array<TExpr>, pos : Position ) : TExpr {
+		var type = null;
+		inline function checkLength(n,t) {
+			var tsize = 0;
+			for( a in args )
+				switch( a.t ) {
+				case TVec(size, k):
+					if( k.toType() != t )
+						unify(a.t, t, a.p);
+					tsize += size;
+				default:
+					unifyExpr(a, t);
+					tsize++; // if we manage to unify
+				}
+			if( tsize != n )
+				error(g.toString() + " requires " + n + " "+t.toString()+" values but has " + tsize, pos);
+		}
+		switch( g ) {
+		case Vec2:
+			checkLength(2,TFloat);
+			type = TVec(2,VFloat);
+		case Vec3:
+			checkLength(3,TFloat);
+			type = TVec(3,VFloat);
+		case Vec4:
+			checkLength(4,TFloat);
+			type = TVec(4,VFloat);
+		case IVec2:
+			checkLength(2,TInt);
+			type = TVec(2,VInt);
+		case IVec3:
+			checkLength(3,TInt);
+			type = TVec(3,VInt);
+		case IVec4:
+			checkLength(4,TInt);
+			type = TVec(4,VInt);
+		case BVec2:
+			checkLength(2,TBool);
+			type = TVec(2,VBool);
+		case BVec3:
+			checkLength(3,TBool);
+			type = TVec(3,VBool);
+		case BVec4:
+			checkLength(4,TBool);
+			type = TVec(4,VBool);
+		case Mat3x4:
+			switch( ([for( a in args ) a.t]) ) {
+			case [TMat4]: type = TMat3x4;
+			default:
+				error("Cannot apply " + g.toString() + " to these parameters", pos);
+			}
+		case Mat3:
+			switch( ([for( a in args ) a.t]) ) {
+			case [TMat3x4|TMat4]: type = TMat3;
+			default:
+				error("Cannot apply " + g.toString() + " to these parameters", pos);
+			}
+		case Mat4:
+			switch( ([for( a in args ) a.t]) ) {
+			case [TMat4]: type = TMat4;
+			default:
+				error("Cannot apply " + g.toString() + " to these parameters", pos);
+			}
+		default:
+		}
+		if( type == null )
+			throw "Custom Global not supported " + g;
+		return { e : TCall(e, args), t : type, p : pos };
+	}
+	
+	function unifyCallParams( efun : TExpr, args : Array<Expr>, variants : Array<FunType>, pos : Position ) {
+		var minArgs = 1000, maxArgs = -1000, sel = [];
+		for( v in variants ) {
+			var n = v.args.length;
+			if( n < minArgs ) minArgs = n;
+			if( n > maxArgs ) maxArgs = n;
+			if( n == args.length ) sel.push(v);
+		}
+		switch( sel ) {
+		case [] if( variants.length == 0 ):
+			switch( efun.e ) {
+			case TGlobal(g):
+				return specialGlobal(g, efun, [for( a in args ) typeExpr(a,Value)], pos);
+			default:
+				throw "assert";
+			}
+		case []:
+			return error("Function expects " + (minArgs == maxArgs ? "" + minArgs : minArgs + "-" + maxArgs)  + " arguments", pos);
+		case [f]:
+			var targs = [];
+			for( i in 0...args.length ) {
+				var ft = f.args[i].type;
+				var a = typeExpr(args[i], With(ft));
+				try {
+					unifyExpr(a, ft);
+				} catch( e : Error ) {
+					e.msg += " for argument '" + f.args[i].name + "'";
+					throw e;
+				}
+				targs.push(a);
+			}
+			if( variants.length > 1 ) efun.t = TFun([f]);
+			return { e : TCall(efun, targs), t : f.ret, p : pos };
+		default:
+			var targs = [for( a in args ) typeExpr(a, Value)];
+			var bestMatch = null, mcount = -1;
+			for( f in sel ) {
+				var m = 0;
+				for( i in 0...targs.length ) {
+					if( !tryUnify(targs[i].t, f.args[i].type) )
+						break;
+					m++;
+				}
+				if( m > mcount ) {
+					bestMatch = f;
+					mcount = m;
+					if( m == targs.length ) {
+						efun.t = TFun([f]);
+						return { e : TCall(efun, targs), t : f.ret, p : pos };
+					}
+				}
+			}
+			for( i in 0...targs.length )
+				try {
+					unify(targs[i].t, bestMatch.args[i].type, targs[i].p);
+				} catch( e : Error ) {
+					e.msg += " for argument '" + bestMatch.args[i].name + "'";
+					throw e;
+				}
+			throw "assert";
+		}
+		
+	}
+
+	
+	function typeBinop(op, e1:TExpr, e2:TExpr, pos : Position) {
+		return switch( op ) {
+		case OpAssign, OpAssignOp(_): throw "assert";
+		case OpMult, OpAdd, OpSub, OpDiv:
+			switch( [op, e1.t, e2.t] ) {
+			case [OpMult,TVec(4,VFloat), TMat4], [OpMult,TMat4, TVec(4,VFloat)]:
+				vec4;
+			case [OpMult,TVec(3,VFloat), TMat3x4]:
+				vec3;
+			case [OpMult,TMat3, TVec(3,VFloat)], [OpMult, TVec(3,VFloat), TMat3]:
+				vec3;
+			case [_, TInt, TInt]: TInt;
+			case [_, TFloat, TFloat]: TFloat;
+			case [_, TVec(a,VFloat), TVec(b,VFloat)] if( a == b ): TVec(a,VFloat);
+			case [_, TFloat, TVec(_,VFloat)]: e2.t;
+			case [_, TVec(_,VFloat), TFloat]: e1.t;
+			default:
+				var opName = switch( op ) {
+				case OpMult: "multiply";
+				case OpAdd: "add";
+				case OpSub: "subtract";
+				case OpDiv: "divide";
+				default: throw "assert";
+				}
+				error("Cannot " + opName + " " + e1.t.toString() + " and " + e2.t.toString(), pos);
+			}
+		case OpLt, OpGt, OpLte, OpGte, OpEq, OpNotEq:
+			switch( e1.t ) {
+			case TFloat, TInt, TString if( e2.t != TVoid ):
+				unifyExpr(e2, e1.t);
+				TBool;
+			case TBool if( (op == OpEq || op == OpNotEq) && e2.t != TVoid ):
+				unifyExpr(e2, e1.t);
+				TBool;
+			default:
+				switch( [e1.e, e2.e] ) {
+				case [TVar(v), TConst(CNull)], [TConst(CNull), TVar(v)]:
+					if( !v.hasQualifier(Nullable) )
+						error("Variable is not declared as nullable", e1.p);
+					TBool;
+				default:
+					error("Cannot compare " + e1.t.toString() + " and " + e2.t.toString(), pos);
+				}
+			}
+		case OpBoolAnd, OpBoolOr:
+			unifyExpr(e1, TBool);
+			unifyExpr(e2, TBool);
+			TBool;
+		case OpInterval:
+			unifyExpr(e1, TInt);
+			unifyExpr(e2, TInt);
+			TArray(TInt, SConst(0));
+		default:
+			error("Unsupported operator " + op, pos);
+		}
+	}
+}

+ 79 - 0
hxsl/Clone.hx

@@ -0,0 +1,79 @@
+package hxsl;
+using hxsl.Ast;
+
+class Clone {
+
+	public var varMap : Map<Int,TVar>;
+	
+	public function new() {
+		varMap = new Map();
+	}
+	
+	public function tvar( v : TVar ) : TVar {
+		var v2 = varMap.get(v.id);
+		if( v2 != null ) return v2;
+		v2 = {
+			id : Tools.allocVarId(),
+			type : v.type,
+			name : v.name,
+			kind : v.kind,
+		};
+		varMap.set(v.id, v2);
+		if( v.parent != null ) v2.parent = tvar(v.parent);
+		if( v.qualifiers != null ) v2.qualifiers = v.qualifiers.copy();
+		v2.type = ttype(v.type);
+		return v2;
+	}
+	
+	public function tfun( f : TFunction ) : TFunction {
+		return {
+			ret : ttype(f.ret),
+			kind : f.kind,
+			ref : tvar(f.ref),
+			args : [for( a in f.args ) tvar(a)],
+			expr : texpr(f.expr),
+		};
+	}
+	
+	public function ttype( t : Type ) {
+		switch( t ) {
+		case TStruct(vl):
+			return TStruct([for( v in vl ) tvar(v)]);
+		case TArray(t, size):
+			return TArray(ttype(t), switch( size ) { case SConst(_): size; case SVar(v): SVar(tvar(v)); } );
+		case TFun(vars):
+			return TFun([for( v in vars ) { args : [for( a in v.args ) { name : a.name, type : ttype(a.type) } ], ret : ttype(v.ret) }]);
+		default:
+			return t;
+		}
+	}
+	
+	public function texpr( e : TExpr ) : TExpr {
+		var e2 : TExpr = e.map(texpr);
+		e2.t = ttype(e.t);
+		e2.e = switch( e2.e ) {
+		case TVar(v):
+			TVar(tvar(v));
+		case TVarDecl(v, init):
+			TVarDecl(tvar(v), init);
+		case TFor(v, it, loop):
+			TFor(tvar(v), it, loop);
+		default:
+			e2.e;
+		}
+		return e2;
+	}
+	
+	public function shader( s : ShaderData ) : ShaderData {
+		return {
+			name : s.name,
+			vars : [for( v in s.vars ) tvar(v)],
+			funs : [for( f in s.funs ) tfun(f)],
+		};
+	}
+	
+	public static function shaderData( s : ShaderData ) {
+		return new Clone().shader(s);
+	}
+	
+}

+ 361 - 0
hxsl/Eval.hx

@@ -0,0 +1,361 @@
+package hxsl;
+
+using hxsl.Ast;
+
+/**
+	Evaluator : will substitute some variables (usually constants) by their runtime value and will
+	evaluate and reduce the expression, unroll loops, etc.
+**/
+class Eval {
+	
+	public var varMap : Map<TVar,TVar>;
+	var constants : Map<TVar,TExprDef>;
+	var funMap : Map<TVar,TFunction>;
+	
+	public function new() {
+		varMap = new Map();
+		funMap = new Map();
+		constants = new Map();
+	}
+	
+	public function setConstant( v : TVar, c : Const ) {
+		constants.set(v, TConst(c));
+	}
+	
+	function mapVar( v : TVar ) {
+		var v2 = varMap.get(v);
+		if( v2 != null )
+			return v2;
+		v2 = {
+			id : v.id == 0 ? Tools.allocVarId() : v.id,
+			name : v.name,
+			type : v.type,
+			kind : v.kind,
+		};
+		if( v.parent != null ) v2.parent = mapVar(v.parent);
+		if( v.qualifiers != null ) v2.qualifiers = v.qualifiers.copy();
+		varMap.set(v, v2);
+		switch( v2.type ) {
+		case TStruct(vl):
+			v2.type = TStruct([for( v in vl ) mapVar(v)]);
+		case TArray(t, SVar(vs)):
+			var c = constants.get(vs);
+			if( c != null )
+				switch( c ) {
+				case TConst(CInt(v)):
+					v2.type = TArray(t, SConst(v));
+				default:
+					Error.t("Integer value expected for array size constant " + vs.name, null);
+				}
+			else {
+				var vs2 = mapVar(vs);
+				v2.type = TArray(t, SVar(vs2));
+			}
+		default:
+		}
+		return v2;
+	}
+	
+	public function eval( s : ShaderData ) : ShaderData {
+		var funs = [];
+		for( f in s.funs ) {
+			var f2 : TFunction = {
+				kind : f.kind,
+				ref : mapVar(f.ref),
+				args : [for( a in f.args ) mapVar(a)],
+				ret : f.ret,
+				expr : f.expr,
+			};
+			if( f.kind != Helper )
+				funs.push(f2);
+			funMap.set(f2.ref, f2);
+		}
+		for( i in 0...funs.length )
+			funs[i].expr = evalExpr(funs[i].expr);
+		return {
+			name : s.name,
+			vars : [for( v in s.vars ) mapVar(v)],
+			funs : funs,
+		};
+	}
+	
+	var markReturn : Bool;
+	
+	function hasReturn( e : TExpr ) {
+		markReturn = false;
+		hasReturnLoop(e);
+		return markReturn;
+	}
+	
+	function hasReturnLoop( e : TExpr ) {
+		switch( e.e ) {
+		case TReturn(_):
+			markReturn = true;
+		default:
+			if( !markReturn ) e.iter(hasReturnLoop);
+		}
+	}
+	
+	function handleReturn( e : TExpr, final : Bool = false ) : TExpr {
+		switch( e.e ) {
+		case TReturn(v):
+			if( !final )
+				Error.t("Cannot inline not final return", e.p);
+			if( v == null )
+				return { e : TBlock([]), t : TVoid, p : e.p };
+			return handleReturn(v, true);
+		case TBlock(el):
+			var i = 0, last = el.length;
+			var out = [];
+			while( i < last ) {
+				var e = el[i++];
+				if( i == last )
+					out.push(handleReturn(e, final));
+				else switch( e.e ) {
+				case TIf(econd, eif, null) if( final && hasReturn(eif) ):
+					out.push(handleReturn( { e : TIf(econd, eif, { e : TBlock(el.slice(i)), t : e.t, p : e.p } ), t : e.t, p : e.p } ));
+					break;
+				default:
+					out.push(handleReturn(e));
+				}
+			}
+			var t = if( final ) out[out.length - 1].t else e.t;
+			return { e : TBlock(out), t : t, p : e.p };
+		case TParenthesis(v):
+			var v = handleReturn(v, final);
+			return { e : TParenthesis(v), t : v.t, p : e.p };
+		case TIf(cond, eif, eelse) if( eelse != null && final ):
+			var cond = handleReturn(cond);
+			var eif = handleReturn(eif, final);
+			return { e : TIf(cond, eif, handleReturn(eelse, final)), t : eif.t, p : e.p };
+		default:
+			return e.map(handleReturnDef);
+		}
+	}
+	
+	function handleReturnDef(e) {
+		return handleReturn(e);
+	}
+	
+	function evalExpr( e : TExpr ) : TExpr {
+		var d : TExprDef = switch( e.e ) {
+		case TGlobal(_), TConst(_): e.e;
+		case TVar(v):
+			var c = constants.get(v);
+			if( c != null )
+				c;
+			else
+				TVar(mapVar(v));
+		case TVarDecl(v, init):
+			TVarDecl(mapVar(v), init == null ? null : evalExpr(init));
+		case TArray(e1, e2):
+			var e1 = evalExpr(e1);
+			switch( [e1.e, e2.e] ) {
+			case [TArrayDecl(el),TConst(CInt(i))] if( i >= 0 && i < el.length ):
+				el[i].e;
+			default:
+				TArray(evalExpr(e1), evalExpr(e2));
+			}
+		case TSwiz(e, r):
+			TSwiz(evalExpr(e), r.copy());
+		case TReturn(e):
+			TReturn(e == null ? null : evalExpr(e));
+		case TCall(c, args):
+			var c = evalExpr(c);
+			var args = [for( a in args ) evalExpr(a)];
+			switch( c.e ) {
+			case TGlobal(_):
+				TCall(c, args);
+			case TVar(v) if( funMap.exists(v) ):
+				var f = funMap.get(v);
+				var outExprs = [], undo = [];
+				for( i in 0...f.args.length ) {
+					var v = f.args[i];
+					var e = args[i];
+					switch( e.e ) {
+					case TConst(_), TVar({ kind : (Input|Param|Global) }):
+						var old = constants.get(v);
+						undo.push(function() old == null ? constants.remove(v) : constants.set(v, old));
+						constants.set(v, e.e);
+					default:
+						var old = varMap.get(v);
+						if( old == null )
+							undo.push(function() varMap.remove(v));
+						else {
+							varMap.remove(v);
+							undo.push(function() varMap.set(v, old));
+						}
+						var v = mapVar(v);
+						outExprs.push( { e : TVarDecl(v, e), t : TVoid, p : e.p } );
+					}
+				}
+				var e = handleReturn(evalExpr(f.expr), true);
+				for( u in undo ) u();
+				switch( e.e ) {
+				case TBlock(el):
+					for( e in el )
+						outExprs.push(e);
+				default:
+					outExprs.push(e);
+				}
+				TBlock(outExprs);
+			default:
+				Error.t("Cannot eval non-static call expresssion '" + new Printer().exprString(c)+"'", c.p);
+			}
+		case TBlock(el):
+			var out = [];
+			var last = el.length - 1;
+			for( i in 0...el.length ) {
+				var e = evalExpr(el[i]);
+				switch( e.e ) {
+				case TConst(_), TVar(_) if( i < last ):
+				default:
+					out.push(e);
+				}
+			}
+			if( out.length == 1 )
+				out[0].e
+			else
+				TBlock(out);
+		case TBinop(op, e1, e2):
+			var e1 = evalExpr(e1);
+			var e2 = evalExpr(e2);
+			inline function fop(callb:Float->Float->Float) {
+				return switch( [e1.e, e2.e] ) {
+				case [TConst(CInt(a)), TConst(CInt(b))]:
+					TConst(CInt(Std.int(callb(a, b))));
+				case [TConst(CFloat(a)), TConst(CFloat(b))]:
+					TConst(CFloat(callb(a, b)));
+				default:
+					TBinop(op, e1, e2);
+				}
+			}
+			inline function iop(callb:Int->Int->Int) {
+				return switch( [e1.e, e2.e] ) {
+				case [TConst(CInt(a)), TConst(CInt(b))]:
+					TConst(CInt(callb(a, b)));
+				default:
+					TBinop(op, e1, e2);
+				}
+			}
+			inline function bop(callb:Bool->Bool->Bool,def) {
+				return switch( [e1.e, e2.e] ) {
+				case [TConst(CBool(a)), TConst(CBool(b))]:
+					TConst(CBool(callb(a, b)));
+				case [TConst(CBool(a)), _]:
+					if( a == def )
+						TConst(CBool(a));
+					else
+						e2.e;
+				case [_, TConst(CBool(a))]:
+					if( a == def )
+						TConst(CBool(a)); // ignore e1 side effects ?
+					else
+						e1.e;
+				default:
+					TBinop(op, e1, e2);
+				}
+			}
+			inline function compare(callb:Int->Bool) {
+				return switch( [e1.e, e2.e] ) {
+				case [TConst(CNull), TConst(CNull)]:
+					TConst(CBool(callb(0)));
+				case [TConst(_), TConst(CNull)]:
+					TConst(CBool(callb(1)));
+				case [TConst(CNull), TConst(_)]:
+					TConst(CBool(callb(-1)));
+				case [TConst(CBool(a)), TConst(CBool(b))]:
+					TConst(CBool(callb(a == b ? 0 : 1)));
+				case [TConst(CInt(a)), TConst(CInt(b))]:
+					TConst(CBool(callb(a - b)));
+				case [TConst(CFloat(a)), TConst(CFloat(b))]:
+					TConst(CBool(callb(a > b ? 1 : (a == b) ? 0 : -1)));
+				case [TConst(CString(a)), TConst(CString(b))]:
+					TConst(CBool(callb(a > b ? 1 : (a == b) ? 0 : -1)));
+				default:
+					TBinop(op, e1, e2);
+				}
+			}
+			switch( op ) {
+			case OpAdd: fop(function(a, b) return a + b);
+			case OpSub: fop(function(a, b) return a - b);
+			case OpMult: fop(function(a, b) return a * b);
+			case OpDiv: fop(function(a, b) return a / b);
+			case OpMod: fop(function(a, b) return a % b);
+			case OpXor: iop(function(a, b) return a ^ b);
+			case OpOr: iop(function(a, b) return a | b);
+			case OpAnd: iop(function(a, b) return a & b);
+			case OpShr: iop(function(a, b) return a >> b);
+			case OpUShr: iop(function(a, b) return a >>> b);
+			case OpShl: iop(function(a, b) return a << b);
+			case OpBoolAnd: bop(function(a, b) return a && b, false);
+			case OpBoolOr: bop(function(a, b) return a || b, true);
+			case OpEq: compare(function(x) return x == 0);
+			case OpNotEq: compare(function(x) return x != 0);
+			case OpGt: compare(function(x) return x > 0);
+			case OpGte: compare(function(x) return x >= 0);
+			case OpLt: compare(function(x) return x < 0);
+			case OpLte: compare(function(x) return x <= 0);
+			case OpInterval, OpAssign, OpAssignOp(_): TBinop(op, e1, e2);
+			case OpArrow: throw "assert";
+			}
+		case TUnop(op, e):
+			var e = evalExpr(e);
+			switch( e.e ) {
+			case TConst(c):
+				switch( [op, c] ) {
+				case [OpNot, CBool(b)]: TConst(CBool(!b));
+				case [OpNeg, CInt(i)]: TConst(CInt( -i));
+				case [OpNeg, CFloat(f)]: TConst(CFloat( -f));
+				default:
+					TUnop(op, e);
+				}
+			default:
+				TUnop(op, e);
+			}
+		case TParenthesis(e):
+			var e = evalExpr(e);
+			switch( e.e ) {
+			case TConst(_): e.e;
+			default: TParenthesis(e);
+			}
+		case TIf(econd, eif, eelse):
+			var e = evalExpr(econd);
+			switch( e.e ) {
+			case TConst(CBool(b)): return b ? evalExpr(eif) : eelse == null ? { e : TConst(CNull), t : TVoid, p : e.p } : evalExpr(eelse);
+			default:
+				TIf(e, evalExpr(eif), eelse == null ? null : evalExpr(eelse));
+			}
+		case TBreak:
+			TBreak;
+		case TContinue:
+			TContinue;
+		case TDiscard:
+			TDiscard;
+		case TFor(v, it, loop):
+			var v2 = mapVar(v);
+			var it = evalExpr(it);
+			var e = switch( it.e ) {
+			case TBinop(OpInterval, { e : TConst(CInt(start)) }, { e : TConst(CInt(len)) } ):
+				var out = [];
+				var old = varMap;
+				for( i in start...len ) {
+					varMap = [for( c in old.keys() ) c => old.get(c)];
+					constants.set(v, TConst(CInt(i)));
+					out.push(evalExpr(loop));
+				}
+				varMap = old;
+				constants.remove(v);
+				TBlock(out);
+			default:
+				TFor(v2, it, evalExpr(loop));
+			}
+			varMap.remove(v);
+			e;
+		case TArrayDecl(el):
+			TArrayDecl([for( e in el ) evalExpr(e)]);
+		};
+		return { e : d, t : e.t, p : e.p }
+	}
+	
+}

+ 250 - 0
hxsl/Flatten.hx

@@ -0,0 +1,250 @@
+package hxsl;
+using hxsl.Ast;
+
+private class Alloc {
+	public var t : VecType;
+	public var pos : Int;
+	public var size : Int;
+	public var g : TVar;
+	public var v : Null<TVar>;
+	public function new(g, t, pos, size) {
+		this.g = g;
+		this.t = t;
+		this.pos = pos;
+		this.size = size;
+	}
+}
+
+class Flatten {
+	
+	var globals : Array<TVar>;
+	var params : Array<TVar>;
+	var outVars : Array<TVar>;
+	var varMap : Map<TVar,Alloc>;
+	public var allocData : Map< TVar, Array<Alloc> >;
+	
+	public function new() {
+	}
+	
+	public function flatten( s : ShaderData, kind : FunctionKind ) : ShaderData {
+		globals = [];
+		params = [];
+		outVars = [];
+		varMap = new Map();
+		allocData = new Map();
+		for( v in s.vars )
+			gatherVar(v);
+		var prefix = switch( kind ) {
+		case Vertex: "vertex";
+		case Fragment: "fragment";
+		default: throw "assert";
+		}
+		pack(prefix + "Globals", Global, globals, VFloat);
+		pack(prefix + "Params", Param, params, VFloat);
+		packTextures(prefix + "Textures", globals.concat(params), TSampler2D);
+		return {
+			name : s.name,
+			vars : outVars,
+			funs : [for( f in s.funs ) {
+				kind : f.kind,
+				ret : f.ret,
+				args : f.args,
+				ref : f.ref,
+				expr : mapExpr(f.expr),
+			}],
+		};
+	}
+	
+	function mapExpr( e : TExpr ) : TExpr {
+		e = switch( e.e ) {
+		case TVar(v):
+			var a = varMap.get(v);
+			if( a == null )
+				e
+			else
+				access(a, v.type, e.p);
+		default:
+			e.map(mapExpr);
+		};
+		return optimize(e);
+	}
+	
+	function access( a : Alloc, t : Type, pos : Position ) : TExpr {
+		inline function mkInt(v:Int) {
+			return { e : TConst(CInt(v)), t : TInt, p : pos };
+		}
+		inline function read( index : Int ) : TExpr {
+			return { e : TArray({ e : TVar(a.g), t : a.g.type, p : pos },mkInt((a.pos>>2)+index)), t : TVec(4,a.t), p : pos }
+		}
+		switch( t ) {
+		case TMat4:
+			return { e : TCall( { e : TGlobal(Mat4), t : TFun([]), p : pos }, [
+				read(0),
+				read(1),
+				read(2),
+				read(3),
+			]), t : TMat4, p : pos }
+		case TMat3x4:
+			return { e : TCall( { e : TGlobal(Mat3x4), t : TFun([]), p : pos }, [
+				read(0),
+				read(1),
+				read(2),
+			]), t : TMat3x4, p : pos }
+		case TArray(t, SConst(len)):
+			var stride = Std.int(a.size / len);
+			return { e : TArrayDecl([for( i in 0...len ) access(new Alloc(a.g, a.t, a.pos + stride * i, stride), t, pos)]), t : t, p : pos };
+		case TSampler2D, TSamplerCube:
+			return read(a.pos);
+		default:
+			var size = varSize(t, a.t);
+			if( size <= 4 ) {
+				var k = read(0);
+				if( size == 4 ) {
+					if( a.pos & 3 != 0 ) throw "assert";
+					return k;
+				} else {
+					var sw = [];
+					for( i in 0...size )
+						sw.push(Tools.SWIZ[i + (a.pos & 3)]);
+					return { e : TSwiz(k, sw), t : t, p : pos };
+				}
+			}
+			return Error.t("Access not supported for " + t.toString(), null);
+		}
+	}
+
+	
+	function optimize( e : TExpr ) {
+		switch( e.e ) {
+		case TCall( { e : TGlobal(Mat3x4) }, [ { e : TCall( { e : TGlobal(Mat4) }, args) } ]):
+			var rem = 0;
+			var size = 0;
+			while( size < 4 ) {
+				var t = args[args.length - 1 - rem].t;
+				size += varSize(t,VFloat);
+				rem++;
+			}
+			if( size == 4 ) {
+				for( i in 0...rem )
+					args.pop();
+				var emat = switch( e.e ) { case TCall(e, _): e; default: throw "assert"; };
+				return { e : TCall(emat, args), t : e.t, p : e.p };
+			}
+		case TArray( { e : TArrayDecl(el) }, { e : TConst(CInt(i)) } ) if( i >= 0 && i < el.length ):
+			return el[i];
+		default:
+		}
+		return e;
+	}
+	
+	function packTextures( name : String, vars : Array<TVar>, t : Type ) {
+		var alloc = new Array<Alloc>();
+		var g : TVar = {
+			id : Tools.allocVarId(),
+			name : name,
+			type : t,
+			kind : Param,
+		};
+		for( v in vars ) {
+			if( v.type != t ) continue;
+			var a = new Alloc(g, null, alloc.length, 1);
+			a.v = v;
+			varMap.set(v, a);
+			alloc.push(a);
+		}
+		g.type = TArray(t, SConst(alloc.length));
+		if( alloc.length > 0 ) {
+			outVars.push(g);
+			allocData.set(g, alloc);
+		}
+		return g;
+	}
+	
+	function pack( name : String, kind : VarKind, vars : Array<TVar>, t : VecType ) {
+		var alloc = new Array<Alloc>(), apos = 0;
+		var g : TVar = {
+			id : Tools.allocVarId(),
+			name : name,
+			type : TVec(0,t),
+			kind : kind,
+		};
+		for( v in vars ) {
+			switch( v.type ) {
+			case TSampler2D, TSamplerCube:
+				continue;
+			default:
+			}
+			var size = varSize(v.type, t);
+			var best : Alloc = null;
+			for( a in alloc )
+				if( a.v == null && a.size >= size && (best == null || best.size > a.size) )
+					best = a;
+			if( best != null ) {
+				var free = best.size - size;
+				if( free > 0 ) {
+					var i = Lambda.indexOf(alloc, best);
+					var a = new Alloc(g, t, best.pos + size, free);
+					alloc.insert(i + 1, a);
+					best.size = size;
+				}
+				best.v = v;
+				varMap.set(v, best);
+			} else {
+				var a = new Alloc(g, t, apos, size);
+				apos += size;
+				a.v = v;
+				varMap.set(v, a);
+				alloc.push(a);
+				var pad = (4 - (size % 4)) % 4;
+				if( pad > 0 ) {
+					var a = new Alloc(g, t, apos, pad);
+					apos += pad;
+					alloc.push(a);
+				}
+			}
+		}
+		g.type = TArray(TVec(4, t), SConst(apos >> 2));
+		if( apos > 0 ) {
+			outVars.push(g);
+			allocData.set(g, alloc);
+		}
+		return g;
+	}
+	
+	function varSize( v : Type, t : VecType ) {
+		return switch( v ) {
+		case TFloat if( t == VFloat ): 1;
+		case TVec(n, t2) if( t == t2 ): n;
+		case TMat4 if( t == VFloat ): 16;
+		case TMat3x4 if( t == VFloat ): 12;
+		case TMat3 if( t == VFloat ): 9;
+		case TArray(at, SConst(n)):
+			var s = varSize(at, t);
+			s += (4 - (s & 3)) & 3;
+			s * n;
+		default:
+			throw v.toString() + " size unknown for type " + t;
+		}
+	}
+	
+	function gatherVar( v : TVar ) {
+		switch( v.type ) {
+		case TStruct(vl):
+			for( v in vl )
+				gatherVar(v);
+		default:
+			switch( v.kind ) {
+			case Global:
+				if( v.hasQualifier(PerObject) )
+					params.push(v);
+				else
+					globals.push(v);
+			case Param:
+				params.push(v);
+			default:
+				outVars.push(v);
+			}
+		}
+	}
+	
+}

+ 48 - 0
hxsl/Globals.hx

@@ -0,0 +1,48 @@
+package hxsl;
+
+abstract GlobalSlot<T>(Int) {
+	public inline function new(name:String) {
+		this = Globals.allocID(name);
+	}
+	public inline function set( globals : Globals, v : T ) {
+		globals.fastSet(this, v);
+	}
+}
+
+class Globals {
+
+	var map : Map<Int,Dynamic>;
+	
+	public function new() {
+		map = new Map<Int,Dynamic>();
+	}
+	
+	public function set( path : String, v : Dynamic ) {
+		map.set(allocID(path), v);
+	}
+
+	public function get( path : String) : Dynamic {
+		return map.get(allocID(path));
+	}
+
+	public inline function fastSet( id : Int, v : Dynamic ) {
+		map.set(id, v);
+	}
+
+	public inline function fastGet( id : Int ) : Dynamic {
+		return map.get(id);
+	}
+
+	static var ALL = [];
+	static var MAP = new Map();
+	public static function allocID( path : String ) {
+		var id = MAP.get(path);
+		if( id == null ) {
+			id = ALL.length;
+			ALL.push(path);
+			MAP.set(path, id);
+		}
+		return id;
+	}
+	
+}

+ 317 - 0
hxsl/GlslOut.hx

@@ -0,0 +1,317 @@
+package hxsl;
+import hxsl.Ast;
+
+class GlslOut {
+
+	var buf : StringBuf;
+	var keywords : Map<String,Bool>;
+	var exprValues : Array<String>;
+	var locals : Array<TVar>;
+	var globalNames : Map<TGlobal,String>;
+	
+	public function new() {
+		keywords = [ "input" => true, "output" => true, "discard" => true ];
+		globalNames = new Map();
+		for( g in hxsl.Ast.TGlobal.createAll() ) {
+			var n = "" + g;
+			n = n.charAt(0).toLowerCase() + n.substr(1);
+			globalNames.set(g, n);
+		}
+	}
+	
+	inline function add( v : Dynamic ) {
+		buf.add(v);
+	}
+	
+	function ident( i : String ) {
+		add(keywords.exists(i) ? "_" + i : i);
+	}
+	
+	function addType( t : Type ) {
+		switch( t ) {
+		case TVoid:
+			add("void");
+		case TInt:
+			add("int");
+		case TBool:
+			add("bool");
+		case TFloat:
+			add("float");
+		case TString:
+			add("string");
+		case TVec(size, k):
+			switch( k ) {
+			case VFloat:
+			case VInt: add("i");
+			case VBool: add("b");
+			}
+			add("vec");
+			add(size);
+		case TMat3:
+			add("mat3");
+		case TMat4:
+			add("mat4");
+		case TMat3x4:
+			add("mat3x4");
+		case TSampler2D:
+			add("sampler2D");
+		case TSamplerCube:
+			add("samplerCube");
+		case TStruct(vl):
+			add("struct { ");
+			for( v in vl ) {
+				addVar(v);
+				add(";");
+			}
+			add(" }");
+		case TFun(_):
+			add("function");
+		case TArray(t, size):
+			addType(t);
+			add("[");
+			switch( size ) {
+			case SVar(v):
+				ident(v.name);
+			case SConst(v):
+				add(v);
+			}
+			add("]");
+		}
+	}
+	
+	function addVar( v : TVar ) {
+		switch( v.type ) {
+		case TArray(t, size):
+			var old = v.type;
+			v.type = t;
+			addVar(v);
+			v.type = old;
+			add("[");
+			switch( size ) {
+			case SVar(v): ident(v.name);
+			case SConst(n): add(n);
+			}
+			add("]");
+		default:
+			addType(v.type);
+			add(" ");
+			ident(v.name);
+		}
+	}
+	
+	function addValue( e : TExpr, tabs : String ) {
+		switch( e.e ) {
+		case TBlock(el):
+			var name = "val" + exprValues.length;
+			var tmp = buf;
+			buf = new StringBuf();
+			addType(e.t);
+			add(" ");
+			add(name);
+			add("(void)");
+			var el2 = el.copy();
+			var last = el2[el2.length - 1];
+			el2[el2.length - 1] = { e : TReturn(last), t : e.t, p : last.p };
+			var e2 = {
+				t : TVoid,
+				e : TBlock(el2),
+				p : e.p,
+			};
+			addExpr(e2, "");
+			exprValues.push(buf.toString());
+			buf = tmp;
+			add(name);
+			add("()");
+		default:
+			addExpr(e, tabs);
+		}
+	}
+	
+	function addExpr( e : TExpr, tabs : String ) {
+		switch( e.e ) {
+		case TConst(c):
+			switch( c ) {
+			case CInt(v): add(v);
+			case CFloat(f):
+				var str = "" + f;
+				add(str);
+				if( str.indexOf(".") == -1 && str.indexOf("e") == -1 )
+					add(".");
+			case CString(v): add('"' + v + '"');
+			case CNull: add("null");
+			case CBool(b): add(b);
+			}
+		case TVar(v):
+			ident(v.name);
+		case TGlobal(g):
+			add(globalNames.get(g));
+		case TParenthesis(e):
+			add("(");
+			addValue(e,tabs);
+			add(")");
+		case TBlock(el):
+			add("{\n");
+			var t2 = tabs + "\t";
+			for( e in el ) {
+				add(t2);
+				addExpr(e, t2);
+				add(";\n");
+			}
+			add(tabs);
+			add("}");
+		case TBinop(op, e1, e2):
+			switch( [op, e1.t, e2.t] ) {
+			case [OpMult, TVec(3,VFloat), TMat3x4]:
+				add("m3x4mult(");
+				addValue(e1, tabs);
+				add(",");
+				addValue(e2, tabs);
+				add(")");
+			default:
+				addValue(e1, tabs);
+				add(" ");
+				add(Printer.opStr(op));
+				add(" ");
+				addValue(e2, tabs);
+			}
+		case TUnop(op, e1):
+			add(switch(op) {
+			case OpNot: "!";
+			case OpNeg: "-";
+			case OpIncrement: "++";
+			case OpDecrement: "--";
+			case OpNegBits: "~";
+			});
+			addValue(e1, tabs);
+		case TVarDecl(v, init):
+			locals.push(v);
+			if( init != null ) {
+				ident(v.name);
+				add(" = ");
+				addValue(init, tabs);
+			} else {
+				add("/*var*/");
+			}
+		case TCall(e, args):
+			addValue(e, tabs);
+			add("(");
+			var first = true;
+			for( e in args ) {
+				if( first ) first = false else add(", ");
+				addValue(e, tabs);
+			}
+			add(")");
+		case TSwiz(e, regs):
+			addValue(e, tabs);
+			add(".");
+			for( r in regs )
+				add(switch(r) {
+				case X: "x";
+				case Y: "y";
+				case Z: "z";
+				case W: "w";
+				});
+		case TIf(econd, eif, eelse):
+			add("if( ");
+			addValue(econd, tabs);
+			add(") ");
+			addExpr(eif, tabs);
+			if( eelse != null ) {
+				add(" else ");
+				addExpr(eelse, tabs);
+			}
+		case TDiscard:
+			add("discard");
+		case TReturn(e):
+			if( e == null )
+				add("return");
+			else {
+				add("return ");
+				addValue(e, tabs);
+			}
+		case TFor(v, it, loop):
+			add("for(...)");
+		case TContinue:
+			add("continue");
+		case TBreak:
+			add("break");
+		case TArray(e, index):
+			addValue(e, tabs);
+			add("[");
+			addValue(index, tabs);
+			add("]");
+		case TArrayDecl(el):
+			add("[");
+			var first = true;
+			for( e in el ) {
+				if( first ) first = false else add(", ");
+				addValue(e,tabs);
+			}
+			add("]");
+		}
+	}
+	
+	public function run( s : ShaderData ) {
+		locals = [];
+		buf = new StringBuf();
+		exprValues = [];
+		add("precision mediump float;\n");
+		add("struct mat3x4 { vec4 a; vec4 b; vec4 c; };\n");
+		add("vec3 m3x4mult( vec3 v, mat3x4 m) { vec4 ve = vec4(v,1.0); return vec3(dot(m.a,ve),dot(m.b,ve),dot(m.c,ve)); }\n");
+
+		if( s.funs.length != 1 ) throw "assert";
+		var f = s.funs[0];
+		
+		for( v in s.vars ) {
+			switch( v.kind ) {
+			case Param, Global:
+				add("uniform ");
+			case Input:
+				add("attribute ");
+			case Var:
+				add("varying ");
+			case Function: continue;
+			case Output:
+				switch( f.kind ) {
+				case Vertex:
+					v.name = "gl_Position";
+				case Fragment:
+					v.name = "gl_FragColor";
+				default:
+					throw "assert";
+				}
+				continue;
+			case Local:
+			}
+			addVar(v);
+			add(";\n");
+		}
+		add("\n");
+		
+		var tmp = buf;
+		buf = new StringBuf();
+		add("void main(void) ");
+		addExpr(f.expr, "");
+		exprValues.push(buf.toString());
+		buf = tmp;
+		
+		for( v in locals ) {
+			addVar(v);
+			add(";\n");
+		}
+		add("\n");
+
+		for( e in exprValues ) {
+			add(e);
+			add("\n\n");
+		}
+		var content = buf.toString();
+		buf = null;
+		return content;
+	}
+	
+	public static function toGlsl( s : ShaderData ) {
+		return new GlslOut().run(s);
+	}
+	
+}

+ 412 - 0
hxsl/Linker.hx

@@ -0,0 +1,412 @@
+package hxsl;
+using hxsl.Ast;
+
+private class AllocatedVar {
+	public var id : Int;
+	public var v : TVar;
+	public var path : String;
+	public var merged : Array<TVar>;
+	public var kind : Null<FunctionKind>;
+	public var parent : AllocatedVar;
+	public var instanceIndex : Int;
+	public function new() {
+	}
+}
+
+private class ShaderInfos {
+	public var name : String;
+	public var priority : Int;
+	public var body : TExpr;
+	public var usedFunctions : Array<TFunction>;
+	public var deps : Map<ShaderInfos, Bool>;
+	public var read : Map<Int,AllocatedVar>;
+	public var write : Map<Int,AllocatedVar>;
+	public var processed : Map<Int, Bool>;
+	public var vertex : Bool;
+	public var onStack : Bool;
+	public var marked : Null<Bool>;
+	public function new(n, v) {
+		this.name = n;
+		this.vertex = v;
+		processed = new Map();
+		usedFunctions = [];
+		read = new Map();
+		write = new Map();
+	}
+}
+
+class Linker {
+
+	public var allVars : Array<AllocatedVar>;
+	var varMap : Map<String,AllocatedVar>;
+	var curShader : ShaderInfos;
+	var shaders : Array<ShaderInfos>;
+	var varIdMap : Map<Int,Int>;
+	var locals : Map<Int,Bool>;
+	var curInstance : Int;
+	
+	public function new() {
+	}
+	
+	function error( msg : String, p : Position ) : Dynamic {
+		return Error.t(msg, p);
+	}
+	
+	function mergeVar( path : String, v : TVar, v2 : TVar, p : Position ) {
+		switch( v.kind ) {
+		case Global, Input, Var, Local, Output:
+			// shared vars
+		case Param, Function:
+			throw "assert";
+		}
+		if( v.kind != v2.kind )
+			error("'" + path + "' kind does not match : " + v.kind + " should be " + v2.kind,p);
+		switch( [v.type, v2.type] ) {
+		case [TStruct(fl1), TStruct(fl2)]:
+			for( f1 in fl1 ) {
+				var ft = null;
+				for( f2 in fl2 )
+					if( f1.name == f2.name ) {
+						ft = f2;
+						break;
+					}
+				// add a new field
+				if( ft == null )
+					fl2.push(f1);
+				else
+					mergeVar(path + "." + ft.name, f1, ft, p);
+			}
+		default:
+			if( !v.type.equals(v2.type) )
+				error("'" + path + "' type does not match : " + v.type.toString() + " should be " + v2.type.toString(),p);
+		}
+	}
+	
+	function allocVar( v : TVar, p : Position, ?path : String, ?parent : TVar ) : AllocatedVar {
+		if( v.parent != null && parent == null ) {
+			parent = allocVar(v.parent, p).v;
+			var p = parent;
+			path = p.name;
+			p = p.parent;
+			while( p != null ) {
+				path = p.name + "." + path;
+				p = p.parent;
+			}
+		}
+		var key = (path == null ? v.name : path + "." + v.name);
+		if( v.qualifiers != null )
+			for( q in v.qualifiers )
+				switch( q ) {
+				case Name(n): key = n;
+				default:
+				}
+		var v2 = varMap.get(key);
+		var vname = v.name;
+		if( v2 != null ) {
+			for( vm in v2.merged )
+				if( vm == v )
+					return v2;
+			if( v.kind == Param || v.kind == Function || (v.kind == Var && v.hasQualifier(Private)) ) {
+				// allocate a new unique name in the shader if already in use
+				var k = 2;
+				while( true ) {
+					var a = varMap.get(key + k);
+					if( a == null ) break;
+					for( vm in a.merged )
+						if( vm == v )
+							return a;
+					k++;
+				}
+				vname += k;
+				key += k;
+			} else {
+				mergeVar(key, v, v2.v, p);
+				v2.merged.push(v);
+				varIdMap.set(v.id, v2.id);
+				return v2;
+			}
+		}
+		var vid = allVars.length;
+		var v2 : TVar = {
+			id : vid,
+			name : vname,
+			type : v.type,
+			kind : v.kind,
+			qualifiers : v.qualifiers,
+			parent : parent,
+		};
+		var a = new AllocatedVar();
+		a.v = v2;
+		a.merged = [v];
+		a.path = key;
+		a.id = vid;
+		a.parent = parent == null ? null : allocVar(parent, p);
+		a.instanceIndex = curInstance;
+		allVars.push(a);
+		varMap.set(key, a);
+		switch( v2.type ) {
+		case TStruct(vl):
+			v2.type = TStruct([for( v in vl ) allocVar(v, p, key, v2).v]);
+		default:
+		}
+		return a;
+	}
+	
+	function mapExprVar( e : TExpr ) {
+		switch( e.e ) {
+		case TVar(v) if( !locals.exists(v.id) ):
+			var v = allocVar(v, e.p);
+			if( curShader != null ) {
+				//trace(curShader.name + " read " + v.path);
+				curShader.read.set(v.id, v);
+			}
+			return { e : TVar(v.v), t : v.v.type, p : e.p };
+		case TBinop(op, e1, e2):
+			switch( [op,e1.e] ) {
+			case [OpAssign | OpAssignOp(_), (TVar(v) | TSwiz({ e : TVar(v) },_))] if( !locals.exists(v.id) ):
+				var v = allocVar(v, e1.p);
+				if( curShader != null ) {
+					//trace(curShader.name + " write " + v.path);
+					curShader.write.set(v.id, v);
+				}
+				// TSwiz might only assign part of the components, let's then consider that we read the other
+				if( op == OpAssign && (switch( e1.e ) { case TVar(_): true; default: false; }) ) {
+					var eout = { e : TVar(v.v), t : e1.t, p : e1.p };
+					return { e : TBinop(OpAssign, eout, mapExprVar(e2)), t : e.t, p : e.p };
+				}
+			default:
+			}
+		case TVarDecl(v, _):
+			locals.set(v.id, true);
+		default:
+		}
+		return e.map(mapExprVar);
+	}
+	
+	function addShader( name : String, vertex : Bool, e : TExpr, p : Int ) {
+		curShader = new ShaderInfos(name, vertex);
+		curShader.priority = p;
+		curShader.body = mapExprVar(e);
+		shaders.push(curShader);
+		curShader = null;
+	}
+	
+	function sortByPriorityDesc( s1 : ShaderInfos, s2 : ShaderInfos ) {
+		return s2.priority - s1.priority;
+	}
+	
+	function buildDependency( parent : ShaderInfos, v : AllocatedVar, isWritten : Bool ) {
+		var found = !isWritten;
+		for( s in shaders ) {
+			if( parent == s ) {
+				found = true;
+				continue;
+			} else if( !found )
+				continue;
+			if( !s.write.exists(v.id) )
+				continue;
+//			trace(parent.name + " => " + s.name + " (" + v.path + ")");
+			parent.deps.set(s, true);
+			if( s.deps == null ) {
+				s.deps = new Map();
+				for( r in s.read )
+					buildDependency(s, r, s.write.exists(r.id));
+			}
+			if( !s.read.exists(v.id) )
+				return;
+		}
+		if( v.v.kind == Var )
+			error("Variable " + v.path + " required by " + parent.name + " is missing initializer", null);
+	}
+	
+	function collect( cur : ShaderInfos, out : Array<ShaderInfos>, vertex : Bool ) {
+		if( cur.onStack )
+			error("Loop in shader dependencies ("+cur.name+")", null);
+		if( cur.marked == vertex )
+			return;
+		cur.marked = vertex;
+		cur.onStack = true;
+		for( d in cur.deps.keys() )
+			collect(d, out, vertex);
+		if( cur.vertex == vertex )
+			out.push(cur);
+		cur.onStack = false;
+	}
+	
+	function uniqueLocals( expr : TExpr, locals : Map < String, Bool > ) {
+		switch( expr.e ) {
+		case TVarDecl(v, _):
+			if( locals.exists(v.name) ) {
+				var k = 2;
+				while( locals.exists(v.name + k) )
+					k++;
+				v.name += k;
+			}
+			locals.set(v.name, true);
+		case TBlock(el):
+			var locals = [for( k in locals.keys() ) k => true];
+			for( e in el )
+				uniqueLocals(e, locals);
+		default:
+			expr.iter(uniqueLocals.bind(_, locals));
+		}
+	}
+	
+	public function link( shadersData : Array<ShaderData>, outVars : Array<String> ) : ShaderData {
+		varMap = new Map();
+		varIdMap = new Map();
+		allVars = new Array();
+		shaders = [];
+		locals = new Map();
+		
+		var dupShaders = new Map();
+		shadersData = [for( s in shadersData ) {
+			var s = s, sreal = s;
+			if( dupShaders.exists(s) )
+				s = Clone.shaderData(s);
+			dupShaders.set(s, sreal);
+			s;
+		}];
+		
+		// globalize vars
+		curInstance = 0;
+		for( s in shadersData ) {
+			for( v in s.vars )
+				allocVar(v, null);
+			for( f in s.funs ) {
+				var v = allocVar(f.ref, f.expr.p);
+				v.kind = f.kind;
+			}
+			curInstance++;
+		}
+		
+		// create shader segments
+		var priority = 0;
+		for( s in shadersData ) {
+			for( f in s.funs ) {
+				var v = allocVar(f.ref, f.expr.p);
+				if( v.kind == null ) throw "assert";
+				switch( v.kind ) {
+				case Vertex, Fragment:
+					addShader(s.name+"."+(v.kind == Vertex ? "vertex" : "fragment"),v.kind == Vertex,f.expr, priority);
+				case Init:
+					switch( f.expr.e ) {
+					case TBlock(el):
+						var index = 0;
+						for( e in el )
+							addShader(s.name+".__init__"+(index++),true,e, -1);
+					default:
+						addShader(s.name+".__init__",true,f.expr, -1);
+					}
+				case Helper:
+					throw "Unexpected helper function in linker "+v.v.name;
+				}
+			}
+			priority++;
+		}
+		shaders.sort(sortByPriorityDesc);
+		
+		// build dependency tree
+		var s = new ShaderInfos("", true);
+		s.deps = new Map();
+		for( outVar in outVars ) {
+			var v = varMap.get(outVar);
+			if( v == null )
+				throw "Variable not found " + outVar;
+			buildDependency(s, v, false);
+		}
+		
+		// collect needed dependencies
+		var v = [], f = [];
+		collect(s, v, true);
+		collect(s, f, false);
+		if( v.pop().name != "" ) throw "assert";
+		
+		// check that all dependencies are matched
+		for( s in shaders )
+			s.marked = null;
+		for( s in v.concat(f) ) {
+			for( d in s.deps.keys() )
+				if( d.marked == null )
+					error(d.name + " needed by " + s.name + " is unreachable", null);
+			s.marked = true;
+		}
+		
+		// build resulting vars
+		var outVars = [];
+		var varMap = new Map();
+		function addVar(v:AllocatedVar) {
+			if( varMap.exists(v.id) )
+				return;
+			varMap.set(v.id, true);
+			if( v.v.parent != null )
+				addVar(v.parent);
+			else
+				outVars.push(v.v);
+		}
+		for( s in v.concat(f) ) {
+			for( v in s.read )
+				addVar(v);
+			for( v in s.write )
+				addVar(v);
+		}
+		// cleanup unused structure vars
+		function cleanVar( v : TVar ) {
+			switch( v.type ) {
+			case TStruct(vl) if( v.kind != Input ):
+				var vout = [];
+				for( v in vl )
+					if( varMap.exists(v.id) ) {
+						cleanVar(v);
+						vout.push(v);
+					}
+				v.type = TStruct(vout);
+			default:
+			}
+		}
+		for( v in outVars )
+			cleanVar(v);
+		// build resulting shader functions
+		function build(kind, name, a:Array<ShaderInfos> ) : TFunction {
+			var v : TVar = {
+				id : Tools.allocVarId(),
+				name : name,
+				type : TFun([ { ret : TVoid, args : [] } ]),
+				kind : Function,
+			};
+			outVars.push(v);
+			var exprs = [];
+			for( s in a )
+				switch( s.body.e ) {
+				case TBlock(el):
+					for( e in el ) exprs.push(e);
+				default:
+					exprs.push(s.body);
+				}
+			var expr = { e : TBlock(exprs), t : TVoid, p : exprs.length == 0 ? null : exprs[0].p };
+			uniqueLocals(expr, new Map());
+			return {
+				kind : kind,
+				ref : v,
+				ret : TVoid,
+				args : [],
+				expr : expr,
+			};
+		}
+		var funs = [
+			build(Vertex, "vertex", v),
+			build(Fragment, "fragment", f),
+		];
+		
+		// make sure the first merged var is the original for duplicate shaders
+		for( s in dupShaders.keys() ) {
+			var sreal = dupShaders.get(s);
+			if( s == sreal ) continue;
+			for( i in 0...s.vars.length )
+				allocVar(s.vars[i],null).merged.unshift(sreal.vars[i]);
+		}
+		
+		return { name : "out", vars : outVars, funs : funs };
+	}
+	
+}

+ 205 - 0
hxsl/MacroParser.hx

@@ -0,0 +1,205 @@
+package hxsl;
+import haxe.macro.Expr;
+using haxe.macro.Tools;
+
+class MacroParser {
+
+	public function new() {
+	}
+	
+	function error( msg : String, pos : Position ) : Dynamic {
+		return Ast.Error.t(msg,pos);
+	}
+	
+	function applyMeta( m : MetadataEntry, v : Ast.VarDecl ) {
+		switch( m.params ) {
+		case []:
+		case [ { expr : EConst(CString(n)), pos : pos } ] if( m.name == "var" || m.name == "global" || m.name == "input" ):
+			v.qualifiers.push(Name(n));
+		case [ { expr : EConst(CInt(n)), pos : pos } ] if( m.name == "const" ):
+			v.qualifiers.push(Const(Std.parseInt(n)));
+			return;
+		default:
+			error("Invalid meta parameter", m.pos);
+		}
+		switch( m.name ) {
+		case "var":
+			if( v.kind == null ) v.kind = Var else error("Duplicate type qualifier", m.pos);
+		case "global":
+			if( v.kind == null ) v.kind = Global else error("Duplicate type qualifier", m.pos);
+		case "param":
+			if( v.kind == null ) v.kind = Param else error("Duplicate type qualifier", m.pos);
+		case "input":
+			if( v.kind == null ) v.kind = Input else error("Duplicate type qualifier", m.pos);
+		case "const":
+			v.qualifiers.push(Const());
+		case "private":
+			v.qualifiers.push(Private);
+		case "nullable":
+			v.qualifiers.push(Nullable);
+		case "perObject":
+			v.qualifiers.push(PerObject);
+		default:
+			error("Unsupported qualifier " + m.name, m.pos);
+		}
+	}
+	
+	public function parseType( t : ComplexType, pos : Position ) : Ast.Type {
+		switch( t ) {
+		case TPath( { pack : [], name : name, sub : null, params : [] } ):
+			switch( name ) {
+			case "Int": return TInt;
+			case "Bool": return TBool;
+			case "Float": return TFloat;
+			case "Vec2": return TVec(2,VFloat);
+			case "Vec3": return TVec(3,VFloat);
+			case "Vec4": return TVec(4,VFloat);
+			case "IVec2": return TVec(2,VInt);
+			case "IVec3": return TVec(3,VInt);
+			case "IVec4": return TVec(4,VInt);
+			case "BVec2": return TVec(2,VBool);
+			case "BVec3": return TVec(3,VBool);
+			case "BVec4": return TVec(4,VBool);
+			case "Mat4": return TMat4;
+			case "Mat3": return TMat3;
+			case "Mat3x4": return TMat3x4;
+			case "String": return TString;
+			case "Sampler2D": return TSampler2D;
+			case "SamplerCube": return TSamplerCube;
+			}
+		case TPath( { pack : [], name : "Array", sub : null, params : [t, size] } ):
+			var t = switch( t ) {
+			case TPType(t): parseType(t, pos);
+			default: null;
+			}
+			var size : Ast.SizeDecl = switch( size ) {
+			case TPExpr({ expr : EConst(CInt(v)) }): SConst(Std.parseInt(v));
+			case TPType(TPath( { pack : pack, name : name, sub : null, params : [] } )):
+				var pack = pack.copy();
+				pack.push(name);
+				SVar( { id : 0, type : null, name : pack.join("."), kind : null } );
+			default: null;
+			}
+			if( t != null && size != null )
+				return TArray(t, size);
+		case TAnonymous(fl):
+			return TStruct([for( f in fl ) {
+				switch( f.kind ) {
+				case FVar(t,e):
+					if( e != null ) error("No expression allowed in structure", e.pos);
+					var v : Ast.VarDecl = {
+						name : f.name,
+						type : t == null ? null : parseType(t, f.pos),
+						qualifiers : [],
+						kind : null,
+						expr : null,
+					};
+					for( m in f.meta )
+						applyMeta(m,v);
+					{ id : 0, name : v.name, type : v.type, kind : v.kind, qualifiers : v.qualifiers };
+				default:
+					error("Only variables are allowed in structures", f.pos);
+				}
+			}]);
+		default:
+		}
+		error("Unsupported type " + t.toString(), pos);
+		return null;
+	}
+	
+	public function parseExpr( e : Expr ) : Ast.Expr {
+		var ed : Ast.ExprDef = switch( e.expr ) {
+		case EBlock(el):
+			EBlock([for( e in el ) parseExpr(e)]);
+		case EMeta(m, e):
+			var e2 = parseExpr(e);
+			switch( e2.expr ) {
+			case EVars(vl):
+				for( v in vl )
+					applyMeta(m, v);
+				e2.expr;
+			default:
+				error("Qualifier only supported before 'var'", e.pos);
+			}
+		case EVars(vl):
+			EVars([for( v in vl ) {
+				{
+					name : v.name,
+					expr : v.expr == null ? null : parseExpr(v.expr),
+					type : v.type == null ? null : parseType(v.type, e.pos),
+					kind : null,
+					qualifiers : [],
+				}
+			}]);
+		case EFunction(name, f) if( name != null && f.expr != null ):
+			EFunction({
+				name : name,
+				ret : f.ret == null ? null : (switch( f.ret ) {
+					case TPath( { pack:[], name:"Void", sub:null } ): TVoid;
+					default: parseType(f.ret, e.pos);
+				}),
+				args : [for( a in f.args ) {
+					{
+						name : a.name,
+						type : a.type == null ? null : parseType(a.type, e.pos),
+						kind : Local,
+						qualifiers : [],
+						expr : a.value == null ? (a.opt ? { expr : EConst(CNull), pos : e.pos } : null) : parseExpr(a.value),
+					}
+				}],
+				expr : parseExpr(f.expr),
+			});
+		case EBinop(op, e1, e2):
+			EBinop(op, parseExpr(e1), parseExpr(e2));
+		case EUnop(op, false, e1):
+			EUnop(op, parseExpr(e1));
+		case EConst(c):
+			switch( c ) {
+			case CString(s):
+				EConst(CString(s));
+			case CInt(v):
+				EConst(CInt(Std.parseInt(v)));
+			case CIdent("null"):
+				EConst(CNull);
+			case CIdent("true"):
+				EConst(CBool(true));
+			case CIdent("false"):
+				EConst(CBool(false));
+			case CIdent("discard"):
+				EDiscard;
+			case CIdent(s):
+				EIdent(s);
+			case CFloat(f):
+				EConst(CFloat(Std.parseFloat(f)));
+			case CRegexp(_):
+				null;
+			}
+		case EField(e, f):
+			EField(parseExpr(e), f);
+		case ECall(e, args):
+			ECall(parseExpr(e), [for( a in args ) parseExpr(a)]);
+		case EParenthesis(e):
+			EParenthesis(parseExpr(e));
+		case EIf(cond, eif, eelse), ETernary(cond, eif, eelse):
+			EIf(parseExpr(cond), parseExpr(eif), eelse == null ? null : parseExpr(eelse));
+		case EFor( { expr : EIn( { expr : EConst(CIdent(n)) }, eloop) }, eblock):
+			EFor(n, parseExpr(eloop), parseExpr(eblock));
+		case EReturn(e):
+			EReturn(e == null ? null : parseExpr(e));
+		case EBreak:
+			EBreak;
+		case EContinue:
+			EContinue;
+		case EArray(e1, e2):
+			EArray(parseExpr(e1), parseExpr(e2));
+		case EArrayDecl(el):
+			EArrayDecl([for( e in el ) parseExpr(e)]);
+		default:
+			null;
+		};
+		if( ed == null )
+			error("Unsupported expression", e.pos);
+		return { expr : ed, pos : e.pos };
+	}
+
+}

+ 276 - 0
hxsl/Macros.hx

@@ -0,0 +1,276 @@
+package hxsl;
+import haxe.macro.Context;
+import haxe.macro.Expr;
+using hxsl.Ast;
+
+class Macros {
+
+	static function makeType( t : Type ) : ComplexType {
+		return switch( t ) {
+		case TVoid: macro : Void;
+		case TVec(_, t):
+			switch( t ) {
+			case VFloat:
+				macro : hxsl.Types.Vec;
+			case VInt:
+				macro : hxsl.Types.IVec;
+			case VBool:
+				macro : hxsl.Types.BVec;
+			}
+		case TStruct(vl):
+			TAnonymous([for( v in vl ) { pos : Context.currentPos(), name : v.name, kind : FVar(makeType(v.type)) } ]);
+		case TSampler2D:
+			macro : hxsl.Types.Sampler2D;
+		case TSamplerCube:
+			macro : hxsl.Types.SamplerCube;
+		case TMat3, TMat3x4, TMat4:
+			macro : hxsl.Types.Matrix;
+		case TString:
+			macro : String;
+		case TInt:
+			macro : Int;
+		case TFloat:
+			macro : Float;
+		case TBool:
+			macro : Bool;
+		case TArray(t, _):
+			var t = makeType(t);
+			macro : Array<$t>;
+		case TFun(_):
+			throw "assert";
+		}
+	}
+	
+	static function makeDef( t : Type, pos : Position ) : haxe.macro.Expr {
+		return switch( t ) {
+		case TFloat, TInt: macro 0;
+		case TVec(_, t):
+			switch( t ) {
+			case VFloat:
+				macro new hxsl.Types.Vec();
+			case VInt:
+				macro new hxsl.Types.IVec();
+			case VBool:
+				macro new hxsl.Types.BVec();
+			}
+		case TStruct(vl):
+			{ expr : EObjectDecl([for( v in vl ) { field : v.name, expr : makeDef(v.type, pos) } ]), pos : pos };
+		case TArray(_):
+			macro new Array();
+		default:
+			null;
+		}
+	}
+	
+	static function getConsts( v : TVar, p : Position, consts : Array<{ pos : Int, bits : Int, v : TVar }> ) {
+		switch( v.type ) {
+		case TStruct(vl):
+			for( v in vl )
+				getConsts(v, p, consts);
+		default:
+			if( v.isConst() ) {
+				var bits = v.getConstBits();
+				if( bits == 0 )
+					Error.t("Unsupported @const type "+v.type.toString(), p);
+				var prev = consts[consts.length - 1];
+				var pos = prev == null ? 0 : prev.pos + prev.bits;
+				if( pos + bits > 32 )
+					Error.t("Too many constants for this shader, reduce int size by specifiying @const(max-value)", p);
+				consts.push({ pos : pos, bits : bits, v : v });
+			}
+		}
+	}
+	
+	static function buildFields( shader : ShaderData, pos : Position ) {
+		var fields = new Array<Field>();
+		var globals = [], consts = [], params = [];
+		for( v in shader.vars ) {
+			var cpos = consts.length;
+			getConsts(v, pos, consts);
+			switch( v.kind ) {
+			case Param:
+				var t = makeType(v.type);
+				var f : Field = {
+					name : v.name,
+					pos : pos,
+					kind : FProp("get","set", t),
+					access : [APublic],
+				};
+				var name = v.name + "__";
+				var f2 : Field = {
+					name : name,
+					pos : pos,
+					kind : FVar(t, makeDef(v.type, pos)),
+					meta : [{ name : ":noCompletion", pos : pos }],
+				};
+				var fget : Field = {
+					name : "get_" + v.name,
+					pos : pos,
+					kind : FFun( {
+						ret : t,
+						args : [],
+						expr : if( consts.length == cpos || (consts.length == cpos+1 && consts[cpos].v == v) )
+							macro return $i{ name };
+						else
+							macro {
+								constModified = true;
+								return $i{ name };
+							},
+					}),
+					access : [AInline],
+					meta : [{ name : ":noCompletion", pos : pos }],
+				};
+				var fset : Field = {
+					name : "set_" + v.name,
+					pos : pos,
+					kind : FFun( {
+						ret : t,
+						args : [ { name : "_v", type : t } ],
+						expr : if( consts.length == cpos )
+							macro return $i{ name } = _v;
+						else
+							macro {
+								constModified = true;
+								return $i{ name } = _v;
+							}
+					}),
+					access : [AInline],
+					meta : [{ name : ":noCompletion", pos : pos }],
+				};
+				fields.push(f);
+				fields.push(f2);
+				params.push(name);
+				fields.push(fget);
+				fields.push(fset);
+			case Global:
+				globals.push(v);
+			default:
+			}
+		}
+		// updateConstants
+		var exprs = [];
+		function getPath(v:TVar) {
+			if( v.parent == null )
+				return { expr : haxe.macro.Expr.ExprDef.EConst(CIdent(v.name+"__")), pos : pos };
+			return { expr : haxe.macro.Expr.ExprDef.EField(getPath(v.parent), v.name), pos : pos };
+		}
+		for( c in consts ) {
+			if( c.v.kind == Global ) continue;
+			var p = getPath(c.v);
+			switch( c.v.type ) {
+			case TInt:
+				exprs.push(macro {
+					var v : Int = $p;
+					if( v >>> $v{ c.bits } != 0 ) throw $v{ c.v.name } +" is out of range " + v + ">" + $v{ (1 << c.bits) - 1 };
+					constBits |= v << $v{ c.pos };
+				});
+			case TBool:
+				exprs.push(macro if( $p ) constBits |= 1 << $v{ c.pos } );
+			default:
+				throw "assert";
+			}
+		}
+		fields.push( {
+			name : "updateConstants",
+			pos : pos,
+			kind : FFun({
+				ret : macro : Void,
+				args : [ { name : "globals", type : macro : hxsl.Globals } ],
+				expr : macro {
+					constBits = 0;
+					{$a{ exprs }};
+					super.updateConstants(globals);
+				},
+			}),
+			access : [AOverride],
+		});
+		var index = 0;
+		fields.push( {
+			name : "getParamValue",
+			pos : pos,
+			kind : FFun( {
+				ret : macro : Dynamic,
+				args : [ { name : "index", type : macro : Int } ],
+				expr : {
+					expr : EBlock([
+						{ expr : ESwitch(macro index, [for( p in params ) { values : [macro $v{ index++ } ], expr : macro return $i{ p } } ], macro {}), pos : pos },
+						macro return null,
+					]),
+					pos : pos,
+				},
+			}),
+			access : [AOverride],
+		});
+		return fields;
+	}
+	
+	public static function buildShader() {
+		var fields = Context.getBuildFields();
+		for( f in fields )
+			if( f.name == "SRC" ) {
+				switch( f.kind ) {
+				case FVar(_, expr) if( expr != null ):
+					var pos = expr.pos;
+					if( !Lambda.has(f.access, AStatic) ) f.access.push(AStatic);
+					try {
+						var shader = new MacroParser().parseExpr(expr);
+						var name = Std.string(Context.getLocalClass());
+						var shader = new Checker().check(name,shader);
+						var str = Serializer.run(shader);
+						f.kind = FVar(null, { expr : EConst(CString(str)), pos : pos } );
+						f.meta.push({
+							name : ":keep",
+							pos : pos,
+						});
+						for( f in buildFields(shader,pos) )
+							fields.push(f);
+					} catch( e : Ast.Error ) {
+						fields.remove(f);
+						Context.error(e.msg, e.pos);
+					}
+				default:
+				}
+			}
+		return fields;
+	}
+	
+	public static function buildGlobals() {
+		var fields = Context.getBuildFields();
+		var globals = [];
+		var sets = [];
+		for( f in fields ) {
+			for( m in f.meta ) {
+				if( m.name == "global" )
+					switch( [f.kind, m.params[0].expr] ) {
+					case [FVar(t, set), EConst(CString(name))]:
+						f.kind = FVar(macro : hxsl.Globals.GlobalSlot<$t>);
+						globals.push(macro $i{ f.name } = new hxsl.Globals.GlobalSlot($v { name } ));
+						if( set != null )
+							sets.push(macro $i{ f.name }.set(globals, $set));
+					default:
+					}
+			}
+		}
+		var p = Context.currentPos();
+		fields.push({
+			name : "initGlobals",
+			kind : FFun({
+				ret : null,
+				expr : { expr : EBlock(globals), pos : p },
+				args : [],
+			}),
+			pos : p,
+		});
+		fields.push({
+			name : "setGlobals",
+			kind : FFun({
+				ret : null,
+				expr : { expr : EBlock(sets), pos : p },
+				args : [],
+			}),
+			pos : p,
+		});
+		return fields;
+	}
+	
+}

+ 260 - 0
hxsl/Printer.hx

@@ -0,0 +1,260 @@
+package hxsl;
+using hxsl.Ast;
+
+class Printer {
+	
+	var buffer : StringBuf;
+	var varId : Bool;
+
+	public function new(varId = false) {
+		this.varId = varId;
+	}
+	
+	inline function add(v:Dynamic) {
+		buffer.add(v);
+	}
+	
+	public function shaderString( s : ShaderData ) {
+		buffer = new StringBuf();
+		for( v in s.vars ) {
+			addVar(v, Var);
+			add(";\n");
+		}
+		if( s.vars.length > 0 )
+			add("\n");
+		for( f in s.funs ) {
+			addFun(f);
+			add("\n\n");
+		}
+		return buffer.toString();
+	}
+	
+	public function varString( v : TVar ) {
+		buffer = new StringBuf();
+		addVar(v, null);
+		return buffer.toString();
+	}
+
+	public function funString( f : TFunction ) {
+		buffer = new StringBuf();
+		addFun(f);
+		return buffer.toString();
+	}
+
+	public function exprString( e : TExpr ) {
+		buffer = new StringBuf();
+		addExpr(e,"");
+		return buffer.toString();
+	}
+
+	function addVar( v : TVar, defKind : VarKind, tabs = "" ) {
+		if( v.qualifiers != null ) {
+			for( q in v.qualifiers )
+				add("@" + (switch( q ) {
+				case Const(max): "const" + (max == null ? "" : "("+max+")");
+				case Private: "private";
+				case Nullable: "nullable";
+				case PerObject: "perObject";
+				case Name(n): "name('" + n + "')";
+				}) + " ");
+		}
+		if( v.kind != defKind )
+			switch( v.kind ) {
+			case Local:
+				add("@local ");
+			case Global:
+				add("@global ");
+			case Var:
+				add("@var ");
+			case Param:
+				add("@param ");
+			case Input:
+				add("@input ");
+			case Function:
+				add("@function ");
+			case Output:
+				add("@output ");
+			}
+		add("var " + v.name + (varId?"@" + v.id:"") + " : ");
+		switch( v.type ) {
+		case TStruct(vl):
+			add("{");
+			var first = true;
+			for( v in vl ) {
+				if( first ) first = false else add(", ");
+				addVar(v,v.kind);
+			}
+			add("}");
+		default:
+			add(v.type.toString());
+		}
+	}
+	
+	function addFun( f : TFunction ) {
+		add("function " + f.ref.name + "(");
+		var first = true;
+		for( a in f.args ) {
+			if( first ) {
+				add(" ");
+				first = false;
+			} else
+				add(", ");
+			addVar(a, Local);
+		}
+		if( f.args.length > 0 ) add(" ");
+		add(") : "+f.ret.toString()+" ");
+		addExpr(f.expr,"");
+	}
+	
+	function addVarName( v : TVar ) {
+		if( v.parent != null ) {
+			addVarName(v.parent);
+			add(".");
+		}
+		add(v.name);
+		if( varId )
+			add("@" + v.id);
+	}
+	
+	function addExpr( e : TExpr, tabs : String ) : Void {
+		switch( e.e ) {
+		case TVar(v):
+			addVarName(v);
+		case TVarDecl(v, init):
+			addVar(v, Local, tabs);
+			if( init != null ) {
+				add(" = ");
+				addExpr(init, tabs);
+			}
+		case TSwiz(e, regs):
+			addExpr(e,tabs);
+			add(".");
+			for( r in regs )
+				add(Std.string(r).toLowerCase());
+		case TReturn(e):
+			add("return");
+			if( e != null ) {
+				add(" ");
+				addExpr(e,tabs);
+			}
+		case TIf(cond, eif, eelse):
+			add("if( ");
+			addExpr(cond, tabs);
+			add(" ) ");
+			addExpr(eif,tabs);
+			if( eelse != null ) {
+				add(" else ");
+				addExpr(eelse,tabs);
+			}
+		case TGlobal(g):
+			add(g.toString());
+		case TCall(e, el):
+			addExpr(e, tabs);
+			add("(");
+			var first = true;
+			for( e in el ) {
+				if( first ) first = false else add(", ");
+				addExpr(e, tabs);
+			}
+			add(")");
+		case TFor(v, it, loop):
+			add("for( " + v.name + " in ");
+			addExpr(it, tabs);
+			add(") ");
+			addExpr(loop, tabs);
+		case TContinue:
+			add("continue");
+		case TBreak:
+			add("break");
+		case TDiscard:
+			add("discard");
+		case TBlock(el):
+			add("{");
+			tabs += "\t";
+			for( e in el ) {
+				add("\n" + tabs);
+				addExpr(e,tabs);
+				add(";");
+			}
+			tabs = tabs.substr(1);
+			if( el.length > 0 )
+				add("\n" + tabs);
+			add("}");
+		case TUnop(op, e):
+			add(switch( op ) {
+			case OpNot:"!";
+			case OpNeg:"-";
+			case OpNegBits:"~";
+			case OpIncrement:"++";
+			case OpDecrement:"--";
+			});
+			addExpr(e, tabs);
+		case TBinop(op, e1, e2):
+			addExpr(e1, tabs);
+			add(" "+opStr(op)+" ");
+			addExpr(e2, tabs);
+		case TArray(e1, e2):
+			addExpr(e1,tabs);
+			add("[");
+			addExpr(e2, tabs);
+			add("]");
+		case TParenthesis(e):
+			add("(");
+			addExpr(e, tabs);
+			add(")");
+		case TConst(c):
+			add(switch(c) {
+			case CNull: "null";
+			case CBool(b): b;
+			case CInt(i): i;
+			case CFloat(f): f;
+			case CString(s): '"' + s + '"';
+			});
+		case TArrayDecl(el):
+			add("[");
+			var first = true;
+			for( e in el ) {
+				if( first ) first = false else add(", ");
+				addExpr(e,tabs);
+			}
+			add("]");
+		}
+	}
+	
+	public static function opStr( op : Ast.Binop ) {
+		return switch(op) {
+		case OpAdd:"+";
+		case OpSub:"-";
+		case OpMult:"*";
+		case OpDiv:"/";
+		case OpMod:"%";
+		case OpEq:"==";
+		case OpNotEq:"!=";
+		case OpGt:">";
+		case OpLt:"<";
+		case OpGte:">=";
+		case OpLte:"<=";
+		case OpXor:"^";
+		case OpOr:"|";
+		case OpAnd:"&";
+		case OpShl:"<<";
+		case OpShr:">>";
+		case OpUShr:">>>";
+		case OpBoolAnd:"&&";
+		case OpBoolOr:"||";
+		case OpAssign:"=";
+		case OpAssignOp(op):opStr(op) + "=";
+		case OpArrow:"=>";
+		case OpInterval:"...";
+		}
+	}
+
+	public static function toString( e : TExpr, varId = false ) {
+		return new Printer(varId).exprString(e);
+	}
+
+	public static function shaderToString( s : ShaderData, varId = false ) {
+		return new Printer(varId).shaderString(s);
+	}
+	
+}

+ 39 - 0
hxsl/Serializer.hx

@@ -0,0 +1,39 @@
+package hxsl;
+using hxsl.Ast;
+
+class Serializer {
+	
+	function new() {
+	}
+	
+	#if macro
+	function mapExpr( e : TExpr ) {
+		var e = e.map(mapExpr);
+		e.p = cast haxe.macro.Context.getPosInfos(e.p);
+		return e;
+	}
+	#end
+	
+	public static function run( s : ShaderData ) {
+		#if macro
+		var ser = new Serializer();
+		var s : ShaderData = {
+			name : s.name,
+			vars : s.vars,
+			funs : [for( f in s.funs ) {
+				kind : f.kind,
+				ref : f.ref,
+				args : f.args,
+				ret : f.ret,
+				expr : ser.mapExpr(f.expr),
+			}],
+		};
+		#end
+		var ser = new haxe.Serializer();
+		ser.useCache = true;
+		ser.useEnumIndex = true;
+		ser.serialize(s);
+		return ser.toString();
+	}
+	
+}

+ 46 - 0
hxsl/Shader.hx

@@ -0,0 +1,46 @@
+package hxsl;
+using hxsl.Ast;
+
+@:autoBuild(hxsl.Macros.buildShader())
+class Shader {
+	
+	var shader : SharedShader;
+	var instance : SharedShader.ShaderInstance;
+	var constBits : Int;
+	var constModified : Bool;
+	
+	public function new() {
+		var cl : Dynamic = std.Type.getClass(this);
+		shader = cl.SHADER;
+		constModified = true;
+		if( shader == null ) {
+			shader = new SharedShader(cl.SRC);
+			cl.SHADER = shader;
+		}
+	}
+	
+	public function getParamValue( index : Int ) : Dynamic {
+		throw "assert"; // will be subclassed in sub shaders
+		return null;
+	}
+	
+	public function updateConstants( globals : Globals ) {
+		for( c in shader.consts )
+			if( c.globalId > 0 ) {
+				var v : Dynamic = globals.fastGet(c.globalId);
+				switch( c.v.type ) {
+				case TInt:
+					var v : Int = v;
+					if( v >>> c.bits != 0 ) throw "Constant " + c.v.name + " is outside range (" + v + " > " + ((1 << c.bits) - 1) + ")";
+					constBits |= v << c.pos;
+				case TBool:
+					var v : Bool = v;
+					if( v ) constBits |= 1 << c.pos;
+				default:
+					throw "assert";
+				}
+			}
+		instance = shader.getInstance(constBits);
+	}
+	
+}

+ 134 - 0
hxsl/SharedShader.hx

@@ -0,0 +1,134 @@
+package hxsl;
+using hxsl.Ast;
+
+class ShaderInstance {
+	public var id : Int;
+	public var shader : ShaderData;
+	public var params : Map<Int,Int>;
+	public function new(shader) {
+		id = Tools.allocVarId();
+		this.shader = shader;
+		params = new Map();
+	}
+}
+
+class ShaderGlobal {
+	public var v : TVar;
+	public var globalId : Int;
+	public function new(v, gid) {
+		this.v = v;
+		this.globalId = gid;
+	}
+}
+
+class ShaderConst {
+	public var v : TVar;
+	public var pos : Int;
+	public var bits : Int;
+	public var globalId : Int;
+	public function new(v, pos, bits) {
+		this.v = v;
+		this.pos = pos;
+		this.bits = bits;
+	}
+}
+
+class SharedShader {
+	
+	public var data : ShaderData;
+	public var globals : Array<ShaderGlobal>;
+	public var consts : Array<ShaderConst>;
+	var instanceCache : Map<Int,ShaderInstance>;
+	var paramsCount : Int;
+	
+	public function new(src:String) {
+		instanceCache = new Map();
+		data = haxe.Unserializer.run(src);
+		consts = [];
+		globals = [];
+		for( v in data.vars )
+			browseVar(v);
+		if( consts.length == 0 ) {
+			var i = new ShaderInstance(data);
+			paramsCount = 0;
+			for( v in data.vars )
+				addSelfParam(i, v);
+			instanceCache.set(0, i);
+		}
+	}
+	
+	public function getInstance( constBits : Int ) {
+		var i = instanceCache.get(constBits);
+		if( i != null )
+			return i;
+		var eval = new hxsl.Eval();
+		for( c in consts )
+			eval.setConstant(c.v, switch( c.v.type ) {
+			case TBool: CBool((constBits >>> c.pos) & 1 != 0);
+			case TInt: CInt((constBits >>> c.pos) & ((1 << c.bits) - 1));
+			default: throw "assert";
+			});
+		i = new ShaderInstance(eval.eval(data));
+		paramsCount = 0;
+		for( v in data.vars )
+			addParam(eval, i, v);
+		instanceCache.set(constBits, i);
+		return i;
+	}
+
+	function addSelfParam( i : ShaderInstance, v : TVar ) {
+		switch( v.type ) {
+		case TStruct(vl):
+			for( v in vl )
+				addSelfParam(i, v);
+		default:
+			if( v.kind == Param ) {
+				i.params.set( v.id,  paramsCount );
+				paramsCount++;
+			}
+		}
+	}
+
+	function addParam( eval : Eval, i : ShaderInstance, v : TVar ) {
+		switch( v.type ) {
+		case TStruct(vl):
+			for( v in vl )
+				addParam(eval, i, v);
+		default:
+			if( v.kind == Param ) {
+				i.params.set( eval.varMap.get(v).id,  paramsCount );
+				paramsCount++;
+			}
+		}
+	}
+	
+	function browseVar( v : TVar, ?path : String ) {
+		v.id = Tools.allocVarId();
+		if( path == null )
+			path = v.getName();
+		else
+			path += "." + v.name;
+		switch( v.type ) {
+		case TStruct(vl):
+			for( vs in vl )
+				browseVar(vs,path);
+		default:
+			var globalId = 0;
+			if( v.kind == Global ) {
+				globalId = Globals.allocID(path);
+				globals.push(new ShaderGlobal(v,globalId));
+			}
+			if( !v.isConst() )
+				return;
+			var bits = v.getConstBits();
+			if( bits > 0 ) {
+				var prev = consts[consts.length - 1];
+				var pos = prev == null ? 0 : prev.pos + prev.bits;
+				var c = new ShaderConst(v, pos, bits);
+				c.globalId = globalId;
+				consts.push(c);
+			}
+		}
+	}
+
+}

+ 192 - 0
hxsl/Splitter.hx

@@ -0,0 +1,192 @@
+package hxsl;
+using hxsl.Ast;
+
+private typedef VarProps = {
+	var v : TVar;
+	var read : Int;
+	var write : Int;
+	var local : Bool;
+}
+
+class Splitter {
+
+	var vars : Map<Int,VarProps>;
+	var varNames : Map<String,TVar>;
+	var varMap : Map<TVar,TVar>;
+	
+	public function new() {
+	}
+	
+	public function split( s : ShaderData ) : { vertex : ShaderData, fragment : ShaderData } {
+		var vfun = null, vvars = new Map();
+		var ffun = null, fvars = new Map();
+		varNames = new Map();
+		for( f in s.funs )
+			switch( f.kind ) {
+			case Vertex:
+				vars = vvars;
+				vfun = f;
+				checkExpr(f.expr);
+			case Fragment:
+				vars = fvars;
+				ffun = f;
+				checkExpr(f.expr);
+			default:
+				throw "assert";
+			}
+		varMap = new Map();
+		for( inf in vvars ) {
+			var v = inf.v;
+			switch( v.kind ) {
+			case Var, Local:
+				v.kind = fvars.exists(v.id) ? Var : Local;
+			default:
+			}
+			switch( v.kind ) {
+			case Var, Output:
+				// alloc a new var if read or multiple writes
+				if( inf.read > 0 || inf.write > 1 ) {
+					var nv : TVar = {
+						id : Tools.allocVarId(),
+						name : v.name,
+						kind : v.kind,
+						type : v.type,
+					};
+					vars = vvars;
+					var ninf = get(nv);
+					v.kind = Local;
+					var p = vfun.expr.p;
+					addExpr(vfun, { e : TBinop(OpAssign, { e : TVar(nv), t : nv.type, p : p }, { e : TVar(v), t : v.type, p : p } ), t : nv.type, p : p });
+					if( nv.kind == Var ) {
+						var old = fvars.get(v.id);
+						varMap.set(v, nv);
+						fvars.remove(v.id);
+						fvars.set(nv.id, { v : nv, read : old.read, write : old.write, local : false });
+					}
+				}
+			default:
+			}
+		}
+		var finits = [];
+		for( inf in fvars ) {
+			var v = inf.v;
+			switch( v.kind ) {
+			case Input:
+				// create a var that will pass the input from vertex to fragment
+				throw "TODO";
+			case Var if( inf.write > 0 ):
+				var nv : TVar = {
+					id : Tools.allocVarId(),
+					name : v.name,
+					kind : Local,
+					type : v.type,
+				};
+				uniqueName(nv);
+				finits.push( { e : TVarDecl(nv, { e : TVar(v), t : v.type, p : ffun.expr.p } ), t:TVoid, p:ffun.expr.p } );
+				varMap.set(v, nv);
+			default:
+			}
+		}
+		// support for double mapping v -> v1 -> v2
+		for( v in varMap.keys() ) {
+			var v2 = varMap.get(varMap.get(v));
+			if( v2 != null )
+				varMap.set(v, v2);
+		}
+		ffun = {
+			ret : ffun.ret,
+			ref : ffun.ref,
+			kind : ffun.kind,
+			args : ffun.args,
+			expr : mapVars(ffun.expr),
+		};
+		switch( ffun.expr.e ) {
+		case TBlock(el):
+			for( e in finits )
+				el.unshift(e);
+		default:
+			finits.push(ffun.expr);
+			ffun.expr = { e : TBlock(finits), t : TVoid, p : ffun.expr.p };
+		}
+		return {
+			vertex : {
+				name : "vertex",
+				vars : [for( v in vvars ) if( !v.local ) v.v],
+				funs : [vfun],
+			},
+			fragment : {
+				name : "fragment",
+				vars : [for( v in fvars ) if( !v.local ) v.v],
+				funs : [ffun],
+			},
+		};
+	}
+	
+	function addExpr( f : TFunction, e : TExpr ) {
+		switch( f.expr.e ) {
+		case TBlock(el):
+			el.push(e);
+		default:
+			f.expr = { e : TBlock([f.expr, e]), t : TVoid, p : f.expr.p };
+		}
+	}
+	
+	function mapVars( e : TExpr ) {
+		return switch( e.e ) {
+		case TVar(v):
+			var v2 = varMap.get(v);
+			v2 == null ? e : { e : TVar(v2), t : e.t, p : e.p };
+		default:
+			e.map(mapVars);
+		}
+	}
+	
+	function get( v : TVar ) {
+		var i = vars.get(v.id);
+		if( i == null ) {
+			i = { v : v, read : 0, write : 0, local : false };
+			vars.set(v.id, i);
+			uniqueName(v);
+		}
+		return i;
+	}
+	
+	function uniqueName( v : TVar ) {
+		if( v.kind == Global )
+			return;
+		v.parent = null;
+		var n = varNames.get(v.name);
+		if( n != null && n != v ) {
+			var k = 2;
+			while( varNames.exists(v.name + k) ) {
+				k++;
+			}
+			v.name += k;
+		}
+		varNames.set(v.name, v);
+	}
+	
+	function checkExpr( e : TExpr ) {
+		switch( e.e ) {
+		case TVar(v):
+			var inf = get(v);
+			inf.read++;
+		case TBinop(OpAssign, { e : (TVar(v) | TSwiz( { e : TVar(v) }, _)) }, e):
+			var inf = get(v);
+			inf.write++;
+			checkExpr(e);
+		case TBinop(OpAssignOp(_), { e : (TVar(v) | TSwiz( { e : TVar(v) }, _)) }, e):
+			var inf = get(v);
+			inf.read++;
+			inf.write++;
+			checkExpr(e);
+		case TVarDecl(v, init):
+			var inf = get(v);
+			inf.local = true;
+			if( init != null ) checkExpr(init);
+		default:
+			e.iter(checkExpr);
+		}
+	}
+	
+}

+ 23 - 0
hxsl/Types.hx

@@ -0,0 +1,23 @@
+package hxsl;
+
+#if h3d
+
+typedef Vec = h3d.Vector;
+typedef IVec = Array<Int>;
+typedef BVec = Array<Bool>;
+typedef Matrix = h3d.Matrix;
+typedef Texture = h3d.mat.Texture;
+typedef Sampler2D = h3d.mat.Texture;
+typedef SamplerCube = h3d.mat.Texture;
+
+#else
+
+typedef Vec = Array<Float>;
+typedef IVec = Array<Int>;
+typedef BVec = Array<Bool>;
+typedef Matrix = Array<Float>;
+typedef Texture = Dynamic;
+typedef Sampler2D = Dynamic;
+typedef SamplerCube = Dynamic;
+
+#end

+ 400 - 0
test/Test.hx

@@ -0,0 +1,400 @@
+
+class Proto extends hxsl.Shader {
+
+	static var SRC = {
+		
+	// globals are injected by passes, they are not local to the per-shader-object. They are thus accessible in sub passes as well
+	@global var camera : {
+		var view : Mat4;
+		var proj : Mat4;
+		var position : Vec3;
+		var projDiag : Vec3; // [_11,_22,_33]
+		var viewProj : Mat4;
+		var inverseViewProj : Mat4;
+		@var var dir : Vec3; // allow mix of variable types in structure (each variable is independent anyway)
+	};
+
+	@global var global : {
+		var time : Float;
+		@perObject var modelView : Mat4;
+		@perObject var modelViewInverse : Mat4;
+		// ... other available globals in BasePass
+	};
+	
+	@input var input : {
+		var position : Vec3;
+		var normal : Vec3;
+	};
+	
+	var output : {
+		var position : Vec4; // written in vertex
+		var color : Vec4; // written in fragment
+	};
+	
+	// vars are always exported
+	
+	var transformedPosition : Vec3;
+	var transformedNormal : Vec3;
+	var projectedPosition : Vec4;
+	var pixelColor : Vec4;
+	
+	@param var color : Vec4;
+	
+	// each __init__ expr is out of order dependency-based
+	function __init__() {
+		transformedPosition = input.position * global.modelView.mat3x4();
+		projectedPosition = vec4(transformedPosition, 1) * camera.viewProj;
+		transformedNormal = input.normal * global.modelViewInverse.mat3();
+		camera.dir = (camera.position - transformedPosition).normalize();
+		pixelColor = color;
+	}
+	
+	function vertex() {
+		output.position = projectedPosition;
+	}
+	
+	function fragment() {
+		output.color = pixelColor;
+	}
+
+	};
+	
+	public function new() {
+		super();
+		color.set(1, 1, 1);
+	}
+
+}
+
+// -------------------buildGlobals()---------------------------------------------------------------------------------------------
+
+class VertexColor extends hxsl.Shader {
+static var SRC = {
+		
+	@input var input : {
+		var color : Vec3;
+	};
+	
+	var pixelColor : Vec4;
+	
+	function fragment() {
+		pixelColor.rgb *= input.color;
+	}
+	
+}
+}
+
+// ----------------------------------------------------------------------------------------------------------------
+
+class Texture extends hxsl.Shader {
+static var SRC = {
+
+	// will add extra required fields to input
+	@input var input : {
+		var uv : Vec2;
+	};
+	
+	@const var additive : Bool;
+	@const var killAlpha : Bool;
+	@param var killAlphaThreshold : Float;
+	
+	@param var texture : Sampler2D;
+	var calculatedUV : Vec2;
+	var pixelColor : Vec4;
+	
+	function vertex() {
+		calculatedUV = input.uv;
+	}
+	
+	function fragment() {
+		var c = texture.get(calculatedUV);
+		if( killAlpha && c.a - killAlphaThreshold < 0 ) discard; // in multipass, we will have to specify if we want to keep the kill's or not
+		if( additive )
+			pixelColor += c;
+		else
+			pixelColor *= c;
+	}
+}
+}
+
+// ----------------------------------------------------------------------------------------------------------------
+
+class AnimatedUV extends hxsl.Shader {
+static var SRC = {
+	var calculatedUV : Vec2;
+	@global("global.time") var globalTime : Float;
+	@param var animationUV : Vec2;
+	@param var timeOffset : Float;
+	
+	function vertex() {
+		calculatedUV += animationUV * (globalTime + timeOffset);
+	}
+}
+}
+
+// ----------------------------------------------------------------------------------------------------------------
+
+class LightSystem extends hxsl.Shader {
+static var SRC = {
+
+	@global var light : {
+		@const var perPixel : Bool;
+		@const(16) var NDirs : Int;
+		@const(64) var NPoints : Int;
+		var ambient : Vec3;
+		var dirDir : Array<Vec3, light.NDirs>;
+		var dirColor : Array<Vec3, light.NDirs>;
+		var pointPos : Array<Vec3, light.NPoints>;
+		var pointColor : Array<Vec3, light.NPoints>;
+		var pointAtt : Array<Vec3, light.NPoints>;
+	};
+	
+	var transformedPosition : Vec3;
+	var transformedNormal : Vec3; // will be tagged as read in either vertex or fragment depending on conditional
+	var pixelColor : Vec4;
+
+	function calcLight() : Vec3 {
+		var col = light.ambient;
+		var tn = transformedNormal.normalize();
+		for( i in 0...light.NDirs )
+			col += light.dirColor[i] * tn.dot(-light.dirDir[i]).max(0.);
+		for( i in 0...light.NPoints ) {
+			var d = transformedPosition - light.pointPos[i];
+			var dist2 = d.dot(d);
+			var dist = dist2.sqrt();
+			col += light.pointColor[i] * (tn.dot(d).max(0.) / light.pointAtt[i].dot(vec3(dist,dist2,dist2 * dist)));
+		}
+		return col;
+	}
+	
+	function vertex() {
+		if( !light.perPixel ) pixelColor.rgb *= calcLight();
+	}
+	
+	function fragment() {
+		if( light.perPixel )
+			pixelColor.rgb *= calcLight();
+	}
+
+}
+}
+
+// ----------------------------------------------------------------------------------------------------------------
+
+class Outline extends hxsl.Shader {
+static var SRC = {
+	
+	// Param are always local
+	@param var color : Vec4;
+	@param var power : Float;
+	@param var size : Float;
+	
+	var pixelColor : Vec4;
+	
+	@global var camera : {
+		var projDiag : Vec3;
+		@var var dir : Vec3;
+	};
+	var transformedNormal : Vec3;
+	var transformedPosition : Vec3;
+
+	function vertex() {
+		transformedPosition.xy += transformedNormal.xy * camera.projDiag.xy * size;
+	}
+	
+	function fragment() {
+		var e = 1. - transformedNormal.normalize().dot(camera.dir.normalize());
+		pixelColor = color * e.pow(power);
+	}
+
+}}
+
+// ----------------------------------------------------------------------------------------------------------------
+
+class Skinning extends hxsl.Shader {
+static var SRC = {
+
+	@input var input : {
+		pos : Vec3,
+		normal : Vec3,
+		weights : Vec3,
+		indexes : IVec3,
+	};
+	
+	var transformedPosition : Vec3;
+	var transformedNormal : Vec3;
+	
+	@const var MaxBones : Int;
+	@param var skinMatrixes : Array< Mat3x4, MaxBones >;
+	
+	function vertex() {
+		var p = input.pos, n = input.normal;
+		// TODO : accessing skinMatrixes should multiply by 255*3 in AGAL (byte converted to [0-1] float + stride for 3 vec4 per element)
+		transformedPosition = p * input.weights.x * skinMatrixes[input.indexes.x] + p * input.weights.y * skinMatrixes[input.indexes.y] + p * input.weights.z * skinMatrixes[input.indexes.z];
+		transformedNormal = n * input.weights.x * skinMatrixes[input.indexes.x].mat3() + n * input.weights.y * skinMatrixes[input.indexes.y].mat3() + n * input.weights.z * skinMatrixes[input.indexes.z].mat3();
+	}
+
+}
+
+	public function new() {
+		super();
+		MaxBones = 34;
+	}
+
+}
+
+// ----------------------------------------------------------------------------------------------------------------
+
+class ApplyShadow extends hxsl.Shader {
+static var SRC = {
+	
+	@global var shadow : {
+		var map : Sampler2D;
+		var color : Vec3;
+		var power : Float;
+		var lightProj : Mat4;
+		var lightCenter : Mat4;
+	};
+
+	// Nullable params, allow to check them in shaders. Will create a isNull Const automatically
+	// actually use the global values unless object-specific local values are defined
+	@param @nullable var shadowColor : Vec3;
+	@param @nullable var shadowPower : Float;
+
+	var pixelColor : Vec4;
+	var transformedPosition : Vec3;
+
+	@private var tshadowPos : Vec3;
+	
+	function vertex() {
+		tshadowPos = transformedPosition * shadow.lightCenter.mat3x4() * shadow.lightProj.mat3x4();
+	}
+	
+	function fragment() {
+		var s = exp( (shadowPower == null ? shadow.power : shadowPower) * (tshadowPos.z - shadow.map.get(tshadowPos.xy).dot(vec4(1, 1. / 255., 1. / (255. * 255.), 1. / (255. * 255. * 255.))))).saturate();
+		pixelColor.rgb *= (1. - s) * (shadowColor == null ? shadow.color : shadowColor) + s.xxx;
+	}
+
+}}
+
+// ----------------------------------------------------------------------------------------------------------------
+
+class DepthMap extends hxsl.Shader {
+static var SRC = {
+
+	@global var depthDelta : Float;
+	@private var distance : Float;
+	
+	// the pass setup will have to declare that it will write distanceColor to its rendertarget
+	var depthColor : Vec4;
+
+	var projectedPosition : Vec4;
+	
+	function vertex() {
+		distance = projectedPosition.z / projectedPosition.w + depthDelta;
+	}
+	
+	function fragment() {
+		var color = (distance.xxxx * vec4(1.,255.,255.*255.,255.*255.*255.)).fract();
+		depthColor = color - color.yzww * vec4(1. / 255., 1. / 255., 1. / 255., 0.);
+	}
+
+}}
+
+// ----------------------------------------------------------------------------------------------------------------
+
+class DistanceMap extends hxsl.Shader {
+static var SRC = {
+
+	@global var distance : {
+		var center : Vec3;
+		var scale : Float;
+		var power : Float;
+	};
+
+	@private var dist : Vec3;
+	
+	// the pass will have to declare that it will write distanceColor to its rendertarget
+	var distanceColor : Vec4;
+	
+	var transformedPosition : Vec3;
+
+	function vertex() {
+		dist = (transformedPosition - distance.center).abs();
+	}
+	
+	function fragment() {
+		var d = (dist.length() * distance.scale).pow(distance.power);
+		var color = (d.xxxx * vec4(1.,255.,255.*255.,255.*255.*255.)).fract();
+		distanceColor = color - color.yzww * vec4(1. / 255., 1. / 255., 1. / 255., 0.);
+	}
+
+}}
+
+// ----------------------------------------------------------------------------------------------------------------
+
+class Test {
+	
+	@:access(hxsl)
+	static function main() {
+		var shaders = [
+			new Proto(),
+			new LightSystem(),
+			{ var t = new Texture(); t.killAlpha = true; t; },
+			new AnimatedUV(),
+			//new AnimatedUV(),
+		];
+		var globals = new hxsl.Globals();
+		globals.set("light.NDirs", 1);
+		globals.set("light.NPoints", 3);
+		var instances = [for( s in shaders ) { s.updateConstants(globals); s.instance; }];
+		var cache = hxsl.Cache.get();
+		var s = cache.link(instances, cache.allocOutputVars(["output.position", "output.color"]));
+		//trace("VERTEX=\n" + hxsl.Printer.shaderToString(s.vertex));
+		//trace("FRAGMENT=\n" + hxsl.Printer.shaderToString(s.fragment));
+		
+		#if js
+		haxe.Log.trace("START");
+		try {
+		
+		var canvas = js.Browser.document.createCanvasElement();
+		var gl = canvas.getContextWebGL();
+		var GL = js.html.webgl.GL;
+		
+		function compile(kind, shader) {
+			var code = hxsl.GlslOut.toGlsl(shader);
+			trace(code);
+			var s = gl.createShader(kind);
+			gl.shaderSource(s, code);
+			gl.compileShader(s);
+			if( gl.getShaderParameter(s, GL.COMPILE_STATUS) != cast 1 ) {
+				var log = gl.getShaderInfoLog(s);
+				var line = code.split("\n")[Std.parseInt(log.substr(9)) - 1];
+				if( line == null ) line = "" else line = "(" + StringTools.trim(line) + ")";
+				throw log + line;
+			}
+			return s;
+		}
+			
+		var vs = compile(GL.VERTEX_SHADER, s.vertex.data);
+		var fs = compile(GL.FRAGMENT_SHADER, s.fragment.data);
+		
+		var p = gl.createProgram();
+		gl.attachShader(p, vs);
+		gl.attachShader(p, fs);
+		gl.linkProgram(p);
+		if( gl.getProgramParameter(p, GL.LINK_STATUS) != cast 1 ) {
+			var log = gl.getProgramInfoLog(p);
+			throw "Program linkage failure: "+log;
+		}
+		
+		trace("LINK SUCCESS");
+		
+		} catch( e : Dynamic ) {
+			trace(e);
+		}
+
+		#end
+	}
+		
+}