UNPKG

phaser

Version:

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

400 lines (341 loc) 14.8 kB
/** * @author Benjamin D. Richards <benjamindrichards@gmail.com> * @copyright 2013-2026 Phaser Studio Inc. * @license {@link https://opensource.org/licenses/MIT|MIT License} */ var CameraEvents = require('../../../cameras/2d/events'); var DynamicTextureCommands = require('../../../textures/DynamicTextureCommands'); var Class = require('../../../utils/Class'); var BlendModes = require('../../BlendModes'); var RenderNode = require('./RenderNode'); /** * @classdesc * A RenderNode responsible for executing the recorded drawing commands of a * `DynamicTexture`. A `DynamicTexture` accumulates drawing operations (stamps, * repeats, fills, clears, game object draws, callbacks, and captures) into a * command buffer. Each time this handler's `run` method is called, it iterates * through that buffer and executes every command against the texture's WebGL * framebuffer. * * The handler manages all necessary WebGL state during execution: it unbinds * the framebuffer's own texture to prevent feedback loops, configures scissor * boxes, and creates or releases cloned drawing contexts as blend modes and * erase mode change across commands. When a PRESERVE command is not present * the command buffer is cleared after rendering, ready for the next frame's * drawing calls. * * @class DynamicTextureHandler * @memberof Phaser.Renderer.WebGL.RenderNodes * @constructor * @since 4.0.0 * @extends Phaser.Renderer.WebGL.RenderNodes.RenderNode * @param {Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager} manager - The manager that owns this RenderNode. */ var DynamicTextureHandler = new Class({ Extends: RenderNode, initialize: function DynamicTextureHandler (manager) { RenderNode.call(this, 'DynamicTextureHandler', manager); /** * The RenderNode that draws a filled rectangle. * * @name Phaser.Renderer.WebGL.RenderNodes.DynamicTextureHandler#fillRectNode * @type {Phaser.Renderer.WebGL.RenderNodes.FillRect} * @since 4.0.0 */ this.fillRectNode = this.manager.getNode('FillRect'); }, /** * Processes the command buffer of the given DynamicTexture and executes * each recorded drawing operation against its WebGL framebuffer. This * includes stamping textures, tiling sprites, filling rectangles, clearing * regions, drawing arbitrary game objects, toggling erase mode, invoking * custom callbacks, and capturing game objects to sub-regions. Drawing * contexts are cloned and released as blend modes change across commands. * After all commands have been executed, the command buffer is cleared * unless a PRESERVE command was encountered, and a POST_RENDER camera * event is emitted. * * @method Phaser.Renderer.WebGL.RenderNodes.DynamicTextureHandler#run * @since 4.0.0 * @param {Phaser.Textures.DynamicTexture} dynamicTexture - The DynamicTexture to render. */ run: function (dynamicTexture) { var drawingContext = dynamicTexture.drawingContext; var camera = drawingContext.camera; var renderer = drawingContext.renderer; var textureManager = dynamicTexture.manager; this.onRunBegin(drawingContext); // Ensure the framebuffer texture is not bound, // to avoid WebGL feedback. var glTexture = drawingContext.framebuffer.renderTexture; if (glTexture) { var glTextureUnits = renderer.glTextureUnits; var units = glTextureUnits.units; for (var i = 0; i < units.length; i++) { if (units[i] === glTexture) { glTextureUnits.bind(null, i); } } } drawingContext.setScissorBox( 0, 0, camera.width, camera.height ); // Enter drawing context. drawingContext.use(); // Big list of reused variables. var alpha, blendMode, frame, height, key, originX, originY, rotation, scaleX, scaleY, tint, width, x, y; // Traverse commands. var commandBuffer = dynamicTexture.commandBuffer; var commandBufferLength = commandBuffer.length; var eraseMode = false; var eraseContext = null; var preserveBuffer = false; var currentContext = drawingContext; var gl = renderer.gl; for (var index = 0; index < commandBufferLength; index++) { var command = commandBuffer[index]; switch (command) { case DynamicTextureCommands.CLEAR: { x = commandBuffer[++index]; y = commandBuffer[++index]; width = commandBuffer[++index]; height = commandBuffer[++index]; var clearContext = currentContext.getClone(); clearContext.setScissorEnable(true); clearContext.setScissorBox(x, y, width, height); clearContext.use(); clearContext.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT ); clearContext.release(); break; } case DynamicTextureCommands.FILL: { var color = commandBuffer[++index]; x = commandBuffer[++index]; y = commandBuffer[++index]; width = commandBuffer[++index]; height = commandBuffer[++index]; this.fillRectNode.run( currentContext, null, null, x, y, width, height, color, color, color, color, false ); break; } case DynamicTextureCommands.STAMP: { key = commandBuffer[++index]; frame = commandBuffer[++index]; x = commandBuffer[++index]; y = commandBuffer[++index]; alpha = commandBuffer[++index]; tint = commandBuffer[++index]; rotation = commandBuffer[++index]; scaleX = commandBuffer[++index]; scaleY = commandBuffer[++index]; originX = commandBuffer[++index]; originY = commandBuffer[++index]; blendMode = commandBuffer[++index]; var stamp = textureManager.resetStamp(alpha, tint); stamp.setPosition(x, y) .setRotation(rotation) .setTexture(key, frame) .setOrigin(originX, originY) .setScale(scaleX, scaleY) .setBlendMode(blendMode); currentContext = this._draw(renderer, stamp, currentContext, drawingContext, eraseContext); break; } case DynamicTextureCommands.REPEAT: { key = commandBuffer[++index]; frame = commandBuffer[++index]; x = commandBuffer[++index]; y = commandBuffer[++index]; alpha = commandBuffer[++index]; tint = commandBuffer[++index]; rotation = commandBuffer[++index]; scaleX = commandBuffer[++index]; scaleY = commandBuffer[++index]; originX = commandBuffer[++index]; originY = commandBuffer[++index]; blendMode = commandBuffer[++index]; width = commandBuffer[++index]; height = commandBuffer[++index]; var tilePositionX = commandBuffer[++index]; var tilePositionY = commandBuffer[++index]; var tileRotation = commandBuffer[++index]; var tileScaleX = commandBuffer[++index]; var tileScaleY = commandBuffer[++index]; var repeat = textureManager.resetTileSprite(alpha, tint); repeat.setPosition(x, y) .setRotation(rotation) .setTexture(key, frame) .setSize(width, height) .setOrigin(originX, originY) .setScale(scaleX, scaleY) .setBlendMode(blendMode) .setTilePosition(tilePositionX, tilePositionY) .setTileRotation(tileRotation) .setTileScale(tileScaleX, tileScaleY); currentContext = this._draw(renderer, repeat, currentContext, drawingContext, eraseContext); break; } case DynamicTextureCommands.DRAW: { var object = commandBuffer[++index]; x = commandBuffer[++index]; y = commandBuffer[++index]; if (x !== undefined) { var prevX = object.x; object.x += x; } if (y !== undefined) { var prevY = object.y; object.y += y; } currentContext = this._draw(renderer, object, currentContext, drawingContext, eraseContext); if (x !== undefined) { object.x = prevX; } if (y !== undefined) { object.y = prevY; } break; } case DynamicTextureCommands.SET_ERASE: { eraseMode = commandBuffer[++index]; if (eraseMode) { if (!eraseContext) { eraseContext = drawingContext.getClone(); eraseContext.setBlendMode(BlendModes.ERASE); } if (currentContext !== eraseContext) { currentContext.release(); currentContext = eraseContext; eraseContext.use(); } } else if (currentContext === eraseContext) { eraseContext.release(); currentContext = drawingContext; drawingContext.use(); } break; } case DynamicTextureCommands.PRESERVE: { preserveBuffer = commandBuffer[++index]; break; } case DynamicTextureCommands.CALLBACK: { var callback = commandBuffer[++index]; callback(); break; } case DynamicTextureCommands.CAPTURE: { object = commandBuffer[++index]; var config = commandBuffer[++index]; var cacheConfig = dynamicTexture.startCapture(object, config); // Handle custom capture camera. var viewContext = currentContext; if (config.camera) { viewContext = viewContext.getClone(); viewContext.setCamera(config.camera); viewContext.use(); } this._draw(renderer, object, viewContext, drawingContext, eraseContext, cacheConfig.transform); dynamicTexture.finishCapture(object, cacheConfig); if (config.camera) { viewContext.release(); } break; } } } if (!preserveBuffer) { // Clear the command buffer. commandBuffer.length = 0; } // Finish rendering. currentContext.release(); // Regenerate any mipmap before using the texture. glTexture.needsMipmapRegeneration = true; camera.emit(CameraEvents.POST_RENDER, camera); this.onRunEnd(drawingContext); }, /** * Draw an object to the DynamicTexture, handling blend modes. * * @method Phaser.Renderer.WebGL.RenderNodes.DynamicTextureHandler#_draw * @private * @since 4.0.0 * * @param {Phaser.Renderer.WebGL.WebGLRenderer} renderer - The WebGLRenderer. * @param {Phaser.GameObjects.GameObject} object - The object to draw. * @param {Phaser.Renderer.WebGL.DrawingContext} currentContext - The current drawing context. * @param {Phaser.Renderer.WebGL.DrawingContext} drawingContext - The base drawing context in use. * @param {Phaser.Renderer.WebGL.DrawingContext} eraseContext - The erase drawing context. * @param {Phaser.GameObjects.Components.TransformMatrix} [parentMatrix] - The parent matrix, if any. * * @return {Phaser.Renderer.WebGL.DrawingContext} The new current drawing context. */ _draw: function (renderer, object, currentContext, drawingContext, eraseContext, parentMatrix) { // Handle blend mode. if ( currentContext !== eraseContext && object.blendMode !== currentContext.blendMode && object.blendMode !== BlendModes.SKIP_CHECK ) { currentContext.release(); var blendMode = object.blendMode; if (blendMode === drawingContext.blendMode) { // Reset to the base context. currentContext = drawingContext; } else { // Change blend mode. currentContext = drawingContext.getClone(); currentContext.setBlendMode(blendMode); } currentContext.use(); } object.renderWebGLStep(renderer, object, currentContext, parentMatrix); return currentContext; } }); module.exports = DynamicTextureHandler;