I've used quite a few graphics APIs. From Canvas2D on the web to my current engine in Vulkan. In this article I'll share with you my journey and provide a guide for anyone wanting to get into graphics.
The Beginning: Canvas2D and JavaScript
When I started making games as a hobby back in 2013, I was only familiar with JavaScript, Perl, Ruby, and PHP. I was a 23 year old web developer who had gone from high school into the industry, skipping university.
Naturally, I used web technology to make games. Enter Canvas2D.
Canvas is pretty easy to use. You create a <canvas>
HTML element, use a function to get a context object, and call methods, like: ctx.fillRect(130, 190, 40, 60);
So, I thought I'd make a game like Snake, but with the ambience of something like flOw.
My game featured a "snake" similar to the one above, but much less beautiful. You would grow tail segments when collecting food.
From the sides of the screen, enemies would fly into view - enemies you can't kill.
There were squares, moving in a straight line across the screen (horizontally or vertically). There were circles, which curved towards the snake but not fast enough to be seeking-missiles. There were hexagons, which would move in random directions, then pause and shoot out 6 triangles (one from each side).
Naturally, the game got more difficult as time went on. As such, the screen filled up with shapes to render. The enemies, the food, and the growing "snake" all taking up valuable compute and rendering time.
JavaScript is a garbage collected language. I was having large slow-downs whenever the garbage collector kicked in.
The way to fix this was to implement pool allocators - ways to re-use memory. Such that the garbage collector didn't clean up the objects. I thought the issue was Canvas being slow - but really it was my understanding. Regardless, I decided to ditch it and move to Unity.
Recreating the game in Unity, I ran into the same issues. C# is also a garbage collected language, and maybe the way I programmed just wasn't up to snuff - I programmed in Unity as if it was JavaScript (that was an option back then, not just C#).
I decided to put the game on ice for a while. I made a different game using Canvas2D again about riding a motorbike to the right, and dodging obstacles. I sent this to a local technical college with an email and was accepted into the Diploma of Game Programming course, despite applications being closed (thanks Marek).
I'm going to skip ahead here, as there's not much to say besides that I got experience in Unity and UE3.
I'd recommend Canvas2D if you are already familiar with web programming and want to get your feet wet with a very simple API. It's missing many features, and although some game engines have been built on it, such as ImpactJS (heavily modified for the game CrossCode), I think we have better web options nowadays.
Diving into the Deep End: OpenGL
I finished my course, started and failed to complete a couple of games with an artist friend I met there, but something was still bugging me. I made so many prototypes in Unity, but I was always fighting with the engine. I could never figure out how to get the look or feel I wanted. And, don't get me started on ScriptableObjects... Plus, I had to implement object pooling in Unity as well to get performance under control.
In 2019 I had the thought that I didn't actually know how to program, so I decided to learn C and OpenGL. This was tough.
To learn OpenGL, I used the fantastic https://learnopengl.com resource. I spent a week at the local university library, all day every day, working through it.
By the end, I could draw very simple things in OpenGL, but still had to work through that first second another five times over the next few years to really understand it.
I watched some videos by TheCherno. I was specifically interested in batch rendering. I didn't realise then that this concept is basically the same as a command buffer, and also conceptually similar to a linear allocator.
I was able to render basically any 2D game I could think of at full FPS using just this small amount of OpenGL (one quad batch renderer). The fact I was only interested in pixel art games at this time made a big difference.
A Focus on Productivity: raylib
Some time later, I decided that all this graphics programming was getting in the way of making things. I didn't know about RenderDoc, NVIDIA NSight, or how to add debug logging to OpenGL. Many days were spent looking at a blank screen or totally incomprehensible shapes and colours flashing across the window.
I chose raylib because it seemed simple and vaguely familiar due to the way rendering works. You first call BeginDrawing
then write various draw calls DrawRectangle..
and then EndDrawing
. It's essentially immediate-mode graphics - similar to Canvas2D, and similar to batch rendering quads.
I worked on various small projects in raylib and enjoyed the experience immensely. It got out of the way and allowed me to develop gameplay systems rather than spending all my time debugging the renderer.
Eventually, though, I ran into a few issues:
raylib is built with OpenGL 3.3 by default, I wanted to use compute shaders, which means I had to rebuild the program in 4.3 mode
raylib doesn't include fences - which are a CPU<->GPU synchronisation mechanism. These are pretty required to know when compute shaders have finished task
These were minor issues as by this point I was very familiar with build systems and modifying existing projects. raylib's source code is very straightforward, so I added the wrapped functions and recompiled from source.
raylib has a fantastic set of samples which you can view on the website. It supports both 2D and 3D, and is more of a framework for interactive applications due to it's other features: audio, input, image loading, and more.
raylib has been used to make commercial games, such as Cat & Onion by Karl Zylinski, though the creator of raylib, Ray, does caution against using it for commercial endeavours.
The Middle Road: Sokol and SDL3
I tried Sokol at some point between OpenGL and raylib. It confused me as there was no concept of a "render pass" or "pipeline" in OpenGL. Of course there is the graphics pipeline - but in OpenGL it's fixed. My experience was: vertex stage -> fragment stage -> done
or compute stage -> done
. Pretty simple.
I didn't use Sokol recently because it didn't have compute shader support. However, as of writing this, it seems it will be added soon.
Looking at Sokol now, it's very easy to understand - but that's because I've already moved onto Vulkan and therefore must have an idea about these concepts. Unfortunately, the jump from OpenGL to Vulkan is large.
What helped me was actually the recently released SDL3's GPU API. Plus a particular page of their documentation. This page walks through step by step what one may do to setup a rendering pipeline using the API. This was the missing link I needed to make the jump to Vulkan.
I think the SDL3 GPU API would be sufficient for most games. The only reason I'm not using it is because I believe Vulkan will be the API I use for the next 20+ years, so I decided to invest in it.
The Present: Vulkan
I am whittling away at the final boss of graphics APIs - Vulkan. It's been a huge learning experience - but the SDL3 GPU API helped reduce confusion.
Besides future investment, I'm interested in creating custom worlds and custom rendering techniques that may be difficult or impossible with APIs besides OpenGL, DirectX or Vulkan.
The control of every aspect of rendering is something I believe will be invaluable.
If you are interested in seeing my progress, I actually implemented this chain of things on-stream recently:
C99 + DX11 Software Renderer
C99 + DX11 Hardware Renderer
Odin + OpenGL Compute Shader Marching Cubes Terrain Generation
Odin + SDL3 GPU API rasterised renderer, model loading, 3D lighting
Odin + Vulkan rasterised renderer (on par with SDL3 one)
Odin + Vulkan raymarching + rasterised renderer
As you can see, I was working with DX11 - similar in difficulty to OpenGL.
I switched to Odin + OpenGL to have more parity with my course material. I teach game programming using Odin + raylib at https://programvideogames.com.
The reason we use raylib is similar to what I stated above - we are focusing on gameplay and systems programming, not graphics programming.
I stayed with Odin, and eventually progressed to Vulkan - where I'm focusing my energy now.
If you are struggling with graphics programming - you are not alone. Nearly everyone I know who has started in the field has struggled. Especially if, like me, their mathematics knowledge unused since high school.
Conclusion
If you are just getting started in graphics programming and are more interested in the end-result (I.E. making a game), then I'd recommend raylib.
For something more advanced, I think SDL3 GPU API or Sokol are the best choices.
The final boss, Vulkan, is not to be undertaken lightly. I did 5 1000+ line hello-triangles before grasping what was going on.
I haven't mentioned wgpu or Metal - I don't have direct experience with them. I have heard good things about wgpu and I know notch is using it for his new game "Levers and Chests". I have read that wgpu is similar to Vulkan, but easier.
So, my order of learning would look like this:
Canvas2D/P5.js
raylib
OpenGL/DX11
Sokol/SDL3 GPU/webgpu
Vulkan/DX12
Obviously, you can skip straight to any API you want - this is just what I think makes sense in order of difficulty to learn and building on previous steps.