New Project: WASM game framework without Emscripten

Why I am making yet another game engine thing
After making KeyGen Jam in 4 days for a GameJam contest I learned something very important about these contests: People do not want to download free executable files when you can just play someone else's web game. I am very proud of my rhythm game and anyone who has played it is very impressed with it. But it scored badly in the Jam because few people bothered to download it.
KeyGen Jam uses raylib, a simple game framework that can be used to make native PC games as well as web games. It uses Emscripten and renders the game in a WebGL context. The problem was I could not get the gpu shaders to work on a web build in time, so I decided to scrap the web version and focus on making as good of a PC game as I could given the time limit. This isn't the first time I have had troubles porting an existing PC game project to the web using Emscripten. So I wanted a completely different approach to making games for Web and PC. I also just wanted to avoid using WebGL, but more on that later.
Why I don't like Emscripten
Emscripten is a C/C++ compiler that emulates the functionality of POSIX. This allows you to use the C or C++ standard libraries, and also implements a version of SDL2 for handling drawing, sound and input. The pitch behind Emscripten is you can take an existing C/C++ application that uses SDL2 and with minimal work get it running in a browser. The problem from a gamedev perspective is this means your web game needs to emulate a compeletely different platform, so there is quite a bit of bloat here. This means more bandwidth goes to Emscripten's code and JS glue, its less snappy to load. It's also not fun to debug. Your shaders may not work on web and you have no idea why. The game might just be a blank screen and no debug messages. SDL was not designed for the web's quirks so you may have to rewrite your sound engine to get it to work with the web's very limited 128 sample buffer. Pain.

This is not ideal. I feel like every time I make a new feature it is east to get it working on PC and then it takes twice that time to get it working in Emscripten. This is what killed my web build of KeyGen Jam. So instead I am just going to use Clang to directly build to WASM. This requires the "Game" part of the engine to not use the standard libraries and for me to write my own JS platform glue. So far this hasn't been much of a problem, because on web builds I can call JS functions and on the PC builds I can have the "engine" part of the code have access to the standard library. All I need to do is figure out what new "engine" functions the game needs and implement those twice.
Because of how simple this game framework is, I am calling it Skelly, the bare bones game framework.
Ok, but why return to software rendering like it's the 90s?
In the past, I have already tried to make a software rendering game engine. I used the CPU to directly change pixel colors in an array and blit that array on screen using SDL. Of course, I then used Emscripten for the web build and I did not have a fun time... But at the time I chose to go the software route instead of conventional hardware acceleration with the GPU because
- I just wanted to learn a new technique
- I got annoyed with graphics APIs.
But since I have started that old project I think things have only gotten worse for the graphics card, and I think now I have a reasonable case for why you should abandon it when making 2D pixel art games or low-poly retro 3D games.
The case against hardware accelerated graphics (in Indie games):
- The tech has stopped improving by much. It is no longer the case that future cards at a given price point will be better than present cards at that price point.
- AI has made new computer components expensive. Gamers are responding by not upgrading and choosing to play older games or Indie games that do not rely on lots of graphics card number crunching.
- AI and other proprietary forces have turned GPU coding into choosing a walled garden. You need to use DX12 if you want to support an Xbox platform. You need to use Metal to support Apple devices. Sony has their own proprietary API that you must use. Even if you choose a "cross platform" graphics API like OpenGL or Vulkan, there are obvious holes in each. OpenGL is deprecated on Apple/IOS. Vulkan can not be built for Web.
- A cross platform hardware accelerated game needs to support multiple graphics APIs, and thus multiple copies of the same shader code in different shader languages.
- Solo or small team indie devs can not really make use of the power in these APIs.
It's worth pointing out that I am by no means the only person pointing out these problems. Here's Jonathan Blow pointing out the problems of modern graphics APIs, This programing talk by Sos on shipping a game with a software renderer got me started down this road, and there's also multiple 3D software rasterizing demos proving you can do alot with this on modern CPUs like Tsoding's rasterizer and Sebastian Langue's rasterizer.
Abandoning the graphics card will make your game extremely easy to port to any platform. DOOM is a software rendered game and this is why DOOM will run on anything.
Ok, but I need to talk about the only downsides to this approach
The only downside is the higher the screen resolution someone is using, the more CPU time is needed to modify pixels in an array. The easy fix to this is to render at a lower resolution and scale up to a larger resolution. This would require a GPU to be used for this final up-scaling, but because this is a common use-case for a GPU, both SDL and JS have built in functions for up-scaling which mean you do not have to touch any of that nasty graphics API stuff. Even if you had to, up-scaling the final frame onto the screen would be the easiest thing to implement in each API.
This does mean that your game's internal resolution would have to be something that can upscale to common monitor resolutions, which means either running at 320x180 or more likely 640x360. This will result in perfectly square crisp pixels at 720p, 1080p, 1440p, 2160p, etc and each resolution will take the same amount of CPU work to render for. You just have to accept the game will be pixelated, but people love indie games with pixelated graphics so this is perfectly fine for many games.
My progress so far:
At the time of this post I have a 640x360 game demo running at around 60 fps on the web. Due to the browser needing to stop the game thread to run it's own code, sometimes this dips to 50 fps for a frame and that is out of my control. The web isn't an ideal gaming platform. You can click on the game to fullscreen it.
I also have keyboard input working, so you can move something around with WASD and see the game work.
You can try out a demo here.
One of the biggest surprise with this project is that the game feels like it loads instantly. Fullscreen loads instantly. To the end user, this would feel noticeably faster than if the same thing was made using Emscripten.
