UNPKG

@pixi/core

Version:
360 lines (357 loc) 12.4 kB
import { Color } from '@pixi/color'; import { ENV } from '@pixi/constants'; import { ExtensionType, extensions } from '@pixi/extensions'; import { settings } from '@pixi/settings'; import { deprecation, premultiplyBlendMode, nextPow2, log2 } from '@pixi/utils'; import { ViewableBuffer } from '../geometry/ViewableBuffer.mjs'; import { checkMaxIfStatementsInShader } from '../shader/utils/checkMaxIfStatementsInShader.mjs'; import { State } from '../state/State.mjs'; import { BaseTexture } from '../textures/BaseTexture.mjs'; import { BatchDrawCall } from './BatchDrawCall.mjs'; import { BatchGeometry } from './BatchGeometry.mjs'; import { BatchShaderGenerator } from './BatchShaderGenerator.mjs'; import { BatchTextureArray } from './BatchTextureArray.mjs'; import { canUploadSameBuffer } from './canUploadSameBuffer.mjs'; import { maxRecommendedTextures } from './maxRecommendedTextures.mjs'; import { ObjectRenderer } from './ObjectRenderer.mjs'; import defaultFragment from './texture.mjs'; import defaultVertex from './texture2.mjs'; const _BatchRenderer = class extends ObjectRenderer { constructor(renderer) { super(renderer); this.setShaderGenerator(); this.geometryClass = BatchGeometry; this.vertexSize = 6; this.state = State.for2d(); this.size = _BatchRenderer.defaultBatchSize * 4; this._vertexCount = 0; this._indexCount = 0; this._bufferedElements = []; this._bufferedTextures = []; this._bufferSize = 0; this._shader = null; this._packedGeometries = []; this._packedGeometryPoolSize = 2; this._flushId = 0; this._aBuffers = {}; this._iBuffers = {}; this.maxTextures = 1; this.renderer.on("prerender", this.onPrerender, this); renderer.runners.contextChange.add(this); this._dcIndex = 0; this._aIndex = 0; this._iIndex = 0; this._attributeBuffer = null; this._indexBuffer = null; this._tempBoundTextures = []; } static get defaultMaxTextures() { this._defaultMaxTextures = this._defaultMaxTextures ?? maxRecommendedTextures(32); return this._defaultMaxTextures; } static set defaultMaxTextures(value) { this._defaultMaxTextures = value; } static get canUploadSameBuffer() { this._canUploadSameBuffer = this._canUploadSameBuffer ?? canUploadSameBuffer(); return this._canUploadSameBuffer; } static set canUploadSameBuffer(value) { this._canUploadSameBuffer = value; } get MAX_TEXTURES() { deprecation("7.1.0", "BatchRenderer#MAX_TEXTURES renamed to BatchRenderer#maxTextures"); return this.maxTextures; } static get defaultVertexSrc() { return defaultVertex; } static get defaultFragmentTemplate() { return defaultFragment; } setShaderGenerator({ vertex = _BatchRenderer.defaultVertexSrc, fragment = _BatchRenderer.defaultFragmentTemplate } = {}) { this.shaderGenerator = new BatchShaderGenerator(vertex, fragment); } contextChange() { const gl = this.renderer.gl; if (settings.PREFER_ENV === ENV.WEBGL_LEGACY) { this.maxTextures = 1; } else { this.maxTextures = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), _BatchRenderer.defaultMaxTextures); this.maxTextures = checkMaxIfStatementsInShader(this.maxTextures, gl); } this._shader = this.shaderGenerator.generateShader(this.maxTextures); for (let i = 0; i < this._packedGeometryPoolSize; i++) { this._packedGeometries[i] = new this.geometryClass(); } this.initFlushBuffers(); } initFlushBuffers() { const { _drawCallPool, _textureArrayPool } = _BatchRenderer; const MAX_SPRITES = this.size / 4; const MAX_TA = Math.floor(MAX_SPRITES / this.maxTextures) + 1; while (_drawCallPool.length < MAX_SPRITES) { _drawCallPool.push(new BatchDrawCall()); } while (_textureArrayPool.length < MAX_TA) { _textureArrayPool.push(new BatchTextureArray()); } for (let i = 0; i < this.maxTextures; i++) { this._tempBoundTextures[i] = null; } } onPrerender() { this._flushId = 0; } render(element) { if (!element._texture.valid) { return; } if (this._vertexCount + element.vertexData.length / 2 > this.size) { this.flush(); } this._vertexCount += element.vertexData.length / 2; this._indexCount += element.indices.length; this._bufferedTextures[this._bufferSize] = element._texture.baseTexture; this._bufferedElements[this._bufferSize++] = element; } buildTexturesAndDrawCalls() { const { _bufferedTextures: textures, maxTextures } = this; const textureArrays = _BatchRenderer._textureArrayPool; const batch = this.renderer.batch; const boundTextures = this._tempBoundTextures; const touch = this.renderer.textureGC.count; let TICK = ++BaseTexture._globalBatch; let countTexArrays = 0; let texArray = textureArrays[0]; let start = 0; batch.copyBoundTextures(boundTextures, maxTextures); for (let i = 0; i < this._bufferSize; ++i) { const tex = textures[i]; textures[i] = null; if (tex._batchEnabled === TICK) { continue; } if (texArray.count >= maxTextures) { batch.boundArray(texArray, boundTextures, TICK, maxTextures); this.buildDrawCalls(texArray, start, i); start = i; texArray = textureArrays[++countTexArrays]; ++TICK; } tex._batchEnabled = TICK; tex.touched = touch; texArray.elements[texArray.count++] = tex; } if (texArray.count > 0) { batch.boundArray(texArray, boundTextures, TICK, maxTextures); this.buildDrawCalls(texArray, start, this._bufferSize); ++countTexArrays; ++TICK; } for (let i = 0; i < boundTextures.length; i++) { boundTextures[i] = null; } BaseTexture._globalBatch = TICK; } buildDrawCalls(texArray, start, finish) { const { _bufferedElements: elements, _attributeBuffer, _indexBuffer, vertexSize } = this; const drawCalls = _BatchRenderer._drawCallPool; let dcIndex = this._dcIndex; let aIndex = this._aIndex; let iIndex = this._iIndex; let drawCall = drawCalls[dcIndex]; drawCall.start = this._iIndex; drawCall.texArray = texArray; for (let i = start; i < finish; ++i) { const sprite = elements[i]; const tex = sprite._texture.baseTexture; const spriteBlendMode = premultiplyBlendMode[tex.alphaMode ? 1 : 0][sprite.blendMode]; elements[i] = null; if (start < i && drawCall.blend !== spriteBlendMode) { drawCall.size = iIndex - drawCall.start; start = i; drawCall = drawCalls[++dcIndex]; drawCall.texArray = texArray; drawCall.start = iIndex; } this.packInterleavedGeometry(sprite, _attributeBuffer, _indexBuffer, aIndex, iIndex); aIndex += sprite.vertexData.length / 2 * vertexSize; iIndex += sprite.indices.length; drawCall.blend = spriteBlendMode; } if (start < finish) { drawCall.size = iIndex - drawCall.start; ++dcIndex; } this._dcIndex = dcIndex; this._aIndex = aIndex; this._iIndex = iIndex; } bindAndClearTexArray(texArray) { const textureSystem = this.renderer.texture; for (let j = 0; j < texArray.count; j++) { textureSystem.bind(texArray.elements[j], texArray.ids[j]); texArray.elements[j] = null; } texArray.count = 0; } updateGeometry() { const { _packedGeometries: packedGeometries, _attributeBuffer: attributeBuffer, _indexBuffer: indexBuffer } = this; if (!_BatchRenderer.canUploadSameBuffer) { if (this._packedGeometryPoolSize <= this._flushId) { this._packedGeometryPoolSize++; packedGeometries[this._flushId] = new this.geometryClass(); } packedGeometries[this._flushId]._buffer.update(attributeBuffer.rawBinaryData); packedGeometries[this._flushId]._indexBuffer.update(indexBuffer); this.renderer.geometry.bind(packedGeometries[this._flushId]); this.renderer.geometry.updateBuffers(); this._flushId++; } else { packedGeometries[this._flushId]._buffer.update(attributeBuffer.rawBinaryData); packedGeometries[this._flushId]._indexBuffer.update(indexBuffer); this.renderer.geometry.updateBuffers(); } } drawBatches() { const dcCount = this._dcIndex; const { gl, state: stateSystem } = this.renderer; const drawCalls = _BatchRenderer._drawCallPool; let curTexArray = null; for (let i = 0; i < dcCount; i++) { const { texArray, type, size, start, blend } = drawCalls[i]; if (curTexArray !== texArray) { curTexArray = texArray; this.bindAndClearTexArray(texArray); } this.state.blendMode = blend; stateSystem.set(this.state); gl.drawElements(type, size, gl.UNSIGNED_SHORT, start * 2); } } flush() { if (this._vertexCount === 0) { return; } this._attributeBuffer = this.getAttributeBuffer(this._vertexCount); this._indexBuffer = this.getIndexBuffer(this._indexCount); this._aIndex = 0; this._iIndex = 0; this._dcIndex = 0; this.buildTexturesAndDrawCalls(); this.updateGeometry(); this.drawBatches(); this._bufferSize = 0; this._vertexCount = 0; this._indexCount = 0; } start() { this.renderer.state.set(this.state); this.renderer.texture.ensureSamplerType(this.maxTextures); this.renderer.shader.bind(this._shader); if (_BatchRenderer.canUploadSameBuffer) { this.renderer.geometry.bind(this._packedGeometries[this._flushId]); } } stop() { this.flush(); } destroy() { for (let i = 0; i < this._packedGeometryPoolSize; i++) { if (this._packedGeometries[i]) { this._packedGeometries[i].destroy(); } } this.renderer.off("prerender", this.onPrerender, this); this._aBuffers = null; this._iBuffers = null; this._packedGeometries = null; this._attributeBuffer = null; this._indexBuffer = null; if (this._shader) { this._shader.destroy(); this._shader = null; } super.destroy(); } getAttributeBuffer(size) { const roundedP2 = nextPow2(Math.ceil(size / 8)); const roundedSizeIndex = log2(roundedP2); const roundedSize = roundedP2 * 8; if (this._aBuffers.length <= roundedSizeIndex) { this._iBuffers.length = roundedSizeIndex + 1; } let buffer = this._aBuffers[roundedSize]; if (!buffer) { this._aBuffers[roundedSize] = buffer = new ViewableBuffer(roundedSize * this.vertexSize * 4); } return buffer; } getIndexBuffer(size) { const roundedP2 = nextPow2(Math.ceil(size / 12)); const roundedSizeIndex = log2(roundedP2); const roundedSize = roundedP2 * 12; if (this._iBuffers.length <= roundedSizeIndex) { this._iBuffers.length = roundedSizeIndex + 1; } let buffer = this._iBuffers[roundedSizeIndex]; if (!buffer) { this._iBuffers[roundedSizeIndex] = buffer = new Uint16Array(roundedSize); } return buffer; } packInterleavedGeometry(element, attributeBuffer, indexBuffer, aIndex, iIndex) { const { uint32View, float32View } = attributeBuffer; const packedVertices = aIndex / this.vertexSize; const uvs = element.uvs; const indicies = element.indices; const vertexData = element.vertexData; const textureId = element._texture.baseTexture._batchLocation; const alpha = Math.min(element.worldAlpha, 1); const argb = Color.shared.setValue(element._tintRGB).toPremultiplied(alpha); for (let i = 0; i < vertexData.length; i += 2) { float32View[aIndex++] = vertexData[i]; float32View[aIndex++] = vertexData[i + 1]; float32View[aIndex++] = uvs[i]; float32View[aIndex++] = uvs[i + 1]; uint32View[aIndex++] = argb; float32View[aIndex++] = textureId; } for (let i = 0; i < indicies.length; i++) { indexBuffer[iIndex++] = packedVertices + indicies[i]; } } }; let BatchRenderer = _BatchRenderer; BatchRenderer.defaultBatchSize = 4096; BatchRenderer.extension = { name: "batch", type: ExtensionType.RendererPlugin }; BatchRenderer._drawCallPool = []; BatchRenderer._textureArrayPool = []; extensions.add(BatchRenderer); export { BatchRenderer }; //# sourceMappingURL=BatchRenderer.mjs.map