playcanvas
Version:
PlayCanvas WebGL game engine
603 lines (600 loc) • 21.7 kB
JavaScript
import { Mat3 } from '../../core/math/mat3.js';
import { BoundingBox } from '../../core/shape/bounding-box.js';
import { PRIMITIVE_TRIFAN, PRIMITIVE_TRISTRIP, TYPE_FLOAT32, SEMANTIC_BLENDINDICES, typedArrayTypes, typedArrayTypesByteSize, SEMANTIC_POSITION, SEMANTIC_NORMAL, SEMANTIC_TANGENT, typedArrayIndexFormats, PRIMITIVE_TRIANGLES } from '../../platform/graphics/constants.js';
import { SPRITE_RENDERMODE_SIMPLE } from '../constants.js';
import { Mesh } from '../mesh.js';
import { MeshInstance } from '../mesh-instance.js';
import { Batch } from './batch.js';
import { BatchGroup } from './batch-group.js';
import { SkinBatchInstance } from './skin-batch-instance.js';
var _triFanIndices = [
0,
1,
3,
2,
3,
1
];
var _triStripIndices = [
0,
1,
3,
0,
3,
2
];
var mat3 = new Mat3();
function paramsIdentical(a, b) {
if (a && !b) return false;
if (!a && b) return false;
a = a.data;
b = b.data;
if (a === b) return true;
if (a instanceof Float32Array && b instanceof Float32Array) {
if (a.length !== b.length) return false;
for(var i = 0; i < a.length; i++){
if (a[i] !== b[i]) return false;
}
return true;
}
return false;
}
function equalParamSets(params1, params2) {
for(var param in params1){
if (params1.hasOwnProperty(param) && !paramsIdentical(params1[param], params2[param])) {
return false;
}
}
for(var param1 in params2){
if (params2.hasOwnProperty(param1) && !paramsIdentical(params2[param1], params1[param1])) {
return false;
}
}
return true;
}
function getScaleSign(mi) {
return mi.node.worldTransform.scaleSign;
}
class BatchManager {
destroy() {
this.device = null;
this.rootNode = null;
this.scene = null;
this._batchGroups = {};
this._batchList = [];
this._dirtyGroups = [];
}
addGroup(name, dynamic, maxAabbSize, id, layers) {
if (id === undefined) {
id = this._batchGroupCounter;
this._batchGroupCounter++;
}
if (this._batchGroups[id]) {
return undefined;
}
var group = new BatchGroup(id, name, dynamic, maxAabbSize, layers);
this._batchGroups[id] = group;
return group;
}
removeGroup(id) {
if (!this._batchGroups[id]) {
return;
}
var newBatchList = [];
for(var i = 0; i < this._batchList.length; i++){
if (this._batchList[i].batchGroupId === id) {
this.destroyBatch(this._batchList[i]);
} else {
newBatchList.push(this._batchList[i]);
}
}
this._batchList = newBatchList;
this._removeModelsFromBatchGroup(this.rootNode, id);
delete this._batchGroups[id];
}
markGroupDirty(id) {
if (this._dirtyGroups.indexOf(id) < 0) {
this._dirtyGroups.push(id);
}
}
getGroupByName(name) {
var groups = this._batchGroups;
for(var group in groups){
if (!groups.hasOwnProperty(group)) continue;
if (groups[group].name === name) {
return groups[group];
}
}
return null;
}
getBatches(batchGroupId) {
var results = [];
var len = this._batchList.length;
for(var i = 0; i < len; i++){
var batch = this._batchList[i];
if (batch.batchGroupId === batchGroupId) {
results.push(batch);
}
}
return results;
}
_removeModelsFromBatchGroup(node, id) {
if (!node.enabled) return;
if (node.model && node.model.batchGroupId === id) {
node.model.batchGroupId = -1;
}
if (node.render && node.render.batchGroupId === id) {
node.render.batchGroupId = -1;
}
if (node.element && node.element.batchGroupId === id) {
node.element.batchGroupId = -1;
}
if (node.sprite && node.sprite.batchGroupId === id) {
node.sprite.batchGroupId = -1;
}
for(var i = 0; i < node._children.length; i++){
this._removeModelsFromBatchGroup(node._children[i], id);
}
}
insert(type, groupId, node) {
var group = this._batchGroups[groupId];
if (group) {
if (group._obj[type].indexOf(node) < 0) {
group._obj[type].push(node);
this.markGroupDirty(groupId);
}
}
}
remove(type, groupId, node) {
var group = this._batchGroups[groupId];
if (group) {
var idx = group._obj[type].indexOf(node);
if (idx >= 0) {
group._obj[type].splice(idx, 1);
this.markGroupDirty(groupId);
}
}
}
_extractRender(node, arr, group, groupMeshInstances) {
if (node.render) {
arr = groupMeshInstances[node.render.batchGroupId] = arr.concat(node.render.meshInstances);
node.render.removeFromLayers();
}
return arr;
}
_extractModel(node, arr, group, groupMeshInstances) {
if (node.model && node.model.model) {
arr = groupMeshInstances[node.model.batchGroupId] = arr.concat(node.model.meshInstances);
node.model.removeModelFromLayers();
}
return arr;
}
_extractElement(node, arr, group) {
if (!node.element) return;
var valid = false;
if (node.element._text && node.element._text._model.meshInstances.length > 0) {
arr.push(node.element._text._model.meshInstances[0]);
node.element.removeModelFromLayers(node.element._text._model);
valid = true;
} else if (node.element._image) {
arr.push(node.element._image._renderable.meshInstance);
node.element.removeModelFromLayers(node.element._image._renderable.model);
if (node.element._image._renderable.unmaskMeshInstance) {
arr.push(node.element._image._renderable.unmaskMeshInstance);
if (!node.element._image._renderable.unmaskMeshInstance.stencilFront || !node.element._image._renderable.unmaskMeshInstance.stencilBack) {
node.element._dirtifyMask();
node.element._onPrerender();
}
}
valid = true;
}
if (valid) {
group._ui = true;
}
}
_collectAndRemoveMeshInstances(groupMeshInstances, groupIds) {
for(var g = 0; g < groupIds.length; g++){
var id = groupIds[g];
var group = this._batchGroups[id];
if (!group) continue;
var arr = groupMeshInstances[id];
if (!arr) arr = groupMeshInstances[id] = [];
for(var m = 0; m < group._obj.model.length; m++){
arr = this._extractModel(group._obj.model[m], arr, group, groupMeshInstances);
}
for(var r = 0; r < group._obj.render.length; r++){
arr = this._extractRender(group._obj.render[r], arr, group, groupMeshInstances);
}
for(var e = 0; e < group._obj.element.length; e++){
this._extractElement(group._obj.element[e], arr, group);
}
for(var s = 0; s < group._obj.sprite.length; s++){
var node = group._obj.sprite[s];
if (node.sprite && node.sprite._meshInstance && (group.dynamic || node.sprite.sprite._renderMode === SPRITE_RENDERMODE_SIMPLE)) {
arr.push(node.sprite._meshInstance);
node.sprite.removeModelFromLayers();
group._sprite = true;
node.sprite._batchGroup = group;
}
}
}
}
generate(groupIds) {
var groupMeshInstances = {};
if (!groupIds) {
groupIds = Object.keys(this._batchGroups);
}
var newBatchList = [];
for(var i = 0; i < this._batchList.length; i++){
if (groupIds.indexOf(this._batchList[i].batchGroupId) < 0) {
newBatchList.push(this._batchList[i]);
continue;
}
this.destroyBatch(this._batchList[i]);
}
this._batchList = newBatchList;
this._collectAndRemoveMeshInstances(groupMeshInstances, groupIds);
if (groupIds === this._dirtyGroups) {
this._dirtyGroups.length = 0;
} else {
var newDirtyGroups = [];
for(var i1 = 0; i1 < this._dirtyGroups.length; i1++){
if (groupIds.indexOf(this._dirtyGroups[i1]) < 0) newDirtyGroups.push(this._dirtyGroups[i1]);
}
this._dirtyGroups = newDirtyGroups;
}
var group, lists, groupData, batch;
for(var groupId in groupMeshInstances){
if (!groupMeshInstances.hasOwnProperty(groupId)) continue;
group = groupMeshInstances[groupId];
groupData = this._batchGroups[groupId];
if (!groupData) {
continue;
}
lists = this.prepare(group, groupData.dynamic, groupData.maxAabbSize, groupData._ui || groupData._sprite);
for(var i2 = 0; i2 < lists.length; i2++){
batch = this.create(lists[i2], groupData.dynamic, parseInt(groupId, 10));
if (batch) {
batch.addToLayers(this.scene, groupData.layers);
}
}
}
}
prepare(meshInstances, dynamic, maxAabbSize, translucent) {
if (maxAabbSize === void 0) maxAabbSize = Number.POSITIVE_INFINITY;
if (meshInstances.length === 0) return [];
var halfMaxAabbSize = maxAabbSize * 0.5;
var maxInstanceCount = 1024;
var maxNumVertices = 0xffffffff;
var aabb = new BoundingBox();
var testAabb = new BoundingBox();
var skipTranslucentAabb = null;
var sf;
var lists = [];
var j = 0;
if (translucent) {
meshInstances.sort((a, b)=>{
return a.drawOrder - b.drawOrder;
});
}
var meshInstancesLeftA = meshInstances;
var meshInstancesLeftB;
var skipMesh = translucent ? function skipMesh(mi) {
if (skipTranslucentAabb) {
skipTranslucentAabb.add(mi.aabb);
} else {
skipTranslucentAabb = mi.aabb.clone();
}
meshInstancesLeftB.push(mi);
} : function(mi) {
meshInstancesLeftB.push(mi);
};
while(meshInstancesLeftA.length > 0){
lists[j] = [
meshInstancesLeftA[0]
];
meshInstancesLeftB = [];
var material = meshInstancesLeftA[0].material;
var layer = meshInstancesLeftA[0].layer;
var defs = meshInstancesLeftA[0]._shaderDefs;
var params = meshInstancesLeftA[0].parameters;
var stencil = meshInstancesLeftA[0].stencilFront;
var vertCount = meshInstancesLeftA[0].mesh.vertexBuffer.getNumVertices();
var drawOrder = meshInstancesLeftA[0].drawOrder;
aabb.copy(meshInstancesLeftA[0].aabb);
var scaleSign = getScaleSign(meshInstancesLeftA[0]);
var vertexFormatBatchingHash = meshInstancesLeftA[0].mesh.vertexBuffer.format.batchingHash;
var indexed = meshInstancesLeftA[0].mesh.primitive[0].indexed;
skipTranslucentAabb = null;
for(var i = 1; i < meshInstancesLeftA.length; i++){
var mi = meshInstancesLeftA[i];
if (dynamic && lists[j].length >= maxInstanceCount) {
meshInstancesLeftB = meshInstancesLeftB.concat(meshInstancesLeftA.slice(i));
break;
}
if (material !== mi.material || layer !== mi.layer || vertexFormatBatchingHash !== mi.mesh.vertexBuffer.format.batchingHash || indexed !== mi.mesh.primitive[0].indexed || defs !== mi._shaderDefs || vertCount + mi.mesh.vertexBuffer.getNumVertices() > maxNumVertices) {
skipMesh(mi);
continue;
}
testAabb.copy(aabb);
testAabb.add(mi.aabb);
if (testAabb.halfExtents.x > halfMaxAabbSize || testAabb.halfExtents.y > halfMaxAabbSize || testAabb.halfExtents.z > halfMaxAabbSize) {
skipMesh(mi);
continue;
}
if (stencil) {
if (!(sf = mi.stencilFront) || stencil.func !== sf.func || stencil.zpass !== sf.zpass) {
skipMesh(mi);
continue;
}
}
if (scaleSign !== getScaleSign(mi)) {
skipMesh(mi);
continue;
}
if (!equalParamSets(params, mi.parameters)) {
skipMesh(mi);
continue;
}
if (translucent && skipTranslucentAabb && skipTranslucentAabb.intersects(mi.aabb) && mi.drawOrder !== drawOrder) {
skipMesh(mi);
continue;
}
aabb.add(mi.aabb);
vertCount += mi.mesh.vertexBuffer.getNumVertices();
lists[j].push(mi);
}
j++;
meshInstancesLeftA = meshInstancesLeftB;
}
return lists;
}
collectBatchedMeshData(meshInstances, dynamic) {
var streams = null;
var batchNumVerts = 0;
var batchNumIndices = 0;
var material = null;
for(var i = 0; i < meshInstances.length; i++){
if (meshInstances[i].visible) {
var mesh = meshInstances[i].mesh;
var numVerts = mesh.vertexBuffer.numVertices;
batchNumVerts += numVerts;
if (mesh.primitive[0].indexed) {
batchNumIndices += mesh.primitive[0].count;
} else {
var primitiveType = mesh.primitive[0].type;
if (primitiveType === PRIMITIVE_TRIFAN || primitiveType === PRIMITIVE_TRISTRIP) {
if (mesh.primitive[0].count === 4) {
batchNumIndices += 6;
}
}
}
if (!streams) {
material = meshInstances[i].material;
streams = {};
var elems = mesh.vertexBuffer.format.elements;
for(var j = 0; j < elems.length; j++){
var semantic = elems[j].name;
streams[semantic] = {
numComponents: elems[j].numComponents,
dataType: elems[j].dataType,
normalize: elems[j].normalize,
count: 0
};
}
if (dynamic) {
streams[SEMANTIC_BLENDINDICES] = {
numComponents: 1,
dataType: TYPE_FLOAT32,
normalize: false,
count: 0
};
}
}
}
}
return {
streams: streams,
batchNumVerts: batchNumVerts,
batchNumIndices: batchNumIndices,
material: material
};
}
create(meshInstances, dynamic, batchGroupId) {
if (!this._init) {
this.vertexFormats = {};
this._init = true;
}
var stream = null;
var semantic;
var mesh, numVerts;
var batch = null;
var batchData = this.collectBatchedMeshData(meshInstances, dynamic);
if (batchData.streams) {
var streams = batchData.streams;
var material = batchData.material;
var batchNumVerts = batchData.batchNumVerts;
var batchNumIndices = batchData.batchNumIndices;
batch = new Batch(meshInstances, dynamic, batchGroupId);
this._batchList.push(batch);
var indexBase, numIndices, indexData;
var verticesOffset = 0;
var indexOffset = 0;
var transform;
var indexArrayType = batchNumVerts <= 0xffff ? Uint16Array : Uint32Array;
var indices = new indexArrayType(batchNumIndices);
for(semantic in streams){
stream = streams[semantic];
stream.typeArrayType = typedArrayTypes[stream.dataType];
stream.elementByteSize = typedArrayTypesByteSize[stream.dataType];
stream.buffer = new stream.typeArrayType(batchNumVerts * stream.numComponents);
}
for(var i = 0; i < meshInstances.length; i++){
if (!meshInstances[i].visible) {
continue;
}
mesh = meshInstances[i].mesh;
numVerts = mesh.vertexBuffer.numVertices;
if (!dynamic) {
transform = meshInstances[i].node.getWorldTransform();
}
for(semantic in streams){
if (semantic !== SEMANTIC_BLENDINDICES) {
stream = streams[semantic];
var subarray = new stream.typeArrayType(stream.buffer.buffer, stream.elementByteSize * stream.count);
var totalComponents = mesh.getVertexStream(semantic, subarray) * stream.numComponents;
stream.count += totalComponents;
if (!dynamic && stream.numComponents >= 3) {
if (semantic === SEMANTIC_POSITION) {
var m = transform.data;
var m0 = m[0];
var m1 = m[1];
var m2 = m[2];
var m4 = m[4];
var m5 = m[5];
var m6 = m[6];
var m8 = m[8];
var m9 = m[9];
var m10 = m[10];
var m12 = m[12];
var m13 = m[13];
var m14 = m[14];
var x = void 0, y = void 0, z = void 0;
for(var j = 0; j < totalComponents; j += stream.numComponents){
x = subarray[j];
y = subarray[j + 1];
z = subarray[j + 2];
subarray[j] = x * m0 + y * m4 + z * m8 + m12;
subarray[j + 1] = x * m1 + y * m5 + z * m9 + m13;
subarray[j + 2] = x * m2 + y * m6 + z * m10 + m14;
}
} else if (semantic === SEMANTIC_NORMAL || semantic === SEMANTIC_TANGENT) {
mat3.invertMat4(transform).transpose();
var [m01, m11, m21, m3, m41, m51, m61, m7, m81] = mat3.data;
var x1 = void 0, y1 = void 0, z1 = void 0;
for(var j1 = 0; j1 < totalComponents; j1 += stream.numComponents){
x1 = subarray[j1];
y1 = subarray[j1 + 1];
z1 = subarray[j1 + 2];
subarray[j1] = x1 * m01 + y1 * m3 + z1 * m61;
subarray[j1 + 1] = x1 * m11 + y1 * m41 + z1 * m7;
subarray[j1 + 2] = x1 * m21 + y1 * m51 + z1 * m81;
}
}
}
}
}
if (dynamic) {
stream = streams[SEMANTIC_BLENDINDICES];
for(var j2 = 0; j2 < numVerts; j2++){
stream.buffer[stream.count++] = i;
}
}
if (mesh.primitive[0].indexed) {
indexBase = mesh.primitive[0].base;
numIndices = mesh.primitive[0].count;
var srcFormat = mesh.indexBuffer[0].getFormat();
indexData = new typedArrayIndexFormats[srcFormat](mesh.indexBuffer[0].storage);
} else {
var primitiveType = mesh.primitive[0].type;
if (primitiveType === PRIMITIVE_TRIFAN || primitiveType === PRIMITIVE_TRISTRIP) {
if (mesh.primitive[0].count === 4) {
indexBase = 0;
numIndices = 6;
indexData = primitiveType === PRIMITIVE_TRIFAN ? _triFanIndices : _triStripIndices;
} else {
numIndices = 0;
continue;
}
}
}
for(var j3 = 0; j3 < numIndices; j3++){
indices[j3 + indexOffset] = indexData[indexBase + j3] + verticesOffset;
}
indexOffset += numIndices;
verticesOffset += numVerts;
}
mesh = new Mesh(this.device);
for(semantic in streams){
stream = streams[semantic];
mesh.setVertexStream(semantic, stream.buffer, stream.numComponents, undefined, stream.dataType, stream.normalize);
}
if (indices.length > 0) {
mesh.setIndices(indices);
}
mesh.update(PRIMITIVE_TRIANGLES, false);
if (dynamic) {
material = material.clone();
material.update();
}
var meshInstance = new MeshInstance(mesh, material, this.rootNode);
meshInstance.castShadow = batch.origMeshInstances[0].castShadow;
meshInstance.parameters = batch.origMeshInstances[0].parameters;
meshInstance.layer = batch.origMeshInstances[0].layer;
meshInstance._shaderDefs = batch.origMeshInstances[0]._shaderDefs;
meshInstance.batching = true;
meshInstance.cull = batch.origMeshInstances[0].cull;
var batchGroup = this._batchGroups[batchGroupId];
if (batchGroup && batchGroup._ui) {
meshInstance.cull = false;
}
if (dynamic) {
var nodes = [];
for(var i1 = 0; i1 < batch.origMeshInstances.length; i1++){
nodes.push(batch.origMeshInstances[i1].node);
}
meshInstance.skinInstance = new SkinBatchInstance(this.device, nodes, this.rootNode);
}
meshInstance._updateAabb = false;
meshInstance.drawOrder = batch.origMeshInstances[0].drawOrder;
meshInstance.stencilFront = batch.origMeshInstances[0].stencilFront;
meshInstance.stencilBack = batch.origMeshInstances[0].stencilBack;
meshInstance.flipFacesFactor = getScaleSign(batch.origMeshInstances[0]);
meshInstance.castShadow = batch.origMeshInstances[0].castShadow;
batch.meshInstance = meshInstance;
batch.updateBoundingBox();
}
return batch;
}
updateAll() {
if (this._dirtyGroups.length > 0) {
this.generate(this._dirtyGroups);
}
for(var i = 0; i < this._batchList.length; i++){
if (!this._batchList[i].dynamic) continue;
this._batchList[i].updateBoundingBox();
}
}
clone(batch, clonedMeshInstances) {
var batch2 = new Batch(clonedMeshInstances, batch.dynamic, batch.batchGroupId);
this._batchList.push(batch2);
var nodes = [];
for(var i = 0; i < clonedMeshInstances.length; i++){
nodes.push(clonedMeshInstances[i].node);
}
batch2.meshInstance = new MeshInstance(batch.meshInstance.mesh, batch.meshInstance.material, batch.meshInstance.node);
batch2.meshInstance._updateAabb = false;
batch2.meshInstance.parameters = clonedMeshInstances[0].parameters;
batch2.meshInstance.cull = clonedMeshInstances[0].cull;
batch2.meshInstance.layer = clonedMeshInstances[0].layer;
if (batch.dynamic) {
batch2.meshInstance.skinInstance = new SkinBatchInstance(this.device, nodes, this.rootNode);
}
batch2.meshInstance.castShadow = batch.meshInstance.castShadow;
return batch2;
}
destroyBatch(batch) {
batch.destroy(this.scene, this._batchGroups[batch.batchGroupId].layers);
}
constructor(device, root, scene){
this.device = device;
this.rootNode = root;
this.scene = scene;
this._init = false;
this._batchGroups = {};
this._batchGroupCounter = 0;
this._batchList = [];
this._dirtyGroups = [];
}
}
export { BatchManager };