phaser
Version:
A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.
392 lines (346 loc) • 13.4 kB
JavaScript
/**
* @author Benjamin D. Richards <benjamindrichards@gmail.com>
* @copyright 2013-2026 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var Class = require('../../../utils/Class');
var WebGLVertexBufferLayoutWrapper = require('../wrappers/WebGLVertexBufferLayoutWrapper');
var ProgramManager = require('../ProgramManager');
var RenderNode = require('./RenderNode');
var ShaderSourceVS = require('../shaders/ShaderQuad-vert');
var ShaderSourceFS = require('../shaders/ShaderQuad-frag');
/**
* @classdesc
* A RenderNode that renders a single quad using a custom GLSL shader program.
* It is used by the Shader Game Object to execute user-defined vertex and
* fragment shaders against a four-vertex quad that matches the game object's
* dimensions. The quad is transformed through the scene camera and supports
* optional rendering directly to a texture via `renderToTexture`. Shader
* uniforms, texture samplers, and shader addition variants are all managed
* through this node, making it the primary entry point for custom WebGL
* shader effects in Phaser.
*
* @class ShaderQuad
* @memberof Phaser.Renderer.WebGL.RenderNodes
* @constructor
* @since 4.0.0
* @extends Phaser.Renderer.WebGL.RenderNodes.RenderNode
* @param {Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager} manager - The manager that owns this RenderNode.
* @param {Phaser.Types.GameObjects.Shader.ShaderQuadConfig} config - The configuration object for this RenderNode.
*/
var ShaderQuad = new Class({
Extends: RenderNode,
initialize: function ShaderQuad (manager, config)
{
RenderNode.call(this, 'ShaderQuad', manager);
var renderer = manager.renderer;
/**
* The WebGLRenderer in use.
*
* @name Phaser.Renderer.WebGL.RenderNodes.ShaderQuad#renderer
* @type {Phaser.Renderer.WebGL.WebGLRenderer}
* @since 4.0.0
*/
this.renderer = renderer;
config = this._completeConfig(config);
if (config.updateShaderConfig)
{
this.updateShaderConfig = config.updateShaderConfig;
}
/**
* The index buffer defining vertex order.
*
* @name Phaser.Renderer.WebGL.RenderNodes.ShaderQuad#indexBuffer
* @type {Phaser.Renderer.WebGL.Wrappers.WebGLBufferWrapper}
* @since 4.0.0
*/
this.indexBuffer = renderer.genericQuadIndexBuffer;
/**
* The vertex buffer layout for this RenderNode.
*
* This consists of 4 vertices, 0-3, forming the corners of a quad instance.
*
* @name Phaser.Renderer.WebGL.RenderNodes.ShaderQuad#vertexBufferLayout
* @type {Phaser.Renderer.WebGL.Wrappers.WebGLVertexBufferLayoutWrapper}
* @since 4.0.0
* @readonly
*/
this.vertexBufferLayout = new WebGLVertexBufferLayoutWrapper(
renderer,
config.vertexBufferLayout,
null
);
/**
* The program manager used to create and manage shader programs.
* This contains shader variants.
*
* @name Phaser.Renderer.WebGL.RenderNodes.ShaderQuad#programManager
* @type {Phaser.Renderer.WebGL.ProgramManager}
* @since 4.0.0
*/
this.programManager = new ProgramManager(
renderer,
[ this.vertexBufferLayout ],
this.indexBuffer
);
// Fill in program configuration from config.
this.programManager.setBaseShader(
config.shaderName,
config.vertexSource,
config.fragmentSource
);
for (var i = 0; i < config.shaderAdditions.length; i++)
{
var addition = config.shaderAdditions[i];
this.programManager.addAddition(addition);
}
/**
* A bound convenience function that queues a uniform value to be applied
* to the current shader program. Delegates to the ProgramManager's
* `setUniform` method.
*
* @name Phaser.Renderer.WebGL.RenderNodes.ShaderQuad#setUniform
* @type {function}
* @since 4.0.0
*/
this.setUniform = this.programManager.setUniform.bind(this.programManager);
/**
* The transformer node used to transform the quad for rendering.
*
* @name Phaser.Renderer.WebGL.RenderNodes.ShaderQuad#transformerNode
* @type {Phaser.Renderer.WebGL.RenderNodes.TransformerImage}
* @since 4.0.0
*/
this.transformerNode = manager.getNode('TransformerImage');
/**
* An object which acts as a proxy for textures in the transformer.
*
* @name Phaser.Renderer.WebGL.RenderNodes.ShaderQuad#_texturerProxy
* @type {object}
* @since 4.0.0
*/
this._texturerProxy = {
frameWidth: 1,
frameHeight: 1,
frame: { realWidth: 1, realHeight: 1 },
uvSource: { x: 0, y: 0 }
};
},
/**
* Completes the configuration for this RenderNode.
* This method is called during initialization.
*
* @method Phaser.Renderer.WebGL.RenderNodes.ShaderQuad#_completeConfig
* @private
* @since 4.0.0
* @param {object} config - The configuration object for this RenderNode.
* @return {object} The completed configuration object.
*/
_completeConfig: function (config)
{
var gl = this.renderer.gl;
var vertexSource = config.vertexSource;
if (!vertexSource)
{
var vertexKey = config.vertexKey;
if (vertexKey)
{
var baseShader = this.manager.renderer.game.cache.shader.get(vertexKey);
if (baseShader && baseShader.glsl)
{
vertexSource = baseShader.glsl;
}
}
}
if (!vertexSource)
{
vertexSource = ShaderSourceVS;
}
var fragmentSource = config.fragmentSource;
if (!fragmentSource)
{
var fragmentKey = config.fragmentKey;
if (fragmentKey)
{
baseShader = this.manager.renderer.game.cache.shader.get(fragmentKey);
if (baseShader && baseShader.glsl)
{
fragmentSource = baseShader.glsl;
}
}
}
if (!fragmentSource)
{
fragmentSource = ShaderSourceFS;
}
return {
name: config.name || 'ShaderQuad',
shaderName: config.shaderName || config.name || 'ShaderQuad',
vertexSource: vertexSource,
fragmentSource: fragmentSource,
shaderAdditions: config.shaderAdditions || [],
vertexBufferLayout: {
usage: 'DYNAMIC_DRAW',
count: 4,
layout: [
{
name: 'inPosition',
size: 2,
type: gl.FLOAT,
normalized: false
},
{
name: 'inTexCoord',
size: 2,
type: gl.FLOAT,
normalized: false
}
]
}
};
},
/**
* Renders a quad using the shader program. Transforms the quad vertices
* based on the game object properties, populates the vertex buffer,
* sets up uniforms and textures, and issues a draw call.
*
* @method Phaser.Renderer.WebGL.RenderNodes.ShaderQuad#run
* @since 4.0.0
* @param {Phaser.Renderer.WebGL.DrawingContext} drawingContext - The current drawing context.
* @param {Phaser.GameObjects.Shader} gameObject - The Shader game object being rendered.
* @param {Phaser.GameObjects.Components.TransformMatrix} parentMatrix - The parent transform matrix.
*/
run: function (drawingContext, gameObject, parentMatrix)
{
var manager = this.manager;
var renderer = this.renderer;
manager.startStandAloneRender();
this.onRunBegin(drawingContext);
// Get transformed quad vertices.
var width = gameObject.width;
var height = gameObject.height;
this._texturerProxy.frame.realWidth = width;
this._texturerProxy.frame.realHeight = height;
this._texturerProxy.frameWidth = width;
this._texturerProxy.frameHeight = height;
var xTL, yTL, xBL, yBL, xBR, yBR, xTR, yTR;
if (gameObject.renderToTexture)
{
xTL = 0;
yTL = 0;
xBL = 0;
yBL = height;
xBR = width;
yBR = height;
xTR = width;
yTR = 0;
}
else
{
var transformerNode = this.transformerNode;
transformerNode.run(drawingContext, gameObject, this._texturerProxy, parentMatrix);
var quad = transformerNode.quad;
xTL = quad[0];
yTL = quad[1];
xBL = quad[2];
yBL = quad[3];
xBR = quad[4];
yBR = quad[5];
xTR = quad[6];
yTR = quad[7];
}
// 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++] = xBL;
vertexF32[offset32++] = yBL;
vertexF32[offset32++] = gameObject.textureCoordinateBottomLeft.x;
vertexF32[offset32++] = gameObject.textureCoordinateBottomLeft.y;
// Top Left.
vertexF32[offset32++] = xTL;
vertexF32[offset32++] = yTL;
vertexF32[offset32++] = gameObject.textureCoordinateTopLeft.x;
vertexF32[offset32++] = gameObject.textureCoordinateTopLeft.y;
// Bottom Right.
vertexF32[offset32++] = xBR;
vertexF32[offset32++] = yBR;
vertexF32[offset32++] = gameObject.textureCoordinateBottomRight.x;
vertexF32[offset32++] = gameObject.textureCoordinateBottomRight.y;
// Top Right.
vertexF32[offset32++] = xTR;
vertexF32[offset32++] = yTR;
vertexF32[offset32++] = gameObject.textureCoordinateTopRight.x;
vertexF32[offset32++] = gameObject.textureCoordinateTopRight.y;
vertexBuffer.update(stride * 4);
// Render.
var programManager = this.programManager;
this.updateShaderConfig(drawingContext, gameObject, this);
var programSuite = programManager.getCurrentProgramSuite();
if (programSuite)
{
var program = programSuite.program;
var vao = programSuite.vao;
var setUniform = this.setUniform;
renderer.setProjectionMatrixFromDrawingContext(drawingContext);
setUniform(
'uProjectionMatrix',
drawingContext.renderer.projectionMatrix.val
);
// Set user uniforms after built-ins to permit overrides.
gameObject.setupUniforms(setUniform, drawingContext);
programManager.applyUniforms(program);
// Draw.
renderer.drawElements(
drawingContext,
this.setupTextures(gameObject),
program,
vao,
4,
0
);
}
this.onRunEnd(drawingContext);
},
/**
* Extracts the WebGL textures from the game object's texture list
* and returns them as an array for use during rendering.
*
* @method Phaser.Renderer.WebGL.RenderNodes.ShaderQuad#setupTextures
* @since 4.0.0
* @param {Phaser.GameObjects.Shader} gameObject - The Shader game object to extract textures from.
* @return {Phaser.Renderer.WebGL.Wrappers.WebGLTextureWrapper[]} An array of WebGL textures.
*/
setupTextures: function (gameObject)
{
var textures = gameObject.textures;
var glTextures = [];
for (var i = 0; i < textures.length; i++)
{
var texture = textures[i];
var glTexture = texture.get().source.glTexture;
glTextures.push(glTexture);
}
return glTextures;
},
/**
* Updates the shader configuration for the current render pass.
* This is called before the shader is rendered.
* This method is a hook for custom shader configurations.
* You should override it if you need to adjust shader additions
* after initialization.
*
* @method Phaser.Renderer.WebGL.RenderNodes.ShaderQuad#updateShaderConfig
* @since 4.0.0
* @param {Phaser.Renderer.WebGL.DrawingContext} drawingContext - The current drawing context.
* @param {Phaser.GameObjects.GameObject} gameObject - The GameObject being rendered.
* @param {Phaser.Renderer.WebGL.RenderNodes.ShaderQuad} renderNode - The RenderNode being rendered.
*/
updateShaderConfig: function (drawingContext, gameObject, renderNode)
{
// NOOP.
}
});
module.exports = ShaderQuad;