Browse Source

compute textures support (glsl)

Nicolas Cannasse 1 year ago
parent
commit
28ec43cbec
14 changed files with 323 additions and 126 deletions
  1. 37 10
      h3d/impl/GlDriver.hx
  2. 4 0
      h3d/mat/Data.hx
  3. 27 7
      hxsl/Ast.hx
  4. 2 2
      hxsl/Cache.hx
  5. 1 1
      hxsl/ChannelTexture.hx
  6. 73 37
      hxsl/Checker.hx
  7. 1 1
      hxsl/Dce.hx
  8. 5 5
      hxsl/Eval.hx
  9. 39 20
      hxsl/Flatten.hx
  10. 61 19
      hxsl/GlslOut.hx
  11. 35 3
      hxsl/MacroParser.hx
  12. 6 8
      hxsl/Macros.hx
  13. 29 8
      hxsl/Serializer.hx
  14. 3 5
      hxsl/Types.hx

+ 37 - 10
h3d/impl/GlDriver.hx

@@ -267,7 +267,7 @@ class GlDriver extends Driver {
 			return makeCompiler().run(sh);
 		}
 		if( shader.mode == Compute )
-			return "// compute:\n" + compile(shader.compute.data);
+			return compile(shader.compute.data);
 		return "// vertex:\n" + compile(shader.vertex.data) + "// fragment:\n" + compile(shader.fragment.data);
 	}
 
@@ -335,16 +335,37 @@ class GlDriver extends Driver {
 			var tt = t.type;
 			var count = 1;
 			switch( tt ) {
-			case TChannel(_): tt = TSampler2D;
+			case TChannel(_): tt = TSampler(T2D,false);
 			case TArray(t,SConst(n)): tt = t; count = n;
 			default:
 			}
 			if( tt != curT ) {
 				curT = tt;
 				name = switch( tt ) {
-				case TSampler2D: mode = GL.TEXTURE_2D; "Textures";
-				case TSamplerCube: mode = GL.TEXTURE_CUBE_MAP; "TexturesCube";
-				case TSampler2DArray: mode = GL.TEXTURE_2D_ARRAY; "TexturesArray";
+				case TSampler(dim,arr):
+					mode = switch( [dim, arr] ) {
+					case [T1D, false]: GL.TEXTURE_1D;
+					case [T2D, false]: GL.TEXTURE_2D;
+					case [T3D, false]: GL.TEXTURE_3D;
+					case [TCube, false]: GL.TEXTURE_CUBE_MAP;
+					case [T1D, true]: GL.TEXTURE_1D_ARRAY;
+					case [T2D, true]: GL.TEXTURE_2D_ARRAY;
+					case [TCube, true]: GL.TEXTURE_CUBE_MAP_ARRAY;
+					default: throw "Texture not supported "+tt;
+					}
+					"Textures" + (dim == T2D ? "" : dim.getName().substr(1))+(arr ? "Array" : "");
+				case TRWTexture(dim, arr, chans):
+					mode = switch( [dim, arr] ) {
+					case [T1D, false]: GL.IMAGE_1D;
+					case [T2D, false]: GL.IMAGE_2D;
+					case [T3D, false]: GL.IMAGE_3D;
+					case [TCube, false]: GL.IMAGE_CUBE;
+					case [T1D, true]: GL.IMAGE_1D_ARRAY;
+					case [T2D, true]: GL.IMAGE_2D_ARRAY;
+					case [TCube, true]: GL.IMAGE_CUBE_MAP_ARRAY;
+					default: throw "Texture not supported "+tt;
+					};
+					"TexturesRW" + (dim == T2D ? "" : dim.getName().substr(1))+chans+(arr ? "Array" : "");
 				default: throw "Unsupported texture type "+tt;
 				}
 				index = 0;
@@ -579,11 +600,11 @@ class GlDriver extends Driver {
 				var pt = s.textures[i];
 				if( t == null || t.isDisposed() ) {
 					switch( pt.t ) {
-					case TSampler2D:
+					case TSampler(TCube, false):
+						t = h3d.mat.Texture.defaultCubeTexture();
+					case TSampler(_, false):
 						var color = h3d.mat.Defaults.loadingTextureColor;
 						t = h3d.mat.Texture.fromColor(color, (color >>> 24) / 255);
-					case TSamplerCube:
-						t = h3d.mat.Texture.defaultCubeTexture();
 					default:
 						throw "Missing texture";
 					}
@@ -606,6 +627,11 @@ class GlDriver extends Driver {
 
 				if( pt.u == null ) continue;
 
+				if( pt.t.match(TRWTexture(_)) ) {
+					gl.bindImageTexture(i, cast t.t.t, 0, false, 0, GL.READ_WRITE, GL.RGBA8);
+					continue;
+				}
+
 				var idx = s.kind == Fragment ? curShader.vertex.textures.length + i : i;
 				if( boundTextures[idx] != t.t ) {
 					boundTextures[idx] = t.t;
@@ -910,9 +936,10 @@ class GlDriver extends Driver {
 	}
 
 	function getBindType( t : h3d.mat.Texture ) {
-		var isCube = t.flags.has(Cube);
 		var isArray = t.flags.has(IsArray);
-		return isCube ? GL.TEXTURE_CUBE_MAP : isArray ? GL.TEXTURE_2D_ARRAY : GL.TEXTURE_2D;
+		if( t.flags.has(Cube) )
+			return isArray ? GL.TEXTURE_CUBE_MAP_ARRAY : GL.TEXTURE_CUBE_MAP;
+		return isArray ? GL.TEXTURE_2D_ARRAY : GL.TEXTURE_2D;
 	}
 
 	override function allocTexture( t : h3d.mat.Texture ) : Texture {

+ 4 - 0
h3d/mat/Data.hx

@@ -130,6 +130,10 @@ enum TextureFlags {
 		By default, the texture are loaded from images when created. If this flag is enabled, the texture will be loaded from disk when first used.
 	**/
 	LazyLoading;
+	/**
+		Texture can be written in shaders using RWTexture
+	**/
+	Writable;
 }
 
 typedef TextureFormat = hxd.PixelFormat;

+ 27 - 7
hxsl/Ast.hx

@@ -5,6 +5,13 @@ enum BufferKind {
 	RW;
 }
 
+enum TexDimension {
+	T1D;
+	T2D;
+	T3D;
+	TCube;
+}
+
 enum Type {
 	TVoid;
 	TInt;
@@ -16,15 +23,14 @@ enum Type {
 	TMat4;
 	TMat3x4;
 	TBytes( size : Int );
-	TSampler2D;
-	TSampler2DArray;
-	TSamplerCube;
+	TSampler( dim : TexDimension, isArray : Bool );
+	TRWTexture( dim : TexDimension, isArray : Bool, channels : Int );
+	TMat2;
 	TStruct( vl : Array<TVar> );
 	TFun( variants : Array<FunType> );
 	TArray( t : Type, size : SizeDecl );
 	TBuffer( t : Type, size : SizeDecl, kind : BufferKind );
 	TChannel( size : Int );
-	TMat2;
 }
 
 enum VecType {
@@ -196,6 +202,15 @@ enum FunctionKind {
 	Main;
 }
 
+enum ComputeVar {
+	GlobalInvocation;
+	LocalInvocation;
+	NumWorkGroups;
+	WorkGroup;
+	WorkGroupSize;
+	LocalInvocationIndex;
+}
+
 enum TGlobal {
 	Radians;
 	Degrees;
@@ -289,6 +304,7 @@ enum TGlobal {
 	RoundEven;
 	// compute
 	SetLayout;
+	ComputeVar;
 }
 
 enum Component {
@@ -407,9 +423,9 @@ class Tools {
 		return false;
 	}
 
-	public static function isSampler( t : Type ) {
+	public static function isTexture( t : Type ) {
 		return switch( t ) {
-		case TSampler2D, TSamplerCube, TSampler2DArray, TChannel(_):
+		case TSampler(_), TChannel(_), TRWTexture(_):
 			true;
 		default:
 			false;
@@ -434,6 +450,10 @@ class Tools {
 			};
 			prefix+" "+toString(t) + "[" + (switch( s ) { case SConst(i): "" + i; case SVar(v): v.name; } ) + "]";
 		case TBytes(n): "Bytes" + n;
+		case TSampler(dim, arr):
+			"Sampler"+dim.getName().substr(1)+(arr ? "Array":"");
+		case TRWTexture(dim, arr,dims):
+			"RWTexture"+dim.getName().substr(1)+(arr ? "Array":"")+"<"+(dims == 1 ? "Float" : "Vec"+dims)+">";
 		default: t.getName().substr(1);
 		}
 	}
@@ -560,7 +580,7 @@ class Tools {
 		case TMat4: 16;
 		case TMat3x4: 12;
 		case TBytes(s): s;
-		case TBool, TString, TSampler2D, TSampler2DArray, TSamplerCube, TFun(_): 0;
+		case TBool, TString, TSampler(_), TRWTexture(_), TFun(_): 0;
 		case TArray(t, SConst(v)), TBuffer(t, SConst(v),_): size(t) * v;
 		case TArray(_, SVar(_)), TBuffer(_): 0;
 		}

+ 2 - 2
hxsl/Cache.hx

@@ -461,7 +461,7 @@ class Cache {
 					}
 					var ap = new AllocParam(a.v.name, a.pos, p.instance, p.index, a.v.type);
 					switch( a.v.type ) {
-					case TArray(t,_) if( t.isSampler() ):
+					case TArray(t,_) if( t.isTexture() ):
 						// hack to mark array of texture, see ShaderManager.fillParams
 						ap.pos = -a.size;
 						count += a.size;
@@ -473,7 +473,7 @@ class Cache {
 				for( i in 0...out.length - 1 )
 					out[i].next = out[i + 1];
 				switch( g.type ) {
-				case TArray(t, _) if( t.isSampler() ):
+				case TArray(t, _) if( t.isTexture() ):
 					textures.push({ t : t, all : out });
 					c.texturesCount += count;
 				case TArray(TVec(4, VFloat), SConst(size)):

+ 1 - 1
hxsl/ChannelTexture.hx

@@ -1,3 +1,3 @@
 package hxsl;
 
-typedef ChannelTexture = { texture : hxsl.Types.ChannelTextureType, channel : hxsl.Channel };
+typedef ChannelTexture = { texture : hxsl.Types.TextureChannel, channel : hxsl.Channel };

+ 73 - 37
hxsl/Checker.hx

@@ -25,6 +25,7 @@ class Checker {
 	static var ivec2 = TVec(2, VInt);
 	static var ivec3 = TVec(3, VInt);
 	static var ivec4 = TVec(4, VInt);
+	static var anyRWT = TRWTexture(T1D,false,-1);
 
 	var vars : Map<String,TVar>;
 	var globals : Map<String,{ g : TGlobal, t : Type }>;
@@ -48,6 +49,16 @@ class Checker {
 		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 } ];
+		var texDefs = [
+			{ dim : T1D, arr : false, uv : TFloat, iuv : TInt },
+			{ dim : T2D, arr : false, uv : vec2, iuv : ivec2 },
+			{ dim : T3D, arr : false, uv : vec3, iuv : ivec3 },
+			{ dim : TCube, arr : false, uv : vec3, iuv : ivec3 },
+			{ dim : T1D, arr : true, uv : vec2, iuv : ivec2 },
+			{ dim : T2D, arr : true, uv : vec3, iuv : ivec3 },
+			{ dim : T3D, arr : false, uv : vec4, iuv : ivec4 },
+			{ dim : TCube, arr : false, uv : vec4, iuv : ivec4 },
+		];
 		for( g in Ast.TGlobal.createAll() ) {
 			var def = switch( g ) {
 			case Vec2, Vec3, Vec4, Mat2, Mat3, Mat3x4, Mat4, IVec2, IVec3, IVec4, BVec2, BVec3, BVec4: [];
@@ -67,33 +78,16 @@ class Checker {
 			case Cross:
 				[ { args : [ { name : "a", type : vec3 }, { name : "b", type : vec3 } ], ret : vec3 } ];
 			case Texture:
-				[
-					{ args : [ { name : "tex", type : TSampler2D }, { name : "uv", type : vec2 } ], ret : vec4 },
-					{ args : [ { name : "tex", type : TSamplerCube }, { name : "normal", type : vec3 } ], ret : vec4 },
-					{ args : [ { name : "tex", type : TSampler2DArray }, { name : "uv", type : vec3 } ], ret : vec4 },
-				];
+				[for( t in texDefs ) { args : [{ name : "tex", type : TSampler(t.dim,t.arr) }, { name : "uv", type : t.uv }], ret : vec4 }];
 			case TextureLod:
-				[
-					{ args : [ { name : "tex", type : TSampler2D }, { name : "uv", type : vec2 }, { name : "lod", type : TFloat } ], ret : vec4 },
-					{ args : [ { name : "tex", type : TSamplerCube }, { name : "normal", type : vec3 }, { name : "lod", type : TFloat } ], ret : vec4 },
-					{ args : [ { name : "tex", type : TSampler2DArray }, { name : "uv", type : vec3 }, { name : "lod", type : TFloat } ], ret : vec4 },
-				];
+				[for( t in texDefs ) { args : [{ name : "tex", type : TSampler(t.dim,t.arr) }, { name : "uv", type : t.uv }, { name : "lod", type : TFloat }], ret : vec4 }];
 			case Texel:
-				[
-					{ args : [ { name: "tex", type: TSampler2D }, { name: "pos", type: ivec2 } ], ret: vec4 },
-					{ args : [ { name: "tex", type: TSampler2DArray }, { name: "pos", type: ivec3 } ], ret: vec4 },
-					{ args : [ { name: "tex", type: TSampler2D }, { name: "pos", type: ivec2 }, { name: "lod", type: TInt } ], ret: vec4 },
-					{ args : [ { name: "tex", type: TSampler2DArray }, { name: "pos", type: ivec3 }, { name: "lod", type: TInt } ], ret: vec4 },
-				];
+				[for( t in texDefs ) { args : [{ name : "tex", type : TSampler(t.dim,t.arr) }, { name : "pos", type : t.iuv }], ret : vec4 }];
 			case TextureSize:
-				[
-					{ args : [ { name: "tex", type: TSampler2D } ], ret: vec2 },
-					{ args : [ { name: "tex", type: TSampler2DArray } ], ret: vec3 },
-					{ args : [ { name: "tex", type: TSamplerCube } ], ret: vec2 },
-					{ args : [ { name: "tex", type: TSampler2D }, { name: "lod", type: TInt } ], ret: vec2 },
-					{ args : [ { name: "tex", type: TSampler2DArray }, { name: "lod", type: TInt } ], ret: vec3 },
-					{ args : [ { name: "tex", type: TSamplerCube }, { name: "lod", type: TInt } ], ret: vec2 },
-				];
+				var arr = [for( t in texDefs ) { args : [{ name : "tex", type : TSampler(t.dim,t.arr) }], ret : vec2 }].concat(
+				[for( t in texDefs ) { args : [{ name : "tex", type : TSampler(t.dim,t.arr) },{ name : "lod", type : TInt }], ret : vec2 }]);
+				arr.push({ args : [{name : "tex", type : TRWTexture(T2D,false,-1)}], ret : vec2 });
+				arr;
 			case ToInt:
 				[for( t in baseType ) { args : [ { name : "value", type : t } ], ret : TInt } ];
 			case ToFloat:
@@ -190,6 +184,8 @@ class Checker {
 				[for( i => t in genType ) { args : [ { name: "x", type: genIType[i] } ], ret: t }];
 			case SetLayout:
 				[{ args : [{ name : "x", type : TInt },{ name : "y", type : TInt },{ name : "z", type : TInt }], ret : TVoid }];
+			case ComputeVar:
+				[];
 			case VertexID, InstanceID, FragCoord, FrontFacing:
 				null;
 			}
@@ -340,6 +336,10 @@ class Checker {
 			return tryUnify(t1,t2);
 		case [TChannel(n1), TChannel(n2)] if( n1 == n2 ):
 			return true;
+		case [TSampler(dim1, arr1), TSampler(dim2, arr2)]:
+			return dim1 == dim2 && arr1 == arr2;
+		case [TRWTexture(dim1, arr1, chans1), TRWTexture(dim2, arr2, chans2)]:
+			return dim1 == dim2 && arr1 == arr2 && (chans1 == chans2 || chans2 == -1);
 		default:
 		}
 		return false;
@@ -368,7 +368,7 @@ class Checker {
 				return;
 			case Local if( v.qualifiers == null || v.qualifiers.indexOf(Final) < 0 ):
 				return;
-			case Param if( v.type.match(TBuffer(_,_,RW)) ):
+			case Param, Local if( v.type.match(TBuffer(_,_,RW) | TRWTexture(_)) ):
 				return;
 			default:
 			}
@@ -501,6 +501,21 @@ class Checker {
 			}
 		case ECall(e1, args):
 			function makeCall(e1) {
+				switch( e1.e ) {
+				case TGlobal(ComputeVar) if( args.length <= 1 ):
+					switch( args[0] ) {
+					case { expr : EIdent(name) }:
+						var v = try ComputeVar.createByName(name,[]) catch( e : Dynamic ) null;
+						if( v != null ) {
+							type = v == LocalInvocationIndex ? TInt : ivec3;
+							return TCall(e1,[{ e : TConst(CInt(v.getIndex())), t : TInt, p : e.pos }]);
+						}
+					case null:
+					default:
+					}
+					error("computeVar() argument should be "+ComputeVar.createAll().join("|"), e.pos);
+				default:
+				}
 				return switch( e1.t ) {
 				case TFun(variants):
 					var e = unifyCallParams(e1, args, variants, e.pos);
@@ -658,11 +673,17 @@ class Checker {
 			TBreak;
 		case EArray(e1, e2):
 			var e1 = typeExpr(e1, Value);
-			var e2 = typeExpr(e2, With(TInt));
-			switch( e2.t ) {
-			case TInt:
-			default: unify(e2.t, TInt, e2.p);
+			var indexType = switch( e1.t ) {
+			case TRWTexture(dim, arr, _):
+				var count = dim.getIndex() + 1;
+				if( count >= 3 ) count = 3;
+				if( arr ) count++;
+				count == 1 ? TInt : TVec(count,VInt);
+			default:
+				TInt;
 			}
+			var e2 = typeExpr(e2, With(indexType));
+			unify(e2.t, indexType, e2.p);
 			switch( e1.t ) {
 			case TArray(t, size), TBuffer(t,size,_):
 				switch( [size, e2.e] ) {
@@ -679,6 +700,14 @@ class Checker {
 				type = vec3;
 			case TMat4, TMat3x4:
 				type = vec4;
+			case TRWTexture(_, _, chans):
+				type = switch(chans) {
+				case 2: vec2;
+				case 3: vec3;
+				case 4: vec4;
+				default:
+					TFloat;
+				}
 			default:
 				error("Cannot index " + e1.t.toString() + " : should be an array", e.pos);
 			}
@@ -864,8 +893,7 @@ class Checker {
 					if ( v.kind != Local ) error("Borrow should not have a type qualifier", pos);
 				case Sampler(_):
 					switch( v.type ) {
-					case TArray(t, _) if( t.isSampler() ):
-					case t if( t.isSampler() ):
+					case TSampler(_), TArray(TSampler(_), _):
 					default: error("Sampler should be on sampler type or sampler array", pos);
 					}
 				case Ignore, Doc(_):
@@ -963,13 +991,13 @@ class Checker {
 		var g = globals.get(f);
 		if( g == null ) {
 			var gl : TGlobal = switch( [f, e.t] ) {
-			case ["get", TSampler2D|TSampler2DArray|TSamplerCube]: Texture;
+			case ["get", TSampler(_)]: Texture;
 			case ["get", TChannel(_)]: ChannelRead;
-			case ["getLod", TSampler2D|TSampler2DArray|TSamplerCube]: TextureLod;
+			case ["getLod", TSampler(_)]: TextureLod;
 			case ["getLod", TChannel(_)]: ChannelReadLod;
-			case ["fetch"|"fetchLod", TSampler2D|TSampler2DArray]: Texel;
+			case ["fetch"|"fetchLod", TSampler(_)]: Texel;
 			case ["fetch"|"fetchLod", TChannel(_)]: ChannelFetch;
-			case ["size", TSampler2D|TSampler2DArray|TSamplerCube]: TextureSize;
+			case ["size", TSampler(_) | TRWTexture(_)]: TextureSize;
 			case ["size", TChannel(_)]: ChannelTextureSize;
 			default: null;
 			}
@@ -988,6 +1016,8 @@ class Checker {
 					args.shift();
 					sel.push({ args : args, ret : v.ret });
 				}
+				if( f == "get" )
+					trace(variants, sel);
 				if( sel.length > 0 || variants.length == 0 )
 					return FGlobal(g.g, e, sel);
 			default:
@@ -1031,6 +1061,14 @@ class Checker {
 	function specialGlobal( g : TGlobal, e : TExpr, args : Array<TExpr>, pos : Position ) : TExpr {
 		var type = null;
 		inline function checkLength(n,t) {
+			var skip = false;
+			if( args.length == 1 && (t == TInt || t == TFloat) ) {
+				switch( args[0].t ) {
+				case TVec(n2,t2) if( (t2 == VInt || t2 == VFloat) && n2 == n ): skip = true;
+				default: false;
+				}
+			}
+			if( skip ) return;
 			var tsize = 0;
 			for( a in args )
 				switch( a.t ) {
@@ -1065,8 +1103,6 @@ class Checker {
 			if( args.length == 1 ) {
 				switch( args[0].t ) {
 				case TInt, TFloat:
-				case TVec(n,VFloat):
-					if( n != 3 ) error("Invalid input vector length: "+n+" should be "+k, pos);
 				default:
 					checkLength(k,TInt);
 				}

+ 1 - 1
hxsl/Dce.hx

@@ -45,7 +45,7 @@ class Dce {
 				var i = get(v);
 				if( v.kind == Input )
 					inputs.push(i);
-				if( v.kind == Output || v.type.match(TBuffer(_,_,RW)) )
+				if( v.kind == Output || v.type.match(TBuffer(_,_,RW) | TRWTexture(_)) )
 					i.keep = true;
 			}
 		}

+ 5 - 5
hxsl/Eval.hx

@@ -78,17 +78,17 @@ class Eval {
 		return v2;
 	}
 
-	function checkSamplerRec(t:Type) {
-		if( t.isSampler() )
+	function checkTextureRec(t:Type) {
+		if( t.isTexture() )
 			return true;
 		switch( t ) {
 		case TStruct(vl):
 			for( v in vl )
-				if( checkSamplerRec(v.type) )
+				if( checkTextureRec(v.type) )
 					return true;
 			return false;
 		case TArray(t, _):
-			return checkSamplerRec(t);
+			return checkTextureRec(t);
 		case TBuffer(_):
 			return true;
 		default:
@@ -98,7 +98,7 @@ class Eval {
 
 	function needsInline(f:TFunction) {
 		for( a in f.args )
-			if( checkSamplerRec(a.type) )
+			if( checkTextureRec(a.type) )
 				return true;
 		return false;
 	}

+ 39 - 20
hxsl/Flatten.hx

@@ -26,6 +26,7 @@ class Flatten {
 	var params : Array<TVar>;
 	var outVars : Array<TVar>;
 	var varMap : Map<TVar,Alloc>;
+	var textureFormats : Array<{ dim : TexDimension, arr : Bool, rw : Int }>;
 	public var allocData : Map< TVar, Array<Alloc> >;
 
 	public function new() {
@@ -35,6 +36,7 @@ class Flatten {
 		globals = [];
 		params = [];
 		outVars = [];
+		textureFormats = [];
 		varMap = new Map();
 		allocData = new Map();
 		for( v in s.vars )
@@ -48,9 +50,12 @@ class Flatten {
 		pack(prefix + "Globals", Global, globals, VFloat);
 		pack(prefix + "Params", Param, params, VFloat);
 		var allVars = globals.concat(params);
-		var textures = packTextures(prefix + "Textures", allVars, TSampler2D)
-			.concat(packTextures(prefix+"TexturesCube", allVars, TSamplerCube))
-			.concat(packTextures(prefix+"TexturesArray", allVars, TSampler2DArray));
+		for( t in textureFormats ) {
+			var name = t.dim == T2D ? "" : t.dim.getName().substr(1);
+			if( t.rw > 0 ) name = "RW"+name+t.rw;
+			if( t.arr ) name += "Array";
+			packTextures(prefix + "Textures" + name, allVars, t.rw == 0 ? TSampler(t.dim, t.arr) : TRWTexture(t.dim, t.arr, t.rw));
+		}
 		packBuffers("buffers", allVars, Uniform);
 		packBuffers("rwbuffers", allVars, RW);
 		var funs = [for( f in s.funs ) mapFun(f, mapExpr)];
@@ -79,13 +84,13 @@ class Flatten {
 				e
 			else
 				access(a, v.type, e.p, AIndex(a));
-		case TArray( { e : TVar(v), p : vp }, eindex) if( !eindex.e.match(TConst(CInt(_))) ):
+		case TArray( { e : TVar(v), p : vp }, eindex) if( !eindex.e.match(TConst(CInt(_))) && !v.type.match(TRWTexture(_)) ):
 			var a = varMap.get(v);
 			if( a == null )
 				e
 			else {
 				switch( v.type ) {
-				case TArray(t, _) if( t.isSampler() ):
+				case TArray(t, _) if( t.isTexture() ):
 					eindex = toInt(mapExpr(eindex));
 					access(a, t, vp, AOffset(a,1,eindex));
 				case TArray(t, _):
@@ -147,7 +152,7 @@ class Flatten {
 			var earr = [for( i in 0...len ) { var a = new Alloc(a.g, a.t, a.pos + stride * i, stride); access(a, t, pos, AIndex(a)); }];
 			return { e : TArrayDecl(earr), t : t, p : pos };
 		default:
-			if( t.isSampler() ) {
+			if( t.isTexture() ) {
 				var e = read(0, pos);
 				e.t = t;
 				return e;
@@ -222,9 +227,9 @@ class Flatten {
 		var samplers = [];
 		for( v in vars ) {
 			var count = 1;
-			if( v.type != t ) {
+			if( !v.type.equals(t) ) {
 				switch( v.type ) {
-				case TChannel(_) if( t == TSampler2D ):
+				case TChannel(_) if( t.match(TSampler(T2D,false)) ):
 				case TArray(t2,SConst(n)) if( t2 == t ):
 					count = n;
 				default:
@@ -291,10 +296,10 @@ class Flatten {
 			kind : kind,
 		};
 		for( v in vars ) {
-			if( v.type.isSampler() || v.type.match(TBuffer(_)) )
+			if( v.type.isTexture() || v.type.match(TBuffer(_)) )
 				continue;
 			switch( v.type ) {
-			case TArray(t,_) if( t.isSampler() ): continue;
+			case TArray(t,_) if( t.isTexture() ): continue;
 			default:
 			}
 			var size = varSize(v.type, t);
@@ -358,18 +363,32 @@ class Flatten {
 		case TStruct(vl):
 			for( v in vl )
 				gatherVar(v);
+			return;
+		case TSampler(dim, arr), TRWTexture(dim, arr, _):
+			var rw = switch( v.type ) {
+			case TRWTexture(_,_,chans): chans;
+			default: 0;
+			}
+			var found = false;
+			for( f in textureFormats )
+				if( f.dim == dim && f.arr == arr && f.rw == rw ) {
+					found = true;
+					break;
+				}
+			if( !found )
+				textureFormats.push({ dim : dim, arr : arr, rw : rw });
 		default:
-			switch( v.kind ) {
-			case Global:
-				if( v.hasQualifier(PerObject) )
-					params.push(v);
-				else
-					globals.push(v);
-			case Param:
+		}
+		switch( v.kind ) {
+		case Global:
+			if( v.hasQualifier(PerObject) )
 				params.push(v);
-			default:
-				outVars.push(v);
-			}
+			else
+				globals.push(v);
+		case Param:
+			params.push(v);
+		default:
+			outVars.push(v);
 		}
 	}
 

+ 61 - 19
hxsl/GlslOut.hx

@@ -130,6 +130,12 @@ class GlslOut {
 			decls.push(s);
 	}
 
+	function getSamplerType(dim:TexDimension,arr) {
+		var name = "sampler" + dim.getName().substr(1);
+		if( arr ) name += "Array";
+		return name;
+	}
+
 	function addType( t : Type ) {
 		switch( t ) {
 		case TVoid:
@@ -162,14 +168,13 @@ class GlslOut {
 		case TMat3x4:
 			decl(MAT34);
 			add("_mat3x4");
-		case TSampler2D:
-			add("sampler2D");
-		case TSampler2DArray:
-			add("sampler2DArray");
-			if( isES )
-				decl("precision lowp sampler2DArray;");
-		case TSamplerCube:
-			add("samplerCube");
+		case TSampler(dim,arr):
+			var name = getSamplerType(dim,arr);
+			add(name);
+			if( isES && arr )
+				decl("precision lowp "+name+";");
+		case TRWTexture(dim, arr, chans):
+			add("image"+dim.getName().substr(1)+(arr?"Array":""));
 		case TStruct(vl):
 			add("struct { ");
 			for( v in vl ) {
@@ -294,18 +299,18 @@ class GlslOut {
 			decl("vec3 unpackNormal( vec4 v ) { return normalize((v.xyz - vec3(0.5)) * vec3(2.)); }");
 		case Texture:
 			switch( args[0].t ) {
-			case TSampler2D, TSampler2DArray, TChannel(_) if( isES2 ):
+			case TSampler(T2D,_), TChannel(_) if( isES2 ):
 				return "texture2D";
-			case TSamplerCube if( isES2 ):
+			case TSampler(TCube,_) if( isES2 ):
 				return "textureCube";
 			default:
 			}
 		case TextureLod:
 			switch( args[0].t ) {
-			case TSampler2D, TSampler2DArray, TChannel(_) if( isES2 ):
+			case TSampler(T2D,_), TChannel(_) if( isES2 ):
 				decl("#extension GL_EXT_shader_texture_lod : enable");
 				return "texture2DLodEXT";
-			case TSamplerCube if( isES2 ):
+			case TSampler(TCube,_) if( isES2 ):
 				decl("#extension GL_EXT_shader_texture_lod : enable");
 				return "textureCubeLodEXT";
 			default:
@@ -318,12 +323,14 @@ class GlslOut {
 				return "texelFetch";
 		case TextureSize:
 			switch( args[0].t ) {
-			case TSampler2D, TChannel(_):
+			case TSampler(_,false), TChannel(_):
 				decl("vec2 _textureSize(sampler2D sampler, int lod) { return vec2(textureSize(sampler, lod)); }");
-			case TSamplerCube:
-				decl("vec2 _textureSize(samplerCube sampler, int lod) { return vec2(textureSize(sampler, lod)); }");
-			case TSampler2DArray:
-				decl("vec3 _textureSize(sampler2DArray sampler, int lod) { return vec3(textureSize(sampler, lod)); }");
+			case TSampler(dim,true):
+				var t = getSamplerType(dim, true);
+				decl("vec3 _textureSize("+t+" sampler, int lod) { return vec3(textureSize(sampler, lod)); }");
+			case TRWTexture(_,arr,_):
+				var size = arr ? "vec3" : "vec2";
+				return size+"(imageSize";
 			default:
 			}
 			return "_textureSize";
@@ -435,6 +442,19 @@ class GlslOut {
 				add(",");
 				addValue(e2, tabs);
 				add("))");
+			case [OpAssign, _, _] if( e1.e.match(TArray({ t : TRWTexture(_) },_)) ):
+				add("imageStore(");
+				switch( e1.e ) {
+				case TArray(tex,uv):
+					addValue(tex, tabs);
+					add(",");
+					addValue(uv, tabs);
+					add(",");
+					addValue(e2, tabs);
+					add(")");
+				default:
+					throw "assert";
+				}
 			default:
 				addValue(e1, tabs);
 				add(" ");
@@ -461,6 +481,21 @@ class GlslOut {
 			} else {
 				add("/*var*/");
 			}
+		case TCall( { e : TGlobal(ComputeVar) }, [{ e : TConst(CInt(i)) }]):
+			switch( ComputeVar.createByIndex(i,[]) ) {
+			case LocalInvocation:
+				add("ivec3(gl_LocalInvocationID)");
+			case LocalInvocationIndex:
+				add("int(gl_LocalInvocationIndex)");
+			case GlobalInvocation:
+				add("ivec3(gl_GlobalInvocationID)");
+			case NumWorkGroups:
+				add("ivec3(gl_NumWorkGroups)");
+			case WorkGroup:
+				add("ivec3(gl_WorkGroup)");
+			case WorkGroupSize:
+				add("ivec3(gl_WorkGroupSize)");
+			}
 		case TCall( { e : TGlobal(SetLayout) }, _):
 			// nothing
 		case TCall( { e : TGlobal(Saturate) }, [e]):
@@ -485,11 +520,13 @@ class GlslOut {
 			add(getFunName(g,args,e.t));
 			add("(");
 			addValue(args[0], tabs);
-			if ( args.length != 1 ) {
+			if( args.length != 1 ) {
 				// with LOD argument
 				add(", ");
 				addValue(args[1], tabs);
 				add(")");
+			} else if( args[0].t.match(TRWTexture(_)) ) {
+				add("))");
 			} else {
 				add(", 0)");
 			}
@@ -679,6 +716,9 @@ class GlslOut {
 				case RW:
 					add("buffer ");
 				}
+			case TArray(TRWTexture(_, _, chans), _):
+				var format = "rgba".substr(0, chans);
+				add('layout(${format}32f) uniform ');
 			default:
 				add("uniform ");
 			}
@@ -754,7 +794,9 @@ class GlslOut {
 		isVertex = f.kind == Vertex;
 		isCompute = f.kind == Main;
 
-		if (isVertex || isCompute)
+		if( isCompute ) {
+			// no prec
+		} else if( isVertex )
 			decl("precision highp float;");
 		else
 			decl("precision mediump float;");

+ 35 - 3
hxsl/MacroParser.hx

@@ -83,6 +83,21 @@ class MacroParser {
 		}
 	}
 
+	function getTexDim( n : String, f : Ast.TexDimension -> Bool -> Ast.Type ) {
+		var arr = false;
+		if( StringTools.endsWith(n,"Array") ) {
+			arr = true;
+			n = n.substr(0,-5);
+		}
+		return switch( n ) {
+		case "1D": f(T1D,arr);
+		case "2D": f(T2D,arr);
+		case "3D": f(T3D,arr);
+		case "Cube": f(TCube,arr);
+		default: null;
+		}
+	}
+
 	public function parseType( t : ComplexType, pos : Position ) : Ast.Type {
 		switch( t ) {
 		case TPath( { pack : [], name : name, sub : null, params : [] } ):
@@ -104,9 +119,6 @@ class MacroParser {
 			case "Mat3x4": return TMat3x4;
 			case "Mat2": return TMat2;
 			case "String": return TString;
-			case "Sampler2D": return TSampler2D;
-			case "Sampler2DArray": return TSampler2DArray;
-			case "SamplerCube": return TSamplerCube;
 			case "Bytes2": return TBytes(2);
 			case "Bytes3": return TBytes(3);
 			case "Bytes4": return TBytes(4);
@@ -114,7 +126,27 @@ class MacroParser {
 			case "Channel2": return TChannel(2);
 			case "Channel3": return TChannel(3);
 			case "Channel4": return TChannel(4);
+			case _ if( StringTools.startsWith(name,"Sampler") ):
+				var t = getTexDim(name.substr(7), (d,arr) -> TSampler(d,arr));
+				if( t != null ) return t;
+			}
+		case TPath( { pack : [], name : name, sub : null, params : pl } ) if( StringTools.startsWith(name,"RWTexture") ):
+			var chans = switch( pl[0] ) {
+			case TPType(TPath({ pack : [], name : n, sub : null, params : [] })):
+				switch( n ) {
+				case "Float": 1;
+				case "Vec2": 2;
+				case "Vec3": 3;
+				case "Vec4": 4;
+				default: 0;
+				}
+			case null, _: 0;
 			}
+			if( chans == 0 )
+				error("Unsupported RWTexture parameter, should be Float|Vec2|Vec3|Vec4", pos);
+			var t = getTexDim(name.substr(9), (dim,arr) -> TRWTexture(dim,arr,chans));
+			if( t != null )
+				return t;
 		case TPath( { pack : [], name : name = ("Array"|"Buffer"|"RWBuffer"), sub : null, params : [t, size] } ):
 			var t = switch( t ) {
 			case TPType(t): parseType(t, pos);

+ 6 - 8
hxsl/Macros.hx

@@ -23,15 +23,13 @@ class Macros {
 			for( v in vl ) {
 				fields.push({ pos : pos, name : v.name, kind : FVar(makeType(v.type)) });
 				if( v.type.match(TChannel(_)) )
-				fields.push({ pos : pos, name : v.name+"Channel", kind : FVar(macro : hxsl.Channel) });
+					fields.push({ pos : pos, name : v.name+"Channel", kind : FVar(macro : hxsl.Channel) });
 			}
 			TAnonymous(fields);
-		case TSampler2D:
-			macro : hxsl.Types.Sampler2D;
-		case TSampler2DArray:
-			macro : hxsl.Types.Sampler2DArray;
-		case TSamplerCube:
-			macro : hxsl.Types.SamplerCube;
+		case TSampler(_, false), TRWTexture(_,false,_):
+			macro : hxsl.Types.Texture;
+		case TSampler(_, true), TRWTexture(_,true,_):
+			macro : hxsl.Types.TextureArray;
 		case TMat2, TMat3, TMat3x4, TMat4:
 			macro : hxsl.Types.Matrix;
 		case TString:
@@ -46,7 +44,7 @@ class Macros {
 			var t = makeType(t);
 			macro : Array<$t>;
 		case TChannel(_):
-			macro : hxsl.Types.ChannelTextureType;
+			macro : hxsl.Types.TextureChannel;
 		case TFun(_):
 			throw "assert";
 		case TBuffer(_):

+ 29 - 8
hxsl/Serializer.hx

@@ -11,6 +11,7 @@ class Serializer {
 	var types : Array<Type>;
 	var uid = 1;
 	var tid = 1;
+	var version : Int;
 
 	public function new() {
 	}
@@ -76,7 +77,7 @@ class Serializer {
 
 	function writeType( t : Type ) {
 		out.addByte(t.getIndex());
-		switch (t) {
+		switch( t ) {
 		case TVec(size, t):
 			out.addByte(size | (t.getIndex() << 3));
 		case TBytes(size):
@@ -101,11 +102,18 @@ class Serializer {
 			}
 		case TChannel(size):
 			out.addByte(size);
-		case TVoid, TInt, TBool, TFloat, TString, TMat2, TMat3, TMat4, TMat3x4, TSampler2D, TSampler2DArray, TSamplerCube:
+		case TSampler(dim, arr):
+			out.addByte((dim.getIndex() << 1) | (arr ? 1 : 0));
+		case TRWTexture(dim, arr, chans):
+			out.addByte((dim.getIndex() << 3) | (arr ? 1 : 0) | ((chans - 1) << 1));
+		case TVoid, TInt, TBool, TFloat, TString, TMat2, TMat3, TMat4, TMat3x4:
+		case __TUnused:
+			throw "assert";
 		}
 	}
 
 	static var TVECS = new Map();
+	static var TDIMS = hxsl.Ast.TexDimension.createAll();
 
 	function readType() : Type {
 		return switch( input.readByte() ) {
@@ -126,9 +134,20 @@ class Serializer {
 		case 7: TMat4;
 		case 8: TMat3x4;
 		case 9: TBytes(input.readInt32());
-		case 10: TSampler2D;
-		case 11: TSampler2DArray;
-		case 12: TSamplerCube;
+		case 10 if( version == 0 ): TSampler(T2D, false);
+		case 11 if( version == 0 ): TSampler(T2D, true);
+		case 12 if( version == 0 ): TSampler(TCube, false);
+		case 18 if( version == 0 ): TMat2;
+		case 10:
+			var b = input.readByte();
+			var dim = TDIMS[b>>1];
+			TSampler(dim, b & 1 != 0);
+		case 11:
+			var b = input.readByte();
+			var dim = TDIMS[b>>3];
+			TRWTexture(dim, b & 1 != 0, ((b>>1)&3) + 1);
+		case 12:
+			TMat2;
 		case 13:
 			var id = readVarInt();
 			var t = types[id];
@@ -154,7 +173,6 @@ class Serializer {
 			TBuffer(t, v == null ? SConst(readVarInt()) : SVar(v), kind);
 		case 17:
 			TChannel(input.readByte());
-		case 18: TMat2;
 		default:
 			throw "assert";
 		}
@@ -437,11 +455,14 @@ class Serializer {
 		};
 	}
 
-	static var SIGN = 0x8B741D; // will be encoded to HXSL
+	static var SIGN = 0x8C741D; // will be encoded to HXSL
 
 	public function unserialize( data : String ) : ShaderData {
 		input = new haxe.io.BytesInput(haxe.crypto.Base64.decode(data,false));
-		if( input.readByte() != (SIGN & 0xFF) || input.readByte() != (SIGN >> 8) & 0xFF || input.readByte() != (SIGN >> 16) & 0xFF )
+		if( input.readByte() != (SIGN & 0xFF) || input.readByte() != (SIGN >> 8) & 0xFF )
+			throw "Invalid HXSL data";
+		version = input.readByte() - 0x8B;
+		if( version < 0 || version > 1 )
 			throw "Invalid HXSL data";
 		varMap = new Map();
 		types = [];

+ 3 - 5
hxsl/Types.hx

@@ -6,14 +6,12 @@ typedef IVec = Array<Int>;
 typedef BVec = Array<Bool>;
 typedef Matrix = h3d.Matrix;
 typedef Texture = h3d.mat.Texture;
-typedef Sampler2D = h3d.mat.Texture;
-typedef Sampler2DArray = h3d.mat.TextureArray;
-typedef SamplerCube = h3d.mat.Texture;
-typedef ChannelTextureType = h3d.mat.Texture;
+typedef TextureArray = h3d.mat.TextureArray;
+typedef TextureChannel = h3d.mat.Texture;
 typedef Buffer = h3d.Buffer;
 
 class ChannelTools {
-	public static inline function isPackedFormat( c : ChannelTextureType ) {
+	public static inline function isPackedFormat( c : TextureChannel ) {
 		return c.format == h3d.mat.Texture.nativeFormat;
 	}
 }