diff --git a/hxsl/Ast.hx b/hxsl/Ast.hx index 488faf332..a6bc9c73d 100644 --- a/hxsl/Ast.hx +++ b/hxsl/Ast.hx @@ -642,6 +642,16 @@ class Tools { } } + public static function hasReturn( e : TExpr ) { + var found = false; + function rec( e : TExpr ) { + if( found ) return; + if( e.e.match(TReturn(_)) ) found = true else iter(e, rec); + } + rec(e); + return found; + } + public static function iter( e : TExpr, f : TExpr -> Void ) { switch( e.e ) { case TParenthesis(e): f(e); diff --git a/hxsl/Cache.hx b/hxsl/Cache.hx index 08a768a94..e31385d0a 100644 --- a/hxsl/Cache.hx +++ b/hxsl/Cache.hx @@ -382,6 +382,24 @@ class Cache { Printer.check(s,[prev[i]]); #end + if( SharedShader.OUTLINE_HELPERS ) { + var prev = sl; + sl = new hxsl.Outliner().outline(sl); + + #if shader_debug_dump + if( dbg != null ) { + dbg.writeString("----- OUTLINE ----\n\n"); + for( s in sl ) + dbg.writeString(Printer.shaderToString(s, Debug.VAR_IDS) + "\n\n"); + } + #end + + #if debug + for( i => s in sl ) + Printer.check(s,[prev[i]]); + #end + } + var r = buildRuntimeShader(sl, paramVars); r.mode = mode; diff --git a/hxsl/Eval.hx b/hxsl/Eval.hx index a9a32c177..cf705a3e4 100644 --- a/hxsl/Eval.hx +++ b/hxsl/Eval.hx @@ -10,6 +10,7 @@ class Eval { public var varMap : Map; public var inlineCalls : Bool; + public var markInlined : Bool; public var unrollLoops : Bool; public var eliminateConditionals : Bool; var constants : Map; @@ -380,7 +381,10 @@ class Eval { default: outExprs.push(e); } - TBlock(outExprs); + if( markInlined && outExprs.length > 0 ) + TMeta(Outliner.META, [CString(f.ref.name)], { e : TBlock(outExprs), t : t, p : e.p }); + else + TBlock(outExprs); case TVar(_): TCall(c, args); default: diff --git a/hxsl/GlslOut.hx b/hxsl/GlslOut.hx index 4bc776821..b111d46c3 100644 --- a/hxsl/GlslOut.hx +++ b/hxsl/GlslOut.hx @@ -92,6 +92,7 @@ class GlslOut { var allNames : Map; var outIndexes : Map; var isCompute : Bool; + var curHelperArgs : Array; var highpUniforms : Bool; var isES(get,never) : Bool; @@ -260,16 +261,30 @@ class GlslOut { } } - function addValue( e : TExpr, tabs : String ) { + function addArgs( decl : Bool ) { + if( curHelperArgs == null ) { + add(decl ? "(void)" : "()"); + return; + } + add("("); + var first = true; + for( a in curHelperArgs ) { + if( first ) first = false else add(", "); + if( decl ) addVar(a) else ident(a); + } + add(")"); + } + + function addValue( e : TExpr, tabs : String, ?helper : String ) { switch( e.e ) { case TBlock(el): - var name = "_val" + (exprIds++); + var name = (helper == null ? "" : helper) + "_val" + (exprIds++); var tmp = buf; buf = new StringBuf(); addType(e.t); add(" "); add(name); - add("(void)"); + addArgs(true); var el2 = el.copy(); var last = el2[el2.length - 1]; el2[el2.length - 1] = { e : TReturn(last), t : e.t, p : last.p }; @@ -282,7 +297,7 @@ class GlslOut { exprValues.push(buf.toString()); buf = tmp; add(name); - add("()"); + addArgs(false); case TIf(econd, eif, eelse): add("( "); addValue(econd, tabs); @@ -290,6 +305,8 @@ class GlslOut { addValue(eif, tabs); add(" : "); addValue(eelse, tabs); + case TMeta(m, [CString(helper)], e) if( m == Outliner.VALUE ): + addValue(e, tabs, helper); case TMeta(_, _, e): addValue(e, tabs); default: @@ -839,6 +856,48 @@ class GlslOut { } } + function emitHelper( f : TFunction ) { + curHelperArgs = f.args.length == 0 ? null : f.args; + addType(f.ret); + add(" "); + ident(f.ref); + add("("); + var first = true; + for( a in f.args ) { + if( first ) first = false else add(", "); + addVar(a); + } + add(") {\n"); + var expr = f.expr; + if( f.ret != TVoid && !Tools.hasReturn(expr) ) { + // the last expression of the body is an implicit return + switch( expr.e ) { + case TBlock(el) if( el.length > 0 ): + el = el.copy(); + var last = el[el.length - 1]; + el[el.length - 1] = { e : TReturn(last), t : TVoid, p : last.p }; + expr = { e : TBlock(el), t : TVoid, p : expr.p }; + case TBlock(_): + default: + expr = { e : TReturn(expr), t : TVoid, p : expr.p }; + } + } + switch( expr.e ) { + case TBlock(el): + for( e in el ) { + add("\t"); + addExpr(e, "\t"); + newLine(e); + } + default: + add("\t"); + addExpr(expr, "\t"); + newLine(expr); + } + add("}"); + curHelperArgs = null; + } + public function run( s : ShaderData ) { var foundGlobals = new Map(); @@ -850,8 +909,8 @@ class GlslOut { buf = new StringBuf(); exprValues = []; - if( s.funs.length != 1 ) throw "assert"; - var f = s.funs[0]; + var f = s.funs[s.funs.length - 1]; + if( f == null || f.kind == Helper ) throw "assert"; isVertex = f.kind == Vertex; isCompute = f.kind == Main; highpUniforms = isES && f.kind == Fragment && (foundGlobals.exists(FloatBitsToInt) || foundGlobals.exists(FloatBitsToUint)); @@ -869,6 +928,12 @@ class GlslOut { decl('layout(local_size_x = ${computeLayout[0]}, local_size_y = ${computeLayout[1]}, local_size_z = ${computeLayout[2]}) in;'); var tmp = buf; + for( h in s.funs ) { + if( h.kind != Helper ) continue; + buf = new StringBuf(); + emitHelper(h); + exprValues.push(buf.toString()); + } buf = new StringBuf(); add("void main(void) {\n"); switch( f.expr.e ) { diff --git a/hxsl/HlslOut.hx b/hxsl/HlslOut.hx index 8bb06bcde..cf6489aed 100644 --- a/hxsl/HlslOut.hx +++ b/hxsl/HlslOut.hx @@ -144,8 +144,11 @@ class HlslOut { var allNames : Map; var bindlessSamplersCount : Int; var bindlessSamplers : Map; + var unresolvedBindless : Map; var samplers : Map; var computeLayout : Array; + var inHelper : Bool; + var curHelperArgs : Array; public var varNames : Map; var varAccess : Map; @@ -283,16 +286,26 @@ class HlslOut { } } - function addValue( e : TExpr, tabs : String ) { + function addValue( e : TExpr, tabs : String, ?helper : String ) { switch( e.e ) { case TBlock(el): - var name = "_val" + (exprIds++); + var name = (helper == null ? "" : helper) + "_val" + (exprIds++); var tmp = buf; buf = new StringBuf(); addType(e.t); add(" "); add(name); - add("(void)"); + if( curHelperArgs == null ) + add("(void)"); + else { + add("("); + var first = true; + for( a in curHelperArgs ) { + if( first ) first = false else add(", "); + addVar(a); + } + add(")"); + } var el2 = el.copy(); var last = el2[el2.length - 1]; el2[el2.length - 1] = { e : TReturn(last), t : e.t, p : last.p }; @@ -305,7 +318,17 @@ class HlslOut { exprValues.push(buf.toString()); buf = tmp; add(name); - add("()"); + if( curHelperArgs == null ) + add("()"); + else { + add("("); + var first = true; + for( a in curHelperArgs ) { + if( first ) first = false else add(", "); + ident(a); + } + add(")"); + } case TIf(econd, eif, eelse): add("( "); addValue(econd, tabs); @@ -313,8 +336,10 @@ class HlslOut { addValue(eif, tabs); add(" : "); addValue(eelse, tabs); + case TMeta(m, [CString(helper)], e) if( m == Outliner.VALUE ): + addValue(e, tabs, helper); case TMeta(m,args,e): - handleMeta(m, args, addValue, e, tabs); + handleMeta(m, args, (e, tabs) -> addValue(e, tabs), e, tabs); default: addExpr(e, tabs); } @@ -481,6 +506,15 @@ class HlslOut { } } + function bindlessSlot( v : TVar ) { + var b = bindlessSamplers.get(v.id); + if( b == null ) { + b = bindlessSamplersCount++; + bindlessSamplers.set(v.id, b); + } + return b; + } + function resolveSamplerRef( e : TExpr ) : { s : SamplerRef, b : Int } { switch( e.e ) { case TVar(v): @@ -488,8 +522,10 @@ class HlslOut { if( b != null ) return { s : null, b : b }; var r = samplers.get(v.id); - if( r == null ) - return null; + if( r == null ) { + unresolvedBindless.set(v.id, v); + return { s : null, b : bindlessSlot(v) }; + } return { s : { arr : r.arr, offset : r.offset, index : r.index }, b : -1 }; case TArray(ea, index): var r = resolveSamplerRef(ea); @@ -620,9 +656,9 @@ class HlslOut { add(", "); addValue(tex, tabs); add(", "); - add('__BindlessSamplers[$bindlessSamplersCount]'); + add('__BindlessSamplers[${bindlessSlot(v)}]'); add(")"); - bindlessSamplers.set(v.id, bindlessSamplersCount++); + unresolvedBindless.remove(v.id); case TCall({ e : TGlobal( g = ResolveBuffer) }, args = [handle, buf = { e : TVar(v)}]): declGlobal(g, args); add("resolveBuffer"); @@ -824,7 +860,7 @@ class HlslOut { add("discard"); case TReturn(e): if( e == null ) { - if ( isCompute ) + if ( isCompute || inHelper ) add("return"); else add("return _out"); @@ -1163,6 +1199,50 @@ class HlslOut { add("}"); } + function emitHelper( f : TFunction ) { + inHelper = true; + curHelperArgs = f.args.length == 0 ? null : f.args; + addType(f.ret); + add(" "); + ident(f.ref); + add("("); + var first = true; + for( a in f.args ) { + if( first ) first = false else add(", "); + addVar(a); + } + add(") {\n"); + var expr = f.expr; + if( f.ret != TVoid && !expr.hasReturn() ) { + // the last expression of the body is an implicit return + switch( expr.e ) { + case TBlock(el) if( el.length > 0 ): + el = el.copy(); + var last = el[el.length - 1]; + el[el.length - 1] = { e : TReturn(last), t : TVoid, p : last.p }; + expr = { e : TBlock(el), t : TVoid, p : expr.p }; + case TBlock(_): + default: + expr = { e : TReturn(expr), t : TVoid, p : expr.p }; + } + } + switch( expr.e ) { + case TBlock(el): + for( e in el ) { + add("\t"); + addExpr(e, "\t"); + newLine(e); + } + default: + add("\t"); + addExpr(expr, "\t"); + newLine(expr); + } + add("}"); + inHelper = false; + curHelperArgs = null; + } + function initLocals() { var locals = Lambda.array(locals); locals.sort(function(v1, v2) return Reflect.compare(v1.name, v2.name)); @@ -1186,13 +1266,14 @@ class HlslOut { public function run( s : ShaderData ) { locals = new Map(); bindlessSamplers = new Map(); + unresolvedBindless = new Map(); bindlessSamplersCount = 0; decls = []; buf = new StringBuf(); exprValues = []; - if( s.funs.length != 1 ) throw "assert"; - var f = s.funs[0]; + var f = s.funs[s.funs.length - 1]; + if( f == null || f.kind == Helper ) throw "assert"; kind = f.kind; varAccess = new Map(); samplers = new Map(); @@ -1202,11 +1283,20 @@ class HlslOut { initStatics(s); var tmp = buf; + for( h in s.funs ) { + if( h.kind != Helper ) continue; + buf = new StringBuf(); + emitHelper(h); + exprValues.push(buf.toString()); + } buf = new StringBuf(); emitMain(f.expr); exprValues.push(buf.toString()); buf = tmp; + for( v in unresolvedBindless ) + throw "Cannot resolve sampler for " + v.name; + initLocals(); decls.push(buf.toString()); diff --git a/hxsl/Outliner.hx b/hxsl/Outliner.hx new file mode 100644 index 000000000..44bb25a98 --- /dev/null +++ b/hxsl/Outliner.hx @@ -0,0 +1,487 @@ +package hxsl; +using hxsl.Ast; + +private class Region { + public var e : TExpr; + public var name : String; + public var isVal : Bool; + public var params : Array = []; + public var args : Array = []; + public var body : Array; + public var freeMap : Map; + public var key : String; + public var size : Int; + public function new() { + } +} + +/** + Eval inlines every helper call and tags the result with a TMeta(META) region : constant args are + folded and DCE works on each call site exactly as with plain inlining. Once DCE is done, the regions + that occur several times identically in a stage are turned back into a single function, the others + are unwrapped. An unwrapped value keeps the helper name in a TMeta(VALUE) so that the function the + backend emits for a value block is named after it. +**/ +class Outliner { + + public static inline var META = "inlined"; + public static inline var VALUE = "inlinedValue"; + // statement regions with fewer expression nodes are kept inlined ; not values, since the backend + // would turn each of them into its own function anyway + public static var MIN_SIZE = 10; + + var shaderVars : Map; + var regions : Array; + var funs : Array; + var refs : Array; + + var ids : Map; + var nextId : Int; + var buf : StringBuf; + var size : Int; + + public function new() { + } + + public function outline( shaders : Array ) { + return [for( s in shaders ) outlineShader(s)]; + } + + function outlineShader( s : ShaderData ) : ShaderData { + shaderVars = new Map(); + for( v in s.vars ) + shaderVars.set(v.id, true); + funs = []; + refs = []; + // each pass resolves the innermost regions, rewriting them in place (Dce created these nodes) + while( true ) { + regions = []; + var found = false; + for( f in s.funs ) + if( collect(f.expr, f.ret != TVoid) ) + found = true; + if( !found ) break; + resolve(); + } + return { + name : s.name, + vars : s.vars.concat(refs), + funs : funs.concat(s.funs), + }; + } + + // innermost regions first : returns true if e contains a region + function collect( e : TExpr, isVal : Bool ) : Bool { + switch( e.e ) { + case TMeta(m, [CString(name)], body) if( m == META ): + if( !collect(body, isVal) ) + regions.push(makeRegion(e, name, body, isVal)); + return true; + case TBlock(el): + var found = false; + for( i => e in el ) + if( collect(e, isVal && i == el.length - 1) ) + found = true; + return found; + case TIf(econd, eif, eelse): + var found = collect(econd, true); + if( collect(eif, isVal) ) found = true; + if( eelse != null && collect(eelse, isVal) ) found = true; + return found; + case TFor(_, it, loop): + var found = collect(it, true); + return collect(loop, false) || found; + case TWhile(econd, loop, _): + var found = collect(econd, true); + return collect(loop, false) || found; + case TSwitch(ec, cases, def): + var found = collect(ec, true); + for( c in cases ) { + for( v in c.values ) + if( collect(v, true) ) found = true; + if( collect(c.expr, isVal) ) found = true; + } + if( def != null && collect(def, isVal) ) found = true; + return found; + case TMeta(_, _, e): + return collect(e, isVal); + default: + var found = false; + e.iter(function(e) if( collect(e, true) ) found = true); + return found; + } + } + + function makeRegion( e : TExpr, name : String, body : TExpr, isVal : Bool ) { + var r = new Region(); + r.e = e; + r.name = name; + r.isVal = isVal; + var el = switch( body.e ) { + case TBlock(el): el; + default: [body]; + }; + var locals = new Map(); + var i = 0; + while( i < el.length - (isVal ? 1 : 0) ) { + switch( el[i].e ) { + // the evaluated args come first : make them parameters, evaluated by the caller + case TVarDecl(v, init) if( init != null && isParamType(v.type) && !init.hasSideEffect() && !readsAny(init, locals) ): + r.params.push(v); + r.args.push(init); + locals.set(v.id, true); + i++; + default: + break; + } + } + r.body = el.slice(i); + if( r.body.length == 0 || !checkBody(r.body, locals) ) + return r; + + // the caller locals it reads are passed by value, it must not write them + var free = []; + var written = new Map(); + for( e in r.body ) + collectFree(e, locals, free, written); + r.freeMap = new Map(); + for( v in free ) { + if( written.exists(v.id) || !isParamType(v.type) ) + return r; + var p : TVar = { id : Tools.allocVarId(), name : v.name, type : v.type, kind : Local }; + r.freeMap.set(v.id, p); + r.params.push(p); + r.args.push({ e : TVar(v), t : v.type, p : e.p }); + } + + ids = new Map(); + nextId = 0; + buf = new StringBuf(); + size = 0; + buf.add(isVal ? typeKey(e.t) : "void"); + for( v in r.params ) + declKey(v); + for( v in free ) + ids.set(v.id, ids.get(r.freeMap.get(v.id).id)); + buf.add("|"); + for( e in r.body ) { + exprKey(e); + buf.add(";"); + } + r.key = buf.toString(); + r.size = size; + buf = null; + return r; + } + + function resolve() { + var groups = new Map>(); + var order = []; + for( r in regions ) { + if( r.key == null ) { + unwrap(r); + continue; + } + var g = groups.get(r.key); + if( g == null ) { + g = []; + groups.set(r.key, g); + order.push(g); + } + g.push(r); + } + for( g in order ) { + if( g.length < 2 || (g[0].size < MIN_SIZE && !g[0].isVal) ) { + for( r in g ) + unwrap(r); + continue; + } + var f = makeFunction(g[0]); + for( r in g ) { + r.e.e = TCall({ e : TVar(f.ref), t : f.ref.type, p : r.e.p }, r.args); + r.e.t = f.ret; + } + } + } + + function unwrap( r : Region ) { + switch( r.e.e ) { + case TMeta(_, _, body): + if( r.isVal ) + r.e.e = TMeta(VALUE, [CString(r.name)], body); + else { + r.e.e = body.e; + r.e.t = body.t; + } + default: + throw "assert"; + } + } + + function makeFunction( r : Region ) : TFunction { + var ret = r.isVal ? r.e.t : TVoid; + var ref : TVar = { + id : Tools.allocVarId(), + name : r.name, + kind : Function, + type : TFun([{ args : [for( p in r.params ) { name : p.name, type : p.type }], ret : ret }]), + }; + function mapFree( e : TExpr ) : TExpr { + return switch( e.e ) { + case TVar(v) if( r.freeMap.exists(v.id) ): + var p = r.freeMap.get(v.id); + { e : TVar(p), t : p.type, p : e.p }; + default: + e.map(mapFree); + } + } + var body = [for( e in r.body ) mapFree(e)]; + var f : TFunction = { + kind : Helper, + ref : ref, + args : r.params, + ret : ret, + expr : body.length == 1 ? body[0] : { e : TBlock(body), t : ret, p : r.e.p }, + }; + funs.push(f); + refs.push(ref); + return f; + } + + function isParamType( t : Type ) { + return switch( t ) { + case TInt, TBool, TFloat, TVec(_), TMat2, TMat3, TMat4, TMat3x4, TEnum(_): true; + default: false; + } + } + + function isShaderVar( v : TVar ) { + while( v.parent != null ) + v = v.parent; + return v.kind != Local || shaderVars.exists(v.id); + } + + function readsAny( e : TExpr, vars : Map ) { + var found = false; + function rec( e : TExpr ) { + switch( e.e ) { + case TVar(v) if( vars.exists(v.id) ): found = true; + default: if( !found ) e.iter(rec); + } + } + rec(e); + return found; + } + + // a function body can't break out of the caller loop + function checkBody( el : Array, locals : Map ) { + var ok = true; + function rec( e : TExpr, inLoop : Bool ) { + if( !ok ) return; + switch( e.e ) { + case TReturn(_), TSyntax(_, _, _): + ok = false; + case TBreak, TContinue if( !inLoop ): + ok = false; + case TFor(v, it, loop): + locals.set(v.id, true); + rec(it, inLoop); + rec(loop, true); + case TWhile(econd, loop, _): + rec(econd, inLoop); + rec(loop, true); + case TVarDecl(v, init): + locals.set(v.id, true); + if( init != null ) rec(init, inLoop); + default: + e.iter(function(e) rec(e, inLoop)); + } + } + for( e in el ) + rec(e, false); + return ok; + } + + function collectFree( e : TExpr, locals : Map, free : Array, written : Map ) { + switch( e.e ) { + case TVar(v) if( !locals.exists(v.id) && !isShaderVar(v) ): + locals.set(v.id, true); + free.push(v); + case TBinop(OpAssign | OpAssignOp(_), e1, _), TUnop(OpIncrement | OpDecrement, e1): + var v = rootVar(e1); + if( v != null ) written.set(v.id, true); + default: + } + e.iter(function(e) collectFree(e, locals, free, written)); + } + + function rootVar( e : TExpr ) { + return switch( e.e ) { + case TVar(v): v; + case TSwiz(e, _), TArray(e, _), TField(e, _), TParenthesis(e): rootVar(e); + default: null; + } + } + + function typeKey( t : Type ) { + return Std.string(t); + } + + function declKey( v : TVar ) { + ids.set(v.id, nextId++); + buf.add("<"); + buf.add(typeKey(v.type)); + buf.add(">"); + } + + function exprKey( e : TExpr ) { + size++; + switch( e.e ) { + case TConst(c): + buf.add("c"); + switch( c ) { + case CNull: buf.add("n"); + case CBool(b): buf.add(b ? "t" : "f"); + case CInt(i): buf.add("i"); buf.add(i); + case CFloat(f): + var bits = haxe.io.FPHelper.doubleToI64(f); + buf.add("f"); buf.add(bits.high); buf.add(":"); buf.add(bits.low); + case CString(s): buf.add("s"); buf.add(s.length); buf.add(":"); buf.add(s); + } + case TVar(v): + var id = ids.get(v.id); + if( id != null ) { + buf.add("$"); + buf.add(id); + } else { + buf.add("@"); + buf.add(v.id); + } + case TGlobal(g): + buf.add("G"); + buf.add(g.getIndex()); + case TParenthesis(e): + buf.add("("); + exprKey(e); + buf.add(")"); + case TBlock(el): + buf.add("{"); + for( e in el ) { + exprKey(e); + buf.add(";"); + } + buf.add("}"); + case TBinop(op, e1, e2): + buf.add("b"); + buf.add(Std.string(op)); + buf.add("("); + exprKey(e1); + buf.add(","); + exprKey(e2); + buf.add(")"); + case TUnop(op, e1): + buf.add("u"); + buf.add(Std.string(op)); + buf.add("("); + exprKey(e1); + buf.add(")"); + case TVarDecl(v, init): + buf.add("d"); + declKey(v); + if( init != null ) { + buf.add("="); + exprKey(init); + } + case TCall(e, args): + buf.add("C"); + buf.add(typeKey(e.t)); + exprKey(e); + buf.add("("); + for( a in args ) { + exprKey(a); + buf.add(","); + } + buf.add(")"); + case TSwiz(e, regs): + buf.add("s"); + for( r in regs ) + buf.add(r.getIndex()); + buf.add("("); + exprKey(e); + buf.add(")"); + case TIf(econd, eif, eelse): + buf.add("i("); + exprKey(econd); + buf.add(")"); + exprKey(eif); + if( eelse != null ) { + buf.add("e"); + exprKey(eelse); + } + case TDiscard: + buf.add("D"); + case TReturn(e): + buf.add("R"); + if( e != null ) exprKey(e); + case TFor(v, it, loop): + buf.add("f"); + declKey(v); + exprKey(it); + exprKey(loop); + case TContinue: + buf.add("k"); + case TBreak: + buf.add("K"); + case TArray(e, index): + buf.add("["); + exprKey(e); + buf.add(","); + exprKey(index); + buf.add("]"); + case TArrayDecl(el): + buf.add("A"); + buf.add(typeKey(e.t)); + buf.add("("); + for( e in el ) { + exprKey(e); + buf.add(","); + } + buf.add(")"); + case TSwitch(e, cases, def): + buf.add("S("); + exprKey(e); + buf.add(")"); + for( c in cases ) { + buf.add("c"); + for( v in c.values ) { + exprKey(v); + buf.add(","); + } + exprKey(c.expr); + } + if( def != null ) { + buf.add("d"); + exprKey(def); + } + case TWhile(e, loop, normalWhile): + buf.add(normalWhile ? "w" : "W"); + exprKey(e); + exprKey(loop); + case TMeta(m, args, e): + buf.add("m"); + buf.add(m.length); + buf.add(":"); + buf.add(m); + buf.add(Std.string(args)); + exprKey(e); + case TField(e, name): + buf.add("."); + buf.add(name); + buf.add(":"); + exprKey(e); + case TSyntax(_, _, _): + throw "assert"; + } + } + +} diff --git a/hxsl/SharedShader.hx b/hxsl/SharedShader.hx index d04785ffd..88c31cf0c 100644 --- a/hxsl/SharedShader.hx +++ b/hxsl/SharedShader.hx @@ -37,6 +37,9 @@ class ShaderConst { class SharedShader { public static var UNROLL_LOOPS = false; + // helpers are inlined so that constant args fold and DCE sees each call site, then identical + // call sites are turned back into functions by hxsl.Outliner + public static var OUTLINE_HELPERS = #if hxsl_no_outline false #else true #end; static var SHADER_RESOLVE : Map = []; public var data : ShaderData; @@ -195,6 +198,7 @@ class SharedShader { } } eval.inlineCalls = true; + eval.markInlined = OUTLINE_HELPERS; eval.unrollLoops = UNROLL_LOOPS; var edata = eval.eval(data); edata = compactMem(edata);