Add TilemapGPULayer and many shader factory enhancements.

This commit is contained in:
Ben Richards 2024-09-04 15:55:11 +12:00
parent 78b4efbf11
commit aadd767f8d
94 changed files with 4712 additions and 1737 deletions

View file

@ -4,6 +4,7 @@
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var DefaultBitmapTextNodes = require('../../../renderer/webgl/renderNodes/defaults/DefaultBitmapTextNodes');
var Class = require('../../../utils/Class');
var Clamp = require('../../../math/Clamp');
var Components = require('../../components');
@ -51,7 +52,7 @@ var Render = require('./BitmapTextRender');
* @extends Phaser.GameObjects.Components.Mask
* @extends Phaser.GameObjects.Components.Origin
* @extends Phaser.GameObjects.Components.PostPipeline
* @extends Phaser.GameObjects.Components.RenderNode
* @extends Phaser.GameObjects.Components.RenderNodes
* @extends Phaser.GameObjects.Components.ScrollFactor
* @extends Phaser.GameObjects.Components.Texture
* @extends Phaser.GameObjects.Components.Tint
@ -79,7 +80,7 @@ var BitmapText = new Class({
Components.Mask,
Components.Origin,
Components.PostPipeline,
Components.RenderNode,
Components.RenderNodes,
Components.ScrollFactor,
Components.Texture,
Components.Tint,
@ -296,12 +297,29 @@ var BitmapText = new Class({
this.setTexture(entry.texture, entry.frame);
this.setPosition(x, y);
this.setOrigin(0, 0);
this.initRenderNodes('BitmapText');
this.initRenderNodes(this._defaultRenderNodesMap);
this.initPostPipeline();
this.setText(text);
},
/**
* The default render nodes to initialize.
*
* @name Phaser.GameObjects.BitmapText#_defaultRenderNodesMap
* @type {Map<string, string>}
* @private
* @webglOnly
* @readonly
* @since 3.90.0
*/
_defaultRenderNodesMap: {
get: function ()
{
return DefaultBitmapTextNodes;
}
},
/**
* Set the lines of text in this BitmapText to be left-aligned.
* This only has any effect if this BitmapText contains more than one line of text.

View file

@ -6,6 +6,7 @@
var BlitterRender = require('./BlitterRender');
var Bob = require('./Bob');
var DefaultBlitterNodes = require('../../renderer/webgl/renderNodes/defaults/DefaultBlitterNodes');
var Class = require('../../utils/Class');
var Components = require('../components');
var Frame = require('../../textures/Frame');
@ -47,7 +48,7 @@ var List = require('../../structs/List');
* @extends Phaser.GameObjects.Components.Lighting
* @extends Phaser.GameObjects.Components.Mask
* @extends Phaser.GameObjects.Components.PostPipeline
* @extends Phaser.GameObjects.Components.RenderNode
* @extends Phaser.GameObjects.Components.RenderNodes
* @extends Phaser.GameObjects.Components.ScrollFactor
* @extends Phaser.GameObjects.Components.Size
* @extends Phaser.GameObjects.Components.Texture
@ -71,7 +72,7 @@ var Blitter = new Class({
Components.Lighting,
Components.Mask,
Components.PostPipeline,
Components.RenderNode,
Components.RenderNodes,
Components.ScrollFactor,
Components.Size,
Components.Texture,
@ -88,7 +89,7 @@ var Blitter = new Class({
this.setTexture(texture, frame);
this.setPosition(x, y);
this.initRenderNodes('Blitter');
this.initRenderNodes(this._defaultRenderNodesMap);
this.initPostPipeline();
/**
@ -124,6 +125,23 @@ var Blitter = new Class({
this.dirty = false;
},
/**
* The default render nodes to use for this Game Object.
*
* @name Phaser.GameObjects.Blitter#_defaultRenderNodesMap
* @type {Map<string, string>}
* @private
* @webglOnly
* @readonly
* @since 3.90.0
*/
_defaultRenderNodesMap: {
get: function ()
{
return DefaultBlitterNodes;
}
},
/**
* Creates a new Bob in this Blitter.
*

View file

@ -0,0 +1,155 @@
/**
* @author Benjamin D. Richards <benjamindrichards@gmail.com>
* @copyright 2013-2024 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
/**
* Provides methods for managing an elapse timer on a Game Object.
* The timer is used to drive animations and other time-based effects.
*
* This is not necessary for normal animations.
* It is intended to drive shader effects that require a time value.
*
* If you are adding this component to a Game Object,
* ensure that you register a preUpdate method on the Game Object, e.g.:
*
* ```javascript
* // Overrides Game Object method
* addedToScene: function ()
* {
* this.scene.sys.updateList.add(this);
* },
*
* // Overrides Game Object method
* removedFromScene: function ()
* {
* this.scene.sys.updateList.remove(this);
* },
*
* preUpdate: function (time, delta)
* {
* this.updateTimer(time, delta);
* }
* ```
*
* @namespace Phaser.GameObjects.Components.ElapseTimer
* @since 3.90.0
*/
var ElapseTimer = {
/**
* The time elapsed since timer initialization, in milliseconds.
*
* @name Phaser.GameObjects.Components.ElapseTimer#timeElapsed
* @type {number}
* @since 3.90.0
*/
timeElapsed: 0,
/**
* The time after which `timeElapsed` will reset, in milliseconds.
* By default, this is 1 hour.
* If you use the timer for animations, you can set this to a period
* that matches the animation durations.
*
* This is necessary for the timer to avoid floating-point precision issues
* in shaders.
* A float32 can represent a few hours of milliseconds accurately,
* but the precision decreases as the value increases.
*
* @name Phaser.GameObjects.Components.ElapseTimer#timeElapsedResetPeriod
* @type {number}
* @since 3.90.0
* @default 3600000
*/
timeElapsedResetPeriod: 60 * 60 * 1000,
/**
* Whether the elapse timer is paused.
*
* @name Phaser.GameObjects.Components.ElapseTimer#timePaused
* @type {boolean}
* @since 3.90.0
* @default false
*/
timePaused: false,
/**
* Set the reset period for the elapse timer for this game object.
*
* @method Phaser.GameObjects.Components.ElapseTimer#setTimerResetPeriod
* @since 3.90.0
* @param {number} period - The time after which `timeElapsed` will reset, in milliseconds.
* @return {this} This game object.
*/
setTimerResetPeriod: function (period)
{
this.timeElapsedResetPeriod = period;
return this;
},
/**
* Pauses or resumes the elapse timer for this game object.
*
* @method Phaser.GameObjects.Components.ElapseTimer#setTimerPaused
* @since 3.90.0
* @param {boolean} [paused] - Pause state (`true` to pause, `false` to unpause). If not specified, the timer will unpause.
* @return {this} This game object.
*/
setTimerPaused: function (paused)
{
this.timePaused = !!paused;
return this;
},
/**
* Reset the elapse timer for this game object.
*
* @method Phaser.GameObjects.Components.ElapseTimer#resetTimer
* @since 3.90.0
* @param {number} [ms=0] - The time to reset the timer to.
* @return {this} This game object.
*/
resetTimer: function (ms)
{
if (ms === undefined) { ms = 0; }
this.timeElapsed = ms;
return this;
},
/**
* Update the elapse timer for this game object.
* This should be called automatically by the preUpdate method.
*
* Override this method to create more advanced time management,
* or set it to a NOOP function to disable the timer update.
* If you want to control animations with a tween or input system,
* disabling the timer update could be useful.
*
* @method Phaser.GameObjects.Components.ElapseTimer#updateTimer
* @since 3.90.0
* @param {number} time - The current time in milliseconds.
* @param {number} delta - The time since the last update, in milliseconds.
* @return {this} This game object.
*/
updateTimer: function (time, delta)
{
if (!this.timePaused)
{
this.timeElapsed += delta;
if (this.timeElapsed >= this.timeElapsedResetPeriod)
{
this.timeElapsed -= this.timeElapsedResetPeriod;
}
}
return this;
}
};
module.exports = ElapseTimer;

View file

@ -9,11 +9,11 @@ var DeepCopy = require('../../utils/object/DeepCopy');
/**
* Provides methods for setting the WebGL render nodes of a Game Object.
*
* @namespace Phaser.GameObjects.Components.RenderNode
* @namespace Phaser.GameObjects.Components.RenderNodes
* @webglOnly
* @since 3.90.0
*/
var RenderNode = {
var RenderNodes = {
/**
* Customized WebGL render nodes of this Game Object.
* RenderNodes are responsible for managing the rendering process of this Game Object.
@ -69,7 +69,7 @@ var RenderNode = {
* @method Phaser.GameObjects.Components.RenderNode#initRenderNodes
* @webglOnly
* @since 3.90.0
* @param {string|Map<string, string>} defaultNodes - The default render nodes to set for this Game Object. This can be a string referring to a map in the RenderNodeManager, or a map of render nodes to set directly.
* @param {Map<string, string>} defaultNodes - The default render nodes to set for this Game Object.
*/
initRenderNodes: function (defaultNodes)
{
@ -84,27 +84,17 @@ var RenderNode = {
return;
}
var manager = this.scene.sys.renderer.renderNodes;
var manager = renderer.renderNodes;
if (!manager)
if (!(manager && defaultNodes))
{
return;
}
if (typeof defaultNodes === 'string')
{
defaultNodes = manager.defaultRenderNodes[defaultNodes];
}
if (!defaultNodes)
{
return;
}
var _this = this;
var defaultRenderNodes = this.defaultRenderNodes;
defaultNodes.each(function (role, node)
{
_this.defaultRenderNodes[role] = manager.getNode(node);
defaultRenderNodes[role] = manager.getNode(node);
});
},
@ -212,4 +202,4 @@ var RenderNode = {
}
};
module.exports = RenderNode;
module.exports = RenderNodes;

View file

@ -16,6 +16,7 @@ module.exports = {
ComputedSize: require('./ComputedSize'),
Crop: require('./Crop'),
Depth: require('./Depth'),
ElapseTimer: require('./ElapseTimer'),
Flip: require('./Flip'),
FX: require('./FX'),
GetBounds: require('./GetBounds'),
@ -25,7 +26,7 @@ module.exports = {
PathFollower: require('./PathFollower'),
Pipeline: require('./Pipeline'),
PostPipeline: require('./PostPipeline'),
RenderNode: require('./RenderNode'),
RenderNodes: require('./RenderNodes'),
ScrollFactor: require('./ScrollFactor'),
Size: require('./Size'),
Texture: require('./Texture'),

View file

@ -10,6 +10,7 @@ var Commands = require('./Commands');
var Components = require('../components');
var Ellipse = require('../../geom/ellipse/Ellipse');
var GameObject = require('../GameObject');
var DefaultGraphicsNodes = require('../../renderer/webgl/renderNodes/defaults/DefaultGraphicsNodes.js');
var GetFastValue = require('../../utils/object/GetFastValue');
var GetValue = require('../../utils/object/GetValue');
var MATH_CONST = require('../../math/const');
@ -74,6 +75,7 @@ var Render = require('./GraphicsRender');
* @extends Phaser.GameObjects.Components.Mask
* @extends Phaser.GameObjects.Components.Pipeline
* @extends Phaser.GameObjects.Components.PostPipeline
* @extends Phaser.GameObjects.Components.RenderNodes
* @extends Phaser.GameObjects.Components.Transform
* @extends Phaser.GameObjects.Components.Visible
* @extends Phaser.GameObjects.Components.ScrollFactor
@ -92,7 +94,7 @@ var Graphics = new Class({
Components.Lighting,
Components.Mask,
Components.PostPipeline,
Components.RenderNode,
Components.RenderNodes,
Components.Transform,
Components.Visible,
Components.ScrollFactor,
@ -109,7 +111,7 @@ var Graphics = new Class({
GameObject.call(this, scene, 'Graphics');
this.setPosition(x, y);
this.initRenderNodes('Graphics');
this.initRenderNodes(this._defaultRenderNodesMap);
this.initPostPipeline();
/**
@ -239,6 +241,23 @@ var Graphics = new Class({
this.setDefaultStyles(options);
},
/**
* The default render nodes for this Game Object.
*
* @name Phaser.GameObjects.Graphics#_defaultRenderNodesMap
* @type {Map<string, string>}
* @private
* @webglOnly
* @readonly
* @since 3.90.0
*/
_defaultRenderNodesMap: {
get: function ()
{
return DefaultGraphicsNodes;
}
},
/**
* Set the default style settings for this Graphics object.
*

View file

@ -4,6 +4,7 @@
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var DefaultImageNodes = require('../../renderer/webgl/renderNodes/defaults/DefaultImageNodes');
var Class = require('../../utils/Class');
var Components = require('../components');
var GameObject = require('../GameObject');
@ -33,7 +34,7 @@ var ImageRender = require('./ImageRender');
* @extends Phaser.GameObjects.Components.Mask
* @extends Phaser.GameObjects.Components.Origin
* @extends Phaser.GameObjects.Components.PostPipeline
* @extends Phaser.GameObjects.Components.RenderNode
* @extends Phaser.GameObjects.Components.RenderNodes
* @extends Phaser.GameObjects.Components.ScrollFactor
* @extends Phaser.GameObjects.Components.Size
* @extends Phaser.GameObjects.Components.TextureCrop
@ -61,7 +62,7 @@ var Image = new Class({
Components.Mask,
Components.Origin,
Components.PostPipeline,
Components.RenderNode,
Components.RenderNodes,
Components.ScrollFactor,
Components.Size,
Components.TextureCrop,
@ -91,8 +92,25 @@ var Image = new Class({
this.setPosition(x, y);
this.setSizeToFrame();
this.setOriginFromFrame();
this.initRenderNodes('Image');
this.initRenderNodes(this._defaultRenderNodesMap);
this.initPostPipeline(true);
},
/**
* The default render nodes for this Game Object.
*
* @name Phaser.GameObjects.Image#_defaultRenderNodesMap
* @type {Map<string, string>}
* @private
* @webglOnly
* @readonly
* @since 3.90.0
*/
_defaultRenderNodesMap: {
get: function ()
{
return DefaultImageNodes;
}
}
});

View file

@ -4,6 +4,7 @@
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var DefaultNineSliceNodes = require('../../renderer/webgl/renderNodes/defaults/DefaultNineSliceNodes');
var Class = require('../../utils/Class');
var Components = require('../components');
var GameObject = require('../GameObject');
@ -100,7 +101,7 @@ var Vertex = require('../../geom/mesh/Vertex');
* @extends Phaser.GameObjects.Components.Mask
* @extends Phaser.GameObjects.Components.Origin
* @extends Phaser.GameObjects.Components.PostPipeline
* @extends Phaser.GameObjects.Components.RenderNode
* @extends Phaser.GameObjects.Components.RenderNodes
* @extends Phaser.GameObjects.Components.ScrollFactor
* @extends Phaser.GameObjects.Components.Texture
* @extends Phaser.GameObjects.Components.Transform
@ -130,7 +131,7 @@ var NineSlice = new Class({
Components.Mask,
Components.Origin,
Components.PostPipeline,
Components.RenderNode,
Components.RenderNodes,
Components.ScrollFactor,
Components.Texture,
Components.Transform,
@ -333,10 +334,27 @@ var NineSlice = new Class({
this.updateDisplayOrigin();
this.initRenderNodes('NineSlice');
this.initRenderNodes(this._defaultRenderNodesMap);
this.initPostPipeline();
},
/**
* The default render nodes for this Game Object.
*
* @name Phaser.GameObjects.NineSlice#_defaultRenderNodesMap
* @type {Map<string, string>}
* @private
* @webglOnly
* @readonly
* @since 3.90.0
*/
_defaultRenderNodesMap: {
get: function ()
{
return DefaultNineSliceNodes;
}
},
/**
* Resets the width, height and slices for this NineSlice Game Object.
*

View file

@ -4,6 +4,7 @@
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var DefaultParticleEmitterNodes = require('../../renderer/webgl/renderNodes/defaults/DefaultParticleEmitterNodes');
var Class = require('../../utils/Class');
var Components = require('../components');
var ComponentsToJSON = require('../components/ToJSON');
@ -322,7 +323,7 @@ var configOpMap = [
* @extends Phaser.GameObjects.Components.Lighting
* @extends Phaser.GameObjects.Components.Mask
* @extends Phaser.GameObjects.Components.PostPipeline
* @extends Phaser.GameObjects.Components.RenderNode
* @extends Phaser.GameObjects.Components.RenderNodes
* @extends Phaser.GameObjects.Components.ScrollFactor
* @extends Phaser.GameObjects.Components.Texture
* @extends Phaser.GameObjects.Components.Transform
@ -345,7 +346,7 @@ var ParticleEmitter = new Class({
Components.Lighting,
Components.Mask,
Components.PostPipeline,
Components.RenderNode,
Components.RenderNodes,
Components.ScrollFactor,
Components.Texture,
Components.Transform,
@ -896,7 +897,7 @@ var ParticleEmitter = new Class({
*/
this.tintFill = false;
this.initRenderNodes('ParticleEmitter');
this.initRenderNodes(this._defaultRenderNodesMap);
this.initPostPipeline();
this.setPosition(x, y);
@ -908,6 +909,23 @@ var ParticleEmitter = new Class({
}
},
/**
* The default render nodes for this Game Object.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#_defaultRenderNodesMap
* @type {Map<string, string>}
* @private
* @webglOnly
* @readonly
* @since 3.90.0
*/
_defaultRenderNodesMap: {
get: function ()
{
return DefaultParticleEmitterNodes;
}
},
// Overrides Game Object method
addedToScene: function ()
{

View file

@ -4,6 +4,7 @@
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var DefaultPointLightNodes = require('../../renderer/webgl/renderNodes/defaults/DefaultPointLightNodes');
var Class = require('../../utils/Class');
var Components = require('../components');
var GameObject = require('../GameObject');
@ -48,7 +49,7 @@ var Render = require('./PointLightRender');
* @extends Phaser.GameObjects.Components.GetBounds
* @extends Phaser.GameObjects.Components.Mask
* @extends Phaser.GameObjects.Components.PostPipeline
* @extends Phaser.GameObjects.Components.RenderNode
* @extends Phaser.GameObjects.Components.RenderNodes
* @extends Phaser.GameObjects.Components.ScrollFactor
* @extends Phaser.GameObjects.Components.Transform
* @extends Phaser.GameObjects.Components.Visible
@ -71,7 +72,7 @@ var PointLight = new Class({
Components.Depth,
Components.Mask,
Components.PostPipeline,
Components.RenderNode,
Components.RenderNodes,
Components.ScrollFactor,
Components.Transform,
Components.Visible,
@ -89,7 +90,7 @@ var PointLight = new Class({
GameObject.call(this, scene, 'PointLight');
this.initRenderNodes('PointLight');
this.initRenderNodes(this._defaultRenderNodesMap);
this.initPostPipeline();
this.setPosition(x, y);
@ -136,6 +137,23 @@ var PointLight = new Class({
this._radius = radius;
},
/**
* The default render nodes for this Game Object.
*
* @name Phaser.GameObjects.PointLight#_defaultRenderNodesMap
* @type {Map<string, string>}
* @private
* @webglOnly
* @readonly
* @since 3.90.0
*/
_defaultRenderNodesMap: {
get: function ()
{
return DefaultPointLightNodes;
}
},
/**
* The radius of the Point Light.
*

View file

@ -5,6 +5,7 @@
*/
var AnimationState = require('../../animations/AnimationState');
var DefaultRopeNodes = require('../../renderer/webgl/renderNodes/defaults/DefaultRopeNodes');
var Class = require('../../utils/Class');
var Components = require('../components');
var GameObject = require('../GameObject');
@ -38,7 +39,7 @@ var Vector2 = require('../../math/Vector2');
* @extends Phaser.GameObjects.Components.Flip
* @extends Phaser.GameObjects.Components.Mask
* @extends Phaser.GameObjects.Components.PostPipeline
* @extends Phaser.GameObjects.Components.RenderNode
* @extends Phaser.GameObjects.Components.RenderNodes
* @extends Phaser.GameObjects.Components.Size
* @extends Phaser.GameObjects.Components.Texture
* @extends Phaser.GameObjects.Components.Transform
@ -66,7 +67,7 @@ var Rope = new Class({
Components.Flip,
Components.Mask,
Components.PostPipeline,
Components.RenderNode,
Components.RenderNodes,
Components.Size,
Components.Texture,
Components.Transform,
@ -276,7 +277,7 @@ var Rope = new Class({
this.setTexture(texture, frame);
this.setPosition(x, y);
this.setSizeToFrame();
this.initRenderNodes('Rope');
this.initRenderNodes(this._defaultRenderNodesMap);
this.initPostPipeline();
if (Array.isArray(points))
@ -289,6 +290,23 @@ var Rope = new Class({
this.updateVertices();
},
/**
* The default render nodes for this Game Object.
*
* @name Phaser.GameObjects.Rope#_defaultRenderNodesMap
* @type {Map<string, string>}
* @private
* @webglOnly
* @readonly
* @since 3.90.0
*/
_defaultRenderNodesMap: {
get: function ()
{
return DefaultRopeNodes;
}
},
// Overrides Game Object method
addedToScene: function ()
{

View file

@ -6,6 +6,11 @@
var GetCalcMatrix = require('../GetCalcMatrix');
var renderOptions = {
multiTexturing: false,
smoothPixelArt: false
};
/**
* Renders this Game Object with the WebGL Renderer to the given Camera.
* The object will not render if any of its renderFlags are set or it is being actively filtered out by the Camera.
@ -32,6 +37,19 @@ var RopeWebGLRenderer = function (renderer, src, drawingContext, parentMatrix)
src.updateVertices();
}
// Get smooth pixel art option.
var smoothPixelArt;
var srcTexture = src.texture;
if (srcTexture && srcTexture.smoothPixelArt !== null)
{
smoothPixelArt = srcTexture.smoothPixelArt;
}
else
{
smoothPixelArt = src.scene.game.config.smoothPixelArt;
}
renderOptions.smoothPixelArt = smoothPixelArt;
(src.customRenderNodes.BatchHandler || src.defaultRenderNodes.BatchHandler).batch(
drawingContext,
src,
@ -43,7 +61,8 @@ var RopeWebGLRenderer = function (renderer, src, drawingContext, parentMatrix)
src.alphas,
src.alpha,
src.tintFill,
src.debugCallback
src.debugCallback,
renderOptions
);
};

View file

@ -4,6 +4,7 @@
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var DefaultGraphicsNodes = require('../../renderer/webgl/renderNodes/defaults/DefaultGraphicsNodes');
var Class = require('../../utils/Class');
var Components = require('../components');
var GameObject = require('../GameObject');
@ -31,7 +32,7 @@ var Line = require('../../geom/line/Line');
* @extends Phaser.GameObjects.Components.Mask
* @extends Phaser.GameObjects.Components.Origin
* @extends Phaser.GameObjects.Components.PostPipeline
* @extends Phaser.GameObjects.Components.RenderNode
* @extends Phaser.GameObjects.Components.RenderNodes
* @extends Phaser.GameObjects.Components.ScrollFactor
* @extends Phaser.GameObjects.Components.Transform
* @extends Phaser.GameObjects.Components.Visible
@ -53,7 +54,7 @@ var Shape = new Class({
Components.Mask,
Components.Origin,
Components.PostPipeline,
Components.RenderNode,
Components.RenderNodes,
Components.ScrollFactor,
Components.Transform,
Components.Visible
@ -210,10 +211,27 @@ var Shape = new Class({
*/
this.height = 0;
this.initRenderNodes('Graphics');
this.initRenderNodes(this._defaultRenderNodesMap);
this.initPostPipeline();
},
/**
* The default render nodes for this Game Object.
*
* @name Phaser.GameObjects.Shape#_defaultRenderNodesMap
* @type {Map<string, string>}
* @private
* @webglOnly
* @readonly
* @since 3.90.0
*/
_defaultRenderNodesMap: {
get: function ()
{
return DefaultGraphicsNodes;
}
},
/**
* Sets the fill color and alpha for this Shape.
*

View file

@ -5,6 +5,7 @@
*/
var AnimationState = require('../../animations/AnimationState');
var DefaultImageNodes = require('../../renderer/webgl/renderNodes/defaults/DefaultImageNodes');
var Class = require('../../utils/Class');
var Components = require('../components');
var GameObject = require('../GameObject');
@ -37,7 +38,7 @@ var SpriteRender = require('./SpriteRender');
* @extends Phaser.GameObjects.Components.Mask
* @extends Phaser.GameObjects.Components.Origin
* @extends Phaser.GameObjects.Components.PostPipeline
* @extends Phaser.GameObjects.Components.RenderNode
* @extends Phaser.GameObjects.Components.RenderNodes
* @extends Phaser.GameObjects.Components.ScrollFactor
* @extends Phaser.GameObjects.Components.Size
* @extends Phaser.GameObjects.Components.TextureCrop
@ -65,7 +66,7 @@ var Sprite = new Class({
Components.Mask,
Components.Origin,
Components.PostPipeline,
Components.RenderNode,
Components.RenderNodes,
Components.ScrollFactor,
Components.Size,
Components.TextureCrop,
@ -108,10 +109,27 @@ var Sprite = new Class({
this.setPosition(x, y);
this.setSizeToFrame();
this.setOriginFromFrame();
this.initRenderNodes('Image');
this.initRenderNodes(this._defaultRenderNodesMap);
this.initPostPipeline(true);
},
/**
* The default render nodes for this Game Object.
*
* @name Phaser.GameObjects.Sprite#_defaultRenderNodesMap
* @type {Map<string, string>}
* @private
* @webglOnly
* @readonly
* @since 3.90.0
*/
_defaultRenderNodesMap: {
get: function ()
{
return DefaultImageNodes;
}
},
// Overrides Game Object method
addedToScene: function ()
{

View file

@ -6,6 +6,7 @@
var AddToDOM = require('../../dom/AddToDOM');
var CanvasPool = require('../../display/canvas/CanvasPool');
var DefaultImageNodes = require('../../renderer/webgl/renderNodes/defaults/DefaultImageNodes');
var Class = require('../../utils/Class');
var Components = require('../components');
var GameObject = require('../GameObject');
@ -69,7 +70,7 @@ var UUID = require('../../utils/string/UUID');
* @extends Phaser.GameObjects.Components.Mask
* @extends Phaser.GameObjects.Components.Origin
* @extends Phaser.GameObjects.Components.PostPipeline
* @extends Phaser.GameObjects.Components.RenderNode
* @extends Phaser.GameObjects.Components.RenderNodes
* @extends Phaser.GameObjects.Components.ScrollFactor
* @extends Phaser.GameObjects.Components.Tint
* @extends Phaser.GameObjects.Components.Transform
@ -99,7 +100,7 @@ var Text = new Class({
Components.Mask,
Components.Origin,
Components.PostPipeline,
Components.RenderNode,
Components.RenderNodes,
Components.ScrollFactor,
Components.Tint,
Components.Transform,
@ -127,7 +128,7 @@ var Text = new Class({
this.setPosition(x, y);
this.setOrigin(0, 0);
this.initRenderNodes('Image');
this.initRenderNodes(this._defaultRenderNodesMap);
this.initPostPipeline(true);
/**
@ -308,6 +309,23 @@ var Text = new Class({
}
},
/**
* The default render nodes for this Game Object.
*
* @name Phaser.GameObjects.Text#_defaultRenderNodesMap
* @type {Map<string, string>}
* @private
* @webglOnly
* @readonly
* @since 3.90.0
*/
_defaultRenderNodesMap: {
get: function ()
{
return DefaultImageNodes;
}
},
/**
* Initialize right to left text.
*

View file

@ -6,6 +6,7 @@
var AnimationState = require('../../animations/AnimationState');
var CanvasPool = require('../../display/canvas/CanvasPool');
var DefaultTileSpriteNodes = require('../../renderer/webgl/renderNodes/defaults/DefaultTileSpriteNodes');
var Class = require('../../utils/Class');
var Components = require('../components');
var GameObject = require('../GameObject');
@ -54,7 +55,7 @@ var _FLAG = 8; // 1000
* @extends Phaser.GameObjects.Components.Mask
* @extends Phaser.GameObjects.Components.Origin
* @extends Phaser.GameObjects.Components.PostPipeline
* @extends Phaser.GameObjects.Components.RenderNode
* @extends Phaser.GameObjects.Components.RenderNodes
* @extends Phaser.GameObjects.Components.ScrollFactor
* @extends Phaser.GameObjects.Components.TextureCrop
* @extends Phaser.GameObjects.Components.Tint
@ -85,7 +86,7 @@ var TileSprite = new Class({
Components.Mask,
Components.Origin,
Components.PostPipeline,
Components.RenderNode,
Components.RenderNodes,
Components.ScrollFactor,
Components.TextureCrop,
Components.Tint,
@ -278,10 +279,27 @@ var TileSprite = new Class({
this.setPosition(x, y);
this.setSize(width, height);
this.setOrigin(0.5, 0.5);
this.initRenderNodes('TileSprite');
this.initRenderNodes(this._defaultRenderNodesMap);
this.initPostPipeline(true);
},
/**
* The default render nodes for this Game Object.
*
* @name Phaser.GameObjects.TileSprite#_defaultRenderNodesMap
* @type {Map<string, string>}
* @private
* @webglOnly
* @readonly
* @since 3.90.0
*/
_defaultRenderNodesMap: {
get: function ()
{
return DefaultTileSpriteNodes;
}
},
// Overrides Game Object method
addedToScene: function ()
{

View file

@ -5,6 +5,7 @@
*/
var Clamp = require('../../math/Clamp');
var DefaultImageNodes = require('../../renderer/webgl/renderNodes/defaults/DefaultImageNodes');
var Class = require('../../utils/Class');
var Components = require('../components');
var Events = require('../events');
@ -105,7 +106,7 @@ var VideoRender = require('./VideoRender');
* @extends Phaser.GameObjects.Components.Mask
* @extends Phaser.GameObjects.Components.Origin
* @extends Phaser.GameObjects.Components.PostPipeline
* @extends Phaser.GameObjects.Components.RenderNode
* @extends Phaser.GameObjects.Components.RenderNodes
* @extends Phaser.GameObjects.Components.ScrollFactor
* @extends Phaser.GameObjects.Components.TextureCrop
* @extends Phaser.GameObjects.Components.Tint
@ -132,7 +133,7 @@ var Video = new Class({
Components.Mask,
Components.Origin,
Components.PostPipeline,
Components.RenderNode,
Components.RenderNodes,
Components.ScrollFactor,
Components.TextureCrop,
Components.Tint,
@ -519,7 +520,7 @@ var Video = new Class({
this.setPosition(x, y);
this.setSize(256, 256);
this.initRenderNodes('Image');
this.initRenderNodes(this._defaultRenderNodesMap);
this.initPostPipeline(true);
game.events.on(GameEvents.PAUSE, this.globalPause, this);
@ -538,6 +539,23 @@ var Video = new Class({
}
},
/**
* The default render node map for this Game Object.
*
* @name Phaser.GameObjects.Video#_defaultRenderNodesMap
* @type {Map<string, string>}
* @private
* @webglOnly
* @readonly
* @since 3.90.0
*/
_defaultRenderNodesMap: {
get: function ()
{
return DefaultImageNodes;
}
},
// Overrides Game Object method
addedToScene: function ()
{

View file

@ -279,6 +279,7 @@ var ProgramManager = new Class({
* @method Phaser.Renderer.WebGL.ProgramManager#getAdditionsByTag
* @since 3.90.0
* @param {string} tag - The tag to filter by.
* @returns {Phaser.Types.Renderer.WebGL.ShaderAdditionConfig[]} The shader additions with the tag.
*/
getAdditionsByTag: function (tag)
{
@ -292,6 +293,22 @@ var ProgramManager = new Class({
});
},
/**
* Returns the index of a shader addition with the given name.
*
* @method Phaser.Renderer.WebGL.ProgramManager#getAdditionIndex
* @since 3.90.0
* @param {string} name - The name to find.
* @returns {number} The index of the addition, or `-1` if it was not found.
*/
getAdditionIndex: function (name)
{
return this.currentConfig.additions.findIndex(function (addition)
{
return addition.name === name;
});
},
/**
* Remove a shader addition from the current configuration.
*
@ -307,6 +324,27 @@ var ProgramManager = new Class({
});
},
/**
* Replace a shader addition in the current configuration.
*
* @method Phaser.Renderer.WebGL.ProgramManager#replaceAddition
* @since 3.90.0
* @param {string} name - The name of the shader addition to replace.
* @param {Phaser.Types.Renderer.WebGL.ShaderAdditionConfig} addition - The new shader addition.
*/
replaceAddition: function (name, addition)
{
var index = this.currentConfig.additions.findIndex(function (a)
{
return a.name === name;
});
if (index !== -1)
{
this.currentConfig.additions[index] = addition;
}
},
/**
* Add a feature to the current configuration.
*

View file

@ -514,6 +514,18 @@ var WebGLRenderer = new Class({
*/
this.instancedArraysExtension = null;
/**
* If the browser supports the `OES_standard_derivatives` extension,
* and the `smoothPixelArt` config option is true,
* this property will hold a reference to the glExtension for it.
*
* @name Phaser.Renderer.WebGL.WebGLRenderer#standardDerivativesExtension
* @type {OES_standard_derivatives}
* @default null
* @since 3.90.0
*/
this.standardDerivativesExtension = null;
/**
* If the browser supports the `OES_vertex_array_object` extension, this property will hold
* a reference to the glExtension for it.
@ -893,6 +905,12 @@ var WebGLRenderer = new Class({
_this.vaoExtension = (exts.indexOf(vaoString) > -1) ? gl.getExtension(vaoString) : null;
if (game.config.smoothPixelArt)
{
var stdDerivativesString = 'OES_standard_derivatives';
_this.standardDerivativesExtension = (exts.indexOf(stdDerivativesString) > -1) ? gl.getExtension(stdDerivativesString) : null;
}
};
setupExtensions();

View file

@ -6,13 +6,21 @@
var Vector2 = require('../../../math/Vector2');
var Class = require('../../../utils/Class');
var DeepCopy = require('../../../utils/object/DeepCopy');
var Utils = require('../Utils');
var ShaderSourceFS = require('../shaders/Multi-frag');
var ShaderSourceVS = require('../shaders/Multi-vert');
var MakeApplyLighting = require('../shaders/configs/MakeApplyLighting');
var MakeApplyTint = require('../shaders/configs/MakeApplyTint');
var MakeDefineLights = require('../shaders/configs/MakeDefineLights');
var MakeDefineTexCount = require('../shaders/configs/MakeDefineTexCount');
var MakeGetNormalFromMap = require('../shaders/configs/MakeGetNormalFromMap');
var MakeGetTexCoordOut = require('../shaders/configs/MakeGetTexCoordOut');
var MakeGetTexRes = require('../shaders/configs/MakeGetTexRes');
var MakeGetTexture = require('../shaders/configs/MakeGetTexture');
var MakeOutInverseRotation = require('../shaders/configs/MakeOutInverseRotation');
var MakeRotationDatum = require('../shaders/configs/MakeRotationDatum');
var MakeSmoothPixelArt = require('../shaders/configs/MakeSmoothPixelArt');
var BatchHandler = require('./BatchHandler');
/**
@ -32,6 +40,7 @@ var BatchHandlerQuad = new Class({
initialize: function BatchHandlerQuad (manager, config)
{
// Placed before super call because the constructor needs it.
/**
* The current render options to which the batch is built.
* These help define the shader.
@ -42,10 +51,12 @@ var BatchHandlerQuad = new Class({
*/
this.renderOptions = {
multiTexturing: false,
texRes: false,
lighting: false,
selfShadow: false,
selfShadowPenumbra: 0,
selfShadowThreshold: 0
selfShadowThreshold: 0,
smoothPixelArt: false
};
BatchHandler.call(this, manager, config, this.defaultConfig);
@ -57,6 +68,16 @@ var BatchHandlerQuad = new Class({
this.manager.renderer.textureUnitIndices
);
/**
* The render options currently being built.
* These are assigned to `renderOptions` by `applyRenderOptions`.
*
* @name Phaser.Renderer.WebGL.RenderNodes.BatchHandlerQuad#nextRenderOptions
* @type {object}
* @since 3.90.0
*/
this.nextRenderOptions = DeepCopy(this.renderOptions);
/**
* A persistent calculation vector used when processing the lights.
*
@ -84,9 +105,16 @@ var BatchHandlerQuad = new Class({
vertexSource: ShaderSourceVS,
fragmentSource: ShaderSourceFS,
shaderAdditions: [
MakeGetTexture(1),
MakeGetTexCoordOut(),
MakeGetTexRes(true),
MakeSmoothPixelArt(true),
MakeDefineTexCount(1),
MakeGetTexture(),
MakeApplyTint(),
MakeDefineLights(true),
MakeRotationDatum(true),
MakeOutInverseRotation(true),
MakeGetNormalFromMap(true),
MakeApplyLighting(true)
],
vertexBufferLayout: {
@ -200,16 +228,14 @@ var BatchHandlerQuad = new Class({
if (this.renderOptions.multiTexturing)
{
var programManager = this.programManager;
var textureAdditions = programManager.getAdditionsByTag('TEXTURE');
while (textureAdditions.length > 0)
var textureAddition = programManager.getAdditionsByTag('TEXTURE')[0];
if (textureAddition)
{
var textureAddition = textureAdditions.pop();
programManager.removeAddition(textureAddition.name);
programManager.replaceAddition(
textureAddition.name,
MakeGetTexture(this.maxTexturesPerBatch)
);
}
programManager.addAddition(
MakeGetTexture(this.maxTexturesPerBatch),
0
);
}
this.resize(renderer.width, renderer.height);
@ -249,111 +275,167 @@ var BatchHandlerQuad = new Class({
drawingContext.renderer.projectionMatrix.val
);
// Lighting uniforms.
Utils.updateLightingUniforms(
renderOptions.lighting,
this.manager.renderer,
drawingContext,
programManager,
1,
this._lightVector,
renderOptions.selfShadow,
renderOptions.selfShadowThreshold,
renderOptions.selfShadowPenumbra
);
if (this.renderOptions.lighting)
{
// Lighting uniforms.
Utils.updateLightingUniforms(
renderOptions.lighting,
this.manager.renderer,
drawingContext,
programManager,
1,
this._lightVector,
renderOptions.selfShadow,
renderOptions.selfShadowThreshold,
renderOptions.selfShadowPenumbra
);
}
},
/**
* Update the texture uniforms for the current shader program.
*
* This method is called automatically when the batch is run.
*
* @method Phaser.Renderer.WebGL.RenderNodes.BatchHandlerQuad#setupTextureUniforms
* @since 3.90.0
* @param {Phaser.Renderer.WebGL.WebGLTextureWrapper[]} textures - The textures to render.
*/
setupTextureUniforms: function (textures)
{
var programManager = this.programManager;
if (this.renderOptions.multiTexturing)
{
// In the shader, this is an array of vec2s.
// But we must compose it as a flat array,
// not an array of arrays.
var dims = [];
for (var i = 0; i < textures.length; i++)
{
dims.push(textures[i].width, textures[i].height);
}
programManager.setUniform(
'uMainResolution[0]',
dims
);
}
else
{
programManager.setUniform(
'uMainResolution[0]',
[ textures[0].width, textures[0].height ]
);
}
},
/**
* Update the render options for the current shader program.
* If the options have changed, the batch is run to apply the changes.
*
* @method Phaser.Renderer.WebGL.RenderNodes.BatchHandlerQuad#updateRenderOptions
* @since 3.90.0
* @param {Phaser.Types.Renderer.WebGL.DrawingContext} drawingContext - The current drawing context.
* @param {object} renderOptions - The new render options.
*/
updateRenderOptions: function (drawingContext, renderOptions)
updateRenderOptions: function (renderOptions)
{
var newRenderOptions = this.nextRenderOptions;
newRenderOptions.multiTexturing = !!renderOptions.multiTexturing && !renderOptions.lighting;
newRenderOptions.lighting = !!renderOptions.lighting;
newRenderOptions.selfShadow = newRenderOptions.lighting && renderOptions.lighting.selfShadow && renderOptions.lighting.selfShadow.enabled;
newRenderOptions.selfShadowPenumbra = newRenderOptions.selfShadow ? renderOptions.lighting.selfShadow.penumbra : 0;
newRenderOptions.selfShadowThreshold = newRenderOptions.selfShadow ? renderOptions.lighting.selfShadow.diffuseFlatThreshold : 0;
newRenderOptions.smoothPixelArt = !!renderOptions.smoothPixelArt;
newRenderOptions.texRes = newRenderOptions.smoothPixelArt;
},
/**
* Check `renderOptions` against `nextRenderOptions`.
* If they differ, run the batch, and apply the new options.
*
* @method Phaser.Renderer.WebGL.RenderNodes.BatchHandlerQuad#applyRenderOptions
* @since 3.90.0
* @param {Phaser.Types.Renderer.WebGL.DrawingContext} drawingContext - The current drawing context.
*/
applyRenderOptions: function (drawingContext)
{
var renderOptions = this.renderOptions;
var nextRenderOptions = this.nextRenderOptions;
var changed = false;
for (var key in nextRenderOptions)
{
if (nextRenderOptions[key] !== renderOptions[key])
{
changed = true;
}
}
if (changed)
{
this.run(drawingContext);
this.updateShaderConfig();
}
},
/**
* Update the shader configuration based on render options.
* This is called automatically when the render options change.
*
* @method Phaser.Renderer.WebGL.RenderNodes.BatchHandlerQuad#updateShaderConfig
* @since 3.90.0
*/
updateShaderConfig: function ()
{
var programManager = this.programManager;
var oldRenderOptions = this.renderOptions;
var newRenderOptions = {
multiTexturing: false,
lighting: false,
selfShadow: false,
selfShadowPenumbra: 0,
selfShadowThreshold: 0
};
var newRenderOptions = this.nextRenderOptions;
var i;
// Parse shader-relevant render options.
if (renderOptions)
if (oldRenderOptions.multiTexturing !== newRenderOptions.multiTexturing)
{
// Multitexturing is disabled if other textures are in use.
newRenderOptions.multiTexturing = !!renderOptions.multiTexturing && !renderOptions.lighting;
var multiTexturing = newRenderOptions.multiTexturing;
oldRenderOptions.multiTexturing = multiTexturing;
newRenderOptions.lighting = !!renderOptions.lighting;
if (renderOptions.lighting && renderOptions.lighting.selfShadow && renderOptions.lighting.selfShadow.enabled)
var texCountAddition = programManager.getAdditionsByTag('TexCount')[0];
if (texCountAddition)
{
newRenderOptions.selfShadow = true;
newRenderOptions.selfShadowPenumbra = renderOptions.lighting.selfShadow.penumbra;
newRenderOptions.selfShadowThreshold = renderOptions.lighting.selfShadow.diffuseFlatThreshold;
programManager.replaceAddition(
texCountAddition.name,
MakeDefineTexCount(multiTexturing ? this.maxTexturesPerBatch : 1)
);
}
}
// Check for changes.
var updateTexturing = newRenderOptions.multiTexturing !== oldRenderOptions.multiTexturing;
var updateLighting = newRenderOptions.lighting !== oldRenderOptions.lighting;
var updateSelfShadow = newRenderOptions.selfShadow !== oldRenderOptions.selfShadow;
var updateSelfShadowPenumbra = newRenderOptions.selfShadowPenumbra !== oldRenderOptions.selfShadowPenumbra;
var updateSelfShadowThreshold = newRenderOptions.selfShadowThreshold !== oldRenderOptions.selfShadowThreshold;
// Run the batch if the shader has changed.
if (updateTexturing || updateLighting || updateSelfShadow || updateSelfShadowPenumbra || updateSelfShadowThreshold)
if (oldRenderOptions.lighting !== newRenderOptions.lighting)
{
this.run(drawingContext);
}
var lighting = newRenderOptions.lighting;
oldRenderOptions.lighting = lighting;
// Cache new render options.
this.renderOptions = newRenderOptions;
// Update shader program configuration.
if (updateTexturing)
{
var texturingAddition = programManager.getAdditionsByTag('TEXTURE')[0];
if (texturingAddition)
var lightingAdditions = programManager.getAdditionsByTag('LIGHTING');
for (i = 0; i < lightingAdditions.length; i++)
{
programManager.removeAddition(texturingAddition.name);
var lightingAddition = lightingAdditions[i];
lightingAddition.disable = !lighting;
}
var texCount = newRenderOptions.multiTexturing ? this.maxTexturesPerBatch : 1;
programManager.addAddition(
MakeGetTexture(texCount),
0
);
}
if (updateLighting)
{
var lightingAddition = programManager.getAddition('LIGHTING');
if (lightingAddition)
if (lighting)
{
lightingAddition.disable = !newRenderOptions.lighting;
if (newRenderOptions.lighting)
var defineLightsAddition = programManager.getAddition('DefineLights');
if (defineLightsAddition)
{
lightingAddition.additions.fragmentDefine = '#define LIGHT_COUNT ' + this.manager.renderer.config.maxLights;
defineLightsAddition.additions.fragmentDefine = '#define LIGHT_COUNT ' + this.manager.renderer.config.maxLights;
}
}
var rotationAddition = programManager.getAddition('RotDatum');
if (rotationAddition)
{
rotationAddition.disable = !newRenderOptions.lighting;
}
}
if (updateSelfShadow)
if (oldRenderOptions.selfShadow !== newRenderOptions.selfShadow)
{
if (newRenderOptions.selfShadow)
var selfShadow = newRenderOptions.selfShadow;
oldRenderOptions.selfShadow = selfShadow;
if (selfShadow)
{
programManager.addFeature('SELFSHADOW');
}
@ -362,6 +444,33 @@ var BatchHandlerQuad = new Class({
programManager.removeFeature('SELFSHADOW');
}
}
oldRenderOptions.selfShadowPenumbra = newRenderOptions.selfShadowPenumbra;
oldRenderOptions.selfShadowThreshold = newRenderOptions.selfShadowThreshold;
if (oldRenderOptions.smoothPixelArt !== newRenderOptions.smoothPixelArt)
{
var smoothPixelArt = newRenderOptions.smoothPixelArt;
oldRenderOptions.smoothPixelArt = smoothPixelArt;
var smoothPixelArtAddition = programManager.getAddition('SmoothPixelArt');
if (smoothPixelArtAddition)
{
smoothPixelArtAddition.disable = !smoothPixelArt;
}
}
if (oldRenderOptions.texRes !== newRenderOptions.texRes)
{
var texRes = newRenderOptions.texRes;
oldRenderOptions.texRes = texRes;
var texResAddition = programManager.getAddition('GetTexRes');
if (texResAddition)
{
texResAddition.disable = !texRes;
}
}
},
/**
@ -405,6 +514,13 @@ var BatchHandlerQuad = new Class({
for (var i = 0; i < subBatches; i++)
{
var entry = this.batchEntries[i];
if (this.renderOptions.texRes)
{
this.setupTextureUniforms(entry.texture);
programManager.applyUniforms(program);
}
renderer.drawElements(
drawingContext,
entry.texture,
@ -455,7 +571,7 @@ var BatchHandlerQuad = new Class({
* @param {number} tintBL - The bottom-left tint color.
* @param {number} tintTR - The top-right tint color.
* @param {number} tintBR - The bottom-right tint color.
* @param {object} [renderOptions] - Optional render features.
* @param {object} renderOptions - Optional render features.
* @param {boolean} [renderOptions.multiTexturing] - Whether to use multi-texturing.
* @param {object} [renderOptions.lighting] - How to treat lighting. If this object is defined, lighting will be activated, and multi-texturing disabled.
* @param {Phaser.Renderer.WebGL.WebGLTextureWrapper} renderOptions.lighting.normalGLTexture - The normal map texture to render.
@ -464,6 +580,7 @@ var BatchHandlerQuad = new Class({
* @param {boolean} renderOptions.lighting.selfShadow.enabled - Whether to use self-shadowing.
* @param {number} renderOptions.lighting.selfShadow.penumbra - Self-shadowing penumbra strength.
* @param {number} renderOptions.lighting.selfShadow.diffuseFlatThreshold - Self-shadowing texture brightness equivalent to a flat surface.
* @param {boolean} [renderOptions.smoothPixelArt] - Whether to use the smooth pixel art algorithm.
*/
batch: function (
currentContext,
@ -485,7 +602,8 @@ var BatchHandlerQuad = new Class({
}
// Check render options and run the batch if they differ.
this.updateRenderOptions(currentContext, renderOptions);
this.updateRenderOptions(renderOptions);
this.applyRenderOptions(currentContext);
// Process textures and get relevant data.
var textureDatum = this.batchTextures(glTexture, renderOptions);

View file

@ -8,6 +8,10 @@ var Class = require('../../../utils/Class');
var ShaderSourceFS = require('../shaders/Multi-frag');
var ShaderSourceVS = require('../shaders/Multi-vert');
var MakeApplyTint = require('../shaders/configs/MakeApplyTint');
var MakeDefineTexCount = require('../shaders/configs/MakeDefineTexCount');
var MakeGetTexCoordOut = require('../shaders/configs/MakeGetTexCoordOut');
var MakeGetTexRes = require('../shaders/configs/MakeGetTexRes');
var MakeSmoothPixelArt = require('../shaders/configs/MakeSmoothPixelArt');
var MakeGetTexture = require('../shaders/configs/MakeGetTexture');
var Utils = require('../Utils');
var BatchHandlerQuad = require('./BatchHandlerQuad');
@ -57,7 +61,11 @@ var BatchHandlerStrip = new Class({
vertexSource: ShaderSourceVS,
fragmentSource: ShaderSourceFS,
shaderAdditions: [
MakeGetTexture(1),
MakeGetTexCoordOut(),
MakeGetTexRes(true),
MakeSmoothPixelArt(true),
MakeDefineTexCount(1),
MakeGetTexture(),
MakeApplyTint()
],
vertexBufferLayout: {
@ -131,8 +139,11 @@ var BatchHandlerStrip = new Class({
* @param {number} alpha - The overall alpha value of the strip.
* @param {number} tintFill - Whether to tint the fill color.
* @param {function} [debugCallback] - The debug callback, called with an array consisting of alternating x,y values of the transformed vertices.
* @param {object} renderOptions - Optional render features.
* @param {boolean} [renderOptions.multiTexturing] - Whether to use multi-texturing. This should always be false for ropes.
* @param {boolean} [renderOptions.smoothPixelArt] - Whether to use the smooth pixel art algorithm.
*/
batch: function (drawingContext, src, calcMatrix, glTexture, vertices, uv, colors, alphas, alpha, tintFill, debugCallback)
batch: function (drawingContext, src, calcMatrix, glTexture, vertices, uv, colors, alphas, alpha, tintFill, debugCallback, renderOptions)
{
if (this.instanceCount === 0)
{
@ -154,6 +165,10 @@ var BatchHandlerStrip = new Class({
// Now the batch is empty.
}
// Check render options and run the batch if they differ.
this.updateRenderOptions(renderOptions);
this.applyRenderOptions(drawingContext);
// Process textures and get relevant data.
var textureDatum = this.batchTextures(glTexture);

View file

@ -9,14 +9,25 @@ var ShaderSourceFS = require('../shaders/Multi-frag');
var ShaderSourceVS = require('../shaders/Multi-vert');
var MakeApplyLighting = require('../shaders/configs/MakeApplyLighting');
var MakeApplyTint = require('../shaders/configs/MakeApplyTint');
var MakeDefineLights = require('../shaders/configs/MakeDefineLights');
var MakeDefineTexCount = require('../shaders/configs/MakeDefineTexCount');
var MakeGetNormalFromMap = require('../shaders/configs/MakeGetNormalFromMap');
var MakeGetTexCoordOut = require('../shaders/configs/MakeGetTexCoordOut');
var MakeGetTexRes = require('../shaders/configs/MakeGetTexRes');
var MakeGetTexture = require('../shaders/configs/MakeGetTexture');
var MakeOutFrame = require('../shaders/configs/MakeOutFrame');
var MakeOutInverseRotation = require('../shaders/configs/MakeOutInverseRotation');
var MakeRotationDatum = require('../shaders/configs/MakeRotationDatum');
var MakeTileSpriteWrap = require('../shaders/configs/MakeTileSpriteWrap');
var MakeSmoothPixelArt = require('../shaders/configs/MakeSmoothPixelArt');
var MakeTexCoordFrameClamp = require('../shaders/configs/MakeTexCoordFrameClamp');
var MakeTexCoordFrameWrap = require('../shaders/configs/MakeTexCoordFrameWrap');
var BatchHandlerQuad = require('./BatchHandlerQuad');
/**
* @classdesc
* This RenderNode handles batch rendering of TileSprites.
* This RenderNode handles batch rendering of TileSprites and Tiles.
* It supplies shaders with knowledge of the frame and texture data,
* which can be used to handle texture borders more intelligently.
*
* @class BatchHandlerTileSprite
* @memberof Phaser.Renderer.WebGL.RenderNodes
@ -42,10 +53,19 @@ var BatchHandlerTileSprite = new Class({
vertexSource: ShaderSourceVS,
fragmentSource: ShaderSourceFS,
shaderAdditions: [
MakeGetTexture(1),
MakeTileSpriteWrap(),
MakeOutFrame(),
MakeGetTexCoordOut(),
MakeGetTexRes(true),
MakeTexCoordFrameWrap(true),
MakeTexCoordFrameClamp(true),
MakeSmoothPixelArt(true),
MakeDefineTexCount(1),
MakeGetTexture(),
MakeApplyTint(),
MakeDefineLights(true),
MakeRotationDatum(true),
MakeOutInverseRotation(true),
MakeGetNormalFromMap(true),
MakeApplyLighting(true)
],
vertexBufferLayout: {
@ -79,12 +99,52 @@ var BatchHandlerTileSprite = new Class({
}
},
updateRenderOptions: function (renderOptions)
{
BatchHandlerQuad.prototype.updateRenderOptions.call(this, renderOptions);
var newRenderOptions = this.nextRenderOptions;
newRenderOptions.clampFrame = !!renderOptions.clampFrame;
newRenderOptions.wrapFrame = !!renderOptions.wrapFrame;
// Enable texture resolution data if not already available.
newRenderOptions.texRes = newRenderOptions.clampFrame || newRenderOptions.texRes;
},
updateShaderConfig: function ()
{
BatchHandlerQuad.prototype.updateShaderConfig.call(this);
var programManager = this.programManager;
var oldRenderOptions = this.renderOptions;
var newRenderOptions = this.nextRenderOptions;
if (newRenderOptions.clampFrame !== oldRenderOptions.clampFrame)
{
var clampFrame = newRenderOptions.clampFrame;
oldRenderOptions.clampFrame = clampFrame;
var clampAddition = programManager.getAddition('TexCoordFrameClamp');
clampAddition.disable = !newRenderOptions.clampFrame;
}
if (newRenderOptions.wrapFrame !== oldRenderOptions.wrapFrame)
{
var wrapFrame = newRenderOptions.wrapFrame;
oldRenderOptions.wrapFrame = wrapFrame;
var wrapAddition = programManager.getAddition('TexCoordFrameWrap');
wrapAddition.disable = !wrapFrame;
}
},
/**
* Add a TileSprite to the batch.
* Add a quad to the batch.
*
* @method Phaser.Renderer.WebGL.RenderNodes.BatchHandlerTileSprite#batch
* @since 3.90.0
* @param {Phaser.Renderer.WebGL.DrawingContext} currentContext - The current drawing context.
* @param {Phaser.Renderer.WebGL.DrawingContext} drawingContext - The current drawing context.
* @param {Phaser.Renderer.WebGL.WebGLTextureWrapper} glTexture - The texture to render.
* @param {number} x0 - The x-coordinate of the top-left corner.
* @param {number} y0 - The y-coordinate of the top-left corner.
@ -111,7 +171,7 @@ var BatchHandlerTileSprite = new Class({
* @param {number} tintBL - The tint color for the bottom-left corner.
* @param {number} tintTR - The tint color for the top-right corner.
* @param {number} tintBR - The tint color for the bottom-right corner.
* @param {object} [renderOptions] - Optional render features.
* @param {object} renderOptions - Optional render features.
* @param {boolean} [renderOptions.multiTexturing] - Whether to use multi-texturing.
* @param {object} [renderOptions.lighting] - How to treat lighting. If this object is defined, lighting will be activated, and multi-texturing disabled.
* @param {Phaser.Renderer.WebGL.WebGLTextureWrapper} renderOptions.lighting.normalGLTexture - The normal map texture to render.
@ -120,9 +180,12 @@ var BatchHandlerTileSprite = new Class({
* @param {boolean} renderOptions.lighting.selfShadow.enabled - Whether to use self-shadowing.
* @param {number} renderOptions.lighting.selfShadow.penumbra - Self-shadowing penumbra strength.
* @param {number} renderOptions.lighting.selfShadow.diffuseFlatThreshold - Self-shadowing texture brightness equivalent to a flat surface.
* @param {boolean} [renderOptions.smoothPixelArt] - Whether to use the smooth pixel art algorithm.
* @param {boolean} [renderOptions.clampFrame] - Whether to clamp the texture frame. This prevents bleeding due to linear filtering. It is mostly useful for tiles.
* @param {boolean} [renderOptions.wrapFrame] - Whether to wrap the texture frame. This is necessary for TileSprites.
*/
batch: function (
currentContext,
drawingContext,
glTexture,
x0, y0, x1, y1, x2, y2, x3, y3,
texX, texY, texWidth, texHeight,
@ -134,11 +197,12 @@ var BatchHandlerTileSprite = new Class({
{
if (this.instanceCount === 0)
{
this.manager.setCurrentBatchNode(this, currentContext);
this.manager.setCurrentBatchNode(this, drawingContext);
}
// Check render options and run the batch if they differ.
this.updateRenderOptions(currentContext, renderOptions);
this.updateRenderOptions(renderOptions);
this.applyRenderOptions(drawingContext);
// Process textures and get relevant data.
var textureDatum = this.batchTextures(glTexture, renderOptions);
@ -209,7 +273,7 @@ var BatchHandlerTileSprite = new Class({
// This guarantees that none of the arrays are full above.
if (this.instanceCount === this.instancesPerBatch)
{
this.run(currentContext);
this.run(drawingContext);
// Now the batch is empty.
}

View file

@ -7,6 +7,8 @@
var Vector2 = require('../../../math/Vector2');
var Class = require('../../../utils/Class');
var MakeApplyLighting = require('../shaders/configs/MakeApplyLighting');
var MakeDefineLights = require('../shaders/configs/MakeDefineLights');
var MakeFlatNormal = require('../shaders/configs/MakeFlatNormal');
var ShaderSourceFS = require('../shaders/Flat-frag');
var ShaderSourceVS = require('../shaders/Flat-vert');
var Utils = require('../Utils');
@ -73,6 +75,18 @@ var BatchHandlerTriFlat = new Class({
this.renderOptions = {
lighting: false
};
/**
* The render options currently being built.
* These are assigned to `renderOptions` by `applyRenderOptions`.
*
* @name Phaser.Renderer.WebGL.RenderNodes.BatchHandlerTriFlat#nextRenderOptions
* @type {object}
* @since 3.90.0
*/
this.nextRenderOptions = {
lighting: false
};
},
defaultConfig: {
@ -83,9 +97,10 @@ var BatchHandlerTriFlat = new Class({
vertexSource: ShaderSourceVS,
fragmentSource: ShaderSourceFS,
shaderAdditions: [
MakeDefineLights(true),
MakeFlatNormal(true),
MakeApplyLighting(true)
],
shaderFeatures: [ 'FLAT_LIGHTING' ],
indexBufferDynamic: true,
vertexBufferLayout: {
usage: 'DYNAMIC_DRAW',
@ -160,37 +175,58 @@ var BatchHandlerTriFlat = new Class({
}
},
/**
* Update the render options for the current shader program.
* If the options have changed, the batch is run to apply the changes.
*
* @method Phaser.Renderer.WebGL.RenderNodes.BatchHandlerTriFlat#updateRenderOptions
* @since 3.90.0
* @param {Phaser.Types.Renderer.WebGL.DrawingContext} drawingContext - The current drawing context.
* @param {boolean} lighting - Should this batch use lighting?
*/
updateRenderOptions: function (drawingContext, lighting)
updateRenderOptions: function (lighting)
{
var newRenderOptions = this.nextRenderOptions;
newRenderOptions.lighting = lighting;
},
applyRenderOptions: function (drawingContext)
{
var renderOptions = this.renderOptions;
var nextRenderOptions = this.nextRenderOptions;
var changed = false;
for (var key in nextRenderOptions)
{
if (nextRenderOptions[key] !== renderOptions[key])
{
changed = true;
}
}
if (changed)
{
this.run(drawingContext);
this.updateShaderConfig();
}
},
updateShaderConfig: function ()
{
var programManager = this.programManager;
var renderOptions = this.renderOptions;
var updateLighting = this.renderOptions.lighting !== lighting;
var nextRenderOptions = this.nextRenderOptions;
if (updateLighting)
if (renderOptions.lighting !== nextRenderOptions.lighting)
{
this.run(drawingContext);
}
renderOptions.lighting = lighting;
if (updateLighting)
{
var lightingAddition = programManager.getAddition('LIGHTING');
if (lightingAddition)
var lighting = nextRenderOptions.lighting;
renderOptions.lighting = lighting;
var lightingAdditions = programManager.getAdditionsByTag('LIGHTING');
for (var i = 0; i < lightingAdditions.length; i++)
{
lightingAddition.disable = !lighting;
if (lighting)
var addition = lightingAdditions[i];
addition.disable = !lighting;
}
if (lighting)
{
var defineLightsAddition = programManager.getAddition('DefineLights');
if (defineLightsAddition)
{
lightingAddition.additions.fragmentDefine = '#define LIGHT_COUNT ' + this.manager.renderer.config.maxLights;
defineLightsAddition.additions.fragmentDefine = '#define LIGHT_COUNT ' + this.manager.renderer.config.maxLights;
}
}
}
@ -277,7 +313,8 @@ var BatchHandlerTriFlat = new Class({
}
// Check render options and run the batch if they differ.
this.updateRenderOptions(currentContext, lighting);
this.updateRenderOptions(lighting);
this.applyRenderOptions(currentContext);
var passID = 0;
var instanceCompletion = 0;

View file

@ -8,17 +8,6 @@ var EventEmitter = require('eventemitter3');
var Class = require('../../../utils/Class');
var Events = require('../../events');
var DefaultBitmapTextNodes = require('./defaults/DefaultBitmapTextNodes');
var DefaultBlitterNodes = require('./defaults/DefaultBlitterNodes');
var DefaultGraphicsNodes = require('./defaults/DefaultGraphicsNodes');
var DefaultImageNodes = require('./defaults/DefaultImageNodes');
var DefaultNineSliceNodes = require('./defaults/DefaultNineSliceNodes');
var DefaultParticleEmitterNodes = require('./defaults/DefaultParticleEmitterNodes');
var DefaultPointLightNodes = require('./defaults/DefaultPointLightNodes');
var DefaultRopeNodes = require('./defaults/DefaultRopeNodes');
var DefaultTilemapLayerNodes = require('./defaults/DefaultTilemapLayerNodes');
var DefaultTileSpriteNodes = require('./defaults/DefaultTileSpriteNodes');
var BatchHandlerPointLight = require('./BatchHandlerPointLight');
var BatchHandlerQuad = require('./BatchHandlerQuad');
var BatchHandlerStrip = require('./BatchHandlerStrip');
@ -34,6 +23,8 @@ 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');
@ -96,28 +87,6 @@ var RenderNodeManager = new Class({
*/
this.maxParallelTextureUnits = (game.config.autoMobilePipeline && !game.device.os.desktop) ? 1 : renderer.maxTextures;
/**
* The default render nodes for game objects.
* These maps are requested when a game object is created,
* and are used to assign default render nodes to the game object.
*
* @name Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager#defaultRenderNodes
* @type {object}
* @since 3.90.0
*/
this.defaultRenderNodes = {
BitmapText: DefaultBitmapTextNodes,
Blitter: DefaultBlitterNodes,
Graphics: DefaultGraphicsNodes,
Image: DefaultImageNodes,
NineSlice: DefaultNineSliceNodes,
ParticleEmitter: DefaultParticleEmitterNodes,
PointLight: DefaultPointLightNodes,
Rope: DefaultRopeNodes,
TilemapLayer: DefaultTilemapLayerNodes,
TileSprite: DefaultTileSpriteNodes
};
/**
* Nodes available for use. This is an internal object,
* where the keys are the names of the nodes.
@ -162,6 +131,8 @@ var RenderNodeManager = new Class({
RebindContext: RebindContext,
StrokePath: StrokePath,
SubmitterQuad: SubmitterQuad,
SubmitterTile: SubmitterTile,
SubmitterTilemapGPULayer: SubmitterTilemapGPULayer,
SubmitterTileSprite: SubmitterTileSprite,
TexturerImage: TexturerImage,
TexturerTileSprite: TexturerTileSprite,

View file

@ -0,0 +1,13 @@
/**
* @author Benjamin D. Richards <benjamindrichards@gmail.com>
* @copyright 2013-2024 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var Map = require('../../../../structs/Map');
var DefaultImageNodes = new Map([
[ 'Submitter', 'SubmitterTilemapGPULayer' ]
]);
module.exports = DefaultImageNodes;

View file

@ -7,8 +7,8 @@
var Map = require('../../../../structs/Map');
var DefaultTilemapLayerNodes = new Map([
[ 'Submitter', 'SubmitterQuad' ],
[ 'BatchHandler', 'BatchHandlerQuad' ],
[ 'Submitter', 'SubmitterTile' ],
[ 'BatchHandler', 'BatchHandlerTileSprite' ],
[ 'Transformer', 'TransformerTile' ]
]);

View file

@ -26,6 +26,8 @@ var RenderNodes = {
RenderNode: require('./RenderNode'),
StrokePath: require('./StrokePath'),
SubmitterQuad: require('./submitter/SubmitterQuad'),
SubmitterTile: require('./submitter/SubmitterTile'),
SubmitterTilemapGPULayer: require('./submitter/SubmitterTilemapGPULayer'),
SubmitterTileSprite: require('./submitter/SubmitterTileSprite'),
TexturerImage: require('./texturer/TexturerImage'),
TexturerTileSprite: require('./texturer/TexturerTileSprite'),

View file

@ -62,7 +62,8 @@ var SubmitterQuad = new Class({
*/
this._renderOptions = {
multiTexturing: true,
lighting: null
lighting: null,
smoothPixelArt: null
};
/**
@ -199,29 +200,39 @@ var SubmitterQuad = new Class({
setRenderOptions: function (gameObject, normalMap, normalMapRotation)
{
var baseTexture, sourceIndex;
if (gameObject.displayTexture)
{
baseTexture = gameObject.displayTexture;
sourceIndex = gameObject.displayFrame.sourceIndex;
}
else if (gameObject.texture)
{
baseTexture = gameObject.texture;
sourceIndex = gameObject.frame.sourceIndex;
}
else if (gameObject.tileset)
{
if (Array.isArray(gameObject.tileset))
{
baseTexture = gameObject.tileset[0].image;
}
else
{
baseTexture = gameObject.tileset.image;
}
sourceIndex = 0;
}
if (gameObject.lighting)
{
// Get normal map.
if (!normalMap)
{
if (gameObject.displayTexture)
if (baseTexture)
{
normalMap = gameObject.displayTexture.dataSource[gameObject.displayFrame.sourceIndex];
}
else if (gameObject.texture)
{
normalMap = gameObject.texture.dataSource[gameObject.frame.sourceIndex];
}
else if (gameObject.tileset)
{
if (Array.isArray(gameObject.tileset))
{
normalMap = gameObject.tileset[0].image.dataSource[0];
}
else
{
normalMap = gameObject.tileset.image.dataSource[0];
}
normalMap = baseTexture.dataSource[sourceIndex];
}
}
if (!normalMap)
@ -264,6 +275,18 @@ var SubmitterQuad = new Class({
{
this._renderOptions.lighting = null;
}
// Get smooth pixel art option.
var smoothPixelArt;
if (baseTexture && baseTexture.smoothPixelArt !== null)
{
smoothPixelArt = baseTexture.smoothPixelArt;
}
else
{
smoothPixelArt = gameObject.scene.game.config.smoothPixelArt;
}
this._renderOptions.smoothPixelArt = smoothPixelArt;
}
});

View file

@ -0,0 +1,160 @@
/**
* @author Benjamin D. Richards <benjamindrichards@gmail.com>
* @copyright 2013-2024 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var Class = require('../../../../utils/Class');
var Utils = require('../../Utils.js');
var SubmitterQuad = require('./SubmitterQuad');
var getTint = Utils.getTintAppendFloatAlpha;
/**
* @classdesc
* The SubmitterTile RenderNode submits data for tiles.
*
* @class SubmitterTile
* @extends Phaser.Renderer.WebGL.RenderNodes.SubmitterQuad
* @memberof Phaser.Renderer.WebGL.RenderNodes
* @constructor
* @since 3.90.0
* @param {Phaser.Renderer.WebGL.WebGLRenderer} manager - The WebGLRenderer that owns this Submitter.
* @param {object} [config] - The configuration object for this Submitter.
* @param {string} [config.name='SubmitterTile'] - The name of this Submitter.
* @param {string} [config.role='Submitter'] - The role of this Submitter.
* @param {string} [config.batchHandler='BatchHandler'] - The key of the default batch handler node to use for this Submitter. This should correspond to a node which extends `BatchHandlerTile`. It will be derived from the game object whenever the node runs.
*/
var SubmitterTile = new Class({
Extends: SubmitterQuad,
initialize: function SubmitterTile (manager, config)
{
SubmitterQuad.call(this, manager, config);
this._renderOptions.clampFrame = true;
},
/**
* The default configuration for this RenderNode.
*
* @name Phaser.Renderer.WebGL.RenderNodes.SubmitterTile#defaultConfig
* @type {object}
*/
defaultConfig: {
name: 'SubmitterTile',
role: 'Submitter',
batchHandler: 'BatchHandler'
},
/**
* Submit data for rendering.
*
* @method Phaser.Renderer.WebGL.RenderNodes.SubmitterTile#run
* @since 3.90.0
* @param {Phaser.Renderer.WebGL.DrawingContext} drawingContext - The current drawing context.
* @param {Phaser.GameObjects.GameObject} gameObject - The GameObject being rendered.
* @param {Phaser.GameObjects.Components.TransformMatrix} parentMatrix - The parent matrix of the GameObject.
* @param {object} [element] - The specific element within the game object. This is used for objects that consist of multiple quads.
* @param {Phaser.Renderer.WebGL.RenderNodes.TexturerTileSprite|Omit<Phaser.Renderer.WebGL.RenderNodes.TexturerTileSprite, 'run'>} texturerNode - The texturer node used to texture the GameObject. You may pass a texturer node or an object containing equivalent data without a `run` method.
* @param {Phaser.Renderer.WebGL.RenderNodes.TransformerTileSprite|{ quad: Float32Array }} transformerNode - The transformer node used to transform the GameObject. You may pass a transformer node or an object with a `quad` property.
* @param {Phaser.Renderer.WebGL.RenderNodes.RenderNode|Omit<Phaser.Renderer.WebGL.RenderNodes.RenderNode, 'run'>} [tinterNode] - The tinter node used to tint the GameObject. You may pass a tinter node or an object containing equivalent data without a `run` method. If omitted, no tinting will be used.
* @param {Phaser.Renderer.WebGL.Wrappers.WebGLTextureWrapper} [normalMap] - The normal map texture to use for lighting. If omitted, the normal map texture of the GameObject will be used, or the default normal map texture of the renderer.
* @param {number} [normalMapRotation] - The rotation of the normal map texture. If omitted, the rotation of the GameObject will be used.
*/
run: function (
drawingContext,
gameObject,
parentMatrix,
element,
texturerNode,
transformerNode,
tinterNode,
normalMap,
normalMapRotation
)
{
this.onRunBegin(drawingContext);
var cameraAlpha = drawingContext.camera.alpha;
var tintFill, tintTopLeft, tintBottomLeft, tintTopRight, tintBottomRight;
if (texturerNode.run)
{
texturerNode.run(drawingContext, gameObject, element);
}
if (transformerNode.run)
{
transformerNode.run(drawingContext, gameObject, parentMatrix, element, texturerNode);
}
if (tinterNode)
{
if (tinterNode.run)
{
tinterNode.run(drawingContext, gameObject, element);
}
tintFill = tinterNode.tintFill;
tintTopLeft = tinterNode.tintTopLeft;
tintBottomLeft = tinterNode.tintBottomLeft;
tintTopRight = tinterNode.tintTopRight;
tintBottomRight = tinterNode.tintBottomRight;
}
else
{
tintFill = gameObject.tintFill;
var tint = getTint(0xffffffff, cameraAlpha);
tintTopLeft = tint;
tintBottomLeft = tint;
tintTopRight = tint;
tintBottomRight = tint;
}
var frame = texturerNode.frame;
var quad = transformerNode.quad;
var uvSource = texturerNode.uvSource;
var u0 = uvSource.u0;
var v0 = uvSource.v0;
var u1 = uvSource.u1;
var v1 = uvSource.v1;
this.setRenderOptions(gameObject, normalMap, normalMapRotation);
(
gameObject.customRenderNodes[this.batchHandler] ||
gameObject.defaultRenderNodes[this.batchHandler]
).batch(
drawingContext,
// Use `frame.source.glTexture` instead of `frame.glTexture`
// to avoid unnecessary getter function calls.
frame.source.glTexture,
// Transformed quad in order TL, BL, TR, BR:
quad[0], quad[1],
quad[2], quad[3],
quad[6], quad[7],
quad[4], quad[5],
// Texture coordinates in X, Y, Width, Height:
u0, v0, u1 - u0, v1 - v0,
// Frame coordinates in order TL, BL, TR, BR:
u0, v0,
u0, v1,
u1, v0,
u1, v1,
tintFill,
// Tint colors in order TL, BL, TR, BR:
tintTopLeft, tintBottomLeft, tintTopRight, tintBottomRight,
// Extra render options:
this._renderOptions
);
this.onRunEnd(drawingContext);
}
});
module.exports = SubmitterTile;

View file

@ -39,6 +39,8 @@ var SubmitterTileSprite = new Class({
initialize: function SubmitterTileSprite (manager, config)
{
SubmitterQuad.call(this, manager, config);
this._renderOptions.wrapFrame = true;
},
/**

View file

@ -0,0 +1,583 @@
/**
* @author Benjamin D. Richards <benjamindrichards@gmail.com>
* @copyright 2013-2024 Phaser Studio Inc.
* @license None
*/
var TransformMatrix = require('../../../../gameobjects/components/TransformMatrix');
var Vector2 = require('../../../../math/Vector2');
var Class = require('../../../../utils/Class');
var Merge = require('../../../../utils/object/Merge');
var ProgramManager = require('../../ProgramManager');
var MakeAnimLength = require('../../shaders/configs/MakeAnimLength');
var MakeApplyLighting = require('../../shaders/configs/MakeApplyLighting');
var MakeDefineLights = require('../../shaders/configs/MakeDefineLights');
var MakeSampleNormal = require('../../shaders/configs/MakeSampleNormal');
var MakeSmoothPixelArt = require('../../shaders/configs/MakeSmoothPixelArt');
var ShaderSourceFS = require('../../shaders/TilemapGPULayer-frag');
var ShaderSourceVS = require('../../shaders/TilemapGPULayer-vert');
var WebGLVertexBufferLayoutWrapper = require('../../wrappers/WebGLVertexBufferLayoutWrapper');
var RenderNode = require('../RenderNode');
var Utils = require('../../Utils');
/**
* @classdesc
* The SubmitterTilemapGPULayer RenderNode handles rendering of
* TilemapGPULayer objects.
*
* It is a Stand Alone Render, meaning that it does not batch.
*
* @class SubmitterTilemapGPULayer
* @extends Phaser.Renderer.WebGL.RenderNodes.RenderNode
* @memberof Phaser.Renderer.WebGL.RenderNodes
* @constructor
* @since 3.90.0
* @param {Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager} manager - The manager that owns this RenderNode.
* @param {object} [config] - The configuration object for this handler.
* @param {string} [config.name='SubmitterTilemapGPULayer'] - The name of this RenderNode.
* @param {string} [config.vertexSource] - The vertex shader source.
* @param {string} [config.fragmentSource] - The fragment shader source.
*/
var SubmitterTilemapGPULayer = new Class({
Extends: RenderNode,
initialize: function SubmitterTilemapGPULayer (manager, config)
{
var renderer = manager.renderer;
var gl = renderer.gl;
var finalConfig = Merge(config || {}, this.defaultConfig);
var name = finalConfig.name;
this._completeLayout(finalConfig);
RenderNode.call(this, name, manager);
/**
* The completed configuration object for this RenderNode.
* This is defined by the default configuration and the user-defined configuration object.
*
* @name Phaser.Renderer.WebGL.RenderNodes.SubmitterTilemapGPULayer#config
* @type {object}
* @since 3.90.0
*/
this.config = finalConfig;
// Ensure that there is no VAO bound, because the following index buffer
// will modify any currently bound VAO.
renderer.glWrapper.updateVAO({ vao: null });
/**
* The index buffer defining vertex order.
*
* @name Phaser.Renderer.WebGL.RenderNodes.SubmitterTilemapGPULayer#indexBuffer
* @type {Phaser.Renderer.WebGL.Wrappers.WebGLBufferWrapper}
* @since 3.90.0
*/
this.indexBuffer = renderer.createIndexBuffer(
new Uint16Array([ 0, 1, 2, 3 ]),
gl.STATIC_DRAW
);
/**
* The vertex buffer layout for this RenderNode.
*
* This consists of 4 bytes, 0-3, forming corners of a quad instance.
*
* @name Phaser.Renderer.WebGL.RenderNodes.SubmitterTilemapGPULayer#vertexBufferLayout
* @type {Phaser.Renderer.WebGL.WebGLVertexBufferLayoutWrapper}
* @since 3.90.0
* @readonly
*/
this.vertexBufferLayout = new WebGLVertexBufferLayoutWrapper(
renderer,
finalConfig.vertexBufferLayout,
finalConfig.createOwnVertexBuffer ? null : renderer.genericVertexBuffer
);
/**
* The program manager used to create and manage shader programs.
* This contains shader variants.
*
* @name Phaser.Renderer.WebGL.RenderNodes.BatchHandler#programManager
* @type {Phaser.Renderer.WebGL.ProgramManager}
* @since 3.90.0
*/
this.programManager = new ProgramManager(
renderer,
this.indexBuffer,
[ this.vertexBufferLayout ]
);
// Fill in program configuration from config.
this.programManager.setBaseShader(
finalConfig.shaderName,
finalConfig.vertexSource,
finalConfig.fragmentSource
);
if (finalConfig.shaderAdditions)
{
for (var i = 0; i < finalConfig.shaderAdditions.length; i++)
{
var addition = finalConfig.shaderAdditions[i];
this.programManager.addAddition(addition);
}
}
if (finalConfig.shaderFeatures)
{
for (i = 0; i < finalConfig.shaderFeatures.length; i++)
{
this.programManager.addFeature(finalConfig.shaderFeatures[i]);
}
}
/**
* The matrix used internally to compute sprite transforms.
*
* @name Phaser.Renderer.WebGL.RenderNodes.SubmitterTilemapGPULayer#_spriteMatrix
* @type {Phaser.GameObjects.Components.TransformMatrix}
* @since 3.90.0
* @private
*/
this._spriteMatrix = new TransformMatrix();
/**
* The matrix used internally to compute the final transform.
*
* @name Phaser.Renderer.WebGL.RenderNodes.SubmitterTilemapGPULayer#_calcMatrix
* @type {Phaser.GameObjects.Components.TransformMatrix}
* @since 3.90.0
* @private
*/
this._calcMatrix = new TransformMatrix();
/**
* A vector used for temporary calculations.
*
* @name Phaser.Renderer.WebGL.RenderNodes.SubmitterTilemapGPULayer#_lightVector
* @type {Phaser.Math.Vector2}
* @since 3.90.0
* @private
*/
this._lightVector = new Vector2();
/**
* The matrix used to store the final quad data for rendering.
*
* @name Phaser.Renderer.WebGL.RenderNodes.SubmitterTilemapGPULayer#_quad
* @type {Float32Array}
* @since 3.90.0
* @private
*/
this._quad = new Float32Array(8);
},
/**
* Default configuration of this RenderNode.
*
* @name Phaser.Renderer.WebGL.RenderNodes.SubmitterTilemapGPULayer#defaultConfig
* @type {object}
* @since 3.90.0
* @readonly
* @property {string} name - The name of this RenderNode.
* @property {string} vertexSource - The vertex shader source.
* @property {string} fragmentSource - The fragment shader source.
*/
defaultConfig: {
name: 'SubmitterTilemapGPULayer',
shaderName: 'TilemapGPULayer',
vertexSource: ShaderSourceVS,
fragmentSource: ShaderSourceFS,
shaderAdditions: [
MakeSmoothPixelArt(true),
MakeSampleNormal(true),
MakeDefineLights(true),
MakeApplyLighting(true)
],
vertexBufferLayout: {
usage: 'DYNAMIC_DRAW',
count: 4,
layout: [
{
name: 'inPosition',
size: 2
},
{
name: 'inTexCoord',
size: 2
}
]
}
},
/**
* Fill out the configuration object with default values where needed.
*
* @method Phaser.Renderer.WebGL.RenderNodes.SubmitterTilemapGPULayer#_completeConfig
* @since 3.90.0
* @param {object} config - The configuration object to complete.
*/
_completeLayout: function (config)
{
// Set up vertex buffer layout.
var layoutSource = config.vertexBufferLayout;
config.vertexBufferLayout = {};
config.vertexBufferLayout.usage = layoutSource.usage;
config.vertexBufferLayout.layout = [];
var remove = config.vertexBufferLayoutRemove || [];
for (var i = 0; i < layoutSource.layout.length; i++)
{
var sourceAttr = layoutSource.layout[i];
if (remove.indexOf(sourceAttr.name) !== -1)
{
continue;
}
config.vertexBufferLayout.layout[i] = {
name: sourceAttr.name,
size: sourceAttr.size || 1,
type: sourceAttr.type || 'FLOAT',
normalized: sourceAttr.normalized || false
};
}
if (config.vertexBufferLayoutAdd)
{
var add = config.vertexBufferLayoutAdd || [];
for (i = 0; i < add.length; i++)
{
var addAttr = add[i];
config.vertexBufferLayout.layout.push({
name: addAttr.name,
size: addAttr.size || 1,
type: addAttr.type || 'FLOAT',
normalized: addAttr.normalized || false
});
}
}
},
/**
* Set up uniforms for rendering.
*
* @method Phaser.Renderer.WebGL.RenderNodes.SubmitterTilemapGPULayer#setupUniforms
* @since 3.90.0
* @param {Phaser.Renderer.WebGL.DrawingContext} drawingContext - The current drawing context.
* @param {Phaser.Tilemap.TilemapGPULayer} tilemapLayer - The TilemapGPULayer being rendered.
*/
setupUniforms: function (drawingContext, tilemapLayer)
{
var camera = drawingContext.camera;
var programManager = this.programManager;
// Standard uniforms.
programManager.setUniform(
'uRoundPixels',
camera.roundPixels
);
programManager.setUniform(
'uResolution',
[ drawingContext.width, drawingContext.height ]
);
drawingContext.renderer.setProjectionMatrix(
drawingContext.width,
drawingContext.height
);
programManager.setUniform(
'uProjectionMatrix',
drawingContext.renderer.projectionMatrix.val
);
// TilemapGPULayer uniforms.
var tileset = tilemapLayer.tileset;
var mainTexture = tileset.glTexture;
var layerTexture = tilemapLayer.layerDataTexture;
var animTexture = tileset.getAnimationDataTexture(drawingContext.renderer);
programManager.setUniform('uMainSampler', 0);
programManager.setUniform('uLayerSampler', 1);
programManager.setUniform('uAnimSampler', 2);
programManager.setUniform(
'uMainResolution',
[ mainTexture.width, mainTexture.height ]
);
programManager.setUniform(
'uLayerResolution',
[ layerTexture.width, layerTexture.height ]
);
programManager.setUniform(
'uAnimResolution',
[ animTexture.width, animTexture.height ]
);
programManager.setUniform(
'uTileColumns',
tileset.columns
);
programManager.setUniform(
'uTileWidthHeightMarginSpacing',
[
tileset.tileWidth,
tileset.tileHeight,
tileset.tileMargin,
tileset.tileSpacing
]
);
programManager.setUniform(
'uAlpha',
tilemapLayer.alpha * camera.alpha
);
programManager.setUniform(
'uTime',
tilemapLayer.timeElapsed
);
// Lighting uniforms.
Utils.updateLightingUniforms(
tilemapLayer.lighting,
this.manager.renderer,
drawingContext,
programManager,
3,
this._lightVector,
tilemapLayer.selfShadow.enabled,
tilemapLayer.selfShadow.diffuseFlatThreshold,
tilemapLayer.selfShadow.penumbra
);
},
/**
* Update render options for a TilemapGPULayer object.
* This may use a different shader program.
* This is called before rendering the object.
*
* @method Phaser.Renderer.WebGL.RenderNodes.SubmitterTilemapGPULayer#updateRenderOptions
* @since 3.90.0
* @param {Phaser.Tilemap.TilemapGPULayer} gameObject - The TilemapGPULayer being rendered.
*/
updateRenderOptions: function (gameObject)
{
var programManager = this.programManager;
var texture = gameObject.tileset.image;
// Set animation options.
var animAddition = programManager.getAdditionsByTag('MAXANIMS')[0];
if (animAddition)
{
programManager.removeAddition(animAddition.name);
}
if (gameObject.tileset.maxAnimationLength > 0)
{
programManager.addAddition(
MakeAnimLength(gameObject.tileset.maxAnimationLength)
);
}
// Set lighting options.
var lighting = gameObject.lighting;
var lightingAdditions = programManager.getAdditionsByTag('LIGHTING');
for (var i = 0; i < lightingAdditions.length; i++)
{
var addition = lightingAdditions[i];
addition.disable = !lighting;
}
if (lighting)
{
var defineLightsAddition = programManager.getAddition('DefineLights');
if (defineLightsAddition)
{
defineLightsAddition.additions.fragmentDefine =
'#define LIGHT_COUNT ' + this.manager.renderer.config.maxLights;
}
}
// Set self-shadow options.
var selfShadow = gameObject.selfShadow.enabled;
if (selfShadow === null)
{
selfShadow = gameObject.scene.game.config.selfShadow;
}
if (selfShadow)
{
programManager.addFeature('SELFSHADOW');
}
else
{
programManager.removeFeature('SELFSHADOW');
}
// Set smooth pixel art options.
var smoothPixelArt = texture.smoothPixelArt;
if (smoothPixelArt === null)
{
smoothPixelArt = gameObject.scene.game.config.smoothPixelArt;
}
var smoothPixelArtAddition = programManager.getAddition('SmoothPixelArt');
if (smoothPixelArtAddition)
{
smoothPixelArtAddition.disable = !smoothPixelArt;
}
// Set up border filtering options.
var borderFilter = texture.source[0].glTexture.magFilter === this.manager.renderer.gl.LINEAR;
if (borderFilter)
{
programManager.addFeature('BORDERFILTER');
}
else
{
programManager.removeFeature('BORDERFILTER');
}
},
/**
* Render a TilemapGPULayer object.
*
* @method Phaser.Renderer.WebGL.RenderNodes.SubmitterTilemapGPULayer#run
* @since 3.90.0
* @param {Phaser.Renderer.WebGL.DrawingContext} drawingContext - The current drawing context.
* @param {Phaser.Tilemap.TilemapGPULayer} tilemapLayer - The TilemapGPULayer being rendered.
*/
run: function (
drawingContext,
tilemapLayer
)
{
var manager = this.manager;
var renderer = manager.renderer;
manager.startStandAloneRender();
this.onRunBegin(drawingContext);
// Transform layer quad.
var camera = drawingContext.camera;
var spriteMatrix = this._spriteMatrix;
var calcMatrix = this._calcMatrix;
var quad = this._quad;
var x = tilemapLayer.x;
var y = tilemapLayer.y;
var width = tilemapLayer.width;
var height = tilemapLayer.height;
spriteMatrix.applyITRS(x, y, 0, tilemapLayer.scaleX, tilemapLayer.scaleY);
spriteMatrix.e -= camera.scrollX * tilemapLayer.scrollFactorX;
spriteMatrix.f -= camera.scrollY * tilemapLayer.scrollFactorY;
// Multiply by the Sprite matrix, store result in calcMatrix
camera.matrix.multiply(spriteMatrix, calcMatrix);
// Compute output quad.
calcMatrix.setQuad(
x,
y,
x + width,
y + height,
false,
quad
);
// Populate vertex buffer.
var stride = this.vertexBufferLayout.layout.stride;
var vertexBuffer = this.vertexBufferLayout.buffer;
var vertexF32 = vertexBuffer.viewF32;
var offset32 = 0;
// Bottom Left.
vertexF32[offset32++] = quad[2];
vertexF32[offset32++] = quad[3];
vertexF32[offset32++] = 0;
vertexF32[offset32++] = 1;
// Top Left.
vertexF32[offset32++] = quad[0];
vertexF32[offset32++] = quad[1];
vertexF32[offset32++] = 0;
vertexF32[offset32++] = 0;
// Bottom Right.
vertexF32[offset32++] = quad[4];
vertexF32[offset32++] = quad[5];
vertexF32[offset32++] = 1;
vertexF32[offset32++] = 1;
// Top Right.
vertexF32[offset32++] = quad[6];
vertexF32[offset32++] = quad[7];
vertexF32[offset32++] = 1;
vertexF32[offset32++] = 0;
// console.log('Update vertex buffer', stride, this.vertexBufferLayout, vertexBuffer);
// Update vertex buffer.
// Because we are probably using a generic vertex buffer
// which is larger than the current batch, we need to update
// the buffer with the correct size.
vertexBuffer.update(stride * 4);
// Assemble textures.
var tileset = tilemapLayer.tileset;
var mainGlTexture = tileset.glTexture;
var animated = tileset.getAnimationDataIndexMap(renderer).size > 0;
var textures = [
mainGlTexture,
tilemapLayer.layerDataTexture
];
if (animated)
{
textures[2] = tileset.getAnimationDataTexture(renderer);
}
if (tilemapLayer.lighting)
{
var texture = tileset.image;
var normalMap = texture.dataSource[0];
if (!normalMap)
{
normalMap = this.manager.renderer.normalTexture;
}
else
{
normalMap = normalMap.glTexture;
}
textures[3] = normalMap;
}
this.updateRenderOptions(tilemapLayer);
var programManager = this.programManager;
var programSuite = programManager.getCurrentProgramSuite();
var program = programSuite.program;
var vao = programSuite.vao;
this.setupUniforms(drawingContext, tilemapLayer);
programManager.applyUniforms(program);
// Render layer.
renderer.drawElements(
drawingContext,
textures,
program,
vao,
4,
0
);
this.onRunEnd(drawingContext);
}
});
module.exports = SubmitterTilemapGPULayer;

View file

@ -0,0 +1,16 @@
module.exports = [
'#if TEXTURE_COUNT == 1',
'texCoord = getBlockyTexCoord(texCoord, uMainResolution);',
'#else',
'vec2 texRes;',
'for (int i = 0; i < TEXTURE_COUNT; i++)',
'{',
' if (outTexDatum == float(i))',
' {',
' texRes = uMainResolution[i];',
' break;',
' }',
'}',
'texCoord = getBlockyTexCoord(texCoord, texRes);',
'#endif',
].join('\n');

View file

@ -1,58 +1,7 @@
module.exports = [
'struct Light',
'vec4 applyLighting (vec4 fragColor, vec3 normal)',
'{',
' vec3 position;',
' vec3 color;',
' float intensity;',
' float radius;',
'};',
'const int kMaxLights = LIGHT_COUNT;',
'uniform vec4 uCamera; /* x, y, rotation, zoom */',
'uniform sampler2D uNormSampler;',
'uniform vec3 uAmbientLightColor;',
'uniform Light uLights[kMaxLights];',
'uniform int uLightCount;',
'#ifdef FEATURE_SELFSHADOW',
'uniform float uDiffuseFlatThreshold;',
'uniform float uPenumbra;',
'#endif',
'vec4 applyLighting (vec4 fragColor)',
'{',
' vec3 finalColor = vec3(0.0);',
' #ifdef FEATURE_FLAT_LIGHTING',
' vec3 normal = vec3(0.0, 0.0, 1.0);',
' #else',
' vec2 texCoord = outTexCoord;',
' vec3 normalMap = texture2D(uNormSampler, texCoord).rgb;',
' vec3 normal = normalize(outInverseRotationMatrix * vec3(normalMap * 2.0 - 1.0));',
' #endif',
' vec2 res = vec2(min(uResolution.x, uResolution.y)) * uCamera.w;',
' #ifdef FEATURE_SELFSHADOW',
' vec3 unpremultipliedColor = fragColor.rgb / fragColor.a;',
' float occlusionThreshold = 1.0 - ((unpremultipliedColor.r + unpremultipliedColor.g + unpremultipliedColor.b) / uDiffuseFlatThreshold);',
' #endif',
' for (int index = 0; index < kMaxLights; ++index)',
' {',
' if (index < uLightCount)',
' {',
' Light light = uLights[index];',
' vec3 lightDir = vec3((light.position.xy / res) - (gl_FragCoord.xy / res), light.position.z / res.x);',
' vec3 lightNormal = normalize(lightDir);',
' float distToSurf = length(lightDir) * uCamera.w;',
' float diffuseFactor = max(dot(normal, lightNormal), 0.0);',
' float radius = (light.radius / res.x * uCamera.w) * uCamera.w;',
' float attenuation = clamp(1.0 - distToSurf * distToSurf / (radius * radius), 0.0, 1.0);',
' #ifdef FEATURE_SELFSHADOW',
' float occluded = smoothstep(0.0, 1.0, (diffuseFactor - occlusionThreshold) / uPenumbra);',
' vec3 diffuse = light.color * diffuseFactor * occluded;',
' #else',
' vec3 diffuse = light.color * diffuseFactor;',
' #endif',
' finalColor += (attenuation * diffuse) * light.intensity;',
' }',
' }',
' vec4 colorOutput = vec4(uAmbientLightColor + finalColor, 1.0);',
' fragColor *= vec4(colorOutput.rgb * colorOutput.a, colorOutput.a);',
' return fragColor;',
' vec4 lighting = getLighting(fragColor, normal);',
' return fragColor * vec4(lighting.rgb * lighting.a, lighting.a);',
'}',
].join('\n');

View file

@ -0,0 +1,13 @@
module.exports = [
'vec2 v2len (vec2 a, vec2 b)',
'{',
' return sqrt(a*a+b*b);',
'}',
'vec2 getBlockyTexCoord (vec2 texCoord, vec2 texRes) {',
' texCoord *= texRes;',
' vec2 seam = floor(texCoord + 0.5);',
' texCoord = (texCoord - seam) / v2len(dFdx(texCoord), dFdy(texCoord)) + seam;',
' texCoord = clamp(texCoord, seam-.5, seam+.5);',
' return texCoord / texRes;',
'}',
].join('\n');

View file

@ -0,0 +1,50 @@
module.exports = [
'struct Light',
'{',
' vec3 position;',
' vec3 color;',
' float intensity;',
' float radius;',
'};',
'const int kMaxLights = LIGHT_COUNT;',
'uniform vec4 uCamera; /* x, y, rotation, zoom */',
'uniform sampler2D uNormSampler;',
'uniform vec3 uAmbientLightColor;',
'uniform Light uLights[kMaxLights];',
'uniform int uLightCount;',
'#ifdef FEATURE_SELFSHADOW',
'uniform float uDiffuseFlatThreshold;',
'uniform float uPenumbra;',
'#endif',
'vec4 getLighting (vec4 fragColor, vec3 normal)',
'{',
' vec3 finalColor = vec3(0.0);',
' vec2 res = vec2(min(uResolution.x, uResolution.y)) * uCamera.w;',
' #ifdef FEATURE_SELFSHADOW',
' vec3 unpremultipliedColor = fragColor.rgb / fragColor.a;',
' float occlusionThreshold = 1.0 - ((unpremultipliedColor.r + unpremultipliedColor.g + unpremultipliedColor.b) / uDiffuseFlatThreshold);',
' #endif',
' for (int index = 0; index < kMaxLights; ++index)',
' {',
' if (index < uLightCount)',
' {',
' Light light = uLights[index];',
' vec3 lightDir = vec3((light.position.xy / res) - (gl_FragCoord.xy / res), light.position.z / res.x);',
' vec3 lightNormal = normalize(lightDir);',
' float distToSurf = length(lightDir) * uCamera.w;',
' float diffuseFactor = max(dot(normal, lightNormal), 0.0);',
' float radius = (light.radius / res.x * uCamera.w) * uCamera.w;',
' float attenuation = clamp(1.0 - distToSurf * distToSurf / (radius * radius), 0.0, 1.0);',
' #ifdef FEATURE_SELFSHADOW',
' float occluded = smoothstep(0.0, 1.0, (diffuseFactor - occlusionThreshold) / uPenumbra);',
' vec3 diffuse = light.color * diffuseFactor * occluded;',
' #else',
' vec3 diffuse = light.color * diffuseFactor;',
' #endif',
' finalColor += (attenuation * diffuse) * light.intensity;',
' }',
' }',
' vec4 colorOutput = vec4(uAmbientLightColor + finalColor, 1.0);',
' return colorOutput;',
'}'
].join('\n');

View file

@ -0,0 +1,10 @@
module.exports = [
'vec2 clampTexCoordWithinFrame (vec2 texCoord)',
'{',
' vec2 texRes = getTexRes();',
' vec4 frameTexel = outFrame * texRes.xyxy;',
' vec2 frameMin = frameTexel.xy + vec2(0.5, 0.5);',
' vec2 frameMax = frameTexel.xy + frameTexel.zw - vec2(0.5, 0.5);',
' return clamp(texCoord, frameMin / texRes, frameMax / texRes);',
'}',
].join('\n');

View file

@ -1,5 +1,6 @@
module.exports = [
'#pragma phaserTemplate(shaderName)',
'#pragma phaserTemplate(extensions)',
'#pragma phaserTemplate(features)',
'#ifdef GL_FRAGMENT_PRECISION_HIGH',
'precision highp float;',

View file

@ -1,5 +1,6 @@
module.exports = [
'#pragma phaserTemplate(shaderName)',
'#pragma phaserTemplate(extensions)',
'#pragma phaserTemplate(features)',
'precision mediump float;',
'#pragma phaserTemplate(vertexDefine)',

View file

@ -0,0 +1,7 @@
module.exports = [
'vec3 getNormalFromMap (vec2 texCoord)',
'{',
' vec3 normalMap = texture2D(uNormSampler, texCoord).rgb;',
' return normalize(outInverseRotationMatrix * vec3(normalMap * 2.0 - 1.0));',
'}',
].join('\n');

View file

@ -0,0 +1,22 @@
module.exports = [
'uniform vec2 uMainResolution[TEXTURE_COUNT];',
'vec2 getTexRes ()',
'{',
' #if TEXTURE_COUNT == 1',
' float texId = 0.0;',
' #else',
' float texId = outTexDatum;',
' #endif',
' #pragma phaserTemplate(texIdProcess)',
' vec2 texRes = vec2(0.0);',
' for (int i = 0; i < TEXTURE_COUNT; i++)',
' {',
' if (texId == float(i))',
' {',
' texRes = uMainResolution[i];',
' break;',
' }',
' }',
' return texRes;',
'}',
].join('\n');

View file

@ -0,0 +1,22 @@
module.exports = [
'uniform sampler2D uMainSampler[TEXTURE_COUNT];',
'vec4 getTexture (vec2 texCoord)',
'{',
' #if TEXTURE_COUNT == 1',
' float texId = 0.0;',
' #else',
' float texId = outTexDatum;',
' #endif',
' #pragma phaserTemplate(texIdProcess)',
' vec4 texture = vec4(0.0);',
' for (int i = 0; i < TEXTURE_COUNT; i++)',
' {',
' if (texId == float(i))',
' {',
' texture = texture2D(uMainSampler[i], texCoord);',
' break;',
' }',
' }',
' return texture;',
'}'
].join('\n');

View file

@ -1,13 +0,0 @@
module.exports = [
'uniform sampler2D uMainSampler[TEXTURE_COUNT];',
'vec4 getTexture ()',
'{',
' float texId = outTexDatum;',
' #pragma phaserTemplate(texIdProcess)',
' vec2 texCoord = outTexCoord;',
' #pragma phaserTemplate(texCoordProcess)',
' vec4 texture = vec4(0.0);',
' #pragma phaserTemplate(texSamplerProcess)',
' return texture;',
'}',
].join('\n');

View file

@ -1,9 +0,0 @@
module.exports = [
'uniform sampler2D uMainSampler;',
'vec4 getTexture ()',
'{',
' vec2 texCoord = outTexCoord;',
' #pragma phaserTemplate(texCoordProcess)',
' return texture2D(uMainSampler, texCoord);',
'}',
].join('\n');

View file

@ -1,5 +1,6 @@
module.exports = [
'#pragma phaserTemplate(shaderName)',
'#pragma phaserTemplate(extensions)',
'#pragma phaserTemplate(features)',
'#ifdef GL_FRAGMENT_PRECISION_HIGH',
'precision highp float;',

View file

@ -1,5 +1,6 @@
module.exports = [
'#pragma phaserTemplate(shaderName)',
'#pragma phaserTemplate(extensions)',
'#pragma phaserTemplate(features)',
'#ifdef GL_FRAGMENT_PRECISION_HIGH',
'precision highp float;',

View file

@ -0,0 +1,10 @@
module.exports = [
'float inverseRotation = -rotation - uCamera.z;',
'float irSine = sin(inverseRotation);',
'float irCosine = cos(inverseRotation);',
'outInverseRotationMatrix = mat3(',
' irCosine, irSine, 0.0,',
' -irSine, irCosine, 0.0,',
' 0.0, 0.0, 1.0',
');',
].join('\n');

View file

@ -1,5 +1,6 @@
module.exports = [
'#pragma phaserTemplate(shaderName)',
'#pragma phaserTemplate(extensions)',
'#pragma phaserTemplate(features)',
'precision mediump float;',
'#pragma phaserTemplate(fragmentDefine)',

View file

@ -1,5 +1,6 @@
module.exports = [
'#pragma phaserTemplate(shaderName)',
'#pragma phaserTemplate(extensions)',
'#pragma phaserTemplate(features)',
'precision mediump float;',
'#pragma phaserTemplate(vertexDefine)',

View file

@ -0,0 +1,233 @@
module.exports = [
'#version 100',
'#pragma phaserTemplate(shaderName)',
'#pragma phaserTemplate(extensions)',
'#pragma phaserTemplate(features)',
'#ifdef GL_FRAGMENT_PRECISION_HIGH',
'precision highp float;',
'#else',
'precision mediump float;',
'#endif',
'/* Redefine MAX_ANIM_FRAMES to support animations with different frame numbers. */',
'#define MAX_ANIM_FRAMES 0',
'#pragma phaserTemplate(fragmentDefine)',
'uniform vec2 uResolution;',
'uniform int roundPixels;',
'uniform sampler2D uMainSampler;',
'uniform sampler2D uLayerSampler;',
'uniform vec2 uMainResolution;',
'uniform vec2 uLayerResolution;',
'uniform float uTileColumns;',
'uniform vec4 uTileWidthHeightMarginSpacing;',
'uniform float uAlpha;',
'uniform float uTime;',
'#if MAX_ANIM_FRAMES > 0',
'uniform sampler2D uAnimSampler;',
'uniform vec2 uAnimResolution;',
'#endif',
'varying vec2 outTexCoord;',
'varying vec2 outTileStride;',
'#pragma phaserTemplate(outVariables)',
'vec2 getTexRes ()',
'{',
' return uMainResolution;',
'}',
'float floatTexel (vec4 texel)',
'{',
' return texel.r * 255.0 + (texel.g * 255.0 * 256.0) + (texel.b * 255.0 * 256.0 * 256.0) + (texel.a * 255.0 * 256.0 * 256.0 * 256.0);',
'}',
'struct Tile',
'{',
' float index;',
' vec2 uv; // In texels.',
' #if MAX_ANIM_FRAMES > 0',
' bool animated;',
' #endif',
' bool empty;',
'};',
'Tile getLayerData (vec2 coord)',
'{',
' vec2 texelCoord = coord * uLayerResolution;',
' vec2 tile = floor(texelCoord);',
' vec2 uv = fract(texelCoord);',
' vec4 texel = texture2D(uLayerSampler, (tile + 0.5) / uLayerResolution) * 255.0;',
' float flags = texel.a;',
' /* Check for empty tile flag in bit 28. */',
' if (flags == 16.0)',
' {',
' return Tile(',
' 0.0,',
' vec2(0.0),',
' #if MAX_ANIM_FRAMES > 0',
' false,',
' #endif',
' true',
' );',
' }',
' /* Bit 31 is flipX. */',
' bool flipX = flags > 127.0;',
' /* Bit 30 is flipY. */',
' bool flipY = mod(flags, 128.0) > 63.0;',
' #if MAX_ANIM_FRAMES > 0',
' /* Bit 29 is animation. */',
' bool animated = mod(flags, 64.0) > 31.0;',
' #endif',
' if (flipX)',
' {',
' uv.x = 1.0 - uv.x;',
' }',
' if (flipY)',
' {',
' uv.y = 1.0 - uv.y;',
' }',
' float index = texel.r + (texel.g * 256.0) + (texel.b * 256.0 * 256.0);',
' return Tile(',
' index,',
' uv * uTileWidthHeightMarginSpacing.xy,',
' #if MAX_ANIM_FRAMES > 0',
' animated,',
' #endif',
' false',
' );',
'}',
'vec2 getFrameCorner (float index)',
'{',
' float x = mod(index, uTileColumns);',
' float y = floor(index / uTileColumns);',
' vec2 xy = vec2(x, y);',
' return xy * outTileStride + uTileWidthHeightMarginSpacing.zz;',
'}',
'#if MAX_ANIM_FRAMES > 0',
'float animationIndex (float index)',
'{',
' float animTextureWidth = uAnimResolution.x;',
' vec2 index2D = vec2(mod(index, animTextureWidth), floor(index / animTextureWidth));',
' vec4 animDurationTexel = texture2D(uAnimSampler, (index2D + 0.5) / uAnimResolution);',
' index2D = vec2(mod(index + 1.0, animTextureWidth), floor((index + 1.0) / animTextureWidth));',
' vec4 animIndexTexel = texture2D(uAnimSampler, (index2D + 0.5) / uAnimResolution);',
' float animDuration = floatTexel(animDurationTexel);',
' float animIndex = floatTexel(animIndexTexel);',
' float animTime = mod(uTime, animDuration);',
' float animTimeAccum = 0.0;',
' for (int i = 0; i < MAX_ANIM_FRAMES; i++)',
' {',
' index2D = vec2(mod(animIndex, animTextureWidth), floor(animIndex / animTextureWidth));',
' animDurationTexel = texture2D(uAnimSampler, (index2D + 0.5) / uAnimResolution);',
' float frameDuration = floatTexel(animDurationTexel);',
' animTimeAccum += frameDuration;',
' if (animTime <= animTimeAccum)',
' {',
' break;',
' }',
' animIndex += 2.0;',
' }',
' animIndex += 1.0;',
' index2D = vec2(mod(animIndex, animTextureWidth), floor(animIndex / animTextureWidth));',
' animIndexTexel = texture2D(uAnimSampler, (index2D + 0.5) / uAnimResolution);',
' float animFrameIndex = floatTexel(animIndexTexel);',
' return animFrameIndex;',
'}',
'#endif',
'vec2 getTileTexelCoord (Tile tile)',
'{',
' if (tile.empty)',
' {',
' return vec2(0.0);',
' }',
' float index = tile.index;',
' #if MAX_ANIM_FRAMES > 0',
' if (tile.animated)',
' {',
' index = animationIndex(index);',
' }',
' #endif',
' vec2 frameCorner = getFrameCorner(index);',
' return frameCorner + tile.uv;',
'}',
'#pragma phaserTemplate(fragmentHeader)',
'struct Samples {',
' vec4 color;',
' #pragma phaserTemplate(defineSamples)',
'};',
'Samples getColorSamples (vec2 texCoord)',
'{',
' Samples samples;',
' samples.color = texture2D(uMainSampler, texCoord);',
' #pragma phaserTemplate(getSamples)',
' return samples;',
'}',
'Samples mixSamples (Samples samples1, Samples samples2, float alpha)',
'{',
' Samples samples;',
' samples.color = mix(samples1.color, samples2.color, alpha);',
' #pragma phaserTemplate(mixSamples)',
' return samples;',
'}',
'Samples getFinalSamples (Tile tile, vec2 layerTexCoord)',
'{',
' vec2 texelCoord = getTileTexelCoord(tile);',
' vec2 texCoord = texelCoord / uMainResolution;',
' #pragma phaserTemplate(texCoord)',
' #ifndef FEATURE_BORDERFILTER',
' return getColorSamples(texCoord);',
' #else',
' #pragma phaserTemplate(finalSamples)',
' vec2 wh = uTileWidthHeightMarginSpacing.xy;',
' vec2 frameCorner = getFrameCorner(tile.index);',
' vec2 frameCornerOpposite = frameCorner + wh;',
' vec2 texCoordClamped = clamp(texCoord, (frameCorner + 0.5) / uMainResolution, (frameCornerOpposite - 0.5) / uMainResolution);',
' vec2 dTexelCoord = (texCoord - texCoordClamped) * uMainResolution;',
' vec2 offsets = sign(dTexelCoord);',
' Samples samples0 = getColorSamples(texCoordClamped);',
' if (offsets.x == 0.0)',
' {',
' if (offsets.y == 0.0)',
' {',
' return samples0;',
' }',
' Tile tileY = getLayerData(layerTexCoord + (offsets - dTexelCoord) / wh / uLayerResolution);',
' vec2 texelCoordY = getTileTexelCoord(tileY);',
' vec2 texCoordY = texelCoordY / uMainResolution;',
' Samples samplesY = getColorSamples(texCoordY);',
' return mixSamples(samples0, samplesY, abs(dTexelCoord.y));',
' }',
' else',
' {',
' if (offsets.y == 0.0)',
' {',
' Tile tileX = getLayerData(layerTexCoord + (offsets - dTexelCoord) / wh / uLayerResolution);',
' vec2 texelCoordX = getTileTexelCoord(tileX);',
' vec2 texCoordX = texelCoordX / uMainResolution;',
' Samples samplesX = getColorSamples(texCoordX);',
' return mixSamples(samples0, samplesX, abs(dTexelCoord.x));',
' }',
' Tile tileX = getLayerData(layerTexCoord + (offsets * vec2(1.0, 0.0) - dTexelCoord) / wh / uLayerResolution);',
' vec2 texelCoordX = getTileTexelCoord(tileX);',
' vec2 texCoordX = texelCoordX / uMainResolution;',
' Samples samplesX = getColorSamples(texCoordX);',
' Tile tileY = getLayerData(layerTexCoord + (offsets * vec2(0.0, 1.0) - dTexelCoord) / wh / uLayerResolution);',
' vec2 texelCoordY = getTileTexelCoord(tileY);',
' vec2 texCoordY = texelCoordY / uMainResolution;',
' Samples samplesY = getColorSamples(texCoordY);',
' Tile tileXY = getLayerData(layerTexCoord + (offsets - dTexelCoord) / wh / uLayerResolution);',
' vec2 texelCoordXY = getTileTexelCoord(tileXY);',
' vec2 texCoordXY = texelCoordXY / uMainResolution;',
' Samples samplesXY = getColorSamples(texCoordXY);',
' Samples samples1 = mixSamples(samples0, samplesX, abs(dTexelCoord.x));',
' Samples samples2 = mixSamples(samplesY, samplesXY, abs(dTexelCoord.x));',
' return mixSamples(samples1, samples2, abs(dTexelCoord.y));',
' }',
' #endif',
'}',
'void main ()',
'{',
' vec2 layerTexCoord = outTexCoord;',
' Tile tile = getLayerData(layerTexCoord);',
' Samples samples = getFinalSamples(tile, layerTexCoord);',
' vec4 fragColor = samples.color;',
' #pragma phaserTemplate(declareSamples)',
' #pragma phaserTemplate(fragmentProcess)',
' fragColor *= uAlpha;',
' gl_FragColor = fragColor;',
'}',
].join('\n');

View file

@ -0,0 +1,32 @@
module.exports = [
'#pragma phaserTemplate(shaderName)',
'#pragma phaserTemplate(extensions)',
'#pragma phaserTemplate(features)',
'#ifdef GL_FRAGMENT_PRECISION_HIGH',
'precision highp float;',
'#else',
'precision mediump float;',
'#endif',
'#pragma phaserTemplate(vertexDefine)',
'uniform mat4 uProjectionMatrix;',
'uniform int uRoundPixels;',
'uniform vec2 uResolution;',
'uniform vec4 uTileWidthHeightMarginSpacing;',
'attribute vec2 inPosition;',
'attribute vec2 inTexCoord;',
'varying vec2 outTexCoord;',
'varying vec2 outTileStride;',
'#pragma phaserTemplate(outVariables)',
'#pragma phaserTemplate(vertexHeader)',
'void main ()',
'{',
' gl_Position = uProjectionMatrix * vec4(inPosition, 1.0, 1.0);',
' if (uRoundPixels == 1)',
' {',
' gl_Position.xy = floor(((gl_Position.xy + 1.0) * 0.5 * uResolution) + 0.5) / uResolution * 2.0 - 1.0;',
' }',
' outTexCoord = inTexCoord;',
' outTileStride = uTileWidthHeightMarginSpacing.xy + uTileWidthHeightMarginSpacing.zz;',
' #pragma phaserTemplate(vertexProcess)',
'}',
].join('\n');

View file

@ -0,0 +1,19 @@
/**
* @author Benjamin D. Richards <benjamindrichards@gmail.com>
* @copyright 2013-2024 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var MakeAnimLength = function (maxAnims, disable)
{
return {
name: maxAnims + 'Anims',
additions: {
fragmentDefine: '#undef MAX_ANIM_FRAMES\n#define MAX_ANIM_FRAMES ' + maxAnims
},
tags: [ 'MAXANIMS' ],
disable: !!disable
};
};
module.exports = MakeAnimLength;

View file

@ -0,0 +1,30 @@
/**
* @author Benjamin D. Richards <benjamindrichards@gmail.com>
* @copyright 2013-2024 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var ApplyLighting = require('../ApplyLighting-glsl');
/**
* Return a ShaderAdditionConfig for applying lighting to a flat piece of geometry.
*
* @function Phaser.Renderer.WebGL.Shaders.MakeApplyFlatLighting
* @since 3.90.0
* @param {boolean} [disable=false] - Whether to disable the shader addition on creation.
* @returns {Phaser.Types.Renderer.WebGL.ShaderAdditionConfig} The shader addition configuration.
*/
var MakeApplyFlatLighting = function (disable)
{
return {
name: 'ApplyFlatLighting',
additions: {
fragmentHeader: ApplyLighting,
fragmentProcess: 'fragColor = applyLighting(fragColor, vec3(0.0, 0.0, 1.0));'
},
tags: ['LIGHTING'],
disable: !!disable
};
};
module.exports = MakeApplyFlatLighting;

View file

@ -6,20 +6,6 @@
var ApplyLighting = require('../ApplyLighting-glsl');
var inverseRotation = [
'',
'#ifndef FEATURE_FLAT_LIGHTING',
'float inverseRotation = -rotation - uCamera.z;',
'float irSine = sin(inverseRotation);',
'float irCosine = cos(inverseRotation);',
'outInverseRotationMatrix = mat3(',
' irCosine, irSine, 0.0,',
' -irSine, irCosine, 0.0,',
' 0.0, 0.0, 1.0',
');',
'#endif'
].join('\n ');
/**
* Return a ShaderAdditionConfig for applying lighting to a texture.
*
@ -33,14 +19,10 @@ var inverseRotation = [
var MakeApplyLighting = function (disable)
{
return {
name: 'LIGHTING',
name: 'ApplyLighting',
additions: {
vertexHeader: 'uniform vec4 uCamera;',
vertexProcess: inverseRotation,
outVariables: 'varying mat3 outInverseRotationMatrix;',
fragmentDefine: '#define LIGHT_COUNT 1',
fragmentHeader: ApplyLighting,
fragmentProcess: 'fragColor = applyLighting(fragColor);'
fragmentProcess: 'fragColor = applyLighting(fragColor, normal);'
},
tags: ['LIGHTING'],
disable: !!disable

View file

@ -17,7 +17,7 @@ var ApplyTint = require('../ApplyTint-glsl');
var MakeApplyTint = function (disable)
{
return {
name: 'TINT',
name: 'Tint',
additions: {
fragmentHeader: ApplyTint,
fragmentProcess: 'fragColor = applyTint(fragColor);'

View file

@ -0,0 +1,32 @@
/**
* @author Benjamin D. Richards <benjamindrichards@gmail.com>
* @copyright 2013-2024 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var DefineLights = require('../DefineLights-glsl');
/**
* Return a ShaderAdditionConfig for defining the lights and core lighting
* algorithm in the fragment shader.
*
* @function Phaser.Renderer.WebGL.Shaders.MakeDefineLights
* @since 3.90.0
*
* @param {boolean} [disable=false] - Whether to disable the shader addition on creation.
* @returns {Phaser.Types.Renderer.WebGL.ShaderAdditionConfig} The shader addition configuration.
*/
var MakeDefineLights = function (disable)
{
return {
name: 'DefineLights',
additions: {
fragmentDefine: '#define LIGHT_COUNT 1',
fragmentHeader: DefineLights
},
tags: [ 'LIGHTING' ],
disable: !!disable
};
};
module.exports = MakeDefineLights;

View file

@ -0,0 +1,19 @@
/**
* @author Benjamin D. Richards <benjamindrichards@gmail.com>
* @copyright 2013-2024 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var MakeDefineTexCount = function (maxTextures, disable)
{
return {
name: maxTextures + 'TexCount',
additions: {
fragmentDefine: '#define TEXTURE_COUNT ' + maxTextures
},
tags: [ 'TexCount' ],
disable: !!disable
};
};
module.exports = MakeDefineTexCount;

View file

@ -0,0 +1,28 @@
/**
* @author Benjamin D. Richards <benjamindrichards@gmail.com>
* @copyright 2013-2024 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
/**
* Return a ShaderAdditionConfig for defining a flat normal.
* This is used to light objects without a normal map.
*
* @function Phaser.Renderer.WebGL.Shaders.MakeFlatNormal
* @since 3.90.0
* @param {boolean} [disable=false] - Whether to disable the shader addition on creation.
* @returns {Phaser.Types.Renderer.WebGL.ShaderAdditionConfig} The shader addition configuration.
*/
var MakeFlatNormal = function (disable)
{
return {
name: 'FlatNormal',
additions: {
fragmentProcess: 'vec3 normal = vec3(0.0, 0.0, 1.0);'
},
tags: [ 'LIGHTING' ],
disable: !!disable
};
};
module.exports = MakeFlatNormal;

View file

@ -0,0 +1,31 @@
/**
* @author Benjamin D. Richards <benjamindrichards@gmail.com>
* @copyright 2013-2024 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var GetNormalFromMap = require('../GetNormalFromMap-glsl');
/**
* Return a ShaderAdditionConfig for creating an outInverseRotationMatrix
* in the vertex shader, which is used to apply lighting to a texture.
*
* @function Phaser.Renderer.WebGL.Shaders.MakeGetNormalFromMap
* @since 3.90.0
* @param {boolean} [disable=false] - Whether to disable the shader addition on creation.
* @returns {Phaser.Types.Renderer.WebGL.ShaderAdditionConfig} The shader addition configuration.
*/
var MakeGetNormalFromMap = function (disable)
{
return {
name: 'NormalMap',
additions: {
fragmentHeader: GetNormalFromMap,
fragmentProcess: 'vec3 normal = getNormalFromMap(texCoord);'
},
tags: [ 'LIGHTING' ],
disable: !!disable
};
};
module.exports = MakeGetNormalFromMap;

View file

@ -0,0 +1,29 @@
/**
* @author Benjamin D. Richards <benjamindrichards@gmail.com>
* @copyright 2013-2024 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
/**
* Return a ShaderAdditionConfig for getting the texture coordinates
* from the vertex shader via the `outTexCoord` variable.
*
* @function Phaser.Renderer.WebGL.Shaders.MakeGetTexCoordOut
* @since 3.90.0
*
* @param {boolean} [disable=false] - Whether to disable the shader addition on creation.
* @returns {Phaser.Types.Renderer.WebGL.ShaderAdditionConfig} The shader addition configuration.
*/
var MakeGetTexCoordOut = function (disable)
{
return {
name: 'TexCoordOut',
additions: {
fragmentProcess: 'vec2 texCoord = outTexCoord;\n#pragma phaserTemplate(texCoord)'
},
tags: [ 'TEXCOORD' ],
disable: !!disable
};
};
module.exports = MakeGetTexCoordOut;

View file

@ -0,0 +1,21 @@
/**
* @author Benjamin D. Richards <benjamindrichards@gmail.com>
* @copyright 2013-2024 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var GetTexRes = require('../GetTexRes-glsl');
var MakeGetTexRes = function (disable)
{
return {
name: 'GetTexRes',
additions: {
fragmentHeader: GetTexRes
},
tags: [ 'TEXRES' ],
disable: !!disable
};
};
module.exports = MakeGetTexRes;

View file

@ -4,67 +4,15 @@
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var GetTextureSingle = require('../GetTextureSingle-glsl');
var GetTextureMulti = require('../GetTextureMulti-glsl');
var GetTexture = require('../GetTexture-glsl');
/**
* Return a ShaderAdditionConfig for getting a texture from a sampler2D.
* This maker can return a single texture shader addition
* or a multi-texture shader addition, depending on the maxTextures parameter.
* The addition will change its name based on the number of textures supported,
* so it is tagged with 'TEXTURE' for quick identification at runtime.
*
* @function Phaser.Renderer.WebGL.Shaders.MakeGetTexture
* @since 3.90.0
* @param {number} maxTextures - The maximum number of textures to support.
* @param {boolean} [disable=false] - Whether to disable the shader addition on creation.
* @returns {Phaser.Types.Renderer.WebGL.ShaderAdditionConfig} The shader addition configuration.
*/
var MakeGetTexture = function (maxTextures, disable)
var MakeGetTexture = function (disable)
{
var fragmentProcess = 'vec4 fragColor = getTexture();';
if (maxTextures === 1)
{
return {
name: '1TEXTURE',
additions: {
fragmentHeader: GetTextureSingle,
fragmentProcess: fragmentProcess
},
tags: [ 'TEXTURE' ],
disable: !!disable
};
}
var src = '';
for (var i = 0; i < maxTextures; i++)
{
if (i > 0)
{
src += '\n\telse ';
}
if (i < maxTextures - 1)
{
src += 'if (texId < ' + i + '.5)';
}
src += '\n\t{';
src += '\n\t\ttexture = texture2D(uMainSampler[' + i + '], texCoord);';
src += '\n\t}';
}
return {
name: maxTextures + 'TEXTURES',
name: 'GetTexture',
additions: {
fragmentDefine: '#define TEXTURE_COUNT ' + maxTextures,
fragmentHeader: GetTextureMulti.replace(
'#pragma phaserTemplate(texSamplerProcess)',
src
),
fragmentProcess: fragmentProcess
fragmentHeader: GetTexture,
fragmentProcess: 'vec4 fragColor = getTexture(texCoord);'
},
tags: [ 'TEXTURE' ],
disable: !!disable

View file

@ -5,27 +5,24 @@
*/
/**
* Returns a ShaderAdditionConfig for wrapping a TileSprite texture.
* This makes the texture repeat within the bounds of the TileSprite frame -
* it's what makes a TileSprite work.
* Returns a ShaderAdditionConfig for providing the vertex shader with the `inFrame` attribute.
*
* @function Phaser.Renderer.WebGL.Shaders.MakeTileSpriteWrap
* @function Phaser.Renderer.WebGL.Shaders.MakeOutFrame
* @since 3.90.0
* @param {boolean} [disable=false] - Whether to disable the shader addition on creation.
* @returns {Phaser.Types.Renderer.WebGL.ShaderAdditionConfig} The shader addition configuration.
*/
var MakeTileSpriteWrap = function (disable)
var MakeOutFrame = function (disable)
{
return {
name: 'TileSpriteWrap',
name: 'OutFrame',
additions: {
vertexHeader: 'attribute vec4 inFrame;',
outVariables: 'varying vec4 outFrame;',
vertexProcess: 'outFrame = inFrame;',
texCoordProcess: '// Wrap texture coordinate into the UV space of the texture frame.\ntexCoord = mod(texCoord, 1.0) * outFrame.zw + outFrame.xy;'
outVariables: 'varying vec4 outFrame;',
},
disable: !!disable
};
};
module.exports = MakeTileSpriteWrap;
module.exports = MakeOutFrame;

View file

@ -0,0 +1,35 @@
/**
* @author Benjamin D. Richards <benjamindrichards@gmail.com>
* @copyright 2013-2024 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var OutInverseRotation = require('../OutInverseRotation-glsl');
/**
* Return a ShaderAdditionConfig for creating an outInverseRotationMatrix
* in the vertex shader, which is used to apply lighting to a texture.
*
* The `rotation` variable must be available in the vertex renderer.
*
* @function Phaser.Renderer.WebGL.Shaders.MakeOutInverseRotation
* @since 3.90.0
*
* @param {boolean} [disable=false] - Whether to disable the shader addition on creation.
* @returns {Phaser.Types.Renderer.WebGL.ShaderAdditionConfig} The shader addition configuration.
*/
var MakeOutInverseRotation = function (disable)
{
return {
name: 'OutInverseRotation',
additions: {
vertexHeader: 'uniform vec4 uCamera;',
vertexProcess: OutInverseRotation,
outVariables: 'varying mat3 outInverseRotationMatrix;'
},
tags: [ 'LIGHTING' ],
disable: !!disable
};
};
module.exports = MakeOutInverseRotation;

View file

@ -0,0 +1,31 @@
/**
* @author Benjamin D. Richards <benjamindrichards@gmail.com>
* @copyright 2013-2024 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
/**
* Return a ShaderAdditionConfig for sampling a normal map
* in the context of a TilemapGPULayer shader.
* This shader uses a `Samples` object to collate texture samples.
*
* @function Phaser.Renderer.WebGL.Shaders.MakeSampleNormal
* @since 3.90.0
* @param {boolean} [disable=false] - Whether to disable the shader addition on creation.
*/
var MakeSampleNormal = function (disable)
{
return {
name: 'SampleNormal',
additions: {
defineSamples: 'vec4 normal;',
getSamples: 'samples.normal = texture2D(uNormSampler, texCoord);',
mixSamples: 'samples.normal = mix(samples1.normal, samples2.normal, alpha);',
declareSamples: 'vec3 normal = normalize(samples.normal.rgb * 2.0 - 1.0);'
},
tags: ['LIGHTING'],
disable: !!disable
};
}
module.exports = MakeSampleNormal;

View file

@ -0,0 +1,22 @@
/**
* @author Benjamin D. Richards <benjamindrichards@gmail.com>
* @copyright 2013-2024 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var DefineBlockyTexCoord = require('../DefineBlockyTexCoord-glsl');
var MakeSmoothPixelArt = function (disable)
{
return {
name: 'SmoothPixelArt',
additions: {
extensions: '#extension GL_OES_standard_derivatives : enable',
fragmentHeader: DefineBlockyTexCoord,
texCoord: 'texCoord = getBlockyTexCoord(texCoord, getTexRes());'
},
disable: !!disable
};
};
module.exports = MakeSmoothPixelArt;

View file

@ -0,0 +1,31 @@
/**
* @author Benjamin D. Richards <benjamindrichards@gmail.com>
* @copyright 2013-2024 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var DefineTexCoordFrameClamp = require('../DefineTexCoordFrameClamp-glsl');
/**
* Returns a ShaderAdditionConfig for clamping coordinates inside a frame.
* This prevents bleeding across the edges of the frame.
* However, it creates a hard edge at the frame boundary.
*
* @function Phaser.Renderer.WebGL.Shaders.MakeTexCoordFrameClamp
* @since 3.90.0
* @param {boolean} [disable=false] - Whether to disable the shader addition on creation.
* @returns {Phaser.Types.Renderer.WebGL.ShaderAdditionConfig} The shader addition configuration.
*/
var MakeTexCoordFrameClamp = function (disable)
{
return {
name: 'TexCoordFrameClamp',
additions: {
fragmentHeader: DefineTexCoordFrameClamp,
fragmentProcess: 'texCoord = clampTexCoordWithinFrame(texCoord);'
},
disable: !!disable
};
};
module.exports = MakeTexCoordFrameClamp;

View file

@ -0,0 +1,28 @@
/**
* @author Benjamin D. Richards <benjamindrichards@gmail.com>
* @copyright 2013-2024 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
/**
* Returns a ShaderAdditionConfig for wrapping coordinates inside a frame.
* This makes the texture repeat within the bounds of the frame -
* it's what makes a TileSprite work.
*
* @function Phaser.Renderer.WebGL.Shaders.MakeTexCoordFrameWrap
* @since 3.90.0
* @param {boolean} [disable=false] - Whether to disable the shader addition on creation.
* @returns {Phaser.Types.Renderer.WebGL.ShaderAdditionConfig} The shader addition configuration.
*/
var MakeTexCoordFrameWrap = function (disable)
{
return {
name: 'TexCoordFrameWrap',
additions: {
fragmentProcess: '// Wrap texture coordinate into the UV space of the texture frame.\ntexCoord = mod(texCoord, 1.0) * outFrame.zw + outFrame.xy;'
},
disable: !!disable
};
};
module.exports = MakeTexCoordFrameWrap;

View file

@ -17,6 +17,9 @@ module.exports = {
BitmapMaskVert: require('./BitmapMask-vert.js'),
ColorMatrixFrag: require('./ColorMatrix-frag.js'),
CopyFrag: require('./Copy-frag.js'),
DefineBlockyTexCoord: require('./DefineBlockyTexCoord-glsl.js'),
DefineLights: require('./DefineLights-glsl.js'),
DefineTexCoordFrameClamp: require('./DefineTexCoordFrameClamp-glsl.js'),
FXBarrelFrag: require('./FXBarrel-frag.js'),
FXBloomFrag: require('./FXBloom-frag.js'),
FXBlurHighFrag: require('./FXBlurHigh-frag.js'),
@ -34,8 +37,9 @@ module.exports = {
FXWipeFrag: require('./FXWipe-frag.js'),
FlatFrag: require('./Flat-frag.js'),
FlatVert: require('./Flat-vert.js'),
GetTextureMulti: require('./GetTextureMulti-glsl.js'),
GetTextureSingle: require('./GetTextureSingle-glsl.js'),
GetNormalFromMap: require('./GetNormalFromMap-glsl.js'),
GetTexRes: require('./GetTexRes-glsl.js'),
GetTexture: require('./GetTexture-glsl.js'),
LightFrag: require('./Light-frag.js'),
LinearBlendFrag: require('./LinearBlend-frag.js'),
MeshFrag: require('./Mesh-frag.js'),
@ -44,13 +48,14 @@ module.exports = {
MobileVert: require('./Mobile-vert.js'),
MultiFrag: require('./Multi-frag.js'),
MultiVert: require('./Multi-vert.js'),
OutInverseRotation: require('./OutInverseRotation-glsl.js'),
PointLightFrag: require('./PointLight-frag.js'),
PointLightVert: require('./PointLight-vert.js'),
PostFXFrag: require('./PostFX-frag.js'),
QuadVert: require('./Quad-vert.js'),
SingleFrag: require('./Single-frag.js'),
SingleVert: require('./Single-vert.js'),
SpriteLayerFrag: require('./SpriteLayer-frag.js'),
SpriteLayerVert: require('./SpriteLayer-vert.js')
TilemapGPULayerFrag: require('./TilemapGPULayer-frag.js'),
TilemapGPULayerVert: require('./TilemapGPULayer-vert.js')
};

View file

@ -1,65 +1,5 @@
struct Light
vec4 applyLighting (vec4 fragColor, vec3 normal)
{
vec3 position;
vec3 color;
float intensity;
float radius;
};
// #define LIGHT_COUNT N
const int kMaxLights = LIGHT_COUNT;
uniform vec4 uCamera; /* x, y, rotation, zoom */
uniform sampler2D uNormSampler;
uniform vec3 uAmbientLightColor;
uniform Light uLights[kMaxLights];
uniform int uLightCount;
#ifdef FEATURE_SELFSHADOW
uniform float uDiffuseFlatThreshold;
uniform float uPenumbra;
#endif
vec4 applyLighting (vec4 fragColor)
{
vec3 finalColor = vec3(0.0);
#ifdef FEATURE_FLAT_LIGHTING
vec3 normal = vec3(0.0, 0.0, 1.0);
#else
vec2 texCoord = outTexCoord;
vec3 normalMap = texture2D(uNormSampler, texCoord).rgb;
vec3 normal = normalize(outInverseRotationMatrix * vec3(normalMap * 2.0 - 1.0));
#endif
vec2 res = vec2(min(uResolution.x, uResolution.y)) * uCamera.w;
#ifdef FEATURE_SELFSHADOW
vec3 unpremultipliedColor = fragColor.rgb / fragColor.a;
float occlusionThreshold = 1.0 - ((unpremultipliedColor.r + unpremultipliedColor.g + unpremultipliedColor.b) / uDiffuseFlatThreshold);
#endif
for (int index = 0; index < kMaxLights; ++index)
{
if (index < uLightCount)
{
Light light = uLights[index];
vec3 lightDir = vec3((light.position.xy / res) - (gl_FragCoord.xy / res), light.position.z / res.x);
vec3 lightNormal = normalize(lightDir);
float distToSurf = length(lightDir) * uCamera.w;
float diffuseFactor = max(dot(normal, lightNormal), 0.0);
float radius = (light.radius / res.x * uCamera.w) * uCamera.w;
float attenuation = clamp(1.0 - distToSurf * distToSurf / (radius * radius), 0.0, 1.0);
#ifdef FEATURE_SELFSHADOW
float occluded = smoothstep(0.0, 1.0, (diffuseFactor - occlusionThreshold) / uPenumbra);
vec3 diffuse = light.color * diffuseFactor * occluded;
#else
vec3 diffuse = light.color * diffuseFactor;
#endif
finalColor += (attenuation * diffuse) * light.intensity;
}
}
vec4 colorOutput = vec4(uAmbientLightColor + finalColor, 1.0);
fragColor *= vec4(colorOutput.rgb * colorOutput.a, colorOutput.a);
return fragColor;
vec4 lighting = getLighting(fragColor, normal);
return fragColor * vec4(lighting.rgb * lighting.a, lighting.a);
}

View file

@ -0,0 +1,12 @@
// Permutator\'s CC0 antialiased blocky sampling algorithm from https://www.shadertoy.com/view/ltfXWS
vec2 v2len (vec2 a, vec2 b)
{
return sqrt(a*a+b*b);
}
vec2 getBlockyTexCoord (vec2 texCoord, vec2 texRes) {
texCoord *= texRes;
vec2 seam = floor(texCoord + 0.5);
texCoord = (texCoord - seam) / v2len(dFdx(texCoord), dFdy(texCoord)) + seam;
texCoord = clamp(texCoord, seam-.5, seam+.5);
return texCoord / texRes;
}

View file

@ -0,0 +1,56 @@
struct Light
{
vec3 position;
vec3 color;
float intensity;
float radius;
};
// #define LIGHT_COUNT N
const int kMaxLights = LIGHT_COUNT;
uniform vec4 uCamera; /* x, y, rotation, zoom */
uniform sampler2D uNormSampler;
uniform vec3 uAmbientLightColor;
uniform Light uLights[kMaxLights];
uniform int uLightCount;
#ifdef FEATURE_SELFSHADOW
uniform float uDiffuseFlatThreshold;
uniform float uPenumbra;
#endif
vec4 getLighting (vec4 fragColor, vec3 normal)
{
vec3 finalColor = vec3(0.0);
vec2 res = vec2(min(uResolution.x, uResolution.y)) * uCamera.w;
#ifdef FEATURE_SELFSHADOW
vec3 unpremultipliedColor = fragColor.rgb / fragColor.a;
float occlusionThreshold = 1.0 - ((unpremultipliedColor.r + unpremultipliedColor.g + unpremultipliedColor.b) / uDiffuseFlatThreshold);
#endif
for (int index = 0; index < kMaxLights; ++index)
{
if (index < uLightCount)
{
Light light = uLights[index];
vec3 lightDir = vec3((light.position.xy / res) - (gl_FragCoord.xy / res), light.position.z / res.x);
vec3 lightNormal = normalize(lightDir);
float distToSurf = length(lightDir) * uCamera.w;
float diffuseFactor = max(dot(normal, lightNormal), 0.0);
float radius = (light.radius / res.x * uCamera.w) * uCamera.w;
float attenuation = clamp(1.0 - distToSurf * distToSurf / (radius * radius), 0.0, 1.0);
#ifdef FEATURE_SELFSHADOW
float occluded = smoothstep(0.0, 1.0, (diffuseFactor - occlusionThreshold) / uPenumbra);
vec3 diffuse = light.color * diffuseFactor * occluded;
#else
vec3 diffuse = light.color * diffuseFactor;
#endif
finalColor += (attenuation * diffuse) * light.intensity;
}
}
vec4 colorOutput = vec4(uAmbientLightColor + finalColor, 1.0);
return colorOutput;
}

View file

@ -0,0 +1,8 @@
vec2 clampTexCoordWithinFrame (vec2 texCoord)
{
vec2 texRes = getTexRes();
vec4 frameTexel = outFrame * texRes.xyxy;
vec2 frameMin = frameTexel.xy + vec2(0.5, 0.5);
vec2 frameMax = frameTexel.xy + frameTexel.zw - vec2(0.5, 0.5);
return clamp(texCoord, frameMin / texRes, frameMax / texRes);
}

View file

@ -1,5 +1,7 @@
#pragma phaserTemplate(shaderName)
#pragma phaserTemplate(extensions)
#pragma phaserTemplate(features)
#ifdef GL_FRAGMENT_PRECISION_HIGH

View file

@ -1,5 +1,7 @@
#pragma phaserTemplate(shaderName)
#pragma phaserTemplate(extensions)
#pragma phaserTemplate(features)
precision mediump float;

View file

@ -0,0 +1,5 @@
vec3 getNormalFromMap (vec2 texCoord)
{
vec3 normalMap = texture2D(uNormSampler, texCoord).rgb;
return normalize(outInverseRotationMatrix * vec3(normalMap * 2.0 - 1.0));
}

View file

@ -0,0 +1,24 @@
// Set elsewhere: `#define TEXTURE_COUNT 1`
uniform vec2 uMainResolution[TEXTURE_COUNT];
vec2 getTexRes ()
{
#if TEXTURE_COUNT == 1
float texId = 0.0;
#else
float texId = outTexDatum;
#endif
#pragma phaserTemplate(texIdProcess)
vec2 texRes = vec2(0.0);
for (int i = 0; i < TEXTURE_COUNT; i++)
{
if (texId == float(i))
{
texRes = uMainResolution[i];
break;
}
}
return texRes;
}

View file

@ -0,0 +1,24 @@
// Set elsewhere: `#define TEXTURE_COUNT 1`
uniform sampler2D uMainSampler[TEXTURE_COUNT];
vec4 getTexture (vec2 texCoord)
{
#if TEXTURE_COUNT == 1
float texId = 0.0;
#else
float texId = outTexDatum;
#endif
#pragma phaserTemplate(texIdProcess)
vec4 texture = vec4(0.0);
for (int i = 0; i < TEXTURE_COUNT; i++)
{
if (texId == float(i))
{
texture = texture2D(uMainSampler[i], texCoord);
break;
}
}
return texture;
}

View file

@ -1,15 +0,0 @@
uniform sampler2D uMainSampler[TEXTURE_COUNT];
vec4 getTexture ()
{
float texId = outTexDatum;
#pragma phaserTemplate(texIdProcess)
vec2 texCoord = outTexCoord;
#pragma phaserTemplate(texCoordProcess)
vec4 texture = vec4(0.0);
#pragma phaserTemplate(texSamplerProcess)
return texture;
}

View file

@ -1,9 +0,0 @@
uniform sampler2D uMainSampler;
vec4 getTexture ()
{
vec2 texCoord = outTexCoord;
#pragma phaserTemplate(texCoordProcess)
return texture2D(uMainSampler, texCoord);
}

View file

@ -1,5 +1,7 @@
#pragma phaserTemplate(shaderName)
#pragma phaserTemplate(extensions)
#pragma phaserTemplate(features)
#ifdef GL_FRAGMENT_PRECISION_HIGH

View file

@ -1,5 +1,7 @@
#pragma phaserTemplate(shaderName)
#pragma phaserTemplate(extensions)
#pragma phaserTemplate(features)
#ifdef GL_FRAGMENT_PRECISION_HIGH

View file

@ -0,0 +1,8 @@
float inverseRotation = -rotation - uCamera.z;
float irSine = sin(inverseRotation);
float irCosine = cos(inverseRotation);
outInverseRotationMatrix = mat3(
irCosine, irSine, 0.0,
-irSine, irCosine, 0.0,
0.0, 0.0, 1.0
);

View file

@ -1,5 +1,7 @@
#pragma phaserTemplate(shaderName)
#pragma phaserTemplate(extensions)
#pragma phaserTemplate(features)
precision mediump float;

View file

@ -1,5 +1,7 @@
#pragma phaserTemplate(shaderName)
#pragma phaserTemplate(extensions)
#pragma phaserTemplate(features)
precision mediump float;

View file

@ -0,0 +1,303 @@
#version 100
#pragma phaserTemplate(shaderName)
#pragma phaserTemplate(extensions)
#pragma phaserTemplate(features)
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
/* Redefine MAX_ANIM_FRAMES to support animations with different frame numbers. */
#define MAX_ANIM_FRAMES 0
#pragma phaserTemplate(fragmentDefine)
uniform vec2 uResolution;
uniform int roundPixels;
uniform sampler2D uMainSampler;
uniform sampler2D uLayerSampler;
uniform vec2 uMainResolution;
uniform vec2 uLayerResolution;
uniform float uTileColumns;
uniform vec4 uTileWidthHeightMarginSpacing;
uniform float uAlpha;
uniform float uTime;
#if MAX_ANIM_FRAMES > 0
uniform sampler2D uAnimSampler;
uniform vec2 uAnimResolution;
#endif
varying vec2 outTexCoord;
varying vec2 outTileStride;
#pragma phaserTemplate(outVariables)
// Utility to support smooth pixel art.
vec2 getTexRes ()
{
return uMainResolution;
}
// Convert a vec4 texel to a float.
float floatTexel (vec4 texel)
{
return texel.r * 255.0 + (texel.g * 255.0 * 256.0) + (texel.b * 255.0 * 256.0 * 256.0) + (texel.a * 255.0 * 256.0 * 256.0 * 256.0);
}
struct Tile
{
float index;
vec2 uv; // In texels.
#if MAX_ANIM_FRAMES > 0
bool animated;
#endif
bool empty;
};
Tile getLayerData (vec2 coord)
{
vec2 texelCoord = coord * uLayerResolution;
vec2 tile = floor(texelCoord);
vec2 uv = fract(texelCoord);
vec4 texel = texture2D(uLayerSampler, (tile + 0.5) / uLayerResolution) * 255.0;
float flags = texel.a;
/* Check for empty tile flag in bit 28. */
if (flags == 16.0)
{
return Tile(
0.0,
vec2(0.0),
#if MAX_ANIM_FRAMES > 0
false,
#endif
true
);
}
/* Bit 31 is flipX. */
bool flipX = flags > 127.0;
/* Bit 30 is flipY. */
bool flipY = mod(flags, 128.0) > 63.0;
#if MAX_ANIM_FRAMES > 0
/* Bit 29 is animation. */
bool animated = mod(flags, 64.0) > 31.0;
#endif
if (flipX)
{
uv.x = 1.0 - uv.x;
}
if (flipY)
{
uv.y = 1.0 - uv.y;
}
float index = texel.r + (texel.g * 256.0) + (texel.b * 256.0 * 256.0);
return Tile(
index,
uv * uTileWidthHeightMarginSpacing.xy,
#if MAX_ANIM_FRAMES > 0
animated,
#endif
false
);
}
// Returns the texel coordinates of the tile in the main texture.
vec2 getFrameCorner (float index)
{
float x = mod(index, uTileColumns);
float y = floor(index / uTileColumns);
vec2 xy = vec2(x, y);
return xy * outTileStride + uTileWidthHeightMarginSpacing.zz;
}
#if MAX_ANIM_FRAMES > 0
// Given an animation index, returns the current frame index.
float animationIndex (float index)
{
// Get initial animation data.
float animTextureWidth = uAnimResolution.x;
vec2 index2D = vec2(mod(index, animTextureWidth), floor(index / animTextureWidth));
vec4 animDurationTexel = texture2D(uAnimSampler, (index2D + 0.5) / uAnimResolution);
index2D = vec2(mod(index + 1.0, animTextureWidth), floor((index + 1.0) / animTextureWidth));
vec4 animIndexTexel = texture2D(uAnimSampler, (index2D + 0.5) / uAnimResolution);
float animDuration = floatTexel(animDurationTexel);
float animIndex = floatTexel(animIndexTexel);
// Seek the correct frame.
float animTime = mod(uTime, animDuration);
float animTimeAccum = 0.0;
for (int i = 0; i < MAX_ANIM_FRAMES; i++)
{
index2D = vec2(mod(animIndex, animTextureWidth), floor(animIndex / animTextureWidth));
animDurationTexel = texture2D(uAnimSampler, (index2D + 0.5) / uAnimResolution);
float frameDuration = floatTexel(animDurationTexel);
animTimeAccum += frameDuration;
if (animTime <= animTimeAccum)
{
// Found the frame.
break;
}
// Proceed to the next frame, in 2-texel strides.
animIndex += 2.0;
}
// The index is 1 texel over from the duration.
animIndex += 1.0;
// Derive the animation frame index.
index2D = vec2(mod(animIndex, animTextureWidth), floor(animIndex / animTextureWidth));
animIndexTexel = texture2D(uAnimSampler, (index2D + 0.5) / uAnimResolution);
float animFrameIndex = floatTexel(animIndexTexel);
return animFrameIndex;
}
#endif
vec2 getTileTexelCoord (Tile tile)
{
if (tile.empty)
{
return vec2(0.0);
}
float index = tile.index;
#if MAX_ANIM_FRAMES > 0
// Animated tile seek.
if (tile.animated)
{
index = animationIndex(index);
}
#endif
vec2 frameCorner = getFrameCorner(index);
return frameCorner + tile.uv;
}
#pragma phaserTemplate(fragmentHeader)
struct Samples {
vec4 color;
#pragma phaserTemplate(defineSamples)
};
// Get all samples that directly contribute to a given texel color.
Samples getColorSamples (vec2 texCoord)
{
Samples samples;
samples.color = texture2D(uMainSampler, texCoord);
#pragma phaserTemplate(getSamples)
return samples;
}
Samples mixSamples (Samples samples1, Samples samples2, float alpha)
{
Samples samples;
samples.color = mix(samples1.color, samples2.color, alpha);
#pragma phaserTemplate(mixSamples)
return samples;
}
// Get the set of samples that will be used to calculate the final color of a fragment.
Samples getFinalSamples (Tile tile, vec2 layerTexCoord)
{
vec2 texelCoord = getTileTexelCoord(tile);
vec2 texCoord = texelCoord / uMainResolution;
#pragma phaserTemplate(texCoord)
#ifndef FEATURE_BORDERFILTER
return getColorSamples(texCoord);
#else
#pragma phaserTemplate(finalSamples)
vec2 wh = uTileWidthHeightMarginSpacing.xy;
vec2 frameCorner = getFrameCorner(tile.index);
vec2 frameCornerOpposite = frameCorner + wh;
vec2 texCoordClamped = clamp(texCoord, (frameCorner + 0.5) / uMainResolution, (frameCornerOpposite - 0.5) / uMainResolution);
vec2 dTexelCoord = (texCoord - texCoordClamped) * uMainResolution;
vec2 offsets = sign(dTexelCoord);
Samples samples0 = getColorSamples(texCoordClamped);
if (offsets.x == 0.0)
{
if (offsets.y == 0.0)
{
// No border conditions.
return samples0;
}
// Vertical border condition.
Tile tileY = getLayerData(layerTexCoord + (offsets - dTexelCoord) / wh / uLayerResolution);
vec2 texelCoordY = getTileTexelCoord(tileY);
vec2 texCoordY = texelCoordY / uMainResolution;
Samples samplesY = getColorSamples(texCoordY);
return mixSamples(samples0, samplesY, abs(dTexelCoord.y));
}
else
{
if (offsets.y == 0.0)
{
// Horizontal border condition.
Tile tileX = getLayerData(layerTexCoord + (offsets - dTexelCoord) / wh / uLayerResolution);
vec2 texelCoordX = getTileTexelCoord(tileX);
vec2 texCoordX = texelCoordX / uMainResolution;
Samples samplesX = getColorSamples(texCoordX);
return mixSamples(samples0, samplesX, abs(dTexelCoord.x));
}
// Corner border condition.
Tile tileX = getLayerData(layerTexCoord + (offsets * vec2(1.0, 0.0) - dTexelCoord) / wh / uLayerResolution);
vec2 texelCoordX = getTileTexelCoord(tileX);
vec2 texCoordX = texelCoordX / uMainResolution;
Samples samplesX = getColorSamples(texCoordX);
Tile tileY = getLayerData(layerTexCoord + (offsets * vec2(0.0, 1.0) - dTexelCoord) / wh / uLayerResolution);
vec2 texelCoordY = getTileTexelCoord(tileY);
vec2 texCoordY = texelCoordY / uMainResolution;
Samples samplesY = getColorSamples(texCoordY);
Tile tileXY = getLayerData(layerTexCoord + (offsets - dTexelCoord) / wh / uLayerResolution);
vec2 texelCoordXY = getTileTexelCoord(tileXY);
vec2 texCoordXY = texelCoordXY / uMainResolution;
Samples samplesXY = getColorSamples(texCoordXY);
// Mix the samples.
Samples samples1 = mixSamples(samples0, samplesX, abs(dTexelCoord.x));
Samples samples2 = mixSamples(samplesY, samplesXY, abs(dTexelCoord.x));
return mixSamples(samples1, samples2, abs(dTexelCoord.y));
}
#endif
}
void main ()
{
vec2 layerTexCoord = outTexCoord;
Tile tile = getLayerData(layerTexCoord);
Samples samples = getFinalSamples(tile, layerTexCoord);
vec4 fragColor = samples.color;
#pragma phaserTemplate(declareSamples)
#pragma phaserTemplate(fragmentProcess)
fragColor *= uAlpha;
gl_FragColor = fragColor;
}

View file

@ -0,0 +1,43 @@
#pragma phaserTemplate(shaderName)
#pragma phaserTemplate(extensions)
#pragma phaserTemplate(features)
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#pragma phaserTemplate(vertexDefine)
uniform mat4 uProjectionMatrix;
uniform int uRoundPixels;
uniform vec2 uResolution;
uniform vec4 uTileWidthHeightMarginSpacing;
attribute vec2 inPosition;
attribute vec2 inTexCoord;
varying vec2 outTexCoord;
varying vec2 outTileStride;
#pragma phaserTemplate(outVariables)
#pragma phaserTemplate(vertexHeader)
void main ()
{
gl_Position = uProjectionMatrix * vec4(inPosition, 1.0, 1.0);
if (uRoundPixels == 1)
{
gl_Position.xy = floor(((gl_Position.xy + 1.0) * 0.5 * uResolution) + 0.5) / uResolution * 2.0 - 1.0;
}
outTexCoord = inTexCoord;
outTileStride = uTileWidthHeightMarginSpacing.xy + uTileWidthHeightMarginSpacing.zz;
#pragma phaserTemplate(vertexProcess)
}

View file

@ -17,6 +17,8 @@ var SpliceOne = require('../utils/array/SpliceOne');
var Sprite = require('../gameobjects/sprite/Sprite');
var Tile = require('./Tile');
var TilemapComponents = require('./components');
var TilemapLayerBase = require('./TilemapLayerBase');
var TilemapGPULayer = require('./TilemapGPULayer');
var TilemapLayer = require('./TilemapLayer');
var Tileset = require('./Tileset');
@ -579,10 +581,11 @@ var Tilemap = new Class({
* @param {(string|string[]|Phaser.Tilemaps.Tileset|Phaser.Tilemaps.Tileset[])} tileset - The tileset, or an array of tilesets, used to render this layer. Can be a string or a Tileset object.
* @param {number} [x=0] - The x position to place the layer in the world. If not specified, it will default to the layer offset from Tiled or 0.
* @param {number} [y=0] - The y position to place the layer in the world. If not specified, it will default to the layer offset from Tiled or 0.
* @param {boolean} [gpu=false] - Create a TilemapGPULayer instead of a TilemapLayer. This option is WebGL-only. A TilemapGPULayer is less flexible, but can be much faster.
*
* @return {?Phaser.Tilemaps.TilemapLayer} Returns the new layer was created, or null if it failed.
* @return {?Phaser.Tilemaps.TilemapLayer|?Phaser.Tilemaps.TilemapGPULayer} Returns the new layer was created, or null if it failed.
*/
createLayer: function (layerID, tileset, x, y)
createLayer: function (layerID, tileset, x, y, gpu)
{
var index = this.getLayerIndex(layerID);
@ -621,9 +624,19 @@ var Tilemap = new Class({
y = layerData.y;
}
var layer = new TilemapLayer(this.scene, this, index, tileset, x, y);
var layer;
if (gpu)
{
layer = new TilemapGPULayer(this.scene, this, index, tileset, x, y);
}
else
{
layer = new TilemapLayer(this.scene, this, index, tileset, x, y);
layer.setRenderOrder(this.renderOrder);
}
layer.setRenderOrder(this.renderOrder);
this.scene.sys.displayList.add(layer);
@ -1316,7 +1329,7 @@ var Tilemap = new Class({
{
return layer;
}
else if (layer instanceof TilemapLayer && layer.tilemap === this)
else if (layer instanceof TilemapLayerBase && layer.tilemap === this)
{
return layer.layerIndex;
}

View file

@ -0,0 +1,234 @@
/**
* @author Benjamin D. Richards <benjamindrichards@gmail.com>
* @copyright 2013-2024 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var DefaultTilemapGPULayerNodes = require('../renderer/webgl/renderNodes/defaults/DefaultTilemapGPULayerNodes');
var Class = require('../utils/Class');
var TilemapLayerBase = require('./TilemapLayerBase');
var TilemapGPULayerRender = require('./TilemapGPULayerRender');
/**
* @classdesc
* A TilemapGPULayer is a special kind of Game Object that renders LayerData from a Tilemap.
* Unlike the more flexible TilemapLayer, this object uses a single Tileset
* and is optimized for speed and quality over flexibility.
* Use it for high-performance rendering of tilemaps which don't update
* their contents. It still supports tile animation and flip.
*
* Performance of this layer can be highly variable.
* It is almost entirely GPU-bound, so it will free up CPU resources
* for other game code (the CPU usually does much more work than the GPU in games).
* It has a fixed cost per pixel on screen, whether there is anything in that
* tile or not.
* In general, it suffers no performance loss when many tiles are visible,
* which can make it superior to TilemapLayer.
* However, while it can be many times faster on desktop devices,
* mobile devices may struggle due to fillrate and texture sampling issues.
* You may want to check `game.device.os.desktop`,
* and review whether you want to improve CPU performance,
* before deciding to use this layer.
*
* Create a TilemapGPULayer by adding the `gpu` flag to a call to
* `Tilemap.createLayer()`. This will return a TilemapGPULayer instance.
*
* This layer has the following abilities and restrictions:
*
* - Use a single tileset, with a single texture image.
* - Maximum tilemap size of 4096x4096 tiles.
* - Maximum of 2^23 (8388608) unique tile IDs.
* - Tiles may be flipped.
* - Tiles may be animated.
* - Animation data limit of 8388608 entries (each animation or each frame of animation uses one entry).
* - Orthographic tilemaps only.
*
* The layer renders via a special shader.
* This uses a texture containing the layer tile data, and a second texture
* containing any tile animations. The shader then renders the tiles
* as a single quad. Because it doesn't have to compute individual tiles
* on the CPU, this is much faster than a TilemapLayer.
* However, because it treats tiles as a single orthographic grid,
* it is not suitable for use with isometric or hexagonal tilemaps,
* or other types of tilemap that require different rendering methods.
*
* If the tileset image uses NEAREST minfiltering, the shader will render
* sharp edged pixels. Otherwise, it assumes LINEAR filtering.
* The shader will automatically render smooth borders between tiles
* in LINEAR mode, with no seams or bleeding, for perfect results.
* A regular TilemapLayer cannot render smooth borders like this,
* creating sharp seams between tiles.
*
* The layer can be edited, but it will not update automatically.
* Regenerate the layer tile data texture by calling `generateLayerDataTexture`.
*
* @class TilemapGPULayer
* @extends Phaser.Tilemaps.TilemapLayerBase
* @memberof Phaser.Tilemaps
* @constructor
* @since 3.90.0
*
* @param {Phaser.Scene} scene - The Scene to which this TilemapGPULayer belongs.
* @param {Phaser.Tilemaps.Tilemap} tilemap - The Tilemap this layer is a part of.
* @param {number} layerIndex - The index of the LayerData associated with this layer.
* @param {(string|Phaser.Tilemaps.Tileset)} tileset - The tileset used to render the tiles in this layer. Can be a string or a Tileset object.
* @param {number} [x] - The world x position where the top left of this layer will be placed.
* @param {number} [y] - The world y position where the top left of this layer will be placed.
*/
var TilemapGPULayer = new Class({
Extends: TilemapLayerBase,
Mixins: [
TilemapGPULayerRender
],
initialize: function TilemapGPULayer (scene, tilemap, layerIndex, tileset, x, y)
{
TilemapLayerBase.call(this, 'TilemapGPULayer', scene, tilemap, layerIndex, x, y);
/**
* The `Tileset` associated with this layer.
*
* Unlike a `TilemapLayer`, this object can only have one tileset,
* because the renderer is optimized for a single texture.
*
* @name Phaser.Tilemaps.TilemapGPULayer#tileset
* @type {Phaser.Tilemaps.Tileset}
* @since 3.50.0
*/
this.tileset = null;
/**
* A texture containing the tile data for this game object.
* Each texel describes a single tile in the layer.
*
* Each texel is stored as a 32-bit value, encoded thus:
*
* - 1 bit: Horizontal flip flag.
* - 1 bit: Vertical flip flag.
* - 1 bit: Animation flag.
* - 1 bit: Unused.
* - 28 bits: Tile index.
*
* @name Phaser.Tilemaps.TilemapGPULayer#layerDataTexture
* @type {Phaser.Renderer.WebGL.WebGLTextureWrapper}
* @since 3.90.0
*/
this.layerDataTexture = null;
this.setTileset(tileset);
this.initRenderNodes(this._defaultRenderNodesMap);
},
/**
* The default render nodes for this Game Object.
*
* @name Phaser.Tilemaps.TilemapGPULayer#_defaultRenderNodesMap
* @type {Map<string, string>}
* @private
* @webglOnly
* @readonly
* @since 3.90.0
*/
_defaultRenderNodesMap: {
get: function ()
{
return DefaultTilemapGPULayerNodes;
}
},
/**
* Populates data structures to render this tilemap layer.
*
* @method Phaser.Tilemaps.TilemapGPULayer#setTileset
* @private
* @since 3.90.0
*
* @param {(string|Phaser.Tilemaps.Tileset)} tileset - The tileset used to render this layer. Can be a string or a Tileset object.
*/
setTileset: function (tileset)
{
if (typeof tileset === 'string')
{
tileset = this.tilemap.getTileset(tileset);
}
this.tileset = tileset;
// Convert layer into a data texture.
this.generateLayerDataTexture();
},
/**
* Generate the data textures for this game object.
* This method is called internally by `setTileset`.
*
* @method Phaser.Tilemaps.TilemapGPULayer#generateLayerDataTexture
* @since 3.90.0
*/
generateLayerDataTexture: function ()
{
var layer = this.layer;
var tileset = this.tileset;
var firstgid = tileset.firstgid;
var renderer = this.scene.renderer;
// Get or initialize the animation data texture in the Tileset.
var indexToAnimMap = tileset.getAnimationDataIndexMap(renderer);
// Generate the layer map.
var u32 = new Uint32Array(layer.width * layer.height);
for (var y = 0; y < layer.height; y++)
{
for (var x = 0; x < layer.width; x++)
{
var tile = layer.data[y][x];
var index = tile.index - firstgid;
var flipX = tile.flipX;
var flipY = tile.flipY;
var animated = false;
if (indexToAnimMap.has(index))
{
// This tile is part of an animation.
index = indexToAnimMap.get(index);
animated = true;
}
// Set flip flags.
if (flipX)
{
index |= 0x80000000;
}
if (flipY)
{
index |= 0x40000000;
}
if (animated)
{
index |= 0x20000000;
}
if (tile.index === -1)
{
// Set the fourth bit to 1 to indicate an empty tile.
index = 0x10000000;
}
u32[y * layer.width + x] = index;
}
}
// Create or update the tile data texture.
if (this.layerDataTexture)
{
this.layerDataTexture.destroy();
}
var u8 = new Uint8Array(u32.buffer);
this.layerDataTexture = renderer.createUint8ArrayTexture(u8, layer.width, layer.height, false);
}
});
module.exports = TilemapGPULayer;

View file

@ -0,0 +1,21 @@
/**
* @author Benjamin D. Richards <benjamindrichards@gmail.com>
* @copyright 2013-2024 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var NOOP = require('../utils/NOOP');
var renderWebGL = NOOP;
var renderCanvas = NOOP;
if (typeof WEBGL_RENDERER)
{
renderWebGL = require('./TilemapGPULayerWebGLRenderer');
}
module.exports = {
renderWebGL: renderWebGL,
renderCanvas: renderCanvas
};

View file

@ -0,0 +1,30 @@
/**
* @author Benjamin D. Richards <benjamindrichards@gmail.com>
* @copyright 2013-2024 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
/**
* Renders this Game Object with the WebGL Renderer to the given Camera.
* The object will not render if any of its renderFlags are set or it is being actively filtered out by the Camera.
* This method should not be called directly. It is a utility function of the Render module.
*
* @method Phaser.Tilemaps.TilemapLayer#renderWebGL
* @since 3.0.0
* @private
*
* @param {Phaser.Renderer.WebGL.WebGLRenderer} renderer - A reference to the current active WebGL renderer.
* @param {Phaser.Tilemaps.TilemapLayer} src - The Game Object being rendered in this call.
* @param {Phaser.Renderer.WebGL.DrawingContext} drawingContext - The current drawing context.
*/
var TilemapLayerWebGLRenderer = function (renderer, src, drawingContext)
{
var submitterNode = src.customRenderNodes.Submitter || src.defaultRenderNodes.Submitter;
submitterNode.run(
drawingContext,
src
);
};
module.exports = TilemapLayerWebGLRenderer;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -4,6 +4,7 @@
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var Map = require('../structs/Map');
var Class = require('../utils/Class');
var Vector2 = require('../math/Vector2');
@ -208,6 +209,49 @@ var Tileset = new Class({
* @default 64
*/
this.animationSearchThreshold = 64;
/**
* The maximum length of any animation in this tileset, in frames.
* This is used internally to optimize rendering.
* It is updated when `createAnimationDataTexture` is called.
*
* @name Phaser.Tilemaps.Tileset#maxAnimationLength
* @type {number}
* @readonly
* @since 3.90.0
*/
this.maxAnimationLength = 0;
/**
* The texture containing the animation data for this tileset, if any.
* This is used by `TilemapGPULayer` to animate tiles.
*
* This will be created when `createAnimationDataTexture` is called.
* Once created, it will be updated when `updateTileData` is called.
*
* Each texel stores a 32-bit number.
* The first set of texels consists of pairs of numbers,
* describing the total duration and starting index of an animation.
* The second set of texels are the targets of these indices, also in pairs,
* describing the duration and actual index of each frame in the animation.
*
* @name Phaser.Tilemaps.Tileset#_animationDataTexture
* @type {?Phaser.Renderer.WebGL.WebGLTextureWrapper}
* @private
*/
this._animationDataTexture = null;
/**
* The map from tile index to animation data index.
* This is used to quickly find the animation data for a tile.
* This is created when `createAnimationDataTexture` is called.
* Once created, it will be updated when `updateTileData` is called.
*
* @name Phaser.Tilemaps.Tileset#_animationDataIndexMap
* @type {?Map<number, number>}
* @private
*/
this._animationDataIndexMap = null;
},
/**
@ -491,7 +535,148 @@ var Tileset = new Class({
ty += this.tileHeight + this.tileSpacing;
}
// Update the animation data texture.
if (this._animationDataTexture)
{
this.createAnimationDataTexture();
}
return this;
},
/**
* Get or create the texture containing the animation data for this tileset.
* This is used by `TilemapGPULayer` to animate tiles.
*
* @method Phaser.Tilemaps.Tileset#getAnimationDataTexture
* @since 3.90.0
* @param {Phaser.Renderer.WebGL.WebGLRenderer} renderer - The renderer to use.
* @return {Phaser.Renderer.WebGL.WebGLTextureWrapper} The animation data texture.
*/
getAnimationDataTexture: function (renderer)
{
if (!this._animationDataTexture)
{
this.createAnimationDataTexture(renderer);
}
return this._animationDataTexture;
},
/**
* Get or create the map from tile index to animation data index.
* This is used by `TilemapGPULayer` to animate tiles.
*
* @method Phaser.Tilemaps.Tileset#getAnimationDataIndexMap
* @since 3.90.0
* @param {Phaser.Renderer.WebGL.WebGLRenderer} renderer - The renderer to use.
* @return {Map<number, number>} The map from tile index to animation data index.
*/
getAnimationDataIndexMap: function (renderer)
{
if (!this._animationDataIndexMap)
{
this.createAnimationDataTexture(renderer);
}
return this._animationDataIndexMap;
},
/**
* Creates a new WebGLTexture for the tileset's animation data.
*
* @method Phaser.Tilemaps.Tileset#createAnimationDataTexture
* @since 3.90.0
*
* @param {Phaser.Renderer.WebGL.WebGLRenderer} renderer - The renderer to use.
*
* @return {Phaser.Renderer.WebGL.WebGLTextureWrapper} The new WebGLTexture.
*/
createAnimationDataTexture: function (renderer)
{
var tileData = this.tileData;
var total = this.total;
var animations = [];
var animFrames = [];
var indexToAnimMap = new Map();
var maxLength = 0;
for (var i = 0; i < total; i++)
{
var tileDatum = tileData[i];
if (tileDatum && tileDatum.animation)
{
var animation = tileDatum.animation;
var animationDuration = tileDatum.animationDuration;
// This index maps to an animation, not a single tile.
indexToAnimMap.set(i, animations.length);
// This animation points to a run of frames.
animations.push([ animationDuration, animFrames.length ]);
// The run of frames stores the duration and the actual index.
for (var j = 0; j < animation.length; j++)
{
var frame = animation[j];
animFrames.push([ frame.duration, frame.tileid ]);
}
// Store the maximum length of any animation.
maxLength = Math.max(maxLength, animation.length);
}
}
var totalTuples = animations.length + animFrames.length;
if (totalTuples > 4096 * 4096 / 2)
{
throw new Error('Tileset.animationDataTexture: too many animations - total number of animations plus animation frames is max 8388608, got ' + (totalTuples));
}
var size = totalTuples * 2;
var width = Math.min(size, 4096);
var height = Math.ceil(size / 4096);
var u32 = new Uint32Array(width * height);
var offset = 0;
var animLen = animations.length;
for (i = 0; i < animLen; i++)
{
animation = animations[i];
var duration = animation[0];
var index = animation[1];
u32[offset++] = duration;
// Store the index as an offset from the start of the animation frames.
// Double the index to account for the 2x 32-bit values per entry.
u32[offset++] = (index + animLen) * 2;
}
for (i = 0; i < animFrames.length; i++)
{
frame = animFrames[i];
var frameDuration = frame[0];
var frameIndex = frame[1];
u32[offset++] = frameDuration;
u32[offset++] = frameIndex;
}
// Create or update the animation data texture.
if (this.animationDataTexture)
{
this.animationDataTexture.destroy();
}
var u8 = new Uint8Array(u32.buffer);
this._animationDataTexture = renderer.createUint8ArrayTexture(u8, width, height, false);
this._animationDataIndexMap = indexToAnimMap;
this.maxAnimationLength = maxLength;
}
});

View file

@ -29,7 +29,9 @@ var Tilemaps = {
TilemapCreator: require('./TilemapCreator'),
TilemapFactory: require('./TilemapFactory'),
Tileset: require('./Tileset'),
TilemapLayerBase: require('./TilemapLayerBase'),
TilemapLayer: require('./TilemapLayer'),
TilemapGPULayer: require('./TilemapGPULayer'),
Orientation: require('./const/ORIENTATION_CONST'),
LayerData: require('./mapdata/LayerData'),