/** * The base sampler shader exposes a simple sprite shader and all the framework to handle: * - Batched shaders and plugin subscription * - Configure method (for special processing done once or punctually) * - Update method (pre-binding, normally done each frame) * All other sampler shaders (batched or not) should extend BaseSamplerShader */ class BaseSamplerShader extends AbstractBaseShader { /** * The named batch sampler plugin that is used by this shader, or null if no batching is used. * @type {string|null} */ static classPluginName = "batch"; /** * Is this shader pausable or not? * @type {boolean} */ static pausable = true; /** * The plugin name associated for this instance, if any. * Returns "batch" if the shader is disabled. * @type {string|null} */ get pluginName() { return this.#pluginName; } #pluginName = this.constructor.classPluginName; /** * Activate or deactivate this sampler. If set to false, the batch rendering is redirected to "batch". * Otherwise, the batch rendering is directed toward the instance pluginName (might be null) * @type {boolean} */ get enabled() { return this.#enabled; } set enabled(enabled) { this.#pluginName = enabled ? this.constructor.classPluginName : "batch"; this.#enabled = enabled; } #enabled = true; /** * Pause or Unpause this sampler. If set to true, the shader is disabled. Otherwise, it is enabled. * Contrary to enabled, a shader might decide to refuse a pause, to continue to render animations per example. * @see {enabled} * @type {boolean} */ get paused() { return !this.#enabled; } set paused(paused) { if ( !this.constructor.pausable ) return; this.enabled = !paused; } /** * Contrast adjustment * @type {string} */ static CONTRAST = ` // Computing contrasted color if ( contrast != 0.0 ) { changedColor = (changedColor - 0.5) * (contrast + 1.0) + 0.5; }`; /** * Saturation adjustment * @type {string} */ static SATURATION = ` // Computing saturated color if ( saturation != 0.0 ) { vec3 grey = vec3(perceivedBrightness(changedColor)); changedColor = mix(grey, changedColor, 1.0 + saturation); }`; /** * Exposure adjustment. * @type {string} */ static EXPOSURE = ` if ( exposure != 0.0 ) { changedColor *= (1.0 + exposure); }`; /** * The adjustments made into fragment shaders. * @type {string} */ static get ADJUSTMENTS() { return `vec3 changedColor = baseColor.rgb; ${this.CONTRAST} ${this.SATURATION} ${this.EXPOSURE} baseColor.rgb = changedColor;`; } /** @override */ static vertexShader = ` precision ${PIXI.settings.PRECISION_VERTEX} float; attribute vec2 aVertexPosition; attribute vec2 aTextureCoord; uniform mat3 projectionMatrix; varying vec2 vUvs; void main() { gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); vUvs = aTextureCoord; } `; /** @override */ static fragmentShader = ` precision ${PIXI.settings.PRECISION_FRAGMENT} float; uniform sampler2D sampler; uniform vec4 tintAlpha; varying vec2 vUvs; void main() { gl_FragColor = texture2D(sampler, vUvs) * tintAlpha; } `; /** * The batch vertex shader source. * @type {string} */ static batchVertexShader = ` #version 300 es precision ${PIXI.settings.PRECISION_VERTEX} float; in vec2 aVertexPosition; in vec2 aTextureCoord; in vec4 aColor; in float aTextureId; uniform mat3 projectionMatrix; uniform mat3 translationMatrix; uniform vec4 tint; out vec2 vTextureCoord; flat out vec4 vColor; flat out float vTextureId; void main(void){ gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); vTextureCoord = aTextureCoord; vTextureId = aTextureId; vColor = aColor * tint; } `; /** * The batch fragment shader source. * @type {string} */ static batchFragmentShader = ` #version 300 es precision ${PIXI.settings.PRECISION_FRAGMENT} float; in vec2 vTextureCoord; flat in vec4 vColor; flat in float vTextureId; uniform sampler2D uSamplers[%count%]; out vec4 fragColor; #define texture2D texture void main(void){ vec4 color; %forloop% fragColor = color * vColor; } `; /** @inheritdoc */ static defaultUniforms = { sampler: 0, tintAlpha: [1, 1, 1, 1] }; /** * Batch geometry associated with this sampler. * @type {typeof PIXI.BatchGeometry|{id: string, size: number, normalized: boolean, type: PIXI.TYPES}[]} */ static batchGeometry = PIXI.BatchGeometry; /** * The size of a vertice with all its packed attributes. * @type {number} */ static batchVertexSize = 6; /** * Pack interleaved geometry custom function. * @type {Function|undefined} * @protected */ static _packInterleavedGeometry; /** * A prerender function happening just before the batch renderer is flushed. * @type {(batchRenderer: BatchRenderer) => void | undefined} * @protected */ static _preRenderBatch; /** * A function that returns default uniforms associated with the batched version of this sampler. * @type {object} */ static batchDefaultUniforms = {}; /** * The number of reserved texture units for this shader that cannot be used by the batch renderer. * @type {number} */ static reservedTextureUnits = 0; /** * Initialize the batch geometry with custom properties. */ static initializeBatchGeometry() {} /** * The batch renderer to use. * @type {typeof BatchRenderer} */ static batchRendererClass = BatchRenderer; /** * The batch generator to use. * @type {typeof BatchShaderGenerator} */ static batchShaderGeneratorClass = BatchShaderGenerator; /* ---------------------------------------- */ /** * Create a batch plugin for this sampler class. * @returns {typeof BatchPlugin} The batch plugin class linked to this sampler class. */ static createPlugin() { const shaderClass = this; const geometryClass = Array.isArray(shaderClass.batchGeometry) ? class BatchGeometry extends PIXI.Geometry { constructor(_static=false) { super(); this._buffer = new PIXI.Buffer(null, _static, false); this._indexBuffer = new PIXI.Buffer(null, _static, true); for ( const {id, size, normalized, type} of shaderClass.batchGeometry ) { this.addAttribute(id, this._buffer, size, normalized, type); } this.addIndex(this._indexBuffer); } } : shaderClass.batchGeometry; return class BatchPlugin extends shaderClass.batchRendererClass { /** @override */ static get shaderGeneratorClass() { return shaderClass.batchShaderGeneratorClass; } /* ---------------------------------------- */ /** @override */ static get defaultVertexSrc() { return shaderClass.batchVertexShader; } /* ---------------------------------------- */ /** @override */ static get defaultFragmentTemplate() { return shaderClass.batchFragmentShader; } /* ---------------------------------------- */ /** @override */ static get defaultUniforms() { return shaderClass.batchDefaultUniforms; } /* ---------------------------------------- */ /** * The batch plugin constructor. * @param {PIXI.Renderer} renderer The renderer */ constructor(renderer) { super(renderer); this.geometryClass = geometryClass; this.vertexSize = shaderClass.batchVertexSize; this.reservedTextureUnits = shaderClass.reservedTextureUnits; this._packInterleavedGeometry = shaderClass._packInterleavedGeometry; this._preRenderBatch = shaderClass._preRenderBatch; } /* ---------------------------------------- */ /** @inheritdoc */ setShaderGenerator(options) { if ( !canvas.performance ) return; super.setShaderGenerator(options); } /* ---------------------------------------- */ /** @inheritdoc */ contextChange() { this.shaderGenerator = null; super.contextChange(); } }; } /* ---------------------------------------- */ /** * Register the plugin for this sampler. * @param {object} [options] The options * @param {object} [options.force=false] Override the plugin of the same name that is already registered? */ static registerPlugin({force=false}={}) { const pluginName = this.classPluginName; // Checking the pluginName if ( !(pluginName && (typeof pluginName === "string") && (pluginName.length > 0)) ) { const msg = `Impossible to create a PIXI plugin for ${this.name}. ` + `The plugin name is invalid: [pluginName=${pluginName}]. ` + "The plugin name must be a string with at least 1 character."; throw new Error(msg); } // Checking for existing plugins if ( !force && BatchRenderer.hasPlugin(pluginName) ) { const msg = `Impossible to create a PIXI plugin for ${this.name}. ` + `The plugin name is already associated to a plugin in PIXI.Renderer: [pluginName=${pluginName}].`; throw new Error(msg); } // Initialize custom properties for the batch geometry this.initializeBatchGeometry(); // Create our custom batch renderer for this geometry const plugin = this.createPlugin(); // Register this plugin with its batch renderer PIXI.extensions.add({ name: pluginName, type: PIXI.ExtensionType.RendererPlugin, ref: plugin }); } /* ---------------------------------------- */ /** @override */ _preRender(mesh, renderer) { const uniforms = this.uniforms; uniforms.sampler = mesh.texture; uniforms.tintAlpha = mesh._cachedTint; } }