UNPKG

phaser

Version:

A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.

651 lines (589 loc) 19.9 kB
/** * @author Benjamin D. Richards <benjamindrichards@gmail.com> * @copyright 2013-2026 Phaser Studio Inc. * @license {@link https://opensource.org/licenses/MIT|MIT License} */ var IsSizePowerOfTwo = require('../../math/pow2/IsSizePowerOfTwo'); var Class = require('../../utils/Class'); /** * Descriptor of the context within which a drawing operation is performed. * * This consists of a subset of the global WebGL state. It includes the following: * * - Framebuffer * - Viewport * - Scissor box * - Blend mode * - Clear color * * This is analogous to a drafting table in a studio. The paper is the * framebuffer, while the rest of the data specifies masks, guides etc for * drawing. * * A DrawingContext can be copied and thrown away, allowing temporary use of * different drawing states on a framebuffer. * * @class DrawingContext * @memberof Phaser.Renderer.WebGL * @constructor * @since 4.0.0 * * @param {Phaser.Renderer.WebGL.WebGLRenderer} renderer - The renderer that owns this context. * @param {Phaser.Types.Renderer.WebGL.RenderNodes.DrawingContextOptions} [options] - The options for this context. */ var DrawingContext = new Class({ initialize: function DrawingContext (renderer, options) { if (options === undefined) { options = {}; } /** * The renderer that owns this context. * * @name Phaser.Renderer.WebGL.DrawingContext#renderer * @type {Phaser.Renderer.WebGL.WebGLRenderer} * @since 4.0.0 */ this.renderer = renderer; /** * The camera used by this context. Set this using `setCamera` to ensure * the view matrix is updated. Ensure that this is not `null` before * rendering. * * @name Phaser.Renderer.WebGL.DrawingContext#camera * @type {?Phaser.Cameras.Scene2D.Camera} * @since 4.0.0 * @readonly */ this.camera = null; this.setCamera(options.camera || null); /** * Relevant WebGL state for the DrawingContext. * Contains the framebuffer, scissor box, and viewport. * * @name Phaser.Renderer.WebGL.DrawingContext#state * @type {Phaser.Types.Renderer.WebGL.WebGLGlobalParameters} * @since 4.0.0 */ this.state = { bindings: { framebuffer: null }, blend: { // This will be automatically populated below. }, colorClearValue: options.clearColor || [ 0, 0, 0, 0 ], scissor: { box: [ 0, 0, 0, 0 ], enable: true }, viewport: [ 0, 0, 0, 0 ] }; /** * The blend mode to use when rendering. * This is an index into the renderer's blendModes array. * It is faster to check than the state object. * * @name Phaser.Renderer.WebGL.DrawingContext#blendMode * @type {number} * @default 0 * @since 4.0.0 */ this.blendMode = -1; this.setBlendMode(options.blendMode || 0); /** * Which renderbuffers in the framebuffer to clear when the DrawingContext comes into use. * This is the mask of buffers to clear: * gl.COLOR_BUFFER_BIT, gl.DEPTH_BUFFER_BIT, gl.STENCIL_BUFFER_BIT. * * @name Phaser.Renderer.WebGL.DrawingContext#autoClear * @type {number} * @default 0 * @since 4.0.0 */ this.autoClear = 0; if (options.autoClear === undefined || options.autoClear === true) { this.setAutoClear(true, true, true); } else if (Array.isArray(options.autoClear)) { this.setAutoClear.apply(this, options.autoClear); } /** * Whether to use the canvas as the framebuffer. * * @name Phaser.Renderer.WebGL.DrawingContext#useCanvas * @type {boolean} * @default false * @since 4.0.0 */ this.useCanvas = !!options.useCanvas; /** * The WebGLFramebufferWrapper which will hold the framebuffer output. * This may contain the canvas. * * @name Phaser.Renderer.WebGL.DrawingContext#framebuffer * @type {Phaser.Renderer.WebGL.Wrappers.WebGLFramebufferWrapper} * @since 4.0.0 */ this.framebuffer = null; /** * The WebGLTextureWrapper which will hold the framebuffer output. * This is only used if `useCanvas` is `false`. * * @name Phaser.Renderer.WebGL.DrawingContext#texture * @type {?Phaser.Renderer.WebGL.Wrappers.WebGLTextureWrapper} * @default null * @since 4.0.0 */ this.texture = null; /** * Whether to enable mipmaps on the framebuffer texture, if it exists. * The game must still be set to use mipmaps for this to work. * * @name Phaser.Renderer.WebGL.DrawingContext#enableMipmap * @type {boolean} * @since 4.1.0 * @default false */ this.enableMipmap = !!options.enableMipmap; /** * The pool to return to when this context is no longer needed. * Used only for temporary contexts. * * @name Phaser.Renderer.WebGL.DrawingContext#pool * @type {Phaser.Renderer.WebGL.DrawingContextPool} * @since 4.0.0 * @default null */ this.pool = options.pool || null; /** * The last time the DrawingContext was used. * This is used to determine whether the context is a candidate * for reuse in a pool. * * @name Phaser.Renderer.WebGL.DrawingContext#lastUsed * @type {number} * @since 4.0.0 */ this.lastUsed = 0; /** * The width of the framebuffer. * * @name Phaser.Renderer.WebGL.DrawingContext#width * @type {number} * @since 4.0.0 */ this.width = 0; /** * The height of the framebuffer. * * @name Phaser.Renderer.WebGL.DrawingContext#height * @type {number} * @since 4.0.0 */ this.height = 0; /** * Locks on the DrawingContext. * These are used to prevent the context from being released * to its pool. * They can be any value, so long as they are distinct. * * @name Phaser.Renderer.WebGL.DrawingContext#_locks * @type {any[]} * @private * @since 4.0.0 */ this._locks = []; if (options.copyFrom) { this.copy(options.copyFrom); } else { this.resize( options.width || renderer.width, options.height || renderer.height ); } }, /** * Resize the DrawingContext. * * If no framebuffer exists yet, a new texture and framebuffer are created at the given dimensions. If a framebuffer already exists, it is resized in place. The scissor box and viewport are reset to match the new size. * * @method Phaser.Renderer.WebGL.DrawingContext#resize * @since 4.0.0 * @param {number} width - The new width of the framebuffer. * @param {number} height - The new height of the framebuffer. */ resize: function (width, height) { width = Math.round(width); height = Math.round(height); if (width <= 0) { width = 1; } if (height <= 0) { height = 1; } if (!this.useCanvas) { if (!this.framebuffer) { var renderer = this.renderer; var gl = renderer.gl; var pow = IsSizePowerOfTwo(width, height); var magFilter = gl.NEAREST; if (renderer.config.antialias) { magFilter = gl.LINEAR; } var minFilter = magFilter; if (pow && this.enableMipmap && renderer.config.mipmapRegeneration && renderer.mipmapFilter) { minFilter = renderer.mipmapFilter; } var wrap = gl.CLAMP_TO_EDGE; if (pow) { wrap = gl.REPEAT; } this.texture = renderer.createTexture2D( 0, minFilter, magFilter, wrap, wrap, gl.RGBA, null, width, height ); this.framebuffer = renderer.createFramebuffer(this.texture, true, false); } else { this.framebuffer.resize(width, height); } } else if (!this.framebuffer) { // Create a framebuffer referencing the canvas. // This is used for the main framebuffer. // It does not need to resize after creation. this.framebuffer = this.renderer.createFramebuffer(null); } this.state.bindings.framebuffer = this.framebuffer; this.width = width; this.height = height; this.state.scissor.box = [ 0, 0, width, height ]; this.state.viewport = [ 0, 0, width, height ]; }, /** * Copy the state of another DrawingContext. * * @method Phaser.Renderer.WebGL.DrawingContext#copy * @since 4.0.0 * @param {Phaser.Renderer.WebGL.DrawingContext} source - The DrawingContext to copy from. */ copy: function (source) { var state = source.state; var blend = state.blend; var scissor = state.scissor; this.autoClear = source.autoClear; this.useCanvas = source.useCanvas; this.framebuffer = source.framebuffer; this.texture = source.texture; this.camera = source.camera; this.blendMode = source.blendMode; this.width = source.width; this.height = source.height; this.state = { bindings: { framebuffer: state.bindings.framebuffer }, blend: { color: blend.color && blend.color.slice(), enable: blend.enable, equation: blend.equation, func: blend.func }, colorClearValue: state.colorClearValue.slice(), scissor: { box: scissor.box.slice(), enable: scissor.enable }, viewport: state.viewport.slice() }; }, /** * Create a clone of the DrawingContext. This is intended to be mutated * for temporary use, and then thrown away. * * The autoClear setting is set to false unless specified. * This is because most clones reference an existing framebuffer, * which is intended to accumulate drawing operations. * * @method Phaser.Renderer.WebGL.DrawingContext#getClone * @since 4.0.0 * @param {boolean} [preserveAutoClear=false] - Whether to preserve the autoClear setting. * @return {Phaser.Renderer.WebGL.DrawingContext} The cloned DrawingContext. */ getClone: function (preserveAutoClear) { var context = new DrawingContext(this.renderer, { copyFrom: this }); if (!preserveAutoClear) { context.setAutoClear(false, false, false); } return context; }, /** * Set the buffers to clear when the DrawingContext comes into use. * * @method Phaser.Renderer.WebGL.DrawingContext#setAutoClear * @since 4.0.0 * @param {boolean} color - Whether to clear the color buffer. * @param {boolean} depth - Whether to clear the depth buffer. * @param {boolean} stencil - Whether to clear the stencil buffer. */ setAutoClear: function (color, depth, stencil) { var autoClear = 0; var gl = this.renderer.gl; if (color) { autoClear |= gl.COLOR_BUFFER_BIT; } if (depth) { autoClear |= gl.DEPTH_BUFFER_BIT; } if (stencil) { autoClear |= gl.STENCIL_BUFFER_BIT; } this.autoClear = autoClear; }, /** * Set the blend mode for the DrawingContext. * * @method Phaser.Renderer.WebGL.DrawingContext#setBlendMode * @since 4.0.0 * @param {number} blendMode - The blend mode to set. * @param {number[]} [blendColor] - The blend color to set. This is an array of 4 values: red, green, blue, alpha. */ setBlendMode: function (blendMode, blendColor) { if (blendMode === this.blendMode) { return; } var blend = this.state.blend; var blendModeData = this.renderer.blendModes[blendMode]; blend.enable = blendModeData.enable; blend.equation = blendModeData.equation; blend.func = blendModeData.func; if (blendColor) { blend.color = blendColor; } else { blend.color = undefined; } this.blendMode = blendMode; }, /** * Set the camera for the DrawingContext. * * @method Phaser.Renderer.WebGL.DrawingContext#setCamera * @since 4.0.0 * @param {Phaser.Cameras.Scene2D.Camera} camera - The camera to set. */ setCamera: function (camera) { this.camera = camera; }, /** * Set the clear color for the DrawingContext. * No changes will be made if the color is the same as the current clear color. * * @method Phaser.Renderer.WebGL.DrawingContext#setClearColor * @since 4.0.0 * @param {number} r - The red component of the color to clear with. * @param {number} g - The green component of the color to clear with. * @param {number} b - The blue component of the color to clear with. * @param {number} a - The alpha component of the color to clear with. */ setClearColor: function (r, g, b, a) { var colorClearValue = this.state.colorClearValue; if ( r === colorClearValue[0] && g === colorClearValue[1] && b === colorClearValue[2] && a === colorClearValue[3] ) { return; } this.state.colorClearValue = [ r, g, b, a ]; }, /** * Set the scissor box for the DrawingContext. * * @method Phaser.Renderer.WebGL.DrawingContext#setScissorBox * @since 4.0.0 * @param {number} x - The x coordinate of the scissor box. * @param {number} y - The y coordinate of the scissor box. * @param {number} width - The width of the scissor box. * @param {number} height - The height of the scissor box. */ setScissorBox: function (x, y, width, height) { // Convert Y coordinate to WebGL space. y = this.height - y - height; this.state.scissor.box = [ x, y, width, height ]; }, /** * Enable or disable the scissor box for the DrawingContext. * * @method Phaser.Renderer.WebGL.DrawingContext#setScissorEnable * @since 4.0.0 * @param {boolean} enable - Whether to enable the scissor box. */ setScissorEnable: function (enable) { this.state.scissor.enable = enable; }, /** * Begin using the DrawingContext. * This will finish any outstanding batches and run any autoClear. * * @method Phaser.Renderer.WebGL.DrawingContext#use * @since 4.0.0 */ use: function () { this.renderer.renderNodes.finishBatch(); if (this.autoClear) { this.clear(); } }, /** * End using the DrawingContext. This marks the context as not in use, * so its framebuffer and texture are not needed any more * and may be cleared at any time. This will finish any outstanding batches. * * If there are no locks on the DrawingContext, and it comes from a pool, * it will be returned to its pool. * * @method Phaser.Renderer.WebGL.DrawingContext#release * @since 4.0.0 */ release: function () { if (this.pool && this._locks.length === 0) { this.lastUsed = Date.now(); this.pool.add(this); } this.renderer.renderNodes.finishBatch(); }, /** * Lock the DrawingContext to be in use. * This prevents `release` from returning it to its pool * until `unlock` is called with the appropriate key. * This is used for temporary DrawingContexts, * which may be returned to a pool. * * @method Phaser.Renderer.WebGL.DrawingContext#lock * @since 4.0.0 * @param {any} key - The key to lock the DrawingContext with. */ lock: function (key) { if (this._locks.indexOf(key) !== -1) { return; } this._locks.push(key); }, /** * Unlock the DrawingContext. * This allows `release` to return it to its pool. * * @method Phaser.Renderer.WebGL.DrawingContext#unlock * @since 4.0.0 * @param {any} key - The key to unlock the DrawingContext with. This must be the same key used to lock it. * @param {boolean} [release] - Whether to release the DrawingContext immediately. This will only happen if there are no other locks on it. */ unlock: function (key, release) { var index = this._locks.indexOf(key); if (index !== -1) { this._locks.splice(index, 1); } if (release) { this.release(); } }, /** * Check whether the DrawingContext is locked. * * @method Phaser.Renderer.WebGL.DrawingContext#isLocked * @since 4.0.0 * @return {boolean} Whether the DrawingContext is locked. */ isLocked: function () { return this._locks.length > 0; }, /** * Begin drawing with the DrawingContext. * * This should be called before rendering to set up the framebuffer * and other WebGL state. * * @method Phaser.Renderer.WebGL.DrawingContext#beginDraw * @since 4.0.0 */ beginDraw: function () { if (this.framebuffer) { // Ensure the framebuffer texture is not bound to a texture unit. this.renderer.glTextureUnits.unbindTexture(this.texture); } this.renderer.glWrapper.update(this.state); }, /** * Clear the framebuffer. This will bind the framebuffer. * * @method Phaser.Renderer.WebGL.DrawingContext#clear * @since 4.0.0 * @param {number} [bits] - A bitmask of WebGL buffer bits to clear (e.g. `gl.COLOR_BUFFER_BIT`, `gl.DEPTH_BUFFER_BIT`, `gl.STENCIL_BUFFER_BIT`). Defaults to the `autoClear` value of this context. */ clear: function (bits) { this.beginDraw(); if (bits === undefined) { bits = this.autoClear; } this.renderer.renderNodes.finishBatch(); this.renderer.gl.clear(bits); }, /** * Destroys the DrawingContext and its resources. * * @method Phaser.Renderer.WebGL.DrawingContext#destroy * @since 4.0.0 */ destroy: function () { this.renderer.deleteTexture(this.texture); this.renderer.deleteFramebuffer(this.state.bindings.framebuffer); this.renderer = null; this.camera = null; this.state = null; this.framebuffer = null; this.texture = null; } }); module.exports = DrawingContext;