Hello there everyone! We have had a very busy week of progress on Pycraft and have a lot to share, including some interesting changes to the rendering pipeline for Pycraft's 2D engine and some detailed statistics on the ongoing optimisations of the settings menu!
But before we get started with that, let's talk about rendering and introduce a few new key terms that we will mention in this article and in future discussions. In Pycraft we have a game loop for every individual menu for Pycraft, whether that's the theme selection menu, the credits menu or even the 3D game engine. In every game loop, we have a rendering pipeline, this controls when, what and how we render content to the display. Before Pycraft v9.5.0dev8 every menu in Pycraft used a synchronous rendering technique. In this approach, the rendering pipeline gets used every time the game loop gets run.
This is very helpful for situations where all the content on-screen is continually updating, like for example the 3D game engine, and this is where this discussion splits to focus on the 2D engine. The 3D engine cannot take advantage of asynchronous rendering. In this approach, the rendering pipeline only gets called occasionally in the game loop, either when an event triggers the graphics to update, or after a certain number of frames elapse. This essentially means that if the content onscreen isn't updated then the rendering pipeline won't get called.
However there is more to a game loop than just rendering, and we can therefore split the game loop into three main components:
The first component is comparatively small in length and contains the scheduling and logic required to control the rest of the game loop, like for example controlling when to update the graphics and compute stages.
The second component is the compute component. This contains all the logic for widget interactions, events much more.
The third and final component is the rendering component. This contains all the computing and controls for rendering content to the screen (by adding the compute for the render stage here as well, we don't inefficiently compute for something that won't get rendered, like a graph).
The concept of asynchronous rendering is very new to Pycraft's 2D engine, and will not be rolled out to Pycraft's 3D engine due to it not being effective. For those menus that can take advantage of asynchronous rendering though, there is currently no inherent support built into Pycraft. Typically, when adding new features to Pycraft we try and gradually add them to existing mechanics over a series of updates and then link them together when we are ready to deploy them, however in the case of asynchronous rendering we are starting almost completely from scratch.
Fortunately, it's a relatively simple technique to roll out, instead of calling the compute and render components for every game loop, you call the render component only where needed. In Pycraft we are currently also requiring scheduled frames, so after a certain number of frames the graphics will be updated. This is because we have not yet finished prototyping an events-only system, and this will also be much more complex to benchmark and test.
A quick fun fact for you, asynchronous rendering, if not referred to by that term, is very commonly used in other applications, so much so we have been using it in Pycraft's Installer and that significantly improves the efficiency of that engine.
Now, there is plenty more to share on this topic, but we are still working on introducing this feature to Pycraft. We have finished work on the settings menu, and have also done a series of optimisations to improve the performance of this menu, as it was very inefficient.
In the graph below, the blue line represents the baseline performance of Pycraft's settings menu, taken before we started any changes and optimisations to this menu. The orange line shows the performance of the settings menu after we have spent some time optimising it, however, it is clear there is more work to do. All other lines that drop to this orange line are render frames. This means that the yellow and grey lines show asynchronous rendering, where for every 4 runs of the game loop, one will be a graphics as well as a compute frame, instead of just a compute frame. The example below ignores forced frames due to events!