1 | import { Color } from "@pixi/color";
|
2 | import { ENV } from "@pixi/constants";
|
3 | import { ExtensionType, extensions } from "@pixi/extensions";
|
4 | import { settings } from "@pixi/settings";
|
5 | import { deprecation, premultiplyBlendMode, nextPow2, log2 } from "@pixi/utils";
|
6 | import { ViewableBuffer } from "../geometry/ViewableBuffer.mjs";
|
7 | import { checkMaxIfStatementsInShader } from "../shader/utils/checkMaxIfStatementsInShader.mjs";
|
8 | import { State } from "../state/State.mjs";
|
9 | import { BaseTexture } from "../textures/BaseTexture.mjs";
|
10 | import { BatchDrawCall } from "./BatchDrawCall.mjs";
|
11 | import { BatchGeometry } from "./BatchGeometry.mjs";
|
12 | import { BatchShaderGenerator } from "./BatchShaderGenerator.mjs";
|
13 | import { BatchTextureArray } from "./BatchTextureArray.mjs";
|
14 | import { canUploadSameBuffer } from "./canUploadSameBuffer.mjs";
|
15 | import { maxRecommendedTextures } from "./maxRecommendedTextures.mjs";
|
16 | import { ObjectRenderer } from "./ObjectRenderer.mjs";
|
17 | import defaultFragment from "./texture.frag.mjs";
|
18 | import defaultVertex from "./texture.vert.mjs";
|
19 | const _BatchRenderer = class _BatchRenderer2 extends ObjectRenderer {
|
20 | |
21 |
|
22 |
|
23 |
|
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 |
|
30 |
|
31 |
|
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 |
|
41 |
|
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 |
|
51 |
|
52 |
|
53 |
|
54 | get MAX_TEXTURES() {
|
55 | return deprecation("7.1.0", "BatchRenderer#MAX_TEXTURES renamed to BatchRenderer#maxTextures"), this.maxTextures;
|
56 | }
|
57 | |
58 |
|
59 |
|
60 |
|
61 | static get defaultVertexSrc() {
|
62 | return defaultVertex;
|
63 | }
|
64 | |
65 |
|
66 |
|
67 |
|
68 | static get defaultFragmentTemplate() {
|
69 | return defaultFragment;
|
70 | }
|
71 | |
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 | setShaderGenerator({
|
78 | vertex = _BatchRenderer2.defaultVertexSrc,
|
79 | fragment = _BatchRenderer2.defaultFragmentTemplate
|
80 | } = {}) {
|
81 | this.shaderGenerator = new BatchShaderGenerator(vertex, fragment);
|
82 | }
|
83 | |
84 |
|
85 |
|
86 |
|
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 |
|
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 |
|
115 | onPrerender() {
|
116 | this._flushId = 0;
|
117 | }
|
118 | |
119 |
|
120 |
|
121 |
|
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 |
|
144 |
|
145 |
|
146 |
|
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 |
|
165 |
|
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 |
|
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 |
|
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 |
|
198 | stop() {
|
199 | this.flush();
|
200 | }
|
201 |
|
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 |
|
209 |
|
210 |
|
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 |
|
220 |
|
221 |
|
222 |
|
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 |
|
232 |
|
233 |
|
234 |
|
235 |
|
236 |
|
237 |
|
238 |
|
239 |
|
240 |
|
241 |
|
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,
|
255 | _BatchRenderer.extension = {
|
256 | name: "batch",
|
257 | type: ExtensionType.RendererPlugin
|
258 | }, |
259 |
|
260 |
|
261 |
|
262 |
|
263 |
|
264 |
|
265 |
|
266 | _BatchRenderer._drawCallPool = [], |
267 |
|
268 |
|
269 |
|
270 |
|
271 |
|
272 |
|
273 |
|
274 | _BatchRenderer._textureArrayPool = [];
|
275 | let BatchRenderer = _BatchRenderer;
|
276 | extensions.add(BatchRenderer);
|
277 | export {
|
278 | BatchRenderer
|
279 | };
|
280 |
|