shithub: cstory

Download patch

ref: 616b9cda9e94d3bb026bd1b79dd57f9a2187cbc0
parent: 53e4742e38f912b025796c632d2712778fe7ba0a
author: Clownacy <Clownacy@users.noreply.github.com>
date: Wed May 6 18:40:10 EDT 2020

Rearrange shader files

The old way just kind of mashed two approaches together.

--- /dev/null
+++ b/src/Backends/Controller/GLFW3.cpp
@@ -1,0 +1,169 @@
+#include "../Controller.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+
+#define GLFW_INCLUDE_NONE
+#include <GLFW/glfw3.h>
+
+#include "../Misc.h"
+
+#define DEADZONE (10000.0f / 32767.0f)
+
+static bool joystick_connected;
+static int connected_joystick_id;
+
+static float *axis_neutrals;
+
+static void JoystickCallback(int joystick_id, int event)
+{
+	switch (event)
+	{
+		case GLFW_CONNECTED:
+			Backend_PrintInfo("Joystick #%d connected - %s", joystick_id, glfwGetJoystickName(joystick_id));
+
+			if (!joystick_connected)
+			{
+				int total_axes;
+				const float *axes = glfwGetJoystickAxes(joystick_id, &total_axes);
+
+				int total_buttons;
+				const unsigned char *buttons = glfwGetJoystickButtons(joystick_id, &total_buttons);
+
+				if (total_axes >= 2 && total_buttons >= 6)
+				{
+#if GLFW_VERSION_MAJOR > 3 || (GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR >= 3)
+					if (glfwJoystickIsGamepad(joystick_id) == GLFW_TRUE)	// Avoid selecting things like laptop touchpads
+#endif
+					{
+						// Set up neutral axes
+						axis_neutrals = (float*)malloc(sizeof(float) * total_axes);
+
+						if (axis_neutrals != NULL)
+						{
+							for (int i = 0; i < total_axes; ++i)
+								axis_neutrals[i] = axes[i];
+
+							Backend_PrintInfo("Joystick #%d selected\n", joystick_id);
+							joystick_connected = true;
+							connected_joystick_id = joystick_id;
+						}
+						else
+						{
+							Backend_PrintError("Couldn't allocate memory for joystick axes");
+						}
+					}
+				}
+			}
+
+			break;
+
+		case GLFW_DISCONNECTED:
+			if (joystick_connected && joystick_id == connected_joystick_id)
+			{
+				Backend_PrintInfo("Joystick #%d disconnected", connected_joystick_id);
+				joystick_connected = false;
+
+				free(axis_neutrals);
+			}
+
+			break;
+	}
+}
+
+bool ControllerBackend_Init(void)
+{
+	// Connect joysticks that are already plugged-in
+	for (int i = GLFW_JOYSTICK_1; i < GLFW_JOYSTICK_LAST; ++i)
+		if (glfwJoystickPresent(i) == GLFW_TRUE)
+			JoystickCallback(i, GLFW_CONNECTED);
+
+	// Set-up the callback for future (dis)connections
+	glfwSetJoystickCallback(JoystickCallback);
+
+	return true;
+}
+
+void ControllerBackend_Deinit(void)
+{
+	glfwSetJoystickCallback(NULL);
+
+	joystick_connected = false;
+	connected_joystick_id = 0;
+
+	free(axis_neutrals);
+	axis_neutrals = NULL;
+}
+
+bool ControllerBackend_GetJoystickStatus(bool **buttons, unsigned int *button_count, short **axes, unsigned int *axis_count)
+{
+	if (!joystick_connected)
+		return false;
+
+	int total_glfw_buttons;
+	const unsigned char *glfw_buttons = glfwGetJoystickButtons(connected_joystick_id, &total_glfw_buttons);
+
+	int total_glfw_axes;
+	const float *glfw_axes = glfwGetJoystickAxes(connected_joystick_id, &total_glfw_axes);
+
+	int total_glfw_hats = 0;
+#if GLFW_VERSION_MAJOR > 3 || (GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR >= 3)
+	const unsigned char *glfw_hats = glfwGetJoystickHats(connected_joystick_id, &total_glfw_hats);
+#endif
+
+	*button_count = total_glfw_buttons + total_glfw_axes * 2 + total_glfw_hats * 4;
+	*axis_count = total_glfw_axes;
+
+	static bool *button_buffer = NULL;
+	static short *axis_buffer = NULL;
+
+	bool *new_button_buffer = (bool*)realloc(button_buffer, *button_count * sizeof(bool));
+	short *new_axis_buffer = (short*)realloc(axis_buffer, *axis_count * sizeof(short));
+
+	if (new_button_buffer == NULL || new_axis_buffer == NULL)
+		return false;
+
+	button_buffer = new_button_buffer;
+	axis_buffer = new_axis_buffer;
+
+	//////////////////////////
+	// Handle button inputs //
+	//////////////////////////
+
+	unsigned int current_button = 0;
+
+	// Start with the joystick buttons
+	for (int i = 0; i < total_glfw_buttons; ++i)
+		button_buffer[current_button++] = glfw_buttons[i] == GLFW_PRESS;
+
+	// Then the joystick axes
+	for (int i = 0; i < total_glfw_axes; ++i)
+	{
+		button_buffer[current_button++] = glfw_axes[i] < axis_neutrals[i] - DEADZONE;
+		button_buffer[current_button++] = glfw_axes[i] > axis_neutrals[i] + DEADZONE;
+	}
+
+#if GLFW_VERSION_MAJOR > 3 || (GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR >= 3)
+	// Then the joystick hats
+	for (int i = 0; i < total_glfw_hats; ++i)
+	{
+		button_buffer[current_button++] = glfw_hats[i] & GLFW_HAT_UP;
+		button_buffer[current_button++] = glfw_hats[i] & GLFW_HAT_RIGHT;
+		button_buffer[current_button++] = glfw_hats[i] & GLFW_HAT_DOWN;
+		button_buffer[current_button++] = glfw_hats[i] & GLFW_HAT_LEFT;
+	}
+#endif
+
+	*buttons = button_buffer;
+
+	////////////////////////
+	// Handle axis inputs //
+	////////////////////////
+
+	for (int i = 0; i < total_glfw_axes; ++i)
+		axis_buffer[i] = (short)(glfw_axes[i] * 0x7FFF);
+
+	*axes = axis_buffer;
+
+	return true;
+}
--- /dev/null
+++ b/src/Backends/Controller/Null.cpp
@@ -1,0 +1,21 @@
+#include "../Controller.h"
+
+bool ControllerBackend_Init(void)
+{
+	return false;
+}
+
+void ControllerBackend_Deinit(void)
+{
+	
+}
+
+bool ControllerBackend_GetJoystickStatus(bool **buttons, unsigned int *button_count, short **axes, unsigned int *axis_count)
+{
+	(void)buttons;
+	(void)button_count;
+	(void)axes;
+	(void)axis_count;
+
+	return false;
+}
--- /dev/null
+++ b/src/Backends/Controller/SDL2.cpp
@@ -1,0 +1,186 @@
+#include "../Controller.h"
+#include "Controller.h"
+
+#include <stddef.h>
+
+#include "SDL.h"
+
+#include "../Misc.h"
+
+#define DEADZONE 10000
+
+static SDL_Joystick *joystick;
+
+static Sint16 *axis_neutrals;
+
+bool ControllerBackend_Init(void)
+{
+	if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0)
+	{
+		Backend_PrintError("Couldn't initialise joystick SDL subsystem: %s", SDL_GetError());
+		return false;
+	}
+
+	return true;
+}
+
+void ControllerBackend_Deinit(void)
+{
+	if (joystick != NULL)
+	{
+		SDL_JoystickClose(joystick);
+		joystick = NULL;
+	}
+
+	SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
+}
+
+bool ControllerBackend_GetJoystickStatus(bool **buttons, unsigned int *button_count, short **axes, unsigned int *axis_count)
+{
+	if (joystick == NULL)
+		return false;
+
+	int total_sdl_buttons = SDL_JoystickNumButtons(joystick);
+	if (total_sdl_buttons < 0)
+		Backend_PrintError("Failed to get number of buttons on joystick: %s", SDL_GetError());
+
+	int total_sdl_axes = SDL_JoystickNumAxes(joystick);
+	if (total_sdl_axes < 0)
+		Backend_PrintError("Failed to get number of general axis controls on joystick: %s", SDL_GetError());
+
+	int total_sdl_hats = SDL_JoystickNumHats(joystick);
+	if (total_sdl_hats < 0)
+		Backend_PrintError("Failed to get number of POV hats on joystick: %s", SDL_GetError());
+
+	*button_count = total_sdl_buttons + total_sdl_axes * 2 + total_sdl_hats * 4;
+	*axis_count = total_sdl_axes;
+
+	static bool *button_buffer = NULL;
+	static short *axis_buffer = NULL;
+
+	bool *new_button_buffer = (bool*)realloc(button_buffer, *button_count * sizeof(bool));
+	short *new_axis_buffer = (short*)realloc(axis_buffer, *axis_count * sizeof(short));
+
+	if (new_button_buffer == NULL || new_axis_buffer == NULL)
+		return false;
+
+	button_buffer = new_button_buffer;
+	axis_buffer = new_axis_buffer;
+
+	//////////////////////////
+	// Handle button inputs //
+	//////////////////////////
+
+	unsigned int current_button = 0;
+
+	// Start with the joystick buttons
+	for (int i = 0; i < total_sdl_buttons; ++i)
+		button_buffer[current_button++] = SDL_JoystickGetButton(joystick, i);
+
+	// Then the joystick axes
+	for (int i = 0; i < total_sdl_axes; ++i)
+	{
+		Sint16 axis = SDL_JoystickGetAxis(joystick, i);
+
+		button_buffer[current_button++] = axis < axis_neutrals[i] - DEADZONE;
+		button_buffer[current_button++] = axis > axis_neutrals[i] + DEADZONE;
+	}
+
+	// Then the joystick hats
+	for (int i = 0; i < total_sdl_hats; ++i)
+	{
+		Uint8 hat = SDL_JoystickGetHat(joystick, i);
+
+		button_buffer[current_button++] = hat == SDL_HAT_UP || hat == SDL_HAT_LEFTUP || hat == SDL_HAT_RIGHTUP;
+		button_buffer[current_button++] = hat == SDL_HAT_RIGHT || hat == SDL_HAT_RIGHTUP || hat == SDL_HAT_RIGHTDOWN;
+		button_buffer[current_button++] = hat == SDL_HAT_DOWN || hat == SDL_HAT_LEFTDOWN || hat == SDL_HAT_RIGHTDOWN;
+		button_buffer[current_button++] = hat == SDL_HAT_LEFT || hat == SDL_HAT_LEFTUP || hat == SDL_HAT_LEFTDOWN;
+	}
+
+	*buttons = button_buffer;
+
+	////////////////////////
+	// Handle axis inputs //
+	////////////////////////
+
+	for (int i = 0; i < total_sdl_axes; ++i)
+		axis_buffer[i] = SDL_JoystickGetAxis(joystick, i);
+
+	*axes = axis_buffer;
+
+	return true;
+}
+
+void ControllerBackend_JoystickConnect(Sint32 joystick_id)
+{
+	const char *joystick_name = SDL_JoystickNameForIndex(joystick_id);
+
+	if (joystick_name != NULL)
+	{
+		Backend_PrintInfo("Joystick #%d connected - %s", joystick_id, joystick_name);
+	}
+	else
+	{
+		Backend_PrintError("Couldn't get joystick name: %s", SDL_GetError());
+		Backend_PrintInfo("Joystick #%d connected - Name unknown", joystick_id);
+	}
+
+	if (joystick == NULL)
+	{
+		joystick = SDL_JoystickOpen(joystick_id);
+
+		if (joystick != NULL)
+		{
+			int total_axes = SDL_JoystickNumAxes(joystick);
+			if (total_axes < 0)
+				Backend_PrintError("Couldn't get number of general axis control on connected joystick: %s", SDL_GetError());
+
+			int total_buttons = SDL_JoystickNumButtons(joystick);
+			if (total_buttons < 0)
+				Backend_PrintError("Couldn't get number of buttons on connected joystick: %s", SDL_GetError());
+
+			if (total_axes >= 2 && total_buttons >= 6)
+			{
+				Backend_PrintInfo("Joystick #%d selected", joystick_id);
+
+				// Set up neutral axes
+				axis_neutrals = (Sint16*)malloc(sizeof(Sint16) * total_axes);
+
+				if (axis_neutrals != NULL)
+				{
+					for (int i = 0; i < total_axes; ++i)
+						axis_neutrals[i] = SDL_JoystickGetAxis(joystick, i);
+
+					return;
+				}
+				else
+				{
+					Backend_PrintError("Couldn't allocate memory for neutral axes");
+				}
+			}
+
+			SDL_JoystickClose(joystick);
+			joystick = NULL;
+		}
+		else
+		{
+			Backend_PrintError("Couldn't open joystick for use: %s", SDL_GetError());
+		}
+	}
+}
+
+void ControllerBackend_JoystickDisconnect(Sint32 joystick_id)
+{
+	SDL_JoystickID current_joystick_id = SDL_JoystickInstanceID(joystick);
+	if (current_joystick_id < 0)
+		Backend_PrintError("Couldn't get instance ID for current joystick: %s", SDL_GetError());
+
+	if (joystick_id == current_joystick_id)
+	{
+		Backend_PrintInfo("Joystick #%d disconnected", joystick_id);
+		SDL_JoystickClose(joystick);
+		joystick = NULL;
+
+		free(axis_neutrals);
+	}
+}
--- /dev/null
+++ b/src/Backends/Controller/WiiU.cpp
@@ -1,0 +1,24 @@
+#include "../Controller.h"
+
+// Vanilla Cave Story's controller system is ill-suited for console ports,
+// so we emulate a keyboard instead (see `Misc.cpp`).
+
+bool ControllerBackend_Init(void)
+{
+	return false;
+}
+
+void ControllerBackend_Deinit(void)
+{
+	
+}
+
+bool ControllerBackend_GetJoystickStatus(bool **buttons, unsigned int *button_count, short **axes, unsigned int *axis_count)
+{
+	(void)buttons;
+	(void)button_count;
+	(void)axes;
+	(void)axis_count;
+
+	return false;
+}
--- a/src/Backends/GLFW3/Controller.cpp
+++ /dev/null
@@ -1,169 +1,0 @@
-#include "../Controller.h"
-
-#include <stddef.h>
-#include <stdlib.h>
-
-#define GLFW_INCLUDE_NONE
-#include <GLFW/glfw3.h>
-
-#include "../Misc.h"
-
-#define DEADZONE (10000.0f / 32767.0f)
-
-static bool joystick_connected;
-static int connected_joystick_id;
-
-static float *axis_neutrals;
-
-static void JoystickCallback(int joystick_id, int event)
-{
-	switch (event)
-	{
-		case GLFW_CONNECTED:
-			Backend_PrintInfo("Joystick #%d connected - %s", joystick_id, glfwGetJoystickName(joystick_id));
-
-			if (!joystick_connected)
-			{
-				int total_axes;
-				const float *axes = glfwGetJoystickAxes(joystick_id, &total_axes);
-
-				int total_buttons;
-				const unsigned char *buttons = glfwGetJoystickButtons(joystick_id, &total_buttons);
-
-				if (total_axes >= 2 && total_buttons >= 6)
-				{
-#if GLFW_VERSION_MAJOR > 3 || (GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR >= 3)
-					if (glfwJoystickIsGamepad(joystick_id) == GLFW_TRUE)	// Avoid selecting things like laptop touchpads
-#endif
-					{
-						// Set up neutral axes
-						axis_neutrals = (float*)malloc(sizeof(float) * total_axes);
-
-						if (axis_neutrals != NULL)
-						{
-							for (int i = 0; i < total_axes; ++i)
-								axis_neutrals[i] = axes[i];
-
-							Backend_PrintInfo("Joystick #%d selected\n", joystick_id);
-							joystick_connected = true;
-							connected_joystick_id = joystick_id;
-						}
-						else
-						{
-							Backend_PrintError("Couldn't allocate memory for joystick axes");
-						}
-					}
-				}
-			}
-
-			break;
-
-		case GLFW_DISCONNECTED:
-			if (joystick_connected && joystick_id == connected_joystick_id)
-			{
-				Backend_PrintInfo("Joystick #%d disconnected", connected_joystick_id);
-				joystick_connected = false;
-
-				free(axis_neutrals);
-			}
-
-			break;
-	}
-}
-
-bool ControllerBackend_Init(void)
-{
-	// Connect joysticks that are already plugged-in
-	for (int i = GLFW_JOYSTICK_1; i < GLFW_JOYSTICK_LAST; ++i)
-		if (glfwJoystickPresent(i) == GLFW_TRUE)
-			JoystickCallback(i, GLFW_CONNECTED);
-
-	// Set-up the callback for future (dis)connections
-	glfwSetJoystickCallback(JoystickCallback);
-
-	return true;
-}
-
-void ControllerBackend_Deinit(void)
-{
-	glfwSetJoystickCallback(NULL);
-
-	joystick_connected = false;
-	connected_joystick_id = 0;
-
-	free(axis_neutrals);
-	axis_neutrals = NULL;
-}
-
-bool ControllerBackend_GetJoystickStatus(bool **buttons, unsigned int *button_count, short **axes, unsigned int *axis_count)
-{
-	if (!joystick_connected)
-		return false;
-
-	int total_glfw_buttons;
-	const unsigned char *glfw_buttons = glfwGetJoystickButtons(connected_joystick_id, &total_glfw_buttons);
-
-	int total_glfw_axes;
-	const float *glfw_axes = glfwGetJoystickAxes(connected_joystick_id, &total_glfw_axes);
-
-	int total_glfw_hats = 0;
-#if GLFW_VERSION_MAJOR > 3 || (GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR >= 3)
-	const unsigned char *glfw_hats = glfwGetJoystickHats(connected_joystick_id, &total_glfw_hats);
-#endif
-
-	*button_count = total_glfw_buttons + total_glfw_axes * 2 + total_glfw_hats * 4;
-	*axis_count = total_glfw_axes;
-
-	static bool *button_buffer = NULL;
-	static short *axis_buffer = NULL;
-
-	bool *new_button_buffer = (bool*)realloc(button_buffer, *button_count * sizeof(bool));
-	short *new_axis_buffer = (short*)realloc(axis_buffer, *axis_count * sizeof(short));
-
-	if (new_button_buffer == NULL || new_axis_buffer == NULL)
-		return false;
-
-	button_buffer = new_button_buffer;
-	axis_buffer = new_axis_buffer;
-
-	//////////////////////////
-	// Handle button inputs //
-	//////////////////////////
-
-	unsigned int current_button = 0;
-
-	// Start with the joystick buttons
-	for (int i = 0; i < total_glfw_buttons; ++i)
-		button_buffer[current_button++] = glfw_buttons[i] == GLFW_PRESS;
-
-	// Then the joystick axes
-	for (int i = 0; i < total_glfw_axes; ++i)
-	{
-		button_buffer[current_button++] = glfw_axes[i] < axis_neutrals[i] - DEADZONE;
-		button_buffer[current_button++] = glfw_axes[i] > axis_neutrals[i] + DEADZONE;
-	}
-
-#if GLFW_VERSION_MAJOR > 3 || (GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR >= 3)
-	// Then the joystick hats
-	for (int i = 0; i < total_glfw_hats; ++i)
-	{
-		button_buffer[current_button++] = glfw_hats[i] & GLFW_HAT_UP;
-		button_buffer[current_button++] = glfw_hats[i] & GLFW_HAT_RIGHT;
-		button_buffer[current_button++] = glfw_hats[i] & GLFW_HAT_DOWN;
-		button_buffer[current_button++] = glfw_hats[i] & GLFW_HAT_LEFT;
-	}
-#endif
-
-	*buttons = button_buffer;
-
-	////////////////////////
-	// Handle axis inputs //
-	////////////////////////
-
-	for (int i = 0; i < total_glfw_axes; ++i)
-		axis_buffer[i] = (short)(glfw_axes[i] * 0x7FFF);
-
-	*axes = axis_buffer;
-
-	return true;
-}
--- a/src/Backends/GLFW3/Misc.cpp
+++ /dev/null
@@ -1,327 +1,0 @@
-#include "../Misc.h"
-
-#include <chrono>
-#include <stdarg.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <thread>
-
-#include <GLFW/glfw3.h>
-
-#include "Window.h"
-#include "../Rendering.h"
-#include "../../Attributes.h"
-#include "../../Main.h"
-#include "../../Organya.h"
-#include "../../Profile.h"
-#include "../../Resource.h"
-
-#define DO_KEY(GLFW_KEY, BACKEND_KEY) \
-	case GLFW_KEY: \
-		keyboard_state[BACKEND_KEY] = action == GLFW_PRESS; \
-		break;
-
-static bool keyboard_state[BACKEND_KEYBOARD_TOTAL];
-
-static GLFWcursor* cursor;
-
-static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods)
-{
-	(void)window;
-	(void)scancode;
-	(void)mods;
-
-	switch (action)
-	{
-		case GLFW_RELEASE:
-		case GLFW_PRESS:
-			switch (key)
-			{
-				DO_KEY(GLFW_KEY_A, BACKEND_KEYBOARD_A)
-				DO_KEY(GLFW_KEY_B, BACKEND_KEYBOARD_B)
-				DO_KEY(GLFW_KEY_C, BACKEND_KEYBOARD_C)
-				DO_KEY(GLFW_KEY_D, BACKEND_KEYBOARD_D)
-				DO_KEY(GLFW_KEY_E, BACKEND_KEYBOARD_E)
-				DO_KEY(GLFW_KEY_F, BACKEND_KEYBOARD_F)
-				DO_KEY(GLFW_KEY_G, BACKEND_KEYBOARD_G)
-				DO_KEY(GLFW_KEY_H, BACKEND_KEYBOARD_H)
-				DO_KEY(GLFW_KEY_I, BACKEND_KEYBOARD_I)
-				DO_KEY(GLFW_KEY_J, BACKEND_KEYBOARD_J)
-				DO_KEY(GLFW_KEY_K, BACKEND_KEYBOARD_K)
-				DO_KEY(GLFW_KEY_L, BACKEND_KEYBOARD_L)
-				DO_KEY(GLFW_KEY_M, BACKEND_KEYBOARD_M)
-				DO_KEY(GLFW_KEY_N, BACKEND_KEYBOARD_N)
-				DO_KEY(GLFW_KEY_O, BACKEND_KEYBOARD_O)
-				DO_KEY(GLFW_KEY_P, BACKEND_KEYBOARD_P)
-				DO_KEY(GLFW_KEY_Q, BACKEND_KEYBOARD_Q)
-				DO_KEY(GLFW_KEY_R, BACKEND_KEYBOARD_R)
-				DO_KEY(GLFW_KEY_S, BACKEND_KEYBOARD_S)
-				DO_KEY(GLFW_KEY_T, BACKEND_KEYBOARD_T)
-				DO_KEY(GLFW_KEY_U, BACKEND_KEYBOARD_U)
-				DO_KEY(GLFW_KEY_V, BACKEND_KEYBOARD_V)
-				DO_KEY(GLFW_KEY_W, BACKEND_KEYBOARD_W)
-				DO_KEY(GLFW_KEY_X, BACKEND_KEYBOARD_X)
-				DO_KEY(GLFW_KEY_Y, BACKEND_KEYBOARD_Y)
-				DO_KEY(GLFW_KEY_Z, BACKEND_KEYBOARD_Z)
-				DO_KEY(GLFW_KEY_0, BACKEND_KEYBOARD_0)
-				DO_KEY(GLFW_KEY_1, BACKEND_KEYBOARD_1)
-				DO_KEY(GLFW_KEY_2, BACKEND_KEYBOARD_2)
-				DO_KEY(GLFW_KEY_3, BACKEND_KEYBOARD_3)
-				DO_KEY(GLFW_KEY_4, BACKEND_KEYBOARD_4)
-				DO_KEY(GLFW_KEY_5, BACKEND_KEYBOARD_5)
-				DO_KEY(GLFW_KEY_6, BACKEND_KEYBOARD_6)
-				DO_KEY(GLFW_KEY_7, BACKEND_KEYBOARD_7)
-				DO_KEY(GLFW_KEY_8, BACKEND_KEYBOARD_8)
-				DO_KEY(GLFW_KEY_9, BACKEND_KEYBOARD_9)
-				DO_KEY(GLFW_KEY_F1, BACKEND_KEYBOARD_F1)
-				DO_KEY(GLFW_KEY_F2, BACKEND_KEYBOARD_F2)
-				DO_KEY(GLFW_KEY_F3, BACKEND_KEYBOARD_F3)
-				DO_KEY(GLFW_KEY_F4, BACKEND_KEYBOARD_F4)
-				DO_KEY(GLFW_KEY_F5, BACKEND_KEYBOARD_F5)
-				DO_KEY(GLFW_KEY_F6, BACKEND_KEYBOARD_F6)
-				DO_KEY(GLFW_KEY_F7, BACKEND_KEYBOARD_F7)
-				DO_KEY(GLFW_KEY_F8, BACKEND_KEYBOARD_F8)
-				DO_KEY(GLFW_KEY_F9, BACKEND_KEYBOARD_F9)
-				DO_KEY(GLFW_KEY_F10, BACKEND_KEYBOARD_F10)
-				DO_KEY(GLFW_KEY_F11, BACKEND_KEYBOARD_F11)
-				DO_KEY(GLFW_KEY_F12, BACKEND_KEYBOARD_F12)
-				DO_KEY(GLFW_KEY_UP, BACKEND_KEYBOARD_UP)
-				DO_KEY(GLFW_KEY_DOWN, BACKEND_KEYBOARD_DOWN)
-				DO_KEY(GLFW_KEY_LEFT, BACKEND_KEYBOARD_LEFT)
-				DO_KEY(GLFW_KEY_RIGHT, BACKEND_KEYBOARD_RIGHT)
-				DO_KEY(GLFW_KEY_ESCAPE, BACKEND_KEYBOARD_ESCAPE)
-				DO_KEY(GLFW_KEY_GRAVE_ACCENT, BACKEND_KEYBOARD_BACK_QUOTE)
-				DO_KEY(GLFW_KEY_TAB, BACKEND_KEYBOARD_TAB)
-				DO_KEY(GLFW_KEY_CAPS_LOCK, BACKEND_KEYBOARD_CAPS_LOCK)
-				DO_KEY(GLFW_KEY_LEFT_SHIFT, BACKEND_KEYBOARD_LEFT_SHIFT)
-				DO_KEY(GLFW_KEY_LEFT_CONTROL, BACKEND_KEYBOARD_LEFT_CTRL)
-				DO_KEY(GLFW_KEY_LEFT_ALT, BACKEND_KEYBOARD_LEFT_ALT)
-				DO_KEY(GLFW_KEY_SPACE, BACKEND_KEYBOARD_SPACE)
-				DO_KEY(GLFW_KEY_RIGHT_ALT, BACKEND_KEYBOARD_RIGHT_ALT)
-				DO_KEY(GLFW_KEY_RIGHT_CONTROL, BACKEND_KEYBOARD_RIGHT_CTRL)
-				DO_KEY(GLFW_KEY_RIGHT_SHIFT, BACKEND_KEYBOARD_RIGHT_SHIFT)
-				DO_KEY(GLFW_KEY_ENTER, BACKEND_KEYBOARD_ENTER)
-				DO_KEY(GLFW_KEY_BACKSPACE, BACKEND_KEYBOARD_BACKSPACE)
-				DO_KEY(GLFW_KEY_MINUS, BACKEND_KEYBOARD_MINUS)
-				DO_KEY(GLFW_KEY_EQUAL, BACKEND_KEYBOARD_EQUALS)
-				DO_KEY(GLFW_KEY_LEFT_BRACKET, BACKEND_KEYBOARD_LEFT_BRACKET)
-				DO_KEY(GLFW_KEY_RIGHT_BRACKET, BACKEND_KEYBOARD_RIGHT_BRACKET)
-				DO_KEY(GLFW_KEY_BACKSLASH, BACKEND_KEYBOARD_BACK_SLASH)
-				DO_KEY(GLFW_KEY_SEMICOLON, BACKEND_KEYBOARD_SEMICOLON)
-				DO_KEY(GLFW_KEY_APOSTROPHE, BACKEND_KEYBOARD_APOSTROPHE)
-				DO_KEY(GLFW_KEY_COMMA, BACKEND_KEYBOARD_COMMA)
-				DO_KEY(GLFW_KEY_PERIOD, BACKEND_KEYBOARD_PERIOD)
-				DO_KEY(GLFW_KEY_SLASH, BACKEND_KEYBOARD_FORWARD_SLASH)
-
-				default:
-					break;
-			}
-
-			break;
-	}
-}
-
-static void WindowFocusCallback(GLFWwindow *window, int focused)
-{
-	(void)window;
-
-	if (focused)
-		ActiveWindow();
-	else
-		InactiveWindow();
-}
-
-static void WindowSizeCallback(GLFWwindow *window, int width, int height)
-{
-	(void)window;
-
-	RenderBackend_HandleWindowResize(width, height);
-}
-
-static void DragAndDropCallback(GLFWwindow *window, int count, const char **paths)
-{
-	(void)window;
-	(void)count;
-
-	LoadProfile(paths[0]);
-}
-
-static void ErrorCallback(int code, const char *description)
-{
-	Backend_PrintError("GLFW error received (%d): %s", code, description);
-}
-
-bool Backend_Init(void)
-{
-	glfwSetErrorCallback(ErrorCallback);
-
-	if (glfwInit() == GL_TRUE)
-		return true;
-
-	Backend_ShowMessageBox("Fatal error", "Could not initialise GLFW3");
-
-	return false;
-}
-
-void Backend_Deinit(void)
-{
-	if (cursor != NULL)
-		glfwDestroyCursor(cursor);
-
-	glfwTerminate();
-}
-
-void Backend_PostWindowCreation(void)
-{
-	// Hook callbacks
-	glfwSetKeyCallback(window, KeyCallback);
-	glfwSetWindowFocusCallback(window, WindowFocusCallback);
-	glfwSetWindowSizeCallback(window, WindowSizeCallback);
-}
-
-bool Backend_GetBasePath(char *string_buffer)
-{
-	(void)string_buffer;
-
-	// GLFW3 doesn't seem to have a mechanism for this
-	return false;
-}
-
-void Backend_HideMouse(void)
-{
-	glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
-}
-
-void Backend_SetWindowIcon(const unsigned char *rgb_pixels, unsigned int width, unsigned int height)
-{
-	// Convert to RGBA, since that's the only thing GLFW3 accepts
-	unsigned char *rgba_pixels = (unsigned char*)malloc(width * height * 4);
-
-	const unsigned char *rgb_pointer = rgb_pixels;
-	unsigned char *rgba_pointer = rgba_pixels;
-
-	if (rgba_pixels != NULL)
-	{
-		for (unsigned int y = 0; y < height; ++y)
-		{
-			for (unsigned int x = 0; x < width; ++x)
-			{
-				*rgba_pointer++ = *rgb_pointer++;
-				*rgba_pointer++ = *rgb_pointer++;
-				*rgba_pointer++ = *rgb_pointer++;
-				*rgba_pointer++ = 0xFF;
-			}
-		}
-
-		GLFWimage glfw_image = {(int)width, (int)height, rgba_pixels};
-		glfwSetWindowIcon(window, 1, &glfw_image);
-
-		free(rgba_pixels);
-	}
-}
-
-void Backend_SetCursor(const unsigned char *rgb_pixels, unsigned int width, unsigned int height)
-{
-	// Convert to RGBA, since that's the only thing GLFW3 accepts
-	unsigned char *rgba_pixels = (unsigned char*)malloc(width * height * 4);
-
-	const unsigned char *rgb_pointer = rgb_pixels;
-	unsigned char *rgba_pointer = rgba_pixels;
-
-	if (rgba_pixels != NULL)
-	{
-		for (unsigned int y = 0; y < height; ++y)
-		{
-			for (unsigned int x = 0; x < width; ++x)
-			{
-				if (rgb_pointer[0] == 0xFF && rgb_pointer[1] == 0 && rgb_pointer[2] == 0xFF)	// Colour-key
-				{
-					*rgba_pointer++ = *rgb_pointer++;
-					*rgba_pointer++ = *rgb_pointer++;
-					*rgba_pointer++ = *rgb_pointer++;
-					*rgba_pointer++ = 0;
-				}
-				else
-				{
-					*rgba_pointer++ = *rgb_pointer++;
-					*rgba_pointer++ = *rgb_pointer++;
-					*rgba_pointer++ = *rgb_pointer++;
-					*rgba_pointer++ = 0xFF;
-				}
-			}
-		}
-
-		GLFWimage glfw_image = {(int)width, (int)height, rgba_pixels};
-		cursor = glfwCreateCursor(&glfw_image, 0, 0);
-
-		if (cursor != NULL)
-			glfwSetCursor(window, cursor);
-
-		free(rgba_pixels);
-	}
-}
-
-void PlaybackBackend_EnableDragAndDrop(void)
-{
-	glfwSetDropCallback(window, DragAndDropCallback);
-}
-
-bool Backend_SystemTask(bool active)
-{
-	if (glfwWindowShouldClose(window))
-	{
-		StopOrganyaMusic();
-		return false;
-	}
-
-	if (active)
-		glfwPollEvents();
-	else
-		glfwWaitEvents();
-
-	return true;
-}
-
-void Backend_GetKeyboardState(bool *out_keyboard_state)
-{
-	memcpy(out_keyboard_state, keyboard_state, sizeof(keyboard_state));
-}
-
-void Backend_ShowMessageBox(const char *title, const char *message)
-{
-	// GLFW3 doesn't have a message box
-	printf("ShowMessageBox - '%s' - '%s'\n", title, message);
-}
-
-ATTRIBUTE_FORMAT_PRINTF(1, 2) void Backend_PrintError(const char *format, ...)
-{
-	va_list argumentList;
-	va_start(argumentList, format);
-	fputs("ERROR: ", stderr);
-	vfprintf(stderr, format, argumentList);
-	fputc('\n', stderr);
-	va_end(argumentList);
-}
-
-ATTRIBUTE_FORMAT_PRINTF(1, 2) void Backend_PrintInfo(const char *format, ...)
-{
-	va_list argumentList;
-	va_start(argumentList, format);
-	fputs("INFO: ", stdout);
-	vprintf(format, argumentList);
-	putchar('\n');
-	va_end(argumentList);
-}
-
-unsigned long Backend_GetTicks(void)
-{
-	return (unsigned long)(glfwGetTime() * 1000.0);
-}
-
-void Backend_Delay(unsigned int ticks)
-{
-	// GLFW3 doesn't have a delay function, so here's some butt-ugly C++11
-	std::this_thread::sleep_for(std::chrono::milliseconds(ticks));
-}
--- a/src/Backends/GLFW3/Window-OpenGL3.cpp
+++ /dev/null
@@ -1,94 +1,0 @@
-#include "../Window-OpenGL.h"
-#include "Window.h"
-
-#include <stddef.h>
-#include <stdlib.h>
-
-#ifdef USE_OPENGLES2
-#include <GLES2/gl2.h>
-#else
-#include <glad/glad.h>
-#endif
-#include <GLFW/glfw3.h>
-
-#include "../Misc.h"
-
-GLFWwindow *window;
-
-bool WindowBackend_OpenGL_CreateWindow(const char *window_title, int *screen_width, int *screen_height, bool fullscreen)
-{
-#ifdef USE_OPENGLES2
-	glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
-	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
-	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
-#else
-	glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
-	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
-	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
-	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
-	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
-#endif
-
-	GLFWmonitor *monitor = NULL;
-
-	if (fullscreen)
-	{
-		monitor = glfwGetPrimaryMonitor();
-
-		if (monitor != NULL)
-		{
-			const GLFWvidmode *mode = glfwGetVideoMode(monitor);
-
-			*screen_width = mode->width;
-			*screen_height = mode->height;
-		}
-	}
-
-	window = glfwCreateWindow(*screen_width, *screen_height, window_title, monitor, NULL);
-
-	if (window != NULL)
-	{
-		glfwMakeContextCurrent(window);
-
-			#ifndef USE_OPENGLES2
-				if (gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
-				{
-					// Check if the platform supports OpenGL 3.2
-					if (GLAD_GL_VERSION_3_2)
-					{
-			#endif
-						Backend_PostWindowCreation();
-
-						return true;
-			#ifndef USE_OPENGLES2
-					}
-					else
-					{
-						Backend_ShowMessageBox("Fatal error (OpenGL rendering backend)", "Your system does not support OpenGL 3.2");
-					}
-				}
-				else
-				{
-					Backend_ShowMessageBox("Fatal error (OpenGL rendering backend)", "Could not initialize OpenGL context");
-				}
-			#endif
-
-		glfwDestroyWindow(window);
-	}
-	else
-	{
-		Backend_ShowMessageBox("Fatal error (OpenGL rendering backend)", "Could not create window");
-	}
-
-	return false;
-}
-
-void WindowBackend_OpenGL_DestroyWindow(void)
-{
-	glfwDestroyWindow(window);
-}
-
-void WindowBackend_OpenGL_Display(void)
-{
-	glfwSwapBuffers(window);
-}
--- a/src/Backends/GLFW3/Window-OpenGLES2.cpp
+++ /dev/null
@@ -1,2 +1,0 @@
-#define USE_OPENGLES2
-#include "Window-OpenGL3.cpp"
--- a/src/Backends/GLFW3/Window-Software.cpp
+++ /dev/null
@@ -1,156 +1,0 @@
-#include "../Window-Software.h"
-#include "Window.h"
-
-#include <stddef.h>
-#include <stdlib.h>
-
-#if defined(__APPLE__)
- #include <OpenGL/gl.h>
-#else
- #include <GL/gl.h>
-#endif
-#include <GLFW/glfw3.h>
-
-#include "../Misc.h"
-
-GLFWwindow *window;
-
-static unsigned char *framebuffer;
-static int framebuffer_width;
-static int framebuffer_height;
-
-static float framebuffer_x_ratio;
-static float framebuffer_y_ratio;
-
-static GLuint screen_texture_id;
-
-bool WindowBackend_Software_CreateWindow(const char *window_title, int screen_width, int screen_height, bool fullscreen)
-{
-	glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
-	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 1);
-	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
-
-	framebuffer_width = screen_width;
-	framebuffer_height = screen_height;
-
-	GLFWmonitor *monitor = NULL;
-
-	if (fullscreen)
-	{
-		monitor = glfwGetPrimaryMonitor();
-
-		if (monitor != NULL)
-		{
-			const GLFWvidmode *mode = glfwGetVideoMode(monitor);
-
-			screen_width = mode->width;
-			screen_height = mode->height;
-		}
-	}
-
-	window = glfwCreateWindow(screen_width, screen_height, window_title, monitor, NULL);
-
-	if (window != NULL)
-	{
-		glfwMakeContextCurrent(window);
-
-		glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
-
-		glEnable(GL_TEXTURE_2D);
-
-		WindowBackend_Software_HandleWindowResize(screen_width, screen_height);
-
-		// Create screen texture
-		glGenTextures(1, &screen_texture_id);
-		glBindTexture(GL_TEXTURE_2D, screen_texture_id);
-
-		int framebuffer_texture_width = 1;
-		while (framebuffer_texture_width < framebuffer_width)
-			framebuffer_texture_width <<= 1;
-
-		int framebuffer_texture_height = 1;
-		while (framebuffer_texture_height < framebuffer_height)
-			framebuffer_texture_height <<= 1;
-
-		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, framebuffer_texture_width, framebuffer_texture_height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
-		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-
-		framebuffer_x_ratio = (float)framebuffer_width / framebuffer_texture_width;
-		framebuffer_y_ratio = (float)framebuffer_height / framebuffer_texture_height;
-
-		framebuffer = (unsigned char*)malloc(framebuffer_width * framebuffer_height * 3);
-
-		Backend_PostWindowCreation();
-
-		return true;
-	}
-	else
-	{
-		Backend_ShowMessageBox("Fatal error (OpenGL rendering backend)", "Could not create window");
-	}
-
-	return false;
-}
-
-void WindowBackend_Software_DestroyWindow(void)
-{
-	free(framebuffer);
-	glDeleteTextures(1, &screen_texture_id);
-	glfwDestroyWindow(window);
-}
-
-unsigned char* WindowBackend_Software_GetFramebuffer(size_t *pitch)
-{
-	*pitch = framebuffer_width * 3;
-
-	return framebuffer;
-}
-
-void WindowBackend_Software_Display(void)
-{
-	glClear(GL_COLOR_BUFFER_BIT);
-
-	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, framebuffer_width, framebuffer_height, GL_RGB, GL_UNSIGNED_BYTE, framebuffer);
-
-	glBegin(GL_TRIANGLE_STRIP);
-		glTexCoord2f(0.0f, framebuffer_y_ratio);
-		glVertex2f(-1.0f, -1.0f);
-		glTexCoord2f(framebuffer_x_ratio, framebuffer_y_ratio);
-		glVertex2f(1.0f, -1.0f);
-		glTexCoord2f(0.0f, 0.0f);
-		glVertex2f(-1.0f, 1.0f);
-		glTexCoord2f(framebuffer_x_ratio, 0.0f);
-		glVertex2f(1.0f, 1.0f);
-	glEnd();
-
-	glfwSwapBuffers(window);
-}
-
-void WindowBackend_Software_HandleWindowResize(unsigned int width, unsigned int height)
-{
-	// Do some viewport trickery, to fit the framebuffer in the center of the screen
-	GLint viewport_x;
-	GLint viewport_y;
-	GLsizei viewport_width;
-	GLsizei viewport_height;
-
-	if ((float)width / (float)height > (float)framebuffer_width / (float)framebuffer_height)
-	{
-		viewport_y = 0;
-		viewport_height = height;
-
-		viewport_width = framebuffer_width * ((float)height / (float)framebuffer_height);
-		viewport_x = (width - viewport_width) / 2;
-	}
-	else
-	{
-		viewport_x = 0;
-		viewport_width = width;
-
-		viewport_height = framebuffer_height * ((float)width / (float)framebuffer_width);
-		viewport_y = (height - viewport_height) / 2;
-	}
-
-	glViewport(viewport_x, viewport_y, viewport_width, viewport_height);
-}
--- a/src/Backends/GLFW3/Window.h
+++ /dev/null
@@ -1,3 +1,0 @@
-#pragma once
-
-extern struct GLFWwindow *window;
--- a/src/Backends/Null/Controller.cpp
+++ /dev/null
@@ -1,21 +1,0 @@
-#include "../Controller.h"
-
-bool ControllerBackend_Init(void)
-{
-	return false;
-}
-
-void ControllerBackend_Deinit(void)
-{
-	
-}
-
-bool ControllerBackend_GetJoystickStatus(bool **buttons, unsigned int *button_count, short **axes, unsigned int *axis_count)
-{
-	(void)buttons;
-	(void)button_count;
-	(void)axes;
-	(void)axis_count;
-
-	return false;
-}
--- a/src/Backends/Null/Misc.cpp
+++ /dev/null
@@ -1,89 +1,0 @@
-#include "../Misc.h"
-
-bool Backend_Init(void)
-{
-	return true;
-}
-
-void Backend_Deinit(void)
-{
-	
-}
-
-void Backend_PostWindowCreation(void)
-{
-	
-}
-
-bool Backend_GetBasePath(char *string_buffer)
-{
-	(void)string_buffer;
-
-	return false;
-}
-
-void Backend_HideMouse(void)
-{
-	
-}
-
-void Backend_SetWindowIcon(const unsigned char *rgb_pixels, unsigned int width, unsigned int height)
-{
-	(void)rgb_pixels;
-	(void)width;
-	(void)height;
-}
-
-void Backend_SetCursor(const unsigned char *rgb_pixels, unsigned int width, unsigned int height)
-{
-	(void)rgb_pixels;
-	(void)width;
-	(void)height;
-}
-
-void PlaybackBackend_EnableDragAndDrop(void)
-{
-	
-}
-
-bool Backend_SystemTask(bool active)
-{
-	(void)active;
-
-	return true;
-}
-
-void Backend_GetKeyboardState(bool *keyboard_state)
-{
-	(void)keyboard_state;
-}
-
-void Backend_ShowMessageBox(const char *title, const char *message)
-{
-	(void)title;
-	(void)message;
-}
-
-ATTRIBUTE_FORMAT_PRINTF(1, 2) void Backend_PrintError(const char *format, ...)
-{
-	(void)format;
-}
-
-ATTRIBUTE_FORMAT_PRINTF(1, 2) void Backend_PrintInfo(const char *format, ...)
-{
-	(void)format;
-}
-
-unsigned long Backend_GetTicks(void)
-{
-	static unsigned long fake_ticks = 0;
-
-	fake_ticks += 1000 / 50;
-
-	return fake_ticks;
-}
-
-void Backend_Delay(unsigned int ticks)
-{
-	(void)ticks;
-}
--- a/src/Backends/Null/Window-Software.cpp
+++ /dev/null
@@ -1,39 +1,0 @@
-#include "../Window-Software.h"
-
-#include <stddef.h>
-#include <stdlib.h>
-
-static unsigned char *framebuffer;
-
-unsigned char* WindowBackend_Software_CreateWindow(const char *window_title, int screen_width, int screen_height, bool fullscreen, size_t *pitch)
-{
-	(void)window_title;
-	(void)fullscreen;
-
-	framebuffer = (unsigned char*)malloc(screen_width * screen_height * 3);
-
-	if (framebuffer != NULL)
-	{
-		*pitch = screen_width * 3;
-
-		return framebuffer;
-	}
-
-	return NULL;
-}
-
-void WindowBackend_Software_DestroyWindow(void)
-{
-	free(framebuffer);
-}
-
-void WindowBackend_Software_Display(void)
-{
-	
-}
-
-void WindowBackend_Software_HandleWindowResize(unsigned int width, unsigned int height)
-{
-	(void)width;
-	(void)height;
-}
--- /dev/null
+++ b/src/Backends/Platform/GLFW3.cpp
@@ -1,0 +1,327 @@
+#include "../Misc.h"
+
+#include <chrono>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <thread>
+
+#include <GLFW/glfw3.h>
+
+#include "Window.h"
+#include "../Rendering.h"
+#include "../../Attributes.h"
+#include "../../Main.h"
+#include "../../Organya.h"
+#include "../../Profile.h"
+#include "../../Resource.h"
+
+#define DO_KEY(GLFW_KEY, BACKEND_KEY) \
+	case GLFW_KEY: \
+		keyboard_state[BACKEND_KEY] = action == GLFW_PRESS; \
+		break;
+
+static bool keyboard_state[BACKEND_KEYBOARD_TOTAL];
+
+static GLFWcursor* cursor;
+
+static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods)
+{
+	(void)window;
+	(void)scancode;
+	(void)mods;
+
+	switch (action)
+	{
+		case GLFW_RELEASE:
+		case GLFW_PRESS:
+			switch (key)
+			{
+				DO_KEY(GLFW_KEY_A, BACKEND_KEYBOARD_A)
+				DO_KEY(GLFW_KEY_B, BACKEND_KEYBOARD_B)
+				DO_KEY(GLFW_KEY_C, BACKEND_KEYBOARD_C)
+				DO_KEY(GLFW_KEY_D, BACKEND_KEYBOARD_D)
+				DO_KEY(GLFW_KEY_E, BACKEND_KEYBOARD_E)
+				DO_KEY(GLFW_KEY_F, BACKEND_KEYBOARD_F)
+				DO_KEY(GLFW_KEY_G, BACKEND_KEYBOARD_G)
+				DO_KEY(GLFW_KEY_H, BACKEND_KEYBOARD_H)
+				DO_KEY(GLFW_KEY_I, BACKEND_KEYBOARD_I)
+				DO_KEY(GLFW_KEY_J, BACKEND_KEYBOARD_J)
+				DO_KEY(GLFW_KEY_K, BACKEND_KEYBOARD_K)
+				DO_KEY(GLFW_KEY_L, BACKEND_KEYBOARD_L)
+				DO_KEY(GLFW_KEY_M, BACKEND_KEYBOARD_M)
+				DO_KEY(GLFW_KEY_N, BACKEND_KEYBOARD_N)
+				DO_KEY(GLFW_KEY_O, BACKEND_KEYBOARD_O)
+				DO_KEY(GLFW_KEY_P, BACKEND_KEYBOARD_P)
+				DO_KEY(GLFW_KEY_Q, BACKEND_KEYBOARD_Q)
+				DO_KEY(GLFW_KEY_R, BACKEND_KEYBOARD_R)
+				DO_KEY(GLFW_KEY_S, BACKEND_KEYBOARD_S)
+				DO_KEY(GLFW_KEY_T, BACKEND_KEYBOARD_T)
+				DO_KEY(GLFW_KEY_U, BACKEND_KEYBOARD_U)
+				DO_KEY(GLFW_KEY_V, BACKEND_KEYBOARD_V)
+				DO_KEY(GLFW_KEY_W, BACKEND_KEYBOARD_W)
+				DO_KEY(GLFW_KEY_X, BACKEND_KEYBOARD_X)
+				DO_KEY(GLFW_KEY_Y, BACKEND_KEYBOARD_Y)
+				DO_KEY(GLFW_KEY_Z, BACKEND_KEYBOARD_Z)
+				DO_KEY(GLFW_KEY_0, BACKEND_KEYBOARD_0)
+				DO_KEY(GLFW_KEY_1, BACKEND_KEYBOARD_1)
+				DO_KEY(GLFW_KEY_2, BACKEND_KEYBOARD_2)
+				DO_KEY(GLFW_KEY_3, BACKEND_KEYBOARD_3)
+				DO_KEY(GLFW_KEY_4, BACKEND_KEYBOARD_4)
+				DO_KEY(GLFW_KEY_5, BACKEND_KEYBOARD_5)
+				DO_KEY(GLFW_KEY_6, BACKEND_KEYBOARD_6)
+				DO_KEY(GLFW_KEY_7, BACKEND_KEYBOARD_7)
+				DO_KEY(GLFW_KEY_8, BACKEND_KEYBOARD_8)
+				DO_KEY(GLFW_KEY_9, BACKEND_KEYBOARD_9)
+				DO_KEY(GLFW_KEY_F1, BACKEND_KEYBOARD_F1)
+				DO_KEY(GLFW_KEY_F2, BACKEND_KEYBOARD_F2)
+				DO_KEY(GLFW_KEY_F3, BACKEND_KEYBOARD_F3)
+				DO_KEY(GLFW_KEY_F4, BACKEND_KEYBOARD_F4)
+				DO_KEY(GLFW_KEY_F5, BACKEND_KEYBOARD_F5)
+				DO_KEY(GLFW_KEY_F6, BACKEND_KEYBOARD_F6)
+				DO_KEY(GLFW_KEY_F7, BACKEND_KEYBOARD_F7)
+				DO_KEY(GLFW_KEY_F8, BACKEND_KEYBOARD_F8)
+				DO_KEY(GLFW_KEY_F9, BACKEND_KEYBOARD_F9)
+				DO_KEY(GLFW_KEY_F10, BACKEND_KEYBOARD_F10)
+				DO_KEY(GLFW_KEY_F11, BACKEND_KEYBOARD_F11)
+				DO_KEY(GLFW_KEY_F12, BACKEND_KEYBOARD_F12)
+				DO_KEY(GLFW_KEY_UP, BACKEND_KEYBOARD_UP)
+				DO_KEY(GLFW_KEY_DOWN, BACKEND_KEYBOARD_DOWN)
+				DO_KEY(GLFW_KEY_LEFT, BACKEND_KEYBOARD_LEFT)
+				DO_KEY(GLFW_KEY_RIGHT, BACKEND_KEYBOARD_RIGHT)
+				DO_KEY(GLFW_KEY_ESCAPE, BACKEND_KEYBOARD_ESCAPE)
+				DO_KEY(GLFW_KEY_GRAVE_ACCENT, BACKEND_KEYBOARD_BACK_QUOTE)
+				DO_KEY(GLFW_KEY_TAB, BACKEND_KEYBOARD_TAB)
+				DO_KEY(GLFW_KEY_CAPS_LOCK, BACKEND_KEYBOARD_CAPS_LOCK)
+				DO_KEY(GLFW_KEY_LEFT_SHIFT, BACKEND_KEYBOARD_LEFT_SHIFT)
+				DO_KEY(GLFW_KEY_LEFT_CONTROL, BACKEND_KEYBOARD_LEFT_CTRL)
+				DO_KEY(GLFW_KEY_LEFT_ALT, BACKEND_KEYBOARD_LEFT_ALT)
+				DO_KEY(GLFW_KEY_SPACE, BACKEND_KEYBOARD_SPACE)
+				DO_KEY(GLFW_KEY_RIGHT_ALT, BACKEND_KEYBOARD_RIGHT_ALT)
+				DO_KEY(GLFW_KEY_RIGHT_CONTROL, BACKEND_KEYBOARD_RIGHT_CTRL)
+				DO_KEY(GLFW_KEY_RIGHT_SHIFT, BACKEND_KEYBOARD_RIGHT_SHIFT)
+				DO_KEY(GLFW_KEY_ENTER, BACKEND_KEYBOARD_ENTER)
+				DO_KEY(GLFW_KEY_BACKSPACE, BACKEND_KEYBOARD_BACKSPACE)
+				DO_KEY(GLFW_KEY_MINUS, BACKEND_KEYBOARD_MINUS)
+				DO_KEY(GLFW_KEY_EQUAL, BACKEND_KEYBOARD_EQUALS)
+				DO_KEY(GLFW_KEY_LEFT_BRACKET, BACKEND_KEYBOARD_LEFT_BRACKET)
+				DO_KEY(GLFW_KEY_RIGHT_BRACKET, BACKEND_KEYBOARD_RIGHT_BRACKET)
+				DO_KEY(GLFW_KEY_BACKSLASH, BACKEND_KEYBOARD_BACK_SLASH)
+				DO_KEY(GLFW_KEY_SEMICOLON, BACKEND_KEYBOARD_SEMICOLON)
+				DO_KEY(GLFW_KEY_APOSTROPHE, BACKEND_KEYBOARD_APOSTROPHE)
+				DO_KEY(GLFW_KEY_COMMA, BACKEND_KEYBOARD_COMMA)
+				DO_KEY(GLFW_KEY_PERIOD, BACKEND_KEYBOARD_PERIOD)
+				DO_KEY(GLFW_KEY_SLASH, BACKEND_KEYBOARD_FORWARD_SLASH)
+
+				default:
+					break;
+			}
+
+			break;
+	}
+}
+
+static void WindowFocusCallback(GLFWwindow *window, int focused)
+{
+	(void)window;
+
+	if (focused)
+		ActiveWindow();
+	else
+		InactiveWindow();
+}
+
+static void WindowSizeCallback(GLFWwindow *window, int width, int height)
+{
+	(void)window;
+
+	RenderBackend_HandleWindowResize(width, height);
+}
+
+static void DragAndDropCallback(GLFWwindow *window, int count, const char **paths)
+{
+	(void)window;
+	(void)count;
+
+	LoadProfile(paths[0]);
+}
+
+static void ErrorCallback(int code, const char *description)
+{
+	Backend_PrintError("GLFW error received (%d): %s", code, description);
+}
+
+bool Backend_Init(void)
+{
+	glfwSetErrorCallback(ErrorCallback);
+
+	if (glfwInit() == GL_TRUE)
+		return true;
+
+	Backend_ShowMessageBox("Fatal error", "Could not initialise GLFW3");
+
+	return false;
+}
+
+void Backend_Deinit(void)
+{
+	if (cursor != NULL)
+		glfwDestroyCursor(cursor);
+
+	glfwTerminate();
+}
+
+void Backend_PostWindowCreation(void)
+{
+	// Hook callbacks
+	glfwSetKeyCallback(window, KeyCallback);
+	glfwSetWindowFocusCallback(window, WindowFocusCallback);
+	glfwSetWindowSizeCallback(window, WindowSizeCallback);
+}
+
+bool Backend_GetBasePath(char *string_buffer)
+{
+	(void)string_buffer;
+
+	// GLFW3 doesn't seem to have a mechanism for this
+	return false;
+}
+
+void Backend_HideMouse(void)
+{
+	glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
+}
+
+void Backend_SetWindowIcon(const unsigned char *rgb_pixels, unsigned int width, unsigned int height)
+{
+	// Convert to RGBA, since that's the only thing GLFW3 accepts
+	unsigned char *rgba_pixels = (unsigned char*)malloc(width * height * 4);
+
+	const unsigned char *rgb_pointer = rgb_pixels;
+	unsigned char *rgba_pointer = rgba_pixels;
+
+	if (rgba_pixels != NULL)
+	{
+		for (unsigned int y = 0; y < height; ++y)
+		{
+			for (unsigned int x = 0; x < width; ++x)
+			{
+				*rgba_pointer++ = *rgb_pointer++;
+				*rgba_pointer++ = *rgb_pointer++;
+				*rgba_pointer++ = *rgb_pointer++;
+				*rgba_pointer++ = 0xFF;
+			}
+		}
+
+		GLFWimage glfw_image = {(int)width, (int)height, rgba_pixels};
+		glfwSetWindowIcon(window, 1, &glfw_image);
+
+		free(rgba_pixels);
+	}
+}
+
+void Backend_SetCursor(const unsigned char *rgb_pixels, unsigned int width, unsigned int height)
+{
+	// Convert to RGBA, since that's the only thing GLFW3 accepts
+	unsigned char *rgba_pixels = (unsigned char*)malloc(width * height * 4);
+
+	const unsigned char *rgb_pointer = rgb_pixels;
+	unsigned char *rgba_pointer = rgba_pixels;
+
+	if (rgba_pixels != NULL)
+	{
+		for (unsigned int y = 0; y < height; ++y)
+		{
+			for (unsigned int x = 0; x < width; ++x)
+			{
+				if (rgb_pointer[0] == 0xFF && rgb_pointer[1] == 0 && rgb_pointer[2] == 0xFF)	// Colour-key
+				{
+					*rgba_pointer++ = *rgb_pointer++;
+					*rgba_pointer++ = *rgb_pointer++;
+					*rgba_pointer++ = *rgb_pointer++;
+					*rgba_pointer++ = 0;
+				}
+				else
+				{
+					*rgba_pointer++ = *rgb_pointer++;
+					*rgba_pointer++ = *rgb_pointer++;
+					*rgba_pointer++ = *rgb_pointer++;
+					*rgba_pointer++ = 0xFF;
+				}
+			}
+		}
+
+		GLFWimage glfw_image = {(int)width, (int)height, rgba_pixels};
+		cursor = glfwCreateCursor(&glfw_image, 0, 0);
+
+		if (cursor != NULL)
+			glfwSetCursor(window, cursor);
+
+		free(rgba_pixels);
+	}
+}
+
+void PlaybackBackend_EnableDragAndDrop(void)
+{
+	glfwSetDropCallback(window, DragAndDropCallback);
+}
+
+bool Backend_SystemTask(bool active)
+{
+	if (glfwWindowShouldClose(window))
+	{
+		StopOrganyaMusic();
+		return false;
+	}
+
+	if (active)
+		glfwPollEvents();
+	else
+		glfwWaitEvents();
+
+	return true;
+}
+
+void Backend_GetKeyboardState(bool *out_keyboard_state)
+{
+	memcpy(out_keyboard_state, keyboard_state, sizeof(keyboard_state));
+}
+
+void Backend_ShowMessageBox(const char *title, const char *message)
+{
+	// GLFW3 doesn't have a message box
+	printf("ShowMessageBox - '%s' - '%s'\n", title, message);
+}
+
+ATTRIBUTE_FORMAT_PRINTF(1, 2) void Backend_PrintError(const char *format, ...)
+{
+	va_list argumentList;
+	va_start(argumentList, format);
+	fputs("ERROR: ", stderr);
+	vfprintf(stderr, format, argumentList);
+	fputc('\n', stderr);
+	va_end(argumentList);
+}
+
+ATTRIBUTE_FORMAT_PRINTF(1, 2) void Backend_PrintInfo(const char *format, ...)
+{
+	va_list argumentList;
+	va_start(argumentList, format);
+	fputs("INFO: ", stdout);
+	vprintf(format, argumentList);
+	putchar('\n');
+	va_end(argumentList);
+}
+
+unsigned long Backend_GetTicks(void)
+{
+	return (unsigned long)(glfwGetTime() * 1000.0);
+}
+
+void Backend_Delay(unsigned int ticks)
+{
+	// GLFW3 doesn't have a delay function, so here's some butt-ugly C++11
+	std::this_thread::sleep_for(std::chrono::milliseconds(ticks));
+}
--- /dev/null
+++ b/src/Backends/Platform/Null.cpp
@@ -1,0 +1,89 @@
+#include "../Misc.h"
+
+bool Backend_Init(void)
+{
+	return true;
+}
+
+void Backend_Deinit(void)
+{
+	
+}
+
+void Backend_PostWindowCreation(void)
+{
+	
+}
+
+bool Backend_GetBasePath(char *string_buffer)
+{
+	(void)string_buffer;
+
+	return false;
+}
+
+void Backend_HideMouse(void)
+{
+	
+}
+
+void Backend_SetWindowIcon(const unsigned char *rgb_pixels, unsigned int width, unsigned int height)
+{
+	(void)rgb_pixels;
+	(void)width;
+	(void)height;
+}
+
+void Backend_SetCursor(const unsigned char *rgb_pixels, unsigned int width, unsigned int height)
+{
+	(void)rgb_pixels;
+	(void)width;
+	(void)height;
+}
+
+void PlaybackBackend_EnableDragAndDrop(void)
+{
+	
+}
+
+bool Backend_SystemTask(bool active)
+{
+	(void)active;
+
+	return true;
+}
+
+void Backend_GetKeyboardState(bool *keyboard_state)
+{
+	(void)keyboard_state;
+}
+
+void Backend_ShowMessageBox(const char *title, const char *message)
+{
+	(void)title;
+	(void)message;
+}
+
+ATTRIBUTE_FORMAT_PRINTF(1, 2) void Backend_PrintError(const char *format, ...)
+{
+	(void)format;
+}
+
+ATTRIBUTE_FORMAT_PRINTF(1, 2) void Backend_PrintInfo(const char *format, ...)
+{
+	(void)format;
+}
+
+unsigned long Backend_GetTicks(void)
+{
+	static unsigned long fake_ticks = 0;
+
+	fake_ticks += 1000 / 50;
+
+	return fake_ticks;
+}
+
+void Backend_Delay(unsigned int ticks)
+{
+	(void)ticks;
+}
--- /dev/null
+++ b/src/Backends/Platform/SDL2.cpp
@@ -1,0 +1,339 @@
+#include "../Misc.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string>
+
+#include "SDL.h"
+
+#include "Controller.h"
+#include "Window.h"
+#include "../Rendering.h"
+#include "../../Main.h"
+#include "../../Organya.h"
+#include "../../Profile.h"
+#include "../../Resource.h"
+
+#define DO_KEY(SDL_KEY, BACKEND_KEY) \
+	case SDL_KEY: \
+		keyboard_state[BACKEND_KEY] = event.key.type == SDL_KEYDOWN; \
+		break;
+
+static bool keyboard_state[BACKEND_KEYBOARD_TOTAL];
+
+static unsigned char *cursor_surface_pixels;
+static SDL_Surface *cursor_surface;
+static SDL_Cursor *cursor;
+
+bool Backend_Init(void)
+{
+	if (SDL_Init(SDL_INIT_EVENTS) == 0)
+	{
+		if (SDL_InitSubSystem(SDL_INIT_VIDEO) == 0)
+		{
+			Backend_PrintInfo("Available SDL video drivers:");
+
+			for (int i = 0; i < SDL_GetNumVideoDrivers(); ++i)
+				Backend_PrintInfo("%s", SDL_GetVideoDriver(i));
+
+			const char *driver = SDL_GetCurrentVideoDriver();
+
+			if (driver != NULL)
+			{
+				Backend_PrintInfo("Selected SDL video driver: %s", driver);
+
+				return true;
+			}
+			else
+			{
+				Backend_PrintError("No SDL video driver initialized!");
+			}
+		}
+		else
+		{
+			std::string error_message = std::string("Could not initialise SDL video subsystem: ") + SDL_GetError();
+			Backend_ShowMessageBox("Fatal error", error_message.c_str());
+		}
+
+		SDL_Quit();
+	}
+	else
+	{
+		std::string error_message = std::string("Could not initialise SDL: ") + SDL_GetError();
+		Backend_ShowMessageBox("Fatal error", error_message.c_str());
+	}
+
+	return false;
+}
+
+void Backend_Deinit(void)
+{
+	if (cursor != NULL)
+		SDL_FreeCursor(cursor);
+
+	if (cursor_surface != NULL)
+		SDL_FreeSurface(cursor_surface);
+
+	free(cursor_surface_pixels);
+
+	SDL_Quit();
+}
+
+void Backend_PostWindowCreation(void)
+{
+	
+}
+
+bool Backend_GetBasePath(char *string_buffer)
+{
+	char *base_path = SDL_GetBasePath();
+	if (base_path == NULL)
+		return false;
+
+	// Trim the trailing '/'
+	size_t base_path_length = strlen(base_path);
+	base_path[base_path_length - 1] = '\0';
+	strcpy(string_buffer, base_path);
+	SDL_free(base_path);
+
+	return true;
+}
+
+void Backend_HideMouse(void)
+{
+	SDL_ShowCursor(SDL_DISABLE);
+}
+
+void Backend_SetWindowIcon(const unsigned char *rgb_pixels, unsigned int width, unsigned int height)
+{
+	SDL_Surface *surface = SDL_CreateRGBSurfaceWithFormatFrom((void*)rgb_pixels, width, height, 0, width * 3, SDL_PIXELFORMAT_RGB24);
+
+	if (surface != NULL)
+	{
+		SDL_SetWindowIcon(window, surface);
+		SDL_FreeSurface(surface);
+	}
+	else
+	{
+		Backend_PrintError("Couldn't create RGB surface for window icon: %s", SDL_GetError());
+	}
+}
+
+void Backend_SetCursor(const unsigned char *rgb_pixels, unsigned int width, unsigned int height)
+{
+	cursor_surface_pixels = (unsigned char*)malloc(width * height * 3);
+
+	if (cursor_surface_pixels != NULL)
+	{
+		memcpy(cursor_surface_pixels, rgb_pixels, width * height * 3);
+
+		cursor_surface = SDL_CreateRGBSurfaceWithFormatFrom(cursor_surface_pixels, width, height, 0, width * 3, SDL_PIXELFORMAT_RGB24);
+
+		if (cursor_surface != NULL)
+		{
+			if (SDL_SetColorKey(cursor_surface, SDL_TRUE, SDL_MapRGB(cursor_surface->format, 0xFF, 0, 0xFF)) == 0)
+			{
+				cursor = SDL_CreateColorCursor(cursor_surface, 0, 0);
+
+				if (cursor != NULL)
+					SDL_SetCursor(cursor);
+			}
+		}
+	}
+	else
+	{
+		Backend_PrintError("Failed to allocate memory for cursor surface");
+	}
+}
+
+void PlaybackBackend_EnableDragAndDrop(void)
+{
+	SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
+}
+
+bool Backend_SystemTask(bool active)
+{
+	if (SDL_PollEvent(NULL) || !active)
+	{
+		SDL_Event event;
+
+		if (!SDL_WaitEvent(&event))
+			return false;
+
+		switch (event.type)
+		{
+			case SDL_KEYUP:
+			case SDL_KEYDOWN:
+				switch (event.key.keysym.sym)
+				{
+					DO_KEY(SDLK_a, BACKEND_KEYBOARD_A)
+					DO_KEY(SDLK_b, BACKEND_KEYBOARD_B)
+					DO_KEY(SDLK_c, BACKEND_KEYBOARD_C)
+					DO_KEY(SDLK_d, BACKEND_KEYBOARD_D)
+					DO_KEY(SDLK_e, BACKEND_KEYBOARD_E)
+					DO_KEY(SDLK_f, BACKEND_KEYBOARD_F)
+					DO_KEY(SDLK_g, BACKEND_KEYBOARD_G)
+					DO_KEY(SDLK_h, BACKEND_KEYBOARD_H)
+					DO_KEY(SDLK_i, BACKEND_KEYBOARD_I)
+					DO_KEY(SDLK_j, BACKEND_KEYBOARD_J)
+					DO_KEY(SDLK_k, BACKEND_KEYBOARD_K)
+					DO_KEY(SDLK_l, BACKEND_KEYBOARD_L)
+					DO_KEY(SDLK_m, BACKEND_KEYBOARD_M)
+					DO_KEY(SDLK_n, BACKEND_KEYBOARD_N)
+					DO_KEY(SDLK_o, BACKEND_KEYBOARD_O)
+					DO_KEY(SDLK_p, BACKEND_KEYBOARD_P)
+					DO_KEY(SDLK_q, BACKEND_KEYBOARD_Q)
+					DO_KEY(SDLK_r, BACKEND_KEYBOARD_R)
+					DO_KEY(SDLK_s, BACKEND_KEYBOARD_S)
+					DO_KEY(SDLK_t, BACKEND_KEYBOARD_T)
+					DO_KEY(SDLK_u, BACKEND_KEYBOARD_U)
+					DO_KEY(SDLK_v, BACKEND_KEYBOARD_V)
+					DO_KEY(SDLK_w, BACKEND_KEYBOARD_W)
+					DO_KEY(SDLK_x, BACKEND_KEYBOARD_X)
+					DO_KEY(SDLK_y, BACKEND_KEYBOARD_Y)
+					DO_KEY(SDLK_z, BACKEND_KEYBOARD_Z)
+					DO_KEY(SDLK_0, BACKEND_KEYBOARD_0)
+					DO_KEY(SDLK_1, BACKEND_KEYBOARD_1)
+					DO_KEY(SDLK_2, BACKEND_KEYBOARD_2)
+					DO_KEY(SDLK_3, BACKEND_KEYBOARD_3)
+					DO_KEY(SDLK_4, BACKEND_KEYBOARD_4)
+					DO_KEY(SDLK_5, BACKEND_KEYBOARD_5)
+					DO_KEY(SDLK_6, BACKEND_KEYBOARD_6)
+					DO_KEY(SDLK_7, BACKEND_KEYBOARD_7)
+					DO_KEY(SDLK_8, BACKEND_KEYBOARD_8)
+					DO_KEY(SDLK_9, BACKEND_KEYBOARD_9)
+					DO_KEY(SDLK_F1, BACKEND_KEYBOARD_F1)
+					DO_KEY(SDLK_F2, BACKEND_KEYBOARD_F2)
+					DO_KEY(SDLK_F3, BACKEND_KEYBOARD_F3)
+					DO_KEY(SDLK_F4, BACKEND_KEYBOARD_F4)
+					DO_KEY(SDLK_F5, BACKEND_KEYBOARD_F5)
+					DO_KEY(SDLK_F6, BACKEND_KEYBOARD_F6)
+					DO_KEY(SDLK_F7, BACKEND_KEYBOARD_F7)
+					DO_KEY(SDLK_F8, BACKEND_KEYBOARD_F8)
+					DO_KEY(SDLK_F9, BACKEND_KEYBOARD_F9)
+					DO_KEY(SDLK_F10, BACKEND_KEYBOARD_F10)
+					DO_KEY(SDLK_F11, BACKEND_KEYBOARD_F11)
+					DO_KEY(SDLK_F12, BACKEND_KEYBOARD_F12)
+					DO_KEY(SDLK_UP, BACKEND_KEYBOARD_UP)
+					DO_KEY(SDLK_DOWN, BACKEND_KEYBOARD_DOWN)
+					DO_KEY(SDLK_LEFT, BACKEND_KEYBOARD_LEFT)
+					DO_KEY(SDLK_RIGHT, BACKEND_KEYBOARD_RIGHT)
+					DO_KEY(SDLK_ESCAPE, BACKEND_KEYBOARD_ESCAPE)
+					DO_KEY(SDLK_BACKQUOTE, BACKEND_KEYBOARD_BACK_QUOTE)
+					DO_KEY(SDLK_TAB, BACKEND_KEYBOARD_TAB)
+					DO_KEY(SDLK_CAPSLOCK, BACKEND_KEYBOARD_CAPS_LOCK)
+					DO_KEY(SDLK_LSHIFT, BACKEND_KEYBOARD_LEFT_SHIFT)
+					DO_KEY(SDLK_LCTRL, BACKEND_KEYBOARD_LEFT_CTRL)
+					DO_KEY(SDLK_LALT, BACKEND_KEYBOARD_LEFT_ALT)
+					DO_KEY(SDLK_SPACE, BACKEND_KEYBOARD_SPACE)
+					DO_KEY(SDLK_RALT, BACKEND_KEYBOARD_RIGHT_ALT)
+					DO_KEY(SDLK_RCTRL, BACKEND_KEYBOARD_RIGHT_CTRL)
+					DO_KEY(SDLK_RSHIFT, BACKEND_KEYBOARD_RIGHT_SHIFT)
+					DO_KEY(SDLK_RETURN, BACKEND_KEYBOARD_ENTER)
+					DO_KEY(SDLK_BACKSPACE, BACKEND_KEYBOARD_BACKSPACE)
+					DO_KEY(SDLK_MINUS, BACKEND_KEYBOARD_MINUS)
+					DO_KEY(SDLK_EQUALS, BACKEND_KEYBOARD_EQUALS)
+					DO_KEY(SDLK_LEFTBRACKET, BACKEND_KEYBOARD_LEFT_BRACKET)
+					DO_KEY(SDLK_RIGHTBRACKET, BACKEND_KEYBOARD_RIGHT_BRACKET)
+					DO_KEY(SDLK_BACKSLASH, BACKEND_KEYBOARD_BACK_SLASH)
+					DO_KEY(SDLK_SEMICOLON, BACKEND_KEYBOARD_SEMICOLON)
+					DO_KEY(SDLK_QUOTE, BACKEND_KEYBOARD_APOSTROPHE)
+					DO_KEY(SDLK_COMMA, BACKEND_KEYBOARD_COMMA)
+					DO_KEY(SDLK_PERIOD, BACKEND_KEYBOARD_PERIOD)
+					DO_KEY(SDLK_SLASH, BACKEND_KEYBOARD_FORWARD_SLASH)
+
+					default:
+						break;
+				}
+
+				break;
+
+			case SDL_JOYDEVICEADDED:
+				ControllerBackend_JoystickConnect(event.jdevice.which);
+				break;
+
+			case SDL_JOYDEVICEREMOVED:
+				ControllerBackend_JoystickDisconnect(event.jdevice.which);
+				break;
+
+			case SDL_DROPFILE:
+				LoadProfile(event.drop.file);
+				SDL_free(event.drop.file);
+				break;
+
+			case SDL_WINDOWEVENT:
+				switch (event.window.event)
+				{
+					case SDL_WINDOWEVENT_FOCUS_LOST:
+						InactiveWindow();
+						break;
+
+					case SDL_WINDOWEVENT_FOCUS_GAINED:
+						ActiveWindow();
+						break;
+
+					case SDL_WINDOWEVENT_RESIZED:
+					case SDL_WINDOWEVENT_SIZE_CHANGED:
+						RenderBackend_HandleWindowResize(event.window.data1, event.window.data2);
+						break;
+				}
+
+				break;
+
+			case SDL_QUIT:
+				StopOrganyaMusic();
+				return false;
+
+			case SDL_RENDER_TARGETS_RESET:
+				RenderBackend_HandleRenderTargetLoss();
+				break;
+
+		}
+	}
+
+	return true;
+}
+
+void Backend_GetKeyboardState(bool *out_keyboard_state)
+{
+	memcpy(out_keyboard_state, keyboard_state, sizeof(keyboard_state));
+}
+
+void Backend_ShowMessageBox(const char *title, const char *message)
+{
+	fprintf(stderr, "ShowMessageBox - '%s' - '%s'\n", title, message);
+
+	if (SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title, message, window) != 0)
+		Backend_PrintError("Was also unable to display a message box containing the error: %s", SDL_GetError());
+}
+
+ATTRIBUTE_FORMAT_PRINTF(1, 2) void Backend_PrintError(const char *format, ...)
+{
+	va_list argumentList;
+	va_start(argumentList, format);
+	fputs("ERROR: ", stderr);
+	vfprintf(stderr, format, argumentList);
+	fputc('\n', stderr);
+	va_end(argumentList);
+}
+
+ATTRIBUTE_FORMAT_PRINTF(1, 2) void Backend_PrintInfo(const char *format, ...)
+{
+	va_list argumentList;
+	va_start(argumentList, format);
+	fputs("INFO: ", stdout);
+	vprintf(format, argumentList);
+	putchar('\n');
+	va_end(argumentList);
+}
+
+unsigned long Backend_GetTicks(void)
+{
+	return SDL_GetTicks();
+}
+
+void Backend_Delay(unsigned int ticks)
+{
+	SDL_Delay(ticks);
+}
--- /dev/null
+++ b/src/Backends/Platform/WiiU.cpp
@@ -1,0 +1,190 @@
+#include "../Misc.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <coreinit/thread.h>
+#include <padscore/kpad.h>
+#include <vpad/input.h>
+#include <whb/log.h>
+#include <whb/log_udp.h>
+#include <whb/proc.h>
+#include <whb/sdcard.h>
+
+static unsigned long ticks_per_second;
+
+bool Backend_Init(void)
+{
+	WHBProcInit();
+
+	if (!WHBMountSdCard())
+		return FALSE;
+
+	VPADInit();
+
+	WPADInit();
+	KPADInit();
+
+	// Enable Wii U Pro Controllers to be connected
+	WPADEnableURCC(1);
+
+	WHBLogUdpInit();
+
+	ticks_per_second = OSGetSystemInfo()->busClockSpeed / 4;
+
+	return true;
+}
+
+void Backend_Deinit(void)
+{
+	WHBLogUdpDeinit();
+
+	WPADShutdown();
+
+	VPADShutdown();
+
+	WHBUnmountSdCard();
+
+	WHBProcShutdown();
+}
+
+void Backend_PostWindowCreation(void)
+{
+	
+}
+
+bool Backend_GetBasePath(char *string_buffer)
+{
+	strcpy(string_buffer, WHBGetSdCardMountPath());
+#ifdef JAPANESE
+	strcat(string_buffer, "/CSE2-portable-jp");
+#else
+	strcat(string_buffer, "/CSE2-portable-en");
+#endif
+
+	return true;
+}
+
+void Backend_HideMouse(void)
+{
+	
+}
+
+void Backend_SetWindowIcon(const unsigned char *rgb_pixels, unsigned int width, unsigned int height)
+{
+	(void)rgb_pixels;
+	(void)width;
+	(void)height;
+}
+
+void Backend_SetCursor(const unsigned char *rgb_pixels, unsigned int width, unsigned int height)
+{
+	(void)rgb_pixels;
+	(void)width;
+	(void)height;
+}
+
+void PlaybackBackend_EnableDragAndDrop(void)
+{
+	
+}
+
+bool Backend_SystemTask(bool active)
+{
+	(void)active;
+
+	return WHBProcIsRunning();
+}
+
+void Backend_GetKeyboardState(bool *keyboard_state)
+{
+	memset(keyboard_state, 0, sizeof(bool) * BACKEND_KEYBOARD_TOTAL);
+
+	// Read gamepad
+	static uint32_t vpad_buttons;
+
+	VPADStatus vpad_status;
+	if (VPADRead(VPAD_CHAN_0, &vpad_status, 1, NULL) == 1)
+		vpad_buttons = vpad_status.hold;
+
+	keyboard_state[BACKEND_KEYBOARD_UP] |= vpad_buttons & (VPAD_BUTTON_UP | VPAD_STICK_L_EMULATION_UP);
+	keyboard_state[BACKEND_KEYBOARD_DOWN] |= vpad_buttons & (VPAD_BUTTON_DOWN | VPAD_STICK_L_EMULATION_DOWN);
+	keyboard_state[BACKEND_KEYBOARD_LEFT] |= vpad_buttons & (VPAD_BUTTON_LEFT | VPAD_STICK_L_EMULATION_LEFT);
+	keyboard_state[BACKEND_KEYBOARD_RIGHT] |= vpad_buttons & (VPAD_BUTTON_RIGHT | VPAD_STICK_L_EMULATION_RIGHT);
+	keyboard_state[BACKEND_KEYBOARD_Z] |= vpad_buttons & VPAD_BUTTON_B;                       // Jump
+	keyboard_state[BACKEND_KEYBOARD_X] |= vpad_buttons & VPAD_BUTTON_Y;                       // Shoot
+	keyboard_state[BACKEND_KEYBOARD_Q] |= vpad_buttons & (VPAD_BUTTON_A | VPAD_BUTTON_PLUS);  // Inventory
+	keyboard_state[BACKEND_KEYBOARD_W] |= vpad_buttons & (VPAD_BUTTON_X | VPAD_BUTTON_MINUS); // Map
+	keyboard_state[BACKEND_KEYBOARD_A] |= vpad_buttons & (VPAD_BUTTON_L | VPAD_BUTTON_ZL | VPAD_STICK_R_EMULATION_LEFT);  // Weapon left
+	keyboard_state[BACKEND_KEYBOARD_S] |= vpad_buttons & (VPAD_BUTTON_R | VPAD_BUTTON_ZR | VPAD_STICK_R_EMULATION_RIGHT); // Weapon right
+
+	// Read Wii U Pro Controller
+	static uint32_t kpad_buttons;
+
+	KPADStatus kpad_status;
+	if (KPADRead(WPAD_CHAN_0, &kpad_status, 1) == 1)
+		kpad_buttons = kpad_status.pro.hold;
+
+	keyboard_state[BACKEND_KEYBOARD_UP] |= kpad_buttons & (WPAD_PRO_BUTTON_UP | WPAD_PRO_STICK_L_EMULATION_UP);
+	keyboard_state[BACKEND_KEYBOARD_DOWN] |= kpad_buttons & (WPAD_PRO_BUTTON_DOWN | WPAD_PRO_STICK_L_EMULATION_DOWN);
+	keyboard_state[BACKEND_KEYBOARD_LEFT] |= kpad_buttons & (WPAD_PRO_BUTTON_LEFT | WPAD_PRO_STICK_L_EMULATION_LEFT);
+	keyboard_state[BACKEND_KEYBOARD_RIGHT] |= kpad_buttons & (WPAD_PRO_BUTTON_RIGHT | WPAD_PRO_STICK_L_EMULATION_RIGHT);
+	keyboard_state[BACKEND_KEYBOARD_Z] |= kpad_buttons & WPAD_PRO_BUTTON_B;                           // Jump
+	keyboard_state[BACKEND_KEYBOARD_X] |= kpad_buttons & WPAD_PRO_BUTTON_Y;                           // Shoot
+	keyboard_state[BACKEND_KEYBOARD_Q] |= kpad_buttons & (WPAD_PRO_BUTTON_A | WPAD_PRO_BUTTON_PLUS);  // Inventory
+	keyboard_state[BACKEND_KEYBOARD_W] |= kpad_buttons & (WPAD_PRO_BUTTON_X | WPAD_PRO_BUTTON_MINUS); // Map
+	keyboard_state[BACKEND_KEYBOARD_A] |= kpad_buttons & (WPAD_PRO_TRIGGER_L | WPAD_PRO_TRIGGER_ZL | WPAD_PRO_STICK_R_EMULATION_LEFT);  // Weapon left
+	keyboard_state[BACKEND_KEYBOARD_S] |= kpad_buttons & (WPAD_PRO_TRIGGER_R | WPAD_PRO_TRIGGER_ZR | WPAD_PRO_STICK_R_EMULATION_RIGHT); // Weapon right
+}
+
+void Backend_ShowMessageBox(const char *title, const char *message)
+{
+	Backend_PrintInfo("ShowMessageBox - %s - %s", title, message);
+}
+
+ATTRIBUTE_FORMAT_PRINTF(1, 2) void Backend_PrintError(const char *format, ...)
+{
+	char message_buffer[0x100];
+
+	va_list argument_list;
+	va_start(argument_list, format);
+	vsnprintf(message_buffer, sizeof(message_buffer), format, argument_list);
+	va_end(argument_list);
+
+	WHBLogPrint("ERROR:");
+	WHBLogPrint(message_buffer);
+}
+
+ATTRIBUTE_FORMAT_PRINTF(1, 2) void Backend_PrintInfo(const char *format, ...)
+{
+	char message_buffer[0x100];
+
+	va_list argument_list;
+	va_start(argument_list, format);
+	vsnprintf(message_buffer, sizeof(message_buffer), format, argument_list);
+	va_end(argument_list);
+
+	WHBLogPrint("INFO:");
+	WHBLogPrint(message_buffer);
+}
+
+unsigned long Backend_GetTicks(void)
+{
+	static uint64_t accumulator;
+
+	static unsigned long last_tick;
+
+	unsigned long current_tick = OSGetTick();
+
+	accumulator += current_tick - last_tick;
+
+	last_tick = current_tick;
+
+	return (accumulator * 1000) / ticks_per_second;
+}
+
+void Backend_Delay(unsigned int ticks)
+{
+	OSSleepTicks((ticks * ticks_per_second) / 1000);
+}
--- /dev/null
+++ b/src/Backends/Rendering/WiiUShaders/colour_fill.gsh.h
@@ -1,0 +1,50 @@
+#pragma once
+
+static const unsigned char rcolour_fill[0x5AC] = {
+	71,102,120,50,0,0,0,32,0,0,0,7,0,0,0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,
+	66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,0,0,0,3,0,0,1,144,0,0,0,0,0,0,0,0,
+	0,0,1,2,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,0,255,255,255,255,255,255,255,255,255,255,255,255,
+	255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,255,255,255,254,
+	0,0,0,1,0,0,0,0,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
+	0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
+	0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
+	0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
+	0,0,0,255,0,0,0,0,0,0,0,14,0,0,0,16,0,0,1,8,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,1,208,96,1,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,202,112,1,68,0,0,0,9,0,0,0,0,
+	0,0,0,0,105,110,112,117,116,95,118,101,114,116,101,120,95,99,111,111,114,100,105,110,97,116,101,115,0,0,0,0,
+	208,96,1,8,202,112,1,52,125,66,76,75,0,0,0,40,0,0,0,0,0,0,1,96,208,96,0,0,0,0,0,28,
+	208,96,1,68,0,0,0,0,0,0,0,2,208,96,1,96,66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,
+	0,0,0,5,0,0,1,8,0,0,0,1,0,0,0,0,0,0,0,0,0,0,128,9,60,160,0,0,136,6,0,148,
+	0,64,0,0,255,15,0,148,32,0,0,0,0,0,0,160,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,13,0,0,66,76,75,123,0,0,0,32,
+	0,0,0,1,0,0,0,0,0,0,0,6,0,0,1,52,0,0,0,2,0,0,0,0,0,0,0,1,0,0,0,2,
+	20,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,1,0,0,0,16,0,0,0,0,0,0,1,32,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,208,96,0,232,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	202,112,0,252,0,0,0,11,0,0,0,1,0,0,0,0,255,255,255,255,99,111,108,111,117,114,0,0,208,96,0,188,
+	202,112,0,232,125,66,76,75,0,0,0,40,0,0,0,0,0,0,1,4,208,96,0,0,0,0,0,8,208,96,0,252,
+	0,0,0,0,0,0,0,2,208,96,1,4,66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,0,0,0,7,
+	0,0,1,32,0,0,0,3,0,0,0,0,32,0,0,0,0,0,12,160,0,0,0,0,136,6,32,148,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,144,12,0,0,0,5,0,0,144,12,0,32,0,9,0,0,
+	144,12,0,64,0,13,0,128,144,12,0,96,66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,0,0,0,1,
+	0,0,0,0,0,0,0,4,0,0,0,0
+};
--- /dev/null
+++ b/src/Backends/Rendering/WiiUShaders/glyph.gsh.h
@@ -1,0 +1,56 @@
+#pragma once
+
+static const unsigned char rglyph[0x674] = {
+	71,102,120,50,0,0,0,32,0,0,0,7,0,0,0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,
+	66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,0,0,0,3,0,0,1,192,0,0,0,0,0,0,0,0,
+	0,0,1,3,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,0,255,255,255,255,255,255,255,255,255,255,255,255,
+	255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,255,255,255,252,
+	0,0,0,2,0,0,0,1,0,0,0,0,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
+	0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
+	0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
+	0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
+	0,0,0,255,0,0,0,0,0,0,0,14,0,0,0,16,0,0,1,24,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,2,208,96,1,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,202,112,1,84,0,0,0,9,0,0,0,0,
+	0,0,0,1,202,112,1,112,0,0,0,9,0,0,0,0,0,0,0,0,105,110,112,117,116,95,116,101,120,116,117,114,
+	101,95,99,111,111,114,100,105,110,97,116,101,115,0,0,0,105,110,112,117,116,95,118,101,114,116,101,120,95,99,111,111,
+	114,100,105,110,97,116,101,115,0,0,0,0,208,96,1,8,202,112,1,52,202,112,1,68,125,66,76,75,0,0,0,40,
+	0,0,0,0,0,0,1,140,208,96,0,0,0,0,0,56,208,96,1,84,0,0,0,0,0,0,0,3,208,96,1,140,
+	66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,0,0,0,5,0,0,1,24,0,0,0,1,0,0,0,0,
+	0,0,0,0,0,0,128,9,32,0,0,0,0,0,4,160,60,32,1,0,136,6,0,148,0,192,0,0,136,4,0,20,
+	34,0,0,0,0,0,0,160,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	1,0,0,0,144,12,32,0,1,4,0,128,144,12,32,32,0,0,0,128,0,13,0,0,66,76,75,123,0,0,0,32,
+	0,0,0,1,0,0,0,0,0,0,0,6,0,0,1,76,0,0,0,2,0,0,0,0,0,0,0,1,0,0,0,2,
+	20,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,1,0,0,0,16,0,0,0,0,0,0,1,144,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,208,96,0,232,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,1,208,96,0,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	202,112,1,8,0,0,0,11,0,0,0,1,0,0,0,0,255,255,255,255,202,112,1,16,0,0,0,1,0,0,0,0,
+	99,111,108,111,117,114,0,0,116,101,120,0,208,96,0,188,208,96,0,212,202,112,0,232,202,112,0,252,125,66,76,75,
+	0,0,0,40,0,0,0,0,0,0,1,20,208,96,0,0,0,0,0,12,208,96,1,8,0,0,0,0,0,0,0,4,
+	208,96,1,20,66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,0,0,0,7,0,0,1,144,0,0,0,3,
+	0,0,0,0,48,0,0,0,0,0,192,128,32,0,0,0,0,0,20,160,0,0,0,0,136,6,32,148,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,161,31,0,144,0,0,0,0,165,159,0,144,0,0,32,0,169,31,1,144,0,0,64,0,12,160,129,
+	144,0,0,96,0,0,128,63,0,0,128,63,0,0,128,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,16,0,0,0,0,254,3,240,0,0,128,16,0,0,0,0,66,76,75,123,0,0,0,32,0,0,0,1,
+	0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,4,0,0,0,0
+};
--- /dev/null
+++ b/src/Backends/Rendering/WiiUShaders/shader sources/colour_fill.frag
@@ -1,0 +1,7 @@
+#version 150 core
+layout(location = 0) uniform vec4 colour;
+out vec4 fragment;
+void main()
+{
+	fragment = colour;
+}
\ No newline at end of file
binary files /dev/null b/src/Backends/Rendering/WiiUShaders/shader sources/colour_fill.gsh differ
--- /dev/null
+++ b/src/Backends/Rendering/WiiUShaders/shader sources/colour_fill.psh
@@ -1,0 +1,18 @@
+; $MODE = "UniformRegister"
+; $NUM_SPI_PS_INPUT_CNTL = 1
+; $SPI_PS_INPUT_CNTL[0].semantic = 0
+; $SPI_PS_INPUT_CNTL[0].default_val = 1
+; $UNIFORM_VARS[0].name = "colour"
+; $UNIFORM_VARS[0].type = "vec4"
+; $UNIFORM_VARS[0].count = 1
+; $UNIFORM_VARS[0].offset = 0
+; $UNIFORM_VARS[0].block = -1
+
+00 ALU: ADDR(32) CNT(4) 
+      0  x: MOV         R0.x,  C0.x      
+         y: MOV         R0.y,  C0.y      
+         z: MOV         R0.z,  C0.z      
+         w: MOV         R0.w,  C0.w      
+01 EXP_DONE: PIX0, R0
+END_OF_PROGRAM
+
--- /dev/null
+++ b/src/Backends/Rendering/WiiUShaders/shader sources/glyph.frag
@@ -1,0 +1,9 @@
+#version 150 core
+layout(location = 0) uniform sampler2D tex;
+layout(location = 0) uniform vec4 colour;
+in vec2 texture_coordinates;
+out vec4 fragment;
+void main()
+{
+	fragment = colour * vec4(1.0, 1.0, 1.0, texture(tex, texture_coordinates).r);
+}
\ No newline at end of file
binary files /dev/null b/src/Backends/Rendering/WiiUShaders/shader sources/glyph.gsh differ
--- /dev/null
+++ b/src/Backends/Rendering/WiiUShaders/shader sources/glyph.psh
@@ -1,0 +1,22 @@
+; $MODE = "UniformRegister"
+; $SAMPLER_VARS[0].name= "tex"
+; $SAMPLER_VARS[0].type= "sampler2D"
+; $SAMPLER_VARS[0].location = 0
+; $NUM_SPI_PS_INPUT_CNTL = 1
+; $SPI_PS_INPUT_CNTL[0].semantic = 0
+; $SPI_PS_INPUT_CNTL[0].default_val = 1
+; $UNIFORM_VARS[0].name = "colour"
+; $UNIFORM_VARS[0].type = "vec4"
+; $UNIFORM_VARS[0].count = 1
+; $UNIFORM_VARS[0].offset = 0
+; $UNIFORM_VARS[0].block = -1
+
+00 TEX: ADDR(48) CNT(1) VALID_PIX 
+      0  SAMPLE R0.___x, R0.xy0x, t0, s0
+01 ALU: ADDR(32) CNT(6) 
+      1  x: MUL         R0.x,  C0.x,  1.0f      
+         y: MUL         R0.y,  C0.y,  1.0f      
+         z: MUL         R0.z,  C0.z,  1.0f      
+         w: MUL         R0.w,  R0.w,  C0.w      
+02 EXP_DONE: PIX0, R0
+END_OF_PROGRAM
--- /dev/null
+++ b/src/Backends/Rendering/WiiUShaders/shader sources/plain.vert
@@ -1,0 +1,6 @@
+#version 150 core
+layout(location = 0) in vec4 input_vertex_coordinates;
+void main() 
+{ 
+	gl_Position = input_vertex_coordinates; 
+} 
--- /dev/null
+++ b/src/Backends/Rendering/WiiUShaders/shader sources/plain.vsh
@@ -1,0 +1,20 @@
+; $MODE = "UniformRegister"
+; $ATTRIB_VARS[0].name = "input_vertex_coordinates"
+; $ATTRIB_VARS[0].type = "vec2"
+; $ATTRIB_VARS[0].location = 0
+; $NUM_SPI_VS_OUT_ID = 1
+; $SPI_VS_OUT_ID[0].SEMANTIC_0 = 0
+
+00 CALL_FS NO_BARRIER 
+01 EXP_DONE: POS0, R1
+02 EXP_DONE: PARAM0, R0.____
+03 ALU: ADDR(32) CNT(1) 
+      0  x: NOP         ____      
+04 NOP NO_BARRIER 
+END_OF_PROGRAM
+
+
+
+
+
+
--- /dev/null
+++ b/src/Backends/Rendering/WiiUShaders/shader sources/texture.frag
@@ -1,0 +1,8 @@
+#version 150 core
+layout(location = 0) uniform sampler2D tex;
+in vec2 texture_coordinates;
+out vec4 fragment;
+void main()
+{
+	fragment = texture(tex, texture_coordinates);
+}
\ No newline at end of file
binary files /dev/null b/src/Backends/Rendering/WiiUShaders/shader sources/texture.gsh differ
--- /dev/null
+++ b/src/Backends/Rendering/WiiUShaders/shader sources/texture.psh
@@ -1,0 +1,18 @@
+; $MODE = "UniformRegister"
+; $SAMPLER_VARS[0].name= "tex"
+; $SAMPLER_VARS[0].type= "sampler2D"
+; $SAMPLER_VARS[0].location = 0
+; $NUM_SPI_PS_INPUT_CNTL = 1
+; $SPI_PS_INPUT_CNTL[0].semantic = 0
+; $SPI_PS_INPUT_CNTL[0].default_val = 1
+; $UNIFORM_VARS[0].name = "texture_coordinates"
+; $UNIFORM_VARS[0].type = "vec2"
+; $UNIFORM_VARS[0].count = 1
+; $UNIFORM_VARS[0].offset = 0
+; $UNIFORM_VARS[0].block = -1
+
+00 TEX: ADDR(32) CNT(1) VALID_PIX 
+      0  SAMPLE R0, R0.xy0x, t0, s0
+01 EXP_DONE: PIX0, R0
+END_OF_PROGRAM
+
--- /dev/null
+++ b/src/Backends/Rendering/WiiUShaders/shader sources/texture.vert
@@ -1,0 +1,9 @@
+#version 150 core
+layout(location = 0) in vec4 input_vertex_coordinates;
+layout(location = 1) in vec2 input_texture_coordinates;
+out vec2 texture_coordinates; 
+void main() 
+{ 
+	gl_Position = input_vertex_coordinates; 
+	texture_coordinates = input_texture_coordinates; 
+} 
--- /dev/null
+++ b/src/Backends/Rendering/WiiUShaders/shader sources/texture.vsh
@@ -1,0 +1,27 @@
+; $MODE = "UniformRegister"
+; $ATTRIB_VARS[0].name = "input_texture_coordinates"
+; $ATTRIB_VARS[0].type = "vec2"
+; $ATTRIB_VARS[0].location = 1
+; $ATTRIB_VARS[1].name = "input_vertex_coordinates"
+; $ATTRIB_VARS[1].type = "vec2"
+; $ATTRIB_VARS[1].location = 0
+; $NUM_SPI_VS_OUT_ID = 1
+; $SPI_VS_OUT_ID[0].SEMANTIC_0 = 0
+
+00 CALL_FS NO_BARRIER 
+01 ALU: ADDR(32) CNT(2) 
+      0  x: MOV         R1.x,  R1.x      
+         y: MOV         R1.y,  R1.y      
+02 EXP_DONE: POS0, R2
+03 EXP_DONE: PARAM0, R1.xyzz  NO_BARRIER 
+04 ALU: ADDR(34) CNT(1) 
+      1  x: NOP         ____      
+05 NOP NO_BARRIER 
+END_OF_PROGRAM
+
+
+
+
+
+
+
--- /dev/null
+++ b/src/Backends/Rendering/WiiUShaders/shader sources/texture_colour_key.frag
@@ -1,0 +1,13 @@
+#version 150 core
+layout(location = 0) uniform sampler2D tex;
+in vec2 texture_coordinates;
+out vec4 fragment;
+void main()
+{
+	vec4 colour = texture(tex, texture_coordinates);
+
+	if (colour.r + colour.g + colour. b == 0.0)
+		discard;
+
+	fragment = colour;
+}
\ No newline at end of file
binary files /dev/null b/src/Backends/Rendering/WiiUShaders/shader sources/texture_colour_key.gsh differ
--- /dev/null
+++ b/src/Backends/Rendering/WiiUShaders/shader sources/texture_colour_key.psh
@@ -1,0 +1,25 @@
+; $MODE = "UniformRegister"
+; $SAMPLER_VARS[0].name= "tex"
+; $SAMPLER_VARS[0].type= "sampler2D"
+; $SAMPLER_VARS[0].location = 0
+; $NUM_SPI_PS_INPUT_CNTL = 1
+; $SPI_PS_INPUT_CNTL[0].semantic = 0
+; $SPI_PS_INPUT_CNTL[0].default_val = 1
+; $UNIFORM_VARS[0].name = "texture_coordinates"
+; $UNIFORM_VARS[0].type = "vec2"
+; $UNIFORM_VARS[0].count = 1
+; $UNIFORM_VARS[0].offset = 0
+; $UNIFORM_VARS[0].block = -1
+
+00 TEX: ADDR(48) CNT(1) VALID_PIX 
+      0  SAMPLE R0, R0.xy0x, t0, s0
+01 ALU: ADDR(32) CNT(3) 
+      1  y: ADD         ____,  R0.x,  R0.y      
+      2  x: ADD         ____,  R0.z,  PV1.y      
+      3  x: KILLE       ____,  PV2.x,  0.0f      
+02 EXP_DONE: PIX0, R0
+END_OF_PROGRAM
+
+
+
+
--- /dev/null
+++ b/src/Backends/Rendering/WiiUShaders/shader sources/wtf is this.txt
@@ -1,0 +1,61 @@
+#######################
+# Shader combinations #
+#######################
+
+texture.vsh + texture.psh = texture.gsh
+texture.vsh + texture_colour_key.psh = texture_colour_key.gsh
+plain.vsh + colour_fill.psh = colour_fill.gsh
+texture.vsh + glyph.psh = glyph.gsh
+
+
+##########################
+# How to compile shaders #
+##########################
+
+I'mma give it to you straight: compiling shaders for the Wii U is an absolute
+nightmare.
+
+You see, there are three major steps:
+* Compile the GLSL to assembly
+* Fill in a header by-hand
+* Assemble the assembly + header
+
+To compile, you need AMD's 'GPU ShaderAnalyzer', which is Windows-only:
+https://gpuopen.com/archive/gpu-shaderanalyzer/
+
+You then need to compile for the RV730.
+
+After that, you need to fill-in a header. The only way I was able to figure out
+what little I did was by finding examples on the internet:
+https://github.com/snickerbockers/gx2gl/tree/master/src/shaders
+https://github.com/yawut/SDL/tree/wiiu-2.0.9/src/video/wiiu/shaders
+https://github.com/devkitPro/wut/tree/master/samples/cmake/content
+
+Even now, I don't have a complete idea of exactly what everything means.
+
+Anyway, once you have *that* out of the way, you still need to assemble your
+shaders. For that, you'll need `latte-assembler` - a tool that comes with the
+Decaf emulator.
+
+For me, I needed to clone the entire source tree (and its dependencies) in order
+to build the tool successfully, which is a royal pain in the ass.
+
+For compilation, I used MSYS2+MinGW-w64. There were a couple of compilation
+errors I had to address, but nothing too hard to solve.
+
+Eventually you'll have the assembler built. With it, you can link your `.psh`
+and `.vsh` files into the final `.gsh` blob.
+
+Oh, right. I should warn you - the devs changed latte-assembler's syntax at some
+point, so all the example `.psh`/`.vsh` files I could find online were
+incompatible. That sucked. The main change was that stuff like 'float2' and
+'float4' were changed to 'vec2' and 'vec4'. Just keep that in mind, and you
+should be okay.
+
+Also, latte-assembler's 'assemble' command was originally named 'compile', and
+the other 'compile' option didn't exist. Keep that in mind if you ever find any
+documentation on how to use the tool.
+
+latte-assembler does have an option to convert straight from GLSL to `.gsh`, but
+this feature is woefully incomplete (it doesn't support `sampler2D`), and not
+worth using.
--- /dev/null
+++ b/src/Backends/Rendering/WiiUShaders/texture.gsh.h
@@ -1,0 +1,52 @@
+#pragma once
+
+static const unsigned char rtexture[0x600] = {
+	71,102,120,50,0,0,0,32,0,0,0,7,0,0,0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,
+	66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,0,0,0,3,0,0,1,192,0,0,0,0,0,0,0,0,
+	0,0,1,3,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,0,255,255,255,255,255,255,255,255,255,255,255,255,
+	255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,255,255,255,252,
+	0,0,0,2,0,0,0,1,0,0,0,0,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
+	0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
+	0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
+	0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
+	0,0,0,255,0,0,0,0,0,0,0,14,0,0,0,16,0,0,1,24,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,2,208,96,1,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,202,112,1,84,0,0,0,9,0,0,0,0,
+	0,0,0,1,202,112,1,112,0,0,0,9,0,0,0,0,0,0,0,0,105,110,112,117,116,95,116,101,120,116,117,114,
+	101,95,99,111,111,114,100,105,110,97,116,101,115,0,0,0,105,110,112,117,116,95,118,101,114,116,101,120,95,99,111,111,
+	114,100,105,110,97,116,101,115,0,0,0,0,208,96,1,8,202,112,1,52,202,112,1,68,125,66,76,75,0,0,0,40,
+	0,0,0,0,0,0,1,140,208,96,0,0,0,0,0,56,208,96,1,84,0,0,0,0,0,0,0,3,208,96,1,140,
+	66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,0,0,0,5,0,0,1,24,0,0,0,1,0,0,0,0,
+	0,0,0,0,0,0,128,9,32,0,0,0,0,0,4,160,60,32,1,0,136,6,0,148,0,192,0,0,136,4,0,20,
+	34,0,0,0,0,0,0,160,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	1,0,0,0,144,12,32,0,1,4,0,128,144,12,32,32,0,0,0,128,0,13,0,0,66,76,75,123,0,0,0,32,
+	0,0,0,1,0,0,0,0,0,0,0,6,0,0,1,88,0,0,0,2,0,0,0,0,0,0,0,1,0,0,0,2,
+	20,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,1,0,0,0,16,0,0,0,0,0,0,1,16,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,208,96,0,232,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,1,208,96,0,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	202,112,1,8,0,0,0,9,0,0,0,1,0,0,0,0,255,255,255,255,202,112,1,28,0,0,0,1,0,0,0,0,
+	116,101,120,116,117,114,101,95,99,111,111,114,100,105,110,97,116,101,115,0,116,101,120,0,208,96,0,188,208,96,0,212,
+	202,112,0,232,202,112,0,252,125,66,76,75,0,0,0,40,0,0,0,0,0,0,1,32,208,96,0,0,0,0,0,24,
+	208,96,1,8,0,0,0,0,0,0,0,4,208,96,1,32,66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,
+	0,0,0,7,0,0,1,16,0,0,0,3,0,0,0,0,32,0,0,0,0,0,192,128,0,0,0,0,136,6,32,148,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,0,0,0,0,16,13,240,0,0,128,16,0,0,0,0,
+	66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,4,0,0,0,0
+};
--- /dev/null
+++ b/src/Backends/Rendering/WiiUShaders/texture_colour_key.gsh.h
@@ -1,0 +1,56 @@
+#pragma once
+
+static const unsigned char rtexture_colour_key[0x680] = {
+	71,102,120,50,0,0,0,32,0,0,0,7,0,0,0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,
+	66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,0,0,0,3,0,0,1,192,0,0,0,0,0,0,0,0,
+	0,0,1,3,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,0,255,255,255,255,255,255,255,255,255,255,255,255,
+	255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,255,255,255,252,
+	0,0,0,2,0,0,0,1,0,0,0,0,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
+	0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
+	0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
+	0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
+	0,0,0,255,0,0,0,0,0,0,0,14,0,0,0,16,0,0,1,24,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,2,208,96,1,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,202,112,1,84,0,0,0,9,0,0,0,0,
+	0,0,0,1,202,112,1,112,0,0,0,9,0,0,0,0,0,0,0,0,105,110,112,117,116,95,116,101,120,116,117,114,
+	101,95,99,111,111,114,100,105,110,97,116,101,115,0,0,0,105,110,112,117,116,95,118,101,114,116,101,120,95,99,111,111,
+	114,100,105,110,97,116,101,115,0,0,0,0,208,96,1,8,202,112,1,52,202,112,1,68,125,66,76,75,0,0,0,40,
+	0,0,0,0,0,0,1,140,208,96,0,0,0,0,0,56,208,96,1,84,0,0,0,0,0,0,0,3,208,96,1,140,
+	66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,0,0,0,5,0,0,1,24,0,0,0,1,0,0,0,0,
+	0,0,0,0,0,0,128,9,32,0,0,0,0,0,4,160,60,32,1,0,136,6,0,148,0,192,0,0,136,4,0,20,
+	34,0,0,0,0,0,0,160,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	1,0,0,0,144,12,32,0,1,4,0,128,144,12,32,32,0,0,0,128,0,13,0,0,66,76,75,123,0,0,0,32,
+	0,0,0,1,0,0,0,0,0,0,0,6,0,0,1,88,0,0,0,2,0,0,0,0,0,0,0,1,0,0,0,2,
+	20,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,1,0,0,0,16,0,0,0,0,0,0,1,144,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,208,96,0,232,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,1,208,96,0,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	202,112,1,8,0,0,0,9,0,0,0,1,0,0,0,0,255,255,255,255,202,112,1,28,0,0,0,1,0,0,0,0,
+	116,101,120,116,117,114,101,95,99,111,111,114,100,105,110,97,116,101,115,0,116,101,120,0,208,96,0,188,208,96,0,212,
+	202,112,0,232,202,112,0,252,125,66,76,75,0,0,0,40,0,0,0,0,0,0,1,32,208,96,0,0,0,0,0,24,
+	208,96,1,8,0,0,0,0,0,0,0,4,208,96,1,32,66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,
+	0,0,0,7,0,0,1,144,0,0,0,3,0,0,0,0,48,0,0,0,0,0,192,128,32,0,0,0,0,0,8,160,
+	0,0,0,0,136,6,32,148,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,128,0,0,0,32,0,200,159,128,0,0,0,0,
+	254,0,31,128,0,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,0,0,0,0,16,13,240,0,0,128,16,0,0,0,0,
+	66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,4,0,0,0,0
+};
--- a/src/Backends/SDL2/Controller.cpp
+++ /dev/null
@@ -1,186 +1,0 @@
-#include "../Controller.h"
-#include "Controller.h"
-
-#include <stddef.h>
-
-#include "SDL.h"
-
-#include "../Misc.h"
-
-#define DEADZONE 10000
-
-static SDL_Joystick *joystick;
-
-static Sint16 *axis_neutrals;
-
-bool ControllerBackend_Init(void)
-{
-	if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0)
-	{
-		Backend_PrintError("Couldn't initialise joystick SDL subsystem: %s", SDL_GetError());
-		return false;
-	}
-
-	return true;
-}
-
-void ControllerBackend_Deinit(void)
-{
-	if (joystick != NULL)
-	{
-		SDL_JoystickClose(joystick);
-		joystick = NULL;
-	}
-
-	SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
-}
-
-bool ControllerBackend_GetJoystickStatus(bool **buttons, unsigned int *button_count, short **axes, unsigned int *axis_count)
-{
-	if (joystick == NULL)
-		return false;
-
-	int total_sdl_buttons = SDL_JoystickNumButtons(joystick);
-	if (total_sdl_buttons < 0)
-		Backend_PrintError("Failed to get number of buttons on joystick: %s", SDL_GetError());
-
-	int total_sdl_axes = SDL_JoystickNumAxes(joystick);
-	if (total_sdl_axes < 0)
-		Backend_PrintError("Failed to get number of general axis controls on joystick: %s", SDL_GetError());
-
-	int total_sdl_hats = SDL_JoystickNumHats(joystick);
-	if (total_sdl_hats < 0)
-		Backend_PrintError("Failed to get number of POV hats on joystick: %s", SDL_GetError());
-
-	*button_count = total_sdl_buttons + total_sdl_axes * 2 + total_sdl_hats * 4;
-	*axis_count = total_sdl_axes;
-
-	static bool *button_buffer = NULL;
-	static short *axis_buffer = NULL;
-
-	bool *new_button_buffer = (bool*)realloc(button_buffer, *button_count * sizeof(bool));
-	short *new_axis_buffer = (short*)realloc(axis_buffer, *axis_count * sizeof(short));
-
-	if (new_button_buffer == NULL || new_axis_buffer == NULL)
-		return false;
-
-	button_buffer = new_button_buffer;
-	axis_buffer = new_axis_buffer;
-
-	//////////////////////////
-	// Handle button inputs //
-	//////////////////////////
-
-	unsigned int current_button = 0;
-
-	// Start with the joystick buttons
-	for (int i = 0; i < total_sdl_buttons; ++i)
-		button_buffer[current_button++] = SDL_JoystickGetButton(joystick, i);
-
-	// Then the joystick axes
-	for (int i = 0; i < total_sdl_axes; ++i)
-	{
-		Sint16 axis = SDL_JoystickGetAxis(joystick, i);
-
-		button_buffer[current_button++] = axis < axis_neutrals[i] - DEADZONE;
-		button_buffer[current_button++] = axis > axis_neutrals[i] + DEADZONE;
-	}
-
-	// Then the joystick hats
-	for (int i = 0; i < total_sdl_hats; ++i)
-	{
-		Uint8 hat = SDL_JoystickGetHat(joystick, i);
-
-		button_buffer[current_button++] = hat == SDL_HAT_UP || hat == SDL_HAT_LEFTUP || hat == SDL_HAT_RIGHTUP;
-		button_buffer[current_button++] = hat == SDL_HAT_RIGHT || hat == SDL_HAT_RIGHTUP || hat == SDL_HAT_RIGHTDOWN;
-		button_buffer[current_button++] = hat == SDL_HAT_DOWN || hat == SDL_HAT_LEFTDOWN || hat == SDL_HAT_RIGHTDOWN;
-		button_buffer[current_button++] = hat == SDL_HAT_LEFT || hat == SDL_HAT_LEFTUP || hat == SDL_HAT_LEFTDOWN;
-	}
-
-	*buttons = button_buffer;
-
-	////////////////////////
-	// Handle axis inputs //
-	////////////////////////
-
-	for (int i = 0; i < total_sdl_axes; ++i)
-		axis_buffer[i] = SDL_JoystickGetAxis(joystick, i);
-
-	*axes = axis_buffer;
-
-	return true;
-}
-
-void ControllerBackend_JoystickConnect(Sint32 joystick_id)
-{
-	const char *joystick_name = SDL_JoystickNameForIndex(joystick_id);
-
-	if (joystick_name != NULL)
-	{
-		Backend_PrintInfo("Joystick #%d connected - %s", joystick_id, joystick_name);
-	}
-	else
-	{
-		Backend_PrintError("Couldn't get joystick name: %s", SDL_GetError());
-		Backend_PrintInfo("Joystick #%d connected - Name unknown", joystick_id);
-	}
-
-	if (joystick == NULL)
-	{
-		joystick = SDL_JoystickOpen(joystick_id);
-
-		if (joystick != NULL)
-		{
-			int total_axes = SDL_JoystickNumAxes(joystick);
-			if (total_axes < 0)
-				Backend_PrintError("Couldn't get number of general axis control on connected joystick: %s", SDL_GetError());
-
-			int total_buttons = SDL_JoystickNumButtons(joystick);
-			if (total_buttons < 0)
-				Backend_PrintError("Couldn't get number of buttons on connected joystick: %s", SDL_GetError());
-
-			if (total_axes >= 2 && total_buttons >= 6)
-			{
-				Backend_PrintInfo("Joystick #%d selected", joystick_id);
-
-				// Set up neutral axes
-				axis_neutrals = (Sint16*)malloc(sizeof(Sint16) * total_axes);
-
-				if (axis_neutrals != NULL)
-				{
-					for (int i = 0; i < total_axes; ++i)
-						axis_neutrals[i] = SDL_JoystickGetAxis(joystick, i);
-
-					return;
-				}
-				else
-				{
-					Backend_PrintError("Couldn't allocate memory for neutral axes");
-				}
-			}
-
-			SDL_JoystickClose(joystick);
-			joystick = NULL;
-		}
-		else
-		{
-			Backend_PrintError("Couldn't open joystick for use: %s", SDL_GetError());
-		}
-	}
-}
-
-void ControllerBackend_JoystickDisconnect(Sint32 joystick_id)
-{
-	SDL_JoystickID current_joystick_id = SDL_JoystickInstanceID(joystick);
-	if (current_joystick_id < 0)
-		Backend_PrintError("Couldn't get instance ID for current joystick: %s", SDL_GetError());
-
-	if (joystick_id == current_joystick_id)
-	{
-		Backend_PrintInfo("Joystick #%d disconnected", joystick_id);
-		SDL_JoystickClose(joystick);
-		joystick = NULL;
-
-		free(axis_neutrals);
-	}
-}
--- a/src/Backends/SDL2/Controller.h
+++ /dev/null
@@ -1,6 +1,0 @@
-#pragma once
-
-#include "SDL.h"
-
-void ControllerBackend_JoystickConnect(Sint32 joystick_id);
-void ControllerBackend_JoystickDisconnect(Sint32 joystick_id);
--- a/src/Backends/SDL2/Misc.cpp
+++ /dev/null
@@ -1,339 +1,0 @@
-#include "../Misc.h"
-
-#include <stddef.h>
-#include <stdlib.h>
-#include <string.h>
-#include <string>
-
-#include "SDL.h"
-
-#include "Controller.h"
-#include "Window.h"
-#include "../Rendering.h"
-#include "../../Main.h"
-#include "../../Organya.h"
-#include "../../Profile.h"
-#include "../../Resource.h"
-
-#define DO_KEY(SDL_KEY, BACKEND_KEY) \
-	case SDL_KEY: \
-		keyboard_state[BACKEND_KEY] = event.key.type == SDL_KEYDOWN; \
-		break;
-
-static bool keyboard_state[BACKEND_KEYBOARD_TOTAL];
-
-static unsigned char *cursor_surface_pixels;
-static SDL_Surface *cursor_surface;
-static SDL_Cursor *cursor;
-
-bool Backend_Init(void)
-{
-	if (SDL_Init(SDL_INIT_EVENTS) == 0)
-	{
-		if (SDL_InitSubSystem(SDL_INIT_VIDEO) == 0)
-		{
-			Backend_PrintInfo("Available SDL video drivers:");
-
-			for (int i = 0; i < SDL_GetNumVideoDrivers(); ++i)
-				Backend_PrintInfo("%s", SDL_GetVideoDriver(i));
-
-			const char *driver = SDL_GetCurrentVideoDriver();
-
-			if (driver != NULL)
-			{
-				Backend_PrintInfo("Selected SDL video driver: %s", driver);
-
-				return true;
-			}
-			else
-			{
-				Backend_PrintError("No SDL video driver initialized!");
-			}
-		}
-		else
-		{
-			std::string error_message = std::string("Could not initialise SDL video subsystem: ") + SDL_GetError();
-			Backend_ShowMessageBox("Fatal error", error_message.c_str());
-		}
-
-		SDL_Quit();
-	}
-	else
-	{
-		std::string error_message = std::string("Could not initialise SDL: ") + SDL_GetError();
-		Backend_ShowMessageBox("Fatal error", error_message.c_str());
-	}
-
-	return false;
-}
-
-void Backend_Deinit(void)
-{
-	if (cursor != NULL)
-		SDL_FreeCursor(cursor);
-
-	if (cursor_surface != NULL)
-		SDL_FreeSurface(cursor_surface);
-
-	free(cursor_surface_pixels);
-
-	SDL_Quit();
-}
-
-void Backend_PostWindowCreation(void)
-{
-	
-}
-
-bool Backend_GetBasePath(char *string_buffer)
-{
-	char *base_path = SDL_GetBasePath();
-	if (base_path == NULL)
-		return false;
-
-	// Trim the trailing '/'
-	size_t base_path_length = strlen(base_path);
-	base_path[base_path_length - 1] = '\0';
-	strcpy(string_buffer, base_path);
-	SDL_free(base_path);
-
-	return true;
-}
-
-void Backend_HideMouse(void)
-{
-	SDL_ShowCursor(SDL_DISABLE);
-}
-
-void Backend_SetWindowIcon(const unsigned char *rgb_pixels, unsigned int width, unsigned int height)
-{
-	SDL_Surface *surface = SDL_CreateRGBSurfaceWithFormatFrom((void*)rgb_pixels, width, height, 0, width * 3, SDL_PIXELFORMAT_RGB24);
-
-	if (surface != NULL)
-	{
-		SDL_SetWindowIcon(window, surface);
-		SDL_FreeSurface(surface);
-	}
-	else
-	{
-		Backend_PrintError("Couldn't create RGB surface for window icon: %s", SDL_GetError());
-	}
-}
-
-void Backend_SetCursor(const unsigned char *rgb_pixels, unsigned int width, unsigned int height)
-{
-	cursor_surface_pixels = (unsigned char*)malloc(width * height * 3);
-
-	if (cursor_surface_pixels != NULL)
-	{
-		memcpy(cursor_surface_pixels, rgb_pixels, width * height * 3);
-
-		cursor_surface = SDL_CreateRGBSurfaceWithFormatFrom(cursor_surface_pixels, width, height, 0, width * 3, SDL_PIXELFORMAT_RGB24);
-
-		if (cursor_surface != NULL)
-		{
-			if (SDL_SetColorKey(cursor_surface, SDL_TRUE, SDL_MapRGB(cursor_surface->format, 0xFF, 0, 0xFF)) == 0)
-			{
-				cursor = SDL_CreateColorCursor(cursor_surface, 0, 0);
-
-				if (cursor != NULL)
-					SDL_SetCursor(cursor);
-			}
-		}
-	}
-	else
-	{
-		Backend_PrintError("Failed to allocate memory for cursor surface");
-	}
-}
-
-void PlaybackBackend_EnableDragAndDrop(void)
-{
-	SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
-}
-
-bool Backend_SystemTask(bool active)
-{
-	if (SDL_PollEvent(NULL) || !active)
-	{
-		SDL_Event event;
-
-		if (!SDL_WaitEvent(&event))
-			return false;
-
-		switch (event.type)
-		{
-			case SDL_KEYUP:
-			case SDL_KEYDOWN:
-				switch (event.key.keysym.sym)
-				{
-					DO_KEY(SDLK_a, BACKEND_KEYBOARD_A)
-					DO_KEY(SDLK_b, BACKEND_KEYBOARD_B)
-					DO_KEY(SDLK_c, BACKEND_KEYBOARD_C)
-					DO_KEY(SDLK_d, BACKEND_KEYBOARD_D)
-					DO_KEY(SDLK_e, BACKEND_KEYBOARD_E)
-					DO_KEY(SDLK_f, BACKEND_KEYBOARD_F)
-					DO_KEY(SDLK_g, BACKEND_KEYBOARD_G)
-					DO_KEY(SDLK_h, BACKEND_KEYBOARD_H)
-					DO_KEY(SDLK_i, BACKEND_KEYBOARD_I)
-					DO_KEY(SDLK_j, BACKEND_KEYBOARD_J)
-					DO_KEY(SDLK_k, BACKEND_KEYBOARD_K)
-					DO_KEY(SDLK_l, BACKEND_KEYBOARD_L)
-					DO_KEY(SDLK_m, BACKEND_KEYBOARD_M)
-					DO_KEY(SDLK_n, BACKEND_KEYBOARD_N)
-					DO_KEY(SDLK_o, BACKEND_KEYBOARD_O)
-					DO_KEY(SDLK_p, BACKEND_KEYBOARD_P)
-					DO_KEY(SDLK_q, BACKEND_KEYBOARD_Q)
-					DO_KEY(SDLK_r, BACKEND_KEYBOARD_R)
-					DO_KEY(SDLK_s, BACKEND_KEYBOARD_S)
-					DO_KEY(SDLK_t, BACKEND_KEYBOARD_T)
-					DO_KEY(SDLK_u, BACKEND_KEYBOARD_U)
-					DO_KEY(SDLK_v, BACKEND_KEYBOARD_V)
-					DO_KEY(SDLK_w, BACKEND_KEYBOARD_W)
-					DO_KEY(SDLK_x, BACKEND_KEYBOARD_X)
-					DO_KEY(SDLK_y, BACKEND_KEYBOARD_Y)
-					DO_KEY(SDLK_z, BACKEND_KEYBOARD_Z)
-					DO_KEY(SDLK_0, BACKEND_KEYBOARD_0)
-					DO_KEY(SDLK_1, BACKEND_KEYBOARD_1)
-					DO_KEY(SDLK_2, BACKEND_KEYBOARD_2)
-					DO_KEY(SDLK_3, BACKEND_KEYBOARD_3)
-					DO_KEY(SDLK_4, BACKEND_KEYBOARD_4)
-					DO_KEY(SDLK_5, BACKEND_KEYBOARD_5)
-					DO_KEY(SDLK_6, BACKEND_KEYBOARD_6)
-					DO_KEY(SDLK_7, BACKEND_KEYBOARD_7)
-					DO_KEY(SDLK_8, BACKEND_KEYBOARD_8)
-					DO_KEY(SDLK_9, BACKEND_KEYBOARD_9)
-					DO_KEY(SDLK_F1, BACKEND_KEYBOARD_F1)
-					DO_KEY(SDLK_F2, BACKEND_KEYBOARD_F2)
-					DO_KEY(SDLK_F3, BACKEND_KEYBOARD_F3)
-					DO_KEY(SDLK_F4, BACKEND_KEYBOARD_F4)
-					DO_KEY(SDLK_F5, BACKEND_KEYBOARD_F5)
-					DO_KEY(SDLK_F6, BACKEND_KEYBOARD_F6)
-					DO_KEY(SDLK_F7, BACKEND_KEYBOARD_F7)
-					DO_KEY(SDLK_F8, BACKEND_KEYBOARD_F8)
-					DO_KEY(SDLK_F9, BACKEND_KEYBOARD_F9)
-					DO_KEY(SDLK_F10, BACKEND_KEYBOARD_F10)
-					DO_KEY(SDLK_F11, BACKEND_KEYBOARD_F11)
-					DO_KEY(SDLK_F12, BACKEND_KEYBOARD_F12)
-					DO_KEY(SDLK_UP, BACKEND_KEYBOARD_UP)
-					DO_KEY(SDLK_DOWN, BACKEND_KEYBOARD_DOWN)
-					DO_KEY(SDLK_LEFT, BACKEND_KEYBOARD_LEFT)
-					DO_KEY(SDLK_RIGHT, BACKEND_KEYBOARD_RIGHT)
-					DO_KEY(SDLK_ESCAPE, BACKEND_KEYBOARD_ESCAPE)
-					DO_KEY(SDLK_BACKQUOTE, BACKEND_KEYBOARD_BACK_QUOTE)
-					DO_KEY(SDLK_TAB, BACKEND_KEYBOARD_TAB)
-					DO_KEY(SDLK_CAPSLOCK, BACKEND_KEYBOARD_CAPS_LOCK)
-					DO_KEY(SDLK_LSHIFT, BACKEND_KEYBOARD_LEFT_SHIFT)
-					DO_KEY(SDLK_LCTRL, BACKEND_KEYBOARD_LEFT_CTRL)
-					DO_KEY(SDLK_LALT, BACKEND_KEYBOARD_LEFT_ALT)
-					DO_KEY(SDLK_SPACE, BACKEND_KEYBOARD_SPACE)
-					DO_KEY(SDLK_RALT, BACKEND_KEYBOARD_RIGHT_ALT)
-					DO_KEY(SDLK_RCTRL, BACKEND_KEYBOARD_RIGHT_CTRL)
-					DO_KEY(SDLK_RSHIFT, BACKEND_KEYBOARD_RIGHT_SHIFT)
-					DO_KEY(SDLK_RETURN, BACKEND_KEYBOARD_ENTER)
-					DO_KEY(SDLK_BACKSPACE, BACKEND_KEYBOARD_BACKSPACE)
-					DO_KEY(SDLK_MINUS, BACKEND_KEYBOARD_MINUS)
-					DO_KEY(SDLK_EQUALS, BACKEND_KEYBOARD_EQUALS)
-					DO_KEY(SDLK_LEFTBRACKET, BACKEND_KEYBOARD_LEFT_BRACKET)
-					DO_KEY(SDLK_RIGHTBRACKET, BACKEND_KEYBOARD_RIGHT_BRACKET)
-					DO_KEY(SDLK_BACKSLASH, BACKEND_KEYBOARD_BACK_SLASH)
-					DO_KEY(SDLK_SEMICOLON, BACKEND_KEYBOARD_SEMICOLON)
-					DO_KEY(SDLK_QUOTE, BACKEND_KEYBOARD_APOSTROPHE)
-					DO_KEY(SDLK_COMMA, BACKEND_KEYBOARD_COMMA)
-					DO_KEY(SDLK_PERIOD, BACKEND_KEYBOARD_PERIOD)
-					DO_KEY(SDLK_SLASH, BACKEND_KEYBOARD_FORWARD_SLASH)
-
-					default:
-						break;
-				}
-
-				break;
-
-			case SDL_JOYDEVICEADDED:
-				ControllerBackend_JoystickConnect(event.jdevice.which);
-				break;
-
-			case SDL_JOYDEVICEREMOVED:
-				ControllerBackend_JoystickDisconnect(event.jdevice.which);
-				break;
-
-			case SDL_DROPFILE:
-				LoadProfile(event.drop.file);
-				SDL_free(event.drop.file);
-				break;
-
-			case SDL_WINDOWEVENT:
-				switch (event.window.event)
-				{
-					case SDL_WINDOWEVENT_FOCUS_LOST:
-						InactiveWindow();
-						break;
-
-					case SDL_WINDOWEVENT_FOCUS_GAINED:
-						ActiveWindow();
-						break;
-
-					case SDL_WINDOWEVENT_RESIZED:
-					case SDL_WINDOWEVENT_SIZE_CHANGED:
-						RenderBackend_HandleWindowResize(event.window.data1, event.window.data2);
-						break;
-				}
-
-				break;
-
-			case SDL_QUIT:
-				StopOrganyaMusic();
-				return false;
-
-			case SDL_RENDER_TARGETS_RESET:
-				RenderBackend_HandleRenderTargetLoss();
-				break;
-
-		}
-	}
-
-	return true;
-}
-
-void Backend_GetKeyboardState(bool *out_keyboard_state)
-{
-	memcpy(out_keyboard_state, keyboard_state, sizeof(keyboard_state));
-}
-
-void Backend_ShowMessageBox(const char *title, const char *message)
-{
-	fprintf(stderr, "ShowMessageBox - '%s' - '%s'\n", title, message);
-
-	if (SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title, message, window) != 0)
-		Backend_PrintError("Was also unable to display a message box containing the error: %s", SDL_GetError());
-}
-
-ATTRIBUTE_FORMAT_PRINTF(1, 2) void Backend_PrintError(const char *format, ...)
-{
-	va_list argumentList;
-	va_start(argumentList, format);
-	fputs("ERROR: ", stderr);
-	vfprintf(stderr, format, argumentList);
-	fputc('\n', stderr);
-	va_end(argumentList);
-}
-
-ATTRIBUTE_FORMAT_PRINTF(1, 2) void Backend_PrintInfo(const char *format, ...)
-{
-	va_list argumentList;
-	va_start(argumentList, format);
-	fputs("INFO: ", stdout);
-	vprintf(format, argumentList);
-	putchar('\n');
-	va_end(argumentList);
-}
-
-unsigned long Backend_GetTicks(void)
-{
-	return SDL_GetTicks();
-}
-
-void Backend_Delay(unsigned int ticks)
-{
-	SDL_Delay(ticks);
-}
--- a/src/Backends/SDL2/Window-OpenGL3.cpp
+++ /dev/null
@@ -1,116 +1,0 @@
-#include "../Window-OpenGL.h"
-#include "Window.h"
-
-#include <stddef.h>
-#include <string>
-
-#ifdef USE_OPENGLES2
-#include <GLES2/gl2.h>
-#else
-#include <glad/glad.h>
-#endif
-#include "SDL.h"
-
-#include "../Misc.h"
-#include "../../Resource.h"
-
-SDL_Window *window;
-
-static SDL_GLContext context;
-
-bool WindowBackend_OpenGL_CreateWindow(const char *window_title, int *screen_width, int *screen_height, bool fullscreen)
-{
-#ifdef USE_OPENGLES2
-	if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES) < 0)
-		Backend_PrintError("Couldn't set OpenGL context type to ES: %s", SDL_GetError());
-
-	if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0) < 0)
-		Backend_PrintError("Couldn't set OpenGL context flags to 0: %s", SDL_GetError());
-
-	if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2) < 0)
-		Backend_PrintError("Couldn't set OpenGL major version to 2: %s", SDL_GetError());
-
-	if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0) < 0)
-		Backend_PrintError("Couldn't set OpenGL minor version to 0: %s", SDL_GetError());
-#else
-	if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE) < 0)
-		Backend_PrintError("Couldn't set OpenGL context type to core: %s", SDL_GetError());
-
-	if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG) < 0)
-		Backend_PrintError("Couldn't set OpenGL forward compatibility: %s", SDL_GetError());
-
-	if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3) < 0)
-		Backend_PrintError("Couldn't set OpenGL major version to 3: %s", SDL_GetError());
-
-	if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2) < 0)
-		Backend_PrintError("Couldn't set OpenGL minor verison to 2: %s", SDL_GetError());
-#endif
-
-	window = SDL_CreateWindow(window_title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, *screen_width, *screen_height, SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL | (fullscreen ? SDL_WINDOW_FULLSCREEN : 0));
-
-	if (window != NULL)
-	{
-		context = SDL_GL_CreateContext(window);
-
-		if (context != NULL)
-		{
-			if (SDL_GL_MakeCurrent(window, context) == 0)
-			{
-			#ifndef USE_OPENGLES2
-				if (gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress))
-				{
-					// Check if the platform supports OpenGL 3.2
-					if (GLAD_GL_VERSION_3_2)
-					{
-			#endif
-						Backend_PostWindowCreation();
-
-						return true;
-			#ifndef USE_OPENGLES2
-					}
-					else
-					{
-						Backend_ShowMessageBox("Fatal error (OpenGL rendering backend)", "Your system does not support OpenGL 3.2");
-					}
-				}
-				else
-				{
-					Backend_ShowMessageBox("Fatal error (OpenGL rendering backend)", "Couldn't load OpenGL functions");
-				}
-			#endif
-			}
-			else
-			{
-				std::string error_message = std::string("Couldn't setup OpenGL context for rendering: ") + SDL_GetError();
-				Backend_ShowMessageBox("Fatal error (OpenGL rendering backend)", "SDL_GL_MakeCurrent failed");
-			}
-
-			SDL_GL_DeleteContext(context);
-		}
-		else
-		{
-			std::string error_message = std::string("Couldn't create OpenGL context: %s", SDL_GetError());
-			Backend_ShowMessageBox("Fatal error (OpenGL rendering backend)", "Could not create OpenGL context");
-		}
-
-		SDL_DestroyWindow(window);
-	}
-	else
-	{
-		std::string error_message = std::string("Could not create window: ") + SDL_GetError();
-		Backend_ShowMessageBox("Fatal error (OpenGL rendering backend)", error_message.c_str());
-	}
-
-	return false;
-}
-
-void WindowBackend_OpenGL_DestroyWindow(void)
-{
-	SDL_GL_DeleteContext(context);
-	SDL_DestroyWindow(window);
-}
-
-void WindowBackend_OpenGL_Display(void)
-{
-	SDL_GL_SwapWindow(window);
-}
--- a/src/Backends/SDL2/Window-OpenGLES2.cpp
+++ /dev/null
@@ -1,2 +1,0 @@
-#define USE_OPENGLES2
-#include "Window-OpenGL3.cpp"
--- a/src/Backends/SDL2/Window-Software.cpp
+++ /dev/null
@@ -1,95 +1,0 @@
-#include "../Window-Software.h"
-#include "Window.h"
-
-#include <stddef.h>
-#include <stdlib.h>
-#include <string>
-
-#include "SDL.h"
-
-#include "../Misc.h"
-
-SDL_Window *window;
-
-static SDL_Surface *window_sdlsurface;
-static SDL_Surface *framebuffer_sdlsurface;
-
-bool WindowBackend_Software_CreateWindow(const char *window_title, int screen_width, int screen_height, bool fullscreen)
-{
-	window = SDL_CreateWindow(window_title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, screen_width, screen_height, 0);
-
-	if (window != NULL)
-	{
-		if (fullscreen)
-			if (SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN) < 0)
-				Backend_PrintError("Couldn't set window to fullscree: %s", SDL_GetError());
-
-		window_sdlsurface = SDL_GetWindowSurface(window);
-
-		if (window_sdlsurface != NULL)
-		{
-
-			framebuffer_sdlsurface = SDL_CreateRGBSurfaceWithFormat(0, window_sdlsurface->w, window_sdlsurface->h, 0, SDL_PIXELFORMAT_RGB24);
-
-			if (framebuffer_sdlsurface != NULL)
-			{
-				Backend_PostWindowCreation();
-
-				return true;
-			}
-			else
-			{
-				std::string error_message = std::string("Couldn't create framebuffer surface: ") + SDL_GetError();
-				Backend_ShowMessageBox("Fatal error (software rendering backend)", error_message.c_str());
-				SDL_DestroyWindow(window);
-			}
-		}
-		else
-		{
-			std::string error_message = std::string("Couldn't get SDL surface associated with window: ") + SDL_GetError();
-			Backend_ShowMessageBox("Fatal error (software rendering backend)", error_message.c_str());
-		}
-	}
-	else
-	{
-		std::string error_message = std::string("Couldn't create window: ") + SDL_GetError();
-		Backend_ShowMessageBox("Fatal error (software rendering backend)", error_message.c_str());
-	}
-
-	return false;
-}
-
-void WindowBackend_Software_DestroyWindow(void)
-{
-	SDL_FreeSurface(framebuffer_sdlsurface);
-	SDL_DestroyWindow(window);
-}
-
-unsigned char* WindowBackend_Software_GetFramebuffer(size_t *pitch)
-{
-	*pitch = framebuffer_sdlsurface->pitch;
-
-	return (unsigned char*)framebuffer_sdlsurface->pixels;
-}
-
-void WindowBackend_Software_Display(void)
-{
-	if (SDL_BlitSurface(framebuffer_sdlsurface, NULL, window_sdlsurface, NULL) < 0)
-		Backend_PrintError("Couldn't blit framebuffer surface to window surface: %s", SDL_GetError());
-
-	if (SDL_UpdateWindowSurface(window) < 0)
-		Backend_PrintError("Couldn't copy window surface to the screen: %s", SDL_GetError());
-}
-
-void WindowBackend_Software_HandleWindowResize(unsigned int width, unsigned int height)
-{
-	(void)width;
-	(void)height;
-
-	// https://wiki.libsdl.org/SDL_GetWindowSurface
-	// We need to fetch a new surface pointer
-	window_sdlsurface = SDL_GetWindowSurface(window);
-
-	if (window_sdlsurface == NULL)
-		Backend_PrintError("Couldn't get SDL surface associated with window: %s", SDL_GetError());
-}
--- a/src/Backends/SDL2/Window.h
+++ /dev/null
@@ -1,5 +1,0 @@
-#pragma once
-
-#include "SDL.h"
-
-extern SDL_Window *window;
--- /dev/null
+++ b/src/Backends/Shared/GLFW3.h
@@ -1,0 +1,3 @@
+#pragma once
+
+extern struct GLFWwindow *window;
--- /dev/null
+++ b/src/Backends/Shared/SDL2.h
@@ -1,0 +1,8 @@
+#pragma once
+
+#include "SDL.h"
+
+extern SDL_Window *window;
+
+void ControllerBackend_JoystickConnect(Sint32 joystick_id);
+void ControllerBackend_JoystickDisconnect(Sint32 joystick_id);
--- a/src/Backends/WiiU/Controller.cpp
+++ /dev/null
@@ -1,24 +1,0 @@
-#include "../Controller.h"
-
-// Vanilla Cave Story's controller system is ill-suited for console ports,
-// so we emulate a keyboard instead (see `Misc.cpp`).
-
-bool ControllerBackend_Init(void)
-{
-	return false;
-}
-
-void ControllerBackend_Deinit(void)
-{
-	
-}
-
-bool ControllerBackend_GetJoystickStatus(bool **buttons, unsigned int *button_count, short **axes, unsigned int *axis_count)
-{
-	(void)buttons;
-	(void)button_count;
-	(void)axes;
-	(void)axis_count;
-
-	return false;
-}
--- a/src/Backends/WiiU/Misc.cpp
+++ /dev/null
@@ -1,190 +1,0 @@
-#include "../Misc.h"
-
-#include <stdarg.h>
-#include <stdio.h>
-#include <string.h>
-
-#include <coreinit/thread.h>
-#include <padscore/kpad.h>
-#include <vpad/input.h>
-#include <whb/log.h>
-#include <whb/log_udp.h>
-#include <whb/proc.h>
-#include <whb/sdcard.h>
-
-static unsigned long ticks_per_second;
-
-bool Backend_Init(void)
-{
-	WHBProcInit();
-
-	if (!WHBMountSdCard())
-		return FALSE;
-
-	VPADInit();
-
-	WPADInit();
-	KPADInit();
-
-	// Enable Wii U Pro Controllers to be connected
-	WPADEnableURCC(1);
-
-	WHBLogUdpInit();
-
-	ticks_per_second = OSGetSystemInfo()->busClockSpeed / 4;
-
-	return true;
-}
-
-void Backend_Deinit(void)
-{
-	WHBLogUdpDeinit();
-
-	WPADShutdown();
-
-	VPADShutdown();
-
-	WHBUnmountSdCard();
-
-	WHBProcShutdown();
-}
-
-void Backend_PostWindowCreation(void)
-{
-	
-}
-
-bool Backend_GetBasePath(char *string_buffer)
-{
-	strcpy(string_buffer, WHBGetSdCardMountPath());
-#ifdef JAPANESE
-	strcat(string_buffer, "/CSE2-portable-jp");
-#else
-	strcat(string_buffer, "/CSE2-portable-en");
-#endif
-
-	return true;
-}
-
-void Backend_HideMouse(void)
-{
-	
-}
-
-void Backend_SetWindowIcon(const unsigned char *rgb_pixels, unsigned int width, unsigned int height)
-{
-	(void)rgb_pixels;
-	(void)width;
-	(void)height;
-}
-
-void Backend_SetCursor(const unsigned char *rgb_pixels, unsigned int width, unsigned int height)
-{
-	(void)rgb_pixels;
-	(void)width;
-	(void)height;
-}
-
-void PlaybackBackend_EnableDragAndDrop(void)
-{
-	
-}
-
-bool Backend_SystemTask(bool active)
-{
-	(void)active;
-
-	return WHBProcIsRunning();
-}
-
-void Backend_GetKeyboardState(bool *keyboard_state)
-{
-	memset(keyboard_state, 0, sizeof(bool) * BACKEND_KEYBOARD_TOTAL);
-
-	// Read gamepad
-	static uint32_t vpad_buttons;
-
-	VPADStatus vpad_status;
-	if (VPADRead(VPAD_CHAN_0, &vpad_status, 1, NULL) == 1)
-		vpad_buttons = vpad_status.hold;
-
-	keyboard_state[BACKEND_KEYBOARD_UP] |= vpad_buttons & (VPAD_BUTTON_UP | VPAD_STICK_L_EMULATION_UP);
-	keyboard_state[BACKEND_KEYBOARD_DOWN] |= vpad_buttons & (VPAD_BUTTON_DOWN | VPAD_STICK_L_EMULATION_DOWN);
-	keyboard_state[BACKEND_KEYBOARD_LEFT] |= vpad_buttons & (VPAD_BUTTON_LEFT | VPAD_STICK_L_EMULATION_LEFT);
-	keyboard_state[BACKEND_KEYBOARD_RIGHT] |= vpad_buttons & (VPAD_BUTTON_RIGHT | VPAD_STICK_L_EMULATION_RIGHT);
-	keyboard_state[BACKEND_KEYBOARD_Z] |= vpad_buttons & VPAD_BUTTON_B;                       // Jump
-	keyboard_state[BACKEND_KEYBOARD_X] |= vpad_buttons & VPAD_BUTTON_Y;                       // Shoot
-	keyboard_state[BACKEND_KEYBOARD_Q] |= vpad_buttons & (VPAD_BUTTON_A | VPAD_BUTTON_PLUS);  // Inventory
-	keyboard_state[BACKEND_KEYBOARD_W] |= vpad_buttons & (VPAD_BUTTON_X | VPAD_BUTTON_MINUS); // Map
-	keyboard_state[BACKEND_KEYBOARD_A] |= vpad_buttons & (VPAD_BUTTON_L | VPAD_BUTTON_ZL | VPAD_STICK_R_EMULATION_LEFT);  // Weapon left
-	keyboard_state[BACKEND_KEYBOARD_S] |= vpad_buttons & (VPAD_BUTTON_R | VPAD_BUTTON_ZR | VPAD_STICK_R_EMULATION_RIGHT); // Weapon right
-
-	// Read Wii U Pro Controller
-	static uint32_t kpad_buttons;
-
-	KPADStatus kpad_status;
-	if (KPADRead(WPAD_CHAN_0, &kpad_status, 1) == 1)
-		kpad_buttons = kpad_status.pro.hold;
-
-	keyboard_state[BACKEND_KEYBOARD_UP] |= kpad_buttons & (WPAD_PRO_BUTTON_UP | WPAD_PRO_STICK_L_EMULATION_UP);
-	keyboard_state[BACKEND_KEYBOARD_DOWN] |= kpad_buttons & (WPAD_PRO_BUTTON_DOWN | WPAD_PRO_STICK_L_EMULATION_DOWN);
-	keyboard_state[BACKEND_KEYBOARD_LEFT] |= kpad_buttons & (WPAD_PRO_BUTTON_LEFT | WPAD_PRO_STICK_L_EMULATION_LEFT);
-	keyboard_state[BACKEND_KEYBOARD_RIGHT] |= kpad_buttons & (WPAD_PRO_BUTTON_RIGHT | WPAD_PRO_STICK_L_EMULATION_RIGHT);
-	keyboard_state[BACKEND_KEYBOARD_Z] |= kpad_buttons & WPAD_PRO_BUTTON_B;                           // Jump
-	keyboard_state[BACKEND_KEYBOARD_X] |= kpad_buttons & WPAD_PRO_BUTTON_Y;                           // Shoot
-	keyboard_state[BACKEND_KEYBOARD_Q] |= kpad_buttons & (WPAD_PRO_BUTTON_A | WPAD_PRO_BUTTON_PLUS);  // Inventory
-	keyboard_state[BACKEND_KEYBOARD_W] |= kpad_buttons & (WPAD_PRO_BUTTON_X | WPAD_PRO_BUTTON_MINUS); // Map
-	keyboard_state[BACKEND_KEYBOARD_A] |= kpad_buttons & (WPAD_PRO_TRIGGER_L | WPAD_PRO_TRIGGER_ZL | WPAD_PRO_STICK_R_EMULATION_LEFT);  // Weapon left
-	keyboard_state[BACKEND_KEYBOARD_S] |= kpad_buttons & (WPAD_PRO_TRIGGER_R | WPAD_PRO_TRIGGER_ZR | WPAD_PRO_STICK_R_EMULATION_RIGHT); // Weapon right
-}
-
-void Backend_ShowMessageBox(const char *title, const char *message)
-{
-	Backend_PrintInfo("ShowMessageBox - %s - %s", title, message);
-}
-
-ATTRIBUTE_FORMAT_PRINTF(1, 2) void Backend_PrintError(const char *format, ...)
-{
-	char message_buffer[0x100];
-
-	va_list argument_list;
-	va_start(argument_list, format);
-	vsnprintf(message_buffer, sizeof(message_buffer), format, argument_list);
-	va_end(argument_list);
-
-	WHBLogPrint("ERROR:");
-	WHBLogPrint(message_buffer);
-}
-
-ATTRIBUTE_FORMAT_PRINTF(1, 2) void Backend_PrintInfo(const char *format, ...)
-{
-	char message_buffer[0x100];
-
-	va_list argument_list;
-	va_start(argument_list, format);
-	vsnprintf(message_buffer, sizeof(message_buffer), format, argument_list);
-	va_end(argument_list);
-
-	WHBLogPrint("INFO:");
-	WHBLogPrint(message_buffer);
-}
-
-unsigned long Backend_GetTicks(void)
-{
-	static uint64_t accumulator;
-
-	static unsigned long last_tick;
-
-	unsigned long current_tick = OSGetTick();
-
-	accumulator += current_tick - last_tick;
-
-	last_tick = current_tick;
-
-	return (accumulator * 1000) / ticks_per_second;
-}
-
-void Backend_Delay(unsigned int ticks)
-{
-	OSSleepTicks((ticks * ticks_per_second) / 1000);
-}
--- a/src/Backends/WiiU/Window-Software.cpp
+++ /dev/null
@@ -1,269 +1,0 @@
-// Sexy new backend that bounces the software-rendered frame to the GPU,
-// eliminating V-tearing, and gaining support for rendering to the TV for
-// free!
-
-#include "../Window-Software.h"
-
-#include <stddef.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <gx2/display.h>
-#include <gx2/draw.h>
-#include <gx2/registers.h>
-#include <gx2/sampler.h>
-#include <gx2/texture.h>
-#include <gx2r/buffer.h>
-#include <gx2r/draw.h>
-#include <gx2r/resource.h>
-#include <gx2r/surface.h>
-#include <whb/gfx.h>
-
-#include "../../Attributes.h"
-
-#include "shaders/texture.gsh.h"
-
-typedef struct Viewport
-{
-	float x;
-	float y;
-	float width;
-	float height;
-} Viewport;
-
-static unsigned char *fake_framebuffer;
-
-static size_t fake_framebuffer_width;
-static size_t fake_framebuffer_height;
-
-static WHBGfxShaderGroup shader_group;
-
-static GX2RBuffer vertex_position_buffer;
-static GX2RBuffer texture_coordinate_buffer;
-
-static GX2Sampler sampler;
-
-static GX2Texture screen_texture;
-
-static Viewport tv_viewport;
-static Viewport drc_viewport;
-
-static void CalculateViewport(unsigned int actual_screen_width, unsigned int actual_screen_height, Viewport *viewport)
-{
-	if ((float)actual_screen_width / (float)actual_screen_height > (float)fake_framebuffer_width / (float)fake_framebuffer_height)
-	{
-		viewport->y = 0.0f;
-		viewport->height = actual_screen_height;
-
-		viewport->width = fake_framebuffer_width * ((float)actual_screen_height / (float)fake_framebuffer_height);
-		viewport->x = (actual_screen_width - viewport->width) / 2;
-	}
-	else
-	{
-		viewport->x = 0.0f;
-		viewport->width = actual_screen_width;
-
-		viewport->height = fake_framebuffer_height * ((float)actual_screen_width / (float)fake_framebuffer_width);
-		viewport->y = (actual_screen_height - viewport->height) / 2;
-	}
-}
-
-bool WindowBackend_Software_CreateWindow(const char *window_title, int screen_width, int screen_height, bool fullscreen)
-{
-	(void)window_title;
-	(void)fullscreen;
-
-	fake_framebuffer_width = screen_width;
-	fake_framebuffer_height = screen_height;
-
-	fake_framebuffer = (unsigned char*)malloc(fake_framebuffer_width * fake_framebuffer_height * 3);
-
-	if (fake_framebuffer != NULL)
-	{
-		WHBGfxInit();
-
-		if (WHBGfxLoadGFDShaderGroup(&shader_group, 0, rtexture))
-		{
-			WHBGfxInitShaderAttribute(&shader_group, "input_vertex_coordinates", 0, 0, GX2_ATTRIB_FORMAT_FLOAT_32_32);
-			WHBGfxInitShaderAttribute(&shader_group, "input_texture_coordinates", 1, 0, GX2_ATTRIB_FORMAT_FLOAT_32_32);
-			WHBGfxInitFetchShader(&shader_group);
-
-			// Initialise vertex position buffer
-			const float vertex_positions[4][2] = {
-				{-1.0f,  1.0f},
-				{ 1.0f,  1.0f},
-				{ 1.0f, -1.0f},
-				{-1.0f, -1.0f}
-			};
-
-			vertex_position_buffer.flags = (GX2RResourceFlags)(GX2R_RESOURCE_BIND_VERTEX_BUFFER |
-			                                                   GX2R_RESOURCE_USAGE_CPU_READ |
-			                                                   GX2R_RESOURCE_USAGE_CPU_WRITE |
-			                                                   GX2R_RESOURCE_USAGE_GPU_READ);
-			vertex_position_buffer.elemSize = sizeof(vertex_positions[0]);
-			vertex_position_buffer.elemCount = sizeof(vertex_positions) / sizeof(vertex_positions[0]);
-			GX2RCreateBuffer(&vertex_position_buffer);
-			memcpy(GX2RLockBufferEx(&vertex_position_buffer, (GX2RResourceFlags)0), vertex_positions, sizeof(vertex_positions));
-			GX2RUnlockBufferEx(&vertex_position_buffer, (GX2RResourceFlags)0);
-
-			// Initialise texture coordinate buffer
-			const float texture_coordinates[4][2] = {
-				{0.0f, 0.0f},
-				{1.0f, 0.0f},
-				{1.0f, 1.0f},
-				{0.0f, 1.0f}
-			};
-
-			texture_coordinate_buffer.flags = (GX2RResourceFlags)(GX2R_RESOURCE_BIND_VERTEX_BUFFER |
-			                                                      GX2R_RESOURCE_USAGE_CPU_READ |
-			                                                      GX2R_RESOURCE_USAGE_CPU_WRITE |
-			                                                      GX2R_RESOURCE_USAGE_GPU_READ);
-			texture_coordinate_buffer.elemSize = sizeof(texture_coordinates[0]);
-			texture_coordinate_buffer.elemCount = sizeof(texture_coordinates) / sizeof(texture_coordinates[0]);
-			GX2RCreateBuffer(&texture_coordinate_buffer);
-			memcpy(GX2RLockBufferEx(&texture_coordinate_buffer, (GX2RResourceFlags)0), texture_coordinates, sizeof(texture_coordinates));
-			GX2RUnlockBufferEx(&texture_coordinate_buffer, (GX2RResourceFlags)0);
-
-			// Initialise sampler
-			GX2InitSampler(&sampler, GX2_TEX_CLAMP_MODE_CLAMP, GX2_TEX_XY_FILTER_MODE_POINT);
-
-			// Initialise screen texture
-			screen_texture.surface.width = fake_framebuffer_width;
-			screen_texture.surface.height = fake_framebuffer_height;
-			screen_texture.surface.format = GX2_SURFACE_FORMAT_UNORM_R8_G8_B8_A8;
-			screen_texture.surface.depth = 1;
-			screen_texture.surface.dim = GX2_SURFACE_DIM_TEXTURE_2D;
-			screen_texture.surface.tileMode = GX2_TILE_MODE_LINEAR_ALIGNED;
-			screen_texture.surface.mipLevels = 1;
-			screen_texture.viewNumMips = 1;
-			screen_texture.viewNumSlices = 1;
-			screen_texture.compMap = 0x00010203;
-			GX2CalcSurfaceSizeAndAlignment(&screen_texture.surface);
-			GX2InitTextureRegs(&screen_texture);
-
-			if (GX2RCreateSurface(&screen_texture.surface, (GX2RResourceFlags)(GX2R_RESOURCE_BIND_TEXTURE | GX2R_RESOURCE_BIND_COLOR_BUFFER |
-			                                                                   GX2R_RESOURCE_USAGE_CPU_WRITE | GX2R_RESOURCE_USAGE_CPU_READ |
-			                                                                   GX2R_RESOURCE_USAGE_GPU_WRITE | GX2R_RESOURCE_USAGE_GPU_READ)))
-			{
-				// Do some binding
-				GX2SetPixelSampler(&sampler, shader_group.pixelShader->samplerVars[0].location);
-				GX2SetPixelTexture(&screen_texture, shader_group.pixelShader->samplerVars[0].location);
-				GX2RSetAttributeBuffer(&vertex_position_buffer, 0, vertex_position_buffer.elemSize, 0);
-				GX2RSetAttributeBuffer(&texture_coordinate_buffer, 1, texture_coordinate_buffer.elemSize, 0);
-
-				// Calculate centred viewports
-				switch (GX2GetSystemTVScanMode())
-				{
-					// For now, we have to match WUT's broken behaviour (its `GX2TVScanMode`
-					// enum is missing values, and the rest are off-by-one)
-					//case GX2_TV_SCAN_MODE_576I:
-					case GX2_TV_SCAN_MODE_480I:	// Actually 576i
-					case GX2_TV_SCAN_MODE_480P:	// Actually 480i
-						CalculateViewport(854, 480, &tv_viewport);
-						break;
-
-					case GX2_TV_SCAN_MODE_720P:	// Actually 480p
-					default:	// Funnel the *real* 1080p into this
-						CalculateViewport(1280, 720, &tv_viewport);
-						break;
-
-					case GX2_TV_SCAN_MODE_1080I:	// Actually invalid
-					case GX2_TV_SCAN_MODE_1080P:	// Actually 1080i
-						CalculateViewport(1920, 1080, &tv_viewport);
-						break;
-				}
-
-				CalculateViewport(854, 480, &drc_viewport);
-
-				return true;
-			}
-
-			GX2RDestroyBufferEx(&texture_coordinate_buffer, (GX2RResourceFlags)0);
-			GX2RDestroyBufferEx(&vertex_position_buffer, (GX2RResourceFlags)0);
-
-			WHBGfxFreeShaderGroup(&shader_group);
-		}
-
-		WHBGfxShutdown();
-
-		free(fake_framebuffer);
-	}
-
-	return false;
-}
-
-void WindowBackend_Software_DestroyWindow(void)
-{
-	GX2RDestroySurfaceEx(&screen_texture.surface, (GX2RResourceFlags)0);
-
-	GX2RDestroyBufferEx(&texture_coordinate_buffer, (GX2RResourceFlags)0);
-	GX2RDestroyBufferEx(&vertex_position_buffer, (GX2RResourceFlags)0);
-
-	WHBGfxFreeShaderGroup(&shader_group);
-
-	WHBGfxShutdown();
-
-	free(fake_framebuffer);
-}
-
-unsigned char* WindowBackend_Software_GetFramebuffer(size_t *pitch)
-{
-	*pitch = fake_framebuffer_width * 3;
-
-	return fake_framebuffer;
-}
-
-ATTRIBUTE_HOT void WindowBackend_Software_Display(void)
-{
-	// Convert frame from RGB24 to RGBA32, and upload it to the GPU texture
-	unsigned char *framebuffer = (unsigned char*)GX2RLockSurfaceEx(&screen_texture.surface, 0, (GX2RResourceFlags)0);
-
-	const unsigned char *in_pointer = fake_framebuffer;
-
-	for (size_t y = 0; y < fake_framebuffer_height; ++y)
-	{
-		unsigned char *out_pointer = &framebuffer[screen_texture.surface.pitch * 4 * y];
-
-		for (size_t x = 0; x < fake_framebuffer_width; ++x)
-		{
-			*out_pointer++ = *in_pointer++;
-			*out_pointer++ = *in_pointer++;
-			*out_pointer++ = *in_pointer++;
-			*out_pointer++ = 0;
-		}
-	}
-
-	GX2RUnlockSurfaceEx(&screen_texture.surface, 0, (GX2RResourceFlags)0);
-
-	WHBGfxBeginRender();
-
-	// Draw to the TV
-	WHBGfxBeginRenderTV();
-	GX2SetViewport(tv_viewport.x, tv_viewport.y, tv_viewport.width, tv_viewport.height, 0.0f, 1.0f);
-	WHBGfxClearColor(0.0f, 0.0f, 0.0f, 1.0f);
-	GX2SetFetchShader(&shader_group.fetchShader);
-	GX2SetVertexShader(shader_group.vertexShader);
-	GX2SetPixelShader(shader_group.pixelShader);
-	GX2DrawEx(GX2_PRIMITIVE_MODE_QUADS, 4, 0, 1);
-	WHBGfxFinishRenderTV();
-
-	// Draw to the gamepad
-	WHBGfxBeginRenderDRC();
-	GX2SetViewport(drc_viewport.x, drc_viewport.y, drc_viewport.width, drc_viewport.height, 0.0f, 1.0f);
-	WHBGfxClearColor(0.0f, 0.0f, 0.0f, 1.0f);
-	GX2SetFetchShader(&shader_group.fetchShader);
-	GX2SetVertexShader(shader_group.vertexShader);
-	GX2SetPixelShader(shader_group.pixelShader);
-	GX2DrawEx(GX2_PRIMITIVE_MODE_QUADS, 4, 0, 1);
-	WHBGfxFinishRenderDRC();
-
-	WHBGfxFinishRender();
-}
-
-void WindowBackend_Software_HandleWindowResize(unsigned int width, unsigned int height)
-{
-	(void)width;
-	(void)height;
-
-	// The window doesn't resize on the Wii U
-}
--- a/src/Backends/WiiU/shaders/colour_fill.gsh.h
+++ /dev/null
@@ -1,50 +1,0 @@
-#pragma once
-
-static const unsigned char rcolour_fill[0x5AC] = {
-	71,102,120,50,0,0,0,32,0,0,0,7,0,0,0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,
-	66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,0,0,0,3,0,0,1,144,0,0,0,0,0,0,0,0,
-	0,0,1,2,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,0,255,255,255,255,255,255,255,255,255,255,255,255,
-	255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,255,255,255,254,
-	0,0,0,1,0,0,0,0,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
-	0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
-	0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
-	0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
-	0,0,0,255,0,0,0,0,0,0,0,14,0,0,0,16,0,0,1,8,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,1,208,96,1,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,202,112,1,68,0,0,0,9,0,0,0,0,
-	0,0,0,0,105,110,112,117,116,95,118,101,114,116,101,120,95,99,111,111,114,100,105,110,97,116,101,115,0,0,0,0,
-	208,96,1,8,202,112,1,52,125,66,76,75,0,0,0,40,0,0,0,0,0,0,1,96,208,96,0,0,0,0,0,28,
-	208,96,1,68,0,0,0,0,0,0,0,2,208,96,1,96,66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,
-	0,0,0,5,0,0,1,8,0,0,0,1,0,0,0,0,0,0,0,0,0,0,128,9,60,160,0,0,136,6,0,148,
-	0,64,0,0,255,15,0,148,32,0,0,0,0,0,0,160,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,13,0,0,66,76,75,123,0,0,0,32,
-	0,0,0,1,0,0,0,0,0,0,0,6,0,0,1,52,0,0,0,2,0,0,0,0,0,0,0,1,0,0,0,2,
-	20,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,1,0,0,0,16,0,0,0,0,0,0,1,32,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,208,96,0,232,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	202,112,0,252,0,0,0,11,0,0,0,1,0,0,0,0,255,255,255,255,99,111,108,111,117,114,0,0,208,96,0,188,
-	202,112,0,232,125,66,76,75,0,0,0,40,0,0,0,0,0,0,1,4,208,96,0,0,0,0,0,8,208,96,0,252,
-	0,0,0,0,0,0,0,2,208,96,1,4,66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,0,0,0,7,
-	0,0,1,32,0,0,0,3,0,0,0,0,32,0,0,0,0,0,12,160,0,0,0,0,136,6,32,148,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,144,12,0,0,0,5,0,0,144,12,0,32,0,9,0,0,
-	144,12,0,64,0,13,0,128,144,12,0,96,66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,0,0,0,1,
-	0,0,0,0,0,0,0,4,0,0,0,0
-};
--- a/src/Backends/WiiU/shaders/glyph.gsh.h
+++ /dev/null
@@ -1,56 +1,0 @@
-#pragma once
-
-static const unsigned char rglyph[0x674] = {
-	71,102,120,50,0,0,0,32,0,0,0,7,0,0,0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,
-	66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,0,0,0,3,0,0,1,192,0,0,0,0,0,0,0,0,
-	0,0,1,3,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,0,255,255,255,255,255,255,255,255,255,255,255,255,
-	255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,255,255,255,252,
-	0,0,0,2,0,0,0,1,0,0,0,0,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
-	0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
-	0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
-	0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
-	0,0,0,255,0,0,0,0,0,0,0,14,0,0,0,16,0,0,1,24,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,2,208,96,1,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,202,112,1,84,0,0,0,9,0,0,0,0,
-	0,0,0,1,202,112,1,112,0,0,0,9,0,0,0,0,0,0,0,0,105,110,112,117,116,95,116,101,120,116,117,114,
-	101,95,99,111,111,114,100,105,110,97,116,101,115,0,0,0,105,110,112,117,116,95,118,101,114,116,101,120,95,99,111,111,
-	114,100,105,110,97,116,101,115,0,0,0,0,208,96,1,8,202,112,1,52,202,112,1,68,125,66,76,75,0,0,0,40,
-	0,0,0,0,0,0,1,140,208,96,0,0,0,0,0,56,208,96,1,84,0,0,0,0,0,0,0,3,208,96,1,140,
-	66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,0,0,0,5,0,0,1,24,0,0,0,1,0,0,0,0,
-	0,0,0,0,0,0,128,9,32,0,0,0,0,0,4,160,60,32,1,0,136,6,0,148,0,192,0,0,136,4,0,20,
-	34,0,0,0,0,0,0,160,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	1,0,0,0,144,12,32,0,1,4,0,128,144,12,32,32,0,0,0,128,0,13,0,0,66,76,75,123,0,0,0,32,
-	0,0,0,1,0,0,0,0,0,0,0,6,0,0,1,76,0,0,0,2,0,0,0,0,0,0,0,1,0,0,0,2,
-	20,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,1,0,0,0,16,0,0,0,0,0,0,1,144,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,208,96,0,232,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,1,208,96,0,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	202,112,1,8,0,0,0,11,0,0,0,1,0,0,0,0,255,255,255,255,202,112,1,16,0,0,0,1,0,0,0,0,
-	99,111,108,111,117,114,0,0,116,101,120,0,208,96,0,188,208,96,0,212,202,112,0,232,202,112,0,252,125,66,76,75,
-	0,0,0,40,0,0,0,0,0,0,1,20,208,96,0,0,0,0,0,12,208,96,1,8,0,0,0,0,0,0,0,4,
-	208,96,1,20,66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,0,0,0,7,0,0,1,144,0,0,0,3,
-	0,0,0,0,48,0,0,0,0,0,192,128,32,0,0,0,0,0,20,160,0,0,0,0,136,6,32,148,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,161,31,0,144,0,0,0,0,165,159,0,144,0,0,32,0,169,31,1,144,0,0,64,0,12,160,129,
-	144,0,0,96,0,0,128,63,0,0,128,63,0,0,128,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,16,0,0,0,0,254,3,240,0,0,128,16,0,0,0,0,66,76,75,123,0,0,0,32,0,0,0,1,
-	0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,4,0,0,0,0
-};
--- a/src/Backends/WiiU/shaders/shader sources/colour_fill.frag
+++ /dev/null
@@ -1,7 +1,0 @@
-#version 150 core
-layout(location = 0) uniform vec4 colour;
-out vec4 fragment;
-void main()
-{
-	fragment = colour;
-}
\ No newline at end of file
binary files a/src/Backends/WiiU/shaders/shader sources/colour_fill.gsh /dev/null differ
--- a/src/Backends/WiiU/shaders/shader sources/colour_fill.psh
+++ /dev/null
@@ -1,18 +1,0 @@
-; $MODE = "UniformRegister"
-; $NUM_SPI_PS_INPUT_CNTL = 1
-; $SPI_PS_INPUT_CNTL[0].semantic = 0
-; $SPI_PS_INPUT_CNTL[0].default_val = 1
-; $UNIFORM_VARS[0].name = "colour"
-; $UNIFORM_VARS[0].type = "vec4"
-; $UNIFORM_VARS[0].count = 1
-; $UNIFORM_VARS[0].offset = 0
-; $UNIFORM_VARS[0].block = -1
-
-00 ALU: ADDR(32) CNT(4) 
-      0  x: MOV         R0.x,  C0.x      
-         y: MOV         R0.y,  C0.y      
-         z: MOV         R0.z,  C0.z      
-         w: MOV         R0.w,  C0.w      
-01 EXP_DONE: PIX0, R0
-END_OF_PROGRAM
-
--- a/src/Backends/WiiU/shaders/shader sources/glyph.frag
+++ /dev/null
@@ -1,9 +1,0 @@
-#version 150 core
-layout(location = 0) uniform sampler2D tex;
-layout(location = 0) uniform vec4 colour;
-in vec2 texture_coordinates;
-out vec4 fragment;
-void main()
-{
-	fragment = colour * vec4(1.0, 1.0, 1.0, texture(tex, texture_coordinates).r);
-}
\ No newline at end of file
binary files a/src/Backends/WiiU/shaders/shader sources/glyph.gsh /dev/null differ
--- a/src/Backends/WiiU/shaders/shader sources/glyph.psh
+++ /dev/null
@@ -1,22 +1,0 @@
-; $MODE = "UniformRegister"
-; $SAMPLER_VARS[0].name= "tex"
-; $SAMPLER_VARS[0].type= "sampler2D"
-; $SAMPLER_VARS[0].location = 0
-; $NUM_SPI_PS_INPUT_CNTL = 1
-; $SPI_PS_INPUT_CNTL[0].semantic = 0
-; $SPI_PS_INPUT_CNTL[0].default_val = 1
-; $UNIFORM_VARS[0].name = "colour"
-; $UNIFORM_VARS[0].type = "vec4"
-; $UNIFORM_VARS[0].count = 1
-; $UNIFORM_VARS[0].offset = 0
-; $UNIFORM_VARS[0].block = -1
-
-00 TEX: ADDR(48) CNT(1) VALID_PIX 
-      0  SAMPLE R0.___x, R0.xy0x, t0, s0
-01 ALU: ADDR(32) CNT(6) 
-      1  x: MUL         R0.x,  C0.x,  1.0f      
-         y: MUL         R0.y,  C0.y,  1.0f      
-         z: MUL         R0.z,  C0.z,  1.0f      
-         w: MUL         R0.w,  R0.w,  C0.w      
-02 EXP_DONE: PIX0, R0
-END_OF_PROGRAM
--- a/src/Backends/WiiU/shaders/shader sources/plain.vert
+++ /dev/null
@@ -1,6 +1,0 @@
-#version 150 core
-layout(location = 0) in vec4 input_vertex_coordinates;
-void main() 
-{ 
-	gl_Position = input_vertex_coordinates; 
-} 
--- a/src/Backends/WiiU/shaders/shader sources/plain.vsh
+++ /dev/null
@@ -1,20 +1,0 @@
-; $MODE = "UniformRegister"
-; $ATTRIB_VARS[0].name = "input_vertex_coordinates"
-; $ATTRIB_VARS[0].type = "vec2"
-; $ATTRIB_VARS[0].location = 0
-; $NUM_SPI_VS_OUT_ID = 1
-; $SPI_VS_OUT_ID[0].SEMANTIC_0 = 0
-
-00 CALL_FS NO_BARRIER 
-01 EXP_DONE: POS0, R1
-02 EXP_DONE: PARAM0, R0.____
-03 ALU: ADDR(32) CNT(1) 
-      0  x: NOP         ____      
-04 NOP NO_BARRIER 
-END_OF_PROGRAM
-
-
-
-
-
-
--- a/src/Backends/WiiU/shaders/shader sources/texture.frag
+++ /dev/null
@@ -1,8 +1,0 @@
-#version 150 core
-layout(location = 0) uniform sampler2D tex;
-in vec2 texture_coordinates;
-out vec4 fragment;
-void main()
-{
-	fragment = texture(tex, texture_coordinates);
-}
\ No newline at end of file
binary files a/src/Backends/WiiU/shaders/shader sources/texture.gsh /dev/null differ
--- a/src/Backends/WiiU/shaders/shader sources/texture.psh
+++ /dev/null
@@ -1,18 +1,0 @@
-; $MODE = "UniformRegister"
-; $SAMPLER_VARS[0].name= "tex"
-; $SAMPLER_VARS[0].type= "sampler2D"
-; $SAMPLER_VARS[0].location = 0
-; $NUM_SPI_PS_INPUT_CNTL = 1
-; $SPI_PS_INPUT_CNTL[0].semantic = 0
-; $SPI_PS_INPUT_CNTL[0].default_val = 1
-; $UNIFORM_VARS[0].name = "texture_coordinates"
-; $UNIFORM_VARS[0].type = "vec2"
-; $UNIFORM_VARS[0].count = 1
-; $UNIFORM_VARS[0].offset = 0
-; $UNIFORM_VARS[0].block = -1
-
-00 TEX: ADDR(32) CNT(1) VALID_PIX 
-      0  SAMPLE R0, R0.xy0x, t0, s0
-01 EXP_DONE: PIX0, R0
-END_OF_PROGRAM
-
--- a/src/Backends/WiiU/shaders/shader sources/texture.vert
+++ /dev/null
@@ -1,9 +1,0 @@
-#version 150 core
-layout(location = 0) in vec4 input_vertex_coordinates;
-layout(location = 1) in vec2 input_texture_coordinates;
-out vec2 texture_coordinates; 
-void main() 
-{ 
-	gl_Position = input_vertex_coordinates; 
-	texture_coordinates = input_texture_coordinates; 
-} 
--- a/src/Backends/WiiU/shaders/shader sources/texture.vsh
+++ /dev/null
@@ -1,27 +1,0 @@
-; $MODE = "UniformRegister"
-; $ATTRIB_VARS[0].name = "input_texture_coordinates"
-; $ATTRIB_VARS[0].type = "vec2"
-; $ATTRIB_VARS[0].location = 1
-; $ATTRIB_VARS[1].name = "input_vertex_coordinates"
-; $ATTRIB_VARS[1].type = "vec2"
-; $ATTRIB_VARS[1].location = 0
-; $NUM_SPI_VS_OUT_ID = 1
-; $SPI_VS_OUT_ID[0].SEMANTIC_0 = 0
-
-00 CALL_FS NO_BARRIER 
-01 ALU: ADDR(32) CNT(2) 
-      0  x: MOV         R1.x,  R1.x      
-         y: MOV         R1.y,  R1.y      
-02 EXP_DONE: POS0, R2
-03 EXP_DONE: PARAM0, R1.xyzz  NO_BARRIER 
-04 ALU: ADDR(34) CNT(1) 
-      1  x: NOP         ____      
-05 NOP NO_BARRIER 
-END_OF_PROGRAM
-
-
-
-
-
-
-
--- a/src/Backends/WiiU/shaders/shader sources/texture_colour_key.frag
+++ /dev/null
@@ -1,13 +1,0 @@
-#version 150 core
-layout(location = 0) uniform sampler2D tex;
-in vec2 texture_coordinates;
-out vec4 fragment;
-void main()
-{
-	vec4 colour = texture(tex, texture_coordinates);
-
-	if (colour.r + colour.g + colour. b == 0.0)
-		discard;
-
-	fragment = colour;
-}
\ No newline at end of file
binary files a/src/Backends/WiiU/shaders/shader sources/texture_colour_key.gsh /dev/null differ
--- a/src/Backends/WiiU/shaders/shader sources/texture_colour_key.psh
+++ /dev/null
@@ -1,25 +1,0 @@
-; $MODE = "UniformRegister"
-; $SAMPLER_VARS[0].name= "tex"
-; $SAMPLER_VARS[0].type= "sampler2D"
-; $SAMPLER_VARS[0].location = 0
-; $NUM_SPI_PS_INPUT_CNTL = 1
-; $SPI_PS_INPUT_CNTL[0].semantic = 0
-; $SPI_PS_INPUT_CNTL[0].default_val = 1
-; $UNIFORM_VARS[0].name = "texture_coordinates"
-; $UNIFORM_VARS[0].type = "vec2"
-; $UNIFORM_VARS[0].count = 1
-; $UNIFORM_VARS[0].offset = 0
-; $UNIFORM_VARS[0].block = -1
-
-00 TEX: ADDR(48) CNT(1) VALID_PIX 
-      0  SAMPLE R0, R0.xy0x, t0, s0
-01 ALU: ADDR(32) CNT(3) 
-      1  y: ADD         ____,  R0.x,  R0.y      
-      2  x: ADD         ____,  R0.z,  PV1.y      
-      3  x: KILLE       ____,  PV2.x,  0.0f      
-02 EXP_DONE: PIX0, R0
-END_OF_PROGRAM
-
-
-
-
--- a/src/Backends/WiiU/shaders/shader sources/wtf is this.txt
+++ /dev/null
@@ -1,61 +1,0 @@
-#######################
-# Shader combinations #
-#######################
-
-texture.vsh + texture.psh = texture.gsh
-texture.vsh + texture_colour_key.psh = texture_colour_key.gsh
-plain.vsh + colour_fill.psh = colour_fill.gsh
-texture.vsh + glyph.psh = glyph.gsh
-
-
-##########################
-# How to compile shaders #
-##########################
-
-I'mma give it to you straight: compiling shaders for the Wii U is an absolute
-nightmare.
-
-You see, there are three major steps:
-* Compile the GLSL to assembly
-* Fill in a header by-hand
-* Assemble the assembly + header
-
-To compile, you need AMD's 'GPU ShaderAnalyzer', which is Windows-only:
-https://gpuopen.com/archive/gpu-shaderanalyzer/
-
-You then need to compile for the RV730.
-
-After that, you need to fill-in a header. The only way I was able to figure out
-what little I did was by finding examples on the internet:
-https://github.com/snickerbockers/gx2gl/tree/master/src/shaders
-https://github.com/yawut/SDL/tree/wiiu-2.0.9/src/video/wiiu/shaders
-https://github.com/devkitPro/wut/tree/master/samples/cmake/content
-
-Even now, I don't have a complete idea of exactly what everything means.
-
-Anyway, once you have *that* out of the way, you still need to assemble your
-shaders. For that, you'll need `latte-assembler` - a tool that comes with the
-Decaf emulator.
-
-For me, I needed to clone the entire source tree (and its dependencies) in order
-to build the tool successfully, which is a royal pain in the ass.
-
-For compilation, I used MSYS2+MinGW-w64. There were a couple of compilation
-errors I had to address, but nothing too hard to solve.
-
-Eventually you'll have the assembler built. With it, you can link your `.psh`
-and `.vsh` files into the final `.gsh` blob.
-
-Oh, right. I should warn you - the devs changed latte-assembler's syntax at some
-point, so all the example `.psh`/`.vsh` files I could find online were
-incompatible. That sucked. The main change was that stuff like 'float2' and
-'float4' were changed to 'vec2' and 'vec4'. Just keep that in mind, and you
-should be okay.
-
-Also, latte-assembler's 'assemble' command was originally named 'compile', and
-the other 'compile' option didn't exist. Keep that in mind if you ever find any
-documentation on how to use the tool.
-
-latte-assembler does have an option to convert straight from GLSL to `.gsh`, but
-this feature is woefully incomplete (it doesn't support `sampler2D`), and not
-worth using.
--- a/src/Backends/WiiU/shaders/texture.gsh.h
+++ /dev/null
@@ -1,52 +1,0 @@
-#pragma once
-
-static const unsigned char rtexture[0x600] = {
-	71,102,120,50,0,0,0,32,0,0,0,7,0,0,0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,
-	66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,0,0,0,3,0,0,1,192,0,0,0,0,0,0,0,0,
-	0,0,1,3,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,0,255,255,255,255,255,255,255,255,255,255,255,255,
-	255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,255,255,255,252,
-	0,0,0,2,0,0,0,1,0,0,0,0,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
-	0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
-	0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
-	0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
-	0,0,0,255,0,0,0,0,0,0,0,14,0,0,0,16,0,0,1,24,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,2,208,96,1,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,202,112,1,84,0,0,0,9,0,0,0,0,
-	0,0,0,1,202,112,1,112,0,0,0,9,0,0,0,0,0,0,0,0,105,110,112,117,116,95,116,101,120,116,117,114,
-	101,95,99,111,111,114,100,105,110,97,116,101,115,0,0,0,105,110,112,117,116,95,118,101,114,116,101,120,95,99,111,111,
-	114,100,105,110,97,116,101,115,0,0,0,0,208,96,1,8,202,112,1,52,202,112,1,68,125,66,76,75,0,0,0,40,
-	0,0,0,0,0,0,1,140,208,96,0,0,0,0,0,56,208,96,1,84,0,0,0,0,0,0,0,3,208,96,1,140,
-	66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,0,0,0,5,0,0,1,24,0,0,0,1,0,0,0,0,
-	0,0,0,0,0,0,128,9,32,0,0,0,0,0,4,160,60,32,1,0,136,6,0,148,0,192,0,0,136,4,0,20,
-	34,0,0,0,0,0,0,160,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	1,0,0,0,144,12,32,0,1,4,0,128,144,12,32,32,0,0,0,128,0,13,0,0,66,76,75,123,0,0,0,32,
-	0,0,0,1,0,0,0,0,0,0,0,6,0,0,1,88,0,0,0,2,0,0,0,0,0,0,0,1,0,0,0,2,
-	20,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,1,0,0,0,16,0,0,0,0,0,0,1,16,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,208,96,0,232,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,1,208,96,0,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	202,112,1,8,0,0,0,9,0,0,0,1,0,0,0,0,255,255,255,255,202,112,1,28,0,0,0,1,0,0,0,0,
-	116,101,120,116,117,114,101,95,99,111,111,114,100,105,110,97,116,101,115,0,116,101,120,0,208,96,0,188,208,96,0,212,
-	202,112,0,232,202,112,0,252,125,66,76,75,0,0,0,40,0,0,0,0,0,0,1,32,208,96,0,0,0,0,0,24,
-	208,96,1,8,0,0,0,0,0,0,0,4,208,96,1,32,66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,
-	0,0,0,7,0,0,1,16,0,0,0,3,0,0,0,0,32,0,0,0,0,0,192,128,0,0,0,0,136,6,32,148,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,0,0,0,0,16,13,240,0,0,128,16,0,0,0,0,
-	66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,4,0,0,0,0
-};
--- a/src/Backends/WiiU/shaders/texture_colour_key.gsh.h
+++ /dev/null
@@ -1,56 +1,0 @@
-#pragma once
-
-static const unsigned char rtexture_colour_key[0x680] = {
-	71,102,120,50,0,0,0,32,0,0,0,7,0,0,0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,
-	66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,0,0,0,3,0,0,1,192,0,0,0,0,0,0,0,0,
-	0,0,1,3,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,0,255,255,255,255,255,255,255,255,255,255,255,255,
-	255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,255,255,255,252,
-	0,0,0,2,0,0,0,1,0,0,0,0,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
-	0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
-	0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
-	0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,
-	0,0,0,255,0,0,0,0,0,0,0,14,0,0,0,16,0,0,1,24,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,2,208,96,1,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,202,112,1,84,0,0,0,9,0,0,0,0,
-	0,0,0,1,202,112,1,112,0,0,0,9,0,0,0,0,0,0,0,0,105,110,112,117,116,95,116,101,120,116,117,114,
-	101,95,99,111,111,114,100,105,110,97,116,101,115,0,0,0,105,110,112,117,116,95,118,101,114,116,101,120,95,99,111,111,
-	114,100,105,110,97,116,101,115,0,0,0,0,208,96,1,8,202,112,1,52,202,112,1,68,125,66,76,75,0,0,0,40,
-	0,0,0,0,0,0,1,140,208,96,0,0,0,0,0,56,208,96,1,84,0,0,0,0,0,0,0,3,208,96,1,140,
-	66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,0,0,0,5,0,0,1,24,0,0,0,1,0,0,0,0,
-	0,0,0,0,0,0,128,9,32,0,0,0,0,0,4,160,60,32,1,0,136,6,0,148,0,192,0,0,136,4,0,20,
-	34,0,0,0,0,0,0,160,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	1,0,0,0,144,12,32,0,1,4,0,128,144,12,32,32,0,0,0,128,0,13,0,0,66,76,75,123,0,0,0,32,
-	0,0,0,1,0,0,0,0,0,0,0,6,0,0,1,88,0,0,0,2,0,0,0,0,0,0,0,1,0,0,0,2,
-	20,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,1,0,0,0,16,0,0,0,0,0,0,1,144,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,208,96,0,232,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,1,208,96,0,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	202,112,1,8,0,0,0,9,0,0,0,1,0,0,0,0,255,255,255,255,202,112,1,28,0,0,0,1,0,0,0,0,
-	116,101,120,116,117,114,101,95,99,111,111,114,100,105,110,97,116,101,115,0,116,101,120,0,208,96,0,188,208,96,0,212,
-	202,112,0,232,202,112,0,252,125,66,76,75,0,0,0,40,0,0,0,0,0,0,1,32,208,96,0,0,0,0,0,24,
-	208,96,1,8,0,0,0,0,0,0,0,4,208,96,1,32,66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,
-	0,0,0,7,0,0,1,144,0,0,0,3,0,0,0,0,48,0,0,0,0,0,192,128,32,0,0,0,0,0,8,160,
-	0,0,0,0,136,6,32,148,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,128,0,0,0,32,0,200,159,128,0,0,0,0,
-	254,0,31,128,0,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,0,0,0,0,16,13,240,0,0,128,16,0,0,0,0,
-	66,76,75,123,0,0,0,32,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,4,0,0,0,0
-};
--- /dev/null
+++ b/src/Backends/Window/GLFW3-OpenGL3.cpp
@@ -1,0 +1,94 @@
+#include "../Window-OpenGL.h"
+#include "Window.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+
+#ifdef USE_OPENGLES2
+#include <GLES2/gl2.h>
+#else
+#include <glad/glad.h>
+#endif
+#include <GLFW/glfw3.h>
+
+#include "../Misc.h"
+
+GLFWwindow *window;
+
+bool WindowBackend_OpenGL_CreateWindow(const char *window_title, int *screen_width, int *screen_height, bool fullscreen)
+{
+#ifdef USE_OPENGLES2
+	glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
+	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
+	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
+#else
+	glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
+	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
+	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
+	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
+#endif
+
+	GLFWmonitor *monitor = NULL;
+
+	if (fullscreen)
+	{
+		monitor = glfwGetPrimaryMonitor();
+
+		if (monitor != NULL)
+		{
+			const GLFWvidmode *mode = glfwGetVideoMode(monitor);
+
+			*screen_width = mode->width;
+			*screen_height = mode->height;
+		}
+	}
+
+	window = glfwCreateWindow(*screen_width, *screen_height, window_title, monitor, NULL);
+
+	if (window != NULL)
+	{
+		glfwMakeContextCurrent(window);
+
+			#ifndef USE_OPENGLES2
+				if (gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
+				{
+					// Check if the platform supports OpenGL 3.2
+					if (GLAD_GL_VERSION_3_2)
+					{
+			#endif
+						Backend_PostWindowCreation();
+
+						return true;
+			#ifndef USE_OPENGLES2
+					}
+					else
+					{
+						Backend_ShowMessageBox("Fatal error (OpenGL rendering backend)", "Your system does not support OpenGL 3.2");
+					}
+				}
+				else
+				{
+					Backend_ShowMessageBox("Fatal error (OpenGL rendering backend)", "Could not initialize OpenGL context");
+				}
+			#endif
+
+		glfwDestroyWindow(window);
+	}
+	else
+	{
+		Backend_ShowMessageBox("Fatal error (OpenGL rendering backend)", "Could not create window");
+	}
+
+	return false;
+}
+
+void WindowBackend_OpenGL_DestroyWindow(void)
+{
+	glfwDestroyWindow(window);
+}
+
+void WindowBackend_OpenGL_Display(void)
+{
+	glfwSwapBuffers(window);
+}
--- /dev/null
+++ b/src/Backends/Window/GLFW3-OpenGLES2.cpp
@@ -1,0 +1,2 @@
+#define USE_OPENGLES2
+#include "Window-OpenGL3.cpp"
--- /dev/null
+++ b/src/Backends/Window/GLFW3-Software.cpp
@@ -1,0 +1,156 @@
+#include "../Window-Software.h"
+#include "Window.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+
+#if defined(__APPLE__)
+ #include <OpenGL/gl.h>
+#else
+ #include <GL/gl.h>
+#endif
+#include <GLFW/glfw3.h>
+
+#include "../Misc.h"
+
+GLFWwindow *window;
+
+static unsigned char *framebuffer;
+static int framebuffer_width;
+static int framebuffer_height;
+
+static float framebuffer_x_ratio;
+static float framebuffer_y_ratio;
+
+static GLuint screen_texture_id;
+
+bool WindowBackend_Software_CreateWindow(const char *window_title, int screen_width, int screen_height, bool fullscreen)
+{
+	glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
+	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 1);
+	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
+
+	framebuffer_width = screen_width;
+	framebuffer_height = screen_height;
+
+	GLFWmonitor *monitor = NULL;
+
+	if (fullscreen)
+	{
+		monitor = glfwGetPrimaryMonitor();
+
+		if (monitor != NULL)
+		{
+			const GLFWvidmode *mode = glfwGetVideoMode(monitor);
+
+			screen_width = mode->width;
+			screen_height = mode->height;
+		}
+	}
+
+	window = glfwCreateWindow(screen_width, screen_height, window_title, monitor, NULL);
+
+	if (window != NULL)
+	{
+		glfwMakeContextCurrent(window);
+
+		glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+
+		glEnable(GL_TEXTURE_2D);
+
+		WindowBackend_Software_HandleWindowResize(screen_width, screen_height);
+
+		// Create screen texture
+		glGenTextures(1, &screen_texture_id);
+		glBindTexture(GL_TEXTURE_2D, screen_texture_id);
+
+		int framebuffer_texture_width = 1;
+		while (framebuffer_texture_width < framebuffer_width)
+			framebuffer_texture_width <<= 1;
+
+		int framebuffer_texture_height = 1;
+		while (framebuffer_texture_height < framebuffer_height)
+			framebuffer_texture_height <<= 1;
+
+		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, framebuffer_texture_width, framebuffer_texture_height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+		framebuffer_x_ratio = (float)framebuffer_width / framebuffer_texture_width;
+		framebuffer_y_ratio = (float)framebuffer_height / framebuffer_texture_height;
+
+		framebuffer = (unsigned char*)malloc(framebuffer_width * framebuffer_height * 3);
+
+		Backend_PostWindowCreation();
+
+		return true;
+	}
+	else
+	{
+		Backend_ShowMessageBox("Fatal error (OpenGL rendering backend)", "Could not create window");
+	}
+
+	return false;
+}
+
+void WindowBackend_Software_DestroyWindow(void)
+{
+	free(framebuffer);
+	glDeleteTextures(1, &screen_texture_id);
+	glfwDestroyWindow(window);
+}
+
+unsigned char* WindowBackend_Software_GetFramebuffer(size_t *pitch)
+{
+	*pitch = framebuffer_width * 3;
+
+	return framebuffer;
+}
+
+void WindowBackend_Software_Display(void)
+{
+	glClear(GL_COLOR_BUFFER_BIT);
+
+	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, framebuffer_width, framebuffer_height, GL_RGB, GL_UNSIGNED_BYTE, framebuffer);
+
+	glBegin(GL_TRIANGLE_STRIP);
+		glTexCoord2f(0.0f, framebuffer_y_ratio);
+		glVertex2f(-1.0f, -1.0f);
+		glTexCoord2f(framebuffer_x_ratio, framebuffer_y_ratio);
+		glVertex2f(1.0f, -1.0f);
+		glTexCoord2f(0.0f, 0.0f);
+		glVertex2f(-1.0f, 1.0f);
+		glTexCoord2f(framebuffer_x_ratio, 0.0f);
+		glVertex2f(1.0f, 1.0f);
+	glEnd();
+
+	glfwSwapBuffers(window);
+}
+
+void WindowBackend_Software_HandleWindowResize(unsigned int width, unsigned int height)
+{
+	// Do some viewport trickery, to fit the framebuffer in the center of the screen
+	GLint viewport_x;
+	GLint viewport_y;
+	GLsizei viewport_width;
+	GLsizei viewport_height;
+
+	if ((float)width / (float)height > (float)framebuffer_width / (float)framebuffer_height)
+	{
+		viewport_y = 0;
+		viewport_height = height;
+
+		viewport_width = framebuffer_width * ((float)height / (float)framebuffer_height);
+		viewport_x = (width - viewport_width) / 2;
+	}
+	else
+	{
+		viewport_x = 0;
+		viewport_width = width;
+
+		viewport_height = framebuffer_height * ((float)width / (float)framebuffer_width);
+		viewport_y = (height - viewport_height) / 2;
+	}
+
+	glViewport(viewport_x, viewport_y, viewport_width, viewport_height);
+}
--- /dev/null
+++ b/src/Backends/Window/Null-Software.cpp
@@ -1,0 +1,39 @@
+#include "../Window-Software.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+
+static unsigned char *framebuffer;
+
+unsigned char* WindowBackend_Software_CreateWindow(const char *window_title, int screen_width, int screen_height, bool fullscreen, size_t *pitch)
+{
+	(void)window_title;
+	(void)fullscreen;
+
+	framebuffer = (unsigned char*)malloc(screen_width * screen_height * 3);
+
+	if (framebuffer != NULL)
+	{
+		*pitch = screen_width * 3;
+
+		return framebuffer;
+	}
+
+	return NULL;
+}
+
+void WindowBackend_Software_DestroyWindow(void)
+{
+	free(framebuffer);
+}
+
+void WindowBackend_Software_Display(void)
+{
+	
+}
+
+void WindowBackend_Software_HandleWindowResize(unsigned int width, unsigned int height)
+{
+	(void)width;
+	(void)height;
+}
--- /dev/null
+++ b/src/Backends/Window/SDL2-OpenGL3.cpp
@@ -1,0 +1,116 @@
+#include "../Window-OpenGL.h"
+#include "Window.h"
+
+#include <stddef.h>
+#include <string>
+
+#ifdef USE_OPENGLES2
+#include <GLES2/gl2.h>
+#else
+#include <glad/glad.h>
+#endif
+#include "SDL.h"
+
+#include "../Misc.h"
+#include "../../Resource.h"
+
+SDL_Window *window;
+
+static SDL_GLContext context;
+
+bool WindowBackend_OpenGL_CreateWindow(const char *window_title, int *screen_width, int *screen_height, bool fullscreen)
+{
+#ifdef USE_OPENGLES2
+	if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES) < 0)
+		Backend_PrintError("Couldn't set OpenGL context type to ES: %s", SDL_GetError());
+
+	if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0) < 0)
+		Backend_PrintError("Couldn't set OpenGL context flags to 0: %s", SDL_GetError());
+
+	if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2) < 0)
+		Backend_PrintError("Couldn't set OpenGL major version to 2: %s", SDL_GetError());
+
+	if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0) < 0)
+		Backend_PrintError("Couldn't set OpenGL minor version to 0: %s", SDL_GetError());
+#else
+	if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE) < 0)
+		Backend_PrintError("Couldn't set OpenGL context type to core: %s", SDL_GetError());
+
+	if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG) < 0)
+		Backend_PrintError("Couldn't set OpenGL forward compatibility: %s", SDL_GetError());
+
+	if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3) < 0)
+		Backend_PrintError("Couldn't set OpenGL major version to 3: %s", SDL_GetError());
+
+	if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2) < 0)
+		Backend_PrintError("Couldn't set OpenGL minor verison to 2: %s", SDL_GetError());
+#endif
+
+	window = SDL_CreateWindow(window_title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, *screen_width, *screen_height, SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL | (fullscreen ? SDL_WINDOW_FULLSCREEN : 0));
+
+	if (window != NULL)
+	{
+		context = SDL_GL_CreateContext(window);
+
+		if (context != NULL)
+		{
+			if (SDL_GL_MakeCurrent(window, context) == 0)
+			{
+			#ifndef USE_OPENGLES2
+				if (gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress))
+				{
+					// Check if the platform supports OpenGL 3.2
+					if (GLAD_GL_VERSION_3_2)
+					{
+			#endif
+						Backend_PostWindowCreation();
+
+						return true;
+			#ifndef USE_OPENGLES2
+					}
+					else
+					{
+						Backend_ShowMessageBox("Fatal error (OpenGL rendering backend)", "Your system does not support OpenGL 3.2");
+					}
+				}
+				else
+				{
+					Backend_ShowMessageBox("Fatal error (OpenGL rendering backend)", "Couldn't load OpenGL functions");
+				}
+			#endif
+			}
+			else
+			{
+				std::string error_message = std::string("Couldn't setup OpenGL context for rendering: ") + SDL_GetError();
+				Backend_ShowMessageBox("Fatal error (OpenGL rendering backend)", "SDL_GL_MakeCurrent failed");
+			}
+
+			SDL_GL_DeleteContext(context);
+		}
+		else
+		{
+			std::string error_message = std::string("Couldn't create OpenGL context: %s", SDL_GetError());
+			Backend_ShowMessageBox("Fatal error (OpenGL rendering backend)", "Could not create OpenGL context");
+		}
+
+		SDL_DestroyWindow(window);
+	}
+	else
+	{
+		std::string error_message = std::string("Could not create window: ") + SDL_GetError();
+		Backend_ShowMessageBox("Fatal error (OpenGL rendering backend)", error_message.c_str());
+	}
+
+	return false;
+}
+
+void WindowBackend_OpenGL_DestroyWindow(void)
+{
+	SDL_GL_DeleteContext(context);
+	SDL_DestroyWindow(window);
+}
+
+void WindowBackend_OpenGL_Display(void)
+{
+	SDL_GL_SwapWindow(window);
+}
--- /dev/null
+++ b/src/Backends/Window/SDL2-OpenGLES2.cpp
@@ -1,0 +1,2 @@
+#define USE_OPENGLES2
+#include "Window-OpenGL3.cpp"
--- /dev/null
+++ b/src/Backends/Window/SDL2-Software.cpp
@@ -1,0 +1,95 @@
+#include "../Window-Software.h"
+#include "Window.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <string>
+
+#include "SDL.h"
+
+#include "../Misc.h"
+
+SDL_Window *window;
+
+static SDL_Surface *window_sdlsurface;
+static SDL_Surface *framebuffer_sdlsurface;
+
+bool WindowBackend_Software_CreateWindow(const char *window_title, int screen_width, int screen_height, bool fullscreen)
+{
+	window = SDL_CreateWindow(window_title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, screen_width, screen_height, 0);
+
+	if (window != NULL)
+	{
+		if (fullscreen)
+			if (SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN) < 0)
+				Backend_PrintError("Couldn't set window to fullscree: %s", SDL_GetError());
+
+		window_sdlsurface = SDL_GetWindowSurface(window);
+
+		if (window_sdlsurface != NULL)
+		{
+
+			framebuffer_sdlsurface = SDL_CreateRGBSurfaceWithFormat(0, window_sdlsurface->w, window_sdlsurface->h, 0, SDL_PIXELFORMAT_RGB24);
+
+			if (framebuffer_sdlsurface != NULL)
+			{
+				Backend_PostWindowCreation();
+
+				return true;
+			}
+			else
+			{
+				std::string error_message = std::string("Couldn't create framebuffer surface: ") + SDL_GetError();
+				Backend_ShowMessageBox("Fatal error (software rendering backend)", error_message.c_str());
+				SDL_DestroyWindow(window);
+			}
+		}
+		else
+		{
+			std::string error_message = std::string("Couldn't get SDL surface associated with window: ") + SDL_GetError();
+			Backend_ShowMessageBox("Fatal error (software rendering backend)", error_message.c_str());
+		}
+	}
+	else
+	{
+		std::string error_message = std::string("Couldn't create window: ") + SDL_GetError();
+		Backend_ShowMessageBox("Fatal error (software rendering backend)", error_message.c_str());
+	}
+
+	return false;
+}
+
+void WindowBackend_Software_DestroyWindow(void)
+{
+	SDL_FreeSurface(framebuffer_sdlsurface);
+	SDL_DestroyWindow(window);
+}
+
+unsigned char* WindowBackend_Software_GetFramebuffer(size_t *pitch)
+{
+	*pitch = framebuffer_sdlsurface->pitch;
+
+	return (unsigned char*)framebuffer_sdlsurface->pixels;
+}
+
+void WindowBackend_Software_Display(void)
+{
+	if (SDL_BlitSurface(framebuffer_sdlsurface, NULL, window_sdlsurface, NULL) < 0)
+		Backend_PrintError("Couldn't blit framebuffer surface to window surface: %s", SDL_GetError());
+
+	if (SDL_UpdateWindowSurface(window) < 0)
+		Backend_PrintError("Couldn't copy window surface to the screen: %s", SDL_GetError());
+}
+
+void WindowBackend_Software_HandleWindowResize(unsigned int width, unsigned int height)
+{
+	(void)width;
+	(void)height;
+
+	// https://wiki.libsdl.org/SDL_GetWindowSurface
+	// We need to fetch a new surface pointer
+	window_sdlsurface = SDL_GetWindowSurface(window);
+
+	if (window_sdlsurface == NULL)
+		Backend_PrintError("Couldn't get SDL surface associated with window: %s", SDL_GetError());
+}
--- /dev/null
+++ b/src/Backends/Window/WiiU-Software.cpp
@@ -1,0 +1,269 @@
+// Sexy new backend that bounces the software-rendered frame to the GPU,
+// eliminating V-tearing, and gaining support for rendering to the TV for
+// free!
+
+#include "../Window-Software.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <gx2/display.h>
+#include <gx2/draw.h>
+#include <gx2/registers.h>
+#include <gx2/sampler.h>
+#include <gx2/texture.h>
+#include <gx2r/buffer.h>
+#include <gx2r/draw.h>
+#include <gx2r/resource.h>
+#include <gx2r/surface.h>
+#include <whb/gfx.h>
+
+#include "../../Attributes.h"
+
+#include "shaders/texture.gsh.h"
+
+typedef struct Viewport
+{
+	float x;
+	float y;
+	float width;
+	float height;
+} Viewport;
+
+static unsigned char *fake_framebuffer;
+
+static size_t fake_framebuffer_width;
+static size_t fake_framebuffer_height;
+
+static WHBGfxShaderGroup shader_group;
+
+static GX2RBuffer vertex_position_buffer;
+static GX2RBuffer texture_coordinate_buffer;
+
+static GX2Sampler sampler;
+
+static GX2Texture screen_texture;
+
+static Viewport tv_viewport;
+static Viewport drc_viewport;
+
+static void CalculateViewport(unsigned int actual_screen_width, unsigned int actual_screen_height, Viewport *viewport)
+{
+	if ((float)actual_screen_width / (float)actual_screen_height > (float)fake_framebuffer_width / (float)fake_framebuffer_height)
+	{
+		viewport->y = 0.0f;
+		viewport->height = actual_screen_height;
+
+		viewport->width = fake_framebuffer_width * ((float)actual_screen_height / (float)fake_framebuffer_height);
+		viewport->x = (actual_screen_width - viewport->width) / 2;
+	}
+	else
+	{
+		viewport->x = 0.0f;
+		viewport->width = actual_screen_width;
+
+		viewport->height = fake_framebuffer_height * ((float)actual_screen_width / (float)fake_framebuffer_width);
+		viewport->y = (actual_screen_height - viewport->height) / 2;
+	}
+}
+
+bool WindowBackend_Software_CreateWindow(const char *window_title, int screen_width, int screen_height, bool fullscreen)
+{
+	(void)window_title;
+	(void)fullscreen;
+
+	fake_framebuffer_width = screen_width;
+	fake_framebuffer_height = screen_height;
+
+	fake_framebuffer = (unsigned char*)malloc(fake_framebuffer_width * fake_framebuffer_height * 3);
+
+	if (fake_framebuffer != NULL)
+	{
+		WHBGfxInit();
+
+		if (WHBGfxLoadGFDShaderGroup(&shader_group, 0, rtexture))
+		{
+			WHBGfxInitShaderAttribute(&shader_group, "input_vertex_coordinates", 0, 0, GX2_ATTRIB_FORMAT_FLOAT_32_32);
+			WHBGfxInitShaderAttribute(&shader_group, "input_texture_coordinates", 1, 0, GX2_ATTRIB_FORMAT_FLOAT_32_32);
+			WHBGfxInitFetchShader(&shader_group);
+
+			// Initialise vertex position buffer
+			const float vertex_positions[4][2] = {
+				{-1.0f,  1.0f},
+				{ 1.0f,  1.0f},
+				{ 1.0f, -1.0f},
+				{-1.0f, -1.0f}
+			};
+
+			vertex_position_buffer.flags = (GX2RResourceFlags)(GX2R_RESOURCE_BIND_VERTEX_BUFFER |
+			                                                   GX2R_RESOURCE_USAGE_CPU_READ |
+			                                                   GX2R_RESOURCE_USAGE_CPU_WRITE |
+			                                                   GX2R_RESOURCE_USAGE_GPU_READ);
+			vertex_position_buffer.elemSize = sizeof(vertex_positions[0]);
+			vertex_position_buffer.elemCount = sizeof(vertex_positions) / sizeof(vertex_positions[0]);
+			GX2RCreateBuffer(&vertex_position_buffer);
+			memcpy(GX2RLockBufferEx(&vertex_position_buffer, (GX2RResourceFlags)0), vertex_positions, sizeof(vertex_positions));
+			GX2RUnlockBufferEx(&vertex_position_buffer, (GX2RResourceFlags)0);
+
+			// Initialise texture coordinate buffer
+			const float texture_coordinates[4][2] = {
+				{0.0f, 0.0f},
+				{1.0f, 0.0f},
+				{1.0f, 1.0f},
+				{0.0f, 1.0f}
+			};
+
+			texture_coordinate_buffer.flags = (GX2RResourceFlags)(GX2R_RESOURCE_BIND_VERTEX_BUFFER |
+			                                                      GX2R_RESOURCE_USAGE_CPU_READ |
+			                                                      GX2R_RESOURCE_USAGE_CPU_WRITE |
+			                                                      GX2R_RESOURCE_USAGE_GPU_READ);
+			texture_coordinate_buffer.elemSize = sizeof(texture_coordinates[0]);
+			texture_coordinate_buffer.elemCount = sizeof(texture_coordinates) / sizeof(texture_coordinates[0]);
+			GX2RCreateBuffer(&texture_coordinate_buffer);
+			memcpy(GX2RLockBufferEx(&texture_coordinate_buffer, (GX2RResourceFlags)0), texture_coordinates, sizeof(texture_coordinates));
+			GX2RUnlockBufferEx(&texture_coordinate_buffer, (GX2RResourceFlags)0);
+
+			// Initialise sampler
+			GX2InitSampler(&sampler, GX2_TEX_CLAMP_MODE_CLAMP, GX2_TEX_XY_FILTER_MODE_POINT);
+
+			// Initialise screen texture
+			screen_texture.surface.width = fake_framebuffer_width;
+			screen_texture.surface.height = fake_framebuffer_height;
+			screen_texture.surface.format = GX2_SURFACE_FORMAT_UNORM_R8_G8_B8_A8;
+			screen_texture.surface.depth = 1;
+			screen_texture.surface.dim = GX2_SURFACE_DIM_TEXTURE_2D;
+			screen_texture.surface.tileMode = GX2_TILE_MODE_LINEAR_ALIGNED;
+			screen_texture.surface.mipLevels = 1;
+			screen_texture.viewNumMips = 1;
+			screen_texture.viewNumSlices = 1;
+			screen_texture.compMap = 0x00010203;
+			GX2CalcSurfaceSizeAndAlignment(&screen_texture.surface);
+			GX2InitTextureRegs(&screen_texture);
+
+			if (GX2RCreateSurface(&screen_texture.surface, (GX2RResourceFlags)(GX2R_RESOURCE_BIND_TEXTURE | GX2R_RESOURCE_BIND_COLOR_BUFFER |
+			                                                                   GX2R_RESOURCE_USAGE_CPU_WRITE | GX2R_RESOURCE_USAGE_CPU_READ |
+			                                                                   GX2R_RESOURCE_USAGE_GPU_WRITE | GX2R_RESOURCE_USAGE_GPU_READ)))
+			{
+				// Do some binding
+				GX2SetPixelSampler(&sampler, shader_group.pixelShader->samplerVars[0].location);
+				GX2SetPixelTexture(&screen_texture, shader_group.pixelShader->samplerVars[0].location);
+				GX2RSetAttributeBuffer(&vertex_position_buffer, 0, vertex_position_buffer.elemSize, 0);
+				GX2RSetAttributeBuffer(&texture_coordinate_buffer, 1, texture_coordinate_buffer.elemSize, 0);
+
+				// Calculate centred viewports
+				switch (GX2GetSystemTVScanMode())
+				{
+					// For now, we have to match WUT's broken behaviour (its `GX2TVScanMode`
+					// enum is missing values, and the rest are off-by-one)
+					//case GX2_TV_SCAN_MODE_576I:
+					case GX2_TV_SCAN_MODE_480I:	// Actually 576i
+					case GX2_TV_SCAN_MODE_480P:	// Actually 480i
+						CalculateViewport(854, 480, &tv_viewport);
+						break;
+
+					case GX2_TV_SCAN_MODE_720P:	// Actually 480p
+					default:	// Funnel the *real* 1080p into this
+						CalculateViewport(1280, 720, &tv_viewport);
+						break;
+
+					case GX2_TV_SCAN_MODE_1080I:	// Actually invalid
+					case GX2_TV_SCAN_MODE_1080P:	// Actually 1080i
+						CalculateViewport(1920, 1080, &tv_viewport);
+						break;
+				}
+
+				CalculateViewport(854, 480, &drc_viewport);
+
+				return true;
+			}
+
+			GX2RDestroyBufferEx(&texture_coordinate_buffer, (GX2RResourceFlags)0);
+			GX2RDestroyBufferEx(&vertex_position_buffer, (GX2RResourceFlags)0);
+
+			WHBGfxFreeShaderGroup(&shader_group);
+		}
+
+		WHBGfxShutdown();
+
+		free(fake_framebuffer);
+	}
+
+	return false;
+}
+
+void WindowBackend_Software_DestroyWindow(void)
+{
+	GX2RDestroySurfaceEx(&screen_texture.surface, (GX2RResourceFlags)0);
+
+	GX2RDestroyBufferEx(&texture_coordinate_buffer, (GX2RResourceFlags)0);
+	GX2RDestroyBufferEx(&vertex_position_buffer, (GX2RResourceFlags)0);
+
+	WHBGfxFreeShaderGroup(&shader_group);
+
+	WHBGfxShutdown();
+
+	free(fake_framebuffer);
+}
+
+unsigned char* WindowBackend_Software_GetFramebuffer(size_t *pitch)
+{
+	*pitch = fake_framebuffer_width * 3;
+
+	return fake_framebuffer;
+}
+
+ATTRIBUTE_HOT void WindowBackend_Software_Display(void)
+{
+	// Convert frame from RGB24 to RGBA32, and upload it to the GPU texture
+	unsigned char *framebuffer = (unsigned char*)GX2RLockSurfaceEx(&screen_texture.surface, 0, (GX2RResourceFlags)0);
+
+	const unsigned char *in_pointer = fake_framebuffer;
+
+	for (size_t y = 0; y < fake_framebuffer_height; ++y)
+	{
+		unsigned char *out_pointer = &framebuffer[screen_texture.surface.pitch * 4 * y];
+
+		for (size_t x = 0; x < fake_framebuffer_width; ++x)
+		{
+			*out_pointer++ = *in_pointer++;
+			*out_pointer++ = *in_pointer++;
+			*out_pointer++ = *in_pointer++;
+			*out_pointer++ = 0;
+		}
+	}
+
+	GX2RUnlockSurfaceEx(&screen_texture.surface, 0, (GX2RResourceFlags)0);
+
+	WHBGfxBeginRender();
+
+	// Draw to the TV
+	WHBGfxBeginRenderTV();
+	GX2SetViewport(tv_viewport.x, tv_viewport.y, tv_viewport.width, tv_viewport.height, 0.0f, 1.0f);
+	WHBGfxClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	GX2SetFetchShader(&shader_group.fetchShader);
+	GX2SetVertexShader(shader_group.vertexShader);
+	GX2SetPixelShader(shader_group.pixelShader);
+	GX2DrawEx(GX2_PRIMITIVE_MODE_QUADS, 4, 0, 1);
+	WHBGfxFinishRenderTV();
+
+	// Draw to the gamepad
+	WHBGfxBeginRenderDRC();
+	GX2SetViewport(drc_viewport.x, drc_viewport.y, drc_viewport.width, drc_viewport.height, 0.0f, 1.0f);
+	WHBGfxClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	GX2SetFetchShader(&shader_group.fetchShader);
+	GX2SetVertexShader(shader_group.vertexShader);
+	GX2SetPixelShader(shader_group.pixelShader);
+	GX2DrawEx(GX2_PRIMITIVE_MODE_QUADS, 4, 0, 1);
+	WHBGfxFinishRenderDRC();
+
+	WHBGfxFinishRender();
+}
+
+void WindowBackend_Software_HandleWindowResize(unsigned int width, unsigned int height)
+{
+	(void)width;
+	(void)height;
+
+	// The window doesn't resize on the Wii U
+}