Restore WebGL context.

All textures and shaders should automatically recover from WebGL
context loss.
Dynamic textures will lose their contents, unfortunately, as the texture
was stored on the GPU.
Frame buffers still have some bugs to work out.
This commit is contained in:
Ben Richards 2024-02-01 16:59:00 +13:00
parent 587b6e7bcd
commit a0f066c543
5 changed files with 139 additions and 17 deletions

View file

@ -24,6 +24,8 @@ var PointLightPipeline = require('./pipelines/PointLightPipeline');
var RopePipeline = require('./pipelines/RopePipeline'); var RopePipeline = require('./pipelines/RopePipeline');
var SinglePipeline = require('./pipelines/SinglePipeline'); var SinglePipeline = require('./pipelines/SinglePipeline');
var UtilityPipeline = require('./pipelines/UtilityPipeline'); var UtilityPipeline = require('./pipelines/UtilityPipeline');
var ArrayEach = require('../../utils/array/Each');
var ArrayRemove = require('../../utils/array/Remove');
/** /**
* @classdesc * @classdesc
@ -143,6 +145,14 @@ var PipelineManager = new Class({
*/ */
this.pipelines = new CustomMap(); this.pipelines = new CustomMap();
/**
* An array of all post-pipelines that are created by this manager.
*
* @name Phaser.Renderer.WebGL.PipelineManager#postPipelineInstances
* @type {Phaser.Renderer.WebGL.Pipelines.PostFXPipeline[]}
*/
this.postPipelineInstances = [];
/** /**
* The default Game Object pipeline. * The default Game Object pipeline.
* *
@ -742,10 +752,28 @@ var PipelineManager = new Class({
newPipeline.gameObject = gameObject; newPipeline.gameObject = gameObject;
} }
this.postPipelineInstances.push(newPipeline);
return newPipeline; return newPipeline;
} }
}, },
/**
* Removes a PostFXPipeline instance from this Pipeline Manager.
*
* Note that the pipeline will not be flushed or destroyed, it's simply removed from
* this manager.
*
* @method Phaser.Renderer.WebGL.PipelineManager#removePostPipeline
* @since 3.80.0
*
* @param {Phaser.Renderer.WebGL.Pipelines.PostFXPipeline} pipeline - The pipeline instance to be removed.
*/
removePostPipeline: function (pipeline)
{
ArrayRemove(this.postPipelineInstances, pipeline);
},
/** /**
* Removes a pipeline instance based on the given name. * Removes a pipeline instance based on the given name.
* *
@ -1264,6 +1292,10 @@ var PipelineManager = new Class({
{ {
pipeline.restoreContext(); pipeline.restoreContext();
}); });
ArrayEach(this.postPipelineInstances, function (pipeline)
{
pipeline.restoreContext();
});
}, },
/** /**

View file

@ -5,6 +5,7 @@
* @license {@link https://opensource.org/licenses/MIT|MIT License} * @license {@link https://opensource.org/licenses/MIT|MIT License}
*/ */
var ArrayEach = require('../../utils/array/Each');
var ArrayRemove = require('../../utils/array/Remove'); var ArrayRemove = require('../../utils/array/Remove');
var CameraEvents = require('../../cameras/2d/events'); var CameraEvents = require('../../cameras/2d/events');
var Class = require('../../utils/Class'); var Class = require('../../utils/Class');
@ -758,6 +759,69 @@ var WebGLRenderer = new Class({
canvas.addEventListener('webglcontextlost', this.contextLostHandler, false); canvas.addEventListener('webglcontextlost', this.contextLostHandler, false);
this.contextRestoredHandler = function (event)
{
if (gl.isContextLost())
{
if (console)
{
console.log('WebGL Context restored, but context is still lost');
}
return;
}
// Clear "current" settings so they can be set again.
_this.currentProgram = null;
_this.currentFramebuffer = null;
_this.setBlendMode(CONST.BlendModes.NORMAL);
// Settings we DON'T need to reset:
// Scissor is set during preRender.
// Mask is set during preRender.
// Camera mask is set during preRenderCamera.
// Restore GL flags.
gl.disable(gl.BLEND);
gl.disable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
// Restore wrapped GL objects.
// Order matters, as some wrappers depend on others.
var wrapperCreateResource = function (wrapper)
{
wrapper.createResource();
};
ArrayEach(_this.glTextureWrappers, wrapperCreateResource);
ArrayEach(_this.glBufferWrappers, wrapperCreateResource);
ArrayEach(_this.glFramebufferWrappers, wrapperCreateResource);
ArrayEach(_this.glProgramWrappers, wrapperCreateResource);
ArrayEach(_this.glAttribLocationWrappers, wrapperCreateResource);
ArrayEach(_this.glUniformLocationWrappers, wrapperCreateResource);
// Create temporary textures.
_this.createTemporaryTextures();
// Restore pipelines.
// _this.pipelines.rebind();
_this.pipelines.restoreContext();
// Apply resize.
_this.resize(_this.width, _this.height);
// Context has been restored.
_this.contextLost = false;
if (console)
{
console.warn('WebGL Context restored. Renderer running again.');
}
event.preventDefault();
};
canvas.addEventListener('webglcontextrestored', this.contextRestoredHandler, false);
// Set it back into the Game, so developers can access it from there too // Set it back into the Game, so developers can access it from there too
game.context = gl; game.context = gl;
@ -855,19 +919,7 @@ var WebGLRenderer = new Class({
this.textureIndexes = []; this.textureIndexes = [];
// Create temporary WebGL textures to stop WebGL errors on mac os this.createTemporaryTextures();
for (var index = 0; index < this.maxTextures; index++)
{
var tempTexture = gl.createTexture();
gl.activeTexture(gl.TEXTURE0 + index);
gl.bindTexture(gl.TEXTURE_2D, tempTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([ 0, 0, 255, 255 ]));
this.textureIndexes.push(index);
}
this.pipelines = new PipelineManager(this); this.pipelines = new PipelineManager(this);
@ -928,6 +980,27 @@ var WebGLRenderer = new Class({
this.resize(width, height); this.resize(width, height);
}, },
/**
* Create temporary WebGL textures to stop WebGL errors on mac os
*/
createTemporaryTextures: function ()
{
var gl = this.gl;
for (var index = 0; index < this.maxTextures; index++)
{
var tempTexture = gl.createTexture();
gl.activeTexture(gl.TEXTURE0 + index);
gl.bindTexture(gl.TEXTURE_2D, tempTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([ 0, 0, 255, 255 ]));
this.textureIndexes.push(index);
}
},
/** /**
* This method is only available in the Debug Build of Phaser, or a build with the * This method is only available in the Debug Build of Phaser, or a build with the
* `WEBGL_DEBUG` flag set in the Webpack Config. * `WEBGL_DEBUG` flag set in the Webpack Config.

View file

@ -297,6 +297,7 @@ var WebGLShader = new Class({
if (reset === undefined) { reset = false; } if (reset === undefined) { reset = false; }
var gl = this.gl; var gl = this.gl;
var renderer = this.renderer;
var vertexSize = this.vertexSize; var vertexSize = this.vertexSize;
var attributes = this.attributes; var attributes = this.attributes;
var program = this.program; var program = this.program;
@ -314,6 +315,10 @@ var WebGLShader = new Class({
if (reset) if (reset)
{ {
if (location !== -1)
{
renderer.deleteAttribLocation(location);
}
var attribLocation = this.renderer.createAttribLocation(program, element.name); var attribLocation = this.renderer.createAttribLocation(program, element.name);
if (attribLocation.webGLAttribLocation >= 0) if (attribLocation.webGLAttribLocation >= 0)
@ -447,10 +452,18 @@ var WebGLShader = new Class({
*/ */
syncUniforms: function () syncUniforms: function ()
{ {
ArrayEach(this.uniforms, function (uniform) var gl = this.gl;
this.renderer.setProgram(this.program);
for (var name in this.uniforms)
{ {
uniform.setter.call(this.gl, uniform.location.webGLUniformLocation, uniform.value1, uniform.value2, uniform.value3, uniform.value4); var uniform = this.uniforms[name];
});
// A uniform that hasn't been set doesn't need to be synced.
if (uniform.setter)
{
uniform.setter.call(gl, uniform.location.webGLUniformLocation, uniform.value1, uniform.value2, uniform.value3, uniform.value4);
}
}
}, },
/** /**

View file

@ -637,6 +637,8 @@ var PostFXPipeline = new Class({
this.halfFrame1 = null; this.halfFrame1 = null;
this.halfFrame2 = null; this.halfFrame2 = null;
this.manager.removePostPipeline(this);
WebGLPipeline.prototype.destroy.call(this); WebGLPipeline.prototype.destroy.call(this);
return this; return this;

View file

@ -9,10 +9,12 @@
*/ */
var Wrappers = { var Wrappers = {
WebGLAttribLocationWrapper: require('./WebGLAttribLocationWrapper'),
WebGLBufferWrapper: require('./WebGLBufferWrapper'), WebGLBufferWrapper: require('./WebGLBufferWrapper'),
WebGLProgramWrapper: require('./WebGLProgramWrapper'), WebGLProgramWrapper: require('./WebGLProgramWrapper'),
WebGLTextureWrapper: require('./WebGLTextureWrapper'), WebGLTextureWrapper: require('./WebGLTextureWrapper'),
WebGLFramebufferWrapper: require('./WebGLFramebufferWrapper') WebGLFramebufferWrapper: require('./WebGLFramebufferWrapper'),
WebGLUniformLocationWrapper: require('./WebGLUniformLocationWrapper')
}; };
module.exports = Wrappers; module.exports = Wrappers;