Skip to content

Commit

Permalink
added shader live reload
Browse files Browse the repository at this point in the history
  • Loading branch information
ncannasse committed Jan 11, 2025
1 parent ea16bbe commit aeae8de
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 11 deletions.
35 changes: 35 additions & 0 deletions hxd/fs/SourceLoader.hx
Original file line number Diff line number Diff line change
@@ -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;
}

}
21 changes: 15 additions & 6 deletions hxsl/Macros.hx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 } );
Expand All @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions hxsl/Shader.hx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand All @@ -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
}
Expand Down
125 changes: 122 additions & 3 deletions hxsl/SharedShader.hx
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -177,7 +183,7 @@ class SharedShader {
}
default:
}
}
}
eval.inlineCalls = true;
eval.unrollLoops = UNROLL_LOOPS;
var edata = eval.eval(data);
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit aeae8de

Please sign in to comment.