WebGL.isWebGL2Available crashes renderer

In my app I am using React with vanilla ThreeJS. Recently I made a mistake and placed the following code in the react component itself instead of placing it in the effect:

let renderTarget = undefined;
  if (WebGL.isWebGL2Available()) {
    const size = renderer.getDrawingBufferSize(new THREE.Vector2());
    renderTarget = new THREE.WebGLRenderTarget(size.width, size.height, {
      samples: 8,
    });
  }
  const composer = new EffectComposer(renderer, renderTarget);

As a result, this code was executed every time when component re-rendered, which was especially frequent when I tried to resize the window for testing.

After some time the app started temporarily crashing saying that: WebGLRenderer: Context lost., but it was also writing a lot of warnings like this: WARNING: Too many active WebGL contexts. Oldest context will be lost.. This particular warning was published when checking the availability of context here:

if (WebGL.isWebGL2Available())

Under the hood this function looks following:

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

It creates the canvas every time it is called, which doesn’t look like the most elegant thing to do considering that it crashes the renderer. So my question is, wouldn’t it be better to create the same function that takes canvas as parameter instead of creating one each time it is called?

Do you need to call isWebGLAvailable more than once?

As I explained, no - I don’t. But it doesn’t make the utility function better.

I don’t think you need to recreate rendertargets on resize. They have a setSize method you can use for resizing. But if you do want to recreate it, you can/should call .dispose() on the old one first.

document.createElement( ‘canvas’ ) is limited, you can only call it x times and the browser will crash. it’s because of the arbitrary limit that browsers set. you need to call it once globally, get the result and use that.

i wonder though, you are shooting yourself in the foot using react plain with three, instead of fiber. there is an entire eco system for three + react, it is larger than anything that exists in vanilla three. even your issue has been figured out https://github.com/pmndrs/three-stdlib/blob/89d7e63589d8e09879fd1737160b93e9f81c6389/src/misc/WebGL.ts three-stdlib is a pmndrs managed fork of three/examples/jsm with better stability and fixed anti patterns. isWebGL2Available is one of hundreds of things that will cause issues in functional programming.

but either way, the fix is

import { isWebGL2Available } from 'three-stdlib'

i would recommend you pull every jsm construct from stdlib as well.

Thank you very much. I can’t use react-fiber because I have my own AR framework integrated into a 3D viewer with seamless switch between them. React-fiber doesn’t cover my requirements there, or maybe my knowledge of it isn’t sufficient.

As for the function. As I explained, I have no intention to use this function more than once, multiple calls were bug which is already fixed. My point was related to the function itself. Even if I call it once, why do I need an extra canvas to be created? It is just waste that results in a crash if not used as intended. Not the best coding style and I am surprised to see things like that in ThreeJS.

In order to create a new instance of EffectComposer you have to pass WebGLRenderer as a parameter, which means that the renderer has to be created in advance. The renderer does have a canvas element in it. So, why can’t we check the context using the existing rendering canvas instead of creating one every time? Something like this:

static isWebGLAvailable(renderer: WebGLRenderer) {
		try {
			return !!(
        window.WebGLRenderingContext &&
        (renderer.domElement.getContext('webgl') ||
          renderer.domElement.getContext('experimental-webgl'))
      );
		} catch ( e ) {
			return false;
		}
	}

Getting a context a second time after the first one is gotten, destroys the first context.

WebGL is available on all the browsers but opera mini.

WebGL/WebGL2 is available on all the browsers ^ but opera mini and IE 11.

If you create your renderer without checking webGLAvailable first… it will throw an exception that you can catch.

try{
renderer = new THREE.WebGLRenderer()
}
catch( error ){
console.error( “WebGL is broken or unavailable:”, error)
}

Using the isWebGLAvailable shim is really not a big deal. I know it seems inefficient but it’s really just a blip.

And if you don’t use it, and instead just try to create your renderer, you can see what errors it may throw here:

1 Like

Got it! Thank you very much for the explanation.

1 Like

hey @manthrax or anybody else who might know the answer, what value is returned by the .isWebGLAvailable() method, a boolean true or false value? Where can I find information about this particular method?

also, what does the THREE.WebGL.getWebGLErrorMessage() in if (WebGL.isWebGLAvailable()) {...} else { THREE.WebGL.getWebGLErrorMessage() } return?

thanks in advance!

(im looking at WebGL.js via dev tools now, so i should be able to figure this out on my own – actually, its still not clear to me what exactly e returns in the catch block (which errors? as strings? or what?))

1 Like

It returns a bool.
The whole detector isn’t super complex:

i shouldve been more specific. in the code below, id like to know what error is or returns.

if (WebGL.isWebGLAvailable()){
  ...
} else {
  const error = THREE.WebGL.getWebGLErrorMessage()
}

i know it executes the code below.

static getWebGLErrorMessage() {

  return this.getErrorMessage( 1 );

}

but what the heck does this.getErrorMessage( 1 ) return?

never mind, found it below. it returns a div element with following innerHTML, 'Your $0 does not seem to support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation" style="color:#000">$1</a>'.

@manthrax

nonetheless, my code in the if block is quite extensive because ive put basically everything to do with WebGL inside of it, so im also wondering if i can place an if (!WebGL.isWebGLAvailable()) throw new Error('end script due to error') statement at the very top instead

thanks for ur responses <3

Yeah you could throw an exception… that’s a fine way to bail out imo.

1 Like