diff --git a/src/renderer/webgl/WebGLRenderer.js b/src/renderer/webgl/WebGLRenderer.js index 21b59692e..1d611fca1 100644 --- a/src/renderer/webgl/WebGLRenderer.js +++ b/src/renderer/webgl/WebGLRenderer.js @@ -553,6 +553,25 @@ var WebGLRenderer = new Class({ */ this.glFuncMap = null; + /** + * Internal GL function mapping for setting uniform data. + * https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/uniform + * + * Each key is a WebGL constant for a specific uniform type. + * Each value has these properties: + * - `size`: The number of terms in the uniform, i.e. the length of the vector. + * - `set`: The function to call to set the uniform value. + * - If it is a matrix: + * - `matrix: true` + * - Otherwise: + * - `setV`: The function to call to set the uniform value as an array. + * + * @name Phaser.Renderer.WebGL.WebGLRenderer#uniformSetterMap + * @type {any} + * @since 3.90.0 + */ + this.uniformSetterMap = null; + /** * The `type` of the Game Object being currently rendered. * This can be used by advanced render functions for batching look-ahead. @@ -917,6 +936,40 @@ var WebGLRenderer = new Class({ }; + // Map uniform types to WebGL functions + this.uniformSetterMap = { + 0x1404: { size: 1, set: gl.uniform1i, setV: gl.uniform1iv, int: true }, + 0x8B53: { size: 2, set: gl.uniform2i, setV: gl.uniform2iv, int: true }, + 0x8B54: { size: 3, set: gl.uniform3i, setV: gl.uniform3iv, int: true }, + 0x8B55: { size: 4, set: gl.uniform4i, setV: gl.uniform4iv, int: true }, + 0x1406: { size: 1, set: gl.uniform1f, setV: gl.uniform1fv }, + 0x8B50: { size: 2, set: gl.uniform2f, setV: gl.uniform2fv }, + 0x8B51: { size: 3, set: gl.uniform3f, setV: gl.uniform3fv }, + 0x8B52: { size: 4, set: gl.uniform4f, setV: gl.uniform4fv }, + 0x8B5A: { size: 4, matrix: true, set: gl.uniformMatrix2fv }, + 0x8B5B: { size: 9, matrix: true, set: gl.uniformMatrix3fv }, + 0x8B5C: { size: 16, matrix: true, set: gl.uniformMatrix4fv } + }; + + // Map extra uniform types to integers. + // BOOL + this.uniformSetterMap[0x8B56] = this.uniformSetterMap[0x1404]; + + // BOOL_VEC2 + this.uniformSetterMap[0x8B57] = this.uniformSetterMap[0x8B53]; + + // BOOL_VEC3 + this.uniformSetterMap[0x8B58] = this.uniformSetterMap[0x8B54]; + + // BOOL_VEC4 + this.uniformSetterMap[0x8B59] = this.uniformSetterMap[0x8B55]; + + // SAMPLER_2D + this.uniformSetterMap[0x8B5E] = this.uniformSetterMap[0x1404]; + + // SAMPLER_CUBE + this.uniformSetterMap[0x8B60] = this.uniformSetterMap[0x1404]; + if (!config.maxTextures || config.maxTextures === -1) { config.maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); diff --git a/src/renderer/webgl/typedefs/WebGLUniform.js b/src/renderer/webgl/typedefs/WebGLUniform.js new file mode 100644 index 000000000..6b276940c --- /dev/null +++ b/src/renderer/webgl/typedefs/WebGLUniform.js @@ -0,0 +1,15 @@ +/** + * @author Benjamin D. Richards + * @copyright 2013-2024 Phaser Studio Inc. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * @typedef {object} Phaser.Types.Renderer.WebGL.WebGLUniform + * @since 3.90.0 + * + * @property {GLint} location - The location of the uniform. + * @property {GLenum} type - The type of the uniform. + * @property {GLint} size - The size of the uniform, in elements. + * @property {number|number[]} value - The value of the uniform. + */ diff --git a/src/renderer/webgl/wrappers/WebGLBufferWrapper.js b/src/renderer/webgl/wrappers/WebGLBufferWrapper.js index 164f84514..dc0c9cf30 100644 --- a/src/renderer/webgl/wrappers/WebGLBufferWrapper.js +++ b/src/renderer/webgl/wrappers/WebGLBufferWrapper.js @@ -150,10 +150,10 @@ var WebGLBufferWrapper = new Class({ */ destroy: function () { - this.gl.deleteBuffer(this.webGLBuffer); + this.renderer.gl.deleteBuffer(this.webGLBuffer); this.webGLBuffer = null; this.initialDataOrSize = null; - this.gl = null; + this.renderer = null; } }); diff --git a/src/renderer/webgl/wrappers/WebGLProgramWrapper.js b/src/renderer/webgl/wrappers/WebGLProgramWrapper.js index 8fec99e6a..7dcca4613 100644 --- a/src/renderer/webgl/wrappers/WebGLProgramWrapper.js +++ b/src/renderer/webgl/wrappers/WebGLProgramWrapper.js @@ -4,6 +4,7 @@ * @license {@link https://opensource.org/licenses/MIT|MIT License} */ +var Map = require('../../../structs/Map'); var Class = require('../../../utils/Class'); /** @@ -19,7 +20,7 @@ var Class = require('../../../utils/Class'); * @constructor * @since 3.80.0 * - * @param {WebGLRenderingContext} gl - The WebGLRenderingContext to create the WebGLProgram for. + * @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} fragmentShader - The fragment shader source code as a string. */ @@ -27,8 +28,17 @@ var WebGLProgramWrapper = new Class({ initialize: - function WebGLProgramWrapper (gl, vertexSource, fragmentSource) + 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 3.90.0 + */ + this.renderer = renderer; + /** * The WebGLProgram being wrapped by this class. * @@ -43,15 +53,6 @@ var WebGLProgramWrapper = new Class({ */ this.webGLProgram = null; - /** - * The WebGLRenderingContext that owns this WebGLProgram. - * - * @name Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper#gl - * @type {WebGLRenderingContext} - * @since 3.80.0 - */ - this.gl = gl; - /** * The vertex shader source code as a string. * @@ -92,6 +93,60 @@ var WebGLProgramWrapper = new Class({ */ 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 {{ location: WebGLAttribLocation, name: string, size: number, type: GLenum }[]} + * @since 3.90.0 + */ + this.glAttributes = []; + + /** + * Map of attribute names to their indexes in `glAttributes`. + * + * @name Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper#glAttributeNames + * @type {Map} + * @since 3.90.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 3.90.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} + * @since 3.90.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} + * @since 3.90.0 + */ + this.uniformRequests = new Map(); + this.createResource(); }, @@ -107,7 +162,12 @@ var WebGLProgramWrapper = new Class({ */ createResource: function () { - var gl = this.gl; + 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()) { @@ -118,6 +178,8 @@ var WebGLProgramWrapper = new Class({ var program = gl.createProgram(); + this.webGLProgram = program; + var vs = gl.createShader(gl.VERTEX_SHADER); var fs = gl.createShader(gl.FRAGMENT_SHADER); @@ -159,9 +221,164 @@ var WebGLProgramWrapper = new Class({ throw new Error('Link Shader failed:' + gl.getProgramInfoLog(program)); } - gl.useProgram(program); + // Extract attributes. + this.glAttributeNames.clear(); + this.glAttributes.length = 0; + var attributeCount = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); - this.webGLProgram = program; + 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 + }; + gl.enableVertexAttribArray(location); + } + + // Extract uniforms. + if (this.glUniforms.size > 0) + { + // 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.uniformSetterMap[uniform.type]; + + var initialValue = setter.int + ? new Int32Array(uniform.size * setter.size) + : new Float32Array(uniform.size * setter.size); + + 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 `uniforms.requests` map. + * These requests are processed when the program is used to draw. + * + * @method Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper#setUniform + * @since 3.90.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. + * + * This will also update the attribute and uniform state. + * + * @method Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper#bind + * @since 3.90.0 + */ + bind: function () + { + var _this = this; + var renderer = this.renderer; + var gl = renderer.gl; + + renderer.glWrapper.updateBindingsProgram({ + bindings: { program: this } + }); + + // TODO: Bind the vertex buffer to the attributes. + + // Process uniform requests. + this.uniformRequests.each(function (name, value) + { + 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 (uniformValue[i] !== value[i]) + { + different = true; + uniformValue[i] = value[i]; + } + } + if (!different) { return; } + } + else + { + if (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.uniformSetterMap[type]; + + // Set the value. + if (setter.matrix) + { + 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; + } + } + }); + + this.uniformRequests.clear(); }, /** @@ -177,7 +394,7 @@ var WebGLProgramWrapper = new Class({ return; } - var gl = this.gl; + var gl = this.renderer.gl; if (!gl.isContextLost()) { if (this._vertexShader) @@ -189,12 +406,22 @@ var WebGLProgramWrapper = new Class({ 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.gl = null; + this.renderer = null; } });