{"id":1602,"date":"2017-02-08T20:42:49","date_gmt":"2017-02-09T01:42:49","guid":{"rendered":"https:\/\/rpchurchill.com\/?p=1602"},"modified":"2017-02-17T02:07:08","modified_gmt":"2017-02-17T07:07:08","slug":"webgl-is-supported-by-most-browsers-most-of-the-time","status":"publish","type":"post","link":"https:\/\/rpchurchill.com\/wordpress\/posts\/2017\/02\/08\/webgl-is-supported-by-most-browsers-most-of-the-time\/","title":{"rendered":"WebGL Is Supported By Most Browsers &#8212; Most of the Time"},"content":{"rendered":"<p>Working through yesterday&#8217;s post I found it necessary to test the code in multiple browsers and, when it failed, to use the debuggers to figure out where.  The different examples I&#8217;ve put together for this site and for last week&#8217;s presentation all run and ran fine on Firefox, which is where I do most of my work.  Everything worked in IE and Edge as well.  Nothing worked in Chrome or Opera, even though everything worked in both last week.<\/p>\n<p>That&#8217;s right &#8212; now you see it, now you don&#8217;t.<\/p>\n<p>Exactly why things do or do not work is dependent on two major things: details of the latest browser version (particularly in how it leverages any GPU in the system for hardware acceleration) and the version of the WebGL and Three.js libraries themselves.  As of today the failures always occur when trying to create the renderer object.<\/p>\n<pre class=\"toolbar-overlay:false wrap:false height-set:true lang:default decode:true \">\r\n        aspectRatio = window.innerWidth\/window.innerHeight;\r\n        camera = new THREE.PerspectiveCamera(75, aspectRatio, 0.1, 1000);\r\n\r\n        renderer = new THREE.WebGLRenderer();  \/\/fails here\r\n        renderer.setSize(window.innerWidth, window.innerHeight);\r\n        \r\n        canvas = renderer.domElement;\r\n        document.body.appendChild(canvas);  \/\/can also be appended to a DIV\r\n\r\n        scene = new THREE.Scene();\r\n<\/pre>\n<p>Within the Three.js code the code for creating the renderer object is here, and the variable <code>_gl<\/code> ends up with a value of null and throws an exception.<\/p>\n<pre class=\"toolbar-overlay:false wrap:false height-set:true lang:default decode:true \">\r\n\t\t\/\/ initialize  \/\/this is line 19672 in Three.js unminified\r\n\r\n\t\tvar _gl;\r\n\r\n\t\ttry {\r\n\r\n\t\t\tvar attributes = {\r\n\t\t\t\talpha: _alpha,\r\n\t\t\t\tdepth: _depth,\r\n\t\t\t\tstencil: _stencil,\r\n\t\t\t\tantialias: _antialias,\r\n\t\t\t\tpremultipliedAlpha: _premultipliedAlpha,\r\n\t\t\t\tpreserveDrawingBuffer: _preserveDrawingBuffer\r\n\t\t\t};\r\n\r\n\t\t\t_gl = _context || _canvas.getContext( 'webgl', attributes ) || _canvas.getContext( 'experimental-webgl', attributes );\r\n\r\n\t\t\tif ( _gl === null ) {\r\n\r\n\t\t\t\tif ( _canvas.getContext( 'webgl' ) !== null ) {\r\n\r\n\t\t\t\t\tthrow 'Error creating WebGL context with your selected attributes.';\r\n\r\n\t\t\t\t} else {\r\n\r\n\t\t\t\t\tthrow 'Error creating WebGL context.';\r\n\r\n\t\t\t\t}\r\n\r\n\t\t\t}\r\n\r\n\t\t\t\/\/ Some experimental-webgl implementations do not have getShaderPrecisionFormat\r\n\r\n\t\t\tif ( _gl.getShaderPrecisionFormat === undefined ) {\r\n\r\n\t\t\t\t_gl.getShaderPrecisionFormat = function () {\r\n\r\n\t\t\t\t\treturn { 'rangeMin': 1, 'rangeMax': 1, 'precision': 1 };\r\n\r\n\t\t\t\t};\r\n\r\n\t\t\t}\r\n\r\n\t\t\t_canvas.addEventListener( 'webglcontextlost', onContextLost, false );\r\n\r\n\t\t} catch ( error ) {\r\n\r\n\t\t\tconsole.error( 'THREE.WebGLRenderer: ' + error );\r\n\r\n\t\t}\r\n<\/pre>\n<p>This happens regardless of which (current) version of Three.js I use, with some possibilities shown below:<\/p>\n<pre class=\"toolbar-overlay:false wrap:false height-set:true lang:default decode:true \">\r\n\t\t<!--script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/three.js\/r82\/three.min.js\"><\/script-->\r\n\t\t<!--script src=\"js\/three.min.js\"><\/script-->\r\n\t\t<script src=\"js\/three.js\"><\/script>\r\n<\/pre>\n<p>What&#8217;s really mystifying is that this worked in Chrome sometimes and not other times.  It never works in Opera, but did last week when I was giving the presentation.<\/p>\n<p>There are a couple of ways to test to see whether your browser supports WebGL.  On is to go to this site:<\/p>\n<blockquote><p><a href=\"https:\/\/get.webgl.org\">https:\/\/get.webgl.org<\/a><\/p><\/blockquote>\n<p>It tries to initialize a simple animation of a rotating cube.  If you don&#8217;t see the cube then WebGL isn&#8217;t working on your browser on your system.  The page gives you a link you can use to get more information about your browser but it only leads to a dead end when you do this in Opera.<\/p>\n<p>There&#8217;s also a <a href=\"http:\/\/stackoverflow.com\/questions\/16504431\/detect-webgl-support-should-we-use-detector-js-or-system-min-js\">page<\/a> that tells you how to include an upfront test in your own code that allows you to provide a message that WebGL has failed.  I actually used the Detector.js version I found <a href=\"https:\/\/github.com\/mrdoob\/three.js\/blob\/master\/examples\/js\/Detector.js\">here<\/a>, but any of the tests should work.<\/p>\n<p>If you run into problems one of the other important things you can do is check the status of your display drivers.  When I examined my graphics drivers on Windows 10 64-bit I found an error and went ahead an deleted and re-installed the driver.  Surprise of surprises, WebGL and Three.js now work in all browsers on Windows.  I know it has also worked reliably on iOS.<\/p>\n<p>Interestingly, however, and I remember this from testing on my 3rd gen iPad a while back, the default background color is actually white in 3D scenes, unless you explicitly set it to something else, like in this example:<\/p>\n<pre class=\"toolbar-overlay:false wrap:false height-set:true lang:default decode:true \">\r\n      renderer.setClearColor(\"#000000\",1);\r\n<\/pre>\n<p>Obviously this has to happen after the 3D renderer is successfully initialized.<\/p>\n<p>Here&#8217;s the test code I&#8217;ve been using, which is a slight rearrangement of one of the examples I included in my presentation:<\/p>\n<pre class=\"toolbar-overlay:false wrap:false height-set:true lang:default decode:true \">\r\n<html>\r\n  <head>\r\n    <title>Handle Resize<\/title>\r\n    <style>\r\n      body { margin: 0; }\r\n      canvas { width: 100%; height: 100% }\r\n    <\/style>\r\n  <\/head>\r\n  <body onload=\"init()\">\r\n    <!--script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/three.js\/r82\/three.min.js\"><\/script-->\r\n    <!--script src=\"js\/three.min.js\"><\/script-->\r\n    <script src=\"js\/three.js\"><\/script>\r\n    <script src=\"js\/Detector.js\"><\/script>\r\n    <script>\r\n      var success = true;\r\n      if (!Detector.webgl) {\r\n        var warning = Detector.getWebGLErrorMessage();\r\n        document.body.appendChild(warning);\r\n        success = false;\r\n      }\r\n\r\n      \/\/basic initialization\r\n      var aspectRatio;\r\n      var camera;\r\n      var renderer;\r\n      var canvas;\r\n      var scene;\r\n\r\n      function init() {\r\n        if (success) {\r\n          init3D();\r\n          grid();\r\n          addCubes();\r\n          render();\r\n        }\r\n      }\r\n      \r\n      \/\/set up 3D\r\n      function init3D() {\r\n        aspectRatio = window.innerWidth\/window.innerHeight;\r\n        camera = new THREE.PerspectiveCamera(75, aspectRatio, 0.1, 1000);\r\n\r\n        renderer = new THREE.WebGLRenderer({antialias: true, alpha: true});\r\n        renderer.setSize(window.innerWidth, window.innerHeight);\r\n        \r\n        canvas = renderer.domElement;\r\n        document.body.appendChild(canvas);  \/\/can also be appended to a DIV\r\n\r\n        scene = new THREE.Scene();\r\n        renderer.setClearColor(\"#000000\",1);\r\n      }\r\n      \r\n      var global3DBaseX = 0;\r\n      var global3DBaseY = 0;\r\n      var global3DBaseZ = 0;\r\n      var global3DLookTarget = new THREE.Vector3(global3DBaseX, global3DBaseY, global3DBaseZ);\r\n\r\n      \/\/define a line in 3D\r\n      function define3DLine(x1,y1,z1,x2,y2,z2,lineColor) {\r\n        var material = new THREE.LineBasicMaterial({ color: lineColor });\r\n        var geometry = new THREE.Geometry();\r\n        geometry.vertices.push(\r\n          new THREE.Vector3(x1, y1, z1),\r\n          new THREE.Vector3(x2, y2, z2)\r\n        );\r\n        var line = new THREE.Line(geometry, material);\r\n        scene.add(line);\r\n        return line;\r\n      }\r\n\r\n      function grid() {\r\n        \/\/build a grid      \r\n        var xMin = -3;\r\n        var xMax = 3;\r\n        var xInc = 0.5;\r\n        var zMin = -3;\r\n        var zMax = 3;\r\n        var zInc = 0.5;\r\n        var gridLineColor;\r\n\r\n        for (var z=zMin; z<=zMax; z+=zInc) {\r\n          if (z != 0) {\r\n            gridLineColor = \"#0000FF\";\r\n          } else {\r\n            gridLineColor = \"#FF0000\";\r\n          }\r\n          define3DLine(xMin,0,z,xMax,0,z,gridLineColor);\r\n        }\r\n\r\n        for (var x=xMin; x<=xMax; x+=xInc) {\r\n          if (x != 0) {\r\n            gridLineColor = \"#0000FF\";\r\n          } else {\r\n            gridLineColor = \"#FF0000\";\r\n          }\r\n          define3DLine(x,0,zMin,x,0,zMax,gridLineColor);\r\n        }\r\n      }\r\n      \r\n      var cube1;\r\n      var cube2;\r\n      var cube3;\r\n      \r\n      function addCubes() {\r\n        \/\/add a green cube at 0, 0, 0      \r\n        var geometry = new THREE.BoxGeometry(1, 1, 1);\r\n\r\n        var material1 = new THREE.MeshBasicMaterial({ wireframe: true, color: 0x00ff00 });\r\n\r\n        cube1 = new THREE.Mesh( geometry, material1 );\r\n\r\n        scene.add(cube1);\r\n\r\n        \/\/add a yellow cube at 2, 0, -2      \r\n        var material2 = new THREE.MeshBasicMaterial({ wireframe: true, color: 0xffff00 });\r\n\r\n        cube2 = new THREE.Mesh( geometry, material2 );\r\n\r\n        cube2.position.set(2, 0, -2);\r\n\r\n        scene.add(cube2);\r\n\r\n        \/\/add a cyan cube at -2, 2, 2      \r\n        var material3 = new THREE.MeshBasicMaterial({ wireframe: true, color: 0x00ffff });\r\n\r\n        cube3 = new THREE.Mesh( geometry, material3 );\r\n\r\n        cube3.position.set(-2, 2, 2);\r\n\r\n        scene.add(cube3);\r\n      }\r\n\r\n      \/\/initialization for actions\r\n      var twoPi = Math.PI * 2.0;\r\n      var halfPi = Math.PI * 0.5;\r\n      var cRadius = 5; \/\/5;\r\n      var cHeight = 4;\r\n      var cInc = 0.005;\r\n      var cAngle = (1.5 * Math.PI) - cInc;\r\n\r\n      \/\/do this for every update\r\n      function cylRotations() {\r\n        cAngle += cInc;\r\n        if (cAngle > twoPi) {\r\n          cAngle -= twoPi;\r\n        } else if (cAngle < 0.0) {\r\n          cAngle += twoPi;\r\n        }\r\n        \r\n        var x = cRadius * Math.cos(cAngle);\r\n        var z = -cRadius * Math.sin(cAngle);\r\n        var y = cHeight;\r\n\r\n        \/\/update location of viewport camera\r\n        camera.position.set(x,y,z);\r\n        camera.lookAt(global3DLookTarget);\r\n      }\r\n\r\n      var cYIncrement = 0.1;\r\n      var cYMax = 10;\r\n      var cYMin = -2;\r\n      var cRIncrement = 0.1;\r\n      var cRMax = 20;\r\n      var cRMin = 2;\r\n\r\n      function moveUp() {\r\n        cHeight += cYIncrement;\r\n        if (cHeight > cYMax) {\r\n          cHeight = cYMax;\r\n        }\r\n      }\r\n\r\n      function moveDown() {\r\n        cHeight -= cYIncrement;\r\n        if (cHeight < cYMin) {\r\n          cHeight = cYMin;\r\n        }\r\n      }\r\n\r\n      function moveIn() {\r\n        cRadius -= cRIncrement;\r\n        if (cRadius < cRMin) {\r\n          cRadius = cRMin;\r\n        }\r\n      }\r\n\r\n      function moveOut() {\r\n        cRadius += cRIncrement;\r\n        if (cRadius > cRMax) {\r\n          cRadius = cRMax;\r\n        }\r\n      }\r\n\r\n      function restoreDefaults() {\r\n        cHeight = 4;\r\n        cRadius = 5;\r\n      }\r\n\r\n      document.onkeydown = checkKey;\r\n\r\n      function checkKey(e) {\r\n        e = e || window.event;  \/\/up 38 down 40\r\n        if (e.keyCode == '37') { \/\/left arrow\r\n        } else if (e.keyCode == '39') { \/\/right arrow\r\n        } else if (e.keyCode == '38') { \/\/up arrow\r\n          moveUp();\r\n        } else if (e.keyCode == '40') { \/\/down arrow\r\n          moveDown();\r\n        } else if (e.keyCode == '66') { \/\/b key\r\n          moveOut();\r\n        } else if (e.keyCode == '70') { \/\/f key\r\n          moveIn();\r\n        } else if (e.keyCode == '36') { \/\/home key\r\n          restoreDefaults();\r\n        }\r\n      }\r\n\r\n      \/\/from: http:\/\/stackoverflow.com\/questions\/641857\/javascript-window-resize-event\r\n      \/*\r\n          function: addEvent\r\n\r\n          @param: obj         (Object)(Required)\r\n            - The object which you wish to attach your event to.\r\n\r\n          @param: type        (String)(Required)\r\n            - The type of event you wish to establish.\r\n\r\n          @param: callback    (Function)(Required)\r\n            - The method you wish to be called by your event listener.\r\n\r\n          @param: eventReturn (Boolean)(Optional)\r\n            - Whether you want the event object returned to your callback method.\r\n      *\/\r\n      var addEvent = function(obj, type, callback, eventReturn) {\r\n        if(obj == null || typeof obj === 'undefined')\r\n          return;\r\n\r\n        if(obj.addEventListener)\r\n          obj.addEventListener(type, callback, eventReturn ? true : false);\r\n        else if(obj.attachEvent)\r\n          obj.attachEvent(\"on\" + type, callback);\r\n        else\r\n          obj[\"on\" + type] = callback;\r\n      };\r\n\r\n      \/\/An example call to the new addEvent function:\r\n\r\n      var watch = function(evt) {\r\n        \/\/Older browser versions may return evt.srcElement\r\n        \/\/Newer browser versions should return evt.currentTarget\r\n        var dimensions = {\r\n          height: (evt.srcElement || evt.currentTarget).innerHeight,\r\n          width: (evt.srcElement || evt.currentTarget).innerWidth\r\n        };\r\n        \/\/https:\/\/threejs.org\/docs\/api\/cameras\/PerspectiveCamera.html\r\n        renderer.setSize(dimensions.width, dimensions.height);\r\n        camera.aspect = dimensions.width \/ dimensions.height;\r\n        \/\/var newFov = calcFovAngle(dimensions.height);\r\n        \/\/camera.fov = newFov;\r\n        camera.updateProjectionMatrix();\r\n      };\r\n\r\n      addEvent(window, 'resize', watch, true);\r\n\r\n      \/\/animation loop      \r\n      function render() {\r\n        \/\/each cube rotates around a different axis\r\n        cube1.rotation.x += 0.01;\r\n        cube2.rotation.y += 0.01;\r\n        cube3.rotation.z += 0.01;\r\n        \r\n        cylRotations();\r\n        \r\n        renderer.render(scene, camera);\r\n                \r\n        requestAnimationFrame(render);\r\n      }\r\n\r\n    <\/script>\r\n  <\/body>\r\n<\/html>\r\n<\/pre>\n<p>A direct link to this is <a href=\"http:\/\/www.rpchurchill.com\/demo\/des\/handle_resize_20170207\">here<\/a>.  Don&#8217;t forget to grab a copy of Detector.js if you want to test this on your own system.<\/p>\n<p>Bottom line: don&#8217;t be afraid to dig into any of this stuff.  This technology is proven and should be fairly stable.  <strong>If it doesn&#8217;t work it&#8217;s very likely that something in your system is out of whack.<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Working through yesterday&#8217;s post I found it necessary to test the code in multiple browsers and, when it failed, to use the debuggers to figure out where. The different examples I&#8217;ve put together for this site and for last week&#8217;s &hellip; <a href=\"https:\/\/rpchurchill.com\/wordpress\/posts\/2017\/02\/08\/webgl-is-supported-by-most-browsers-most-of-the-time\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[133,134],"_links":{"self":[{"href":"https:\/\/rpchurchill.com\/wordpress\/wp-json\/wp\/v2\/posts\/1602"}],"collection":[{"href":"https:\/\/rpchurchill.com\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rpchurchill.com\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rpchurchill.com\/wordpress\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/rpchurchill.com\/wordpress\/wp-json\/wp\/v2\/comments?post=1602"}],"version-history":[{"count":2,"href":"https:\/\/rpchurchill.com\/wordpress\/wp-json\/wp\/v2\/posts\/1602\/revisions"}],"predecessor-version":[{"id":1604,"href":"https:\/\/rpchurchill.com\/wordpress\/wp-json\/wp\/v2\/posts\/1602\/revisions\/1604"}],"wp:attachment":[{"href":"https:\/\/rpchurchill.com\/wordpress\/wp-json\/wp\/v2\/media?parent=1602"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rpchurchill.com\/wordpress\/wp-json\/wp\/v2\/categories?post=1602"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rpchurchill.com\/wordpress\/wp-json\/wp\/v2\/tags?post=1602"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}