Are there any best practices for scaling a THREEjs / WebGL page so they run well on a variety of different devices? On a higher end desktop, for example, you’d be able to use more intensive shaders and screen effects while you’d want to turn those off for laptops or turn it down even more for mobile devices. I’ve seen some websites give the option to toggle features or select premade “low”, “medium”, or “high” settings but it would seem best if you can get rid of that step.
WebGLRenderer.capabilities is an obvious one that can be used for hard cutoffs like attribute count and texture sizes. And I haven’t tried it but tuning effects and buffer resolution based on framerate seems do-able, as well.
I’ve also considered using the
UNMASKED_RENDERER_WEBGL debug gl parametrs to get a general idea of the “power” of the current GPU, but that’s pretty inconsistent and not super reliable across devices.
Have you guys used any other approaches?
Why? Offering a choice of quality settings makes it much easier to define different levels of rendering complexity. Besides, many users are familiar with this approach from PC games.
What might be intersting for you is a feature of
Babylon.js. According to WebGL Insights, there is a concept called
EffectFallbacks that allows the automatic simplification of a shader program if low-end devices are unable to compile it. Developers can define priorities (ranks) for certain parts of (optional) shader code. The code blocks are gradually removed until a compilation of the shader is possible. I’m not sure about this but it seems the code blocks can also be removed if the performance of the application is bad. Unfortunately, I’ve never worked with
EffectFallbacks so I’m unable to tell how good this approach works.
Why? Offering a choice of quality settings makes it much easier to define different levels of rendering complexity.
It’s definitely an easier approach developmentally but I feel like there’s an expectation of simplicity when you’re dealing with a browser and mobile, especially when you might only be on a website for a few minutes. Recently the web has been built on being automatically responsive to a platform so I was hoping there were simpler ways to extend that to WebGL, as well (though admittedly it’s a bit different). Websites don’t ask what browser you’re using or if you’re on a phone, for example. They scale and adjust automatically.
Besides, many users are familiar with this approach from PC games.
A lot of times users are not gamers, though (as in my case) and twiddling with settings after first booting up a game is by far my least favorite part of the gaming experience. The best case (in my opinion) is when the game is able to automatically choose an appropriate graphics configuration based on my hardware (vram availability, power, etc). The options are nice to have but having a set of “reasonable defaults” based on hardware only seems like it would help streamline an experience.
Maybe a more direct question is has anyone done any work to develop a heuristic based on webgl capabilities to get a sense for the power of the current hardware?
it seems the code blocks can also be removed if the performance of the application is bad
That seems like a nice approach if it works well! Tuning based on framerate might be the best I can get.
This is only the case for CSS styles - and then, it basically only takes into account resolution, nothing to do with the power or capabilities of the device.
check the User-Agent and if it’s a mobile browser do one thing, if it’s a computer do another. However this is neither foolproof (people can change their user agent easily), nor future proof (you don’t know how browsers will report their user agent in the future).
do something similar to CSS media queries - do one thing if above a particular resolution, and another if below. E.g. show the full version of your app if the screen is landscape and has a width over 1280px, and a stripped down version otherwise. Currently, I use this approach to set 3 levels of shadow quality (off, medium, high) in one app and it seems to work well.
For WebGL we have additional options:
make some choices based on the capabilities reported by your graphics card.
test the frame rate at some point after the app has finished loading (say, 1 second), and if it’s below a certain threshold switch to a less intensive version of your app. Or the other way around - start with the stripped down version and if you get a high framerate then try the more intensive version. Seems like this is similar to the Babylon.js effect fallback @Mugen87 linked to above.
I’ve considered testing this last idea in the past, however, it would need quite a bit of experimenting and testing across devices to see if it is viable.
Aside from the WebGL capabilities reported by the graphics card, all of these have in common that they are far from perfect and you cannot really know how powerful the device your app is running on is. The only safe approach is to choose the most lower power device that you need to target and make sure that you get a reasonable framerate on that.
Thanks @looeee! I kind of figured these would be the preferred approaches but it’s good to get validation – using the the window or screen width is an interesting idea.
I’ve just had a quick scan through the Babylon.js effects fallback src.
There’s a comment at the top saying:
EffectFallbacks can be used to add fallbacks (properties to disable) to certain properties when desired to improve performance. (Eg. Start at high quality with reflection and fog, if fps is low, remove reflection, if still low remove fog)
The actual testing of FPS is done by another thing called the sceneOptimizer. Here’s the src. It seems like they test the FPS every 2 seconds and successively step through a series of fallbacks that you define until the desired framerate is achieved.
Babylon.js has a lot of cool features…
There’s a live example here: https://www.babylonjs-playground.com/#3Q8PCL#15
I’ve just tested it on my laptop which gets a steady 60fps and an old phone which started out at 7fps.
I’ve set it to target 40 fps and it seems to work pretty well.