Dreamcast homebrew engine part 1
Dreamcast
Released in Japan 1998 / elsewhere 1999, the Dreamcast was Sega’s last home game console. It was designed with 3D games in mind, excels in that regard, but was quickly eclipsed by the Playstation 2 and ultimately had a very short commercial lifespan. I love a doomed underdog, and when I first became aware of its homebrew scene a few years ago, I knew that Dreamcast development lay in my near future. I’ve made a few prototypes already, but for various reasons they have been indefinitely postponed. There is an actively maintained open source ‘operating system’ named Kallistios that enables new software to be developed for the console without having to use the copyrighted official software development kit from Sega. Coupled with forums filled with technical discussions, a recently revamped wiki and multiple busy Discord servers (boo), there’s a wealth of resources for understanding the system’s workings and how to develop for it.
Dreamdisc 24
This December will see the ‘Dreamdisc 24’ gamejam, a community event where all Dreamcast developers are invited to create a game, some of which will end up on a physical compilation disc. I have been known to participate in a gamejam or two (it’s basically my entire personality), so I’m excited to participate. In preparation, I’ve been implementing a game engine - a framework on which multiple different games coule be built.
Homebrew engine
Though the engine I put together for Dashy No Blast was reasonably sophisticated, I was using an opengl abstraction layer named GLDC rather than working with the lower level Power VR graphics api that Kallistios exposes. When I recently returned to Dreamcast development and tried to rebuild Dashy no Blast with the latest Kallistios and GLDC updates, I was met with only a black screen on the Dreamcast. Initially I assumed I was failing to initialise something properly, and despite a few hours of investigation was unable to draw anything to the display. Eventually I discovered that a recent Kallistios update made a breaking change that prevented GLDC from working - but before learning this I had already started exploring rendering 3D on the system without opengl.
Here’s a mighty triangle running on an emulated dreamcast, my first baby. All 3D images on the Dreamcast are created from triangles - often hundreds or thousands of them - so being able to output a single triangle to the screen is an important step. This confirms that I’m able to draw in 3D without using OpenGL. In theory, I can draw more triangles and at a faster rate if I use the Kallistios PVR api directly, without the slightly more generalised OpenGL layer performing calculations in the middle. In practice there is probably quite a lot of optimisation needed to get from an initial naive approach to a system that parallels the performance of the dreamcast OpenGL implementation. But for now the next step was loading and displaying a more complicated mesh, with textures:
Z Culling
If you look closely in the video above you may notice pieces of the floor turning black. Any geometry that extends offscreen needs to be ‘clipped’ to prevent errors and chaotic triangle soup from being drawn all over the screen. Here’s an example of when culling isn’t working:
‘Illegal’ triangle data in the stream sent to the graphics chip is discarded, leaving holes that invalidate the meaning of subsequent data, leading to unpredictable results. My ‘easy’ solution for now is to cull any triangle that is has one or more corners with a Z coordinate behind the camera. Culled triangles are never sent to the PVR graphics chip, preventing this problem from occurring - but at the cost of not being able to draw geometry that can be behind the view. This can mean holes in floors, missing walls, and other incomplete geometry if the camera is too close.
It is only possible to submit ‘so many’ triangles to the PVR each frame. Exceeding this number can slow the framerate of the game if the scene is not ready to be drawn when the next frame must be drawn. If any of the submitted triangles are not visible, due to being offscreen to the left or right, or perhaps too far into the distance, then there’s no point in sending them to the PVR. Rendering a tall skyscraper 90’ to the right of where the camera is looking is useless if it can’t be seen. Instead the triangle budget could be spent on more detail for the objects that are onscreen. To limit the number of wasted triangles sent to the PVR, another type of culling is needed.
Bounding sphere visibility culling
Let’s say we want to draw this spaceship scene. The engine transforms the position of the ship and all of its triangles by the camera projection matrix. This operation converts from ‘3D space inside the game’ to ‘2D space on the screen’. If the camera is at the coordinates 0,0,0 and the ship is located at 0,0,50 then if the camera is facing the right direction, the ship will be drawn at an appropriate scale for being ‘50 units’ distant. If the ship were moved to 0,0,100 then the distance from the camera to ship is 100 and we would expect a reduction in the size of the ship drawn to the screen. Likewise adjusting the X and Y coordinates would move the ship around the screen horizontally and vertically respectively. Too far in either of these directions, and the ship would be ‘offscreen’ and invisible to the viewer.
My engine creates an imaginary sphere containing each object to be drawn to the screen. Before the somewhat expensive operation of calculating the transformed position of every triangle, the engine calculates the screen position of where the sphere would be by transforming the position of the ship and calculating the screen size of the sphere. Then it is a simple matter of 2D maths to check if this position overlaps the visible area of the screen.
The yellow rectangle represents the visible screen space. Any spheres that are contained or overlap this rectangle will have their contents drawn to the screen, as is shown with the green onscreen sphere. The red offscreen sphere does not intersect the screen rectangle, and so is culled and not sent to the PVR graphics chip, saving triangles.
This system is not perfect - in this example the ships are long but not tall. A sphere that slightly overlaps at the top or bottom of the screen will still send wasted triangles in this example, but it is a good approximation and cheap to test. If I write a game that would benefit from more precise culling, I could extend the spheres to support being squashed on some axes, or using multiple small spheres instead of a single large one.
To be continued
The engine contains a Level Of Detail system to save triangles by drawing simplified versions of meshes at long distances where details are not visible, limited gltf file format support, audio with dynamic pitch and volume, and a rudimentary ‘cel-shading’ like style with model outlines. If there’s anything to be said about those or other features, I’ll add a part 2 in the near future. Until I decide on what game I’d like to make for the Dreamdisc-24 jam, I can’t be sure what else the engine will need.