UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

604 lines (601 loc) 21.8 kB
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'; const _triFanIndices = [ 0, 1, 3, 2, 3, 1 ]; const _triStripIndices = [ 0, 1, 3, 0, 3, 2 ]; const 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(let i = 0; i < a.length; i++){ if (a[i] !== b[i]) return false; } return true; } return false; } function equalParamSets(params1, params2) { for(const param in params1){ if (params1.hasOwnProperty(param) && !paramsIdentical(params1[param], params2[param])) { return false; } } for(const param in params2){ if (params2.hasOwnProperty(param) && !paramsIdentical(params2[param], params1[param])) { return false; } } return true; } function getScaleSign(mi) { return mi.node.worldTransform.scaleSign; } class BatchManager { 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 = []; } 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; } const group = new BatchGroup(id, name, dynamic, maxAabbSize, layers); this._batchGroups[id] = group; return group; } removeGroup(id) { if (!this._batchGroups[id]) { return; } const newBatchList = []; for(let 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) { const groups = this._batchGroups; for(const group in groups){ if (!groups.hasOwnProperty(group)) continue; if (groups[group].name === name) { return groups[group]; } } return null; } getBatches(batchGroupId) { const results = []; const len = this._batchList.length; for(let i = 0; i < len; i++){ const 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(let i = 0; i < node._children.length; i++){ this._removeModelsFromBatchGroup(node._children[i], id); } } insert(type, groupId, node) { const 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) { const group = this._batchGroups[groupId]; if (group) { const 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; let 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(let g = 0; g < groupIds.length; g++){ const id = groupIds[g]; const group = this._batchGroups[id]; if (!group) continue; let arr = groupMeshInstances[id]; if (!arr) arr = groupMeshInstances[id] = []; for(let m = 0; m < group._obj.model.length; m++){ arr = this._extractModel(group._obj.model[m], arr, group, groupMeshInstances); } for(let r = 0; r < group._obj.render.length; r++){ arr = this._extractRender(group._obj.render[r], arr, group, groupMeshInstances); } for(let e = 0; e < group._obj.element.length; e++){ this._extractElement(group._obj.element[e], arr, group); } for(let s = 0; s < group._obj.sprite.length; s++){ const 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) { const groupMeshInstances = {}; if (!groupIds) { groupIds = Object.keys(this._batchGroups); } const newBatchList = []; for(let 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 { const newDirtyGroups = []; for(let i = 0; i < this._dirtyGroups.length; i++){ if (groupIds.indexOf(this._dirtyGroups[i]) < 0) newDirtyGroups.push(this._dirtyGroups[i]); } this._dirtyGroups = newDirtyGroups; } let group, lists, groupData, batch; for(const 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(let i = 0; i < lists.length; i++){ batch = this.create(lists[i], groupData.dynamic, parseInt(groupId, 10)); if (batch) { batch.addToLayers(this.scene, groupData.layers); } } } } prepare(meshInstances, dynamic, maxAabbSize = Number.POSITIVE_INFINITY, translucent) { if (meshInstances.length === 0) return []; const halfMaxAabbSize = maxAabbSize * 0.5; const maxInstanceCount = 1024; const maxNumVertices = 0xffffffff; const aabb = new BoundingBox(); const testAabb = new BoundingBox(); let skipTranslucentAabb = null; let sf; const lists = []; let j = 0; if (translucent) { meshInstances.sort((a, b)=>{ return a.drawOrder - b.drawOrder; }); } let meshInstancesLeftA = meshInstances; let meshInstancesLeftB; const skipMesh = translucent ? function(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 = []; const material = meshInstancesLeftA[0].material; const layer = meshInstancesLeftA[0].layer; const defs = meshInstancesLeftA[0]._shaderDefs; const params = meshInstancesLeftA[0].parameters; const stencil = meshInstancesLeftA[0].stencilFront; let vertCount = meshInstancesLeftA[0].mesh.vertexBuffer.getNumVertices(); const drawOrder = meshInstancesLeftA[0].drawOrder; aabb.copy(meshInstancesLeftA[0].aabb); const scaleSign = getScaleSign(meshInstancesLeftA[0]); const vertexFormatBatchingHash = meshInstancesLeftA[0].mesh.vertexBuffer.format.batchingHash; const indexed = meshInstancesLeftA[0].mesh.primitive[0].indexed; skipTranslucentAabb = null; for(let i = 1; i < meshInstancesLeftA.length; i++){ const 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) { let streams = null; let batchNumVerts = 0; let batchNumIndices = 0; let material = null; for(let i = 0; i < meshInstances.length; i++){ if (meshInstances[i].visible) { const mesh = meshInstances[i].mesh; const numVerts = mesh.vertexBuffer.numVertices; batchNumVerts += numVerts; if (mesh.primitive[0].indexed) { batchNumIndices += mesh.primitive[0].count; } else { const 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 = {}; const elems = mesh.vertexBuffer.format.elements; for(let j = 0; j < elems.length; j++){ const 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; } let stream = null; let semantic; let mesh, numVerts; let batch = null; const batchData = this.collectBatchedMeshData(meshInstances, dynamic); if (batchData.streams) { const streams = batchData.streams; let material = batchData.material; const batchNumVerts = batchData.batchNumVerts; const batchNumIndices = batchData.batchNumIndices; batch = new Batch(meshInstances, dynamic, batchGroupId); this._batchList.push(batch); let indexBase, indexBaseVertex, numIndices, indexData; let verticesOffset = 0; let indexOffset = 0; let transform; const indexArrayType = batchNumVerts <= 0xffff ? Uint16Array : Uint32Array; const 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(let 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]; const subarray = new stream.typeArrayType(stream.buffer.buffer, stream.elementByteSize * stream.count); const totalComponents = mesh.getVertexStream(semantic, subarray) * stream.numComponents; stream.count += totalComponents; if (!dynamic && stream.numComponents >= 3) { if (semantic === SEMANTIC_POSITION) { const m = transform.data; const m0 = m[0]; const m1 = m[1]; const m2 = m[2]; const m4 = m[4]; const m5 = m[5]; const m6 = m[6]; const m8 = m[8]; const m9 = m[9]; const m10 = m[10]; const m12 = m[12]; const m13 = m[13]; const m14 = m[14]; let x, y, z; for(let 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(); const [m0, m1, m2, m3, m4, m5, m6, m7, m8] = mat3.data; let x, y, z; for(let j = 0; j < totalComponents; j += stream.numComponents){ x = subarray[j]; y = subarray[j + 1]; z = subarray[j + 2]; subarray[j] = x * m0 + y * m3 + z * m6; subarray[j + 1] = x * m1 + y * m4 + z * m7; subarray[j + 2] = x * m2 + y * m5 + z * m8; } } } } } if (dynamic) { stream = streams[SEMANTIC_BLENDINDICES]; for(let j = 0; j < numVerts; j++){ stream.buffer[stream.count++] = i; } } if (mesh.primitive[0].indexed) { indexBase = mesh.primitive[0].base; indexBaseVertex = mesh.primitive[0].baseVertex || 0; numIndices = mesh.primitive[0].count; const srcFormat = mesh.indexBuffer[0].getFormat(); indexData = new typedArrayIndexFormats[srcFormat](mesh.indexBuffer[0].storage); } else { indexBaseVertex = 0; const 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(let j = 0; j < numIndices; j++){ indices[j + indexOffset] = indexData[indexBase + j] + indexBaseVertex + 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(); } const 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; const batchGroup = this._batchGroups[batchGroupId]; if (batchGroup && batchGroup._ui) { meshInstance.cull = false; } if (dynamic) { const nodes = []; for(let i = 0; i < batch.origMeshInstances.length; i++){ nodes.push(batch.origMeshInstances[i].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(let i = 0; i < this._batchList.length; i++){ if (!this._batchList[i].dynamic) continue; this._batchList[i].updateBoundingBox(); } } clone(batch, clonedMeshInstances) { const batch2 = new Batch(clonedMeshInstances, batch.dynamic, batch.batchGroupId); this._batchList.push(batch2); const nodes = []; for(let 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); } } export { BatchManager };