asny / three-d

2D/3D renderer - makes it simple to draw stuff across platforms (including web)
MIT License
1.34k stars 110 forks source link

Performance issues with the `renderer` module #354

Closed silverlyra closed 1 year ago

silverlyra commented 1 year ago

On #350 I mentioned wanting to switch to using core exclusively due to performance problems I ran into using the tools in the renderer module, but I didn’t elaborate 😆 on what I was seeing.

Here’s a capture from Chrome’s profiler of one of our requestAnimationFrame callbacks. This is a wasm-pack profiling build (optimizations + debuginfo):

renderer call graph

I couldn’t figure out how to get non-opaque colors to work without adding a light, and I noticed that framerates got worse when I did. The profile confirms the light did that; 40% of each frame is spent in lights_shader_source, building a String on every frame. (There’s only one fixed AmbientLight in my scene.)

Another ~50% of each frame is spent in HashMap::get. It’s not obvious where this is coming from with optimizations enabled, but if I re-run my app in a debug build:

renderer call graph debug

It seems this is happening in Context::program, with most of the HashMap time spent computing the hashes themselves.

The bottom-up profiler view shows that it’s lights_shader_source which is responsible for most of the garbage collection which runs during frames:

renderer bottom-up

I wonder if building that shader source in a reusable buffer (instead of allocating a new String every time) would make that part of the problem go away.

asny commented 1 year ago

Thanks for the detailed report, it's super helpful 👍 As I said earlier, it probably makes sense to use core because it will always be faster if you build something for a specific use-case. However, most of these performance issues have a solution, so maybe I should dedicate some time to fixing them 🙂

Basically, except for the garbage collection, these are issues that I have seen before when profiling. I recently did one optimisation pass, so it is already better, but I think it's time to rethink the idea of using the shader source as the key into a cache of shader programs. That solution was not necessarily meant to be permanent, it was just easy to test out the design this way. And well, if the default hashing and string building was not so slow, it would probably have been good enough - but it is slow and I usually say that to everyone who wants to listen 😆 Anyway, I'll change the key to be some integers that are fast to generate instead and that should solve the string building, the garbage collection of strings and the string hashing - so basically all problems at once 💥 The problem will then be to create unique ids for geometries, materials and lights, which wouldn't be a problem if they could not be created outside of three-d, but they can 😬 But well, I think it's worth it, the performance gain outweighs the downside that it will be a bit more difficult and error prone to do custom geometries, materials and lights.

I couldn’t figure out how to get non-opaque colors to work without adding a light, and I noticed that framerates got worse when I did.

That's interesting. You have vertex colors? You might need to use the ColorMaterial::new_transparent instead of ColorMaterial::new? And yes, that will definitely make a negative impact on performance before the above problems are solved.

asny commented 1 year ago

FYI, working on this in the optimisation branch.