Where Angels Fear to Thread
Leadwerks Engine 2 is single-threaded. The engine performs one task at a time. The engine updates physics, then performs some other tasks, then renders the scene, waiting until each task if finished before moving onto the next one:
I have experience programming threads in BlitzMax, and it works pretty much the same way in C++. Leadwerks3D is taking full advantage of threads and multicore CPUs, splitting off any expensive tasks that can be run on separate threads.
Multithread programming is a little different than linear programming, and you just have to design things carefully. It's okay for two threads to read a variable at the same time, but if they both try to write to the same variable, or one writes while another reads, you will have major problems. This is made harder by the unrestricted nature of Leadwerks, because the end user might call any function at any given time, and I don't want to make arbitrary rules like "You can't modify surfaces during the main game loop".
Mutexes (short for mutually exclusive) are a way around this, as they lock sections of the code to prevent multiple threads from accessing them at the same time, but I try to avoid using them. It's extremely difficult to find all the parts of your code that might need a mutex locked. Also, when you start using mutexes liberally, you lose all the benefits of concurrency, because the threads have to stop and wait for each other. I prefer a design where you gather data for a thread, run the thread without interacting with any other parts of the program, and then get the results a few frames later, or whenever the thread finishes processing.
Multithreading has some big benefits for the new engine.
Physics
Physics in Leadwerks3D are asynchronous, meaning that while the engine renders the scene and executes your game code, it's already calculating the next frame of physics on a separate thread! The diagram below shows how the physics get split off onto its own thread, then split into many threads by the solver, and the two threads meet again the next frame. (It's actually more complicated than that, the engine just keeps running until 16.667 milliseconds have passed, then it waits for the physics thread to finish, if it hasn't already.)
Let's say our ideal framerate is 60 FPS, which means our target frame time is 16.667 milliseconds (=1000/60). Whereas before a physics processing time on a single thread of 10 milliseconds would eat up 2/3 of your ideal frame time, now you can have physics that take up to 16 milliseconds, and have no performance cost for a program that is otherwise running at 60 FPS. Below you can see 5000 bodies falling at near real-time speeds:
NavMeshes
Navmesh generation is a slow process. However, it does not need to be instantaneous. This makes navmesh recalculation perfect for multithreading. As you can see in this video, the obstacles can move around and the AI navigation will react dynamically to the environment. I've never seen a game with dynamic environments like this, so we are breaking totally new ground. As CPU core counts rise the engine will be able to use those cores to handle more complex maps and greater numbers of characters. In the meantime, it will perform well on any dual-core mobile device!
Rendering
Rendering in Leadwerks3D will split culling up among threads to take advantage of multiple CPU cores. This is not yet implemented, but it's a little easier than physics and navmesh threading, since it doesn't run concurrently to the main engine thread.
Below is a diagram showing the complete threading design for the engine:
Multithreading in Leadwerks3D is all done automatically behind the scenes, so you can still program however you want, without fear of interfering with other threads.
9 Comments
Recommended Comments