UNPKG

12.4 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.mjs';
18import defaultVertex from './texture2.mjs';
19
20const _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};
348let BatchRenderer = _BatchRenderer;
349BatchRenderer.defaultBatchSize = 4096;
350BatchRenderer.extension = {
351 name: "batch",
352 type: ExtensionType.RendererPlugin
353};
354BatchRenderer._drawCallPool = [];
355BatchRenderer._textureArrayPool = [];
356extensions.add(BatchRenderer);
357
358export { BatchRenderer };
359//# sourceMappingURL=BatchRenderer.mjs.map