53

I am attempting to detect WebGL support across multiple browsers and I've encountered the following scenario. The current version of Firefox appears to report positive support using the following check, even when the visitor's video card is black-listed and/or WebGL is disabled:

if (window.WebGLRenderingContext) {
    // This is true in Firefox under certain circumstances,
    // even when WebGL is disabled...
}

I've tried instructing my users to enable WebGL using the following steps. This has worked in some cases, but not always. Obviously, this is not something I can request of the general public:

  1. Type about:config in Firefox’s address bar
  2. To enable WebGL, set webgl.force-enabled to true

This has led me to create my own method for detecting support, which uses jQuery to inject a canvas element to detect support. This pulls on a number of techniques I found in various WebGL libraries and plugins. The trouble is, it is extremely difficult to test (any comments on whether the link below works for you are much appreciated!). To make this an objective question, I would like to know if there's a universally accepted way to detect WebGL support across all browsers.

TEST URL:

http://jsfiddle.net/Jn49q/5/

Derek Hunziker
  • 12,518
  • 3
  • 53
  • 103
  • 1
    eek, I had no idea there were more ways of getting the webgl context beyond `experimental-webgl`, thanks for pointing that out. – Matt Greer Aug 08 '12 at 18:47
  • And what do you mean your method is hard to test? Are you afraid it will create false positive/negatives? It seems to me if you ask for a webgl context and don't get one, then your app can't proceed. Unless I'm missing something? – Matt Greer Aug 08 '12 at 18:48
  • @MattGreer, it's hard to test in the sense that finding a test machine with the specific combination of Firefox and a black-listed video card and/or unsupported graphics is hard to come by. Specifically, I am trying to find out under what circumstances my test method will return 'false' in the latest version Firefox. – Derek Hunziker Aug 08 '12 at 18:54
  • 1
    Ah, ok. For what it's worth I have a little webgl based website and I've had several machines fail with Firefox. It seems machines around 2009ish or so fail (sorry I don't have better data than that). I can also always get Firefox to fail by running it in Windows in VirtualBox (OSX as host), VirtualBox's 3D acceleration support is quite weak. – Matt Greer Aug 08 '12 at 19:00
  • Just detecting WebGL support does not automatically mean it will be any good. WebGL can emulated by "swiftshader" or, in case of Firefox on Linux, it can be partially or fully emulated by "mesa 3D". Since Mesa accelerates 2D very well, it may make sense to manually choose canvas even when WebGL seems available. – user185953 Dec 09 '20 at 10:57

7 Answers7

48

The excellent Three library has, in fact, a mechanism for detecting the following:

  1. WebGL support
  2. File API support
  3. Workers support

For WebGL, particularly, here is the code that is used:

function webgl_support () { 
   try {
    var canvas = document.createElement('canvas'); 
    return !!window.WebGLRenderingContext &&
      (canvas.getContext('webgl') || canvas.getContext('experimental-webgl'));
   } catch(e) {
     return false;
   }
 };

That code snippet is part of a Detector class which may also display the corresponding error messages to the user.

Balthazar
  • 33,765
  • 10
  • 77
  • 104
oabarca
  • 9,380
  • 6
  • 50
  • 65
  • 7
    nice and compact. just exactly what the author asked. i don't get it why people include the extra irrelevant code in their answers – user151496 Sep 30 '15 at 09:39
36

[Oct 2014] I've updated modernizrs example to match their current implementation, which is a cleaned up version from http://get.webgl.org/ further below.

Modernizr does,

var canvas;
var ctx;
var exts;

try {
  canvas = createElement('canvas');
  ctx = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
  exts = ctx.getSupportedExtensions();
}
catch (e) {
  return;
}

if (ctx !== undefined) {
  Modernizr.webglextensions = new Boolean(true);
}

for (var i = -1, len = exts.length; ++i < len; ){
  Modernizr.webglextensions[exts[i]] = true;
}

canvas = undefined;

Chromium points to http://get.webgl.org/ for the canonical support implementation,

try { gl = canvas.getContext("webgl"); }
catch (x) { gl = null; }

if (gl == null) {
    try { gl = canvas.getContext("experimental-webgl"); experimental = true; }
    catch (x) { gl = null; }
}
Andrew
  • 13,367
  • 12
  • 62
  • 79
  • @Cupidvogel: All [canvas-supporting browsers](http://caniuse.com/canvas), so 88.94%. You can make it 100% by wrapping it in a [try-catch statement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch). – rvighne Aug 04 '14 at 20:09
  • 2
    @Cupidvogel It doesn't work for Safari (as of version 7.0.3), which doesn't yet support `getContext("webgl")`, and will only work with `getContext("experimental-webgl")` or `getContext("webkit-3d")`. – TachyonVortex Oct 05 '14 at 11:40
20

As seen in http://www.browserleaks.com/webgl#howto-detect-webgl

This is a proper javascript function to detect WebGL support, with all kind of experimental WebGL context names and with checking of special cases, such as blocking WebGL functions by NoScript or TorBrowser.

It will report one of the three WebGL capability states:

  • WebGL is enabled — return TRUE, or return
  • WebGL object, if the first argument was passed
  • WebGL is disabled — return FALSE, you can change it if you need>
  • WebGL is not implimented — return FALSE
function webgl_detect(return_context)
{
    if (!!window.WebGLRenderingContext) {
        var canvas = document.createElement("canvas"),
             names = ["webgl2", "webgl", "experimental-webgl", "moz-webgl", "webkit-3d"],
           context = false;

        for(var i=0;i< names.length;i++) {
            try {
                context = canvas.getContext(names[i]);
                if (context && typeof context.getParameter == "function") {
                    // WebGL is enabled
                    if (return_context) {
                        // return WebGL object if the function's argument is present
                        return {name:names[i], gl:context};
                    }
                    // else, return just true
                    return true;
                }
            } catch(e) {}
        }

        // WebGL is supported, but disabled
        return false;
    }

    // WebGL not supported
    return false;
}
Nikola Lukic
  • 3,277
  • 5
  • 33
  • 57
Juan Arias
  • 658
  • 7
  • 16
  • why the double negation on window.WebGLRenderingContext? – sijpkes Jan 19 '16 at 00:12
  • I'm really not sure, I pasted the code that worked for me from the link above, but here are some explanation about that: http://stackoverflow.com/questions/784929/what-is-the-not-not-operator-in-javascript – Juan Arias Jan 21 '16 at 16:52
  • 2
    @sijpkes double negations converts truthy/falsey values to their boolean representation: `!!"" === false && !!1 === true` – WickyNilliams May 20 '16 at 11:31
  • @WickyNilliams type casting in PHP is always confusing, but this takes the cake. – sijpkes May 25 '16 at 02:59
  • `return_context` shouldn't be just `context`? Only making this change the code works for me – ibsenleo Apr 12 '17 at 16:51
  • @ibsenleo actually, `return_context` is just an optional bool parameter you send in case you need the context that was returned by the function. It is different from the `context` variable used inside `webgl_detect`. – Juan Arias Apr 12 '17 at 22:48
8

In addition to @Andrew answer, there is also experimental mode which can be supported. I have written following snippet of code:

var canvasID = 'webgl',
    canvas = document.getElementById(canvasID),
    gl,
    glExperimental = false;

function hasWebGL() {

    try { gl = canvas.getContext("webgl"); }
    catch (x) { gl = null; }

    if (gl === null) {
        try { gl = canvas.getContext("experimental-webgl"); glExperimental = true; }
        catch (x) { gl = null; }
    }

    if(gl) { return true; }
    else if ("WebGLRenderingContext" in window) { return true; } // not a best way, as you're not 100% sure, so you can change it to false
    else { return false; }
}

Change canvasID variable according to your ID.

Tested on Chrome, Safari, Firefox, Opera and IEs (8 to 10). In case of Safari remember that it's available, but you need to enable WebGL explicitly (enable the developer menu and enable Web GL option after).

Karol
  • 7,142
  • 5
  • 43
  • 65
2

In order to detect browsers that support WebGL, but leaving out older browsers with may not support it well (as needed in WebGL detected as supported when it is actually not for ruling out Android 4.4.2 devices), I am adding a tighter, though unrelated check:

function hasWebGL() {
    var supported;

    try {
        var canvas = document.createElement('canvas');
        supported = !! window.WebGLRenderingContext && (canvas.getContext('webgl') || canvas.getContext('experimental-webgl'));
    } catch(e) { supported = false; }

    try {
        // let is by no means required, but will help us rule out some old browsers/devices with potentially buggy implementations: http://caniuse.com/#feat=let
        eval('let foo = 123;');
    } catch (e) { supported = false; }

    if (supported === false) {
        console.log("WebGL is not supported");
    }

    canvas = undefined;

    return supported;
},
Community
  • 1
  • 1
Jose Gómez
  • 2,891
  • 2
  • 28
  • 52
1
// this code will detect WebGL version until WebGL Version maxVersionTest 
var
maxVersionTest = 5,
canvas = document.createElement('canvas'),
webglVersion = (canvas.getContext('webgl') || canvas.getContext('experimental-webgl')) ? 1 : null,
canvas = null; // free context

// range: if maxVersionTest = 5 makes [5, 4, 3, 2]
Array.apply(null, Array(maxVersionTest - 1))
.map(function (_, idx) {return idx + 2;})
.reverse()
.some(function(version){
    // cant reuse canvas, potential to exceed contexts or mem limit *
    if (document.createElement('canvas').getContext('webgl'+version))
        return !!(webglVersion = version);
});

console.log(webglVersion);

* re "potential to exceed contexts or mem limit" see https://bugs.chromium.org/p/chromium/issues/detail?id=226868

ekerner
  • 4,995
  • 1
  • 32
  • 28
  • Incredible solution. Can you explain little more about canvas = null; also return!! ... – Nikola Lukic Jun 25 '18 at 11:24
  • Creating the canvas is resource consuming, setting canvas = null allows the resources used in the first version test to be freed. Also there is a maximum contexts limit in Chrome at least. The canvases created in the loop are anon and will be allowed to free once out of scope. !!value simply converts value to a boolean, the expected return to Array.prototype.some, eg: !!null == false – ekerner Jun 26 '18 at 17:13
1

From MDN:

// Run everything inside window load event handler, to make sure
// DOM is fully loaded and styled before trying to manipulate it.
window.addEventListener("load", function() {
  var paragraph = document.querySelector("p"),
    button = document.querySelector("button");
  // Adding click event handler to button.
  button.addEventListener("click", detectWebGLContext, false);
  function detectWebGLContext () {
    // Create canvas element. The canvas is not added to the
    // document itself, so it is never displayed in the
    // browser window.
    var canvas = document.createElement("canvas");
    // Get WebGLRenderingContext from canvas element.
    var gl = canvas.getContext("webgl")
      || canvas.getContext("experimental-webgl");
    // Report the result.
    if (gl && gl instanceof WebGLRenderingContext) {
      paragraph.innerHTML =
        "Congratulations! Your browser supports WebGL.";
    } else {
      paragraph.innerHTML = "Failed to get WebGL context. "
        + "Your browser or device may not support WebGL.";
    }
  }
}, false);
body {
  text-align : center;
}
button {
  display : block;
  font-size : inherit;
  margin : auto;
  padding : 0.6em;
}
<p>[ Here would go the result of WebGL feature detection ]</p>
<button>Press here to detect WebGLRenderingContext</button>
Tomasz Mularczyk
  • 27,156
  • 17
  • 99
  • 146