phaser
Version:
A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.
192 lines (167 loc) • 7.6 kB
JavaScript
/**
* @author Benjamin D. Richards <benjamindrichards@gmail.com>
* @copyright 2013-2026 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var Rectangle = require('../../../../geom/rectangle/Rectangle');
var Class = require('../../../../utils/Class');
var BlendModes = require('../../../BlendModes');
var BaseFilter = require('./BaseFilter');
/**
* @classdesc
* A RenderNode that processes two independent filter chains — a bottom layer
* and a top layer — each applied to the same input, then composites their
* results together using a configurable blend mode. This allows you to combine
* two completely different filter effects on a single Game Object; for example,
* applying a glow to the bottom layer and a color correction to the top layer,
* then merging them with an add or multiply blend.
*
* Each layer (bottom and top) runs its own ordered chain of filters in series,
* where the output of one filter feeds into the next. Once both chains have
* completed, the results are blended using the `FilterBlend` RenderNode
* according to the controller's blend settings.
*
* If neither layer contains any active filters, the input is copied directly
* to the output with no modifications.
*
* This node delegates rendering work to other RenderNodes during execution.
* See {@link Phaser.Filters.ParallelFilters} for the associated controller.
*
* @class FilterParallelFilters
* @extends Phaser.Renderer.WebGL.RenderNodes.BaseFilter
* @memberof Phaser.Renderer.WebGL.RenderNodes
* @constructor
* @since 4.0.0
* @param {Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager} manager - The manager that owns this RenderNode.
*/
var FilterParallelFilters = new Class({
Extends: BaseFilter,
initialize: function FilterParallelFilters (manager)
{
BaseFilter.call(this, 'FilterParallelFilters', manager);
},
/**
* Runs the parallel filter process. The input drawing context is passed
* through the bottom filter chain and the top filter chain independently,
* producing two separately filtered results. Those results are then blended
* together using the `FilterBlend` RenderNode with the blend mode and
* settings defined on the controller.
*
* Padding is applied on the first filter in each chain only; subsequent
* filters in the same chain receive zero padding to avoid double-expanding
* the output bounds.
*
* If no filters are active on either layer, the input is copied directly
* to the output unchanged.
*
* @method Phaser.Renderer.WebGL.RenderNodes.FilterParallelFilters#run
* @since 4.0.0
* @param {Phaser.Filters.ParallelFilters} controller - The controller that defines the bottom and top filter chains and their blend settings.
* @param {Phaser.Renderer.WebGL.DrawingContext} inputDrawingContext - The drawing context to use as the source image for both filter chains.
* @param {Phaser.Renderer.WebGL.DrawingContext} outputDrawingContext - The drawing context to render the final blended result into. May be `null`, in which case a new context is allocated.
* @param {Phaser.Geom.Rectangle} padding - The padding to apply around the rendered area on the first filter pass.
* @return {Phaser.Renderer.WebGL.DrawingContext} The drawing context containing the final composited output.
*/
run: function (controller, inputDrawingContext, outputDrawingContext, padding)
{
this.onRunBegin(outputDrawingContext);
// Prevent the input from being sent back to its pool.
inputDrawingContext.lock(this);
var bottomFilters = controller.bottom.getActive();
var topFilters = controller.top.getActive();
var initialPadding = padding || controller.getPaddingCeil();
if (bottomFilters.length + topFilters.length > 0)
{
var bottomContext = inputDrawingContext;
var topContext = inputDrawingContext;
// Process bottom filters.
if (bottomFilters.length > 0)
{
padding = initialPadding;
for (var i = 0; i < bottomFilters.length; i++)
{
var childController = bottomFilters[i];
var filter = this.manager.getNode(childController.renderNode);
bottomContext = filter.run(
childController,
bottomContext,
null,
padding
);
// Don't apply more padding after the first filter.
if (i === 0 && i < bottomFilters.length - 1)
{
padding = new Rectangle();
}
}
}
// Process top filters.
if (topFilters.length > 0)
{
padding = initialPadding;
for (i = 0; i < topFilters.length; i++)
{
childController = topFilters[i];
filter = this.manager.getNode(childController.renderNode);
topContext = filter.run(
childController,
topContext,
null,
padding
);
// Don't apply more padding after the first filter.
if (i === 0 && i < topFilters.length - 1)
{
padding = new Rectangle();
}
}
}
inputDrawingContext.unlock(this);
// Check whether the input is no longer in use,
// and won't be released automatically below.
// If it is the bottom context, it will be released by the Blend.
// If it is the top context, it will be released directly.
if (
inputDrawingContext !== bottomContext &&
inputDrawingContext !== topContext
)
{
inputDrawingContext.release();
}
// Blend the top and bottom filters.
var blendController = controller.blend;
blendController.glTexture = topContext.texture;
filter = this.manager.getNode('FilterBlend');
outputDrawingContext = this.manager.getNode('FilterBlend').run(
controller.blend,
bottomContext,
outputDrawingContext,
padding // This will be 0 because at least one filter has already been applied.
);
// Whether top context is new or the input, it now needs to be released.
topContext.release();
}
else
{
// No filters to run.
// Copy the input to the output.
filter = this.manager.getNode('FilterBlend');
var proxyController = {
blendMode: BlendModes.COPY,
glTexture: inputDrawingContext.texture,
amount: 1,
color: [ 1, 1, 1, 1 ]
};
inputDrawingContext.unlock(this);
outputDrawingContext = filter.run(
proxyController,
inputDrawingContext,
outputDrawingContext,
initialPadding
);
}
this.onRunEnd(outputDrawingContext);
return outputDrawingContext;
}
});
module.exports = FilterParallelFilters;