I have been reading through the source code of id Software's Doom 3 BFG engine. The whole codebase is on GitHub at id-Software/DOOM-3-BFG. The architecture is both clean and elegant. The whole code base is of exceptional code quality. (It makes for a great code read.)
Fabian Sanglard has an easy to follow code review of the engine. In particular, I am interested in id Software's implementation of the renderer, which is explained here: http://fabiensanglard.net/doom3_bfg/renderer.php.
Architecture of renderer
Fabian's site offers this architectural diagram of the renderer.
From the source code, and from the review, you can see that the renderer consists of a frontend and a backend. The engine is multithreaded in two ways, first there is a job pool whose task items are consumed by worker threads without relying on mutexes (this is awesome). In addition to the job pool, the renderer front end and back also each have their own thread.
My implementation of something similar
I played around with trying to build something similar in my own game engine. I will probably make the repo public and link to it here at some point, in the future.
The renderer front end lives on the same thread as the program entry point
main(), where my Service Provider and Subsystem Singletons also live. Upon startup it launches the backend on a separate thread. The front end talks to the back end via a non-blocking command buffer. I implemented it using this awesome single-producer / single-consumer non-locking queue on GitHub at cameron314/readerwriterqueue.
The backend consumes commands of the command buffer queue, and turns them into OpenGL calls. The backend creates and manages the OpenGL context lifecycle. (At the moment this is an obstacle, because I am using SDL2, and I don't want SDL API related functionality, like input, running on the renderer backend thread.
Is it worth it?
Aren't OpenGL calls suppose to occur asynchronously, given that we are somewhat successful at minimizing GPU / CPU synchronization? Is all the added architectural complexity worth it from a performance perspective? With more modern rendering techniques like vertex buffer streaming, it seems as if more and more work is done to transfer load from the CPU to the GPU, and to make drawing asynchronous.
Adding the command buffer queue also adds a relatively heavy layer of abstraction between the originating
draw service call and the underlying driver call. This complexity certainly must impact cache coherence and branch prediction.
Newer, closer to the silicon graphics APIs like METAL, only further exaggerate my point. I guess moving away from the CPU is the obvious future path of progression for graphics performance.
Is it relevant?
As elegant as the Doom 3 BFG engine is, the architecture is probably a bit dated, and probably misses out on some new innovations. Did the engine originally rely on a fixed pipeline? This might explain this design choice.
Do any modern game engines use something like this?
Sorry for the long-winded and multi-faceted question. I hope the community will forgive me. I also realize, profiling would be able to answer my question. Unfortunately, neither my implementation of the technique I described, nor the method without it, is mature enough, to do any meaningful profiling. Not to mention, there are a whole lot of other variables as well.
I'd rather not build a complete rendering pipeline around a flawed and outdated idea, only to discover that it is over engineered and slow.
In the meantime, I will continue to develop my job schedular approach, but this could be a feature design improvement.
Any insights or discussion would be greatly appreciated. Thanks!