'use strict'; var uid = require('../../../utils/data/uid.js'); var ViewableBuffer = require('../../../utils/data/ViewableBuffer.js'); var fastCopy = require('../../renderers/shared/buffer/utils/fastCopy.js'); var getAdjustedBlendModeBlend = require('../../renderers/shared/state/getAdjustedBlendModeBlend.js'); var maxRecommendedTextures = require('../gl/utils/maxRecommendedTextures.js'); var BatchTextureArray = require('./BatchTextureArray.js'); "use strict"; class Batch { constructor() { this.renderPipeId = "batch"; this.action = "startBatch"; // TODO - eventually this could be useful for flagging batches as dirty and then only rebuilding those ones // public elementStart = 0; // public elementSize = 0; // for drawing.. this.start = 0; this.size = 0; this.textures = new BatchTextureArray.BatchTextureArray(); this.blendMode = "normal"; this.canBundle = true; } destroy() { this.textures = null; this.gpuBindGroup = null; this.bindGroup = null; this.batcher = null; } } const batchPool = []; let batchPoolIndex = 0; function getBatchFromPool() { return batchPoolIndex > 0 ? batchPool[--batchPoolIndex] : new Batch(); } function returnBatchToPool(batch) { batchPool[batchPoolIndex++] = batch; } let BATCH_TICK = 0; const _Batcher = class _Batcher { constructor(options = {}) { /** unique id for this batcher */ this.uid = uid.uid("batcher"); /** Indicates whether the batch data has been modified and needs updating. */ this.dirty = true; /** The current index of the batch being processed. */ this.batchIndex = 0; /** An array of all batches created during the current rendering process. */ this.batches = []; this._elements = []; _Batcher.defaultOptions.maxTextures = _Batcher.defaultOptions.maxTextures ?? maxRecommendedTextures.getMaxTexturesPerBatch(); options = { ..._Batcher.defaultOptions, ...options }; const { maxTextures, attributesInitialSize, indicesInitialSize } = options; this.attributeBuffer = new ViewableBuffer.ViewableBuffer(attributesInitialSize * 4); this.indexBuffer = new Uint16Array(indicesInitialSize); this.maxTextures = maxTextures; } begin() { this.elementSize = 0; this.elementStart = 0; this.indexSize = 0; this.attributeSize = 0; for (let i = 0; i < this.batchIndex; i++) { returnBatchToPool(this.batches[i]); } this.batchIndex = 0; this._batchIndexStart = 0; this._batchIndexSize = 0; this.dirty = true; } add(batchableObject) { this._elements[this.elementSize++] = batchableObject; batchableObject._indexStart = this.indexSize; batchableObject._attributeStart = this.attributeSize; batchableObject._batcher = this; this.indexSize += batchableObject.indexSize; this.attributeSize += batchableObject.attributeSize * this.vertexSize; } checkAndUpdateTexture(batchableObject, texture) { const textureId = batchableObject._batch.textures.ids[texture._source.uid]; if (!textureId && textureId !== 0) return false; batchableObject._textureId = textureId; batchableObject.texture = texture; return true; } updateElement(batchableObject) { this.dirty = true; const attributeBuffer = this.attributeBuffer; if (batchableObject.packAsQuad) { this.packQuadAttributes( batchableObject, attributeBuffer.float32View, attributeBuffer.uint32View, batchableObject._attributeStart, batchableObject._textureId ); } else { this.packAttributes( batchableObject, attributeBuffer.float32View, attributeBuffer.uint32View, batchableObject._attributeStart, batchableObject._textureId ); } } /** * breaks the batcher. This happens when a batch gets too big, * or we need to switch to a different type of rendering (a filter for example) * @param instructionSet */ break(instructionSet) { const elements = this._elements; if (!elements[this.elementStart]) return; let batch = getBatchFromPool(); let textureBatch = batch.textures; textureBatch.clear(); const firstElement = elements[this.elementStart]; let blendMode = getAdjustedBlendModeBlend.getAdjustedBlendModeBlend(firstElement.blendMode, firstElement.texture._source); if (this.attributeSize * 4 > this.attributeBuffer.size) { this._resizeAttributeBuffer(this.attributeSize * 4); } if (this.indexSize > this.indexBuffer.length) { this._resizeIndexBuffer(this.indexSize); } const f32 = this.attributeBuffer.float32View; const u32 = this.attributeBuffer.uint32View; const indexBuffer = this.indexBuffer; let size = this._batchIndexSize; let start = this._batchIndexStart; let action = "startBatch"; const maxTextures = this.maxTextures; for (let i = this.elementStart; i < this.elementSize; ++i) { const element = elements[i]; elements[i] = null; const texture = element.texture; const source = texture._source; const adjustedBlendMode = getAdjustedBlendModeBlend.getAdjustedBlendModeBlend(element.blendMode, source); const breakRequired = blendMode !== adjustedBlendMode; if (source._batchTick === BATCH_TICK && !breakRequired) { element._textureId = source._textureBindLocation; size += element.indexSize; if (element.packAsQuad) { this.packQuadAttributes( element, f32, u32, element._attributeStart, element._textureId ); this.packQuadIndex( indexBuffer, element._indexStart, element._attributeStart / this.vertexSize ); } else { this.packAttributes( element, f32, u32, element._attributeStart, element._textureId ); this.packIndex( element, indexBuffer, element._indexStart, element._attributeStart / this.vertexSize ); } element._batch = batch; continue; } source._batchTick = BATCH_TICK; if (textureBatch.count >= maxTextures || breakRequired) { this._finishBatch( batch, start, size - start, textureBatch, blendMode, instructionSet, action ); action = "renderBatch"; start = size; blendMode = adjustedBlendMode; batch = getBatchFromPool(); textureBatch = batch.textures; textureBatch.clear(); ++BATCH_TICK; } element._textureId = source._textureBindLocation = textureBatch.count; textureBatch.ids[source.uid] = textureBatch.count; textureBatch.textures[textureBatch.count++] = source; element._batch = batch; size += element.indexSize; if (element.packAsQuad) { this.packQuadAttributes( element, f32, u32, element._attributeStart, element._textureId ); this.packQuadIndex( indexBuffer, element._indexStart, element._attributeStart / this.vertexSize ); } else { this.packAttributes( element, f32, u32, element._attributeStart, element._textureId ); this.packIndex( element, indexBuffer, element._indexStart, element._attributeStart / this.vertexSize ); } } if (textureBatch.count > 0) { this._finishBatch( batch, start, size - start, textureBatch, blendMode, instructionSet, action ); start = size; ++BATCH_TICK; } this.elementStart = this.elementSize; this._batchIndexStart = start; this._batchIndexSize = size; } _finishBatch(batch, indexStart, indexSize, textureBatch, blendMode, instructionSet, action) { batch.gpuBindGroup = null; batch.bindGroup = null; batch.action = action; batch.batcher = this; batch.textures = textureBatch; batch.blendMode = blendMode; batch.start = indexStart; batch.size = indexSize; ++BATCH_TICK; this.batches[this.batchIndex++] = batch; instructionSet.add(batch); } finish(instructionSet) { this.break(instructionSet); } /** * Resizes the attribute buffer to the given size (1 = 1 float32) * @param size - the size in vertices to ensure (not bytes!) */ ensureAttributeBuffer(size) { if (size * 4 <= this.attributeBuffer.size) return; this._resizeAttributeBuffer(size * 4); } /** * Resizes the index buffer to the given size (1 = 1 float32) * @param size - the size in vertices to ensure (not bytes!) */ ensureIndexBuffer(size) { if (size <= this.indexBuffer.length) return; this._resizeIndexBuffer(size); } _resizeAttributeBuffer(size) { const newSize = Math.max(size, this.attributeBuffer.size * 2); const newArrayBuffer = new ViewableBuffer.ViewableBuffer(newSize); fastCopy.fastCopy(this.attributeBuffer.rawBinaryData, newArrayBuffer.rawBinaryData); this.attributeBuffer = newArrayBuffer; } _resizeIndexBuffer(size) { const indexBuffer = this.indexBuffer; let newSize = Math.max(size, indexBuffer.length * 1.5); newSize += newSize % 2; const newIndexBuffer = newSize > 65535 ? new Uint32Array(newSize) : new Uint16Array(newSize); if (newIndexBuffer.BYTES_PER_ELEMENT !== indexBuffer.BYTES_PER_ELEMENT) { for (let i = 0; i < indexBuffer.length; i++) { newIndexBuffer[i] = indexBuffer[i]; } } else { fastCopy.fastCopy(indexBuffer.buffer, newIndexBuffer.buffer); } this.indexBuffer = newIndexBuffer; } packQuadIndex(indexBuffer, index, indicesOffset) { indexBuffer[index] = indicesOffset + 0; indexBuffer[index + 1] = indicesOffset + 1; indexBuffer[index + 2] = indicesOffset + 2; indexBuffer[index + 3] = indicesOffset + 0; indexBuffer[index + 4] = indicesOffset + 2; indexBuffer[index + 5] = indicesOffset + 3; } packIndex(element, indexBuffer, index, indicesOffset) { const indices = element.indices; const size = element.indexSize; const indexOffset = element.indexOffset; const attributeOffset = element.attributeOffset; for (let i = 0; i < size; i++) { indexBuffer[index++] = indicesOffset + indices[i + indexOffset] - attributeOffset; } } destroy() { for (let i = 0; i < this.batches.length; i++) { returnBatchToPool(this.batches[i]); } this.batches = null; for (let i = 0; i < this._elements.length; i++) { this._elements[i]._batch = null; } this._elements = null; this.indexBuffer = null; this.attributeBuffer.destroy(); this.attributeBuffer = null; } }; _Batcher.defaultOptions = { maxTextures: null, attributesInitialSize: 4, indicesInitialSize: 6 }; let Batcher = _Batcher; exports.Batch = Batch; exports.Batcher = Batcher; //# sourceMappingURL=Batcher.js.map