In 2020 I continued working on rendering for Clover’s Toy Box.
Sorry for the wall of text. Apparently I’m too lazy to take screenshots now even though I started writing this post at the end of May.
There was 366 code revisions adding 20,000 new lines of code (including comment and blank lines) with another few thousand that haven’t been committed yet. It’s about ⅓ of Clover’s Toy Box ~70,000 lines of code (not including third party code).
Renderer of the Silent Age
Quake 3 BSP rendering
I started adding Quake 3 level (BSP) rendering which has gone through a few iterations and is still a work-in-progress. Just preload in GPU memory (vertex/index buffers) and it should be fast enough right? Oh naive me.
First I tried loading and rendering it from static vertex/index buffers just like any other model. Speed was about half of Spearmint’s opengl1 renderer (issuing 10,000 draw calls is slow). I tried merging surfaces by material at load time (so less then 100 draw calls) which helped some but still notably slower than Spearmint’s opengl1 renderer. It’s also not directly a viable option if I’m going to support Spearmint’s (unused) feature to change material on any specific BSP surface. (Spearmint’s opengl2 renderer uses merged surfaces with fallback to separate surfaces if a surface is remapped. But this a nusense to add and it’s already slower.)
I wanted to try dynamically uploading and merging visible surfaces (using BSP cluster visibility data) more similar to the opengl1 renderer. I upload all dynamic geometry for the frame once before issuing draw calls to avoid stalling OpenGL driver so this needs to happen when the BSP is added to the 3D scene (which I still don’t have a proper API for and how to handle sharing “dynamic BSP surfaces” between scenes). I hacked in dynamic upload of merged visible surfaces and it worked really well! Performance actually matches Spearmint’s opengl1 renderer. (It slightly leaves me scratching my head about what the benefit of preloading the models is).
I then added view (frustum) culling for BSP nodes/leafs/surfaces which didn’t improvement performance at all. I guess BSP cluster vis data already dropped most not visible. On Quake 3 user created level with lots of geometry—tvy-bench—rendering even got a little slower.
My last idea of preloading BSP vertexes and only uploading the triangle indexes was a bigger rework and again didn’t improvement performance. Though this might be due to testing using persistent mapped memory on an integrated GPU. I think “upload” in this case is just a regular memory copy. It may use less GPU memory though. Dynamic geometry is buffered for 5 frames. So 4 splitscreen views × 5 partial world views might be more vertexes than in the whole BSP.
I target OpenGL ES 2 which lacks 32-bit triangle indexes so I just limited models and dynamic geometry to 16-bit indexes / 65,536 vertexes (65k is enough for anyone, right?). In order to support Quake 3 BSPs with more geometry I decided to have blocks / pages of 65k vertexes and passing around ‘vertexPage’ (along with first/num vertexes and indexes) with all draw calls. This is used to offset triangle indexes in glDrawElementsBaseVertex() or by rebinding the vertex buffer. Building surfaces now has to “preallocate space” as it might need to change to next vertex page and when appending geometry to a surface it needs to support fallback to creating a new surface when the end of the vertex page is reached.
Future work will include adding support for curved bézier patches (which will probably use dynamic vertex upload alongside BSP triangles using static vertex buffer) and working out how to handle “add BSP model to scene” API / draw call generation.
Laundry list of other rendering changes
I added framebuffer object support. It’s used for soft particles (finally!), rendering the game or 3D scene at low resolution and up-scaling to window size (like for “fake” fullscreen without video mode change), and Quake 3-style high dynamic range (overbright). There is also grayscale and Sonic Robo Blast 2 style underwater wave effect.
I added Wavefront OBJ model loading with material library MTL loading to get texture names. The “MeshBuilder” functions and data structure take triangles and convert to a more optimized model for drawing and generate vertex normals if needed. It’s also used for work-in-progress loading support for Inter-Quake Export (IQE, text format of IQM) and Sonic R (BIN) models.
I added some options for model entities to mimic Misfit Model 3D view options. Wireframe (only or with textured faces), joint lines and bones, disable material alpha, force enable/disable back-face culling. (Missing flat shading and MM3D style lighting.)
I added normal map support. My material system doesn’t support normal maps yet so it’s just one normal map for all models in the game.
I added support for scaling image height and width to a power of two when not supported instead of rejecting the image with an warning message.
I added the ability to specify 3D scene field-of-view which allowed Toy Box renderer for Spearmint to fix Quake 3 HUD models and underwater FOV-wobble effect.
I added no draw (“nodraw”) material like I added to Spearmint which can be used by model skins to disable rendering meshes.
I completed support for OpenGL ES 3.0 and OpenGL 3.2 Core Profile. I fixed up the GLSL shaders differences using macros and replaced a depreciated method to get default framebuffer color bit depth. OpenGL 3.2 Core (forward compatible) works on Windows and macOS (NVIDIA) and Linux (software renderer; as I currently only have OpenGL 2.1 hardware set up).
I’ve gone beyond OpenGL ES 2.0 and WebGL 1.0 functionality so I can’t treat them as one API anymore. There is now explicit WebGL support in order to support soft particles (requires framebuffer depth texture) and GPU compressed image (DXT1-5) formats.
Images Among Us
Image support has evolved from a basic loader to get color data (using stb_image) to a small framework supporting reading and writing many formats, scaling images, handling images with pre-multiplied alpha, and handling GPU compressed (DXT1-5) formats. It also supports loading images from system paths instead of just game virtual filesystem which is useful for utility programs.
Similar to integrating libpng support last year; loading and saving JPEG images now use libjpeg-turbo with SIMD instead of stb_image. This should offer better performance. There is now support for loading all additional image formats supported by Quake 3 (BMP, PCX), Spearmint (DDS, FTX), and for some bizarre reason Sonic R (1997 PC game) image formats (RAW, PLY, 256).
TGA and BMP loading are all that left using stb_image. I fixed a minor issue with stb_image BMP loading erroring when I tested it with an image exported from GIMP. I wrote new Toy Box image importers and exporters for TGA (export), BMP (export), PCX, FTX, DDS (DXTn only, no uncompressed formats), Sonic R (RAW, PLY, 256). DDS export can convert to DXT1-5 formats using stb_dxt. Missing DXT decoding for converting DDS to other formats.
I decode alpha in DDS DXT1-3 images when loading to check if alpha blending should be used. Missing DXT4-5 alpha decoding. DX10 DDS format allows specifying alpha type so decoding alpha can be skipped.
There is now support for Quake 1 & 2 PAK archives and Quake 2 MD2 models. This was mainly for being able to test if Maverick Model 3D exports MD2 models correctly. Maverick did not export vertex light direction correctly but Maverick MD2 export has now been fixed.
Series of MD2/MD3 frame names (pain001, pain002, …) are now converted to an animation list (pain, 2 frames). It would be useful for splitting up animations in model viewer.
Toy Box 2019 was missing a feature for full Quake 3 MD3 model support: Internal skins (texture sets). This feature was a hangover from Quake 2 and is not required for Quake 3. Implementing Quake 2 MD2 model support also gave me the ability to properly test the feature. Toy Box now supports rendering MD2/MD3 models using a specified internal skin. Internal skins are used by setting a per-entity material override list like my handling of Quake 3 .skin files (in Toy Box renderer for Spearmint).
Relative Paths in the Membrane
Adding Quake 1 & 2 PAK archive support came with a fun surprise. In Quake 2 there are two model textures have literal “..”directory in their filename (used for moving up a directory); models/monsters/tank/../ctank/skin.pcx. ioquake3 and Spearmint forbids “..” in game paths as it can be used to escape the game files to access system files. In order to support these textures in Toy Box I added support for resolving (canonicalizing) “.” and “..” in game paths.
The next (unrelated) logical step was to add support for textures relative to the model that referenced it (e.g., “./image.png”, “../textures/image.png”). This mainly required passing the model directory as the current working directory to my canonicalize function. Though I decided to rework things so that the current working directory is validated and canonicalized as well to hopefully avoid VFS escape issues in the future. Overall a lot of time went into testing and fixing issues with canonicalizing file paths.
On the subject of filesystem; I added partial read/write/seek API to game virtual filesystem and made game VFS block accessing to read/write executable files and archives (including Linux executables, by checking for ELF file header).
Wing and a Network
I added HTTP file download based on my HTTP parser for WebSocket connections. Added a loopback buffer so that emscripten builds (HTML5/JS) can run local games without an external server. And added server kick command to disconnect a client and send disconnect message.
I fixed various networking issues; such as an infinite loop trying to send data to a closed TCP connection (broken pipe) and not handling incoming HTTP data that is fragmented across multiple network packets.
I spent quite a lot of time to write a basic IRC server so IRC clients can connect to the game and chat but it hasn’t been finalized yet. It’s kind of funny as the Toy Box network code was originally salvaged from an IRC client/bot I wrote.
The Clover Tribunal
Clover Resource Utility (formerly Clover Model Viewer) was merged into the main Clover’s Toy Box repository that that it doesn’t require renderer changes. This makes it easier to add third party libraries and cross-compile for Windows as I switched from “qmake” to integrated in the main Toy Box Makefile. Resource Viewer now supports image viewing and exporting to other image formats as well as model viewing. It gained view options for Quake 3 overbright and various Maverick Model 3D inspired view options.
I added basic MIDI keyboard support on Linux (using ALSA / libasound2); it was actually pretty simple. It can be used for game input like a computer keyboard but it doesn’t play audio for the notes yet. Ideally this would eventually lead to a Hasune Miku: Project DIVA inspired game but with a keyboard instead of PlayStation controller but don’t count on it.
I added initially support for compiling the game server for (homebrew) GameCube and Wii consoles using devkitPPC but USB keyboard and network don’t seem to work in Dolphin emulator and I haven’t tested on an actual Wii yet. Ideally this would eventually lead to a sequel to the Ninja Turtle games for GameCube but don’t count on it.
Last year for future plans I wrote:
I want to finish up some geometry rendering features and polishing. After that maybe I’ll focus on another area. I don’t anticipate releasing anything developed for Clover’s Toy Box in 2020.
I’m still working on geometry rendering and it needs more polishing and finishing various features now than it did before.
Lately I only sporadically feel like working on Clover’s Toy Box or software in general. There is often things I’d like to happen but I don’t feel like doing them. So maybe I’ll just take the year off. (doubt)
TMNT (2003) season 4 episode references.
- Sons of the Silent Age
- Aliens Among Us
- I, Monster
- Insane in the Membrane
- Wing and a Prayer
- The Ninja Tribunal
Leave a Reply