UNPKG

phaser

Version:

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

581 lines (524 loc) 20.7 kB
/** * @author Benjamin D. Richards <benjamindrichards@gmail.com> * @copyright 2013-2026 Phaser Studio Inc. * @license {@link https://opensource.org/licenses/MIT|MIT License} */ var EventEmitter = require('eventemitter3'); var Class = require('../../../utils/Class'); var Events = require('../../events'); var BaseFilter = require('./filters/BaseFilter'); var BaseFilterShader = require('./filters/BaseFilterShader'); var BatchHandlerPointLight = require('./BatchHandlerPointLight'); var BatchHandlerQuad = require('./BatchHandlerQuad'); var BatchHandlerQuadSingle = require('./BatchHandlerQuadSingle'); var BatchHandlerStrip = require('./BatchHandlerStrip'); var BatchHandlerTileSprite = require('./BatchHandlerTileSprite'); var BatchHandlerTriFlat = require('./BatchHandlerTriFlat'); var Camera = require('./Camera'); var DrawLine = require('./DrawLine'); var DynamicTextureHandler = require('./DynamicTextureHandler'); var FillCamera = require('./FillCamera'); var FillPath = require('./FillPath'); var FillRect = require('./FillRect'); var FillTri = require('./FillTri'); var FilterBarrel = require('./filters/FilterBarrel'); var FilterBlend = require('./filters/FilterBlend'); var FilterBlocky = require('./filters/FilterBlocky'); var FilterBlur = require('./filters/FilterBlur'); var FilterBlurHigh = require('./filters/FilterBlurHigh'); var FilterBlurLow = require('./filters/FilterBlurLow'); var FilterBlurMed = require('./filters/FilterBlurMed'); var FilterBokeh = require('./filters/FilterBokeh'); var FilterColorMatrix = require('./filters/FilterColorMatrix'); var FilterCombineColorMatrix = require('./filters/FilterCombineColorMatrix'); var FilterDisplacement = require('./filters/FilterDisplacement'); var FilterGlow = require('./filters/FilterGlow'); var FilterGradientMap = require('./filters/FilterGradientMap'); var FilterImageLight = require('./filters/FilterImageLight'); var FilterKey = require('./filters/FilterKey'); var FilterMask = require('./filters/FilterMask'); var FilterNormalTools = require('./filters/FilterNormalTools'); var FilterPanoramaBlur = require('./filters/FilterPanoramaBlur'); var FilterParallelFilters = require('./filters/FilterParallelFilters'); var FilterPixelate = require('./filters/FilterPixelate'); var FilterQuantize = require('./filters/FilterQuantize'); var FilterSampler = require('./filters/FilterSampler'); var FilterShadow = require('./filters/FilterShadow'); var FilterThreshold = require('./filters/FilterThreshold'); var FilterVignette = require('./filters/FilterVignette'); var FilterWipe = require('./filters/FilterWipe'); var ListCompositor = require('./ListCompositor'); var RebindContext = require('./RebindContext'); var StrokePath = require('./StrokePath'); var SubmitterQuad = require('./submitter/SubmitterQuad'); var SubmitterTile = require('./submitter/SubmitterTile'); var SubmitterTilemapGPULayer = require('./submitter/SubmitterTilemapGPULayer'); var SubmitterTileSprite = require('./submitter/SubmitterTileSprite'); var TexturerImage = require('./texturer/TexturerImage'); var TexturerTileSprite = require('./texturer/TexturerTileSprite'); var TransformerImage = require('./transformer/TransformerImage'); var TransformerStamp = require('./transformer/TransformerStamp'); var TransformerTile = require('./transformer/TransformerTile'); var TransformerTileSprite = require('./transformer/TransformerTileSprite'); var YieldContext = require('./YieldContext'); /** * @typedef {object} DebugGraphNode * @property {string} name - The name of the node. * @property {DebugGraphNode[]} children - The children of the node. * @property {DebugGraphNode} parent - The parent of the node. */ /** * The RenderNodeManager creates, stores, and provides access to all RenderNode * instances used by the WebGL renderer. Render nodes are the fundamental units * of the WebGL rendering pipeline — each node is responsible for a specific * rendering task, such as batching sprites, applying post-processing filters, * compositing layered render lists, transforming geometry, or managing WebGL * drawing contexts. The manager lazily constructs built-in nodes on first * request via `getNode` and caches them for reuse. Custom nodes and * constructors can be registered with `addNode` and `addNodeConstructor`. * * The manager also tracks the currently active batch node so that an * in-progress batch can be flushed automatically when a different rendering * operation begins, controls the `maxParallelTextureUnits` limit used to tune * multi-texture batching performance on desktop and mobile, and provides an * optional debug mode that captures a single frame's complete render graph as * a tree structure inspectable via `debugToString`. * * @class RenderNodeManager * @memberof Phaser.Renderer.WebGL.RenderNodes * @constructor * @since 4.0.0 * @param {Phaser.Renderer.WebGL.WebGLRenderer} renderer - The renderer that owns this manager. */ var RenderNodeManager = new Class({ Extends: EventEmitter, initialize: function RenderNodeManager (renderer) { EventEmitter.call(this); /** * The renderer that owns this manager. * * @name Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager#renderer * @type {Phaser.Renderer.WebGL.WebGLRenderer} * @since 4.0.0 */ this.renderer = renderer; var game = renderer.game; /** * The maximum number of texture units to use as choices in a batch. * Batches can bind several textures and select one of them per instance, * allowing for larger batches. * However, some mobile devices degrade performance when using multiple * texture units. So if the game config option `autoMobileTextures` is * enabled and the device is not a desktop, this will be set to 1. * Otherwise, it will be set to the renderer's `maxTextures`. * * Some shaders may require more than one texture unit, * so the actual limit on texture units per batch is `maxTextures`. * * This value can be changed at runtime via `setMaxParallelTextureUnits`. * * @name Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager#maxParallelTextureUnits * @type {number} * @since 4.0.0 */ this.maxParallelTextureUnits = (game.config.autoMobileTextures && !game.device.os.desktop) ? 1 : renderer.maxTextures; /** * Nodes available for use. This is an internal object, * where the keys are the names of the nodes. * * Nodes are constructed when requested by `getNode`. * Custom nodes can be added via `addNode` or `addNodeConstructor`. * * @name Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager#nodes * @type {object} * @since 4.0.0 * @private */ this._nodes = {}; /** * Built-in nodes which can be constructed by name. * Use `getNode` to either return a constructed built-in node * from `_nodes`, or construct a new one if it does not exist. * * Use `addNodeConstructor` to add custom nodes * without constructing them. * Use `addNode` to add custom nodes that have already been constructed. * * @name Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager#_nodeConstructors * @type {object} * @since 4.0.0 * @private */ this._nodeConstructors = { BaseFilter: BaseFilter, BaseFilterShader: BaseFilterShader, BatchHandlerPointLight: BatchHandlerPointLight, BatchHandlerQuad: BatchHandlerQuad, BatchHandlerQuadSingle: BatchHandlerQuadSingle, BatchHandlerStrip: BatchHandlerStrip, BatchHandlerTileSprite: BatchHandlerTileSprite, BatchHandlerTriFlat: BatchHandlerTriFlat, Camera: Camera, DrawLine: DrawLine, DynamicTextureHandler: DynamicTextureHandler, FillCamera: FillCamera, FillPath: FillPath, FillRect: FillRect, FillTri: FillTri, FilterBarrel: FilterBarrel, FilterBlend: FilterBlend, FilterBlocky: FilterBlocky, FilterBlur: FilterBlur, FilterBlurHigh: FilterBlurHigh, FilterBlurLow: FilterBlurLow, FilterBlurMed: FilterBlurMed, FilterBokeh: FilterBokeh, FilterColorMatrix: FilterColorMatrix, FilterCombineColorMatrix: FilterCombineColorMatrix, FilterDisplacement: FilterDisplacement, FilterGlow: FilterGlow, FilterGradientMap: FilterGradientMap, FilterImageLight: FilterImageLight, FilterKey: FilterKey, FilterMask: FilterMask, FilterNormalTools: FilterNormalTools, FilterPanoramaBlur: FilterPanoramaBlur, FilterParallelFilters: FilterParallelFilters, FilterPixelate: FilterPixelate, FilterQuantize: FilterQuantize, FilterSampler: FilterSampler, FilterShadow: FilterShadow, FilterThreshold: FilterThreshold, FilterVignette: FilterVignette, FilterWipe: FilterWipe, ListCompositor: ListCompositor, RebindContext: RebindContext, StrokePath: StrokePath, SubmitterQuad: SubmitterQuad, SubmitterTile: SubmitterTile, SubmitterTilemapGPULayer: SubmitterTilemapGPULayer, SubmitterTileSprite: SubmitterTileSprite, TexturerImage: TexturerImage, TexturerTileSprite: TexturerTileSprite, TransformerImage: TransformerImage, TransformerStamp: TransformerStamp, TransformerTile: TransformerTile, TransformerTileSprite: TransformerTileSprite, YieldContext: YieldContext }; Object.entries(game.config.renderNodes).forEach(function (entry) { var name = entry[0]; var constructor = entry[1]; this.addNodeConstructor(name, constructor); }, this); /** * The RenderNode which is currently being filled. * This is stored so that it can be completed when another type of * render is run. * * @name Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager#currentBatchNode * @type {?Phaser.Renderer.WebGL.RenderNodes.RenderNode} * @default null * @since 4.0.0 */ this.currentBatchNode = null; /** * The drawing context of the current batch. * This is stored here because the batch node is stateless. * * @name Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager#currentBatchDrawingContext * @type {?Phaser.Renderer.WebGL.DrawingContext} * @default null * @since 4.0.0 */ this.currentBatchDrawingContext = null; /** * Whether nodes should record their run method for debugging. * This should be set via `setDebug`. * * @name Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager#debug * @type {boolean} * @since 4.0.0 * @default false */ this.debug = false; /** * The debug graph of nodes that have been run. * This is used when `debug` is enabled. * * @name Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager#debugGraph * @type {?DebugGraphNode} */ this.debugGraph = null; /** * The current node in the debug graph. * This is used when `debug` is enabled. * * @name Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager#currentDebugNode * @type {?DebugGraphNode} * @default null */ this.currentDebugNode = null; }, /** * Add a node to the manager. * * @method Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager#addNode * @since 4.0.0 * @param {string} name - The name of the node. * @param {Phaser.Renderer.WebGL.RenderNodes.RenderNode} node - The node to add. * @throws {Error} Will throw an error if the node already exists. */ addNode: function (name, node) { if (this._nodes[name]) { throw new Error('node ' + name + ' already exists.'); } this._nodes[name] = node; // If a node is somehow added during a debug render pass, // ensure that it is also set to debug. if (this.debug) { node.setDebug(true); } }, /** * Add a constructor for a node to the manager. * This will allow the node to be constructed when `getNode` is called. * * @method Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager#addNodeConstructor * @since 4.0.0 * @param {string} name - The name of the node. * @param {function} constructor - The constructor for the node. * @throws {Error} Will throw an error if the node constructor already exists. */ addNodeConstructor: function (name, constructor) { if (this._nodeConstructors[name]) { throw new Error('node constructor ' + name + ' already exists.'); } this._nodeConstructors[name] = constructor; }, /** * Get a node from the manager. * * If the node does not exist, and a constructor is available, * it will be constructed and added to the manager, * then returned. * * @method Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager#getNode * @since 4.0.0 * @param {string} name - The name of the node. * @return {?Phaser.Renderer.WebGL.RenderNodes.RenderNode} The node, or null if it does not exist. */ getNode: function (name) { if (this._nodes[name]) { return this._nodes[name]; } if (this._nodeConstructors[name]) { var node = new this._nodeConstructors[name](this); this.addNode(name, node); return node; } return null; }, /** * Check if a node exists in the manager. * * If a node is not constructed, but a constructor is available, * it will be considered to exist. Set `constructed` to true to * require that the node has already been constructed. * * @method Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager#hasNode * @since 4.0.0 * @param {string} name - The name of the node. * @param {boolean} [constructed=false] - Whether the node must be constructed to be considered to exist. * @return {boolean} Whether the node exists. */ hasNode: function (name, constructed) { return !!this._nodes[name] || (!constructed && !!this._nodeConstructors[name]); }, /** * Set the current batch node. If a batch node is already in progress, * it will be completed before the new node is set. * * @method Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager#setCurrentBatchNode * @since 4.0.0 * @param {?Phaser.Renderer.WebGL.RenderNodes.BatchHandler} node - The node to set, or null to clear the current node. * @param {Phaser.Renderer.WebGL.DrawingContext} [drawingContext] - The drawing context. Only used if `node` is defined. */ setCurrentBatchNode: function (node, drawingContext) { if (this.currentBatchNode !== node) { if (this.currentBatchNode !== null) { this.currentBatchNode.run( this.currentBatchDrawingContext ); } this.currentBatchNode = node; this.currentBatchDrawingContext = node ? drawingContext : null; } }, /** * Set `maxParallelTextureUnits` to a new value. * This will be clamped to the range [1, renderer.maxTextures]. * * This can be useful for providing the user with a way to adjust the * performance of the game at runtime. * * @method Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager#setMaxParallelTextureUnits * @since 4.0.0 * @param {number} [value] - The new value for `maxParallelTextureUnits`. Must be a number; it will be clamped to the range [1, renderer.maxTextures]. * @fires Phaser.Renderer.Events#SET_PARALLEL_TEXTURE_UNITS */ setMaxParallelTextureUnits: function (value) { this.maxParallelTextureUnits = Math.max(1, Math.min(value, this.renderer.maxTextures)); this.emit(Events.SET_PARALLEL_TEXTURE_UNITS, this.maxParallelTextureUnits); }, /** * Finish rendering the current batch. * This should be called when starting a new rendering task. * * @method Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager#finishBatch * @since 4.0.0 */ finishBatch: function () { if (this.currentBatchNode !== null) { this.setCurrentBatchNode(null); } }, /** * Start a standalone render (SAR), which is not part of a batch. * This will trigger batch completion if a batch is in progress. * * @method Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager#startStandAloneRender * @since 4.0.0 */ startStandAloneRender: function () { this.finishBatch(); }, /** * Set whether nodes should record their run method for debugging. * This will set the debug property on all nodes, reset the debug graph, * and record a single frame of the graph before disabling debug. * * @method Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager#setDebug * @since 4.0.0 * @param {boolean} value - Whether nodes should record their run method for debugging. */ setDebug: function (value) { this.debug = value; for (var key in this._nodes) { this._nodes[key].setDebug(value); } if (value) { this.debugGraph = null; this.currentDebugNode = null; // Insert a synthetic root node. this.pushDebug('[Render Tree Root]'); this.renderer.once( Events.POST_RENDER, function () { this.setDebug(false); }, this ); } }, /** * Record a newly run RenderNode in the debug graph. * * @method Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager#pushDebug * @since 4.0.0 * @param {string} name - The name of the node. */ pushDebug: function (name) { if (!this.debug) { return; } var node = { name: name, children: [], parent: this.currentDebugNode }; if (this.debugGraph) { this.currentDebugNode.children.push(node); } else { this.debugGraph = node; } this.currentDebugNode = node; }, /** * Pop the last recorded RenderNode from the debug graph. * * @method Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager#popDebug * @since 4.0.0 */ popDebug: function () { if (!this.debug) { return; } if (this.currentDebugNode.parent) { this.currentDebugNode = this.currentDebugNode.parent; } else { this.currentDebugNode = null; } }, /** * Format the current debug graph as an indented string. * * @method Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager#debugToString * @since 4.0.0 * @return {string} The formatted debug graph. */ debugToString: function () { var output = ''; var indent = 0; var node = this.debugGraph; function indentString (indent) { return ' '.repeat(indent); } function formatNode (node, indent) { var str = indentString(indent) + node.name + '\n'; for (var i = 0; i < node.children.length; i++) { str += formatNode(node.children[i], indent + 1); } return str; } output = formatNode(node, indent); return output; } }); module.exports = RenderNodeManager;