Long time no see
Hey all, how's it going? I've never really been super active on the forums but you may have noticed that nowadays I make even less posts than usual, plus when it comes to BBMOD, it's mostly been only posts to let you know that there's a new update and what the changelog is and I almost never really share with you the development process, my thoughts on it or my experience with it. So today I thought I could do something a little different and share with you some info like where is the library now in terms of development, some of my thoughts, some fun little statistics etc.
Where is BBMOD now?
The first ever pushed commit into the
BBMOD repo on GitHub was on Feb 15, 2020 and the library has come a long way since then. Currently there are 966 commits and 66 versions released, the latest one being
3.17.0. Initially my plans with the library were just to make a simple binary file format for 3D models and animations that could be read fast and easily from GameMaker (that's why the project is called BBMOD, as in "BlueBurn model"), but with time the library grew a lot and now it focuses more on rendering 3D graphics in GameMaker as a whole. Here is a full list of features that BBMOD has now:
- Maths - vectors, quaternions, dual quaternions, matrices
- Structs for encapsulating vertex formats, vertices, meshes, models, bones, animations, shaders, materials and material properties
- Animation player with support for looping animations, animation transitions, animation events, overriding of bone transforms (e.g. for making a character point their hand in the camera's direction)
- General purpose state machine and animation state machine
- Physically based rendering (PBR) shaders with support for both metallic-roughness and specular color-smoothness workflows
- Support for cheap subsurface scattering and emissive materials
- System for batching multiple dynamic moving models into a single vertex buffer to save draw calls
- Camera - FPS, TPS, perspective, ortho, support for roll, built-in mouselook that works also on HTML5
- Resource manager - custom ref. counting of loaded resources, support for async. loading (required on HTML5)
- Image based lighting with support for HDR rendering (uses RGBM encoding)
- Support for baked lightmaps with a dedicated second UV layer (also HDR with RGBM encoding)
- Dynamic lights - point, spot, directional
- Dynamic shadows for spot and directional lights
- Cubemap struct and rendering into cubemaps
- Reflection probes with area of influence - captured and pre-filtered in-game!
- Render queues, using which you can first prepare all your commands for the GPU and execute them later (possibly multiple times and in different contexts)
- Render passess - ReflectionCapture, Shadows, Deferred/DepthOnly, GBuffer, Forward, Alpha, Id
- Renderer, which handles rendering of your game in multiple passes
- Screen-space ambient occlusion (SSAO) - HBAO inspired, very high quality, using only the depth-buffer
- Mouse-picking of game objects and a gizmo moving, rotating and scaling game objects (multi-selection supported)
- Save system of game objects for level editing
- Terrain - heightmap based with support for 5 material layers (controlled through splatmap)
- Post-processing effects - color grading, chromatic aberration, desaturation, vignette
- FXAA
- CPU soft particles - smooth transitions on intersection with other geometry, module based, support for collisions
- OBJ importer (just for the memes really)
- Dynamic library (dll, dylib) for importing all supported third-party model formats from within your game
- Vertex texture fetching (VTF) on Windows through an extension (GM supports VTF on OpenGL platforms natively)
- Utility functions, like checking whether VTF or multiple render targets (MRT) are supported on the current platform
- Simple raycasting library - sphere, plane, AABB, frustum
- Support for ColMesh - convert BBMOD models into ColMesh
Aside from the BBMOD GML library, there're of course BBMOD CLI,
BBMOD GUI, the
homepage with some
tutorials and the
online documentation.
Little BBMOD project that I made for testing purposes
Code vs docs
Personally I didn't have any expectations from this and I don't know if this is too little or too much for such library, but I thought it could be interesting for some to share the total lines of code in BBMOD and how much of that is the actual code and how much is comments, so I wrote a little tool in Python that counts that for me.
So, as of 3.17.0, the BBMOD GML library contains 32804 non-empty lines in total, out of which 14925 are the actual code, 337 are comments, 13647 are documentation comments from which the online HTML5 documentation is generated and 3895 lines are what I called bull
in my little Python script that measured this, which are lines that contained only a single character - so mostly "{" and "}" (I use the
Allman style indentation).
Personally I was surprised to see that the ratio between code and documentation comments is very close to 50:50, which I knew I'm writing a lot of documentation, but I didn't expect it to be this much
And for shaders included in BBMOD, it is 11523 non-empty lines in total, out of which 6749 are the actual code, 1838 are comments, 1345 are docs (comments starting with "///") and 1591 is bull
again. But bear in mind that I'm actually using
Xpanda to generate multiple shader variations out of a single uber-shader, so I don't have to write them by hand!
I've used https://piechartmaker.co to make these pie-charts.
What I've learnt
It's been a great ride and I think that BBMOD is turning out good, but there are some things that I'd do differently if I was writing it from scratch again, so I'd like to leave them here maybe as tips for others who may try to do something similar.
1) Don't use the same naming conventions as GameMaker does
I started writing the library when GML had no structs yet and I wanted to use the
snake_case
naming for all BBMOD functions to make them follow the GM's standard. Later on when struct were added, I decided to rewrite the entire library into OOP style and use
UpperCamelCase
names for structs and
snake_case
for their methods. This is when I've stumbled upon an issue that I can't just have methods like
floor
,
min
,
max
in vectors or
draw_sprite
in render queues, because of the obvious nameclash with the GM's built-in functions.
GML:
function BBMOD_Vec3(_x, _y, _z) constructor
{
// This won't work:
static floor = function ()
{
return new BBMOD_Vec3(floor(X), floor(Y), floor(Z));
};
}
So for some of the structs that this issue would occur, I've instead decided to use
UpperCamelCase
for method names as well. So now I have a single library with two naming conventions, which just sucks. Now I'm using
UpperCamelCase
in every new project that I have and if I was writing BBMOD again, it would be using this naming everywhere as well.
2) Don't use structs everywhere
I mean this one should be obvious, but while working in C++ and regularly using classes for things like vectors and matrices, I didn't think that using structs for them in GML could have a big impact on performance.
When structs came to GML, I decided to make everything object oriented, because that's what students are taught in CS, so I thought it's going to be easy to learn and understand, plus it's going to be easy for me to write, document, maintain, extend etc.
For one, it turned out that not all GM users are familiar with OOP, because GML is the only language they have experience with and they are accustomed to the pre-structs version of GML, so BBMOD being written OOP style has likely reduced its potential userbase.
Secondly, structs in GML are simply not C structs or C++ classes, but more like garbage-collected
ds_map
s with nicer syntax.
SLOW! Making potentially hundreds or maybe thousands of structs every frame just to throw them away and make new ones the next frame is something that should absolutely be avoided by miles.
But I should add that this applies to any other dynamically allocated structure, whether it's arrays or
ds_
data structures. If you're writing something that's supposed to be high performance, think thoroughly which data structure fits the job the best.
3) ds_grid hacks
Going in hand with 2), when I was working on the CPU particle system for BBMOD, I've figured out that there is just no way I'm having each particle as a separate struct, that was extremely slow and handled only few hundred particles before FPS dropped below 60.
While watching some data oriented design lectures (which in short teaches to have cleverly composed data into arrays and tables and process these in a single go or in parallel), I remembered that GM's has functions like
ds_grid_add_grid_region
and
ds_grid_multiply_grid_region
, using which you can add and multiply together two different regions of
ds_grid
s. So instead of having particles are an array of structs, I have them all in a single grid, where rows are individual particles and columns are their properties like their x,y,z position, color, size, remaining life, velocity etc. If I then want to for example move all particles by their velocity, I can do that with a single call to
ds_grid_add_grid_region
. You can also have a separate table for intermediate results for more complex computations. With this method BBMOD can handle close to thousand of particles instead of only few hundred.
If you're interested in the specific implementation of velocity Verlet integration using this method in BBMOD, you can have a look at the file
BBMOD_ParticleEmitter.gml.
Someone on the forums was looking more closely into this idea, but I can't remember who. Was it
@Bart maybe?
4) Test on all target platforms since the beginning
BBMOD being written in GML and GLSL ES only, I was thinking it's going to Just Workβ’ on all platforms that GM supports. But not having access to HTML5 and macOS for example since the entire beginning, I've only found out later on that some shaders don't compile here and there, some resources don't want to load (I'm looking at you HTML5) or that some things rendered in OpenGL are vertically flipped but they work normally on Windows etc. So if you're working on multi-platform project in GM, I recommend you to test your project on all of them since the beginning and avoid bad surprises later on.
5) Use your libraries in actual projects
I once imagined BBMOD is going to be this ultimate solution for rendering 3D graphics in GameMaker. But I can possibly never make a solution for problems that I don't know they exist. So, when working on a library like this, I recommend using it in an actual project so you can discover problems that otherwise the users would have to deal with. The more general-purpose a library like this is supposed to be, the more real projects it needs to be able to tell whether it actually works!
Where can BBMOD go next?
I still have some ideas left that could be added to BBMOD, like for example a shader for rendering water, I think that would be cool. Also, if a deferred rendering pipeline was added to BBMOD, its G-buffers could be used for more interesting effects like screen-space reflections or screen-space global illumination.
Screen-space global illumination in GM, https://github.com/kraifpatrik/SSGI
Unfinished screen-space reflections
And since BBMOD has support for baked lightmaps and it does already have reflection probes, it would make sense to add in light probes as well. There's also the gizmo and the level saving & loading stuff, so it would make sense to add terrain editing tools as well. Maybe some more post-processing effects like light bloom, sun shafts, motion blur, depth of field etc.
These are all things that could be added in even in the current runtime, but knowing there's going to be the new one with WebGPU and a whole new world of possibilities, I'm currently not rushing into adding new things, as there might be a whole new and much more powerful BBMOD 4 anyways. We will see!
Wrapping it up
Alright, I think that's all I have for now. Hopefully at least some of you have enjoyed reading this. If you have questions about anything said, feel free to hit me up. Cheers!