Browse Source

added shader live reload

Nicolas Cannasse 7 months ago
parent
commit
aeae8dea1c
4 changed files with 174 additions and 11 deletions
  1. 35 0
      hxd/fs/SourceLoader.hx
  2. 15 6
      hxsl/Macros.hx
  3. 2 2
      hxsl/Shader.hx
  4. 122 3
      hxsl/SharedShader.hx

+ 35 - 0
hxd/fs/SourceLoader.hx

@@ -0,0 +1,35 @@
+package hxd.fs;
+
+class SourceLoader {
+
+	static var RELOAD_LFS : Array<hxd.fs.FileSystem> = [];
+	#if sys
+	public static function addLivePath( path : String ) {
+		RELOAD_LFS.push(new hxd.fs.LocalFileSystem(path,""));
+	}
+	public static function addLivePathHaxelib( libs : Array<String> ) {
+		var p = new sys.io.Process("haxelib",["path"].concat(libs));
+		var out = p.stdout.readAll().toString().split("\r\n").join("\n").split("\n");
+		p.exitCode();
+		for( line in out ) {
+			if( line.charCodeAt(0) == "-".code ) continue;
+			addLivePath(line);
+		}
+	}
+	public static function initLivePaths() {
+		addLivePath(".");
+		addLivePathHaxelib(["heaps" #if hide,"hide"#end]);
+	}
+	#end
+
+	public static function isActive() {
+		return RELOAD_LFS.length > 0;
+	}
+
+	public static function resolve( path : String ) {
+		for( fs in RELOAD_LFS )
+			try return fs.get(path) catch( e : hxd.res.NotFound ) {};
+		return null;
+	}
+
+}

+ 15 - 6
hxsl/Macros.hx

@@ -403,11 +403,12 @@ class Macros {
 				case FVar(_, expr) if( expr != null ):
 					var pos = expr.pos;
 					if( !Lambda.has(f.access, AStatic) ) f.access.push(AStatic);
-					Context.getLocalClass().get().meta.add(":src", [expr], pos);
+					var cl = Context.getLocalClass();
+					var c = cl.get();
+					c.meta.add(":src", [expr], pos);
 					try {
 						var shader = new MacroParser().parseExpr(expr);
-						var c = Context.getLocalClass();
-						var csup = c.get().superClass;
+						var csup = c.superClass;
 						var supFields = new Map();
 						// add auto extends
 						do {
@@ -427,13 +428,12 @@ class Macros {
 							supFields.remove("clone");
 							csup = tsup.superClass;
 						} while( true);
-						var name = Std.string(c);
-						var check = new Checker();
+						var className = Std.string(cl);						var check = new Checker();
 						check.loadShader = loadShader;
 						check.warning = function(msg,pos) {
 							haxe.macro.Context.warning(msg, pos);
 						};
-						var shader = check.check(name, shader);
+						var shader = check.check(className, shader);
 						//Printer.check(shader);
 						var str = Context.defined("display") ? "" : Serializer.run(shader);
 						f.kind = FVar(null, { expr : EConst(CString(str)), pos : pos } );
@@ -444,6 +444,15 @@ class Macros {
 						for( f in buildFields(shader, check.inits, pos) )
 							if( !supFields.exists(f.name) )
 								fields.push(f);
+
+						fields.push( {
+							name : "_MODULE",
+							kind : FVar(null, macro $v{c.module}),
+							pos : pos,
+							access : [AStatic],
+							meta : [{ name : ":keep", pos : pos }],
+						});
+
 					} catch( e : Ast.Error ) {
 						fields.remove(f);
 						Context.error(e.msg, e.pos);

+ 2 - 2
hxsl/Shader.hx

@@ -28,7 +28,7 @@ class Shader {
 				throw std.Type.getClassName(cl) + " has no shader source";
 			shader = curClass._SHADER;
 			if( shader == null ) {
-				shader = new SharedShader(curClass.SRC);
+				shader = new SharedShader(curClass.SRC,curClass._MODULE);
 				curClass._SHADER = shader;
 			}
 		}
@@ -50,7 +50,7 @@ class Shader {
 		throw "assert"; // will be subclassed in sub shaders
 		return 0.;
 	}
-	
+
 	public function setParamIndexValue( index : Int, val : Dynamic ) {
 		throw "assert"; // will be subclassed in sub shaders
 	}

+ 122 - 3
hxsl/SharedShader.hx

@@ -36,26 +36,32 @@ class ShaderConst {
 }
 
 class SharedShader {
-
 	public static var UNROLL_LOOPS = false;
+	static var SHADER_RESOLVE : Map<String, SharedShader> = [];
 
 	public var data : ShaderData;
 	public var globals : Array<ShaderGlobal>;
 	public var consts : ShaderConst;
 	var instanceCache : Map<Int,ShaderInstance>;
 	var paramsCount : Int;
+	var file : hxd.fs.FileEntry;
+	var module : String;
 
-	public function new(src:String) {
+	public function new(src:String,?module:String) {
 		instanceCache = new Map();
 		consts = null;
 		globals = [];
 		if( src == "" )
 			return;
+		this.module = module;
 		data = new hxsl.Serializer().unserialize(src);
 		for( v in data.vars )
 			initVarId(v);
 		data = compactMem(data);
 		initialize();
+		#if !macro
+		initLiveReload();
+		#end
 	}
 
 	function initialize() {
@@ -177,7 +183,7 @@ class SharedShader {
 				}
 			default:
 			}
-		} 
+		}
 		eval.inlineCalls = true;
 		eval.unrollLoops = UNROLL_LOOPS;
 		var edata = eval.eval(data);
@@ -257,6 +263,119 @@ class SharedShader {
 		}
 	}
 
+	#if !macro
+
+	function initLiveReload() {
+		if( module == null )
+			return;
+		if( hxd.fs.SourceLoader.isActive() )
+			SHADER_RESOLVE.set(data.name, this);
+		var path = module.split(".").join("/")+".hx";
+		file = hxd.fs.SourceLoader.resolve(path);
+		if( file != null )
+			file.watch(onFileReload);
+		else
+			trace("Could not live reload shader "+data.name);
+	}
+
+	function onFileReload() {
+		// reload all shaders within the same file
+		for( sh in SHADER_RESOLVE )
+			if( sh.file == file )
+				sh.reloadShader();
+	}
+
+	function reloadShader() {
+		try {
+			var expr = loadShader(file, data.name);
+			if( expr == null )
+				return;
+			var checker = new hxsl.Checker();
+			checker.loadShader = function(name) {
+				var sh = SHADER_RESOLVE.get(name);
+				if( sh == null )
+					throw "Could not resolve shader "+name;
+				if( sh.file == null )
+					throw "Shader "+name+" can't be live reload because of missing live path";
+				return loadShader(sh.file, sh.data.name);
+			};
+			var data = checker.check(data.name,expr);
+			applyChanges(data);
+		} catch( e : hxsl.Ast.Error ) {
+			var line = file.getText().substr(0,e.pos.min).split("\n").length;
+			#if sys
+			Sys.println(e.pos.file+":"+line+": "+e.msg);
+			#else
+			haxe.Log.trace(e.msg,{ methodName: null, className: null, fileName : e.pos.file, lineNumber : line });
+			#end
+			return;
+		}
+	}
+
+	static function mergeVars( vl : Array<TVar>, vl2 : Array<TVar> ) {
+		if( vl.length != vl2.length )
+			return false;
+		for( i => v in vl ) {
+			var v2 = vl2[i];
+			if( v.name != v2.name )
+				return false;
+			v2.id = v.id; // copy ids
+			switch( [v.type, v2.type] ) {
+			case [TStruct(vl),TStruct(vl2)]:
+				if( vl.length != vl2.length ) return false;
+				if( !mergeVars(vl, vl2) )
+					return false;
+			default:
+			}
+		}
+		return true;
+	}
+
+	function applyChanges( data2 : ShaderData ) {
+		if( !mergeVars(data.vars, data2.vars) )
+			return false;
+		data = compactMem(data2);
+		instanceCache = new Map();
+		return true;
+	}
+
+	static function loadShader( fs : hxd.fs.FileEntry, name : String ) : Ast.Expr {
+		var text = fs.getText();
+		#if !hscript
+		throw "Shader live reload requires --library hscript";
+		return null;
+		#else
+		var parser = new hscript.Parser();
+		var m = try parser.parseModule(text,fs.path) catch( e : hscript.Expr.Error ) {
+			Sys.println(e.toString());
+			return null;
+		}
+		var clName = name.split(".").pop();
+		var found = null;
+		for( def in m )
+			switch( def ) {
+			case DClass(c) if( c.name == clName ):
+				for( f in c.fields )
+					if( f.name == "SRC" ) {
+						switch( f.kind ) {
+						case KVar(v): found = v.expr;
+						default:
+						}
+						break;
+					}
+			default:
+			}
+		// no matching class with SRC found in this module
+		if( found == null )
+			return null;
+		var expr = new hscript.Macro({ file : fs.path, min : 0, max : 0 }).convert(found);
+		var expr = new hxsl.MacroParser().parseExpr(expr);
+		return expr;
+		#end
+	}
+
+	#end
+
 	public static function compactMem<T>( mem : T ) {
 		#if (hl && heaps_compact_mem)
 		mem = hl.Api.compact(mem, null, 0, null);