Three.js detect webgl support and fallback to regular canvas - javascript

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.

Related

Webgl context is null if 2d context is called on canvas

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).

handling WebGL loss in three js

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...

How to obtain a WebGLProgram object from the already created WebGL context?

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.

enable webGL automatically?

I am using glfx.js jquery plugin for adjusting image's hue/saturation but most of the browser not supporting WebGL
is there a way to automatically enable webgl in browser if website uses it?
I know that you need to enable it manually in Safari, and there is a plugin for IE.
Or Is there any way so we can know at the time of page load that WebGl is disabled ?
There isn't a way to automatically enable WebGL in a browser. It's in the hands of the browser vendor to enable it by default - Safari and IE are the two latecomers in that area. WebGL is coming in IE11 (source), and I would imagine the next release of Safari will have it on by default (it's been over a year since the last release).
You can however detect if WebGL is enabled.
As you're probably aware, glfx.js spits out an error message if WebGL isn't enabled. It sounds like you're trying to take some other action if that's the case (no WebGL support).
Looking at the glfx.js documentation, they provide this example:
window.onload = function() {
// try to create a WebGL canvas (will fail if WebGL isn't supported)
try {
var canvas = fx.canvas();
} catch (e) {
alert(e);
// Here is where you want to take some other action
// For example, redirecting to another page with window.location.href = ...
return;
}
}
For those reading this not using glfx.js, theeasiest check is to use !!window.WebGLRenderingContext, but that doesn't always work (some browsers throw back false positives). Your best be is to create a canvas element and check for the WebGL context:
function webGLSupport()
{
var canvas = document.createElement( 'canvas' );
var webgl = false;
try
{
webgl = !!( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) );
}
catch(e) {};
return webgl;
}

How do you save an image from a Three.js canvas?

How do you save an image from a Three.js canvas?
I'm attempting to use Canvas2Image but it doesn't like to play with Threejs. Since the canvas isn't defined until it has a div to attach the canvas object to.
http://ajaxian.com/archives/canvas2image-save-out-your-canvas-data-to-images
Since the toDataURL is a method of canvas html element, that will work for 3d context too. But you have to take care of couple of things.
Make sure when the 3D context is initialized you set preserveDrawingBuffer flag to true, like so:
var context = canvas.getContext("experimental-webgl", {preserveDrawingBuffer: true});
Then user canvas.toDataURL() to get the image
In threejs you would have to do the following when the renderer is instantiated:
new THREE.WebGLRenderer({
preserveDrawingBuffer: true
});
Also, keep in mind this can have performance implications. (Read: https://github.com/mrdoob/three.js/pull/421#issuecomment-1792008)
This is only for webgl renderer, in case of threejs canvasRenderer though, you can simply do renderer.domElement.toDataURL(); directly, no initialization parameter needed.
My webgl experiment: http://jsfiddle.net/TxcTr/3/ press 'p' to screenshot.
Props to gaitat, I just followed the link in his comment to get to this answer.
I read the conversation posted by Dinesh (https://github.com/mrdoob/three.js/pull/421#issuecomment-1792008) and came up with a solution that won't slow down your application.
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
if(getImageData == true){
imgData = renderer.domElement.toDataURL();
getImageData = false;
}
}
With this you can leave the preserveDrawingBuffer-Flag at false and still get the image from THREE.js. Simply set getImageData to true and call render() and you are good to go.
getImageData = true;
render();
console.debug(imgData);
Hope this helps people like me who need the high fps :)
use canvas to create the url, and then the same can be downloaded
function createImage(saveAsFileName) {
var canvas = document.getElementById("canvas");
var url = canvas.toDataURL();
var link = document.createElement('a');
link.setAttribute('href', url);
link.setAttribute('target', '_blank');
link.setAttribute('download', saveAsFileName);
link.click();
}

Categories