I can't figure out what to do in case of webgl loss in my application (written with electron js) with three js. We have these two functions
// renderer is THREE.WebGLRenderer
renderer.context.canvas.addEventListener("webglcontextlost", contextLostFunction);
renderer.context.canvas.addEventListener("webglcontextrestored", contextRestoredFunction);
When I simulate context loss using something like this
var canvas = document.getElementById("playground").childNodes[0].childNodes[0];
var gl = canvas.getContext("webgl");
var WEBGL_lose_context = gl.getExtension('WEBGL_lose_context');
WEBGL_lose_context.loseContext();
Then webglcontextrestored event fires and everything restores as should be.
When webgl is killed for real or using something like this
renderer.context.getExtension( 'WEBGL_lose_context' ).loseContext();
Then this event webglcontextrestored never has been fired.
What is going ? What to do to catch that context has been lost.
Thanks for any ideas.
You should use same extension reference you use to loose the context to restore the context with the restoreContext() method of the object:
var canvas = document.getElementById("playground").childNodes[0].childNodes[0];
var gl = canvas.getContext("webgl");
var WEBGL_lose_context = gl.getExtension('WEBGL_lose_context');
WEBGL_lose_context.loseContext();
window.setTimeout(()=> {
WEBGL_lose_context.restoreContext();
}, 2000);
you can also do it from the inspector to simulate it in an iterative way...
Related
I wrote the following JS code:
var main=function() {
var CANVAS=document.getElementById("your_canvas");
CANVAS.width=window.innerWidth;
CANVAS.height=window.innerHeight;
//ctx=CANVAS.getContext("2d");
//ctx.fillText("Hello World",10,50);
/*========================= GET WEBGL CONTEXT ========================= */
var GL;
try {
GL = CANVAS.getContext("experimental-webgl", {antialias: true});
} catch (e) {
alert("You are not webgl compatible :(") ;
return false;
}
var CUBE_VERTEX= GL.createBuffer ();
};
If I uncomment the two commented lines, then the webgl context is NULL. Is this expected ? It's not possible to use 2D context and webgl context on the same canvas ?
This is expected, as webgl basically is a wrapper around opengl, primarily used for accelerated 3D rendering. getContext("experimental-webgl") is essentially telling the browser, that the defined canvas is to be used exclusively by OpenGL/WebGL. (It is possible to use OpenGL/WebGL for 2D, but unless you know what you are doing, you're giving yourself a hard time in doing so.)
If you are trying to render text on top of a 3D view, you will have to overlay two different DOM-elements (or render the text with the correct projection in 3D, again hard time-territory).
I wonder, how can I obtain any WebGL program instance (WebGLProgram) from any desired WebGL context?
To fetch the WebGL context is NOT a problem. You are searching the DOM of the current page for the canvas element using document.getElementsByTagName() or document.getElementById(), if you know the exact canvas id:
let canvas = document.getElementById( "canvasId" );
let context = canvas.getContext( "webgl" );
Here we fetch the current context as I suppose, but if I want to get some shader parameters or get certain value from already running vertex/fragment shader - I need to have a WebGL program, which is associated with the current WebGL rendering context.
But I can't find any method in WebGL API like context.getAttachedProgram() or context.getActiveProgram().
So what is the way get the active WebGL program which is used for the rendering process?
Maybe, there is some special WebGL parameter?
There is no way to get all the programs or any other resources from a WebGL context. If the context is already existing the best you can do is look at the current resources with things like gl.getParameter(gl.CURRENT_PROGRAM) etc..
What you can do instead is wrap the WebGL context
var allPrograms = [];
someContext.createProgram = (function(oldFunc) {
return function() {
// call the real createProgram
var prg = oldFunc.apply(this, arguments);
// if a program was created save it
if (prg) {
allPrograms.push(prg);
}
return prg;
};
}(someContext.createProgram));
Of course you'd need to wrap gl.deleteProgram as well to remove things from the array of all programs.
someContext.deleteProgram = (function(oldFunc) {
return function(prg) {
// call the real deleteProgram
oldFunc.apply(this, arguments);
// remove the program from allPrograms
var ndx = allPrograms.indexOf(prg);
if (ndx >= 0) {
allPrograms.splice(ndx, 1);
}
};
}(someContext.deleteProgram));
These are the techniques used by things like the WebGL Inspector and the WebGL Shader Editor Extension.
If you want to wrap all contexts you can use a similar technique to wrap getContext.
HTMLCanvasElement.prototype.getContext = (function(oldFunc) {
return function(type) {
var ctx = oldFunc.apply(this, arguments);
if (ctx && (type === "webgl" || type === "experimental-webgl")) {
ctx = wrapTheContext(ctx);
}
return ctx;
};
}(HTMLCanvasElement.prototype.getContext));
gl.getParameter(gl.CURRENT_PROGRAM). Check out https://www.khronos.org/files/webgl/webgl-reference-card-1_0.pdf pg 2 to the right.
I need to show two animations using WebGL on the page. Do I need to instantiate multiple shaders, or is there a way to reuse one shader? They are using the same program (so not entirely different animations). They both need to react to mouse events.
something like this
window.onload = function() {
main('canvas1');
main('canvas2');
}
function main(element) {
// Get A WebGL context
var canvas = document.getElementById(element);
var gl = getWebGLContext(canvas);
if (!gl) {
return;
}
// setup GLSL program
vertexShader = createShaderFromScriptElement(gl, "2d-vertex-shader");
fragmentShader = createShaderFromScriptElement(gl, "2d-fragment-shader");
program = createProgram(gl, [vertexShader, fragmentShader]);
gl.useProgram(program);
...
}
I don't understand the derogaratory remarks. I asked a theoretical question about webgl, not particular code.
I ran this code and it works. So now all I need to know is if I can use mouse events on both. I'd be surprised if that didn't work.
This was already answered by someone else:
Is it possible to have two WebGL contexts on the same page?
WebGL cannot share resources over multiple contexts. But you can create an object with its own gl variable, and then have multiple contexts on the page. The only limit I see now is that somehow it's intensive for the system and it won't allow more than 16 concurrent contexts.
Requirement:
Now: Draw on a Canvas, and hit Save (store Canvas state/drawing offline - but NOT as image).
Later: Open up the Canvas with previously saved drawing showing, and continue to draw again.
For drawing we normally use code as follows:
canvas = document.getElementById('can');
ctx = canvas.getContext("2d");
...
ctx.beginPath();
ctx.moveTo(prevX, prevY);
ctx.lineTo(currX, currY);
....
In order to restore Canvas state later - exporting to Image does not help.
I want to restore the Canvas to it's original state to continue editing the drawing at a later date.
I guess, the Canvas context has to be exported and stored offline - how?
Your best shot here is to use a Proxy that will both store the draw commands and perform the drawings.
Since the browser support for Proxy is very bad (only FF as of today), you'll have to build the Proxy yourself, either by using nosuchmethod, or by building a new brand new WatchedContext Class out of the Context2D.
I took the last solution (WatchedContext Class) for this short demo :
function WatchedContext(hostedCtx) {
this.commands= [];
Context2dPrototype = CanvasRenderingContext2D.prototype;
for (var p in Context2dPrototype ) {
this[p] = function(methodName) {
return function() {
this.commands.push(methodName, arguments);
return Context2dPrototype[methodName].apply(hostedCtx, arguments);
}
}(p);
}
this.replay=function() {
for (var i=0; i<this.commands.length; i+=2) {
var com = this.commands[i];
var args = this.commands[i+1];
Context2dPrototype[com].apply(hostedCtx, args);
}
}
}
Obviously you might need some other method (start/stop recording, clear, ...)
Just a small example of use :
var cv = document.getElementById('cv');
var ctx=cv.getContext('2d');
var watchedContext=new WatchedContext(ctx);
// do some drawings on the watched context
// --> they are performed also on the real context
watchedContext.beginPath();
watchedContext.moveTo(10, 10);
watchedContext.lineTo(100, 100);
watchedContext.stroke();
// clear context (not using the watched context to avoid recording)
ctx.clearRect(0,0,100,1000);
// replay what was recorded
watchedContext.replay();
You can see here :
http://jsbin.com/gehixavebe/2/edit?js,output
That the replay does work, and the line is re-drawn as a result of replaying the stored commands.
For storing offline you can either store the commands locally using localStorage or store them remotely on a server an use AJAX calls or similar.
Can anyone who has used three.js tell me if its possible to detect webgl support, and, if not present, fallback to a standard Canvas render?
Yes, it's possible. You can use CanvasRenderer instead of WebGLRenderer.
About WebGL detection:
1) Read this WebGL wiki article: http://www.khronos.org/webgl/wiki/FAQ
if (!window.WebGLRenderingContext) {
// the browser doesn't even know what WebGL is
window.location = "http://get.webgl.org";
} else {
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("webgl");
if (!context) {
// browser supports WebGL but initialization failed.
window.location = "http://get.webgl.org/troubleshooting";
}
}
2) Three.js already has a WebGL detector:
https://github.com/mrdoob/three.js/blob/master/examples/js/Detector.js
renderer = Detector.webgl? new THREE.WebGLRenderer(): new THREE.CanvasRenderer();
3) Check also the Modernizr detector:
https://github.com/Modernizr/Modernizr/blob/master/feature-detects/webgl.js
Juan Mellado's pointer to the Three.js detector was super useful, but I prefer not to bring the whole file into my project. So here is the extracted Detector.webgl() function.
function webglAvailable() {
try {
var canvas = document.createElement("canvas");
return !!
window.WebGLRenderingContext &&
(canvas.getContext("webgl") ||
canvas.getContext("experimental-webgl"));
} catch(e) {
return false;
}
}
And it is used similar to his example:
renderer = webglAvailable() ? new THREE.WebGLRenderer() : new THREE.CanvasRenderer();
Unfortunatelly, just detecting WebGL support does not automatically mean it will be any good. WebGL can be backed by software renderer like "google swiftshader" or partial emulation like "mesa 3D". Especially with a good 2D renderer like Mesa 2D it makes sense to manually choose canvas even when WebGL seems available.