Hooray!
An exciting new release of Tofu Engine is now available! You can find the engine code here.
While this release may not be as groundbreaking as some of the previous ones, it is still incredibly important as it signifies the beginning of a new era.
There are two significant reasons for this.
I hinted at the arrival of some major changes in a previous post, which you can find here.
Let’s delve into these changes a bit.
The Rendering System
As you may already know, Tofu Engine currently utilizes a custom software-rendering approach. I take great pride in this achievement as it allowed me to implement algorithms I extensively worked on during the ’90s. Unfortunately, some of this knowledge has become lost over the past two decades. Back in the day, we were all busy crafting mind-bending roto-zoomers, but nowadays, only a select few know what they are and how to implement them. Additionally, this approach enabled me to incorporate unique and specific effects like Mode-7 transformations and Copper-lists.
However, running a software renderer on modern hardware… well, let’s just say it can be far from optimal.
It’s a legitimate choice if one aims to create a virtual retro-machine like pico-8, where the FPS and overall performance are intentionally limited. However, for a general-purpose game engine designed to run on modern hardware and support complex games, it’s not the most sensible choice.
Therefore, over the past year, I’ve been experimenting with reimplementing most of the features in Tofu Engine, leveraging the power of the GPU as much as possible. This involves transitioning back to full OpenGL (or Vulkan) usage, which, at the moment, is only utilized as a cross-platform way to present the framebuffer.
Fortunately, many of the engine’s current features can be reimplemented with minimal changes. Some may require some redesigning (leveraging fragment shaders, if needed), such as the stencil/alpha/tiled blitting and Copper-lists. Others may be permanently removed, like the Mode-7 inspired transformations.
I need to carefully weigh their importance and eliminate what isn’t truly necessary. For instance, the aforementioned Mode-7 transformations may be cool and unique, but it serves little purpose in a real-world scenario and can likely be discarded. On the other hand, certain Copper-list features have practical applications and can be maintained with some minor simplification.
If all goes well, this will results in a much more efficient game-engine, in its utilization of hardware resources. It will also reduce overall power consumption, as running an uncapped software renderer for FPS heavily drains the machine’s power. You notice this when the fans start making some noise!
Let’s hope that all the effort put into this endeavour will prove worthwhile! :)
( and let’s also hope to fix that diamond-rule issue that inspired me to implement a software renderer back in 2019, as I was facing challenges in ensuring pixel-perfect blitting )
Abstraction
When I initially began working on Tofu Engine, my goal was to create a straightforward environment. An easily accessible sandbox with all the essential components ready to be combined.
However, as development progressed, I found it increasingly challenging to restrain myself. I ended up making the engine more and more low-level and generic, which is a tendency of mine, as I prefer creating solutions that are applicable to a wide range of situations rather than specific ones.
This led to something that, to some extent, resembled other game engines (e.g. Löve, which I always cited as an influence), offering a scriptable framework that provides everything one would need to create almost any type of game but without specific assistance for game development.
This is particularly evident in the way the game loop interacts with the game “instance” (let’s refer to its class/object as Main
), which involves calling the following methods:
Main.process()
: called on each iteration after updating the input state,Main.update()
: called at regular intervals to update the game state, andMain.render()
: called on every iteration to present the graphics to the user.
While this approach is undoubtedly correct, it lacks abstraction of the engine’s internal workings. The scripted code matches almost one-to-one the engine’s one. Where’s the benefit of the abstraction that an engine should grant? It seems like we are coding for a virtual console for which Lua is the native language… :>
Furthermore, there are suboptimal design choices in this approach. For example, the Main.render()
method is called on every frame, meaning we frequently cross the C-to-Lua boundary only to subsequently cross back from Lua to C for each drawing operation. It works, but it’s far from efficient.
( well, it does work, nonetheless :D )
Is this really what I envision for my game engine? I must admit that it deviates significantly from my initial idea, and I fear that, in the long run, developers would end up writing excessive boilerplate code even for the simplest tasks.
What’s Next?
As I grapple with this realization, it has become evident that a paradigm shift is necessary at this point. I need to go back to square one and completely rethink this aspect.
I’m currently taking notes and starting the ground-up redesign of these two crucial parts of the engine.
I anticipate that implementation will commence soon.
Probably. :)