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.mjs';
|
18 | import defaultVertex from './texture2.mjs';
|
19 |
|
20 | const _BatchRenderer = class extends ObjectRenderer {
|
21 | constructor(renderer) {
|
22 | super(renderer);
|
23 | this.setShaderGenerator();
|
24 | this.geometryClass = BatchGeometry;
|
25 | this.vertexSize = 6;
|
26 | this.state = State.for2d();
|
27 | this.size = _BatchRenderer.defaultBatchSize * 4;
|
28 | this._vertexCount = 0;
|
29 | this._indexCount = 0;
|
30 | this._bufferedElements = [];
|
31 | this._bufferedTextures = [];
|
32 | this._bufferSize = 0;
|
33 | this._shader = null;
|
34 | this._packedGeometries = [];
|
35 | this._packedGeometryPoolSize = 2;
|
36 | this._flushId = 0;
|
37 | this._aBuffers = {};
|
38 | this._iBuffers = {};
|
39 | this.maxTextures = 1;
|
40 | this.renderer.on("prerender", this.onPrerender, this);
|
41 | renderer.runners.contextChange.add(this);
|
42 | this._dcIndex = 0;
|
43 | this._aIndex = 0;
|
44 | this._iIndex = 0;
|
45 | this._attributeBuffer = null;
|
46 | this._indexBuffer = null;
|
47 | this._tempBoundTextures = [];
|
48 | }
|
49 | static get defaultMaxTextures() {
|
50 | this._defaultMaxTextures = this._defaultMaxTextures ?? maxRecommendedTextures(32);
|
51 | return this._defaultMaxTextures;
|
52 | }
|
53 | static set defaultMaxTextures(value) {
|
54 | this._defaultMaxTextures = value;
|
55 | }
|
56 | static get canUploadSameBuffer() {
|
57 | this._canUploadSameBuffer = this._canUploadSameBuffer ?? canUploadSameBuffer();
|
58 | return this._canUploadSameBuffer;
|
59 | }
|
60 | static set canUploadSameBuffer(value) {
|
61 | this._canUploadSameBuffer = value;
|
62 | }
|
63 | get MAX_TEXTURES() {
|
64 | deprecation("7.1.0", "BatchRenderer#MAX_TEXTURES renamed to BatchRenderer#maxTextures");
|
65 | return this.maxTextures;
|
66 | }
|
67 | static get defaultVertexSrc() {
|
68 | return defaultVertex;
|
69 | }
|
70 | static get defaultFragmentTemplate() {
|
71 | return defaultFragment;
|
72 | }
|
73 | setShaderGenerator({
|
74 | vertex = _BatchRenderer.defaultVertexSrc,
|
75 | fragment = _BatchRenderer.defaultFragmentTemplate
|
76 | } = {}) {
|
77 | this.shaderGenerator = new BatchShaderGenerator(vertex, fragment);
|
78 | }
|
79 | contextChange() {
|
80 | const gl = this.renderer.gl;
|
81 | if (settings.PREFER_ENV === ENV.WEBGL_LEGACY) {
|
82 | this.maxTextures = 1;
|
83 | } else {
|
84 | this.maxTextures = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), _BatchRenderer.defaultMaxTextures);
|
85 | this.maxTextures = checkMaxIfStatementsInShader(this.maxTextures, gl);
|
86 | }
|
87 | this._shader = this.shaderGenerator.generateShader(this.maxTextures);
|
88 | for (let i = 0; i < this._packedGeometryPoolSize; i++) {
|
89 | this._packedGeometries[i] = new this.geometryClass();
|
90 | }
|
91 | this.initFlushBuffers();
|
92 | }
|
93 | initFlushBuffers() {
|
94 | const {
|
95 | _drawCallPool,
|
96 | _textureArrayPool
|
97 | } = _BatchRenderer;
|
98 | const MAX_SPRITES = this.size / 4;
|
99 | const MAX_TA = Math.floor(MAX_SPRITES / this.maxTextures) + 1;
|
100 | while (_drawCallPool.length < MAX_SPRITES) {
|
101 | _drawCallPool.push(new BatchDrawCall());
|
102 | }
|
103 | while (_textureArrayPool.length < MAX_TA) {
|
104 | _textureArrayPool.push(new BatchTextureArray());
|
105 | }
|
106 | for (let i = 0; i < this.maxTextures; i++) {
|
107 | this._tempBoundTextures[i] = null;
|
108 | }
|
109 | }
|
110 | onPrerender() {
|
111 | this._flushId = 0;
|
112 | }
|
113 | render(element) {
|
114 | if (!element._texture.valid) {
|
115 | return;
|
116 | }
|
117 | if (this._vertexCount + element.vertexData.length / 2 > this.size) {
|
118 | this.flush();
|
119 | }
|
120 | this._vertexCount += element.vertexData.length / 2;
|
121 | this._indexCount += element.indices.length;
|
122 | this._bufferedTextures[this._bufferSize] = element._texture.baseTexture;
|
123 | this._bufferedElements[this._bufferSize++] = element;
|
124 | }
|
125 | buildTexturesAndDrawCalls() {
|
126 | const {
|
127 | _bufferedTextures: textures,
|
128 | maxTextures
|
129 | } = this;
|
130 | const textureArrays = _BatchRenderer._textureArrayPool;
|
131 | const batch = this.renderer.batch;
|
132 | const boundTextures = this._tempBoundTextures;
|
133 | const touch = this.renderer.textureGC.count;
|
134 | let TICK = ++BaseTexture._globalBatch;
|
135 | let countTexArrays = 0;
|
136 | let texArray = textureArrays[0];
|
137 | let start = 0;
|
138 | batch.copyBoundTextures(boundTextures, maxTextures);
|
139 | for (let i = 0; i < this._bufferSize; ++i) {
|
140 | const tex = textures[i];
|
141 | textures[i] = null;
|
142 | if (tex._batchEnabled === TICK) {
|
143 | continue;
|
144 | }
|
145 | if (texArray.count >= maxTextures) {
|
146 | batch.boundArray(texArray, boundTextures, TICK, maxTextures);
|
147 | this.buildDrawCalls(texArray, start, i);
|
148 | start = i;
|
149 | texArray = textureArrays[++countTexArrays];
|
150 | ++TICK;
|
151 | }
|
152 | tex._batchEnabled = TICK;
|
153 | tex.touched = touch;
|
154 | texArray.elements[texArray.count++] = tex;
|
155 | }
|
156 | if (texArray.count > 0) {
|
157 | batch.boundArray(texArray, boundTextures, TICK, maxTextures);
|
158 | this.buildDrawCalls(texArray, start, this._bufferSize);
|
159 | ++countTexArrays;
|
160 | ++TICK;
|
161 | }
|
162 | for (let i = 0; i < boundTextures.length; i++) {
|
163 | boundTextures[i] = null;
|
164 | }
|
165 | BaseTexture._globalBatch = TICK;
|
166 | }
|
167 | buildDrawCalls(texArray, start, finish) {
|
168 | const {
|
169 | _bufferedElements: elements,
|
170 | _attributeBuffer,
|
171 | _indexBuffer,
|
172 | vertexSize
|
173 | } = this;
|
174 | const drawCalls = _BatchRenderer._drawCallPool;
|
175 | let dcIndex = this._dcIndex;
|
176 | let aIndex = this._aIndex;
|
177 | let iIndex = this._iIndex;
|
178 | let drawCall = drawCalls[dcIndex];
|
179 | drawCall.start = this._iIndex;
|
180 | drawCall.texArray = texArray;
|
181 | for (let i = start; i < finish; ++i) {
|
182 | const sprite = elements[i];
|
183 | const tex = sprite._texture.baseTexture;
|
184 | const spriteBlendMode = premultiplyBlendMode[tex.alphaMode ? 1 : 0][sprite.blendMode];
|
185 | elements[i] = null;
|
186 | if (start < i && drawCall.blend !== spriteBlendMode) {
|
187 | drawCall.size = iIndex - drawCall.start;
|
188 | start = i;
|
189 | drawCall = drawCalls[++dcIndex];
|
190 | drawCall.texArray = texArray;
|
191 | drawCall.start = iIndex;
|
192 | }
|
193 | this.packInterleavedGeometry(sprite, _attributeBuffer, _indexBuffer, aIndex, iIndex);
|
194 | aIndex += sprite.vertexData.length / 2 * vertexSize;
|
195 | iIndex += sprite.indices.length;
|
196 | drawCall.blend = spriteBlendMode;
|
197 | }
|
198 | if (start < finish) {
|
199 | drawCall.size = iIndex - drawCall.start;
|
200 | ++dcIndex;
|
201 | }
|
202 | this._dcIndex = dcIndex;
|
203 | this._aIndex = aIndex;
|
204 | this._iIndex = iIndex;
|
205 | }
|
206 | bindAndClearTexArray(texArray) {
|
207 | const textureSystem = this.renderer.texture;
|
208 | for (let j = 0; j < texArray.count; j++) {
|
209 | textureSystem.bind(texArray.elements[j], texArray.ids[j]);
|
210 | texArray.elements[j] = null;
|
211 | }
|
212 | texArray.count = 0;
|
213 | }
|
214 | updateGeometry() {
|
215 | const {
|
216 | _packedGeometries: packedGeometries,
|
217 | _attributeBuffer: attributeBuffer,
|
218 | _indexBuffer: indexBuffer
|
219 | } = this;
|
220 | if (!_BatchRenderer.canUploadSameBuffer) {
|
221 | if (this._packedGeometryPoolSize <= this._flushId) {
|
222 | this._packedGeometryPoolSize++;
|
223 | packedGeometries[this._flushId] = new this.geometryClass();
|
224 | }
|
225 | packedGeometries[this._flushId]._buffer.update(attributeBuffer.rawBinaryData);
|
226 | packedGeometries[this._flushId]._indexBuffer.update(indexBuffer);
|
227 | this.renderer.geometry.bind(packedGeometries[this._flushId]);
|
228 | this.renderer.geometry.updateBuffers();
|
229 | this._flushId++;
|
230 | } else {
|
231 | packedGeometries[this._flushId]._buffer.update(attributeBuffer.rawBinaryData);
|
232 | packedGeometries[this._flushId]._indexBuffer.update(indexBuffer);
|
233 | this.renderer.geometry.updateBuffers();
|
234 | }
|
235 | }
|
236 | drawBatches() {
|
237 | const dcCount = this._dcIndex;
|
238 | const { gl, state: stateSystem } = this.renderer;
|
239 | const drawCalls = _BatchRenderer._drawCallPool;
|
240 | let curTexArray = null;
|
241 | for (let i = 0; i < dcCount; i++) {
|
242 | const { texArray, type, size, start, blend } = drawCalls[i];
|
243 | if (curTexArray !== texArray) {
|
244 | curTexArray = texArray;
|
245 | this.bindAndClearTexArray(texArray);
|
246 | }
|
247 | this.state.blendMode = blend;
|
248 | stateSystem.set(this.state);
|
249 | gl.drawElements(type, size, gl.UNSIGNED_SHORT, start * 2);
|
250 | }
|
251 | }
|
252 | flush() {
|
253 | if (this._vertexCount === 0) {
|
254 | return;
|
255 | }
|
256 | this._attributeBuffer = this.getAttributeBuffer(this._vertexCount);
|
257 | this._indexBuffer = this.getIndexBuffer(this._indexCount);
|
258 | this._aIndex = 0;
|
259 | this._iIndex = 0;
|
260 | this._dcIndex = 0;
|
261 | this.buildTexturesAndDrawCalls();
|
262 | this.updateGeometry();
|
263 | this.drawBatches();
|
264 | this._bufferSize = 0;
|
265 | this._vertexCount = 0;
|
266 | this._indexCount = 0;
|
267 | }
|
268 | start() {
|
269 | this.renderer.state.set(this.state);
|
270 | this.renderer.texture.ensureSamplerType(this.maxTextures);
|
271 | this.renderer.shader.bind(this._shader);
|
272 | if (_BatchRenderer.canUploadSameBuffer) {
|
273 | this.renderer.geometry.bind(this._packedGeometries[this._flushId]);
|
274 | }
|
275 | }
|
276 | stop() {
|
277 | this.flush();
|
278 | }
|
279 | destroy() {
|
280 | for (let i = 0; i < this._packedGeometryPoolSize; i++) {
|
281 | if (this._packedGeometries[i]) {
|
282 | this._packedGeometries[i].destroy();
|
283 | }
|
284 | }
|
285 | this.renderer.off("prerender", this.onPrerender, this);
|
286 | this._aBuffers = null;
|
287 | this._iBuffers = null;
|
288 | this._packedGeometries = null;
|
289 | this._attributeBuffer = null;
|
290 | this._indexBuffer = null;
|
291 | if (this._shader) {
|
292 | this._shader.destroy();
|
293 | this._shader = null;
|
294 | }
|
295 | super.destroy();
|
296 | }
|
297 | getAttributeBuffer(size) {
|
298 | const roundedP2 = nextPow2(Math.ceil(size / 8));
|
299 | const roundedSizeIndex = log2(roundedP2);
|
300 | const roundedSize = roundedP2 * 8;
|
301 | if (this._aBuffers.length <= roundedSizeIndex) {
|
302 | this._iBuffers.length = roundedSizeIndex + 1;
|
303 | }
|
304 | let buffer = this._aBuffers[roundedSize];
|
305 | if (!buffer) {
|
306 | this._aBuffers[roundedSize] = buffer = new ViewableBuffer(roundedSize * this.vertexSize * 4);
|
307 | }
|
308 | return buffer;
|
309 | }
|
310 | getIndexBuffer(size) {
|
311 | const roundedP2 = nextPow2(Math.ceil(size / 12));
|
312 | const roundedSizeIndex = log2(roundedP2);
|
313 | const roundedSize = roundedP2 * 12;
|
314 | if (this._iBuffers.length <= roundedSizeIndex) {
|
315 | this._iBuffers.length = roundedSizeIndex + 1;
|
316 | }
|
317 | let buffer = this._iBuffers[roundedSizeIndex];
|
318 | if (!buffer) {
|
319 | this._iBuffers[roundedSizeIndex] = buffer = new Uint16Array(roundedSize);
|
320 | }
|
321 | return buffer;
|
322 | }
|
323 | packInterleavedGeometry(element, attributeBuffer, indexBuffer, aIndex, iIndex) {
|
324 | const {
|
325 | uint32View,
|
326 | float32View
|
327 | } = attributeBuffer;
|
328 | const packedVertices = aIndex / this.vertexSize;
|
329 | const uvs = element.uvs;
|
330 | const indicies = element.indices;
|
331 | const vertexData = element.vertexData;
|
332 | const textureId = element._texture.baseTexture._batchLocation;
|
333 | const alpha = Math.min(element.worldAlpha, 1);
|
334 | const argb = Color.shared.setValue(element._tintRGB).toPremultiplied(alpha);
|
335 | for (let i = 0; i < vertexData.length; i += 2) {
|
336 | float32View[aIndex++] = vertexData[i];
|
337 | float32View[aIndex++] = vertexData[i + 1];
|
338 | float32View[aIndex++] = uvs[i];
|
339 | float32View[aIndex++] = uvs[i + 1];
|
340 | uint32View[aIndex++] = argb;
|
341 | float32View[aIndex++] = textureId;
|
342 | }
|
343 | for (let i = 0; i < indicies.length; i++) {
|
344 | indexBuffer[iIndex++] = packedVertices + indicies[i];
|
345 | }
|
346 | }
|
347 | };
|
348 | let BatchRenderer = _BatchRenderer;
|
349 | BatchRenderer.defaultBatchSize = 4096;
|
350 | BatchRenderer.extension = {
|
351 | name: "batch",
|
352 | type: ExtensionType.RendererPlugin
|
353 | };
|
354 | BatchRenderer._drawCallPool = [];
|
355 | BatchRenderer._textureArrayPool = [];
|
356 | extensions.add(BatchRenderer);
|
357 |
|
358 | export { BatchRenderer };
|
359 |
|