Nicolas Cannasse 2 年之前
父節點
當前提交
ee7c57f3ec
共有 4 個文件被更改,包括 756 次插入31 次删除
  1. 6 0
      h3d/impl/Driver.hx
  2. 2 2
      h3d/impl/WebGpuApi.hx
  3. 177 29
      h3d/impl/WebGpuDriver.hx
  4. 571 0
      hxsl/WgslOut.hx

+ 6 - 0
h3d/impl/Driver.hx

@@ -12,6 +12,12 @@ typedef VertexBuffer = Stage3dDriver.VertexWrapper;
 typedef Texture = flash.display3D.textures.TextureBase;
 typedef DepthBuffer = {};
 typedef Query = {};
+#elseif (js && webgpu)
+typedef IndexBuffer = { buf : WebGpuApi.GPUBuffer, stride : Int };
+typedef VertexBuffer = { buf : WebGpuApi.GPUBuffer, stride : Int };
+typedef Texture = {};
+typedef DepthBuffer = {};
+typedef Query = {};
 #elseif js
 typedef IndexBuffer = { b : js.html.webgl.Buffer, is32 : Bool };
 typedef VertexBuffer = { b : js.html.webgl.Buffer, stride : Int #if multidriver, driver : Driver #end };

+ 2 - 2
h3d/impl/WebGpuApi.hx

@@ -917,8 +917,8 @@ enum abstract GPUIndexFormat(String) {
 }
 
 enum abstract GPUFrontFace(String) {
-	var Ccw = "ccw";
-	var Cw = "cw";
+	var CCW = "ccw";
+	var CW = "cw";
 }
 
 enum abstract GPUCullMode(String) {

+ 177 - 29
h3d/impl/WebGpuDriver.hx

@@ -4,20 +4,38 @@ import h3d.impl.Driver;
 import h3d.impl.WebGpuApi;
 import h3d.mat.Pass;
 
+class WebGpuShader {
+	public var inputs : InputNames;
+	public var pipeline : GPURenderPipeline;
+	public function new() {
+	}
+}
+
+class WebGpuFrame {
+	public var colorTex : GPUTexture;
+	public var depthTex : GPUTexture;
+	public var colorView : GPUTextureView;
+	public var depthView : GPUTextureView;
+	public var toDelete : Array<{ function destroy() : Void; }> = [];
+	public function new() {
+	}
+}
+
 class WebGpuDriver extends h3d.impl.Driver {
 
 	var canvas : js.html.CanvasElement;
 	var context : GPUCanvasContext;
 	var device : GPUDevice;
 
-	var colorTex : GPUTexture;
-	var depthTex : GPUTexture;
-	var colorView : GPUTextureView;
-	var depthView : GPUTextureView;
 	var commandList : GPUCommandEncoder;
 	var renderPass : GPURenderPassEncoder;
 	var renderPassDesc : GPURenderPassDescriptor;
 	var needClear : Bool;
+	var currentShader : WebGpuShader;
+	var shaderCache : Map<Int, WebGpuShader> = new Map();
+	var frames : Array<WebGpuFrame> = [];
+	var frame : WebGpuFrame;
+	var frameCount : Int = 0;
 
 	public function new() {
 		inst = this;
@@ -37,7 +55,6 @@ class WebGpuDriver extends h3d.impl.Driver {
 				canvas = @:privateAccess hxd.Window.getInstance().canvas;
 				context = cast canvas.getContext("webgpu");
 				resize(canvas.width, canvas.height);
-				beginFrame();
 				onCreate(false);
 			});
 		});
@@ -58,15 +75,29 @@ class WebGpuDriver extends h3d.impl.Driver {
 			alphaMode : Opaque,
 		});
 
-		colorTex = context.getCurrentTexture();
-		colorView = colorTex.createView();
-		depthTex = device.createTexture({
-			size : { width : width, height : height },
-			dimension : D2,
-			format : Depth24plus_stencil8,
-			usage : (RENDER_ATTACHMENT:GPUTextureUsageFlags) | COPY_SRC,
-		});
-		depthView = depthTex.createView();
+		if( frames != null ) {
+			for( f in frames ) {
+				f.depthTex.destroy();
+				for( t in f.toDelete )
+					t.destroy();
+			}
+		}
+
+		frames = [];
+		for( i in 0...2 ) {
+			var f = new WebGpuFrame();
+			f.depthTex = device.createTexture({
+				size : { width : width, height : height },
+				dimension : D2,
+				format : Depth24plus_stencil8,
+				usage : (RENDER_ATTACHMENT:GPUTextureUsageFlags) | COPY_SRC,
+			});
+			f.depthView = f.depthTex.createView();
+			frames.push(f);
+		}
+
+		frameCount = 0;
+		beginFrame();
 	}
 
 	override function setRenderTarget(tex:Null<h3d.mat.Texture>, layer:Int = 0, mipLevel:Int = 0) {
@@ -74,12 +105,12 @@ class WebGpuDriver extends h3d.impl.Driver {
 		if( tex == null ) {
 			renderPassDesc = {
 				colorAttachments : [{
-					view : colorView,
+					view : frame.colorView,
 					loadOp : Load,
 					storeOp: Store
 				}],
 				depthStencilAttachment: {
-					view : depthView,
+					view : frame.depthView,
 					depthLoadOp: Load,
 					depthStoreOp: Store,
 					stencilLoadOp: Load,
@@ -93,23 +124,35 @@ class WebGpuDriver extends h3d.impl.Driver {
 
 	function beginFrame() {
 		if( device == null ) return;
-		colorTex = context.getCurrentTexture();
-		colorView = colorTex.createView();
+		frame = frames[(frameCount++)%frames.length];
+		frame.colorTex = context.getCurrentTexture();
+		frame.colorView = frame.colorTex.createView();
+		for( t in frame.toDelete )
+			t.destroy();
+		frame.toDelete = [];
 		commandList = device.createCommandEncoder();
 		setRenderTarget(null);
+		currentShader = null;
+	}
+
+	function beginPass() {
+		if( renderPass != null )
+			return;
+		renderPass = commandList.beginRenderPass(renderPassDesc);
+		for( c in renderPassDesc.colorAttachments )
+			c.clearValue = js.Lib.undefined;
+		var depth = renderPassDesc.depthStencilAttachment;
+		if( depth != null ) {
+			depth.depthClearValue = js.Lib.undefined;
+			depth.stencilClearValue = js.Lib.undefined;
+		}
+		needClear = false;
 	}
 
 	function flushPass() {
 		if( needClear ) {
-			renderPass = commandList.beginRenderPass(renderPassDesc);
-			for( c in renderPassDesc.colorAttachments )
-				c.clearValue = js.Lib.undefined;
-			var depth = renderPassDesc.depthStencilAttachment;
-			if( depth != null ) {
-				depth.depthClearValue = js.Lib.undefined;
-				depth.stencilClearValue = js.Lib.undefined;
-			}
-			needClear = false;
+			if( renderPass != null ) throw "assert";
+			beginPass();
 		}
 		if( renderPass != null ) {
 			renderPass.end();
@@ -123,6 +166,7 @@ class WebGpuDriver extends h3d.impl.Driver {
 			device.queue.submit([commandList.finish()]);
 			commandList = null;
 		}
+		frame = frame == frames[0] ? frames[1] : frames[0];
 	}
 
 	override function begin(frame:Int) {
@@ -161,11 +205,115 @@ class WebGpuDriver extends h3d.impl.Driver {
 	}
 
 	override function allocVertexes(m:ManagedBuffer):VertexBuffer {
-		return cast {};
+		return allocBuffer(VERTEX,m.size,m.stride << 2);
 	}
 
 	override function allocIndexes(count:Int, is32:Bool):IndexBuffer {
-		return cast {};
+		return allocBuffer(INDEX,count,is32?2:4);
+	}
+
+	function allocBuffer(type:GPUBufferUsage,count,stride) {
+		var buf = device.createBuffer({
+			size : count * stride,
+			usage : (type:GPUBufferUsageFlags) | COPY_DST,
+			mappedAtCreation: false,
+		});
+		return { buf : buf, count : count, stride : stride };
+	}
+
+	override function uploadVertexBytes(v:VertexBuffer, startVertex:Int, vertexCount:Int, buf:haxe.io.Bytes, bufPos:Int) {
+		uploadBuffer(v,startVertex,vertexCount,buf,bufPos);
+	}
+
+	override function uploadIndexBytes(i:IndexBuffer, startIndice:Int, indiceCount:Int, buf:haxe.io.Bytes, bufPos:Int) {
+		uploadBuffer(i,startIndice,indiceCount,buf,bufPos);
+	}
+
+	function uploadBuffer(b:VertexBuffer,start:Int,count:Int,buf:haxe.io.Bytes, bufPos:Int) {
+		var size = count * b.stride;
+		var tmpBuf = device.createBuffer({
+			size : size,
+			usage : (MAP_WRITE:GPUBufferUsageFlags) | COPY_SRC,
+			mappedAtCreation : true,
+		});
+		new js.lib.Uint8Array(tmpBuf.getMappedRange()).set(cast buf.getData(), bufPos);
+		tmpBuf.unmap();
+		// copy
+		commandList.copyBufferToBuffer(tmpBuf,0,b.buf,start*b.stride,size);
+		// delete later
+		frame.toDelete.push(tmpBuf);
+	}
+
+
+	function compile( shader : hxsl.RuntimeShader.RuntimeShaderData ) {
+		var comp = new hxsl.WgslOut();
+		var source = comp.run(shader.data);
+		trace(source);
+		return device.createShaderModule({ code : source });
+	}
+
+	function makeShader( shader : hxsl.RuntimeShader ) {
+		var sh = new WebGpuShader();
+		var attribNames = [];
+		for( v in shader.vertex.data.vars ) {
+			if( v.kind != Input ) continue;
+			attribNames.push(v.name);
+		}
+		sh.inputs = InputNames.get(attribNames);
+
+		var vertex = compile(shader.vertex);
+		var fragment = compile(shader.fragment);
+
+		var layout = device.createPipelineLayout({ bindGroupLayouts: [] });
+		var pipeline = device.createRenderPipeline({
+			layout : layout,
+			vertex : { module : vertex, entryPoint : "main", buffers : [
+				{
+					attributes: [{ shaderLocation : 0, offset : 0, format : Float32x3 }],
+					arrayStride: 4 * 3,
+					stepMode: GPUVertexStepMode.Vertex
+				},
+				{
+					attributes: [{ shaderLocation : 1, offset : 0, format : Float32x3 }],
+					arrayStride: 4 * 3, // sizeof(float) * 3
+					stepMode: GPUVertexStepMode.Vertex
+				}
+			]},
+			fragment : { module : fragment, entryPoint : "main", targets : [{ format : Bgra8unorm }] },
+			primitive : { frontFace : CW, cullMode : None, topology : Triangle_list },
+			depthStencil : {
+				depthWriteEnabled: true,
+				depthCompare: Less,
+				format: Depth24plus_stencil8
+			}
+		});
+
+		sh.pipeline = pipeline;
+
+		return sh;
+	}
+
+	override function selectShader( shader : hxsl.RuntimeShader ) {
+		var sh = shaderCache.get(shader.id);
+		if( sh == null ) {
+			sh = makeShader(shader);
+			shaderCache.set(shader.id, sh);
+		}
+		if( sh == currentShader )
+			return false;
+		currentShader = sh;
+		beginPass();
+		renderPass.setPipeline(sh.pipeline);
+		return true;
+	}
+
+	override function getShaderInputNames():InputNames {
+		return currentShader.inputs;
+	}
+
+	override function draw(ibuf:IndexBuffer, startIndex:Int, ntriangles:Int) {
+		renderPass.setIndexBuffer(ibuf.buf, ibuf.stride==2?Uint16:Uint32, startIndex*ibuf.stride);
+		renderPass.draw(ntriangles*3);
 	}
 
 	static var inst : WebGpuDriver;

+ 571 - 0
hxsl/WgslOut.hx

@@ -0,0 +1,571 @@
+package hxsl;
+using hxsl.Ast;
+
+class WgslOut {
+
+	static var KWD_LIST = [
+	];
+	static var KWDS = [for( k in KWD_LIST ) k => true];
+	static var GLOBALS = {
+		var m = new Map();
+		for( g in hxsl.Ast.TGlobal.createAll() ) {
+			var n = "" + g;
+			n = n.charAt(0).toLowerCase() + n.substr(1);
+			m.set(g, n);
+		}
+		for( g in m )
+			KWDS.set(g, true);
+		m;
+	};
+
+	var buf : StringBuf;
+	var exprIds = 0;
+	var exprValues : Array<String>;
+	var locals : Map<Int,TVar>;
+	var decls : Array<String>;
+	var isVertex : Bool;
+	var allNames : Map<String, Int>;
+	public var varNames : Map<Int,String>;
+
+	var varAccess : Map<Int,String>;
+
+	public function new() {
+		varNames = new Map();
+		allNames = new Map();
+	}
+
+	inline function add( v : Dynamic ) {
+		buf.add(v);
+	}
+
+	inline function ident( v : TVar ) {
+		add(varName(v));
+	}
+
+	function decl( s : String ) {
+		for( d in decls )
+			if( d == s ) return;
+		if( s.charCodeAt(0) == '#'.code )
+			decls.unshift(s);
+		else
+			decls.push(s);
+	}
+
+	function addType( t : Type ) {
+		switch( t ) {
+		case TVoid:
+			add("void");
+		case TInt:
+			add("int");
+		case TBytes(n):
+			add("uint"+n);
+		case TBool:
+			add("bool");
+		case TFloat:
+			add("float");
+		case TString:
+			add("string");
+		case TVec(size, k):
+			switch( k ) {
+			case VFloat: add("float");
+			case VInt: add("int");
+			case VBool: add("bool");
+			}
+			add(size);
+		case TMat2:
+			add("float2x2");
+		case TMat3:
+			add("float3x3");
+		case TMat4:
+			add("float4x4");
+		case TMat3x4:
+			add("float4x3");
+		case TSampler2D:
+			add("Texture2D");
+		case TSamplerCube:
+			add("TextureCube");
+		case TSampler2DArray:
+			add("Texture2DArray");
+		case TStruct(vl):
+			add("struct { ");
+			for( v in vl ) {
+				addVar(v);
+				add(";");
+			}
+			add(" }");
+		case TFun(_):
+			add("function");
+		case TArray(t, size), TBuffer(t,size):
+			addType(t);
+			add("[");
+			switch( size ) {
+			case SVar(v):
+				ident(v);
+			case SConst(v):
+				add(v);
+			}
+			add("]");
+		case TChannel(n):
+			add("channel" + n);
+		}
+	}
+
+	function addArraySize( size ) {
+		add("[");
+		switch( size ) {
+		case SVar(v): ident(v);
+		case SConst(n): add(n);
+		}
+		add("]");
+	}
+
+	function addVar( v : TVar ) {
+		switch( v.type ) {
+		case TArray(t, size), TBuffer(t,size):
+			addVar({
+				id : v.id,
+				name : v.name,
+				type : t,
+				kind : v.kind,
+			});
+			addArraySize(size);
+		default:
+			addType(v.type);
+			add(" ");
+			ident(v);
+		}
+	}
+
+	function addValue( e : TExpr, tabs : String ) {
+		switch( e.e ) {
+		case TBlock(el):
+			var name = "val" + (exprIds++);
+			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("()");
+		case TIf(econd, eif, eelse):
+			add("( ");
+			addValue(econd, tabs);
+			add(" ) ? ");
+			addValue(eif, tabs);
+			add(" : ");
+			addValue(eelse, tabs);
+		case TMeta(m,args,e):
+			handleMeta(m, args, addValue, e, tabs);
+		default:
+			addExpr(e, tabs);
+		}
+	}
+
+	function handleMeta( m, args : Array<Ast.Const>, callb, e, tabs ) {
+		switch( [m, args] ) {
+		default:
+			callb(e,tabs);
+		}
+	}
+
+	function addBlock( e : TExpr, tabs ) {
+		if( e.e.match(TBlock(_)) )
+			addExpr(e,tabs);
+		else {
+			add("{");
+			addExpr(e,tabs);
+			if( !isBlock(e) )
+				add(";");
+			add("}");
+		}
+	}
+
+	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):
+			var acc = varAccess.get(v.id);
+			if( acc != null ) add(acc);
+			ident(v);
+		case TGlobal(g):
+			add(GLOBALS.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);
+				newLine(e);
+			}
+			add(tabs);
+			add("}");
+		case TVarDecl(v, { e : TArrayDecl(el) }):
+			locals.set(v.id, v);
+			for( i in 0...el.length ) {
+				ident(v);
+				add("[");
+				add(i);
+				add("] = ");
+				addExpr(el[i], tabs);
+				newLine(el[i]);
+			}
+		case TBinop(OpAssign,evar = { e : TVar(_) },{ e : TArrayDecl(el) }):
+			for( i in 0...el.length ) {
+				addExpr(evar, tabs);
+				add("[");
+				add(i);
+				add("] = ");
+				addExpr(el[i], tabs);
+			}
+		case TArrayDecl(el):
+			add("{");
+			var first = true;
+			for( e in el ) {
+				if( first ) first = false else add(", ");
+				addValue(e,tabs);
+			}
+			add("}");
+		case TBinop(op, e1, e2):
+			switch( [op, e1.t, e2.t] ) {
+			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: "~";
+			default: throw "assert"; // OpSpread for Haxe4.2+
+			});
+			addValue(e1, tabs);
+		case TVarDecl(v, init):
+			locals.set(v.id, v);
+			if( init != null ) {
+				ident(v);
+				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(") ");
+			addBlock(eif, tabs);
+			if( eelse != null ) {
+				add(" else ");
+				addBlock(eelse, tabs);
+			}
+		case TDiscard:
+			add("discard");
+		case TReturn(e):
+			if( e == null )
+				add("return _out");
+			else {
+				add("return ");
+				addValue(e, tabs);
+			}
+		case TFor(v, it, loop):
+			locals.set(v.id, v);
+			switch( it.e ) {
+			case TBinop(OpInterval, e1, e2):
+				add("[loop] for(");
+				add(v.name+"=");
+				addValue(e1,tabs);
+				add(";"+v.name+"<");
+				addValue(e2,tabs);
+				add(";" + v.name+"++) ");
+				addBlock(loop, tabs);
+			default:
+				throw "assert";
+			}
+		case TWhile(e, loop, false):
+			var old = tabs;
+			tabs += "\t";
+			add("[loop] do ");
+			addBlock(loop,tabs);
+			add(" while( ");
+			addValue(e,tabs);
+			add(" )");
+		case TWhile(e, loop, _):
+			add("[loop] while( ");
+			addValue(e, tabs);
+			add(" ) ");
+			addBlock(loop,tabs);
+		case TSwitch(_):
+			add("switch(...)");
+		case TContinue:
+			add("continue");
+		case TBreak:
+			add("break");
+		case TArray(e, index):
+			switch( e.t ) {
+			default:
+				addValue(e, tabs);
+				add("[");
+				addValue(index, tabs);
+				add("]");
+			}
+		case TMeta(m, args, e):
+			handleMeta(m, args, addExpr, e, tabs);
+		}
+	}
+
+	function varName( v : TVar ) {
+		var n = varNames.get(v.id);
+		if( n != null )
+			return n;
+		n = v.name;
+		while( KWDS.exists(n) )
+			n = "_" + n;
+		if( allNames.exists(n) ) {
+			var k = 2;
+			n += "_";
+			while( allNames.exists(n + k) )
+				k++;
+			n += k;
+		}
+		varNames.set(v.id, n);
+		allNames.set(n, v.id);
+		return n;
+	}
+
+	function newLine( e : TExpr ) {
+		if( isBlock(e) )
+			add("\n");
+		else
+			add(";\n");
+	}
+
+	function isBlock( e : TExpr ) {
+		switch( e.e ) {
+		case TFor(_, _, loop), TWhile(_,loop,true):
+			return isBlock(loop);
+		case TIf(_,eif,eelse):
+			return isBlock(eelse == null ? eif : eelse);
+		case TBlock(_):
+			return true;
+		default:
+			return false;
+		}
+	}
+
+	function collectGlobals( m : Map<TGlobal,Bool>, e : TExpr ) {
+		switch( e.e )  {
+		case TGlobal(g): m.set(g,true);
+		default: e.iter(collectGlobals.bind(m));
+		}
+	}
+
+	function initVars( s : ShaderData ) {
+		var index = 0;
+		function declVar(prefix:String, v : TVar ) {
+			add("\t");
+			addVar(v);
+			add(";\n");
+			varAccess.set(v.id, prefix);
+		}
+
+		var foundGlobals = new Map();
+		for( f in s.funs )
+			collectGlobals(foundGlobals, f.expr);
+
+		add("struct s_input {\n");
+		for( v in s.vars )
+			if( v.kind == Input || (v.kind == Var && !isVertex) )
+				declVar("_in.", v);
+		add("};\n\n");
+
+		add("struct s_output {\n");
+		for( v in s.vars )
+			if( v.kind == Output )
+				declVar("_out.", v);
+		for( v in s.vars )
+			if( v.kind == Var && isVertex )
+				declVar("_out.", v);
+		add("};\n\n");
+	}
+
+	function initGlobals( s : ShaderData ) {
+		add('cbuffer _globals {\n');
+		for( v in s.vars )
+			if( v.kind == Global ) {
+				add("\t");
+				addVar(v);
+				add(";\n");
+			}
+		add("};\n\n");
+	}
+
+	function initParams( s : ShaderData ) {
+		var textures = [];
+		var buffers = [];
+		add('cbuffer _params {\n');
+		for( v in s.vars )
+			if( v.kind == Param ) {
+				switch( v.type ) {
+				case TArray(t, _) if( t.isSampler() ):
+					textures.push(v);
+					continue;
+				case TBuffer(_):
+					buffers.push(v);
+					continue;
+				default:
+					if( v.type.isSampler() ) {
+						textures.push(v);
+						continue;
+					}
+				}
+				add("\t");
+				addVar(v);
+				add(";\n");
+			}
+		add("};\n\n");
+
+		var bufCount = 0;
+		for( b in buffers ) {
+			add('cbuffer _buffer$bufCount { ');
+			addVar(b);
+			add("; };\n");
+			bufCount++;
+		}
+		if( bufCount > 0 ) add("\n");
+
+		var texCount = 0;
+		for( v in textures ) {
+			addVar(v);
+			add(' : register(t${texCount});\n');
+			switch( v.type ) {
+			case TArray(_,SConst(n)): texCount += n;
+			default: texCount++;
+			}
+		}
+	}
+
+	function initStatics( s : ShaderData ) {
+		add("s_input _in;\n");
+		add("s_output _out;\n");
+
+		add("\n");
+		for( v in s.vars )
+			if( v.kind == Local ) {
+				addVar(v);
+				add(";\n");
+			}
+		add("\n");
+	}
+
+	function emitMain( expr : TExpr ) {
+		add("s_output main( s_input __in ) {\n");
+		add("\t_in = __in;\n");
+		switch( expr.e ) {
+		case TBlock(el):
+			for( e in el ) {
+				add("\t");
+				addExpr(e, "\t");
+				newLine(e);
+			}
+		default:
+			addExpr(expr, "");
+		}
+		add("\treturn _out;\n");
+		add("}");
+	}
+
+	function initLocals() {
+		var locals = Lambda.array(locals);
+		locals.sort(function(v1, v2) return Reflect.compare(v1.name, v2.name));
+		for( v in locals ) {
+			addVar(v);
+			add(";\n");
+		}
+		add("\n");
+
+		for( e in exprValues ) {
+			add(e);
+			add("\n\n");
+		}
+	}
+
+	public function run( s : ShaderData ) {
+		locals = new Map();
+		decls = [];
+		buf = new StringBuf();
+		exprValues = [];
+
+		if( s.funs.length != 1 ) throw "assert";
+		var f = s.funs[0];
+		isVertex = f.kind == Vertex;
+
+		varAccess = new Map();
+		initVars(s);
+		initGlobals(s);
+		initParams(s);
+		initStatics(s);
+
+		var tmp = buf;
+		buf = new StringBuf();
+		emitMain(f.expr);
+		exprValues.push(buf.toString());
+		buf = tmp;
+
+		initLocals();
+
+		decls.push(buf.toString());
+		buf = null;
+		return decls.join("\n");
+	}
+
+}