Just a friendly warning that this is a very long and technical blog post. My main reason for writing it was that I needed to rethink what I had done and how this stuff came into existence. You have been warned! 
My homebrew game project started around June 2018 shortly after discovering some essential documentation of the Jazz2 file formats. The earliest prototypes were written in Python3 since that was easy to modify and run without any headaches about compiling or memory leaks and memory pointers. In a few months I had an almost playable game running in Python (using Pygame/SDL) but it had some issues I did not like very much. Things like screen tearing and not being able to show the game to anyone without a full PC or laptop bugged me the most.
The main thing I really needed after having worked a lot on a 3DS version was a way to easily debug the code on my PC. Running it in the Citra emulator helps...but that is not exactly what I wanted to do. I wanted to use Linux tools to find memory leaks, fix them and figure out where in the code things crashed on the 3DS! To make this possible I would need a way to compile the same code for both 3DS and Linux with very little difference between them. To do this I had to start over from scratch!
I created a new makefile and created a directory called 'ports' and made sub directories for each platform: 3ds, gc, linux and wii! For the consoles I used the "sprites" demo's as a basis and on Linux it was OpenGL from the start but I tried glut and glfw before settling on glfw. I just wanted to do "make 3ds", "make gc", "make linux" and "make wii" to get my code compiled for those platforms. Then adding "-emu" for the consoles to start the emulator with the executable compiled just before. For linux I chose "-run" as that made more sense.
With the abstraction from native libraries and details provided by the "drivers" it actually becomes really easy to make a game or app that works on multiple platforms. You just call a few functions at certain times and you are already running something that you can compile for any of those platforms supported! It's true that right now I am only really using it for a single game, but the Installer I also made on certain platforms is actually a separate "app" using the same "libraries" I have described above.
It's true that the three newest platforms have not gotten the installer yet, but in theory it should compile for them just fine. It's more that I have tried rewriting the installer to have a nicer GUI and that kind of failed and needs repairs. 
Please forgive me for writing so much text to explain how my multi-platform code is structured and working. I hope it was not boring or complicated and you enjoyed it...and maybe even learned a thing or two!
Thank you for your time, and until next time.
My homebrew game project started around June 2018 shortly after discovering some essential documentation of the Jazz2 file formats. The earliest prototypes were written in Python3 since that was easy to modify and run without any headaches about compiling or memory leaks and memory pointers. In a few months I had an almost playable game running in Python (using Pygame/SDL) but it had some issues I did not like very much. Things like screen tearing and not being able to show the game to anyone without a full PC or laptop bugged me the most.
While I wanted the game to be portable, as in on a portable device, I actually wanted to write a game for the GameCube after having bought my first one and reading a lot of technical documentation about it. I still had no idea how to do it but I did try looking at examples and stuff and how I could actually compile something for the gamecube. I am not sure if I tried devkitpro right away for something on the gamecube....but during my research I kind of looked at the NDS. The graphics part of the GameCube scared me and gave me headaches...I really did not understand how it worked! 
The NDS is off course less powerful than a GameCube but the way it's GPU works is very similar to what I knew form the Sega Mega Drive/Genesis...that I had wrote a demo for once but that's a story for another time.
. Goging through the examples in devkitpro and compiling them, tweaking them, adding my own stuff...I managed to get the BatteryCheck menu mostly working and it could show the splashscreens. The problem was that all graphics had to be converted into png first, then resized to fit on the small NDS screen and the resized images looked awful. Until I learned about the lanczos algorithm that took out a lot of the rough edges. This still did not really feel like what I was looking for and the compromises for the small screen was not something I could live with. Which made me move to the 3DS!
Now the 3DS is a whole different story! The examples looked a lot more complex but a lot of the complexity was handled by the citro2d and citro3d libraries that come with devkitpro for the 3DS. There was still the requirement to convert the graphics into textures at compile time on the PC but this time they at least looked normal and it was not required to scale them down! Sure, the lower resolution of the 3DS compared to the original 640x480 is still noticeable. But now I had a portable version I could work on and show to anyone who did not really want to see it!!!

...some people got a little tired of me 
To make the screen move in a way I wanted though I had to use something deeply hidden in the citro3d library and it took me a few days to workaround it. I even had a thread on here about that and shared my solution really had no idea that was in September 2018! Time really goes fast!
Within a couple of weeks I actually had a pretty good working and playable demo in a level I designed myself. It was a copy-paste version of the first BatteryCheck level I put together to test out a few things like: walking, jumping, falling, collisions, etc. I made it in the Tiled level editor with help of exported graphics from my python scripts. Then I converted the saved level and converted that back into something I could use on the 3DS. While this was working there was a really big flaw in my workflow....debugging was just impossible! If the game crashed on the 3DS I had no good way to figure out what happened and which bit of code caused the problem. If the jump was off that was "fixable" but an actual crash into a memory dump screen (forgot the exact name) is really taking guesses of what might be it.
While working on the 3DS and reading into how citro2d and citro3d actually did things I learned about textures, vertices, UV coordinates and all that fun stuff normally used in 3D games. I tried a few weird thing in python to see if I had understood the concepts and had great results actually! Then I did a few OpenGL tutorials to learn how to use it and make a PC version of my game. But I also still wanted to run it on the 3DS! So what was my solution to this? I created an abstraction layer that is just a thin wrapper around either OpenGL on the PC or citro2d on the 3DS....and that is how that all started!

The NDS is off course less powerful than a GameCube but the way it's GPU works is very similar to what I knew form the Sega Mega Drive/Genesis...that I had wrote a demo for once but that's a story for another time.
Now the 3DS is a whole different story! The examples looked a lot more complex but a lot of the complexity was handled by the citro2d and citro3d libraries that come with devkitpro for the 3DS. There was still the requirement to convert the graphics into textures at compile time on the PC but this time they at least looked normal and it was not required to scale them down! Sure, the lower resolution of the 3DS compared to the original 640x480 is still noticeable. But now I had a portable version I could work on and show to anyone who did not really want to see it!!!

...some people got a little tired of me To make the screen move in a way I wanted though I had to use something deeply hidden in the citro3d library and it took me a few days to workaround it. I even had a thread on here about that and shared my solution really had no idea that was in September 2018! Time really goes fast!
Within a couple of weeks I actually had a pretty good working and playable demo in a level I designed myself. It was a copy-paste version of the first BatteryCheck level I put together to test out a few things like: walking, jumping, falling, collisions, etc. I made it in the Tiled level editor with help of exported graphics from my python scripts. Then I converted the saved level and converted that back into something I could use on the 3DS. While this was working there was a really big flaw in my workflow....debugging was just impossible! If the game crashed on the 3DS I had no good way to figure out what happened and which bit of code caused the problem. If the jump was off that was "fixable" but an actual crash into a memory dump screen (forgot the exact name) is really taking guesses of what might be it.
While working on the 3DS and reading into how citro2d and citro3d actually did things I learned about textures, vertices, UV coordinates and all that fun stuff normally used in 3D games. I tried a few weird thing in python to see if I had understood the concepts and had great results actually! Then I did a few OpenGL tutorials to learn how to use it and make a PC version of my game. But I also still wanted to run it on the 3DS! So what was my solution to this? I created an abstraction layer that is just a thin wrapper around either OpenGL on the PC or citro2d on the 3DS....and that is how that all started!
The main thing I really needed after having worked a lot on a 3DS version was a way to easily debug the code on my PC. Running it in the Citra emulator helps...but that is not exactly what I wanted to do. I wanted to use Linux tools to find memory leaks, fix them and figure out where in the code things crashed on the 3DS! To make this possible I would need a way to compile the same code for both 3DS and Linux with very little difference between them. To do this I had to start over from scratch!
I created a new makefile and created a directory called 'ports' and made sub directories for each platform: 3ds, gc, linux and wii! For the consoles I used the "sprites" demo's as a basis and on Linux it was OpenGL from the start but I tried glut and glfw before settling on glfw. I just wanted to do "make 3ds", "make gc", "make linux" and "make wii" to get my code compiled for those platforms. Then adding "-emu" for the consoles to start the emulator with the executable compiled just before. For linux I chose "-run" as that made more sense.
There is a main Makefile that I use at the "root" of my project folder that in the beginning had all these command's as compile targets. But that became a little complex to maintain so I split those up into smaller ones for each platform.
It took me a month or two to switch to this structure actually but essentially this is still what I use today! It just grew a bit with more platforms now 
Then for example the specific "makefile-3ds.mk" holds the actual commands for the 3DS:
And in the "./ports/3ds" directory is the "real" makefile that is the same as in the devkitpro examples with a few tweaks here and there. But that same idea is repeated for all the other platforms I have added over the years. Only for Linux I had to write the Makefile from scratch to work in a similar way to how Devkitpro is setup so it gives the same per-file output and same recursive search of source files and to put all compiled .o and .d files into a "build-linux" directory and stuff like that.
The "ports" directories also hold the source code that is unique for each platform. Think of it like a miniature driver to access the GPU, AUDIO, FileSystem and INPUT of a system. There is (or should) not be ANY game or application specific code in these directories!
Code:
### Choose which type of engine to build
#export APPNAME :=batterycheck
#export APPNAME:=jazz2
export APPNAME := squares
PORTSDIR := ./ports
include $(PORTSDIR)/make-linux.mk
include $(PORTSDIR)/make-gc.mk
include $(PORTSDIR)/make-wii.mk
include $(PORTSDIR)/make-3ds.mk
clean:
@$(MAKE) --no-print-directory linux-clean
@$(MAKE) --no-print-directory wii-clean
@$(MAKE) --no-print-directory 3ds-clean
Then for example the specific "makefile-3ds.mk" holds the actual commands for the 3DS:
Code:
3ds:
@cd $(PORTSDIR)/3ds && $(MAKE) --no-print-directory
@cp $(PORTSDIR)/3ds/$(APPNAME).3dsx ./$(APPNAME).3dsx
3ds-emu:
@citra $(PORTSDIR)/3ds/$(APPNAME).3dsx
3ds-link:
@3dslink ./$(APPNAME).3dsx
3ds-demo:
@cd ./source_/3ds && $(MAKE) --no-print-directory
@cp ./source_/3ds/batcheck-demo.3dsx ./batcheck-demo.3dsx
3ds-clean:
@cd $(PORTSDIR)/3ds && $(MAKE) --no-print-directory clean
@rm -fr ./*.3dsx
The "ports" directories also hold the source code that is unique for each platform. Think of it like a miniature driver to access the GPU, AUDIO, FileSystem and INPUT of a system. There is (or should) not be ANY game or application specific code in these directories!
To make a game or application work on any of the platforms that are supported in the "ports" directory without actually using any native function calls we need "abstraction functions" that are commonly implemented into the "platform drivers" to give access to Graphics, Audio, Storage and Input. The only exception to this rule is when a platform uses a big-endian architecture that needs byte swapping or other methods to make shared assets readable. Most notable currently supported big-endian platforms are: GameCube, Wii and Wii U. If in the future support is added for: XBOX 360 or PlayStation 3 then they benefit from the big-endian compatibility since all these consoles have a PowerPC CPU.
Here is a nearly complete list of the functions that are in each "platform driver":
- sysMainLoop() - called in the main loop of the code to do whatever the platform requires for that. Returns false if the app should exit.
- sysExitLoop() - called just before exiting your game or app to do a clean exit
- jzGPU_init(width, height) - This name should already imply what it does obviously, but besides initializing the graphics it also does FileSystem stuff in most cases.
- jzGPU_startFrame() - called at the start of a frame
- jzGPU_endFrame() - called at the end of a frame. this is where the framebuffers are swapped and updated on screen
- jzGPU_exit() - does whatever a platform requires to be done for shutting down the graphics. And the FileSystem if that was done in init().
- jzGPU_createTexture(img) - creates a texture from an image and returns the ID. On most platforms this creates real GPU textures.
- jzGPU_drawQuadRGBA(x,y,width, height, r, g, b, a, depth - draws a colored rectangle using the GPU. It has MANY aliases to not having to specify all these parameters every time
- jzGPU_drawQuadTex(sheet, x, y, tx, ty, w, h, flip_h, depth) - This draws a specified part of a texture on the screen. It's the basis of every sprite and tile that is drawn!
- jzGPU_translate(x, y, z) - translates the offsets to the current matrix (see glTranslatef for detailed info)
jzCONTROL:
- jzCONTROL_init() - does what is required to setup input
- jzCONTROL_GetButtons() - reads the buttons on the platform and translates them into something common and shared among platforms.
jzFS:
- jzFS_File::jzFS_File(filename) - part of a class that opens files and provides useful Jazz2 related functions, this function only opens the file using a platform specific path prefix. (and also default dir)
jzAUDIO:
- jzAUDIO_init() - setup and initialize the audio system on a platform
- jzAUDIO_update() - While not required on all platforms, this function is called in the mainloop of the game or app in case it's required!
- jzAUDIO_exit() - does whatever is required to shutdown the audio
- jzAUDIO_loadMusic() - not very flexible yet, but on platforms that support it the background music is loaded
- jzAUDIO_initSample(soundID, data, length) - loads a raw audio sample into the audio driver (for sound effects)
- jzAUDIO_playSampleID(voice, soundID) - plays a sound effect loaded with initSample on the specified voice/channel
There are many aliases defined in a header for the GPU functions for specific situations, and there are actually a few very complicated jzGPU functions that draw trapezoids specific to the water effect in BatteryCheck. I should really try to rewrite those to be less complex so the GPU driver's for a platform do not have to care about them so much. For a few of them I already did that by merging it but that needs to be shared over to the others. Complex stuff...sorry
. But I guess if you even made it this far in you must be either a developer or very interested in the subject anyway 
Other than that, these are ALL the actual function calls that make my "multi-platform game engine" magic work really.
. On top of these low-level driver functions are many macro's and classes that help make things easier to use. For example to draw a tile there is a function simply called "jzGPU_drawTile(x, y, index, flip_h, depth)" to draw a tile at the specified coordinates.
There are a lot of assumptions behind those helper functions though and that is part of the reason my code is not public and open source yet. It's a mess and needs refactoring and documentation! 

This abstraction layer is the only reason it has been possible to port my game to as many platforms as I have. All that's really needed to add a platform is setup it's makefile environment I explained in the spoiler block above, and then for each of these "driver functions" you need to write the platform specific code to make it perform the required functionality. Whether that is initializing graphics, reading the buttons of a controller, drawing a rectangle or drawing a sprite.....that is all it really takes!
Usually I just copy one of the existing platforms and then clean out all the code in the functions related to the platform. Remove or comment is kind of the same. It's just a way to make the compile happy that the driver function exists and does not complain about missing functions that belong on the other platform. If I release this "ports" driver library on github one day I would include a "template" that is empty but contains comments to instruct what should be happening in that function.
Here is a nearly complete list of the functions that are in each "platform driver":
- sysMainLoop() - called in the main loop of the code to do whatever the platform requires for that. Returns false if the app should exit.
- sysExitLoop() - called just before exiting your game or app to do a clean exit
- jzGPU_init(width, height) - This name should already imply what it does obviously, but besides initializing the graphics it also does FileSystem stuff in most cases.
- jzGPU_startFrame() - called at the start of a frame
- jzGPU_endFrame() - called at the end of a frame. this is where the framebuffers are swapped and updated on screen
- jzGPU_exit() - does whatever a platform requires to be done for shutting down the graphics. And the FileSystem if that was done in init().
- jzGPU_createTexture(img) - creates a texture from an image and returns the ID. On most platforms this creates real GPU textures.
- jzGPU_drawQuadRGBA(x,y,width, height, r, g, b, a, depth - draws a colored rectangle using the GPU. It has MANY aliases to not having to specify all these parameters every time
- jzGPU_drawQuadTex(sheet, x, y, tx, ty, w, h, flip_h, depth) - This draws a specified part of a texture on the screen. It's the basis of every sprite and tile that is drawn!
- jzGPU_translate(x, y, z) - translates the offsets to the current matrix (see glTranslatef for detailed info)
jzCONTROL:
- jzCONTROL_init() - does what is required to setup input
- jzCONTROL_GetButtons() - reads the buttons on the platform and translates them into something common and shared among platforms.
jzFS:
- jzFS_File::jzFS_File(filename) - part of a class that opens files and provides useful Jazz2 related functions, this function only opens the file using a platform specific path prefix. (and also default dir)
jzAUDIO:
- jzAUDIO_init() - setup and initialize the audio system on a platform
- jzAUDIO_update() - While not required on all platforms, this function is called in the mainloop of the game or app in case it's required!
- jzAUDIO_exit() - does whatever is required to shutdown the audio
- jzAUDIO_loadMusic() - not very flexible yet, but on platforms that support it the background music is loaded
- jzAUDIO_initSample(soundID, data, length) - loads a raw audio sample into the audio driver (for sound effects)
- jzAUDIO_playSampleID(voice, soundID) - plays a sound effect loaded with initSample on the specified voice/channel
There are many aliases defined in a header for the GPU functions for specific situations, and there are actually a few very complicated jzGPU functions that draw trapezoids specific to the water effect in BatteryCheck. I should really try to rewrite those to be less complex so the GPU driver's for a platform do not have to care about them so much. For a few of them I already did that by merging it but that needs to be shared over to the others. Complex stuff...sorry
Other than that, these are ALL the actual function calls that make my "multi-platform game engine" magic work really.
This abstraction layer is the only reason it has been possible to port my game to as many platforms as I have. All that's really needed to add a platform is setup it's makefile environment I explained in the spoiler block above, and then for each of these "driver functions" you need to write the platform specific code to make it perform the required functionality. Whether that is initializing graphics, reading the buttons of a controller, drawing a rectangle or drawing a sprite.....that is all it really takes!
Usually I just copy one of the existing platforms and then clean out all the code in the functions related to the platform. Remove or comment is kind of the same. It's just a way to make the compile happy that the driver function exists and does not complain about missing functions that belong on the other platform. If I release this "ports" driver library on github one day I would include a "template" that is empty but contains comments to instruct what should be happening in that function.
With the abstraction from native libraries and details provided by the "drivers" it actually becomes really easy to make a game or app that works on multiple platforms. You just call a few functions at certain times and you are already running something that you can compile for any of those platforms supported! It's true that right now I am only really using it for a single game, but the Installer I also made on certain platforms is actually a separate "app" using the same "libraries" I have described above.
Working on the Wii U was quite difficult as I could not find understandable examples that I could translate and rewrite into my driver structure. The startup code worked fine and inputs are working too, but graphics and audio were not that simple! Since I saw that SDL2 was ported to the WiiU I though why not try it anyway....I am not a fan of SDL because of my issues from the Python experiments. It has it's place for simple things...but I liked the performance of using OpenGL and similar systems a lot more.
Now the WiiU port is using SDL2 for both graphics and audio....it even supports the background music! Because of a stupid mistake the SFX sound horrible....I fixed that by the way but only shared that copy on discord! Whoops! 
Then I gave myself a challenge last week to port the game to the Nintendo Switch in 8 hours! That would have been nearly impossible if I did not have this driver and build system infrastructure ready! Setting up the makefiles took less than an hour but getting graphics working was about as difficult as it was on the Wii U! Not because there were not enough examples....but more that my understanding of the more modern method of using OpenGL is not there yet! I mean stuff like shaders, draw buffers and whatever it's all called!
The difficult part of making it work on the switch using only the yuzu emulator was that I could not get any debug info if the game crashed! Also my usual method of "printf" seemed to be invisible! Making it hard to pinpoint the trouble spots! The work I did on the Wii U by using SDL2 actually saved my challange...without it I would not have succeeded! There was one issue though with the colors...for whatever reason everything had a red shade over it. I had to tweak my texture generation to swap the RGBA values arround until it looked normal. Also the transparency needed some tweaks not required on the WiiU but whatever...it WORKED!
Getting the music working was a bit more work as that seems to crash yuzu...or at least it triggers something that makes it not work. Then someone on discord
offered to run it on his switch and a few versions later it had working sound effects and background music on a real switch!!! but not in yuzu!
I have found a different switch emulator called Ryujinx that has it's own issues with stability....but at least there it works and plays the music! 
Then I gave myself a challenge last week to port the game to the Nintendo Switch in 8 hours! That would have been nearly impossible if I did not have this driver and build system infrastructure ready! Setting up the makefiles took less than an hour but getting graphics working was about as difficult as it was on the Wii U! Not because there were not enough examples....but more that my understanding of the more modern method of using OpenGL is not there yet! I mean stuff like shaders, draw buffers and whatever it's all called!

The difficult part of making it work on the switch using only the yuzu emulator was that I could not get any debug info if the game crashed! Also my usual method of "printf" seemed to be invisible! Making it hard to pinpoint the trouble spots! The work I did on the Wii U by using SDL2 actually saved my challange...without it I would not have succeeded! There was one issue though with the colors...for whatever reason everything had a red shade over it. I had to tweak my texture generation to swap the RGBA values arround until it looked normal. Also the transparency needed some tweaks not required on the WiiU but whatever...it WORKED!
offered to run it on his switch and a few versions later it had working sound effects and background music on a real switch!!! but not in yuzu!
My issues against SDL2 have not been a secret in my other posts I think....but using it also gave me a change to rethink how my "Drivers" currently work. I have looked at the sourcecode for SDL to see how it does things and how it abstracts platforms and other libraries. Some might say it does it better than me....they might be right...but I want to be closer to the hardware.
What I was thinking about is that on some platforms it might be possible to use different libraries or back ends to do certain things! What I mean is that now that I have a GPU driver for my game engine that already works on WiiU and switch...maybe I can reuse it on other new platforms too. Then make the platform work faster if it has SDL2 support and later use the native libraries! That might save some time!
The way I am thinking of doing it is making a new directory structure that separates the platform drivers and the backends! For example the GameCube and Wii can share the GX backend. Linux and PSP both use libmikmod directly. It's little things like that....but really complex to explain actually. I really hope I am not boring people with this enormous blog post...hahahaha

. But among the GX, OpenGL and Citro2D backends for graphics I could add SDL2 and maybe rewrite it a bit for SDL1 too. Then the platform drivers would became a bit simpler by just selecting backend drivers instead of having the entire implementation. It prevents duplicated code I think...and that in turn makes things easier to understand and maintain. But at the same time I would need to keep an eye on making sure that this separated code idea does not introduce slower code! maybe I could use compiler macro's and just include some headers to select a certain backend on a platform. I am really not sure yet how to implement it 
The way I am thinking of doing it is making a new directory structure that separates the platform drivers and the backends! For example the GameCube and Wii can share the GX backend. Linux and PSP both use libmikmod directly. It's little things like that....but really complex to explain actually. I really hope I am not boring people with this enormous blog post...hahahaha


. But among the GX, OpenGL and Citro2D backends for graphics I could add SDL2 and maybe rewrite it a bit for SDL1 too. Then the platform drivers would became a bit simpler by just selecting backend drivers instead of having the entire implementation. It prevents duplicated code I think...and that in turn makes things easier to understand and maintain. But at the same time I would need to keep an eye on making sure that this separated code idea does not introduce slower code! maybe I could use compiler macro's and just include some headers to select a certain backend on a platform. I am really not sure yet how to implement it 
Please forgive me for writing so much text to explain how my multi-platform code is structured and working. I hope it was not boring or complicated and you enjoyed it...and maybe even learned a thing or two!
Thank you for your time, and until next time.