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
							 |