This year a lot of progress has been made on geometry and text rendering for Clover’s Toy Box.
Throwing vertexes at the screen
There is now support for rotating individual IQM bone joints (e.g., torso and neck control bones) and using a single model with separate legs and torso animations. The animations list in IQM files can now be accessed. These are features that I’ve wanted for Turtle Arena / Spearmint since the beginning (2009) so it’s nice it’s finally coming together.
Support for various types of geometry has been flushed out. Including several features not supported by Spearmint.
- Added support for full model rotation instead of only rotating yaw.
- Added mirror and portal support, including multiple visible at once (no recursion yet though).
- Added Quake 3 MD3 model support with vertex frame animation.
- Draw debug axis if model failed to load.
- Added debug drawing for triangle wireframe, bone joints, and MD3 tags.
- 2D/3D rectangle: Add arbitrary texture coordinates and vertical gradient color.
- 2D/3D polygons: New type, with vertex indexes support.
- 2D/3D lines: New type, with vertex indexes support.
- 3D flare (corona): New type.
Toy Box is missing seven things compared to Spearmint geometry. Optimized world drawing (binary space partitioning), curved world surfaces, bullet marks, projected/stencil shadows, sky box, procedural vertex animation (Quake 3
vertexDeform shaders), and debug lines for vertex normals.
I am too enamored with text
- Added in-game console. Complete with dynamic output text wrapping with unlimited line length and number of lines (limited to 32,000 character buffer).
- Integrated Freetype and libpng libraries to support proportional-width TrueType fonts and also color bitmap fonts.
- Added Emoji support. Currently very basic but
:D :) :| :( :cry: :smiling_imp:is entertaining none the less.
- Added GitHub/Discord-like markdown text styling. Bold, italic, bold italic, strike-through, and underline.
^X######(24-bit) color codes (inspired by Xonotic and old Unvanquished 24-bit colors). Quake 3 ^# 4-bit colors were already supported.
\r(return to beginning of line) in in-game console and Unix terminal.
- Added support for reading files from .pk3 (zip archive) and .pk3dir (unzipped directory).
- Command history for in-game console and system terminal is saved across restarts.
- Added support for taking screenshots.
I added the ability to use the Toy Box renderer in a Qt app with multiple OpenGL widgets like Maverick Model 3D. It’s still missing several features that would be needed for Maverick Model 3D and I’m not currently planning to add it to Maverick.
It would be simpler to merge the viewports instead of using multiple OpenGL contexts but that would not work for applications like GtkRadiant (Quake 3 level editor).
- Added support for rendering with multiple OpenGL contexts (with resource sharing) and use a callback function for getting OpenGL functions to abstract SDL and Qt OpenGL GetProcAddress functions.
- Added support for OpenGL [ES] 3 vertex array objects that hold model attributes (separate for each OpenGL context).
I hooked the Toy Box renderer up to Spearmint for testing and eventually comparing performance with Spearmint’s ioquake3-based opengl1 and opengl2 renderers. I’m not currently planning to make a complete Quake 3 renderer.
This was the reason I finally added full model rotation support. Using it I found an issue with drawing 3D scene not working on top of 2D drawing in player model select menu (need to clear depth buffer for scene region before drawing scene). The lack of world .bsp rendering makes it difficult to play but it hasn’t stopped me from spending a lot of time running around in the dark.
Spearmint adds duplicate objects to draw for each player viewport. It sets up the model skeleton each time an attachment position is accessed and when drawing each model mesh for each player viewport. There is some room for improving Spearmint by caching the skeleton between drawing meshes or accessing attachment points for the same object.
In Toy Box it’s possible to add all objects to draw for the frame and then draw a list of objects for each player viewport. This reduces memory usage and dynamically uploaded geometry. Model skeletons are setup twice (vertex transform, absolute position) and stored in the render object regardless of the number of model meshes or local players.
Setting up a model skeleton (4×4 matrix multiply on 56 bones) is a time consuming operation. For the Turtle Arena player model with four player splitscreen: Spearmint would have to set up skeletons 256 times (4 player models × (12 meshes + 4 get attachment position) × 4 local players); ~3.2 milliseconds. Toy Box only has to setup the skeletons 8 times (4 player models × 2 (transform and absolute) skeletons); ~0.1 milliseconds. It’s a 32× improvement but unfortunately the actually skeleton model drawing is still slower than I would like.
Faster if you aren’t looking
The way I had been viewing the frame time / frame rate was printing a message to the terminal each frame. However on my Raspberry Pi 3B (854×480 7″ touchscreen) this causes a major decrease in performance. Moving the frame rate to be displayed in-game cut ~20 milliseconds off the frame time and allowed moving from ~30 to ~60 frames per-second. (On desktop system in screenshot, printing message each frame is causing 40 to 50 millisecond spikes in frame time.)
Dynamic vertex lag
All dynamic vertexes (such as all 2D drawing and arbitrary 3D polygons) are now uploaded before issuing any draw calls to avoid the OpenGL driver stalling. This caused a notable performance improvement on my Raspberry Pi.
I think it might be one of the reasons why my OpenGL ES 2 compatible version of ioquake3’s opengl2 renderer only runs at ~5 frames per-second on my Raspberry Pi. (Though ioq3 opengl2 does at least ‘orphan’ the buffers to indicate to the OpenGL driver to make a copy of it instead of stalling.)
Originally glBufferSubData() was called each time the rendered material was changed to repeatedly replace the content of one vertex and index buffer. (I’ve read it causes a copy of the buffers to be made.) Now the vertex and index buffers are uploaded before any drawing and there are multiple buffers so that there can be four frames in flight. Current frame and previous frames still being processed.
Uploading vertexes ahead of time required reworking how sprites were handled since they have to face the camera and mirrors. Originally sprite vertexes were generated and uploaded while rendering the scene like in Quake 3. They had to be changed to being uploaded in local space when added to be drawn and rotated like models to face the scene origin.
OpenGL 4.4 persistent mapped buffers are now used if supported. It allows writing dynamic vertexes directly into the memory for the OpenGL driver. This skips a memory copy and reportedly works better than glBufferSubData() on the proprietary Nvidia driver. On my current development system with integrated Intel graphics it doesn’t make any noticeable different though.
It was disappointing considering the difficulty I had getting it to work. On the plus side I learned how to find the source code of where the Mesa open source Intel driver is crashing (wrong vertex buffer bound). It’s pretty nice compared to times the proprietary Nvidia driver has crashed in the past.
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.
Leave a Reply