UNPKG

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
/** * @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