phaser
Version:
A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.
1,541 lines (1,329 loc) • 93 kB
JavaScript
/**
* @author Richard Davey <rich@phaser.io>
* @copyright 2013-2025 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var Class = require('../../utils/Class');
var DeepCopy = require('../../utils/object/DeepCopy');
var EventEmitter = require('eventemitter3');
var Events = require('./pipelines/events');
var GetFastValue = require('../../utils/object/GetFastValue');
var Matrix4 = require('../../math/Matrix4');
var RendererEvents = require('../events');
var RenderTarget = require('./RenderTarget');
var Utils = require('./Utils');
var WebGLShader = require('./WebGLShader');
/**
* @classdesc
* The `WebGLPipeline` is a base class used by all of the core Phaser pipelines.
*
* It describes the way elements will be rendered in WebGL. Internally, it handles
* compiling the shaders, creating vertex buffers, assigning primitive topology and
* binding vertex attributes, all based on the given configuration data.
*
* The pipeline is configured by passing in a `WebGLPipelineConfig` object. Please
* see the documentation for this type to fully understand the configuration options
* available to you.
*
* Usually, you would not extend from this class directly, but would instead extend
* from one of the core pipelines, such as the Multi Pipeline.
*
* The pipeline flow per render-step is as follows:
*
* 1) onPreRender - called once at the start of the render step
* 2) onRender - call for each Scene Camera that needs to render (so can be multiple times per render step)
* 3) Internal flow:
* 3a) bind (only called if a Game Object is using this pipeline and it's not currently active)
* 3b) onBind (called for every Game Object that uses this pipeline)
* 3c) flush (can be called by a Game Object, internal method or from outside by changing pipeline)
* 4) onPostRender - called once at the end of the render step
*
* @class WebGLPipeline
* @extends Phaser.Events.EventEmitter
* @memberof Phaser.Renderer.WebGL
* @constructor
* @since 3.0.0
*
* @param {Phaser.Types.Renderer.WebGL.WebGLPipelineConfig} config - The configuration object for this WebGL Pipeline.
*/
var WebGLPipeline = new Class({
Extends: EventEmitter,
initialize:
function WebGLPipeline (config)
{
EventEmitter.call(this);
var game = config.game;
var renderer = game.renderer;
var gl = renderer.gl;
/**
* Name of the pipeline. Used for identification and setting from Game Objects.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#name
* @type {string}
* @since 3.0.0
*/
this.name = GetFastValue(config, 'name', 'WebGLPipeline');
/**
* The Phaser Game instance to which this pipeline is bound.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#game
* @type {Phaser.Game}
* @since 3.0.0
*/
this.game = game;
/**
* The WebGL Renderer instance to which this pipeline is bound.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#renderer
* @type {Phaser.Renderer.WebGL.WebGLRenderer}
* @since 3.0.0
*/
this.renderer = renderer;
/**
* A reference to the WebGL Pipeline Manager.
*
* This is initially undefined and only set when this pipeline is added
* to the manager.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#manager
* @type {?Phaser.Renderer.WebGL.PipelineManager}
* @since 3.50.0
*/
this.manager;
/**
* The WebGL context this WebGL Pipeline uses.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#gl
* @type {WebGLRenderingContext}
* @since 3.0.0
*/
this.gl = gl;
/**
* The canvas which this WebGL Pipeline renders to.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#view
* @type {HTMLCanvasElement}
* @since 3.0.0
*/
this.view = game.canvas;
/**
* Width of the current viewport.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#width
* @type {number}
* @since 3.0.0
*/
this.width = 0;
/**
* Height of the current viewport.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#height
* @type {number}
* @since 3.0.0
*/
this.height = 0;
/**
* The current number of vertices that have been added to the pipeline batch.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#vertexCount
* @type {number}
* @default 0
* @since 3.0.0
*/
this.vertexCount = 0;
/**
* The total number of vertices that this pipeline batch can hold before it will flush.
*
* This defaults to `renderer batchSize * 6`, where `batchSize` is defined in the Renderer Game Config.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#vertexCapacity
* @type {number}
* @since 3.0.0
*/
this.vertexCapacity = 0;
/**
* Raw byte buffer of vertices.
*
* Either set via the config object `vertices` property, or generates a new Array Buffer of
* size `vertexCapacity * vertexSize`.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#vertexData
* @type {ArrayBuffer}
* @readonly
* @since 3.0.0
*/
this.vertexData;
/**
* The WebGLBuffer that holds the vertex data.
*
* Created from the `vertexData` ArrayBuffer. If `vertices` are set in the config, a `STATIC_DRAW` buffer
* is created. If not, a `DYNAMIC_DRAW` buffer is created.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#vertexBuffer
* @type {Phaser.Renderer.WebGL.Wrappers.WebGLBufferWrapper}
* @readonly
* @since 3.0.0
*/
this.vertexBuffer;
/**
* The currently active WebGLBuffer.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#activeBuffer
* @type {Phaser.Renderer.WebGL.Wrappers.WebGLBufferWrapper}
* @since 3.60.0
*/
this.activeBuffer;
/**
* The primitive topology which the pipeline will use to submit draw calls.
*
* Defaults to GL_TRIANGLES if not otherwise set in the config.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#topology
* @type {GLenum}
* @since 3.0.0
*/
this.topology = GetFastValue(config, 'topology', gl.TRIANGLES);
/**
* Uint8 view to the `vertexData` ArrayBuffer. Used for uploading vertex buffer resources to the GPU.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#bytes
* @type {Uint8Array}
* @since 3.0.0
*/
this.bytes;
/**
* Float32 view of the array buffer containing the pipeline's vertices.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#vertexViewF32
* @type {Float32Array}
* @since 3.0.0
*/
this.vertexViewF32;
/**
* Uint32 view of the array buffer containing the pipeline's vertices.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#vertexViewU32
* @type {Uint32Array}
* @since 3.0.0
*/
this.vertexViewU32;
/**
* Indicates if the current pipeline is active, or not.
*
* Toggle this property to enable or disable a pipeline from rendering anything.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#active
* @type {boolean}
* @since 3.10.0
*/
this.active = true;
/**
* Some pipelines require the forced use of texture zero (like the light pipeline).
*
* This property should be set when that is the case.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#forceZero
* @type {boolean}
* @since 3.50.0
*/
this.forceZero = GetFastValue(config, 'forceZero', false);
/**
* Indicates if this pipeline has booted or not.
*
* A pipeline boots only when the Game instance itself, and all associated systems, is
* fully ready.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#hasBooted
* @type {boolean}
* @readonly
* @since 3.50.0
*/
this.hasBooted = false;
/**
* Indicates if this is a Post FX Pipeline, or not.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#isPostFX
* @type {boolean}
* @readonly
* @since 3.50.0
*/
this.isPostFX = false;
/**
* Indicates if this is a Pre FX Pipeline, or not.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#isPreFX
* @type {boolean}
* @readonly
* @since 3.60.0
*/
this.isPreFX = false;
/**
* An array of RenderTarget instances that belong to this pipeline.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#renderTargets
* @type {Phaser.Renderer.WebGL.RenderTarget[]}
* @since 3.50.0
*/
this.renderTargets = [];
/**
* A reference to the currently bound Render Target instance from the `WebGLPipeline.renderTargets` array.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#currentRenderTarget
* @type {Phaser.Renderer.WebGL.RenderTarget}
* @since 3.50.0
*/
this.currentRenderTarget;
/**
* An array of all the WebGLShader instances that belong to this pipeline.
*
* Shaders manage their own attributes and uniforms, but share the same vertex data buffer,
* which belongs to this pipeline.
*
* Shaders are set in a call to the `setShadersFromConfig` method, which happens automatically,
* but can also be called at any point in your game. See the method documentation for details.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#shaders
* @type {Phaser.Renderer.WebGL.WebGLShader[]}
* @since 3.50.0
*/
this.shaders = [];
/**
* A reference to the currently bound WebGLShader instance from the `WebGLPipeline.shaders` array.
*
* For lots of pipelines, this is the only shader, so it is a quick way to reference it without
* an array look-up.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#currentShader
* @type {Phaser.Renderer.WebGL.WebGLShader}
* @since 3.50.0
*/
this.currentShader;
/**
* The Projection matrix, used by shaders as 'uProjectionMatrix' uniform.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#projectionMatrix
* @type {Phaser.Math.Matrix4}
* @since 3.50.0
*/
this.projectionMatrix;
/**
* The cached width of the Projection matrix.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#projectionWidth
* @type {number}
* @since 3.50.0
*/
this.projectionWidth = 0;
/**
* The cached height of the Projection matrix.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#projectionHeight
* @type {number}
* @since 3.50.0
*/
this.projectionHeight = 0;
/**
* The configuration object that was used to create this pipeline.
*
* Treat this object as 'read only', because changing it post-creation will not
* impact this pipeline in any way. However, it is used internally for cloning
* and post-boot set-up.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#config
* @type {Phaser.Types.Renderer.WebGL.WebGLPipelineConfig}
* @since 3.50.0
*/
this.config = config;
/**
* Has the GL Context been reset to the Phaser defaults since the last time
* this pipeline was bound? This is set automatically when the Pipeline Manager
* resets itself, usually after handing off to a 3rd party renderer like Spine.
*
* You should treat this property as read-only.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#glReset
* @type {boolean}
* @since 3.53.0
*/
this.glReset = false;
/**
* The temporary Pipeline batch. This array contains the batch entries for
* the current frame, which is a package of textures and vertex offsets used
* for drawing. This package is built dynamically as the frame is built
* and cleared during the flush method.
*
* Treat this array and all of its contents as read-only.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#batch
* @type {Phaser.Types.Renderer.WebGL.WebGLPipelineBatchEntry[]}
* @since 3.60.0
*/
this.batch = [];
/**
* The most recently created Pipeline batch entry.
*
* Reset to null as part of the flush method.
*
* Treat this value as read-only.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#currentBatch
* @type {?Phaser.Types.Renderer.WebGL.WebGLPipelineBatchEntry}
* @since 3.60.0
*/
this.currentBatch = null;
/**
* The most recently bound texture, used as part of the batch process.
*
* Reset to null as part of the flush method.
*
* Treat this value as read-only.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#currentTexture
* @type {?Phaser.Renderer.WebGL.Wrappers.WebGLTextureWrapper}
* @since 3.60.0
*/
this.currentTexture = null;
/**
* Holds the most recently assigned texture unit.
*
* Treat this value as read-only.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#currentUnit
* @type {number}
* @since 3.50.0
*/
this.currentUnit = 0;
/**
* The currently active WebGLTextures, used as part of the batch process.
*
* Reset to empty as part of the bind method.
*
* Treat this array as read-only.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#activeTextures
* @type {Phaser.Renderer.WebGL.Wrappers.WebGLTextureWrapper[]}
* @since 3.60.0
*/
this.activeTextures = [];
/**
* If the WebGL Renderer changes size, this uniform will be set with the new width and height values
* as part of the pipeline resize method. Various built-in pipelines, such as the MultiPipeline, set
* this property automatically to `uResolution`.
*
* @name Phaser.Renderer.WebGL.WebGLPipeline#resizeUniform
* @type {string}
* @since 3.80.0
*/
this.resizeUniform = GetFastValue(config, 'resizeUniform', '');
},
/**
* Called when the Game has fully booted and the Renderer has finished setting up.
*
* By this stage all Game level systems are now in place. You can perform any final tasks that the
* pipeline may need, that relies on game systems such as the Texture Manager being ready.
*
* @method Phaser.Renderer.WebGL.WebGLPipeline#boot
* @fires Phaser.Renderer.WebGL.Pipelines.Events#BOOT
* @since 3.11.0
*/
boot: function ()
{
var i;
var gl = this.gl;
var config = this.config;
var renderer = this.renderer;
if (!this.isPostFX)
{
this.projectionMatrix = new Matrix4().identity();
}
// Create the Render Targets
var renderTargets = this.renderTargets;
var targets = GetFastValue(config, 'renderTarget', false);
// If boolean, set to number = 1
if (typeof(targets) === 'boolean' && targets)
{
targets = 1;
}
var width = renderer.width;
var height = renderer.height;
if (typeof(targets) === 'number')
{
// Create this many default RTs
for (i = 0; i < targets; i++)
{
renderTargets.push(new RenderTarget(renderer, width, height, 1, 0, true));
}
}
else if (Array.isArray(targets))
{
for (i = 0; i < targets.length; i++)
{
var scale = GetFastValue(targets[i], 'scale', 1);
var minFilter = GetFastValue(targets[i], 'minFilter', 0);
var autoClear = GetFastValue(targets[i], 'autoClear', 1);
var autoResize = GetFastValue(targets[i], 'autoResize', false);
var targetWidth = GetFastValue(targets[i], 'width', null);
var targetHeight = GetFastValue(targets[i], 'height', targetWidth);
if (targetWidth)
{
renderTargets.push(new RenderTarget(renderer, targetWidth, targetHeight, 1, minFilter, autoClear, autoResize));
}
else
{
renderTargets.push(new RenderTarget(renderer, width, height, scale, minFilter, autoClear, autoResize));
}
}
}
if (renderTargets.length)
{
// Default to the first one in the array
this.currentRenderTarget = renderTargets[0];
}
// Create the Shaders
this.setShadersFromConfig(config);
// Which shader has the largest vertex size?
var shaders = this.shaders;
var vertexSize = 0;
for (i = 0; i < shaders.length; i++)
{
if (shaders[i].vertexSize > vertexSize)
{
vertexSize = shaders[i].vertexSize;
}
}
var batchSize = GetFastValue(config, 'batchSize', renderer.config.batchSize);
// * 6 because there are 6 vertices in a quad and 'batchSize' represents the quantity of quads in the batch
this.vertexCapacity = batchSize * 6;
var data = new ArrayBuffer(this.vertexCapacity * vertexSize);
this.vertexData = data;
this.bytes = new Uint8Array(data);
this.vertexViewF32 = new Float32Array(data);
this.vertexViewU32 = new Uint32Array(data);
var configVerts = GetFastValue(config, 'vertices', null);
if (configVerts)
{
this.vertexViewF32.set(configVerts);
this.vertexBuffer = renderer.createVertexBuffer(data, gl.STATIC_DRAW);
}
else
{
this.vertexBuffer = renderer.createVertexBuffer(data.byteLength, gl.DYNAMIC_DRAW);
}
// Set-up shaders
this.setVertexBuffer();
for (i = shaders.length - 1; i >= 0; i--)
{
shaders[i].rebind();
}
this.hasBooted = true;
renderer.on(RendererEvents.RESIZE, this.resize, this);
renderer.on(RendererEvents.PRE_RENDER, this.onPreRender, this);
renderer.on(RendererEvents.RENDER, this.onRender, this);
renderer.on(RendererEvents.POST_RENDER, this.onPostRender, this);
this.emit(Events.BOOT, this);
this.onBoot();
},
/**
* This method is called once when this pipeline has finished being set-up
* at the end of the boot process. By the time this method is called, all
* of the shaders are ready and configured.
*
* @method Phaser.Renderer.WebGL.WebGLPipeline#onBoot
* @since 3.50.0
*/
onBoot: function ()
{
},
/**
* This method is called once when this pipeline has finished being set-up
* at the end of the boot process. By the time this method is called, all
* of the shaders are ready and configured. It's also called if the renderer
* changes size.
*
* @method Phaser.Renderer.WebGL.WebGLPipeline#onResize
* @since 3.50.0
*
* @param {number} width - The new width of this WebGL Pipeline.
* @param {number} height - The new height of this WebGL Pipeline.
*/
onResize: function ()
{
},
/**
* Sets the currently active shader within this pipeline.
*
* @method Phaser.Renderer.WebGL.WebGLPipeline#setShader
* @since 3.50.0
*
* @param {Phaser.Renderer.WebGL.WebGLShader} shader - The shader to set as being current.
* @param {boolean} [setAttributes=false] - Should the vertex attribute pointers be set?
* @param {Phaser.Renderer.WebGL.Wrappers.WebGLBufferWrapper} [vertexBuffer] - The vertex buffer to be set before the shader is bound. Defaults to the one owned by this pipeline.
*
* @return {this} This WebGLPipeline instance.
*/
setShader: function (shader, setAttributes, vertexBuffer)
{
var renderer = this.renderer;
if (shader !== this.currentShader || renderer.currentProgram !== this.currentShader.program)
{
this.flush();
var wasBound = this.setVertexBuffer(vertexBuffer);
if (wasBound && !setAttributes)
{
setAttributes = true;
}
shader.bind(setAttributes, false);
this.currentShader = shader;
}
return this;
},
/**
* Searches all shaders in this pipeline for one matching the given name, then returns it.
*
* @method Phaser.Renderer.WebGL.WebGLPipeline#getShaderByName
* @since 3.50.0
*
* @param {string} name - The index of the shader to set.
*
* @return {Phaser.Renderer.WebGL.WebGLShader} The WebGLShader instance, if found.
*/
getShaderByName: function (name)
{
var shaders = this.shaders;
for (var i = 0; i < shaders.length; i++)
{
if (shaders[i].name === name)
{
return shaders[i];
}
}
},
/**
* Destroys all shaders currently set in the `WebGLPipeline.shaders` array and then parses the given
* `config` object, extracting the shaders from it, creating `WebGLShader` instances and finally
* setting them into the `shaders` array of this pipeline.
*
* This is a destructive process. Be very careful when you call it, should you need to.
*
* @method Phaser.Renderer.WebGL.WebGLPipeline#setShadersFromConfig
* @since 3.50.0
*
* @param {Phaser.Types.Renderer.WebGL.WebGLPipelineConfig} config - The configuration object for this WebGL Pipeline.
*
* @return {this} This WebGLPipeline instance.
*/
setShadersFromConfig: function (config)
{
var i;
var shaders = this.shaders;
var renderer = this.renderer;
for (i = 0; i < shaders.length; i++)
{
shaders[i].destroy();
}
var vName = 'vertShader';
var fName = 'fragShader';
var aName = 'attributes';
var defaultVertShader = GetFastValue(config, vName, null);
var defaultFragShader = Utils.parseFragmentShaderMaxTextures(GetFastValue(config, fName, null), renderer.maxTextures);
var defaultAttribs = GetFastValue(config, aName, null);
var configShaders = GetFastValue(config, 'shaders', []);
var len = configShaders.length;
if (len === 0)
{
if (defaultVertShader && defaultFragShader)
{
this.shaders = [ new WebGLShader(this, 'default', defaultVertShader, defaultFragShader, DeepCopy(defaultAttribs)) ];
}
}
else
{
var newShaders = [];
for (i = 0; i < len; i++)
{
var shaderEntry = configShaders[i];
var name;
var vertShader;
var fragShader;
var attributes;
if (typeof shaderEntry === 'string')
{
name = 'default';
vertShader = defaultVertShader;
fragShader = Utils.parseFragmentShaderMaxTextures(shaderEntry, renderer.maxTextures);
attributes = defaultAttribs;
}
else
{
name = GetFastValue(shaderEntry, 'name', 'default');
vertShader = GetFastValue(shaderEntry, vName, defaultVertShader);
fragShader = Utils.parseFragmentShaderMaxTextures(GetFastValue(shaderEntry, fName, defaultFragShader), renderer.maxTextures);
attributes = GetFastValue(shaderEntry, aName, defaultAttribs);
}
if (name === 'default')
{
var lines = fragShader.split('\n');
var test = lines[0].trim();
if (test.indexOf('#define SHADER_NAME') > -1)
{
name = test.substring(20);
}
}
if (vertShader && fragShader)
{
newShaders.push(new WebGLShader(this, name, vertShader, fragShader, DeepCopy(attributes)));
}
}
this.shaders = newShaders;
}
if (this.shaders.length === 0)
{
console.warn('Pipeline: ' + this.name + ' - Invalid shader config');
}
else
{
this.currentShader = this.shaders[0];
}
return this;
},
/**
* Creates a new WebGL Pipeline Batch Entry, sets the texture unit as zero
* and pushes the entry into the batch.
*
* @method Phaser.Renderer.WebGL.WebGLPipeline#createBatch
* @since 3.60.0
*
* @param {Phaser.Renderer.WebGL.Wrappers.WebGLTextureWrapper} texture - The texture assigned to this batch entry.
*
* @return {number} The texture unit that was bound.
*/
createBatch: function (texture)
{
this.currentBatch = {
start: this.vertexCount,
count: 0,
texture: [ texture ],
unit: 0,
maxUnit: 0
};
this.currentUnit = 0;
this.currentTexture = texture;
this.batch.push(this.currentBatch);
return 0;
},
/**
* Adds the given texture to the current WebGL Pipeline Batch Entry and
* increases the batch entry unit and maxUnit values by 1.
*
* @method Phaser.Renderer.WebGL.WebGLPipeline#addTextureToBatch
* @since 3.60.0
*
* @param {Phaser.Renderer.WebGL.Wrappers.WebGLTextureWrapper} texture - The texture assigned to this batch entry.
*/
addTextureToBatch: function (texture)
{
var batch = this.currentBatch;
if (batch)
{
batch.texture.push(texture);
batch.unit++;
batch.maxUnit++;
}
},
/**
* Takes the given WebGLTextureWrapper and determines what to do with it.
*
* If there is no current batch (i.e. after a flush) it will create a new
* batch from it.
*
* If the texture is already bound, it will return the current texture unit.
*
* If the texture already exists in the current batch, the unit gets reset
* to match it.
*
* If the texture cannot be found in the current batch, and it supports
* multiple textures, it's added into the batch and the unit indexes are
* advanced.
*
* @method Phaser.Renderer.WebGL.WebGLPipeline#pushBatch
* @since 3.60.0
*
* @param {Phaser.Renderer.WebGL.Wrappers.WebGLTextureWrapper} texture - The texture assigned to this batch entry.
*
* @return {number} The texture unit that was bound.
*/
pushBatch: function (texture)
{
// No current batch? Create one and return
if (!this.currentBatch || (this.forceZero && texture !== this.currentTexture))
{
return this.createBatch(texture);
}
// Otherwise, check if the texture is in the current batch
if (texture === this.currentTexture)
{
return this.currentUnit;
}
else
{
var current = this.currentBatch;
var idx = current.texture.indexOf(texture);
if (idx === -1)
{
// This is a brand new texture, not in the current batch
// Have we exceed our limit?
if (current.texture.length === this.renderer.maxTextures)
{
return this.createBatch(texture);
}
else
{
// We're good, push it in
current.unit++;
current.maxUnit++;
current.texture.push(texture);
this.currentUnit = current.unit;
this.currentTexture = texture;
return current.unit;
}
}
else
{
this.currentUnit = idx;
this.currentTexture = texture;
return idx;
}
}
},
/**
* Custom pipelines can use this method in order to perform any required pre-batch tasks
* for the given Game Object. It must return the texture unit the Game Object was assigned.
*
* @method Phaser.Renderer.WebGL.WebGLPipeline#setGameObject
* @since 3.50.0
*
* @param {Phaser.GameObjects.GameObject} gameObject - The Game Object being rendered or added to the batch.
* @param {Phaser.Textures.Frame} [frame] - Optional frame to use. Can override that of the Game Object.
*
* @return {number} The texture unit the Game Object has been assigned.
*/
setGameObject: function (gameObject, frame)
{
if (frame === undefined) { frame = gameObject.frame; }
return this.pushBatch(frame.source.glTexture);
},
/**
* Check if the current batch of vertices is full.
*
* You can optionally provide an `amount` parameter. If given, it will check if the batch
* needs to flush _if_ the `amount` is added to it. This allows you to test if you should
* flush before populating the batch.
*
* @method Phaser.Renderer.WebGL.WebGLPipeline#shouldFlush
* @since 3.0.0
*
* @param {number} [amount=0] - Will the batch need to flush if this many vertices are added to it?
*
* @return {boolean} `true` if the current batch should be flushed, otherwise `false`.
*/
shouldFlush: function (amount)
{
if (amount === undefined) { amount = 0; }
return (this.vertexCount + amount > this.vertexCapacity);
},
/**
* Returns the number of vertices that can be added to the current batch before
* it will trigger a flush to happen.
*
* @method Phaser.Renderer.WebGL.WebGLPipeline#vertexAvailable
* @since 3.60.0
*
* @return {number} The number of vertices that can still be added to the current batch before it will flush.
*/
vertexAvailable: function ()
{
return this.vertexCapacity - this.vertexCount;
},
/**
* Resizes the properties used to describe the viewport.
*
* This method is called automatically by the renderer during its resize handler.
*
* @method Phaser.Renderer.WebGL.WebGLPipeline#resize
* @fires Phaser.Renderer.WebGL.Pipelines.Events#RESIZE
* @since 3.0.0
*
* @param {number} width - The new width of this WebGL Pipeline.
* @param {number} height - The new height of this WebGL Pipeline.
*
* @return {this} This WebGLPipeline instance.
*/
resize: function (width, height)
{
if (width !== this.width || height !== this.height)
{
this.flush();
}
this.width = width;
this.height = height;
var targets = this.renderTargets;
for (var i = 0; i < targets.length; i++)
{
targets[i].resize(width, height);
}
this.setProjectionMatrix(width, height);
if (this.resizeUniform)
{
this.set2f(this.resizeUniform, width, height);
}
this.emit(Events.RESIZE, width, height, this);
this.onResize(width, height);
return this;
},
/**
* Adjusts this pipelines ortho Projection Matrix to use the given dimensions
* and resets the `uProjectionMatrix` uniform on all bound shaders.
*
* This method is called automatically by the renderer during its resize handler.
*
* @method Phaser.Renderer.WebGL.WebGLPipeline#setProjectionMatrix
* @since 3.50.0
*
* @param {number} width - The new width of this WebGL Pipeline.
* @param {number} height - The new height of this WebGL Pipeline.
*
* @return {this} This WebGLPipeline instance.
*/
setProjectionMatrix: function (width, height)
{
var projectionMatrix = this.projectionMatrix;
// Because not all pipelines have them
if (!projectionMatrix)
{
return this;
}
this.projectionWidth = width;
this.projectionHeight = height;
projectionMatrix.ortho(0, width, height, 0, -1000, 1000);
var shaders = this.shaders;
var name = 'uProjectionMatrix';
for (var i = 0; i < shaders.length; i++)
{
var shader = shaders[i];
if (shader.hasUniform(name))
{
shader.resetUniform(name);
shader.setMatrix4fv(name, false, projectionMatrix.val, shader);
}
}
return this;
},
/**
* Adjusts this pipelines ortho Projection Matrix to flip the y
* and bottom values. Call with 'false' as the parameter to flip
* them back again.
*
* @method Phaser.Renderer.WebGL.WebGLPipeline#flipProjectionMatrix
* @since 3.60.0
*
* @param {boolean} [flipY=true] - Flip the y and bottom values?
*/
flipProjectionMatrix: function (flipY)
{
if (flipY === undefined) { flipY = true; }
var projectionMatrix = this.projectionMatrix;
// Because not all pipelines have them
if (!projectionMatrix)
{
return this;
}
var width = this.projectionWidth;
var height = this.projectionHeight;
if (flipY)
{
projectionMatrix.ortho(0, width, 0, height, -1000, 1000);
}
else
{
projectionMatrix.ortho(0, width, height, 0, -1000, 1000);
}
this.setMatrix4fv('uProjectionMatrix', false, projectionMatrix.val);
},
/**
* Adjusts this pipelines ortho Projection Matrix to match that of the global
* WebGL Renderer Projection Matrix.
*
* This method is called automatically by the Pipeline Manager when this
* pipeline is set.
*
* @method Phaser.Renderer.WebGL.WebGLPipeline#updateProjectionMatrix
* @since 3.50.0
*/
updateProjectionMatrix: function ()
{
if (this.projectionMatrix)
{
var globalWidth = this.renderer.projectionWidth;
var globalHeight = this.renderer.projectionHeight;
if (this.projectionWidth !== globalWidth || this.projectionHeight !== globalHeight)
{
this.setProjectionMatrix(globalWidth, globalHeight);
}
}
},
/**
* This method is called every time the Pipeline Manager makes this pipeline the currently active one.
*
* It binds the resources and shader needed for this pipeline, including setting the vertex buffer
* and attribute pointers.
*
* @method Phaser.Renderer.WebGL.WebGLPipeline#bind
* @fires Phaser.Renderer.WebGL.Pipelines.Events#BIND
* @since 3.0.0
*
* @param {Phaser.Renderer.WebGL.WebGLShader} [currentShader] - The shader to set as being current.
*
* @return {this} This WebGLPipeline instance.
*/
bind: function (currentShader)
{
if (currentShader === undefined) { currentShader = this.currentShader; }
if (this.glReset)
{
return this.rebind(currentShader);
}
var wasBound = false;
var gl = this.gl;
if (gl.getParameter(gl.ARRAY_BUFFER_BINDING) !== this.vertexBuffer)
{
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer.webGLBuffer);
this.activeBuffer = this.vertexBuffer;
wasBound = true;
}
currentShader.bind(wasBound);
this.currentShader = currentShader;
this.activeTextures.length = 0;
this.emit(Events.BIND, this, currentShader);
this.onActive(currentShader);
return this;
},
/**
* This method is called every time the Pipeline Manager rebinds this pipeline.
*
* It resets all shaders this pipeline uses, setting their attributes again.
*
* @method Phaser.Renderer.WebGL.WebGLPipeline#rebind
* @fires Phaser.Renderer.WebGL.Pipelines.Events#REBIND
* @since 3.0.0
*
* @param {Phaser.Renderer.WebGL.WebGLShader} [currentShader] - The shader to set as being current.
*
* @return {this} This WebGLPipeline instance.
*/
rebind: function (currentShader)
{
this.activeBuffer = null;
this.setVertexBuffer();
var shaders = this.shaders;
// Loop in reverse, so the first shader in the array is left as being bound
for (var i = shaders.length - 1; i >= 0; i--)
{
var shader = shaders[i].rebind();
if (!currentShader || shader === currentShader)
{
this.currentShader = shader;
}
}
this.activeTextures.length = 0;
this.emit(Events.REBIND, this.currentShader);
this.onActive(this.currentShader);
this.onRebind();
this.glReset = false;
return this;
},
/**
* This method is called if the WebGL context is lost and restored.
* It ensures that uniforms are synced back to the GPU
* for all shaders in this pipeline.
*
* @method Phaser.Renderer.WebGL.WebGLPipeline#restoreContext
* @since 3.80.0
*/
restoreContext: function ()
{
var shaders = this.shaders;
var hasVertexBuffer = !!this.vertexBuffer;
// Deactivate all invalidated state.
this.activeBuffer = null;
this.activeTextures.length = 0;
this.batch.length = 0;
this.currentBatch = null;
this.currentTexture = null;
this.currentUnit = 0;
if (hasVertexBuffer)
{
this.setVertexBuffer();
}
for (var i = 0; i < shaders.length; i++)
{
var shader = shaders[i];
shader.syncUniforms();
if (hasVertexBuffer)
{
shader.rebind();
}
}
},
/**
* Binds the vertex buffer to be the active ARRAY_BUFFER on the WebGL context.
*
* It first checks to see if it's already set as the active buffer and only
* binds itself if not.
*
* @method Phaser.Renderer.WebGL.WebGLPipeline#setVertexBuffer
* @since 3.50.0
*
* @param {Phaser.Renderer.WebGL.Wrappers.WebGLBufferWrapper} [buffer] - The Vertex Buffer to be bound. Defaults to the one owned by this pipeline.
*
* @return {boolean} `true` if the vertex buffer was bound, or `false` if it was already bound.
*/
setVertexBuffer: function (buffer)
{
if (buffer === undefined) { buffer = this.vertexBuffer; }
if (buffer !== this.activeBuffer)
{
var gl = this.gl;
this.gl.bindBuffer(gl.ARRAY_BUFFER, buffer.webGLBuffer);
this.activeBuffer = buffer;
return true;
}
return false;
},
/**
* This method is called as a result of the `WebGLPipeline.batchQuad` method, right before a quad
* belonging to a Game Object is about to be added to the batch. When this is called, the
* renderer has just performed a flush. It will bind the current render target, if any are set
* and finally call the `onPreBatch` hook.
*
* It is also called as part of the `PipelineManager.preBatch` method when processing Post FX Pipelines.
*
* @method Phaser.Renderer.WebGL.WebGLPipeline#preBatch
* @since 3.50.0
*
* @param {(Phaser.GameObjects.GameObject|Phaser.Cameras.Scene2D.Camera)} [gameObject] - The Game Object or Camera that invoked this pipeline, if any.
*
* @return {this} This WebGLPipeline instance.
*/
preBatch: function (gameObject)
{
if (this.currentRenderTarget)
{
this.currentRenderTarget.bind();
}
this.onPreBatch(gameObject);
return this;
},
/**
* This method is called as a result of the `WebGLPipeline.batchQuad` method, right after a quad
* belonging to a Game Object has been added to the batch. When this is called, the
* renderer has just performed a flush.
*
* It calls the `onDraw` hook followed by the `onPostBatch` hook, which can be used to perform
* additional Post FX Pipeline processing.
*
* It is also called as part of the `PipelineManager.postBatch` method when processing Post FX Pipelines.
*
* @method Phaser.Renderer.WebGL.WebGLPipeline#postBatch
* @since 3.50.0
*
* @param {(Phaser.GameObjects.GameObject|Phaser.Cameras.Scene2D.Camera)} [gameObject] - The Game Object or Camera that invoked this pipeline, if any.
*
* @return {this} This WebGLPipeline instance.
*/
postBatch: function (gameObject)
{
this.onDraw(this.currentRenderTarget);
this.onPostBatch(gameObject);
return this;
},
/**
* This method is only used by Sprite FX and Post FX Pipelines and those that extend from them.
*
* This method is called every time the `postBatch` method is called and is passed a
* reference to the current render target.
*
* At the very least a Post FX Pipeline should call `this.bindAndDraw(renderTarget)`,
* however, you can do as much additional processing as you like in this method if
* you override it from within your own pipelines.
*
* @method Phaser.Renderer.WebGL.WebGLPipeline#onDraw
* @since 3.50.0
*
* @param {Phaser.Renderer.WebGL.RenderTarget} renderTarget - The Render Target.
* @param {Phaser.Renderer.WebGL.RenderTarget} [swapTarget] - A Swap Render Target, useful for double-buffer effects. Only set by SpriteFX Pipelines.
*/
onDraw: function ()
{
},
/**
* This method is called every time the Pipeline Manager deactivates this pipeline, swapping from
* it to another one. This happens after a call to `flush` and before the new pipeline is bound.
*
* @method Phaser.Renderer.WebGL.WebGLPipeline#unbind
* @since 3.50.0
*/
unbind: function ()
{
if (this.currentRenderTarget)
{
this.currentRenderTarget.unbind();
}
},
/**
* Uploads the vertex data and emits a draw call for the current batch of vertices.
*
* @method Phaser.Renderer.WebGL.WebGLPipeline#flush
* @fires Phaser.Renderer.WebGL.Pipelines.Events#BEFORE_FLUSH
* @fires Phaser.Renderer.WebGL.Pipelines.Events#AFTER_FLUSH
* @since 3.0.0
*
* @param {boolean} [isPostFlush=false] - Was this flush invoked as part of a post-process, or not?
*
* @return {this} This WebGLPipeline instance.
*/
flush: function (isPostFlush)
{
if (isPostFlush === undefined) { isPostFlush = false; }
if (this.vertexCount > 0)
{
this.emit(Events.BEFORE_FLUSH, this, isPostFlush);
this.onBeforeFlush(isPostFlush);
var gl = this.gl;
var vertexCount = this.vertexCount;
var vertexSize = this.currentShader.vertexSize;
var topology = this.topology;
if (this.active)
{
this.setVertexBuffer();
if (vertexCount === this.vertexCapacity)
{
gl.bufferData(gl.ARRAY_BUFFER, this.vertexData, gl.DYNAMIC_DRAW);
}
else
{
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.bytes.subarray(0, vertexCount * vertexSize));
}
var i;
var entry;
var texture;
var batch = this.batch;
var activeTextures = this.activeTextures;
if (this.forceZero)
{
// Single Texture Pipeline
if (!activeTextures[0])
{
gl.activeTexture(gl.TEXTURE0);
}
for (i = 0; i < batch.length; i++)
{
entry = batch[i];
texture = entry.texture[0];
if (activeTextures[0] !== texture)
{
gl.bindTexture(gl.TEXTURE_2D, texture.webGLTexture);
activeTextures[0] = texture;
}
gl.drawArrays(topology, entry.start, entry.count);
}
}
else
{
for (i = 0; i < batch.length; i++)
{
entry = batch[i];
for (var t = 0; t <= entry.maxUnit; t++)
{
texture = entry.texture[t];
if (activeTextures[t] !== texture)
{
gl.activeTexture(gl.TEXTURE0 + t);
gl.bindTexture(gl.TEXTURE_2D, texture.webGLTexture);
activeTextures[t] = texture;
}
}
gl.drawArrays(topology, entry.start, entry.count);
}
}
}
this.vertexCount = 0;
this.batch.length = 0;
this.currentBatch = null;
this.currentTexture = null;
this.currentUnit = 0;
this.emit(Events.AFTER_FLUSH, this, isPostFlush);
this.onAfterFlush(isPostFlush);
}
return this;
},
/**
* By default this is an empty method hook that you can override and use in your own custom pipelines.
*
* This method is called every time the Pipeline Manager makes this the active pipeline. It is called
* at the end of the `WebGLPipeline.bind` method, after the current shader has been set. The current
* shader is passed to this hook.
*
* For example, if a display list has 3 Sprites in it that all use the same pipeline, this hook will
* only be called for the first one, as the 2nd and 3rd Sprites do not cause the pipeline to be changed.
*
* If you need to listen for that event instead, use the `onBind` hook.
*
* @method Phaser.Renderer.WebGL.WebGLPipeline#onActive
* @since 3.50.0
*
* @param {Phaser.Renderer.WebGL.WebGLShader} currentShader - The shader that was set as current.
*/
onActive: function ()
{
},
/**
* By default this is an empty method hook that you can override and use in your own custom pipelines.
*
* This method is called every time a **Game Object** asks the Pipeline Manager to use this pipeline,
* even if the pipeline is already active.
*
* Unlike the `onActive` method, which is only called when the Pipeline Manager makes this pipeline
* active, this hook is called for every Game Object that requests use of this pipeline, allowing you to
* perform per-object set-up, such as loading shader uniform data.
*
* @method Phaser.Renderer.WebGL.WebGLPipeline#onBind
* @since 3.50.0
*
* @param {Phaser.GameObjects.GameObject} [gameObject] - The Game Object that invoked this pipeline, if any.
*/
onBind: function ()
{
},
/**
* By default this is an empty method hook that you can override and use in your own custom pipelines.
*
* This method is called when the Pipeline Manager needs to rebind this pipeline. This happens after a
* pipeline has been cleared, usually when passing control over to a 3rd party WebGL library, like Spine,
* and then returing to Phaser again.
*
* @method Phaser.Renderer.WebGL.WebGLPipeline#onRebind
* @since 3.50.0
*/
onRebind: function ()
{
},
/**
* By default this is an empty method hook that you can override and use in your own custom pipelines.
*
* This method is called every time the `batchQuad` or `batchTri` methods are called. If this was
* as a result of a Game Object, then the Game Object reference is passed to this hook too.
*
* This hook is called _after_ the quad (or tri) has been added to the batch, so you can safely
* call 'flush' from within this.
*
* Note that Game Objects may c