2
0
Эх сурвалжийг харах

refactor: add format for buffer and primitive, allow runtime remap to shader (not only based on stride)

Nicolas Cannasse 2 жил өмнө
parent
commit
167e365bfe

+ 3 - 3
h2d/Graphics.hx

@@ -87,13 +87,13 @@ private class GraphicsContent extends h3d.prim.Primitive {
 	override function alloc( engine : h3d.Engine ) {
 		if (index.length <= 0) return ;
 		var alloc = Allocator.get();
-		buffer = alloc.ofFloats(tmp, 8, RawFormat);
+		buffer = alloc.ofFloats(tmp, hxd.BufferFormat.H2D);
 		#if track_alloc
 		@:privateAccess buffer.allocPos = allocPos;
 		#end
 		indexes = alloc.ofIndexes(index);
 		for( b in buffers ) {
-			if( b.vbuf == null || b.vbuf.isDisposed() ) b.vbuf = alloc.ofFloats(b.buf, 8, RawFormat);
+			if( b.vbuf == null || b.vbuf.isDisposed() ) b.vbuf = alloc.ofFloats(b.buf, hxd.BufferFormat.H2D);
 			if( b.ibuf == null || b.ibuf.isDisposed() ) b.ibuf = alloc.ofIndexes(b.idx);
 		}
 		bufferDirty = false;
@@ -114,7 +114,7 @@ private class GraphicsContent extends h3d.prim.Primitive {
 			var allocator = Allocator.get();
 			if ( bufferDirty ) {
 				allocator.disposeBuffer(buffer);
-				buffer = allocator.ofFloats(tmp, 8, RawFormat);
+				buffer = allocator.ofFloats(tmp, hxd.BufferFormat.H2D);
 				bufferDirty = false;
 			}
 			if ( indexDirty ) {

+ 2 - 2
h2d/RenderContext.hx

@@ -580,7 +580,7 @@ class RenderContext extends h3d.impl.RenderContext {
 		if( bufPos == 0 ) return;
 		beforeDraw();
 		var nverts = Std.int(bufPos / stride);
-		var tmp = new h3d.Buffer(nverts, stride, [Dynamic,RawFormat]);
+		var tmp = new h3d.Buffer(nverts, hxd.BufferFormat.XY_UV_RGBA, [Dynamic]);
 		tmp.uploadVector(buffer, 0, nverts);
 		engine.renderQuadBuffer(tmp);
 		tmp.dispose();
@@ -750,7 +750,7 @@ class RenderContext extends h3d.impl.RenderContext {
 		baseShader.uvPos.set(tile.u, tile.v, tile.u2 - tile.u, tile.v2 - tile.v);
 		beforeDraw();
 		if( fixedBuffer == null || fixedBuffer.isDisposed() ) {
-			fixedBuffer = new h3d.Buffer(4, 8, [RawFormat]);
+			fixedBuffer = new h3d.Buffer(4, hxd.BufferFormat.H2D);
 			var k = new hxd.FloatBuffer();
 			for( v in [0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] )
 				k.push(v);

+ 1 - 1
h2d/SpriteBatch.hx

@@ -438,7 +438,7 @@ class SpriteBatch extends Drawable {
 		}
 		empty = bufferVertices == 0;
 		if( bufferVertices > 0 )
-			buffer = h3d.Buffer.ofSubFloats(tmpBuf, 8, bufferVertices, [Dynamic, RawFormat]);
+			buffer = h3d.Buffer.ofSubFloats(tmpBuf, bufferVertices, hxd.BufferFormat.H2D, [Dynamic]);
 	}
 
 	override function draw( ctx : RenderContext ) {

+ 2 - 2
h2d/TileGroup.hx

@@ -511,8 +511,8 @@ class TileLayerContent extends h3d.prim.Primitive {
 		if( tmp == null ) clear();
 		if( tmp.length > 0 ) {
 			buffer = tmp.length < useAllocatorLimit
-				? hxd.impl.Allocator.get().ofFloats(tmp, 8, RawFormat)
-				: h3d.Buffer.ofFloats(tmp, 8, [RawFormat]);
+				? hxd.impl.Allocator.get().ofFloats(tmp, hxd.BufferFormat.H2D)
+				: h3d.Buffer.ofFloats(tmp, hxd.BufferFormat.H2D);
 		}
 	}
 

+ 12 - 12
h3d/Buffer.hx

@@ -5,10 +5,6 @@ enum BufferFlag {
 		Indicate that the buffer content will be often modified.
 	**/
 	Dynamic;
-	/**
-		Directly map the buffer content to the shader inputs, without assuming [pos:vec3,normal:vec3,uv:vec2] default prefix.
-	**/
-	RawFormat;
 	/**
 		Used internaly
 	**/
@@ -31,13 +27,13 @@ class Buffer {
 	var mem : h3d.impl.MemoryManager;
 	var vbuf : h3d.impl.Driver.GPUBuffer;
 	public var vertices(default,null) : Int;
-	public var stride(default,null) : Int;
+	public var format(default,null) : hxd.BufferFormat;
 	public var flags(default, null) : haxe.EnumFlags<BufferFlag>;
 
-	public function new(vertices, stride, ?flags : Array<BufferFlag> ) {
+	public function new(vertices, format : hxd.BufferFormat, ?flags : Array<BufferFlag> ) {
 		id = GUID++;
 		this.vertices = vertices;
-		this.stride = stride;
+		this.format = format;
 		this.flags = new haxe.EnumFlags();
 		#if track_alloc
 		this.allocPos = new hxd.impl.AllocPos();
@@ -49,6 +45,10 @@ class Buffer {
 			@:privateAccess h3d.Engine.getCurrent().mem.allocBuffer(this);
 	}
 
+	public inline function getMemSize() {
+		return vertices * (format.stride << 2);
+	}
+
 	public inline function isDisposed() {
 		return vbuf == null;
 	}
@@ -78,15 +78,15 @@ class Buffer {
 		mem.driver.readBufferBytes(vbuf, startVertice, vertices, bytes, bytesPosition);
 	}
 
-	public static function ofFloats( v : hxd.FloatBuffer, stride : Int, ?flags ) {
-		var nvert = Std.int(v.length / stride);
-		var b = new Buffer(nvert, stride, flags);
+	public static function ofFloats( v : hxd.FloatBuffer, format : hxd.BufferFormat, ?flags ) {
+		var nvert = Std.int(v.length / format.stride);
+		var b = new Buffer(nvert, format, flags);
 		b.uploadVector(v, 0, nvert);
 		return b;
 	}
 
-	public static function ofSubFloats( v : hxd.FloatBuffer, stride : Int, vertices : Int, ?flags ) {
-		var b = new Buffer(vertices, stride, flags);
+	public static function ofSubFloats( v : hxd.FloatBuffer, vertices : Int, format : hxd.BufferFormat, ?flags ) {
+		var b = new Buffer(vertices, format, flags);
 		b.uploadVector(v, 0, vertices);
 		return b;
 	}

+ 17 - 16
h3d/impl/DirectXDriver.hx

@@ -41,6 +41,7 @@ private class CompiledShader {
 	public var layout : Layout;
 	public var inputs : InputNames;
 	public var offsets : Array<Int>;
+	public var format : hxd.BufferFormat;
 	public function new() {
 	}
 }
@@ -316,11 +317,11 @@ class DirectXDriver extends h3d.impl.Driver {
 	}
 
 	override function allocBuffer(b:Buffer):GPUBuffer {
-		var size = b.vertices * b.stride * 4;
+		var size = b.getMemSize();
 		var uniform = b.flags.has(UniformBuffer);
 		var res = uniform ? dx.Driver.createBuffer(size, Dynamic, ConstantBuffer, CpuWrite, None, 0, null) : dx.Driver.createBuffer(size, Default, VertexBuffer, None, None, 0, null);
 		if( res == null ) return null;
-		return { res : res, count : b.vertices, stride : b.stride, uniform : uniform };
+		return { res : res, count : b.vertices, stride : b.format.stride, uniform : uniform };
 	}
 
 	override function allocIndexes( count : Int, is32 : Bool ) : IndexBuffer {
@@ -1099,7 +1100,7 @@ class DirectXDriver extends h3d.impl.Driver {
 			s.fragment = fragment.s;
 			s.offsets = [];
 
-			var layout = [], offset = 0;
+			var layout = [], offset = 0, format : Array<hxd.BufferFormat.BufferInput> = [];
 			for( v in shader.vertex.data.vars )
 				if( v.kind == Input ) {
 					var e = new LayoutElement();
@@ -1130,20 +1131,15 @@ class DirectXDriver extends h3d.impl.Driver {
 					layout.push(e);
 					s.offsets.push(offset);
 					inputs.push(v.name);
-
-					var size = switch( v.type ) {
-					case TVec(n, _): n;
-					case TBytes(n): n;
-					case TFloat: 1;
-					default: throw "assert " + v.type;
-					}
-
-					offset += size;
+					var t = hxd.BufferFormat.InputFormat.fromHXSL(v.type);
+					format.push({ name : v.name, type : t });
+					offset += t.getSize();
 				}
 
 			var n = new hl.NativeArray(layout.length);
 			for( i in 0...layout.length )
 				n[i] = layout[i];
+			s.format = hxd.BufferFormat.make(format);
 			s.inputs = InputNames.get(inputs);
 			s.layout = Driver.createInputLayout(n, vertex.bytes, vertex.bytes.length);
 			if( s.layout == null )
@@ -1171,11 +1167,16 @@ class DirectXDriver extends h3d.impl.Driver {
 		if( hasDeviceError ) return;
 		var vbuf = @:privateAccess buffer.vbuf;
 		var start = -1, max = -1, position = 0;
+		var bufOffsets;
+		if( buffer.format == currentShader.format || currentShader.format.isSubSet(buffer.format) )
+			bufOffsets = currentShader.offsets;
+		else
+			bufOffsets = buffer.format.getMatchingOffsets(currentShader.format);
 		for( i in 0...currentShader.inputs.names.length ) {
-			if( currentVBuffers[i] != vbuf.res || offsets[i] != currentShader.offsets[i] << 2 ) {
+			if( currentVBuffers[i] != vbuf.res || offsets[i] != bufOffsets[i] << 2 ) {
 				currentVBuffers[i] = vbuf.res;
-				strides[i] = buffer.stride << 2;
-				offsets[i] = currentShader.offsets[i] << 2;
+				strides[i] = buffer.format.stride << 2;
+				offsets[i] = bufOffsets[i] << 2;
 				if( start < 0 ) start = i;
 				max = i;
 			}
@@ -1193,7 +1194,7 @@ class DirectXDriver extends h3d.impl.Driver {
 			if( currentVBuffers[index] != vbuf.res || offsets[index] != bl.offset << 2 ) {
 				currentVBuffers[index] = vbuf.res;
 				offsets[index] = bl.offset << 2;
-				strides[index] = bl.buffer.stride << 2;
+				strides[index] = bl.buffer.format.stride << 2;
 				if( start < 0 ) start = index;
 				max = index;
 			}

+ 31 - 47
h3d/impl/GlDriver.hx

@@ -68,7 +68,7 @@ private class CompiledProgram {
 	public var p : Program;
 	public var vertex : CompiledShader;
 	public var fragment : CompiledShader;
-	public var stride : Int;
+	public var format : hxd.BufferFormat;
 	public var inputs : InputNames;
 	public var attribs : Array<CompiledAttribute>;
 	public var hasAttribIndex : Array<Bool>;
@@ -413,27 +413,31 @@ class GlDriver extends Driver {
 			var attribNames = [];
 			p.attribs = [];
 			p.hasAttribIndex = [];
-			p.stride = 0;
+			var format : Array<hxd.BufferFormat.BufferInput> = [];
+			var stride = 0;
 			for( v in shader.vertex.data.vars )
 				switch( v.kind ) {
 				case Input:
-					var t = GL.FLOAT;
-					var size = switch( v.type ) {
-					case TVec(n, _): n;
-					case TBytes(n): t = GL.BYTE; n;
-					case TFloat: 1;
-					default: throw "assert " + v.type;
-					}
+					var t = hxd.BufferFormat.InputFormat.fromHXSL(v.type);
 					var index = gl.getAttribLocation(p.p, glout.varNames.exists(v.id) ? glout.varNames.get(v.id) : v.name);
 					if( index < 0 ) {
-						p.stride += size;
+						stride += t.getSize();
 						continue;
 					}
 					var a = new CompiledAttribute();
-					a.type = t;
-					a.size = size;
+					a.type = GL.FLOAT;
 					a.index = index;
-					a.offset = p.stride;
+					a.size = t.getSize();
+					a.offset = stride;
+					stride += a.size;
+
+					switch( v.type ) {
+					case TBytes(n):
+						a.type = GL.BYTE;
+						a.size = n;
+					default:
+					}
+
 					a.divisor = 0;
 					if( v.qualifiers != null ) {
 						for( q in v.qualifiers )
@@ -445,9 +449,10 @@ class GlDriver extends Driver {
 					p.attribs.push(a);
 					p.hasAttribIndex[a.index] = true;
 					attribNames.push(v.name);
-					p.stride += size;
+					format.push({ name : v.name, type : t });
 				default:
 				}
+			p.format = hxd.BufferFormat.make(format);
 			p.inputs = InputNames.get(attribNames);
 			programs.set(shader.id, p);
 		}
@@ -1008,13 +1013,13 @@ class GlDriver extends Driver {
 		discardError();
 		var vb = gl.createBuffer();
 		gl.bindBuffer(GL.ARRAY_BUFFER, vb);
-		if( b.vertices * b.stride == 0 ) throw "assert";
+		if( b.vertices * b.format.stride == 0 ) throw "assert";
 		#if js
-		gl.bufferData(GL.ARRAY_BUFFER, b.vertices * b.stride * 4, b.flags.has(Dynamic) ? GL.DYNAMIC_DRAW : GL.STATIC_DRAW);
+		gl.bufferData(GL.ARRAY_BUFFER, b.getMemSize(), b.flags.has(Dynamic) ? GL.DYNAMIC_DRAW : GL.STATIC_DRAW);
 		#elseif hl
-		gl.bufferDataSize(GL.ARRAY_BUFFER, b.vertices * b.stride * 4, b.flags.has(Dynamic) ? GL.DYNAMIC_DRAW : GL.STATIC_DRAW);
+		gl.bufferDataSize(GL.ARRAY_BUFFER, b.getMemSize(), b.flags.has(Dynamic) ? GL.DYNAMIC_DRAW : GL.STATIC_DRAW);
 		#else
-		var tmp = new Uint8Array(b.vertices * b.stride * 4);
+		var tmp = new Uint8Array(b.getMemSize());
 		gl.bufferData(GL.ARRAY_BUFFER, tmp, b.flags.has(Dynamic) ? GL.DYNAMIC_DRAW : GL.STATIC_DRAW);
 		#end
 		var outOfMem = outOfMemoryCheck && gl.getError() == GL.OUT_OF_MEMORY;
@@ -1023,7 +1028,7 @@ class GlDriver extends Driver {
 			gl.deleteBuffer(vb);
 			return null;
 		}
-		return { b : vb, stride : b.stride #if multidriver, driver : this #end };
+		return { b : vb, stride : b.format.stride #if multidriver, driver : this #end };
 	}
 
 	override function allocIndexes( count : Int, is32 : Bool ) : IndexBuffer {
@@ -1268,7 +1273,6 @@ class GlDriver extends Driver {
 	}
 
 	override function selectBuffer( b : h3d.Buffer ) {
-
 		if( b == curBuffer )
 			return;
 
@@ -1277,41 +1281,21 @@ class GlDriver extends Driver {
 		curBuffer = b;
 
 		var m = @:privateAccess b.vbuf;
-		if( m.stride < curShader.stride )
-			throw "Buffer stride (" + m.stride + ") and shader stride (" + curShader.stride + ") mismatch";
-
 		#if multidriver
 		if( m.driver != this )
 			throw "Invalid buffer context";
 		#end
 		gl.bindBuffer(GL.ARRAY_BUFFER, m.b);
-
-		if( b.flags.has(RawFormat) ) {
+		var strideBytes = m.stride * 4;
+		if( b.format == curShader.format || curShader.format.isSubSet(b.format) ) {
 			for( a in curShader.attribs ) {
-				var pos = a.offset;
-				gl.vertexAttribPointer(a.index, a.size, a.type, false, m.stride * 4, pos * 4);
+				gl.vertexAttribPointer(a.index, a.size, a.type, false, strideBytes, a.offset * 4);
 				updateDivisor(a);
 			}
 		} else {
-			var offset = 8;
-			for( i in 0...curShader.attribs.length ) {
-				var a = curShader.attribs[i];
-				var pos;
-				switch( curShader.inputs.names[i] ) {
-				case "position":
-					pos = 0;
-				case "normal":
-					if( m.stride < 6 ) throw "Buffer is missing NORMAL data, set it to RAW format ?" #if track_alloc + @:privateAccess v.allocPos #end;
-					pos = 3;
-				case "uv":
-					if( m.stride < 8 ) throw "Buffer is missing UV data, set it to RAW format ?" #if track_alloc + @:privateAccess v.allocPos #end;
-					pos = 6;
-				case s:
-					pos = offset;
-					offset += a.size;
-					if( offset > m.stride ) throw "Buffer is missing '"+s+"' data, set it to RAW format ?" #if track_alloc + @:privateAccess v.allocPos #end;
-				}
-				gl.vertexAttribPointer(a.index, a.size, a.type, false, m.stride * 4, pos * 4);
+			var offsets = b.format.getMatchingOffsets(curShader.format);
+			for( i => a in curShader.attribs ) {
+				gl.vertexAttribPointer(a.index, a.size, a.type, false, strideBytes, offsets[i] * 4);
 				updateDivisor(a);
 			}
 		}
@@ -1320,7 +1304,7 @@ class GlDriver extends Driver {
 	override function selectMultiBuffers( buffers : Buffer.BufferOffset ) {
 		for( a in curShader.attribs ) {
 			gl.bindBuffer(GL.ARRAY_BUFFER, @:privateAccess buffers.buffer.vbuf.b);
-			gl.vertexAttribPointer(a.index, a.size, a.type, false, buffers.buffer.stride * 4, buffers.offset * 4);
+			gl.vertexAttribPointer(a.index, a.size, a.type, false, buffers.buffer.format.stride * 4, buffers.offset * 4);
 			updateDivisor(a);
 			buffers = buffers.next;
 		}

+ 1 - 1
h3d/impl/LogDriver.hx

@@ -307,7 +307,7 @@ class LogDriver extends Driver {
 	}
 
 	override function allocBuffer( b : Buffer ) : GPUBuffer {
-		log('AllocBuffer count=${b.vertices} stride=${b.stride}');
+		log('AllocBuffer count=${b.vertices} format=${b.format}');
 		return d.allocBuffer(b);
 	}
 

+ 3 - 3
h3d/impl/MemoryManager.hx

@@ -81,7 +81,7 @@ class MemoryManager {
 	function allocBuffer( b : Buffer ) {
 		if( b.vbuf != null ) return;
 
-		var mem = b.vertices * b.stride * 4;
+		var mem = b.getMemSize();
 
 		if( mem == 0 ) return;
 
@@ -107,7 +107,7 @@ class MemoryManager {
 		driver.disposeBuffer(b.vbuf);
 		b.vbuf = null;
 		b.mem = null;
-		usedMemory -= b.vertices * b.stride * 4;
+		usedMemory -= b.getMemSize();
 		buffers.remove(b);
 	}
 
@@ -243,7 +243,7 @@ class MemoryManager {
 	public function stats() {
 		var total = 0.;
 		for( b in buffers )
-			total += b.stride * b.vertices * 4;
+			total += b.getMemSize();
 		return {
 			bufferCount : buffers.length,
 			bufferMemory : total,

+ 11 - 3
h3d/parts/GpuParticles.hx

@@ -452,6 +452,15 @@ class GpuParticles extends h3d.scene.MultiMaterial {
 
 	static inline var VERSION = 2;
 	static inline var STRIDE = 14;
+	static var PFORMAT = hxd.BufferFormat.make([
+		{ name : "position", type : DVec3 },
+		{ name : "normal", type : DVec3 },
+		{ name : "uv", type : DVec2 },
+		{ name : "time", type : DFloat },
+		{ name : "life", type : DFloat },
+		{ name : "init", type : DVec2 },
+		{ name : "delta", type : DVec2 },
+	]);
 
 	var groups : Array<GpuPartGroup>;
 	var primitiveBuffers : Array<hxd.FloatBuffer>;
@@ -685,11 +694,10 @@ class GpuParticles extends h3d.scene.MultiMaterial {
 			primitiveBuffers.resize(groups.length);
 
 		for( gid in 0...groups.length ) {
-			if( primitiveBuffers[gid] == null ||  primitiveBuffers[gid].length > STRIDE * partCount * 4 )
+			if( primitiveBuffers[gid] == null || primitiveBuffers[gid].length > STRIDE * partCount * 4 )
 				primitiveBuffers[gid] = new hxd.FloatBuffer();
 			primitiveBuffers[gid].grow(STRIDE * groups[gid].nparts * 4);
-			primitives[gid] = new h3d.prim.RawPrimitive( { vbuf : primitiveBuffers[gid], stride : STRIDE, quads : true, bounds:bounds }, true);
-			primitives[gid].buffer.flags.set(RawFormat);
+			primitives[gid] = new h3d.prim.RawPrimitive( { vbuf : primitiveBuffers[gid], format : PFORMAT, bounds:bounds }, true);
 		}
 
 		if( hasLoop ) {

+ 18 - 1
h3d/parts/Particles.hx

@@ -365,7 +365,21 @@ class Particles extends h3d.scene.Mesh {
 		var stride = 10;
 		if( hasColor ) stride += 4;
 		var count = Std.int(pos/stride);
-		var buffer = hxd.impl.Allocator.get().ofSubFloats(tmp, stride, count,RawFormat);
+		var format = PART_FMT;
+		if( format == null ) {
+			format = PART_FMT = hxd.BufferFormat.make([
+				{ name : "position", type : DVec3 },
+				{ name : "rpos", type : DVec2 },
+				{ name : "rotation", type : DFloat },
+				{ name : "size", type : DVec2 },
+				{ name : "uv", type : DVec2 },
+			]);
+		}
+		if( hasColor ) {
+			format = PART_FMT_COLOR;
+			if( format == null ) format = PART_FMT_COLOR = PART_FMT.append("color", DVec4);
+		}
+		var buffer = hxd.impl.Allocator.get().ofSubFloats(tmp, count, format);
 		if( pshader.is3D )
 			pshader.size.set(globalSize, globalSize);
 		else
@@ -375,4 +389,7 @@ class Particles extends h3d.scene.Mesh {
 		buffer.dispose();
 	}
 
+	static var PART_FMT : hxd.BufferFormat;
+	static var PART_FMT_COLOR : hxd.BufferFormat;
+
 }

+ 1 - 1
h3d/pass/Border.hx

@@ -42,7 +42,7 @@ class Border extends ScreenFx<BorderShader> {
 		add(width-size, height);
 		add(width, height);
 
-		this.primitive = new h3d.prim.RawPrimitive({ vbuf : bbuf, stride : 2, quads : true }, true);
+		this.primitive = new h3d.prim.RawPrimitive({ vbuf : bbuf, format : hxd.BufferFormat.make([{ name : "position", type : DVec2 }]) }, true);
 		shader.color.set(1,1,1,1);
 	}
 

+ 16 - 20
h3d/prim/BigPrimitive.hx

@@ -6,8 +6,7 @@ package h3d.prim;
 **/
 class BigPrimitive extends Primitive {
 
-	var isRaw : Bool;
-	var stride : Int;
+	public var format(default,null) : hxd.BufferFormat;
 	var buffers : Array<Buffer>;
 	var allIndexes : Array<Indexes>;
 	var tmpBuf : hxd.FloatBuffer;
@@ -29,14 +28,13 @@ class BigPrimitive extends Primitive {
 	static var PREV_BUFFER : hxd.FloatBuffer;
 	static var PREV_INDEX : hxd.IndexBuffer;
 
-	public function new(stride, isRaw=false, ?alloc) {
-		this.isRaw = isRaw;
+	public function new(format, ?alloc) {
+		this.format = format;
 		buffers = [];
 		allIndexes = [];
 		bounds = new h3d.col.Bounds();
 		this.allocator = alloc;
-		this.stride = stride;
-		if( stride < 3 ) throw "Minimum stride = 3";
+		if( format.stride < 3 ) throw "Minimum stride = 3";
 		#if track_alloc
 		allocPos = new hxd.impl.AllocPos();
 		#end
@@ -47,7 +45,7 @@ class BigPrimitive extends Primitive {
 		The count value is the number of vertexes you will add, it will automatically flush() if it doesn't fit into the current buffer.
 	**/
 	public function begin(vcount,icount) {
-		startIndex = Std.int(bufPos / stride);
+		startIndex = Std.int(bufPos / format.stride);
 		if( startIndex + vcount >= 65535 ) {
 			if( vcount >= 65535 ) throw "Too many vertices in begin()";
 			flush();
@@ -59,10 +57,10 @@ class BigPrimitive extends Primitive {
 			else
 				PREV_BUFFER = null;
 			if( isStatic )
-				tmpBuf.grow(65535 * stride);
+				tmpBuf.grow(65535 * format.stride);
 		}
 		if( !isStatic )
-			tmpBuf.grow(vcount * stride + bufPos);
+			tmpBuf.grow(vcount * format.stride + bufPos);
 		if( tmpIdx == null ) {
 			tmpIdx = PREV_INDEX;
 			if( tmpIdx == null )
@@ -112,7 +110,7 @@ class BigPrimitive extends Primitive {
 		var count = 0;
 		for( b in buffers )
 			count += b.vertices;
-		count += Std.int(bufPos / stride);
+		count += Std.int(bufPos / format.stride);
 		return count;
 	}
 
@@ -126,11 +124,9 @@ class BigPrimitive extends Primitive {
 				flushing = true;
 				var b : h3d.Buffer;
 				if(allocator != null)
-					b = allocator.ofSubFloats(tmpBuf, stride, Std.int(bufPos / stride), isRaw ? RawFormat : Dynamic);
-				else {
-					b = h3d.Buffer.ofSubFloats(tmpBuf, stride, Std.int(bufPos / stride));
-					if( isRaw ) b.flags.set(RawFormat);
-				}
+					b = allocator.ofSubFloats(tmpBuf, Std.int(bufPos / format.stride), format);
+				else
+					b = h3d.Buffer.ofSubFloats(tmpBuf, Std.int(bufPos / format.stride), format);
 
 				buffers.push(b);
 				var idx = if(allocator != null)
@@ -194,7 +190,7 @@ class BigPrimitive extends Primitive {
 		See addSub for complete documentation.
 	**/
 	public function add( buf : hxd.FloatBuffer, idx : hxd.IndexBuffer, dx : Float = 0. , dy : Float = 0., dz : Float = 0., rotation = 0., scale = 1., stride = -1 ) {
-		return addSub(buf, idx, 0, 0, Std.int(buf.length / (stride < 0 ? this.stride : stride)), Std.int(idx.length / 3), dx, dy, dz, rotation, scale, stride);
+		return addSub(buf, idx, 0, 0, Std.int(buf.length / (stride < 0 ? format.stride : stride)), Std.int(idx.length / 3), dx, dy, dz, rotation, scale, stride);
 	}
 	/**
 		Adds a buffer to the primitive, with custom position,scale,rotation.
@@ -206,8 +202,8 @@ class BigPrimitive extends Primitive {
 	**/
 	@:noDebug
 	public function addSub( buf : hxd.FloatBuffer, idx : hxd.IndexBuffer, startVert, startTri, nvert, triCount, dx : Float = 0. , dy : Float = 0., dz : Float = 0., rotation = 0., scale = 1., stride = -1, deltaU = 0., deltaV = 0., color = 1., mat : h3d.Matrix = null) {
-		if( stride < 0 ) stride = this.stride;
-		if( stride < this.stride ) throw "only stride >= " + this.stride+" allowed";
+		if( stride < 0 ) stride = format.stride;
+		if( stride < format.stride ) throw "only stride >= " + format.stride+" allowed";
 		begin(nvert, triCount*3);
 		var start = startIndex;
 		var cr = Math.cos(rotation);
@@ -246,7 +242,8 @@ class BigPrimitive extends Primitive {
 				bounds.addPos(vx, vy, vz);
 			}
 
-			if(this.stride >= 6) {
+			var stride = format.stride;
+			if(stride >= 6) {
 				var nx = buf[p++];
 				var ny = buf[p++];
 				var nz = buf[p++];
@@ -268,7 +265,6 @@ class BigPrimitive extends Primitive {
 				}
 			}
 
-			var stride = this.stride;
 			if( hasTangents ) {
 				var tx = buf[p++];
 				var ty = buf[p++];

+ 5 - 5
h3d/prim/DynamicPrimitive.hx

@@ -6,7 +6,7 @@ class DynamicPrimitive extends Primitive {
 	var ibuf : hxd.IndexBuffer;
 	var vsize : Int;
 	var isize : Int;
-	var stride : Int;
+	var format : hxd.BufferFormat;
 
 	/** Minimum number of elements in vertex buffer **/
 	public var minVSize = 0;
@@ -15,8 +15,8 @@ class DynamicPrimitive extends Primitive {
 
 	public var bounds = new h3d.col.Bounds();
 
-	public function new( stride : Int ) {
-		this.stride = stride;
+	public function new( format ) {
+		this.format = format;
 	}
 
 	override function getBounds() {
@@ -24,7 +24,7 @@ class DynamicPrimitive extends Primitive {
 	}
 
 	public function getBuffer( vertices : Int ) {
-		if( vbuf == null ) vbuf = hxd.impl.Allocator.get().allocFloats(vertices * stride) else vbuf.grow(vertices * stride);
+		if( vbuf == null ) vbuf = hxd.impl.Allocator.get().allocFloats(vertices * format.stride) else vbuf.grow(vertices * format.stride);
 		vsize = vertices;
 		return vbuf;
 	}
@@ -59,7 +59,7 @@ class DynamicPrimitive extends Primitive {
 		}
 
 		if( buffer == null )
-			buffer = alloc.allocBuffer(hxd.Math.imax(minVSize, vsize), stride, Dynamic);
+			buffer = alloc.allocBuffer(hxd.Math.imax(minVSize, vsize), format, Dynamic);
 		if( indexes == null )
 			indexes = alloc.allocIndexBuffer(hxd.Math.imax(minISize, isize));
 

+ 4 - 4
h3d/prim/HMDModel.hx

@@ -59,7 +59,7 @@ class HMDModel extends MeshPrimitive {
 
 	override function alloc(engine:h3d.Engine) {
 		dispose();
-		buffer = new h3d.Buffer(data.vertexCount, data.vertexFormat.stride);
+		buffer = new h3d.Buffer(data.vertexCount, data.vertexFormat);
 
 		var entry = lib.resource.entry;
 
@@ -97,7 +97,7 @@ class HMDModel extends MeshPrimitive {
 		var alias = bufferAliases.get(name);
 		var buffer = bufferCache.get(hxsl.Globals.allocID(alias.realName));
 		if( buffer == null ) throw "Buffer " + alias.realName+" not found for alias " + name;
-		if( buffer.offset + alias.offset > buffer.buffer.stride ) throw "Alias " + name+" for buffer " + alias.realName+" outside stride";
+		if( buffer.offset + alias.offset > buffer.buffer.format.stride ) throw "Alias " + name+" for buffer " + alias.realName+" outside stride";
 		addBuffer(name, buffer.buffer, buffer.offset + alias.offset);
 	}
 
@@ -157,7 +157,7 @@ class HMDModel extends MeshPrimitive {
 			v[k++] = n.y;
 			v[k++] = n.z;
 		}
-		var buf = h3d.Buffer.ofFloats(v, 3);
+		var buf = h3d.Buffer.ofFloats(v, hxd.BufferFormat.make([{ name : "normal", type : DVec3 }]));
 		addBuffer(name, buf, 0);
 		normalsRecomputed = name;
 	}
@@ -199,7 +199,7 @@ class HMDModel extends MeshPrimitive {
 			v[k++] = t.y;
 			v[k++] = t.z;
 		}
-		var buf = h3d.Buffer.ofFloats(v, 3);
+		var buf = h3d.Buffer.ofFloats(v, hxd.BufferFormat.make([{ name : "tangent", type : DVec3 }]));
 		addBuffer("tangent", buf, 0);
 	}
 

+ 1 - 1
h3d/prim/Plane2D.hx

@@ -35,7 +35,7 @@ class Plane2D extends Primitive {
 		v.push( 1);
 		v.push( 0);
 
-		buffer = h3d.Buffer.ofFloats(v, 4, [RawFormat]);
+		buffer = h3d.Buffer.ofFloats(v, hxd.BufferFormat.XY_UV);
 	}
 
 	override function render(engine:h3d.Engine) {

+ 15 - 28
h3d/prim/Polygon.hx

@@ -29,29 +29,15 @@ class Polygon extends MeshPrimitive {
 	override function alloc( engine : h3d.Engine ) {
 		dispose();
 
-		var size = 3;
-		var names = ["position"];
-		var positions = [0];
-		if( normals != null ) {
-			names.push("normal");
-			positions.push(size);
-			size += 3;
-		}
-		if( tangents != null ) {
-			names.push("tangent");
-			positions.push(size);
-			size += 3;
-		}
-		if( uvs != null ) {
-			names.push("uv");
-			positions.push(size);
-			size += 2;
-		}
-		if( colors != null ) {
-			names.push("color");
-			positions.push(size);
-			size += 3;
-		}
+		var format = hxd.BufferFormat.POS3D;
+		if( normals != null )
+			format = format.append("normal", DVec3);
+		if( tangents != null )
+			format = format.append("tangent", DVec3);
+		if( uvs != null )
+			format = format.append("uv", DVec2);
+		if( colors != null )
+			format = format.append("color", DVec3);
 
 		var buf = new hxd.FloatBuffer();
 		for( k in 0...points.length ) {
@@ -83,12 +69,13 @@ class Polygon extends MeshPrimitive {
 				buf.push(c.z);
 			}
 		}
-		var flags : Array<h3d.Buffer.BufferFlag> = [];
-		if( normals == null || tangents != null ) flags.push(RawFormat);
-		buffer = h3d.Buffer.ofFloats(buf, size, flags);
+		buffer = h3d.Buffer.ofFloats(buf, format);
 
-		for( i in 0...names.length )
-			addBuffer(names[i], buffer, positions[i]);
+		var position = 0;
+		for( i in format.getInputs() ) {
+			addBuffer(i.name, buffer, position);
+			position += i.type.getSize();
+		}
 
 		if( idx != null )
 			indexes = h3d.Indexes.alloc(idx);

+ 9 - 6
h3d/prim/Quads.hx

@@ -94,12 +94,15 @@ class Quads extends Primitive {
 				v.push(t.v);
 			}
 		}
-		var size = 3;
-		if( normals != null ) size += 3;
-		if( uvs != null ) size += 2;
-		var flags : Array<h3d.Buffer.BufferFlag> = [];
-		if( normals == null ) flags.push(RawFormat);
-		buffer = h3d.Buffer.ofFloats(v, size, flags);
+		var format = if( normals != null && uvs != null )
+			hxd.BufferFormat.POS3D_NORMAL_UV
+		else if( normals != null )
+			hxd.BufferFormat.POS3D_NORMAL
+		else if( uvs != null )
+			hxd.BufferFormat.POS3D_UV
+		else
+			hxd.BufferFormat.POS3D;
+		buffer = h3d.Buffer.ofFloats(v, format);
 	}
 
 	public function addNormals() {

+ 3 - 5
h3d/prim/RawPrimitive.hx

@@ -5,9 +5,9 @@ class RawPrimitive extends Primitive {
 	var vcount : Int;
 	var tcount : Int;
 	var bounds : h3d.col.Bounds;
-	public var onContextLost : Void -> { vbuf : hxd.FloatBuffer, stride : Int, ?ibuf : hxd.IndexBuffer };
+	public var onContextLost : Void -> { vbuf : hxd.FloatBuffer, format : hxd.BufferFormat, ?ibuf : hxd.IndexBuffer };
 
-	public function new( inf : { vbuf : hxd.FloatBuffer, stride : Int, ?ibuf : hxd.IndexBuffer, ?quads : Bool, ?bounds : h3d.col.Bounds }, persist = false ) {
+	public function new( inf : { vbuf : hxd.FloatBuffer, format : hxd.BufferFormat, ?ibuf : hxd.IndexBuffer, ?bounds : h3d.col.Bounds }, persist = false ) {
 		onContextLost = function() return inf;
 		this.bounds = inf.bounds;
 		alloc(null);
@@ -17,9 +17,7 @@ class RawPrimitive extends Primitive {
 	override function alloc( engine : h3d.Engine ) {
 		if( onContextLost == null ) throw "Cannot realloc " + this;
 		var inf = onContextLost();
-		var flags : Array<h3d.Buffer.BufferFlag> = [];
-		if( inf.stride < 8 ) flags.push(RawFormat);
-		buffer = h3d.Buffer.ofFloats(inf.vbuf, inf.stride, flags);
+		buffer = h3d.Buffer.ofFloats(inf.vbuf, inf.format);
 		vcount = buffer.vertices;
 		tcount = inf.ibuf != null ? Std.int(inf.ibuf.length / 3) : Std.int(vcount/3);
 		if( inf.ibuf != null )

+ 1 - 1
h3d/scene/Graphics.hx

@@ -40,7 +40,7 @@ class Graphics extends Mesh {
 	public var is3D(default, set) : Bool;
 
 	public function new(?parent) {
-		bprim = new h3d.prim.BigPrimitive(12);
+		bprim = new h3d.prim.BigPrimitive(hxd.BufferFormat.POS3D_NORMAL_UV_RGBA);
 		bprim.isStatic = false;
 		super(bprim, null, parent);
 		tmpPoints = [];

+ 5 - 2
h3d/scene/MeshBatch.hx

@@ -327,6 +327,9 @@ class MeshBatch extends MultiMaterial {
 		}
 	}
 
+	static var VEC4_FMT = hxd.BufferFormat.make([{ name : "data", type : DVec4 }]);
+	static var SINGLE_FLOAT_FMT = hxd.BufferFormat.make([{ name : "data", type : DFloat }]);
+
 	override function sync(ctx:RenderContext) {
 		super.sync(ctx);
 		if( instanceCount == 0 ) return;
@@ -343,7 +346,7 @@ class MeshBatch extends MultiMaterial {
 				if( count > p.maxInstance )
 					count = p.maxInstance;
 				if( buf == null || buf.isDisposed() ) {
-					buf = alloc.allocBuffer(MAX_BUFFER_ELEMENTS,4,UniformDynamic);
+					buf = alloc.allocBuffer(MAX_BUFFER_ELEMENTS,VEC4_FMT,UniformDynamic);
 					p.buffers[index] = buf;
 					upload = true;
 				}
@@ -376,7 +379,7 @@ class MeshBatch extends MultiMaterial {
 				var tmp = haxe.io.Bytes.alloc(4 * instanceCount);
 				for( i in 0...instanceCount )
 					tmp.setFloat(i<<2, i);
-				offsets = new h3d.Buffer(instanceCount, 1);
+				offsets = new h3d.Buffer(instanceCount, SINGLE_FLOAT_FMT);
 				offsets.uploadBytes(tmp,0,instanceCount);
 				@:privateAccess prim.addBuffer("Batch_Start", offsets);
 			}

+ 1 - 1
h3d/scene/Trail.hx

@@ -30,7 +30,7 @@ class Trail extends Mesh {
 	var pending = new TrailElement(); // tmp
 
 	public function new(?parent) {
-		dprim = new h3d.prim.DynamicPrimitive(8);
+		dprim = new h3d.prim.DynamicPrimitive(hxd.BufferFormat.POS3D_NORMAL_UV);
 		super(dprim, null, parent);
 		material.props = getMaterialProps();
 		material.mainPass.dynamicParameters = true;

+ 1 - 1
h3d/scene/World.hx

@@ -533,7 +533,7 @@ class World extends Object {
 			for( g in model.geometries ) {
 				var b = c.buffers.get(g.m.bits);
 				if( b == null ) {
-					var bp = new h3d.prim.BigPrimitive(getFormat(model).stride, true);
+					var bp = new h3d.prim.BigPrimitive(getFormat(model));
 					bp.hasTangents = enableNormalMaps;
 					b = new h3d.scene.Mesh(bp, c.root);
 					b.name = g.m.name;

+ 1 - 1
h3d/scene/pbr/LightBuffer.hx

@@ -36,7 +36,7 @@ class LightBuffer {
 		size += MAX_SPOT_LIGHT * SPOT_LIGHT_INFO_SIZE;
 		size = hxd.Math.imax(1, size); // Avoid empty buffer
 		lightInfos = new hxd.FloatBuffer(size * stride);
-		defaultForwardShader.lightInfos = new h3d.Buffer(size, stride, [UniformBuffer, Dynamic]);
+		defaultForwardShader.lightInfos = new h3d.Buffer(size, hxd.BufferFormat.make([{ name : "uniformData", type : DVec4 }]), [UniformBuffer, Dynamic]);
 		defaultForwardShader.BUFFER_SIZE = size;
 		defaultForwardShader.dirLightStride = DIR_LIGHT_INFO_SIZE * MAX_DIR_LIGHT;
 		defaultForwardShader.pointLightStride = POINT_LIGHT_INFO_SIZE * MAX_POINT_LIGHT;

+ 4 - 3
h3d/shader/ParticleShader.hx

@@ -15,7 +15,8 @@ class ParticleShader extends hxsl.Shader {
 
 		@input var input : {
 			var position : Vec3;
-			var normal : Vec3;
+			var rpos : Vec2;
+			var rot : Float;
 			var size : Vec2;
 			var uv : Vec2;
 		};
@@ -36,8 +37,8 @@ class ParticleShader extends hxsl.Shader {
 		}
 
 		function vertex() {
-			var rpos = input.normal.xy;
-			var rot = input.normal.z;
+			var rpos = input.rpos;
+			var rot = input.rot;
 			var cr = rot.cos();
 			var sr = rot.sin();
 			var pos = input.size * rpos;

+ 42 - 0
hxd/BufferFormat.hx

@@ -63,6 +63,7 @@ class BufferFormat {
 	public var uid : Int;
 	public var stride(default,null) : Int;
 	var inputs : Array<BufferInput>;
+	var offsets : Map<Int, Array<Int>>;
 
 	function new( inputs : Array<BufferInput> ) {
 		uid = _UID++;
@@ -91,6 +92,42 @@ class BufferFormat {
 		return make(inputs);
 	}
 
+	public function isSubSet( fmt : BufferFormat ) {
+		if( fmt == this )
+			return true;
+		if( inputs.length >= fmt.inputs.length )
+			return false;
+		for( i in 0...inputs.length ) {
+			var i1 = inputs[i];
+			var i2 = fmt.inputs[i];
+			if( i1.name != i2.name || i1.type != i2.type )
+				return false;
+		}
+		return true;
+	}
+
+	public function getMatchingOffsets( target : BufferFormat ) {
+		var offs = offsets == null ? null : offsets.get(target.uid);
+		if( offs != null )
+			return offs;
+		offs = [];
+		for( i in target.inputs ) {
+			var v = 0;
+			for( i2 in inputs ) {
+				if( i2.name == i.name ) {
+					offs.push(v);
+					v = -1;
+					break;
+				}
+				v += i2.type.getSize();
+			}
+			if( v >= 0 ) throw "Missing buffer input '"+i.name+"'";
+		}
+		if( offsets == null ) offsets = new Map();
+		offsets.set(target.uid, offs);
+		return offs;
+	}
+
 	public inline function getInputs() {
 		return inputs.iterator();
 	}
@@ -105,6 +142,7 @@ class BufferFormat {
 	public static var POS3D_NORMAL(get,null) : BufferFormat;
 	public static var POS3D_UV(get,null) : BufferFormat;
 	public static var POS3D_NORMAL_UV(get,null) : BufferFormat;
+	public static var POS3D_NORMAL_UV_RGBA(get,null) : BufferFormat;
 
 	static inline function get_H2D() return XY_UV_RGBA;
 	static function get_XY_UV_RGBA() {
@@ -127,6 +165,10 @@ class BufferFormat {
 		if( POS3D_NORMAL_UV == null ) POS3D_NORMAL_UV = make([{ name : "position", type : DVec3 },{ name : "normal", type : DVec3 },{ name : "uv", type : DVec2 }]);
 		return POS3D_NORMAL_UV;
 	}
+	static function get_POS3D_NORMAL_UV_RGBA() {
+		if( POS3D_NORMAL_UV_RGBA == null ) POS3D_NORMAL_UV_RGBA = POS3D_NORMAL_UV.append("color",DVec4);
+		return POS3D_NORMAL_UV_RGBA;
+	}
 	static function get_POS3D_UV() {
 		if( POS3D_UV == null ) POS3D_UV = make([{ name : "position", type : DVec3 },{ name : "uv", type : DVec2 }]);
 		return POS3D_UV;

+ 12 - 12
hxd/impl/Allocator.hx

@@ -2,8 +2,8 @@ package hxd.impl;
 
 enum abstract BufferFlags(Int) {
 	public var Dynamic = 0;
-	public var UniformDynamic = 1;
-	public var RawFormat = 2;
+	public var Static = 1;
+	public var UniformDynamic = 2;
 	public inline function toInt() : Int {
 		return this;
 	}
@@ -16,22 +16,22 @@ class Allocator {
 
 	// GPU
 
-	public function allocBuffer( vertices : Int, stride : Int, flags : BufferFlags ) : h3d.Buffer {
-		return new h3d.Buffer(vertices, stride,
+	public function allocBuffer( vertices : Int, format, flags : BufferFlags = Dynamic ) : h3d.Buffer {
+		return new h3d.Buffer(vertices, format,
 			switch( flags ) {
-				case Dynamic: [Dynamic];
-				case UniformDynamic: [UniformBuffer,Dynamic];
-				case RawFormat: [RawFormat];
+			case Static: null;
+			case Dynamic: [Dynamic];
+			case UniformDynamic: [UniformBuffer,Dynamic];
 			});
 	}
 
-	public function ofFloats( v : hxd.FloatBuffer, stride : Int, flags : BufferFlags ) {
-		var nvert = Std.int(v.length / stride);
-		return ofSubFloats(v, stride, nvert, flags);
+	public function ofFloats( v : hxd.FloatBuffer, format : hxd.BufferFormat, flags : BufferFlags = Dynamic ) {
+		var nvert = Std.int(v.length / format.stride);
+		return ofSubFloats(v, nvert, format, flags);
 	}
 
-	public function ofSubFloats( v : hxd.FloatBuffer, stride : Int, vertices : Int, flags : BufferFlags ) {
-		var b = allocBuffer(vertices, stride, flags);
+	public function ofSubFloats( v : hxd.FloatBuffer, vertices : Int, format, flags : BufferFlags = Dynamic ) {
+		var b = allocBuffer(vertices, format, flags);
 		b.uploadVector(v, 0, vertices);
 		return b;
 	}

+ 5 - 5
hxd/impl/CacheAllocator.hx

@@ -50,24 +50,24 @@ class CacheAllocator extends Allocator {
 	**/
 	public var maxKeepTime = 60.;
 
-	override function allocBuffer(vertices:Int, stride:Int, flags:BufferFlags):h3d.Buffer {
+	override function allocBuffer(vertices:Int, format:hxd.BufferFormat, flags:BufferFlags=Dynamic):h3d.Buffer {
 		if( vertices >= 65536 ) throw "assert";
 		checkFrame();
-		var id = flags.toInt() | (stride << 3) | (vertices << 16);
+		var id = flags.toInt() | (format.uid << 3) | (vertices << 16);
 		var c = buffers.get(id);
 		if( c != null ) {
 			var b = c.get();
 			if( b != null ) return b;
 		}
 		checkGC();
-		return super.allocBuffer(vertices,stride,flags);
+		return super.allocBuffer(vertices,format,flags);
 	}
 
 	override function disposeBuffer(b:h3d.Buffer) {
 		if( b.isDisposed() ) return;
 		var f = b.flags;
-		var flags = f.has(RawFormat) ? RawFormat : (f.has(UniformBuffer) ? UniformDynamic : Dynamic);
-		var id = flags.toInt() | (b.stride << 3) | (b.vertices << 16);
+		var flags = f.has(UniformBuffer) ? UniformDynamic : (f.has(Dynamic) ? Dynamic : Static);
+		var id = flags.toInt() | (b.format.uid << 3) | (b.vertices << 16);
 		var c = buffers.get(id);
 		if( c == null ) {
 			c = new Cache(function(b:h3d.Buffer) b.dispose());