/** * A batch renderer with a customizable data transfer function to packed geometries. * @extends PIXI.AbstractBatchRenderer */ class BatchRenderer extends PIXI.BatchRenderer { /** * The batch shader generator class. * @type {typeof BatchShaderGenerator} */ static shaderGeneratorClass = BatchShaderGenerator; /* -------------------------------------------- */ /** * The default uniform values for the batch shader. * @type {object | (maxTextures: number) => object} */ static defaultUniforms = {}; /* -------------------------------------------- */ /** * The PackInterleavedGeometry function provided by the sampler. * @type {Function|undefined} * @protected */ _packInterleavedGeometry; /* -------------------------------------------- */ /** * The update function provided by the sampler and that is called just before a flush. * @type {(batchRenderer: BatchRenderer) => void | undefined} * @protected */ _preRenderBatch; /* -------------------------------------------- */ /** * Get the uniforms bound to this abstract batch renderer. * @returns {object|undefined} */ get uniforms() { return this._shader?.uniforms; } /* -------------------------------------------- */ /** * The number of reserved texture units that the shader generator should not use (maximum 4). * @param {number} val * @protected */ set reservedTextureUnits(val) { // Some checks before... if ( typeof val !== "number" ) { throw new Error("BatchRenderer#reservedTextureUnits must be a number!"); } if ( (val < 0) || (val > 4) ) { throw new Error("BatchRenderer#reservedTextureUnits must be positive and can't exceed 4."); } this.#reservedTextureUnits = val; } /** * Number of reserved texture units reserved by the batch shader that cannot be used by the batch renderer. * @returns {number} */ get reservedTextureUnits() { return this.#reservedTextureUnits; } #reservedTextureUnits = 0; /* -------------------------------------------- */ /** @override */ setShaderGenerator({ vertex=this.constructor.defaultVertexSrc, fragment=this.constructor.defaultFragmentTemplate, uniforms=this.constructor.defaultUniforms }={}) { this.shaderGenerator = new this.constructor.shaderGeneratorClass(vertex, fragment, uniforms); } /* -------------------------------------------- */ /** * This override allows to allocate a given number of texture units reserved for a custom batched shader. * These reserved texture units won't be used to batch textures for PIXI.Sprite or SpriteMesh. * @override */ contextChange() { const gl = this.renderer.gl; // First handle legacy environment if ( PIXI.settings.PREFER_ENV === PIXI.ENV.WEBGL_LEGACY ) this.maxTextures = 1; else { // Step 1: first check max texture units the GPU can handle const gpuMaxTex = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), 65536); // Step 2: Remove the number of reserved texture units that could be used by a custom batch shader const batchMaxTex = gpuMaxTex - this.#reservedTextureUnits; // Step 3: Checking if remainder of texture units is at least 1. Should never happens on GPU < than 20 years old! if ( batchMaxTex < 1 ) { const msg = "Impossible to allocate the required number of texture units in contextChange#BatchRenderer. " + "Your GPU should handle at least 8 texture units. Currently, it is supporting: " + `${gpuMaxTex} texture units.`; throw new Error(msg); } // Step 4: Check with the maximum number of textures of the setting (webGL specifications) this.maxTextures = Math.min(batchMaxTex, PIXI.settings.SPRITE_MAX_TEXTURES); // Step 5: Check the maximum number of if statements the shader can have too.. this.maxTextures = PIXI.checkMaxIfStatementsInShader(this.maxTextures, gl); } // Generate the batched shader this._shader = this.shaderGenerator?.generateShader(this.maxTextures) ?? null; // Initialize packed geometries for ( let i = 0; i < this._packedGeometryPoolSize; i++ ) { this._packedGeometries[i] = new (this.geometryClass)(); } this.initFlushBuffers(); } /* -------------------------------------------- */ /** @inheritdoc */ onPrerender() { if ( !this.shaderGenerator ) this.setShaderGenerator(); this._shader ??= this.shaderGenerator.generateShader(this.maxTextures); super.onPrerender(); } /* -------------------------------------------- */ /** @override */ start() { this._preRenderBatch?.(this); super.start(); } /* -------------------------------------------- */ /** @override */ packInterleavedGeometry(element, attributeBuffer, indexBuffer, aIndex, iIndex) { // If we have a specific function to pack data into geometry, we call it if ( this._packInterleavedGeometry ) { this._packInterleavedGeometry(element, attributeBuffer, indexBuffer, aIndex, iIndex); return; } // Otherwise, we call the parent method, with the classic packing super.packInterleavedGeometry(element, attributeBuffer, indexBuffer, aIndex, iIndex); } /* -------------------------------------------- */ /** * Verify if a PIXI plugin exists. Check by name. * @param {string} name The name of the pixi plugin to check. * @returns {boolean} True if the plugin exists, false otherwise. */ static hasPlugin(name) { return Object.keys(PIXI.Renderer.__plugins).some(k => k === name); } }