Adventures in engine development: a multi-core foundation.
Adventures in writing an engine: a multi-core foundation.
I recently put up my engine editor up on the mac app store and though that's a shameless plug it actually stands as a major achievement who's spent a few years of his life to bring it to fruition. It's not perfect, what software is? But there's some really nice features that I'd like to feature here in my first blog.
The first feature is multi-threading. Most engines I've worked on (and I've worked on most of them from CryEngine to idTech6) are lightly multi-core. I say lightly because they'll use them systemically not the basis for everything the engine does. This seemed sad to me when I was starting my EON Engine (named after the awesome book by Greg Bear--thinking about it I should have called it ION Engine, maybe I'll rename it :) So the basic principal of the engine is to be MASSIVELY multi-core. Not just as port listeners. The only area where I remain single threaded is in my OpenGL|ES pipeline. I chose GL because it was easy and got me initially on more platforms than any other API. But I want my renderer to be as multi-core as everything else so I'll be fully embracing DirectX 12, Vulkan and Metal for my next version.
Making threads the basic unit of the engine had some incredible challenges as you can imagine. The first thing was how to architect it? In my way of thinking ease of programming comes first. I want to be able to put games together FAST and not worry about what thread I'm running on. So I decided on a simple path to Nerdvana: everything is thread safe or everything is hard core black boxed so no other thread raids a running thread. This is why the engine took me three years honestly. I adhere strongly to the concept of const correctness and with that comes the time honoured rule that all const functions must be thread safe.
The low hanging fruit to the problem was to make my own resource system. Again it is completely thread safe. I also wrapped all the major standard containers like deque, vector, map, unordered_map, etc. so that they're thread safe.
Now the question you may be asking is how to do you make a big engine thread safe intrinsically? Doesn't using mutexes, semaphores, and the like slow things down?
Yes it does so I don't use them.
I use lockless algorithms everywhere. I also use read/write locks extensively, spin locks with incremental backoff, and pass everything into functions by const reference in C++11 and return everything by move semantics or by value. Once you have a good library put together (the foundation classes) that have this all built in then it's easy to write multi-threaded code. If two threads need to produce/consume data then use a lockless deque. My move semantics are most often atomic.
In the end I found that I didn't need to spend a lot of time thinking about the multi-core ramifications because my object library handled it all for me. Only when I'm sharing data between threads or having many threads farm over a single buffer do I need to think about multi-core problems. And then it's as simple as not overlapping writes and reads.
Anyway, the final result is an engine that imports a directory worth of animation FBX files, textures, and static and skinned meshes in under three seconds because everything in the import process than can be parallelized and also export process has been. Then the data format is completely multi-core as well. I've broken the data up into streamable chunks that all load in parallel. So loading a complex level seems instantaneous.
Lke I said Swordlight isn't finished yet but what I do have sitting there is pretty advanced. I look forward to blogging about it's systems in greater detail later.
Brian.
About the Author
You May Also Like