Presently, when compiling shaders - three.js always checks for compile errors, this is typically the biggest time hog, and if you have a decent number of them (shaders that is) - it tends to cause considerable FPS spikes. For example, in my game it's about a series of very janky spikes just just as you first load and try to move the camera.
Here's the first frame:
and here's the aggregate of the entire game load and first couple of seconds of gameplay:
as you can see, just checking for errors and retrieving error messages takes up close to 800ms.
I think it's great to have these checks during development and it's great to be able to keep them, but in production a lot of the time they can be irrelevant or at least it's a developer's call whether to get better performance or get better error reporting.
I believe it would be beneficial for developers to be able to opt-out from these error checks/reports.
There's a separate point, that these checks can be done asynchronously in most browsers. See @toji 's demo: http://toji.github.io/shader-perf/
Also, I'm aware of WebGLRenderer.compile, that's a separate point, good fit for static scenes - I'm glad we have this feature.
I actually had no idea this was happening! Thanks for bringing it up. I kind of figured some of the hiccups after adding new materials was just the nature of WebGL. Some of the applications I work on require creating new materials on the fly or changing attributes that necessitate recompilation so I would really like to see an option that allows for disabling those checks for production.
What would you imagine the API change looking like? I'm having a hard time imagining wanting to toggle printing warnings per material so maybe it could just be a static variable like Object3D.DefaultMatrixAutoUpdate
? So setting WebGLShader.CheckShaderErrors = false;
would globally disable the check.
It looks like the relevant lines of code are here.
What would you imagine the API change looking like?
I would prefer to have it scoped to the renderer itself, like new WebGLRenderer().checkShaderErrors = false;
but a global flag is fine too, it would be consistent with Object3D.DefaultMatrixAutoUpdate
as you mentioned.
https://github.com/mrdoob/three.js/blob/aa6ed41b18c20e37412e3ec3d128075920e07607/src/core/Object3D.js#L104
Maybe WebGLRenderer.programCheckEnabled
. Default would be true
of course^^.
I actually had no idea this was happening! Thanks for bringing it up. I kind of figured some of the hiccups after adding new materials was just the nature of WebGL
Same here! I already disabled frustumCulling on any meshes first render (one all textures are loaded) in order to push all data to the gpu right away instead of as they appear (causes jank as you do your first bit of gameplay).
I kept thinking I either missed something or there was some inherit lag for webgl =]
Another large portion is texture decoding. PNG is wonderful, but it takes browser a while to convert it into a bitmap, I've been half-convinced to go with raw bitmaps for a while now to avoid that decoding cost.
@Usnul the async demo you point to indicates that checking the compile status isn't the time consuming operation, but the check forces the browser to ensure that the compile has completed before a result can be obtained, ie the large time delay is waiting for the compilation to complete rather than the act of checking.
If this is true, avoiding the checks may not provide the performance increase you expect if there is little other work being done between the shader compile and the first call that requires access to the compiled shader. It would be interesting to see what timings you get when the status checks are removed.
You raise an interesting set of questions, I have wondered about them for a while also.
@aardgoose
Yes and no. There is a bit of an overhead associated with just getting the errors. It might be an inherent problem in the OpenGL implementation. As far back as I can remember the recommendation has been to never retrieve the error messages, unless you're actually debugging stuff.
Another point, shaders are compiled asynchronously, so you can stagger the compile time with other JS code, say the compile time is 10ms each for 5 shaders, but while building the materials, it takes 10ms per material to build, the compile time essentially becomes invisible, and the whole process takes ~50ms. I'm not sure if the compilation module is multithreaded though, if it is - then you win just through the fact that now your shaders are compiled in parallel and when your scene loads for the first time - your shaders will be compiled a lot sooner since you're not forcing the compile module to build your shaders one at a time (essentially).
I don't expect it to be a magic bullet but I expect it to help a bit. At the very least you enable other javascript to run while shaders are compiling and in the best case the browser can compile multiple programs in parallel. There's definitely some subtlety to it and there are probably ways to do better but this is a great start.
shaders are compiled asynchronously, so you can stagger the compile time with other JS code, say the compile time is 10ms each for 5 shaders, but while building the materials, it takes 10ms per material to build, the compile time essentially becomes invisible, and the whole process takes ~50ms.
I don't believe this is necessarily true even with the change. Shaders will only be compiled when they're about to be rendered. In order to get the behavior you're describing you would need the capability to ask THREE to compile the material _immediately_ after you create it so it can work while you generate the next material, which is unfortunately not possible at the moment.
@gkjohnson true.
| shaders are compiled asynchronously, so you can stagger the compile time with other JS code,
Unfortunately in this case after a compile is scheduled, the next bit of javascript to execute is setting up the uniforms and attributes for the shader, these activities require a fully compiled and linked shader to be available, so the wait will just occur at a different gl call, if you eliminate the compile status waits.
To avoid the stalls, you would either have wait for a signal that a compilation had finished (which sadly isn't available) or insert an arbitrary delay before attempting to use the shader (for example: skip objects using a material for n frames) which is rather hacky and would produce intermediate incomplete frames.
The first post in the following discussion by the author of the async demo page describes this limitation.
https://groups.google.com/forum/#!topic/webgl-dev-list/S8-nBx7q-jA/discussion
@aardgoose
I feel this is a bit off-topic, but there are non-hacky ways of doing what you describe also. Such as staging the redering pipeline. It would take a fair bit of refactoring though. Here's an example:
@aardgoose @Usnul
Is it worth opening another issue to discuss better ways to handle or give more control over shader compilation?
I am experimenting with breaking the program compiliation/linkage process into two stages and using WebGL2 fenceSync() to avoid using blocking operations to access shaders in the process of being compiled. WebGL 1 doesn't have any mechanism to avoid blocking operations other than using arbitrary delays.
Most helpful comment
Maybe
WebGLRenderer.programCheckEnabled
. Default would betrue
of course^^.