phaser
Version:
A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers.
1,563 lines (1,323 loc) • 98.3 kB
JavaScript
/**
* @author Richard Davey <rich@photonstorm.com>
* @author Felipe Alfonso <@bitnenfer>
* @copyright 2020 Photon Storm Ltd.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var ArrayRemove = require('../../utils/array/Remove');
var CameraEvents = require('../../cameras/2d/events');
var Class = require('../../utils/Class');
var CONST = require('../../const');
var EventEmitter = require('eventemitter3');
var Events = require('../events');
var GameEvents = require('../../core/events');
var IsSizePowerOfTwo = require('../../math/pow2/IsSizePowerOfTwo');
var Matrix4 = require('../../math/Matrix4');
var NOOP = require('../../utils/NOOP');
var PipelineManager = require('./PipelineManager');
var RenderTarget = require('./RenderTarget');
var ScaleEvents = require('../../scale/events');
var TextureEvents = require('../../textures/events');
var Utils = require('./Utils');
var WebGLSnapshot = require('../snapshot/WebGLSnapshot');
/**
* @callback WebGLContextCallback
*
* @param {Phaser.Renderer.WebGL.WebGLRenderer} renderer - The WebGL Renderer which owns the context.
*/
/**
* @classdesc
* WebGLRenderer is a class that contains the needed functionality to keep the
* WebGLRenderingContext state clean. The main idea of the WebGLRenderer is to keep track of
* any context change that happens for WebGL rendering inside of Phaser. This means
* if raw webgl functions are called outside the WebGLRenderer of the Phaser WebGL
* rendering ecosystem they might pollute the current WebGLRenderingContext state producing
* unexpected behavior. It's recommended that WebGL interaction is done through
* WebGLRenderer and/or WebGLPipeline.
*
* @class WebGLRenderer
* @extends Phaser.Events.EventEmitter
* @memberof Phaser.Renderer.WebGL
* @constructor
* @since 3.0.0
*
* @param {Phaser.Game} game - The Game instance which owns this WebGL Renderer.
*/
var WebGLRenderer = new Class({
Extends: EventEmitter,
initialize:
function WebGLRenderer (game)
{
EventEmitter.call(this);
var gameConfig = game.config;
var contextCreationConfig = {
alpha: gameConfig.transparent,
desynchronized: gameConfig.desynchronized,
depth: false,
antialias: gameConfig.antialiasGL,
premultipliedAlpha: gameConfig.premultipliedAlpha,
stencil: true,
failIfMajorPerformanceCaveat: gameConfig.failIfMajorPerformanceCaveat,
powerPreference: gameConfig.powerPreference
};
/**
* The local configuration settings of this WebGL Renderer.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#config
* @type {object}
* @since 3.0.0
*/
this.config = {
clearBeforeRender: gameConfig.clearBeforeRender,
antialias: gameConfig.antialias,
backgroundColor: gameConfig.backgroundColor,
contextCreation: contextCreationConfig,
roundPixels: gameConfig.roundPixels,
maxTextures: gameConfig.maxTextures,
maxTextureSize: gameConfig.maxTextureSize,
batchSize: gameConfig.batchSize,
maxLights: gameConfig.maxLights,
mipmapFilter: gameConfig.mipmapFilter
};
/**
* The Game instance which owns this WebGL Renderer.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#game
* @type {Phaser.Game}
* @since 3.0.0
*/
this.game = game;
/**
* A constant which allows the renderer to be easily identified as a WebGL Renderer.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#type
* @type {number}
* @since 3.0.0
*/
this.type = CONST.WEBGL;
/**
* An instance of the Pipeline Manager class, that handles all WebGL Pipelines.
*
* Use this to manage all of your interactions with pipelines, such as adding, getting,
* setting and rendering them.
*
* The Pipeline Manager class is created in the `init` method and then populated
* with pipelines during the `boot` method.
*
* Prior to Phaser v3.50.0 this was just a plain JavaScript object, not a class.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#pipelines
* @type {Phaser.Renderer.WebGL.PipelineManager}
* @since 3.50.0
*/
this.pipelines = null;
/**
* The width of the canvas being rendered to.
* This is populated in the onResize event handler.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#width
* @type {number}
* @since 3.0.0
*/
this.width = 0;
/**
* The height of the canvas being rendered to.
* This is populated in the onResize event handler.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#height
* @type {number}
* @since 3.0.0
*/
this.height = 0;
/**
* The canvas which this WebGL Renderer draws to.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#canvas
* @type {HTMLCanvasElement}
* @since 3.0.0
*/
this.canvas = game.canvas;
/**
* An array of blend modes supported by the WebGL Renderer.
*
* This array includes the default blend modes as well as any custom blend modes added through {@link #addBlendMode}.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#blendModes
* @type {array}
* @default []
* @since 3.0.0
*/
this.blendModes = [];
/**
* This property is set to `true` if the WebGL context of the renderer is lost.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#contextLost
* @type {boolean}
* @default false
* @since 3.0.0
*/
this.contextLost = false;
/**
* Details about the currently scheduled snapshot.
*
* If a non-null `callback` is set in this object, a snapshot of the canvas will be taken after the current frame is fully rendered.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#snapshotState
* @type {Phaser.Types.Renderer.Snapshot.SnapshotState}
* @since 3.0.0
*/
this.snapshotState = {
x: 0,
y: 0,
width: 1,
height: 1,
getPixel: false,
callback: null,
type: 'image/png',
encoder: 0.92,
isFramebuffer: false,
bufferWidth: 0,
bufferHeight: 0
};
/**
* Cached value for the last texture unit that was used.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#currentActiveTexture
* @type {number}
* @since 3.1.0
*/
this.currentActiveTexture = 0;
/**
* Contains the current starting active texture unit.
* This value is constantly updated and should be treated as read-only by your code.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#startActiveTexture
* @type {number}
* @since 3.50.0
*/
this.startActiveTexture = 0;
/**
* The maximum number of textures the GPU can handle. The minimum under the WebGL1 spec is 8.
* This is set via the Game Config `maxTextures` property and should never be changed after boot.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#maxTextures
* @type {number}
* @since 3.50.0
*/
this.maxTextures = 0;
/**
* An array of the available WebGL texture units, used to populate the uSampler uniforms.
*
* This array is populated during the init phase and should never be changed after boot.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#textureIndexes
* @type {array}
* @since 3.50.0
*/
this.textureIndexes;
/**
* An array of default temporary WebGL Textures.
*
* This array is populated during the init phase and should never be changed after boot.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#tempTextures
* @type {array}
* @since 3.50.0
*/
this.tempTextures;
/**
* The currently bound texture at texture unit zero, if any.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#textureZero
* @type {?WebGLTexture}
* @since 3.50.0
*/
this.textureZero;
/**
* The currently bound normal map texture at texture unit one, if any.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#normalTexture
* @type {?WebGLTexture}
* @since 3.50.0
*/
this.normalTexture;
/**
* The currently bound framebuffer in use.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#currentFramebuffer
* @type {WebGLFramebuffer}
* @default null
* @since 3.0.0
*/
this.currentFramebuffer = null;
/**
* A stack into which the frame buffer objects are pushed and popped.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#fboStack
* @type {WebGLFramebuffer[]}
* @since 3.50.0
*/
this.fboStack = [];
/**
* Current WebGLProgram in use.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#currentProgram
* @type {WebGLProgram}
* @default null
* @since 3.0.0
*/
this.currentProgram = null;
/**
* Current blend mode in use
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#currentBlendMode
* @type {number}
* @since 3.0.0
*/
this.currentBlendMode = Infinity;
/**
* Indicates if the the scissor state is enabled in WebGLRenderingContext
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#currentScissorEnabled
* @type {boolean}
* @default false
* @since 3.0.0
*/
this.currentScissorEnabled = false;
/**
* Stores the current scissor data
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#currentScissor
* @type {Uint32Array}
* @since 3.0.0
*/
this.currentScissor = null;
/**
* Stack of scissor data
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#scissorStack
* @type {Uint32Array}
* @since 3.0.0
*/
this.scissorStack = [];
/**
* The handler to invoke when the context is lost.
* This should not be changed and is set in the boot method.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#contextLostHandler
* @type {function}
* @since 3.19.0
*/
this.contextLostHandler = NOOP;
/**
* The handler to invoke when the context is restored.
* This should not be changed and is set in the boot method.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#contextRestoredHandler
* @type {function}
* @since 3.19.0
*/
this.contextRestoredHandler = NOOP;
/**
* The underlying WebGL context of the renderer.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#gl
* @type {WebGLRenderingContext}
* @default null
* @since 3.0.0
*/
this.gl = null;
/**
* Array of strings that indicate which WebGL extensions are supported by the browser.
* This is populated in the `boot` method.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#supportedExtensions
* @type {string[]}
* @default null
* @since 3.0.0
*/
this.supportedExtensions = null;
/**
* If the browser supports the `ANGLE_instanced_arrays` extension, this property will hold
* a reference to the glExtension for it.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#instancedArraysExtension
* @type {ANGLE_instanced_arrays}
* @default null
* @since 3.50.0
*/
this.instancedArraysExtension = null;
/**
* If the browser supports the `OES_vertex_array_object` extension, this property will hold
* a reference to the glExtension for it.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#vaoExtension
* @type {OES_vertex_array_object}
* @default null
* @since 3.50.0
*/
this.vaoExtension = null;
/**
* The WebGL Extensions loaded into the current context.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#extensions
* @type {object}
* @default {}
* @since 3.0.0
*/
this.extensions = {};
/**
* Stores the current WebGL component formats for further use.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#glFormats
* @type {array}
* @default []
* @since 3.2.0
*/
this.glFormats = [];
/**
* Stores the supported WebGL texture compression formats.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#compression
* @type {object}
* @since 3.8.0
*/
this.compression = {
ETC1: false,
PVRTC: false,
S3TC: false
};
/**
* Cached drawing buffer height to reduce gl calls.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#drawingBufferHeight
* @type {number}
* @readonly
* @since 3.11.0
*/
this.drawingBufferHeight = 0;
/**
* A blank 32x32 transparent texture, as used by the Graphics system where needed.
* This is set in the `boot` method.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#blankTexture
* @type {WebGLTexture}
* @readonly
* @since 3.12.0
*/
this.blankTexture = null;
/**
* A pure white 4x4 texture, as used by the Graphics system where needed.
* This is set in the `boot` method.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#whiteTexture
* @type {WebGLTexture}
* @readonly
* @since 3.50.0
*/
this.whiteTexture = null;
/**
* The total number of masks currently stacked.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#maskCount
* @type {number}
* @since 3.17.0
*/
this.maskCount = 0;
/**
* The mask stack.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#maskStack
* @type {Phaser.Display.Masks.GeometryMask[]}
* @since 3.17.0
*/
this.maskStack = [];
/**
* Internal property that tracks the currently set mask.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#currentMask
* @type {any}
* @since 3.17.0
*/
this.currentMask = { mask: null, camera: null };
/**
* Internal property that tracks the currently set camera mask.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#currentCameraMask
* @type {any}
* @since 3.17.0
*/
this.currentCameraMask = { mask: null, camera: null };
/**
* Internal gl function mapping for uniform look-up.
* https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/uniform
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#glFuncMap
* @type {any}
* @since 3.17.0
*/
this.glFuncMap = null;
/**
* The `type` of the Game Object being currently rendered.
* This can be used by advanced render functions for batching look-ahead.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#currentType
* @type {string}
* @since 3.19.0
*/
this.currentType = '';
/**
* Is the `type` of the Game Object being currently rendered different than the
* type of the object before it in the display list? I.e. it's a 'new' type.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#newType
* @type {boolean}
* @since 3.19.0
*/
this.newType = false;
/**
* Does the `type` of the next Game Object in the display list match that
* of the object being currently rendered?
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#nextTypeMatch
* @type {boolean}
* @since 3.19.0
*/
this.nextTypeMatch = false;
/**
* Is the Game Object being currently rendered the final one in the list?
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#finalType
* @type {boolean}
* @since 3.50.0
*/
this.finalType = false;
/**
* The mipmap magFilter to be used when creating textures.
*
* You can specify this as a string in the game config, i.e.:
*
* `renderer: { mipmapFilter: 'NEAREST_MIPMAP_LINEAR' }`
*
* The 6 options for WebGL1 are, in order from least to most computationally expensive:
*
* NEAREST (for pixel art)
* LINEAR (the default)
* NEAREST_MIPMAP_NEAREST
* LINEAR_MIPMAP_NEAREST
* NEAREST_MIPMAP_LINEAR
* LINEAR_MIPMAP_LINEAR
*
* Mipmaps only work with textures that are fully power-of-two in size.
*
* For more details see https://webglfundamentals.org/webgl/lessons/webgl-3d-textures.html
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#mipmapFilter
* @type {GLenum}
* @since 3.21.0
*/
this.mipmapFilter = null;
/**
* The number of times the renderer had to flush this frame, due to running out of texture units.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#textureFlush
* @type {number}
* @since 3.50.0
*/
this.textureFlush = 0;
/**
* The default scissor, set during `preRender` and modified during `resize`.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#defaultScissor
* @type {number[]}
* @private
* @since 3.50.0
*/
this.defaultScissor = [ 0, 0, 0, 0 ];
/**
* Has this renderer fully booted yet?
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#isBooted
* @type {boolean}
* @since 3.50.0
*/
this.isBooted = false;
/**
* A Render Target you can use to capture the current state of the Renderer.
*
* A Render Target encapsulates a framebuffer and texture for the WebGL Renderer.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#renderTarget
* @type {Phaser.Renderer.WebGL.RenderTarget}
* @since 3.50.0
*/
this.renderTarget = null;
/**
* The global game Projection matrix, used by shaders as 'uProjectionMatrix' uniform.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#projectionMatrix
* @type {Phaser.Math.Matrix4}
* @since 3.50.0
*/
this.projectionMatrix;
/**
* The cached width of the Projection matrix.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#projectionWidth
* @type {number}
* @since 3.50.0
*/
this.projectionWidth = 0;
/**
* The cached height of the Projection matrix.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#projectionHeight
* @type {number}
* @since 3.50.0
*/
this.projectionHeight = 0;
this.init(this.config);
},
/**
* Creates a new WebGLRenderingContext and initializes all internal state.
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#init
* @since 3.0.0
*
* @param {object} config - The configuration object for the renderer.
*
* @return {this} This WebGLRenderer instance.
*/
init: function (config)
{
var gl;
var game = this.game;
var canvas = this.canvas;
var clearColor = config.backgroundColor;
// Did they provide their own context?
if (game.config.context)
{
gl = game.config.context;
}
else
{
gl = canvas.getContext('webgl', config.contextCreation) || canvas.getContext('experimental-webgl', config.contextCreation);
}
if (!gl || gl.isContextLost())
{
this.contextLost = true;
throw new Error('WebGL unsupported');
}
this.gl = gl;
var _this = this;
this.contextLostHandler = function (event)
{
_this.contextLost = true;
_this.game.events.emit(GameEvents.CONTEXT_LOST, _this);
event.preventDefault();
};
this.contextRestoredHandler = function ()
{
_this.contextLost = false;
_this.init(_this.config);
_this.game.events.emit(GameEvents.CONTEXT_RESTORED, _this);
};
canvas.addEventListener('webglcontextlost', this.contextLostHandler, false);
canvas.addEventListener('webglcontextrestored', this.contextRestoredHandler, false);
// Set it back into the Game, so developers can access it from there too
game.context = gl;
for (var i = 0; i <= 27; i++)
{
this.blendModes.push({ func: [ gl.ONE, gl.ONE_MINUS_SRC_ALPHA ], equation: gl.FUNC_ADD });
}
// ADD
this.blendModes[1].func = [ gl.ONE, gl.DST_ALPHA ];
// MULTIPLY
this.blendModes[2].func = [ gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA ];
// SCREEN
this.blendModes[3].func = [ gl.ONE, gl.ONE_MINUS_SRC_COLOR ];
// ERASE
this.blendModes[17] = { func: [ gl.ZERO, gl.ONE_MINUS_SRC_ALPHA ], equation: gl.FUNC_REVERSE_SUBTRACT };
this.glFormats[0] = gl.BYTE;
this.glFormats[1] = gl.SHORT;
this.glFormats[2] = gl.UNSIGNED_BYTE;
this.glFormats[3] = gl.UNSIGNED_SHORT;
this.glFormats[4] = gl.FLOAT;
// Set the gl function map
this.glFuncMap = {
mat2: { func: gl.uniformMatrix2fv, length: 1, matrix: true },
mat3: { func: gl.uniformMatrix3fv, length: 1, matrix: true },
mat4: { func: gl.uniformMatrix4fv, length: 1, matrix: true },
'1f': { func: gl.uniform1f, length: 1 },
'1fv': { func: gl.uniform1fv, length: 1 },
'1i': { func: gl.uniform1i, length: 1 },
'1iv': { func: gl.uniform1iv, length: 1 },
'2f': { func: gl.uniform2f, length: 2 },
'2fv': { func: gl.uniform2fv, length: 1 },
'2i': { func: gl.uniform2i, length: 2 },
'2iv': { func: gl.uniform2iv, length: 1 },
'3f': { func: gl.uniform3f, length: 3 },
'3fv': { func: gl.uniform3fv, length: 1 },
'3i': { func: gl.uniform3i, length: 3 },
'3iv': { func: gl.uniform3iv, length: 1 },
'4f': { func: gl.uniform4f, length: 4 },
'4fv': { func: gl.uniform4fv, length: 1 },
'4i': { func: gl.uniform4i, length: 4 },
'4iv': { func: gl.uniform4iv, length: 1 }
};
// Load supported extensions
var exts = gl.getSupportedExtensions();
if (!config.maxTextures || config.maxTextures === -1)
{
config.maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
}
if (!config.maxTextureSize)
{
config.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
}
var extString = 'WEBGL_compressed_texture_';
var wkExtString = 'WEBKIT_' + extString;
this.compression.ETC1 = gl.getExtension(extString + 'etc1') || gl.getExtension(wkExtString + 'etc1');
this.compression.PVRTC = gl.getExtension(extString + 'pvrtc') || gl.getExtension(wkExtString + 'pvrtc');
this.compression.S3TC = gl.getExtension(extString + 's3tc') || gl.getExtension(wkExtString + 's3tc');
this.supportedExtensions = exts;
var angleString = 'ANGLE_instanced_arrays';
this.instancedArraysExtension = (exts.indexOf(angleString) > -1) ? gl.getExtension(angleString) : null;
var vaoString = 'OES_vertex_array_object';
this.vaoExtension = (exts.indexOf(vaoString) > -1) ? gl.getExtension(vaoString) : null;
// Setup initial WebGL state
gl.disable(gl.DEPTH_TEST);
gl.disable(gl.CULL_FACE);
gl.enable(gl.BLEND);
gl.clearColor(clearColor.redGL, clearColor.greenGL, clearColor.blueGL, clearColor.alphaGL);
// Mipmaps
this.mipmapFilter = gl[config.mipmapFilter];
// Check maximum supported textures
this.maxTextures = Utils.checkShaderMax(gl, config.maxTextures);
this.textureIndexes = [];
// Create temporary WebGL textures
var tempTextures = this.tempTextures;
if (Array.isArray(tempTextures))
{
for (var t = 0; i < this.maxTextures; t++)
{
gl.deleteTexture(tempTextures[t]);
}
}
else
{
tempTextures = new Array(this.maxTextures);
}
// Create temp textures to stop WebGL errors on mac os
for (var index = 0; index < this.maxTextures; index++)
{
var tempTexture = gl.createTexture();
gl.activeTexture(gl.TEXTURE0 + index);
gl.bindTexture(gl.TEXTURE_2D, tempTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([ 0, 0, 255, 255 ]));
tempTextures[index] = tempTexture;
this.textureIndexes.push(index);
}
this.tempTextures = tempTextures;
// Reset to texture 1 (texture zero is reserved for framebuffers)
this.currentActiveTexture = 1;
this.startActiveTexture++;
gl.activeTexture(gl.TEXTURE1);
this.pipelines = new PipelineManager(this);
this.setBlendMode(CONST.BlendModes.NORMAL);
this.projectionMatrix = new Matrix4().identity();
game.textures.once(TextureEvents.READY, this.boot, this);
return this;
},
/**
* Internal boot handler. Calls 'boot' on each pipeline.
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#boot
* @private
* @since 3.11.0
*/
boot: function ()
{
var game = this.game;
var pipelineManager = this.pipelines;
var baseSize = game.scale.baseSize;
this.width = baseSize.width;
this.height = baseSize.height;
this.isBooted = true;
this.renderTarget = new RenderTarget(this, this.width, this.height, 1, 0, true, true);
// Set-up pipelines
pipelineManager.boot(game.config.pipeline);
// Set-up default textures, fbo and scissor
this.blankTexture = game.textures.getFrame('__DEFAULT');
this.whiteTexture = game.textures.getFrame('__WHITE');
var gl = this.gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.enable(gl.SCISSOR_TEST);
game.scale.on(ScaleEvents.RESIZE, this.onResize, this);
this.resize(baseSize.width, baseSize.height);
},
/**
* The event handler that manages the `resize` event dispatched by the Scale Manager.
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#onResize
* @since 3.16.0
*
* @param {Phaser.Structs.Size} gameSize - The default Game Size object. This is the un-modified game dimensions.
* @param {Phaser.Structs.Size} baseSize - The base Size object. The game dimensions. The canvas width / height values match this.
*/
onResize: function (gameSize, baseSize)
{
// Has the underlying canvas size changed?
if (baseSize.width !== this.width || baseSize.height !== this.height)
{
this.resize(baseSize.width, baseSize.height);
}
},
/**
* Binds the WebGL Renderers Render Target, so all drawn content is now redirected to it.
*
* Make sure to call `endCapture` when you are finished.
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#beginCapture
* @since 3.50.0
*
* @param {number} [width] - Optional new width of the Render Target.
* @param {number} [height] - Optional new height of the Render Target.
*/
beginCapture: function (width, height)
{
if (width === undefined) { width = this.width; }
if (height === undefined) { height = this.height; }
this.renderTarget.bind(true, width, height);
this.setProjectionMatrix(width, height);
this.resetTextures();
},
/**
* Unbinds the WebGL Renderers Render Target and returns it, stopping any further content being drawn to it.
*
* If the viewport or scissors were modified during the capture, you should reset them by calling
* `resetViewport` and `resetScissor` accordingly.
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#endCapture
* @since 3.50.0
*
* @return {Phaser.Renderer.WebGL.RenderTarget} A reference to the WebGL Renderer Render Target.
*/
endCapture: function ()
{
this.renderTarget.unbind(true);
this.resetProjectionMatrix();
return this.renderTarget;
},
/**
* Resizes the drawing buffer to match that required by the Scale Manager.
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#resize
* @fires Phaser.Renderer.Events#RESIZE
* @since 3.0.0
*
* @param {number} [width] - The new width of the renderer.
* @param {number} [height] - The new height of the renderer.
*
* @return {this} This WebGLRenderer instance.
*/
resize: function (width, height)
{
var gl = this.gl;
this.width = width;
this.height = height;
this.setProjectionMatrix(width, height);
gl.viewport(0, 0, width, height);
this.drawingBufferHeight = gl.drawingBufferHeight;
gl.scissor(0, (gl.drawingBufferHeight - height), width, height);
this.defaultScissor[2] = width;
this.defaultScissor[3] = height;
this.emit(Events.RESIZE, width, height);
return this;
},
/**
* Gets the aspect ratio of the WebGLRenderer dimensions.
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#getAspectRatio
* @since 3.50.0
*
* @return {number} The aspect ratio of the WebGLRenderer dimensions.
*/
getAspectRatio: function ()
{
return this.width / this.height;
},
/**
* Sets the Projection Matrix of this renderer to the given dimensions.
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#setProjectionMatrix
* @since 3.50.0
*
* @param {number} width - The new width of the Projection Matrix.
* @param {number} height - The new height of the Projection Matrix.
*
* @return {this} This WebGLRenderer instance.
*/
setProjectionMatrix: function (width, height)
{
if (width !== this.projectionWidth || height !== this.projectionHeight)
{
this.projectionWidth = width;
this.projectionHeight = height;
this.projectionMatrix.ortho(0, width, height, 0, -1000, 1000);
}
return this;
},
/**
* Resets the Projection Matrix back to this renderers width and height.
*
* This is called during `endCapture`, should the matrix have been changed
* as a result of the capture process.
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#resetProjectionMatrix
* @since 3.50.0
*/
resetProjectionMatrix: function ()
{
this.projectionWidth = this.width;
this.projectionHeight = this.height;
this.projectionMatrix.ortho(0, this.width, this.height, 0, -1000, 1000);
},
/**
* Checks if a WebGL extension is supported
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#hasExtension
* @since 3.0.0
*
* @param {string} extensionName - Name of the WebGL extension
*
* @return {boolean} `true` if the extension is supported, otherwise `false`.
*/
hasExtension: function (extensionName)
{
return this.supportedExtensions ? this.supportedExtensions.indexOf(extensionName) : false;
},
/**
* Loads a WebGL extension
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#getExtension
* @since 3.0.0
*
* @param {string} extensionName - The name of the extension to load.
*
* @return {object} WebGL extension if the extension is supported
*/
getExtension: function (extensionName)
{
if (!this.hasExtension(extensionName)) { return null; }
if (!(extensionName in this.extensions))
{
this.extensions[extensionName] = this.gl.getExtension(extensionName);
}
return this.extensions[extensionName];
},
/**
* Flushes the current pipeline if the pipeline is bound
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#flush
* @since 3.0.0
*/
flush: function ()
{
this.pipelines.flush();
},
/**
* Pushes a new scissor state. This is used to set nested scissor states.
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#pushScissor
* @since 3.0.0
*
* @param {number} x - The x position of the scissor.
* @param {number} y - The y position of the scissor.
* @param {number} width - The width of the scissor.
* @param {number} height - The height of the scissor.
* @param {number} [drawingBufferHeight] - Optional drawingBufferHeight override value.
*
* @return {number[]} An array containing the scissor values.
*/
pushScissor: function (x, y, width, height, drawingBufferHeight)
{
if (drawingBufferHeight === undefined) { drawingBufferHeight = this.drawingBufferHeight; }
var scissorStack = this.scissorStack;
var scissor = [ x, y, width, height ];
scissorStack.push(scissor);
this.setScissor(x, y, width, height, drawingBufferHeight);
this.currentScissor = scissor;
return scissor;
},
/**
* Sets the current scissor state.
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#setScissor
* @since 3.0.0
*
* @param {number} x - The x position of the scissor.
* @param {number} y - The y position of the scissor.
* @param {number} width - The width of the scissor.
* @param {number} height - The height of the scissor.
* @param {number} [drawingBufferHeight] - Optional drawingBufferHeight override value.
*/
setScissor: function (x, y, width, height, drawingBufferHeight)
{
if (drawingBufferHeight === undefined) { drawingBufferHeight = this.drawingBufferHeight; }
var gl = this.gl;
var current = this.currentScissor;
var setScissor = (width > 0 && height > 0);
if (current && setScissor)
{
var cx = current[0];
var cy = current[1];
var cw = current[2];
var ch = current[3];
setScissor = (cx !== x || cy !== y || cw !== width || ch !== height);
}
if (setScissor)
{
this.flush();
// https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/scissor
gl.scissor(x, (drawingBufferHeight - y - height), width, height);
}
},
/**
* Resets the gl scissor state to be whatever the current scissor is, if there is one, without
* modifying the scissor stack.
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#resetScissor
* @since 3.50.0
*/
resetScissor: function ()
{
var gl = this.gl;
gl.enable(gl.SCISSOR_TEST);
var current = this.currentScissor;
if (current)
{
var x = current[0];
var y = current[1];
var width = current[2];
var height = current[3];
if (width > 0 && height > 0)
{
gl.scissor(x, (this.drawingBufferHeight - y - height), width, height);
}
}
},
/**
* Pops the last scissor state and sets it.
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#popScissor
* @since 3.0.0
*/
popScissor: function ()
{
var scissorStack = this.scissorStack;
// Remove the current scissor
scissorStack.pop();
// Reset the previous scissor
var scissor = scissorStack[scissorStack.length - 1];
if (scissor)
{
this.setScissor(scissor[0], scissor[1], scissor[2], scissor[3]);
}
this.currentScissor = scissor;
},
/**
* Is there an active stencil mask?
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#hasActiveStencilMask
* @since 3.17.0
*
* @return {boolean} `true` if there is an active stencil mask, otherwise `false`.
*/
hasActiveStencilMask: function ()
{
var mask = this.currentMask.mask;
var camMask = this.currentCameraMask.mask;
return ((mask && mask.isStencil) || (camMask && camMask.isStencil));
},
/**
* Resets the gl viewport to the current renderer dimensions.
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#resetViewport
* @since 3.50.0
*/
resetViewport: function ()
{
var gl = this.gl;
gl.viewport(0, 0, this.width, this.height);
this.drawingBufferHeight = gl.drawingBufferHeight;
},
/**
* Sets the blend mode to the value given.
*
* If the current blend mode is different from the one given, the pipeline is flushed and the new
* blend mode is enabled.
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#setBlendMode
* @since 3.0.0
*
* @param {number} blendModeId - The blend mode to be set. Can be a `BlendModes` const or an integer value.
* @param {boolean} [force=false] - Force the blend mode to be set, regardless of the currently set blend mode.
*
* @return {boolean} `true` if the blend mode was changed as a result of this call, forcing a flush, otherwise `false`.
*/
setBlendMode: function (blendModeId, force)
{
if (force === undefined) { force = false; }
var gl = this.gl;
var blendMode = this.blendModes[blendModeId];
if (force || (blendModeId !== CONST.BlendModes.SKIP_CHECK && this.currentBlendMode !== blendModeId))
{
this.flush();
gl.enable(gl.BLEND);
gl.blendEquation(blendMode.equation);
if (blendMode.func.length > 2)
{
gl.blendFuncSeparate(blendMode.func[0], blendMode.func[1], blendMode.func[2], blendMode.func[3]);
}
else
{
gl.blendFunc(blendMode.func[0], blendMode.func[1]);
}
this.currentBlendMode = blendModeId;
return true;
}
return false;
},
/**
* Creates a new custom blend mode for the renderer.
*
* See https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Constants#Blending_modes
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#addBlendMode
* @since 3.0.0
*
* @param {GLenum[]} func - An array containing the WebGL functions to use for the source and the destination blending factors, respectively. See the possible constants for {@link WebGLRenderingContext#blendFunc()}.
* @param {GLenum} equation - The equation to use for combining the RGB and alpha components of a new pixel with a rendered one. See the possible constants for {@link WebGLRenderingContext#blendEquation()}.
*
* @return {number} The index of the new blend mode, used for referencing it in the future.
*/
addBlendMode: function (func, equation)
{
var index = this.blendModes.push({ func: func, equation: equation });
return index - 1;
},
/**
* Updates the function bound to a given custom blend mode.
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#updateBlendMode
* @since 3.0.0
*
* @param {number} index - The index of the custom blend mode.
* @param {function} func - The function to use for the blend mode.
* @param {function} equation - The equation to use for the blend mode.
*
* @return {this} This WebGLRenderer instance.
*/
updateBlendMode: function (index, func, equation)
{
if (this.blendModes[index])
{
this.blendModes[index].func = func;
if (equation)
{
this.blendModes[index].equation = equation;
}
}
return this;
},
/**
* Removes a custom blend mode from the renderer.
* Any Game Objects still using this blend mode will error, so be sure to clear them first.
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#removeBlendMode
* @since 3.0.0
*
* @param {number} index - The index of the custom blend mode to be removed.
*
* @return {this} This WebGLRenderer instance.
*/
removeBlendMode: function (index)
{
if (index > 17 && this.blendModes[index])
{
this.blendModes.splice(index, 1);
}
return this;
},
/**
* Sets the current active texture for texture unit zero to be a blank texture.
* This only happens if there isn't a texture already in use by texture unit zero.
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#setBlankTexture
* @private
* @since 3.12.0
*/
setBlankTexture: function ()
{
this.setTexture2D(this.blankTexture.glTexture);
},
/**
* Activates the Texture Source and assigns it the next available texture unit.
* If none are available, it will flush the current pipeline first.
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#setTextureSource
* @since 3.50.0
*
* @param {Phaser.Textures.TextureSource} textureSource - The Texture Source to be assigned the texture unit.
*
* @return {number} The texture unit that was assigned to the Texture Source.
*/
setTextureSource: function (textureSource)
{
if (this.pipelines.forceZero())
{
this.setTextureZero(textureSource.glTexture, true);
return 0;
}
var gl = this.gl;
var currentActiveTexture = this.currentActiveTexture;
if (textureSource.glIndexCounter < this.startActiveTexture)
{
textureSource.glIndexCounter = this.startActiveTexture;
if (currentActiveTexture < this.maxTextures)
{
textureSource.glIndex = currentActiveTexture;
gl.activeTexture(gl.TEXTURE0 + currentActiveTexture);
gl.bindTexture(gl.TEXTURE_2D, textureSource.glTexture);
this.currentActiveTexture++;
}
else
{
// We're out of textures, so flush the batch and reset back to 0
this.flush();
this.startActiveTexture++;
this.textureFlush++;
textureSource.glIndexCounter = this.startActiveTexture;
textureSource.glIndex = 1;
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, textureSource.glTexture);
this.currentActiveTexture = 2;
}
}
return textureSource.glIndex;
},
/**
* Checks to see if the given diffuse and normal map textures are already bound, or not.
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#isNewNormalMap
* @since 3.50.0
*
* @param {WebGLTexture} texture - The WebGL diffuse texture.
* @param {WebGLTexture} normalMap - The WebGL normal map texture.
*
* @return {boolean} Returns `false` if this combination is already set, or `true` if it's a new combination.
*/
isNewNormalMap: function (texture, normalMap)
{
return (this.textureZero !== texture || this.normalTexture !== normalMap);
},
/**
* Binds a texture directly to texture unit zero then activates it.
* If the texture is already at unit zero, it skips the bind.
* Make sure to call `clearTextureZero` after using this method.
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#setTextureZero
* @since 3.50.0
*
* @param {WebGLTexture} texture - The WebGL texture that needs to be bound.
* @param {boolean} [flush=false] - Flush the pipeline if the texture is different?
*/
setTextureZero: function (texture, flush)
{
if (this.textureZero !== texture)
{
if (flush)
{
this.flush();
}
var gl = this.gl;
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
this.textureZero = texture;
}
},
/**
* Clears the texture that was directly bound to texture unit zero.
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#clearTextureZero
* @since 3.50.0
*/
clearTextureZero: function ()
{
this.textureZero = null;
},
/**
* Binds a texture directly to texture unit one then activates it.
* If the texture is already at unit one, it skips the bind.
* Make sure to call `clearNormalMap` after using this method.
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#setNormalMap
* @since 3.50.0
*
* @param {WebGLTexture} texture - The WebGL texture that needs to be bound.
*/
setNormalMap: function (texture)
{
if (this.normalTexture !== texture)
{
var gl = this.gl;
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, texture);
this.normalTexture = texture;
if (this.currentActiveTexture === 1)
{
this.currentActiveTexture = 2;
}
}
},
/**
* Clears the texture that was directly bound to texture unit one and
* increases the start active texture counter.
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#clearNormalMap
* @since 3.50.0
*/
clearNormalMap: function ()
{
this.normalTexture = null;
this.startActiveTexture++;
this.currentActiveTexture = 1;
this.textureFlush++;
},
/**
* Activates each texture, in turn, then binds them all to `null`.
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#unbindTextures
* @since 3.50.0
*
* @param {boolean} [all=false] - Reset all textures, or just the first two?
*/
unbindTextures: function ()
{
var gl = this.gl;
var temp = this.tempTextures;
for (var i = 0; i < temp.length; i++)
{
gl.activeTexture(gl.TEXTURE0 + i);
gl.bindTexture(gl.TEXTURE_2D, null);
}
this.normalTexture = null;
this.textureZero = null;
this.currentActiveTexture = 1;
this.startActiveTexture++;
this.textureFlush++;
},
/**
* Flushes the current pipeline, then resets the first two textures
* back to the default temporary