forked from LeenkxTeam/LNXSDK
		
	
		
			
				
	
	
		
			275 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
			
		
		
	
	
			275 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
package iron.data;
 | 
						|
 | 
						|
#if lnx_batch
 | 
						|
 | 
						|
import kha.arrays.ByteArray;
 | 
						|
import kha.graphics4.VertexBuffer;
 | 
						|
import kha.graphics4.IndexBuffer;
 | 
						|
import kha.graphics4.Usage;
 | 
						|
import kha.graphics4.VertexStructure;
 | 
						|
import kha.graphics4.Graphics;
 | 
						|
import iron.object.MeshObject;
 | 
						|
import iron.object.Uniforms;
 | 
						|
import iron.data.Geometry;
 | 
						|
import iron.data.MaterialData;
 | 
						|
import iron.data.ShaderData;
 | 
						|
import iron.data.SceneFormat;
 | 
						|
 | 
						|
@:access(iron.object.MeshObject)
 | 
						|
class MeshBatch {
 | 
						|
 | 
						|
	var buckets: Map<ShaderData, Bucket> = new Map();
 | 
						|
	public var nonBatched: Array<MeshObject> = [];
 | 
						|
 | 
						|
	public function new() {}
 | 
						|
 | 
						|
	public function remove() {
 | 
						|
		for (b in buckets) b.remove();
 | 
						|
	}
 | 
						|
 | 
						|
	public static function isBatchable(m: MeshObject): Bool {
 | 
						|
		m.computeDepthRead();
 | 
						|
		var batch =
 | 
						|
			m.materials != null &&
 | 
						|
			m.materials.length == 1 &&
 | 
						|
			!m.data.geom.instanced &&
 | 
						|
			!m.data.isSkinned &&
 | 
						|
			m.data.raw.morph_target == null &&
 | 
						|
			!m.depthRead;
 | 
						|
		return batch;
 | 
						|
	}
 | 
						|
 | 
						|
	public function addMesh(m: MeshObject, isLod: Bool): Bool {
 | 
						|
		// No instancing, multimat, skinning, morph targets or lod batching
 | 
						|
		if (!isBatchable(m) || isLod) {
 | 
						|
			nonBatched.push(m);
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
		var shader = m.materials[0].shader;
 | 
						|
		var b = buckets.get(shader);
 | 
						|
		if (b == null) {
 | 
						|
			b = new Bucket(shader);
 | 
						|
			buckets.set(shader, b);
 | 
						|
		}
 | 
						|
		b.addMesh(m);
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	public function removeMesh(m: MeshObject) {
 | 
						|
		var shader = m.materials[0].shader;
 | 
						|
		var b = buckets.get(shader);
 | 
						|
		if (b != null) b.removeMesh(m);
 | 
						|
		else nonBatched.remove(m);
 | 
						|
	}
 | 
						|
 | 
						|
	@:access(iron.RenderPath)
 | 
						|
	public function render(g: Graphics, context: String, bindParams: Array<String>) {
 | 
						|
 | 
						|
		for (b in buckets) {
 | 
						|
			if (!b.batched) b.batch();
 | 
						|
			if (b.meshes.length > 0 && b.meshes[0].cullMaterial(context)) continue;
 | 
						|
 | 
						|
			var scontext = b.shader.getContext(context);
 | 
						|
			g.setPipeline(scontext.pipeState);
 | 
						|
			// #if lnx_deinterleaved // TODO
 | 
						|
			// g.setVertexBuffers(b.vertexBuffers);
 | 
						|
			// #else
 | 
						|
			g.setVertexBuffer(b.getVertexBuffer(scontext.raw.vertex_elements));
 | 
						|
			// #end
 | 
						|
			g.setIndexBuffer(b.indexBuffer);
 | 
						|
 | 
						|
			Uniforms.setContextConstants(g, scontext, bindParams);
 | 
						|
 | 
						|
			RenderPath.sortMeshesDistance(b.meshes);
 | 
						|
 | 
						|
			for (m in b.meshes) {
 | 
						|
				if (!m.visible) continue; // Skip render if object is hidden
 | 
						|
				if (m.cullMesh(context, Scene.active.camera, RenderPath.active.light)) continue;
 | 
						|
 | 
						|
				// Get context
 | 
						|
				var materialContexts: Array<MaterialContext> = [];
 | 
						|
				var shaderContexts: Array<ShaderContext> = [];
 | 
						|
				m.getContexts(context, m.materials, materialContexts, shaderContexts);
 | 
						|
 | 
						|
				Uniforms.posUnpack = m.data.scalePos;
 | 
						|
				Uniforms.texUnpack = m.data.scaleTex;
 | 
						|
				m.transform.update();
 | 
						|
 | 
						|
				// Render mesh
 | 
						|
				Uniforms.setObjectConstants(g, scontext, m);
 | 
						|
				Uniforms.setMaterialConstants(g, scontext, materialContexts[0]);
 | 
						|
 | 
						|
				g.drawIndexedVertices(m.data.start, m.data.count);
 | 
						|
 | 
						|
				#if lnx_veloc
 | 
						|
				m.prevMatrix.setFrom(m.transform.worldUnpack);
 | 
						|
				#end
 | 
						|
 | 
						|
				#if lnx_debug
 | 
						|
				RenderPath.drawCalls++;
 | 
						|
				RenderPath.batchCalls++;
 | 
						|
				#end
 | 
						|
			}
 | 
						|
 | 
						|
			#if lnx_debug
 | 
						|
			RenderPath.batchBuckets++;
 | 
						|
			#end
 | 
						|
		}
 | 
						|
 | 
						|
		// Render non-batched meshes
 | 
						|
		inline RenderPath.meshRenderLoop(g, context, bindParams, nonBatched);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
class Bucket {
 | 
						|
 | 
						|
	public var batched = false;
 | 
						|
	public var shader: ShaderData;
 | 
						|
	var vertexBuffer: VertexBuffer;
 | 
						|
	var vertexBufferMap: Map<String, VertexBuffer> = new Map();
 | 
						|
	public var indexBuffer: IndexBuffer;
 | 
						|
	public var meshes: Array<MeshObject> = [];
 | 
						|
 | 
						|
	public function new(shader: ShaderData) {
 | 
						|
		this.shader = shader;
 | 
						|
	}
 | 
						|
 | 
						|
	public function remove() {
 | 
						|
		indexBuffer.delete();
 | 
						|
		// this.vertexBuffer is in the map, so it's also deleted here
 | 
						|
		for (buf in vertexBufferMap) buf.delete();
 | 
						|
		meshes = [];
 | 
						|
	}
 | 
						|
 | 
						|
	public function addMesh(m: MeshObject) {
 | 
						|
		meshes.push(m);
 | 
						|
	}
 | 
						|
 | 
						|
	public function removeMesh(m: MeshObject) {
 | 
						|
		meshes.remove(m);
 | 
						|
	}
 | 
						|
 | 
						|
	function copyAttribute(attribSize: Int, count: Int, to: ByteArray, toStride: Int, toOffset: Int, from: ByteArray, fromStride: Int, fromOffset: Int) {
 | 
						|
		for (i in 0...count) {
 | 
						|
			for (j in 0...attribSize) {
 | 
						|
				to.setInt16((i * toStride + toOffset + j) * 2, from.getInt16((i * fromStride + fromOffset + j) * 2));
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	function extractVertexBuffer(elems: Array<TVertexElement>): VertexBuffer {
 | 
						|
		// Build vertex buffer for specific context
 | 
						|
		var vs = new VertexStructure();
 | 
						|
		for (e in elems) vs.add(e.name, ShaderContext.parseData(e.data));
 | 
						|
 | 
						|
		var vb = new VertexBuffer(vertexBuffer.count(), vs, Usage.StaticUsage);
 | 
						|
		var to = vb.lock();
 | 
						|
		var from = vertexBuffer.lock();
 | 
						|
 | 
						|
		var toOffset = 0;
 | 
						|
		var toStride = Std.int(vb.stride() / 2);
 | 
						|
		var fromOffset = 0;
 | 
						|
		var fromStride = Std.int(vertexBuffer.stride() / 2);
 | 
						|
 | 
						|
		for (e in elems) {
 | 
						|
			var size = 0;
 | 
						|
			if (e.name == "pos") { size = 4; fromOffset = 0; }
 | 
						|
			else if (e.name == "nor") { size = 2; fromOffset = 4; }
 | 
						|
			else if (e.name == "tex") { size = 2; fromOffset = 6; }
 | 
						|
			copyAttribute(size, vertexBuffer.count(), to, toStride, toOffset, from, fromStride, fromOffset);
 | 
						|
			toOffset += size;
 | 
						|
		}
 | 
						|
 | 
						|
		vb.unlock();
 | 
						|
		return vb;
 | 
						|
	}
 | 
						|
 | 
						|
	public function getVertexBuffer(elems: Array<TVertexElement>): VertexBuffer {
 | 
						|
		var s = "";
 | 
						|
		for (e in elems) s += e.name;
 | 
						|
		var vb = vertexBufferMap.get(s);
 | 
						|
		if (vb == null) {
 | 
						|
			vb = extractVertexBuffer(elems);
 | 
						|
			vertexBufferMap.set(s, vb);
 | 
						|
		}
 | 
						|
		return vb;
 | 
						|
	}
 | 
						|
 | 
						|
	function vertexCount(g: Geometry, hasUVs: Bool): Int {
 | 
						|
		var vcount = g.getVerticesLength();
 | 
						|
		if (hasUVs && g.uvs == null) {
 | 
						|
			vcount += Std.int(g.positions.values.length / 4) * 2;
 | 
						|
		}
 | 
						|
		return vcount;
 | 
						|
	}
 | 
						|
 | 
						|
	public function batch() {
 | 
						|
		batched = true;
 | 
						|
 | 
						|
		// Ensure same vertex structure for batched meshes
 | 
						|
		var hasUVs = false;
 | 
						|
		for (m in meshes) {
 | 
						|
			if (m.data.geom.uvs != null) {
 | 
						|
				hasUVs = true;
 | 
						|
				break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// Unique mesh datas
 | 
						|
		var vcount = 0;
 | 
						|
		var icount = 0;
 | 
						|
		var mdatas: Array<MeshData> = [];
 | 
						|
		for (m in meshes) {
 | 
						|
			var mdFound = false;
 | 
						|
			for (md in mdatas) {
 | 
						|
				if (m.data == md) {
 | 
						|
					mdFound = true;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if (!mdFound) {
 | 
						|
				mdatas.push(m.data);
 | 
						|
				m.data.start = icount;
 | 
						|
				m.data.count = m.data.geom.indices[0].length;
 | 
						|
				icount += m.data.count;
 | 
						|
				vcount += vertexCount(m.data.geom, hasUVs);
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if (mdatas.length == 0) return;
 | 
						|
 | 
						|
		// Pick UVs if present
 | 
						|
		var vs = mdatas[0].geom.struct;
 | 
						|
		for (md in mdatas) if (md.geom.struct.size() > vs.size()) vs = md.geom.struct;
 | 
						|
 | 
						|
		// Build shared buffers
 | 
						|
		vertexBuffer = new VertexBuffer(vcount, vs, Usage.StaticUsage);
 | 
						|
		var vertices = vertexBuffer.lock();
 | 
						|
		var offset = 0;
 | 
						|
		for (md in mdatas) {
 | 
						|
			md.geom.copyVertices(vertices, offset, hasUVs);
 | 
						|
			offset += vertexCount(md.geom, hasUVs);
 | 
						|
		}
 | 
						|
		vertexBuffer.unlock();
 | 
						|
 | 
						|
		var s = "";
 | 
						|
		for (e in vs.elements) s += e.name;
 | 
						|
		vertexBufferMap.set(s, vertexBuffer);
 | 
						|
 | 
						|
		indexBuffer = new IndexBuffer(icount, Usage.StaticUsage);
 | 
						|
		var indices = indexBuffer.lock();
 | 
						|
		var di = -1;
 | 
						|
		var offset = 0;
 | 
						|
		for (md in mdatas) {
 | 
						|
			for (i in 0...md.geom.indices[0].length) {
 | 
						|
				indices[++di] = md.geom.indices[0][i] + offset;
 | 
						|
			}
 | 
						|
			offset += Std.int(md.geom.getVerticesLength() / md.geom.structLength);
 | 
						|
		}
 | 
						|
		indexBuffer.unlock();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
#end
 |