It’s been a reasonably productive week. Work continues apace at switching to modern OpenGL, and I’m taking the opportunity to minimize some pain-points while I’m at it. The fruits of this labor are being gradually back-ported to RLTK – currently in a private branch, I’ll publish it once it’s more useful.
First of all, why the switch? There’s a few reasons:
- I was getting really fed up with OpenGL extensions. Even with GLEW (which manages them for you), I’d write some perfectly normal looking
glBindVertexArraycode (or similar) – and it would work great on 2/3 platforms (generally Linux and Windows) and I’d find myself in
#ifdefhell getting it to work on all three. As an experiment, switching to a 3.3 Core Profile cleaned that up and the code runs unmodified on all three every time.
- Apple’s OpenGL implementation is poor. On every other platform, you can request an OpenGL 3.3 “compatibility” profile, and have all of the old 1.2 shaders work (so SFML works). On Apple, it denies the compatibility options – so any SFML render code gives you a black screen (you can work around it by doing everything yourself, but at that point SFML has lost it’s appeal – it really isn’t helping!). Since almost everything uses modern OpenGL (I’d love to use 4.5, which 2/3 platforms support – but Apple are stuck at 4.1 with the new releases, 3.3 with older). 3.3 has what I need, so it’s a real boon to get its features without extension fighting.
- I did some benchmarking. SFML, a compatibility profile, and using SFML’s GL wrappers for things like vertex buffers gives okay performance. A light
glfw3program in GLSL 1.2 mode gives a small speed increase (presumably because SFML is trying to do so much). Switching to GLSL 3.3 – and removing any traces of the legacy fixed function pipeline – gives a really big speed improvement – around 10x faster per frame in some test cases, such as rendering a big psuedo-ASCII grid! (Most roguelikes don’t have to worry too much about FPS; Dwarf Fortress-like games have to worry because so much else wants to eat your CPU).
I generally don’t recommend changing technology mid-stream unless you hit something insurmountable, but I have the advantage that cleaning up code-bases is a large part of what I do for a living (more so than making them!), so it isn’t really too bad for me.
So, onto what’s changed:
- I’ve switched to PCG for random number generation. It’s lightweight, fast, and gives good randomness. This was super-easy to back-port to RLTK.
- SFML is gone, and I’m using GLFW instead. When I get to audio, I’ll just use OpenAL or similar – SFML is basically a wrapper for it anyway.
- RLTK’s noise funcitonality is replaced with FastNoise. This is back-ported in the branch. I was already using FastNoise in Nox Futura, so this was a no-brainer.
- It was annoying me, and making debugging more tricky, that many features fired up their own thread. You’d see tonnes of thread creation in the debugger – and threads were pretty short lived. Worse, OpenMP behaves quite differently on different platforms; under Apple it used a pool and threads were quite reasonably re-used. The same was true with Windows, if I compiled with Visual Studio – but not MSYS, which simply spammed out threads everywhere. Linux just made lots and lots of threads. I’ve added a thread pool which is helping immensely with this. I’m gradually moving threaded code over to the new regime, and the time gains from not creating/destroying threads are absolutely worth the headache.
- The telemetry functions have been rewritten to use LibCURL rather than SFML’s network code. This was pretty easy, since it’s just an HTTP post. I cleaned up a bit of the threaded queue – telemetry submissions are batched into a multi-writer single-reader multi-threaded queue, and a reader thread periodically sends telemetry out (if the user has allowed me to do so).
- Reimplimented SFML’s image loading with the excellent STB image loader. I’m using various other features from STB – highly recommended.
- Merged in Dear ImGui using plain old OpenGL 3.3 bindings. It’s faster, and considerably more predictable. In particular, input handling is easier to work with because you aren’t shimming through SFML’s existing input system.
- Replaced SFML’s sprite functionality with a simple sprite blitter (it handles fading, darkening, rotation and translation – all in shader code, REALLY fast).
- Cleaned up the assets folder.
- Implemented array textures, for fast texture swapping and a huge reduction in the number of state changes/draw codes. This is a HUGE win, and is the #1 problem I was having with relying on extensions; they just didn’t seem to work consistently across platforms.
- To validate all of this, created a new splash page. I went a little crazy on features, mostly for testing purposes – I’ll scale it back for a more final release. The Bracket Productions logo spins onto the screen, fading in as it does so. Meanwhile, threaded/async functions initialize the global state – loading the raw files from Lua, ensuring that important textures are in place, and so on. It’s actually pretty straightforward thanks to the library work I’ve done, but it’s a great debug testbed.
- The main menu is now using the new regime, and includes the much-promised Patreon Thank You list. I have a bit of alignment to clean up, but it’s functional. (The colors are default ImGui; theming is coming very soon).
- World-gen deserves it’s own section – see below.
World-gen is a fun topic. The base is done (with more content to come), and has been covered extensively in my past posts. The biggest issues are that it takes a while, and it’s the first thing anybody sees when they try to play the game – so it needs to be pretty and not too boring! It also serves as the basis of the in-game world map (a future topic!), so it should be re-usable.
To that end, world-gen now has a few render stages:
- Step 1 shows the options for creating your world. There’s a lot more to come here, and ImGui is making it easy to offer options. It’s largely unchanged, other than being a little easier on the programmer!
- Step 2 is where the basic world parameters are defined. A height-map (noise-based), as well as things like precipitation maps. The world gets divided into water/land, and then further divided into categories (mountains, hills, marsh, etc.) and biomes (e.g. sub-tropical savanna). For this phase, I rendered the world as a globe – spinning in space. It looked a little barren, so I added a star-field background. I always loved the globe in X-COM, so it was a big influence here.
- Step 3 runs decades of history (defaulting to a century); civilizations are placed, and their units go to war, spreading out across the world. This is better represented by a mostly-2D map (it’s more stylized than physical at this point; otherwise, interesting features would be thousands of miles tall!). A sudden transition works, but is dull – so I came up with an animation that “unfolds” the world into a horribly inaccurate Mercator projection (and currently spins around it, but that will change).
- World-gen in action– I’m getting to be quite proud of this!