Welcome to the very first devlog of Effect Engine! Being the first devlog, this one will be a bit unusual. It will roughly cover how the game engine currently works as well as what I am currently working on. There won’t be many demos at the moment or the forseeable future, as the game engine is still in a state where it cannot make a fully functional game. However, I hope to change that slightly later on with the introduction of working text and menus.
The engine as it stands
The Effect Engine is currently written in Rust and utilises wgpu. This may change in the future to also utilise Vulkan directly via Ash. However that is a future problem, as my immediate goals are to clean up the current codebase, then add text and functional menus. In its current state, the game engine is mostly a 2D renderer with some additional features such as audio and a built in camera.
The renderer currently is only capable of producing 2D graphics, however basic 3D with custom shader support is planned in the future. The architecture is fairly simple. The renderer divides everything up into “render layers”. These render layers are then drawn in ascending order based on their LayerID.
Each layer stores its related entity data in buffer, along with a texture atlas. The entity data just includes basic position and transformation data as well as its current layer, and how to extract its required texture from the texture atlas. What this means is, each texture is only stored once per layer, reducing duplication of the data. The benefit of the texture atlas and associated layer in this case is that all entities of a particular layer can be drawn in a single draw call, reducing CPU overhead.
In order to update each entity, each layer has to be updated individually by calling a function in Layer2DSystem, which takes the entities then updates the buffers in the layer based on the entity data. In the future, I plan to design this such that each layer can be updated in parallel.
Speaking of which, the current architectural goal, in terms of thread model, is to have a singular main thread, which uses thread pools to delegate work which can be done in parallel, as well as having any engine functionality that can be done in parallel, do so. This should make the problem of utilising the full CPU as well as synchronisation a lot easier. I still have a lot to learn on this topic, so this will likely change as my understanding of modern game engine architecture improves.
So… what am I doing currently?
I noticed a few issues with the engine as it stands..namely, there is a lot of repetition and initialising various structures is very verbose.
For example, take this pipeline initialisation:
There are quite a number of steps here, and a lot of what is done here is repeated later on. Not to mention the user has very little control of these options, unless I create a huge list of parameters to pass. There is a solution for this, the builder pattern.
The builder pattern allows you to easily create any structure, using as minimal options as possible if you want, but also granting the ability to use more advanced features if you wish. Not only that, it is very easy to extend with new options and features without breaking existing code. It is essentially a cleaner version of function overloading. The builder pattern does add some verbosity for the structure it is “building”, but in my opinion the trade off is worth it, as it makes future usability and maintainability much simpler.
For example, take this new BufferAllocator structure, which builds a Buffer structure:
As you can see, some good defaults are provided, while also allowing some choices to be optional, simplifying intialisation of the Buffer structure. You may notice that the member functions don’t take a reference, but instead actually move the data of self. Then it returns itself. This allows one to continuously call member functions in a chain. An example of its usage is as follows:
There are of course two different allocate functions. The one used here actually returns a wgpu::Buffer because this is an internal write function of my own Buffer structure, but as you can see, it makes things simpler than manually building a structure or passing a huge amount of arguments. Another example is the actual inialisation of the engine itself,
Most of these arguments are optional, and there will be even more arguments to come. Imagine if they were all function overloads or all had to be manually passed, it would be a nightmare! However with the builder pattern, the initialisation can be even simpler!:
I think this adequately highlights the strength of this design pattern.
Another thing I have been working on is simplifying functionality that is often repeated, eg creating and writing to buffers.
Forgive the long block of code, but it is necessary to highlight how verbose it is:
This is the old code that essentially updated the layer buffers based on new entities passed to it. This is obviously far too complicated, leading to the potential for more mistakes that could lead to memory bugs or performance issues. As you can see there is a lot of repeated code, and extra parameters passed around that only really serve the purpose of a flag. There is a much better way to do this. Here are the relevant parts of the Buffer structure:
The usage is stored within the buffer, so a flag no longer needs to be passed around. The allocation and writing itself is then fully managed within one function. So if the amount of data passed exceeds the buffer size, this will be handled by the write function. Now the Layer structure no longer has to store the capacity itself or manage any of these details. This functionality can now also be easily reused for anything else that may need a buffer. Remember, the buffer is created using the BufferAllocator builder, and calling the allocate() function.
The managing of buffers related to the entities is now much simpler for Layer2DSystem!:
Yes, this is currently the entire implementation of Layer2DSystem. Much better! Once the buffer is initially created, it is then just a single function call! Okay yes I admit, some of the code such as the vertex calculation was just moved elsewhere, namely to the initialisation of the layer itself, since I will be switching to Texture Arrays over Texture Atlases. Since textures in a texture array must be uniform in this case, all vertex calculation can be done upon initialisation. The reason I chose to require the layers to have all the textures they need upon initialisation is because it was much simpler to code for a texture atlas. However it is not likely a huge deal for a texture array, so this is functionality I can probably provide later on.
My plans for the future
A lot of the plans are written down in a README in the repository, but to give you an idea of my immediate goals after the current codebase cleanup..it is to release 0.3.0-alpha once the cleanup is done, then working on the GUI. Most games require some sort of text display and menus, so it is important I get this done as soon as possible, I plan to make a GUI library so common menu design patterns are easy to do, for example, a list of buttons. The main issue will be figuring out mouse and menu collisions, as well as mouse and non-menu collisions for point and click games, this may result in needing to work on the physics system before mouse menu control would be supported.
Now, talking about this site. I plan to write mostly dev logs for the engine, however I also want to write some articles about news in the tech world that I am interested in, mostly new hardware, technologies, leaks and speculation among other things. In terms of article frequency, there is no guarantee, multiple articles may be written in the same week, or it might be years between articles. Regarding the theme and appearance, I may change it in the future, however it would be a substantial amount of effort, so it would require significant readership to motivate me, something which I believe is unlikely due to the nature of this site.
I hope you enjoyed this first devlog! I actually plan on making the repository for this site public, so issues can be made to correct any errors. Oh and don’t worry, once engine development is further along..there will be more images..and videos, rather than blocks of code.