Hi there! It’s been a long time since my previous newsletter. More than half a year!
Since then a lot has happened. I got married, went to China for our honeymoon and I’ve done lots of work on different Odin projects!
Karl2D beta 4 and beta-2026-11
For those who aren’t familiar with it, Karl2D is my game creation library that is fully written in Odin. Read more about it on karl2d.com
In October I released Karl2D beta 4, and shortly thereafter I released beta-2026-11. As you can see, I have switched version numbering system. It now matches the system that the Odin compiler uses. The idea is that the Karl2D beta-2026-11 release is tested on the Odin 2026-11 release.
Anyways, beta 3 was released in April. A LOT changed between beta 3 and beta 4, and then some additional things changed in beta-2026-11. Let’s discuss some of the major changes.
Audio mixer thread
When doing software mixing, a separate thread for audio mixing is important. As the number of playing sounds in a game increases, the mixing takes more and more time.
I’ve added a thread for mixing on all platforms that support it. This means every platform except web. Each audio backend runs a thread and calls into a mixing procedure called _mix_audio_into_buffer. There is no longer a “shared mix buffer”, instead each backend has their own mixing buffer. This is good, because different backends need differently sized mixing buffers, because they all have different latency.
I had to add a general audio_mutex that the mixing thread can lock when accessing data. The same mutex is locked when for example play_audio_stream is run, making it possible to add the playing stream to the array that it shares with the mixer.
The only other mutexes is a decode_mutex on each audio stream. Audio streams decode data from disk, and that can happen either on the main thread or on a separate thread. It never happens on the mixer thread. A separate decode thread is often used to make sure that music doesn’t stutter during a frame rate spike or when you grab the window title bar and move the window.
Audio API redesign
The audio API now has three main audio types: Audio_Clip, Sound and Audio_Stream. I’ve stream-lined the API for playing “one shot” audio clips. I found it annoying to have to instantiate sounds before being able to play them (when you want to play multiple instances at the same time). So now you can just do:
k2.play_audio_clip(
clip_shoot,
volume = rand.float32_range(0.7, 0.9),
pan = math.remap_clamped(player.pos.x, 0, SCREEN_WIDTH, -0.5, 0.5),
pitch = rand.float32_range(0.8, 1.2),
)play_audio_clip does not return anything, it will simply play until the clip is done. If you want to control the playback or stop it early, then you can create a Sound from an Audio_Clip. A Sound is like a wrapper around a playing Audio_Clip than can be used to control it. I think most people just want k2.play_audio_clip, so I’ve made that proc as simple to use as possible.
Better font rendering
During the Karl2D Jam the biggest issue people ran into was that the font rendering was... bad. Since then, I’ve stopped using vendor:fontstash and instead made a custom font_cache package. It does similar stuff, but only implements the things I need. It’s faster than fontstash and has better compatibility with how I do stuff. What happens now is that the font atlas grows until it becomes so big that it can’t grow anymore. At that point it instead ejects the 50% least recently used glyphs and rebuilds the atlas.
I have thought about maybe adding slug font rendering. However, it’s perhaps not as obvious a choice that you may think. For example, it needs a separate shader. Currently, Karl2D can render the text with the same shader as the normal textures.
Monitor queries and interface abstraction changes
There are now queries such as get_monitors. They have been carefully designed to be possible to use before k2.init. This makes it possible to choose a monitor before creating the window. In order to facilitate this I made the platform interface accept a Platform_State struct, instead of having the platform layer depend on a global variable. The idea was that the monitor query procs wouldn’t need the state struct, making it obvious that they can be used before init. I did the same to the renderer interface and audio interface, just to have everything same-y.
The main k2 API still has a global state, so you don’t have to pass some Karl2D_State to each Karl2D API proc. But internally the library now uses explicit state passing when talking with the different abstractions for platform, rendering and audio. I think that’s a good balance: I keep the library API less chatty. But the internals don’t have to be designed in the same way.
However, I realized that the platform-specific monitor query procs still needed a ^Platform_State parameter. This is because the monitor queries behave a bit differently before and after init (on some platforms), so they need the state, if it exists. Regardless, I think the explicit state passing is still a win. It made the code easier to read and it made it less likely to accidentally call some platform or rendering procedure.
Touch support for web
On web, you can now use get_touches to fetch the current touches. The touch example works very nicely on web! Try it: https://karl2d.com/examples/touch/
There’s more
Check out the full release notes for Karl2D beta 4 and beta-2026-11. There are a lot of nice improvements in there!
Odin Installer for Windows
Programming beginners may have some trouble getting up and running with Odin on Windows. Downloading the compiler isn’t a big deal. But having to install the required Visual Studio components and Windows SDK can really throw some people off.
Therefore, I have created an Odin Installer that downloads an Odin release as well as the required Visual Studio things. Check it out here.
The Visual Studio things it downloads will live in the bin\msvc subfolder of the Odin compiler. The compiler will look in that folder for the MSVC things it needs. I added that part in a Pull Request into Odin: https://github.com/odin-lang/Odin/pull/8224 . This means that you can have the Visual Studio things in bin\msvc without the need for any additional environment variables. They are simply dumped into there. I think it’s pretty nifty! The whole Odin toolchain becomes portable on Windows.
Slop-free AI programming
AI (LLM) assisted programming has gotten very popular over the last 6 months. I tried it myself and fell into some traps. I recorded what I learned in an article called Slop-free AI programming.
Tom’s Namespaces
I wrote an article about a personal mental journey regarding the package system of Odin and how I “want namespaces for organization”. It turned out pretty popular, have a read: https://zylinski.se/posts/toms-namespaces/
Future plans: Android Karl2D
With the 2026-11 release of Odin, proper Android support was added. My plan is to add Android support to Karl2D as soon as possible. I think that I might have to add a Vulkan rendering backend first. I read somewhere that Android can use GL ES, but I am not sure what the best way forward is. Got any ideas? Please leave a comment. Hopefully I can get it done for the beta-2026-12 release of Karl2D!
Thanks for reading!
If you want to learn more about Odin, then have a look at my book Understanding the Odin Programming Language.
Have a nice day! /Karl Zylinski


