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
|