phaser
Version:
A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.
595 lines (526 loc) • 19.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 Map = require('../../../structs/Map');
var Class = require('../../../utils/Class');
function isDifferent (a, b)
{
if (a === b)
{
return false;
}
if (Number.isNaN(a) || a === undefined)
{
if (Number.isNaN(b) || b === undefined)
{
return false;
}
}
return true;
}
/**
* @classdesc
* Wraps a WebGL shader program, which is the GPU-side program compiled from a vertex shader
* and a fragment shader. Together, these shaders define how geometry is transformed and
* how each pixel within that geometry is colored. Every pipeline in Phaser's WebGL renderer
* is backed by a WebGLProgramWrapper.
*
* Beyond holding the compiled program, this wrapper manages the full lifecycle of the
* associated WebGL resources: it tracks vertex attribute locations and layout, stores
* uniform values, and queues uniform update requests that are applied lazily when the
* program is bound for drawing.
*
* The raw WebGLProgram object should never be used or stored directly outside of the
* WebGLRenderer. By routing all access through this wrapper, the renderer can transparently
* handle WebGL context loss and restoration — recreating the underlying program and
* re-applying all state — without any other system needing to be aware of it.
* Always use WebGLProgramWrapper instead of holding a direct WebGLProgram reference.
*
* @class WebGLProgramWrapper
* @memberof Phaser.Renderer.WebGL.Wrappers
* @constructor
* @since 3.80.0
*
* @param {Phaser.Renderer.WebGL.WebGLRenderer} renderer - The WebGLRenderer instance that owns this wrapper.
* @param {string} vertexSource - The vertex shader source code as a string.
* @param {string} fragmentSource - The fragment shader source code as a string.
*/
var WebGLProgramWrapper = new Class({
initialize:
function WebGLProgramWrapper (renderer, vertexSource, fragmentSource)
{
/**
* The WebGLRenderer instance that owns this wrapper.
*
* @name Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper#renderer
* @type {Phaser.Renderer.WebGL.WebGLRenderer}
* @since 4.0.0
*/
this.renderer = renderer;
/**
* The WebGLProgram being wrapped by this class.
*
* This property could change at any time.
* Therefore, you should never store a reference to this value.
* It should only be passed directly to the WebGL API for drawing.
*
* @name Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper#webGLProgram
* @type {?WebGLProgram}
* @default null
* @since 3.80.0
*/
this.webGLProgram = null;
/**
* Whether this program is currently being compiled.
* This will always be false, unless parallel shader compilation
* is enabled via `config.render.skipUnreadyShaders`.
*
* @name Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper#compiling
* @type {boolean}
* @default false
* @readonly
* @since 4.0.0
*/
this.compiling = false;
/**
* The time at which the compilation of this program started.
* This is used to track the time taken to compile the program.
*
* @name Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper#_compileStartTime
* @type {number}
* @private
* @since 4.0.0
*/
this._compileStartTime = 0;
/**
* The time taken to compile this program, in milliseconds.
*
* @name Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper#compileTimeMs
* @type {number}
* @readonly
* @since 4.0.0
*/
this.compileTimeMs = 0;
/**
* The WebGL state necessary to bind this program.
*
* This is used internally to accelerate state changes.
*
* @name Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper#glState
* @type {Phaser.Types.Renderer.WebGL.WebGLGlobalParameters}
* @since 4.0.0
*/
this.glState = { bindings: { program: this } };
/**
* The vertex shader source code as a string.
*
* @name Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper#vertexSource
* @type {string}
* @since 3.80.0
*/
this.vertexSource = vertexSource;
/**
* The fragment shader source code as a string.
*
* @name Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper#fragmentSource
* @type {string}
* @since 3.80.0
*/
this.fragmentSource = fragmentSource;
/**
* The vertex shader object.
*
* @name Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper#_vertexShader
* @type {WebGLShader}
* @default null
* @private
* @since 4.0.0
*/
this._vertexShader = null;
/**
* The fragment shader object.
*
* @name Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper#_fragmentShader
* @type {WebGLShader}
* @default null
* @private
* @since 4.0.0
*/
this._fragmentShader = null;
/**
* The attribute state of this program.
*
* These represent the actual state in WebGL, and are only updated when
* the program is used to draw.
*
* @name Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper#glAttributes
* @type {Array<{ location: GLint, name: string, size: number, type: GLenum }>}
* @since 4.0.0
*/
this.glAttributes = [];
/**
* Map of attribute names to their indexes in `glAttributes`.
*
* @name Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper#glAttributeNames
* @type {Map<string, number>}
* @since 4.0.0
*/
this.glAttributeNames = new Map();
/**
* The buffer which this program is using for its attributes.
*
* @name Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper#glAttributeBuffer
* @type {?WebGLBuffer}
* @default null
* @since 4.0.0
*/
this.glAttributeBuffer = null;
/**
* The uniform state of this program.
*
* These represent the actual state in WebGL, and are only updated when
* the program is used to draw.
*
* @name Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper#glUniforms
* @type {Map<string, Phaser.Types.Renderer.WebGL.WebGLUniform>}
* @since 4.0.0
*/
this.glUniforms = new Map();
/**
* Requests to update the uniform state.
* Set a request by name to a new value.
* These are only processed when the program is used to draw.
*
* @name Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper#uniformRequests
* @type {Map<string, any>}
* @since 4.0.0
*/
this.uniformRequests = new Map();
this.createResource();
},
/**
* Creates a WebGLProgram from the given vertex and fragment shaders.
*
* This is called automatically by the constructor. It may also be
* called again if the WebGLProgram needs re-creating.
*
* @method Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper#createResource
* @throws {Error} If the shaders failed to compile or link.
* @since 3.80.0
*/
createResource: function ()
{
var renderer = this.renderer;
var gl = renderer.gl;
// Ensure that there is no vertex buffer associated with this program,
// so that the attributes are reset.
this.glAttributeBuffer = null;
if (gl.isContextLost())
{
// GL state can't be updated right now.
// `createResource` will run when the context is restored.
return;
}
this.compiling = true;
this._compileStartTime = performance.now();
// Unbind current program before creating a new one.
// Otherwise, the old program will stay in use,
// and cause errors.
if (renderer.glWrapper.state.bindings.program === this)
{
renderer.glWrapper.updateBindingsProgram({
bindings: { program: null }
});
}
var program = gl.createProgram();
this.webGLProgram = program;
var vs = gl.createShader(gl.VERTEX_SHADER);
var fs = gl.createShader(gl.FRAGMENT_SHADER);
this._vertexShader = vs;
this._fragmentShader = fs;
gl.shaderSource(vs, this.vertexSource);
gl.shaderSource(fs, this.fragmentSource);
gl.compileShader(vs);
gl.compileShader(fs);
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
if (!renderer.game.config.skipUnreadyShaders)
{
this._completeProgram();
}
},
/**
* Poll shader compilation status, and complete the program if it is ready.
* This is only called if `skipUnreadyShaders` is enabled
* and the KHR_parallel_shader_compile extension is available.
*
* @method Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper#checkParallelCompile
* @since 4.0.0
*/
checkParallelCompile: function ()
{
var renderer = this.renderer;
var gl = renderer.gl;
var ext = renderer.parallelShaderCompileExtension;
if (!ext || !gl.getProgramParameter(this.webGLProgram, ext.COMPLETION_STATUS_KHR))
{
return;
}
this._completeProgram();
},
/**
* Complete the program after the shaders have compiled.
* This checks the status of the shaders and the program,
* and throws an error if they failed to compile.
*
* @method Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper#_completeProgram
* @private
* @since 4.0.0
*/
_completeProgram: function ()
{
var program = this.webGLProgram;
var renderer = this.renderer;
var gl = renderer.gl;
var vs = this._vertexShader;
var fs = this._fragmentShader;
var failed = 'Shader failed:\n';
if (!gl.getProgramParameter(program, gl.LINK_STATUS))
{
if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS))
{
console.log(this.vertexSource);
throw new Error('Vertex ' + failed + gl.getShaderInfoLog(vs));
}
if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS))
{
console.log(this.fragmentSource);
throw new Error('Fragment ' + failed + gl.getShaderInfoLog(fs));
}
console.log(this.vertexSource, this.fragmentSource);
throw new Error('Link Shader failed:' + gl.getProgramInfoLog(program));
}
this._setupAttributesAndUniforms();
this.compileTimeMs = performance.now() - this._compileStartTime;
this.compiling = false;
},
/**
* Set up the attributes and uniforms for this program.
* This is called after the program is created or re-created.
*
* @method Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper#_setupAttributesAndUniforms
* @private
* @since 4.0.0
*/
_setupAttributesAndUniforms: function ()
{
var program = this.webGLProgram;
var renderer = this.renderer;
var gl = renderer.gl;
var _this = this;
// Extract attributes.
this.glAttributeNames.clear();
this.glAttributes.length = 0;
var attributeCount = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);
for (var index = 0; index < attributeCount; index++)
{
var attribute = gl.getActiveAttrib(program, index);
var location = gl.getAttribLocation(program, attribute.name);
this.glAttributeNames.set(attribute.name, index);
this.glAttributes[index] = {
location: location,
name: attribute.name,
size: attribute.size,
type: attribute.type
};
}
// Send the old uniforms to the request map,
// so they are recreated with the new program.
this.glUniforms.each(function (name, uniform)
{
if (!_this.uniformRequests.has(name))
{
_this.uniformRequests.set(name, uniform.value);
}
});
this.glUniforms.clear();
var uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
for (index = 0; index < uniformCount; index++)
{
var uniform = gl.getActiveUniform(program, index);
var setter = renderer.shaderSetters.constants[uniform.type];
var initialValue = 0;
var terms = uniform.size * setter.size;
if (terms > 1)
{
initialValue = setter.baseType === gl.FLOAT
? new Float32Array(terms)
: new Int32Array(terms);
}
this.glUniforms.set(uniform.name, {
location: gl.getUniformLocation(program, uniform.name),
size: uniform.size,
type: uniform.type,
value: initialValue
});
}
},
/**
* Set a uniform value for this WebGLProgram.
*
* This method doesn't set the WebGL value directly.
* Instead, it adds a request to the `uniformRequests` map.
* These requests are processed when the program is used to draw.
*
* @method Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper#setUniform
* @since 4.0.0
* @param {string} name - The name of the uniform.
* @param {number|number[]|Int32Array|Float32Array} value - The value to set.
*/
setUniform: function (name, value)
{
this.uniformRequests.set(name, value);
},
/**
* Set this program as the active program in the WebGL context, then
* process all pending uniform update requests that were queued via
* `setUniform`. Each request is applied to the GPU and the queue is
* cleared, so only values that have actually changed are sent to WebGL.
*
* @method Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper#bind
* @since 4.0.0
*/
bind: function ()
{
this.renderer.glWrapper.updateBindingsProgram(this.glState);
this.uniformRequests.each(this._processUniformRequest.bind(this));
this.uniformRequests.clear();
},
/**
* Process a request to update a uniform value.
*
* Requests are stored in the `uniformRequests` map,
* and only bound with this method when the program is used to draw.
* This ensures that the WebGL state is only updated when necessary.
*
* This method works from the description of uniforms provided by WebGL.
* It will only update uniforms that exist, in the fields that exist
* on those uniforms. If requests for invalid uniforms or fields are made,
* they are ignored.
*
* @method Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper#_processUniformRequest
* @since 4.0.0
* @private
* @param {string} name - The name of the uniform.
* @param {number|number[]|Int32Array|Float32Array} value - The value to set.
*/
_processUniformRequest: function (name, value)
{
var renderer = this.renderer;
var gl = renderer.gl;
var uniform = this.glUniforms.get(name);
if (!uniform) { return; }
var uniformValue = uniform.value;
// Update stored values if they are different.
if (uniformValue.length)
{
var different = false;
for (var i = 0; i < uniformValue.length; i++)
{
if (isDifferent(uniformValue[i], value[i]))
{
different = true;
uniformValue[i] = value[i];
}
}
if (!different) { return; }
}
else
{
if (!isDifferent(uniformValue, value)) { return; }
uniformValue = value;
uniform.value = value;
}
// Get info about the uniform.
var location = uniform.location;
var type = uniform.type;
var size = uniform.size;
var setter = renderer.shaderSetters.constants[type];
// Set the value.
if (setter.isMatrix)
{
setter.set.call(gl, location, false, uniformValue);
}
else if (size > 1)
{
setter.setV.call(gl, location, uniformValue);
}
else
{
switch (setter.size)
{
case 1:
setter.set.call(gl, location, value);
break;
case 2:
setter.set.call(gl, location, value[0], value[1]);
break;
case 3:
setter.set.call(gl, location, value[0], value[1], value[2]);
break;
case 4:
setter.set.call(gl, location, value[0], value[1], value[2], value[3]);
break;
}
}
},
/**
* Destroys this WebGLProgramWrapper and releases all associated WebGL resources.
*
* If the context is not lost, this deletes the vertex shader, fragment shader,
* and the linked WebGLProgram from the GPU, and disables all vertex attribute
* arrays used by this program. The uniform map, attribute map, and any pending
* uniform requests are also cleared. All internal references are then nulled so
* this wrapper can be safely garbage-collected.
*
* @method Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper#destroy
* @since 3.80.0
*/
destroy: function ()
{
if (!this.webGLProgram)
{
return;
}
var gl = this.renderer.gl;
if (!gl.isContextLost())
{
if (this._vertexShader)
{
gl.deleteShader(this._vertexShader);
}
if (this._fragmentShader)
{
gl.deleteShader(this._fragmentShader);
}
gl.deleteProgram(this.webGLProgram);
for (var i = 0; i < this.glAttributes.length; i++)
{
gl.disableVertexAttribArray(this.glAttributes[i].location);
}
this.glAttributes.length = 0;
this.glUniforms.clear();
}
this.glAttributeBuffer = null;
this.glAttributeNames.clear();
this.uniformRequests.clear();
this._vertexShader = null;
this._fragmentShader = null;
this.webGLProgram = null;
this.renderer = null;
}
});
module.exports = WebGLProgramWrapper;