UNPKG

phaser

Version:

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

368 lines (325 loc) 13.5 kB
/** * @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 SimpleTextureVert = require('../../shaders/SimpleTexture-vert.js'); var MakeBoundedSampler = require('../../shaders/additionMakers/MakeBoundedSampler.js'); var ProgramManager = require('../../ProgramManager'); var WebGLVertexBufferLayoutWrapper = require('../../wrappers/WebGLVertexBufferLayoutWrapper'); var BaseFilter = require('./BaseFilter'); /** * @classdesc * This is a base class for all filters that use a shader. * Most filters will extend this class. * * It takes care of setting up the shader program and vertex buffer layout. * It also provides the `run` method which handles the rendering of the filter. * When rendering, it generates a new DrawingContext to render to, * and releases the input DrawingContext. * * Note: be careful when using `gl_FragCoord` in shader code. * This built-in variable gives you the "window relative" coordinate * of the pixel being processed. * But this is actually relative to the framebuffer size, * and Phaser treats all framebuffers except the main canvas * as being vertically flipped. * This means that `gl_FragCoord.y = 0` in a shader will be the bottom of a framebuffer, * but the top of the canvas. * This means `gl_FragCoord` gives different results when it's inside a * framebuffer (like a Render Texture or Filter) compared to the main canvas. * Be aware of this restriction when writing shaders. * * @class BaseFilterShader * @extends Phaser.Renderer.WebGL.RenderNodes.BaseFilter * @memberof Phaser.Renderer.WebGL.RenderNodes * @constructor * @since 4.0.0 * @param {string} name - The name of the filter. * @param {Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager} manager - The manager that owns this filter. * @param {string} [fragmentShaderKey] - The key of the fragment shader source in the shader cache. This will only be used if `fragmentShaderSource` is not set. * @param {string} [fragmentShaderSource] - The fragment shader source. * @param {?Phaser.Types.Renderer.WebGL.ShaderAdditionConfig[]} [shaderAdditions] - An array of shader additions to apply to the shader program. */ var BaseFilterShader = new Class({ Extends: BaseFilter, initialize: function BaseFilterShader (name, manager, fragmentShaderKey, fragmentShaderSource, shaderAdditions) { if (!fragmentShaderSource) { var baseShader = manager.renderer.game.cache.shader.get(fragmentShaderKey); if (!(baseShader && baseShader.glsl)) { throw new Error('BaseFilterShader: No fragment shader source provided and no shader found with key ' + fragmentShaderKey); } fragmentShaderSource = baseShader.glsl; } BaseFilter.call(this, name, manager); var renderer = manager.renderer; var gl = renderer.gl; var config = { name: name, shaderName: name, vertexSource: SimpleTextureVert, fragmentSource: fragmentShaderSource, shaderAdditions: 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 } ] } }; // Include the BoundedSampler addition. config.shaderAdditions.push(MakeBoundedSampler()); /** * The index buffer defining vertex order. * * @name Phaser.Renderer.WebGL.RenderNodes.BaseFilterShader#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 forming the corners of a quad. * * @name Phaser.Renderer.WebGL.RenderNodes.BaseFilterShader#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.BaseFilterShader#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); } // Set the shader program to use texture unit 0. this.programManager.setUniform('uMainSampler', 0); }, /** * Runs the filter shader. This method handles the full rendering pipeline for the filter: * it acquires an output DrawingContext (or uses the one provided), computes * padded quad vertices, populates the vertex buffer, sets up textures and uniforms * via the shader program, issues the draw call, and releases the input context. * * @method Phaser.Renderer.WebGL.RenderNodes.BaseFilterShader#run * @since 4.0.0 * @param {Phaser.Filters.Controller} controller - The filter controller that owns this filter. * @param {Phaser.Renderer.WebGL.DrawingContext} inputDrawingContext - The input drawing context containing the source texture. * @param {Phaser.Renderer.WebGL.DrawingContext} [outputDrawingContext] - An optional output drawing context. If not provided, one will be obtained from the pool. * @param {Phaser.Geom.Rectangle} [padding] - Optional padding Rectangle to apply. If not provided, the controller's padding is used. * @return {Phaser.Renderer.WebGL.DrawingContext} The output drawing context containing the filtered result. */ run: function (controller, inputDrawingContext, outputDrawingContext, padding) { var manager = this.manager; var renderer = manager.renderer; manager.startStandAloneRender(); // Get a new DrawingContext to render to. if (!padding) { padding = controller.getPaddingCeil(); } if (!outputDrawingContext) { outputDrawingContext = renderer.drawingContextPool.get( inputDrawingContext.width + padding.width, inputDrawingContext.height + padding.height ); } outputDrawingContext.use(); this.onRunBegin(outputDrawingContext); // Assemble textures. var textures = [ inputDrawingContext.texture ]; this.setupTextures(controller, textures, outputDrawingContext); // Compute quad vertices. var xBL = -1; var yBL = -1; var xTL = -1; var yTL = 1; var xTR = 1; var yTR = 1; var xBR = 1; var yBR = -1; // Account for padding. if (padding.left) { var paddingLeft = 2 * padding.left / outputDrawingContext.width; xBL -= paddingLeft; xTL -= paddingLeft; } if (padding.right) { var paddingRight = 2 * padding.right / outputDrawingContext.width; xBR -= paddingRight; xTR -= paddingRight; } if (padding.top) { var paddingTop = 2 * padding.top / outputDrawingContext.height; yTL += paddingTop; yTR += paddingTop; } if (padding.bottom) { var paddingBottom = 2 * padding.bottom / outputDrawingContext.height; yBL += paddingBottom; yBR += paddingBottom; } // 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++] = -1; vertexF32[offset32++] = -1; vertexF32[offset32++] = remapCoord(0, xBL, xBR); vertexF32[offset32++] = remapCoord(0, yBL, yBR); // Top Left. vertexF32[offset32++] = -1; vertexF32[offset32++] = 1; vertexF32[offset32++] = remapCoord(0, xTL, xTR); vertexF32[offset32++] = remapCoord(1, yTL, yTR); // Bottom Right. vertexF32[offset32++] = 1; vertexF32[offset32++] = -1; vertexF32[offset32++] = remapCoord(1, xBL, xBR); vertexF32[offset32++] = remapCoord(0, yBL, yBR); // Top Right. vertexF32[offset32++] = 1; vertexF32[offset32++] = 1; vertexF32[offset32++] = remapCoord(1, xTL, xTR); vertexF32[offset32++] = remapCoord(1, yTL, yTR); // Update vertex buffer. // Because we frequently aren't filling the entire buffer, // we need to update the buffer with the correct size. vertexBuffer.update(stride * 4); // Render. var programManager = this.programManager; this.updateShaderConfig(controller, outputDrawingContext); var programSuite = programManager.getCurrentProgramSuite(); if (programSuite) { var program = programSuite.program; var vao = programSuite.vao; this.setupUniforms(controller, outputDrawingContext); programManager.applyUniforms(program); // Render layer. renderer.drawElements( outputDrawingContext, textures, program, vao, 4, 0 ); } // Complete render. inputDrawingContext.release(); this.onRunEnd(outputDrawingContext); return outputDrawingContext; }, /** * Set up the shader configuration for this shader. * Override this method to handle shader configuration. * * @method Phaser.Renderer.WebGL.RenderNodes.BaseFilterShader#updateShaderConfig * @since 4.0.0 * @param {Phaser.Filters.Controller} controller - The filter controller. * @param {Phaser.Renderer.WebGL.DrawingContext} drawingContext - The drawing context in use. */ updateShaderConfig: function (controller, drawingContext) { // NOOP }, /** * Run any necessary modifications on the textures array. * Override this method to handle texture inputs. * * @method Phaser.Renderer.WebGL.RenderNodes.BaseFilterShader#setupTextures * @since 4.0.0 * @param {Phaser.Filters.Controller} controller - The filter controller. * @param {Phaser.Renderer.WebGL.Wrappers.WebGLTextureWrapper[]} textures - The array of textures to modify in-place. * @param {Phaser.Renderer.WebGL.DrawingContext} drawingContext - The drawing context in use. */ setupTextures: function (controller, textures, drawingContext) { // NOOP }, /** * Set up the uniforms for this shader, based on the controller. * Override this method to handle uniform setup. * * @method Phaser.Renderer.WebGL.RenderNodes.BaseFilterShader#setupUniforms * @since 4.0.0 * @param {Phaser.Filters.Controller} controller - The filter controller. * @param {Phaser.Renderer.WebGL.DrawingContext} drawingContext - The drawing context in use. */ setupUniforms: function (controller, drawingContext) { // This is the base setupUniforms method that all filters should override } }); /** * Remaps a normalized coordinate (0 or 1) into the range defined by `low` and `high`, * where `low` and `high` are clip-space values in the range -1 to 1. * This is used to compute texture coordinates for padded filter quads, * mapping the full quad UV space onto a potentially offset sub-region. * * @ignore * @function remapCoord * @since 4.0.0 * @param {number} coord - The normalized coordinate to remap, either 0 (start) or 1 (end). * @param {number} low - The clip-space lower bound, in the range -1 to 1. * @param {number} high - The clip-space upper bound, in the range -1 to 1. * @return {number} The remapped coordinate. */ function remapCoord (coord, low, high) { // Low,high are in the range -1,1. // Convert low,high to 0,1. low = ((1 / low) + 1) * 0.5; high = ((1 / high) + 1) * 0.5; return low + coord * (high - low); } module.exports = BaseFilterShader;