UNPKG

14.7 kBJavaScriptView Raw
1import { Color } from "@pixi/color";
2import { ENV } from "@pixi/constants";
3import { ExtensionType, extensions } from "@pixi/extensions";
4import { settings } from "@pixi/settings";
5import { deprecation, premultiplyBlendMode, nextPow2, log2 } from "@pixi/utils";
6import { ViewableBuffer } from "../geometry/ViewableBuffer.mjs";
7import { checkMaxIfStatementsInShader } from "../shader/utils/checkMaxIfStatementsInShader.mjs";
8import { State } from "../state/State.mjs";
9import { BaseTexture } from "../textures/BaseTexture.mjs";
10import { BatchDrawCall } from "./BatchDrawCall.mjs";
11import { BatchGeometry } from "./BatchGeometry.mjs";
12import { BatchShaderGenerator } from "./BatchShaderGenerator.mjs";
13import { BatchTextureArray } from "./BatchTextureArray.mjs";
14import { canUploadSameBuffer } from "./canUploadSameBuffer.mjs";
15import { maxRecommendedTextures } from "./maxRecommendedTextures.mjs";
16import { ObjectRenderer } from "./ObjectRenderer.mjs";
17import defaultFragment from "./texture.frag.mjs";
18import defaultVertex from "./texture.vert.mjs";
19const _BatchRenderer = class _BatchRenderer2 extends ObjectRenderer {
20 /**
21 * This will hook onto the renderer's `contextChange`
22 * and `prerender` signals.
23 * @param {PIXI.Renderer} renderer - The renderer this works for.
24 */
25 constructor(renderer) {
26 super(renderer), this.setShaderGenerator(), this.geometryClass = BatchGeometry, this.vertexSize = 6, this.state = State.for2d(), this.size = _BatchRenderer2.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 = [];
27 }
28 /**
29 * The maximum textures that this device supports.
30 * @static
31 * @default 32
32 */
33 static get defaultMaxTextures() {
34 return this._defaultMaxTextures = this._defaultMaxTextures ?? maxRecommendedTextures(32), this._defaultMaxTextures;
35 }
36 static set defaultMaxTextures(value) {
37 this._defaultMaxTextures = value;
38 }
39 /**
40 * Can we upload the same buffer in a single frame?
41 * @static
42 */
43 static get canUploadSameBuffer() {
44 return this._canUploadSameBuffer = this._canUploadSameBuffer ?? canUploadSameBuffer(), this._canUploadSameBuffer;
45 }
46 static set canUploadSameBuffer(value) {
47 this._canUploadSameBuffer = value;
48 }
49 /**
50 * @see PIXI.BatchRenderer#maxTextures
51 * @deprecated since 7.1.0
52 * @readonly
53 */
54 get MAX_TEXTURES() {
55 return deprecation("7.1.0", "BatchRenderer#MAX_TEXTURES renamed to BatchRenderer#maxTextures"), this.maxTextures;
56 }
57 /**
58 * The default vertex shader source
59 * @readonly
60 */
61 static get defaultVertexSrc() {
62 return defaultVertex;
63 }
64 /**
65 * The default fragment shader source
66 * @readonly
67 */
68 static get defaultFragmentTemplate() {
69 return defaultFragment;
70 }
71 /**
72 * Set the shader generator.
73 * @param {object} [options]
74 * @param {string} [options.vertex=PIXI.BatchRenderer.defaultVertexSrc] - Vertex shader source
75 * @param {string} [options.fragment=PIXI.BatchRenderer.defaultFragmentTemplate] - Fragment shader template
76 */
77 setShaderGenerator({
78 vertex = _BatchRenderer2.defaultVertexSrc,
79 fragment = _BatchRenderer2.defaultFragmentTemplate
80 } = {}) {
81 this.shaderGenerator = new BatchShaderGenerator(vertex, fragment);
82 }
83 /**
84 * Handles the `contextChange` signal.
85 *
86 * It calculates `this.maxTextures` and allocating the packed-geometry object pool.
87 */
88 contextChange() {
89 const gl = this.renderer.gl;
90 settings.PREFER_ENV === ENV.WEBGL_LEGACY ? this.maxTextures = 1 : (this.maxTextures = Math.min(
91 gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS),
92 _BatchRenderer2.defaultMaxTextures
93 ), this.maxTextures = checkMaxIfStatementsInShader(
94 this.maxTextures,
95 gl
96 )), this._shader = this.shaderGenerator.generateShader(this.maxTextures);
97 for (let i = 0; i < this._packedGeometryPoolSize; i++)
98 this._packedGeometries[i] = new this.geometryClass();
99 this.initFlushBuffers();
100 }
101 /** Makes sure that static and dynamic flush pooled objects have correct dimensions. */
102 initFlushBuffers() {
103 const {
104 _drawCallPool,
105 _textureArrayPool
106 } = _BatchRenderer2, MAX_SPRITES = this.size / 4, MAX_TA = Math.floor(MAX_SPRITES / this.maxTextures) + 1;
107 for (; _drawCallPool.length < MAX_SPRITES; )
108 _drawCallPool.push(new BatchDrawCall());
109 for (; _textureArrayPool.length < MAX_TA; )
110 _textureArrayPool.push(new BatchTextureArray());
111 for (let i = 0; i < this.maxTextures; i++)
112 this._tempBoundTextures[i] = null;
113 }
114 /** Handles the `prerender` signal. It ensures that flushes start from the first geometry object again. */
115 onPrerender() {
116 this._flushId = 0;
117 }
118 /**
119 * Buffers the "batchable" object. It need not be rendered immediately.
120 * @param {PIXI.DisplayObject} element - the element to render when
121 * using this renderer
122 */
123 render(element) {
124 element._texture.valid && (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);
125 }
126 buildTexturesAndDrawCalls() {
127 const {
128 _bufferedTextures: textures,
129 maxTextures
130 } = this, textureArrays = _BatchRenderer2._textureArrayPool, batch = this.renderer.batch, boundTextures = this._tempBoundTextures, touch = this.renderer.textureGC.count;
131 let TICK = ++BaseTexture._globalBatch, countTexArrays = 0, texArray = textureArrays[0], start = 0;
132 batch.copyBoundTextures(boundTextures, maxTextures);
133 for (let i = 0; i < this._bufferSize; ++i) {
134 const tex = textures[i];
135 textures[i] = null, tex._batchEnabled !== TICK && (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);
136 }
137 texArray.count > 0 && (batch.boundArray(texArray, boundTextures, TICK, maxTextures), this.buildDrawCalls(texArray, start, this._bufferSize), ++countTexArrays, ++TICK);
138 for (let i = 0; i < boundTextures.length; i++)
139 boundTextures[i] = null;
140 BaseTexture._globalBatch = TICK;
141 }
142 /**
143 * Populating drawcalls for rendering
144 * @param texArray
145 * @param start
146 * @param finish
147 */
148 buildDrawCalls(texArray, start, finish) {
149 const {
150 _bufferedElements: elements,
151 _attributeBuffer,
152 _indexBuffer,
153 vertexSize
154 } = this, drawCalls = _BatchRenderer2._drawCallPool;
155 let dcIndex = this._dcIndex, aIndex = this._aIndex, iIndex = this._iIndex, drawCall = drawCalls[dcIndex];
156 drawCall.start = this._iIndex, drawCall.texArray = texArray;
157 for (let i = start; i < finish; ++i) {
158 const sprite = elements[i], tex = sprite._texture.baseTexture, spriteBlendMode = premultiplyBlendMode[tex.alphaMode ? 1 : 0][sprite.blendMode];
159 elements[i] = null, 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;
160 }
161 start < finish && (drawCall.size = iIndex - drawCall.start, ++dcIndex), this._dcIndex = dcIndex, this._aIndex = aIndex, this._iIndex = iIndex;
162 }
163 /**
164 * Bind textures for current rendering
165 * @param texArray
166 */
167 bindAndClearTexArray(texArray) {
168 const textureSystem = this.renderer.texture;
169 for (let j = 0; j < texArray.count; j++)
170 textureSystem.bind(texArray.elements[j], texArray.ids[j]), texArray.elements[j] = null;
171 texArray.count = 0;
172 }
173 updateGeometry() {
174 const {
175 _packedGeometries: packedGeometries,
176 _attributeBuffer: attributeBuffer,
177 _indexBuffer: indexBuffer
178 } = this;
179 _BatchRenderer2.canUploadSameBuffer ? (packedGeometries[this._flushId]._buffer.update(attributeBuffer.rawBinaryData), packedGeometries[this._flushId]._indexBuffer.update(indexBuffer), this.renderer.geometry.updateBuffers()) : (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++);
180 }
181 drawBatches() {
182 const dcCount = this._dcIndex, { gl, state: stateSystem } = this.renderer, drawCalls = _BatchRenderer2._drawCallPool;
183 let curTexArray = null;
184 for (let i = 0; i < dcCount; i++) {
185 const { texArray, type, size, start, blend } = drawCalls[i];
186 curTexArray !== texArray && (curTexArray = texArray, this.bindAndClearTexArray(texArray)), this.state.blendMode = blend, stateSystem.set(this.state), gl.drawElements(type, size, gl.UNSIGNED_SHORT, start * 2);
187 }
188 }
189 /** Renders the content _now_ and empties the current batch. */
190 flush() {
191 this._vertexCount !== 0 && (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);
192 }
193 /** Starts a new sprite batch. */
194 start() {
195 this.renderer.state.set(this.state), this.renderer.texture.ensureSamplerType(this.maxTextures), this.renderer.shader.bind(this._shader), _BatchRenderer2.canUploadSameBuffer && this.renderer.geometry.bind(this._packedGeometries[this._flushId]);
196 }
197 /** Stops and flushes the current batch. */
198 stop() {
199 this.flush();
200 }
201 /** Destroys this `BatchRenderer`. It cannot be used again. */
202 destroy() {
203 for (let i = 0; i < this._packedGeometryPoolSize; i++)
204 this._packedGeometries[i] && this._packedGeometries[i].destroy();
205 this.renderer.off("prerender", this.onPrerender, this), this._aBuffers = null, this._iBuffers = null, this._packedGeometries = null, this._attributeBuffer = null, this._indexBuffer = null, this._shader && (this._shader.destroy(), this._shader = null), super.destroy();
206 }
207 /**
208 * Fetches an attribute buffer from `this._aBuffers` that can hold atleast `size` floats.
209 * @param size - minimum capacity required
210 * @returns - buffer than can hold atleast `size` floats
211 */
212 getAttributeBuffer(size) {
213 const roundedP2 = nextPow2(Math.ceil(size / 8)), roundedSizeIndex = log2(roundedP2), roundedSize = roundedP2 * 8;
214 this._aBuffers.length <= roundedSizeIndex && (this._iBuffers.length = roundedSizeIndex + 1);
215 let buffer = this._aBuffers[roundedSize];
216 return buffer || (this._aBuffers[roundedSize] = buffer = new ViewableBuffer(roundedSize * this.vertexSize * 4)), buffer;
217 }
218 /**
219 * Fetches an index buffer from `this._iBuffers` that can
220 * have at least `size` capacity.
221 * @param size - minimum required capacity
222 * @returns - buffer that can fit `size` indices.
223 */
224 getIndexBuffer(size) {
225 const roundedP2 = nextPow2(Math.ceil(size / 12)), roundedSizeIndex = log2(roundedP2), roundedSize = roundedP2 * 12;
226 this._iBuffers.length <= roundedSizeIndex && (this._iBuffers.length = roundedSizeIndex + 1);
227 let buffer = this._iBuffers[roundedSizeIndex];
228 return buffer || (this._iBuffers[roundedSizeIndex] = buffer = new Uint16Array(roundedSize)), buffer;
229 }
230 /**
231 * Takes the four batching parameters of `element`, interleaves
232 * and pushes them into the batching attribute/index buffers given.
233 *
234 * It uses these properties: `vertexData` `uvs`, `textureId` and
235 * `indicies`. It also uses the "tint" of the base-texture, if
236 * present.
237 * @param {PIXI.DisplayObject} element - element being rendered
238 * @param attributeBuffer - attribute buffer.
239 * @param indexBuffer - index buffer
240 * @param aIndex - number of floats already in the attribute buffer
241 * @param iIndex - number of indices already in `indexBuffer`
242 */
243 packInterleavedGeometry(element, attributeBuffer, indexBuffer, aIndex, iIndex) {
244 const {
245 uint32View,
246 float32View
247 } = attributeBuffer, packedVertices = aIndex / this.vertexSize, uvs = element.uvs, indicies = element.indices, vertexData = element.vertexData, textureId = element._texture.baseTexture._batchLocation, alpha = Math.min(element.worldAlpha, 1), argb = Color.shared.setValue(element._tintRGB).toPremultiplied(alpha, element._texture.baseTexture.alphaMode > 0);
248 for (let i = 0; i < vertexData.length; i += 2)
249 float32View[aIndex++] = vertexData[i], float32View[aIndex++] = vertexData[i + 1], float32View[aIndex++] = uvs[i], float32View[aIndex++] = uvs[i + 1], uint32View[aIndex++] = argb, float32View[aIndex++] = textureId;
250 for (let i = 0; i < indicies.length; i++)
251 indexBuffer[iIndex++] = packedVertices + indicies[i];
252 }
253};
254_BatchRenderer.defaultBatchSize = 4096, /** @ignore */
255_BatchRenderer.extension = {
256 name: "batch",
257 type: ExtensionType.RendererPlugin
258}, /**
259* Pool of `BatchDrawCall` objects that `flush` used
260* to create "batches" of the objects being rendered.
261*
262* These are never re-allocated again.
263* Shared between all batch renderers because it can be only one "flush" working at the moment.
264* @member {PIXI.BatchDrawCall[]}
265*/
266_BatchRenderer._drawCallPool = [], /**
267* Pool of `BatchDrawCall` objects that `flush` used
268* to create "batches" of the objects being rendered.
269*
270* These are never re-allocated again.
271* Shared between all batch renderers because it can be only one "flush" working at the moment.
272* @member {PIXI.BatchTextureArray[]}
273*/
274_BatchRenderer._textureArrayPool = [];
275let BatchRenderer = _BatchRenderer;
276extensions.add(BatchRenderer);
277export {
278 BatchRenderer
279};
280//# sourceMappingURL=BatchRenderer.mjs.map