yterm

commit e4d90a1128671b6087ec8accf1a706a7914cf5c8

Author: mesyeti <mesyeti@mesyeti.uk>

wip broken software backend

 Makefile | 2 
 source/app.c | 14 +-
 source/app.h | 4 
 source/backend.c | 40 ++++--
 source/backend.h | 54 ++------
 source/backends/glLegacy.c | 231 +++++++++------------------------------
 source/backends/glLegacy.h | 29 ----
 source/backends/soft.c | 196 +++++++++++++++++++++++++++++++++
 source/backends/soft.h | 6 +
 source/backends/stub.c | 139 +++--------------------
 source/backends/stub.h | 4 
 source/bitmapFont.c | 38 +++---
 source/bitmapFont.h | 1 
 source/font.c | 30 +++++
 source/main.c | 28 ++++
 source/screen.c | 44 +++---
 source/screen.h | 9 
 source/text.c | 55 ---------
  | 1 


diff --git a/Makefile b/Makefile
index b89644fb3d2fbe3fefc9f998df99f69022c19bd7..06b43e87ef90af4e127b7a56d2987c0f2369a0b9 100644
--- a/Makefile
+++ b/Makefile
@@ -25,7 +25,7 @@
 override CFLAGS += -std=c99 -Wall -Wextra -Wuninitialized -Wundef -pedantic -Ilib -Werror=return-type
 override LDLIBS += -lm
 
-override CPPFLAGS += -DYT_BACKEND_GL_11
+#override CPPFLAGS += -DYT_BACKEND_GL_11
 override CPPFLAGS += -DYT_WINDOW_SDL2 -DYT_INPUT_SDL2 -DYT_EVENT_SDL2
 override CPPFLAGS += -DSDL_MAIN_HANDLED -D_POSIX_C_SOURCE=199309L
 




diff --git a/source/app.c b/source/app.c
index eb3fe559b5ed833ad311535130f65520577af76f..10882cadef6bfebe8c77d8f33699e36c5b7d7c6a 100644
--- a/source/app.c
+++ b/source/app.c
@@ -15,6 +15,12 @@ 	Screen_Init();
 }
 
 void App_Update(void) {
+	static uint64_t oldFrameTime = 0;
+
+	uint64_t newFrameTime  = Platform_GetTime();
+	uint64_t frameTimeDiff = newFrameTime - oldFrameTime;
+	app.delta              = frameTimeDiff / 1000000.0f;
+
 	Event e;
 
 	while (Event_Poll(&e)) {
@@ -24,12 +30,8 @@ 			default:         continue;
 		}
 	}
 
-	Backend_Begin();
-	Backend_Clear(0, 0, 0);
-
-	Screen_Render();
-
-	Backend_FinishRender();
+	Backend_Render();
+	oldFrameTime = newFrameTime;
 }
 
 void App_Free(void) {




diff --git a/source/app.h b/source/app.h
index 7470181ff74045295309cd2f6a01a57d15948304..28816ffff0c2827cd98044db089f436ef1aa6c8a 100644
--- a/source/app.h
+++ b/source/app.h
@@ -3,9 +3,11 @@ #define YT_APP_H
 
 #include "video.h"
 #include "common.h"
+#include "platform.h"
 
 typedef struct {
-	bool running;
+	bool  running;
+	float delta;
 } App;
 
 extern App app;




diff --git a/source/backend.c b/source/backend.c
index dc71c0b9a001274147f77bcf3656a61eed1c1f0f..76f3f9d00ff9be9198b0eff97fff14e628709d0c 100644
--- a/source/backend.c
+++ b/source/backend.c
@@ -1,21 +1,35 @@
 #include "backend.h"
 
-BackendOptions backendOptions = {
-	.vsync = true,
-	.name  = "gl1"
-};
+static Backend backend;
 
-void Backend_VLine(int x, int y, int thick, int len, Colour colour) {
-	Backend_RenderRect((Rect) {x, y, thick, len}, colour);
+void Backend_Use(Backend p_backend) {
+	backend = p_backend;
 }
 
-void Backend_HLine(int x, int y, int thick, int len, Colour colour) {
-	Backend_RenderRect((Rect) {x, y, len, thick}, colour);
+void Backend_Init(bool beforeWindow) {
+	backend.init(beforeWindow);
 }
 
-void Backend_RenderRectOL(Rect rect, Colour colour) {
-	Backend_HLine(rect.x, rect.y, 1, rect.w, colour);
-	Backend_VLine(rect.x, rect.y, 1, rect.h, colour);
-	Backend_HLine(rect.x, rect.y + rect.h - 1, 1, rect.w, colour);
-	Backend_VLine(rect.x + rect.w - 1, rect.y, 1, rect.h, colour);
+void Backend_Free(void) {
+	backend.free();
+}
+
+Texture* Backend_LoadTexture(uint8_t* data, int w, int h, int aW, int aH, int ch) {
+	return backend.loadTexture(data, w, h, aW, aH, ch);
+}
+
+void Backend_FreeTexture(Texture* texture) {
+	backend.freeTexture(texture);
+}
+
+Vec2 Backend_GetTextureSize(Texture* texture) {
+	return backend.getTextureSize(texture);
+}
+
+void Backend_OnWindowResize(void) {
+	backend.onWindowResize();
+}
+
+void Backend_Render(void) {
+	backend.render();
 }




diff --git a/source/backend.h b/source/backend.h
index 20e886e2cafdfb920f3f74c5211678f0f799e2d9..fc69e3fce90219a92a5c6be18ab26ae3d6193460 100644
--- a/source/backend.h
+++ b/source/backend.h
@@ -1,16 +1,6 @@
 #ifndef YT_BACKEND_H
 #define YT_BACKEND_H
 
-// defined:
-//   (struct) Texture
-#if defined(YT_BACKEND_GL_11)
-	#include "backends/glLegacy.h"
-#elif defined(YT_BACKEND_STUB)
-	#include "backends/stub.h"
-#else
-	#error "No backend"
-#endif
-
 #include "types.h"
 #include "video.h"
 #include "window.h"
@@ -21,45 +11,29 @@ 	bool enabled;
 } BackendViewport;
 
 typedef struct {
-	bool vsync;
-	char name[20];
-} BackendOptions;
-
-extern BackendOptions backendOptions;
-
-typedef struct {
 	bool   doTint;
 	Colour tint;
 } TextureRenderOpt;
 
-// implemented per backend
+typedef void Texture;
+
+typedef struct {
+	void     (*init)(bool beforeWindow);
+	void     (*free)(void);
+	Texture* (*loadTexture)(uint8_t* data, int w, int h, int aW, int aH, int ch);
+	void     (*freeTexture)(Texture* texture);
+	Vec2     (*getTextureSize)(Texture* texture);
+	void     (*onWindowResize)(void);
+	void     (*render)(void);
+} Backend;
+
+void     Backend_Use(Backend p_backend);
 void     Backend_Init(bool beforeWindow);
 void     Backend_Free(void);
-void     Backend_SetTarget(Window* window);
 Texture* Backend_LoadTexture(uint8_t* data, int w, int h, int aW, int aH, int ch);
 void     Backend_FreeTexture(Texture* texture);
 Vec2     Backend_GetTextureSize(Texture* texture);
 void     Backend_OnWindowResize(void);
-
-void Backend_DrawTexture(
-	Texture* texture, TextureRenderOpt* p_opt, Rect* p_src, Rect* p_dest
-);
-
-void Backend_Begin(void);
-void Backend_Clear(uint8_t r, uint8_t g, uint8_t b);
-void Backend_SetViewport(int x, int y, int w, int h);
-void Backend_EnableViewport(bool enable);
-void Backend_RenderRect(Rect rect, Colour colour);
-void Backend_RenderLine(Vec2 a, Vec2 b, Colour colour);
-void Backend_EnableAlpha(bool enable);
-void Backend_FinishRender(void);
-
-BackendViewport Backend_SaveViewport(void);
-void            Backend_RestoreViewport(BackendViewport viewport);
-
-// portable
-void Backend_HLine(int x, int y, int thick, int len, Colour colour);
-void Backend_VLine(int x, int y, int thick, int len, Colour colour);
-void Backend_RenderRectOL(Rect rect, Colour colour);
+void     Backend_Render(void);
 
 #endif




diff --git a/source/backends/glLegacy.c b/source/backends/glLegacy.c
index a64b18c47af50ed6319adbdf572b5dc545dd5963..67ccb46c3fefa8f4b716368a9a19a98a37d4cbbc 100644
--- a/source/backends/glLegacy.c
+++ b/source/backends/glLegacy.c
@@ -7,9 +7,37 @@
 #ifdef YT_BACKEND_GL_11
 #include "glLegacy.h"
 
+#define GL_GLEXT_PROTOTYPES
+#ifdef YT_BACKEND_GL_11
+	#ifdef PLATFORM_OSX
+		#include <OpenGL/gl.h>
+		#include <OpenGL/glext.h>
+	#else
+		#include <GL/gl.h>
+		#include <GL/glext.h>
+	#endif
+#endif
+
+#ifndef USE_KHR_DEBUG
+    #if !defined(NDEBUG) && defined(GL_KHR_debug) && GL_KHR_debug
+        #define USE_KHR_DEBUG 1
+    #else
+        #define USE_KHR_DEBUG 0
+    #endif
+#endif
+
 #ifndef GL_CLAMP_TO_EDGE_SGIS
 	#define GL_CLAMP_TO_EDGE_SGIS GL_CLAMP_TO_EDGE
 #endif
+
+typedef struct {
+	bool   used;
+	GLuint name;
+	int    width;
+	int    height;
+	int    actualWidth;
+	int    actualHeight;
+} BTexture;
 
 static void GL_Error(GLenum error, const char* file, int line) {
 	const char* errorStr;
@@ -53,12 +81,12 @@
 	BackendViewport viewport;
 
 	SDL_GLContext ctx;
-	Texture       textures[32];
+	BTexture      textures[32];
 } State;
 
 static State state;
 
-void Backend_Init(bool beforeWindow) {
+static void Init(bool beforeWindow) {
 	if (beforeWindow) {
 		int major = 1;
 		int minor = 1;
@@ -141,22 +169,12 @@ 	glEnable(GL_TEXTURE_2D);
 	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 }
 
-void Backend_Free(void) {
+static void Free(void) {
 	SDL_GL_DeleteContext(state.ctx);
 	// Model_Free(&state.model);
 }
 
-void Backend_SetTarget(Window* window) {
-	(void) window; // unimplemented
-
-	static bool warning = false;
-	if (!warning) {
-		warning = true;
-		Warn("Backend_SetTarget unsupported");
-	}
-}
-
-Texture* Backend_LoadTexture(uint8_t* data, int w, int h, int aW, int aH, int ch) {
+static Texture* LoadTexture(uint8_t* data, int w, int h, int aW, int aH, int ch) {
 	GLuint tex;
 	GL(glGenTextures(1, &tex));
 	GL(glBindTexture(GL_TEXTURE_2D, tex));
@@ -173,7 +191,7 @@ 	free(data);
 	GL(glBindTexture(GL_TEXTURE_2D, 0));
 
 	// now put this texture in the texture array
-	for (size_t i = 0; i < sizeof(state.textures) / sizeof(Texture); ++ i) {
+	for (size_t i = 0; i < sizeof(state.textures) / sizeof(BTexture); ++ i) {
 		if (!state.textures[i].used) {
 			state.textures[i].used         = true;
 			state.textures[i].name         = tex;
@@ -181,7 +199,7 @@ 			state.textures[i].width        = w;
 			state.textures[i].height       = h;
 			state.textures[i].actualWidth  = aW;
 			state.textures[i].actualHeight = aH;
-			return &state.textures[i];
+			return (Texture*) &state.textures[i];
 		}
 	}
 
@@ -189,175 +207,36 @@ 	Error("No more room for textures");
 	return NULL;
 }
 
-void Backend_FreeTexture(Texture* texture) {
-	GL(glDeleteTextures(1, &texture->name));
-	texture->used = false;
-}
+static void FreeTexture(Texture* texture) {
+	BTexture* tex = (BTexture*) texture;
 
-Vec2 Backend_GetTextureSize(Texture* texture) {
-	return (Vec2) {texture->width, texture->height};
+	GL(glDeleteTextures(1, &tex->name));
+	tex->used = false;
 }
 
-void Backend_DrawTexture(
-	Texture* texture, TextureRenderOpt* p_opt, Rect* p_src, Rect* p_dest
-) {
-	TextureRenderOpt opt;
-	if (p_opt == NULL) {
-		opt.doTint = false;
-	}
-	else {
-		opt = *p_opt;
-	}
-
-	Rect src;
-	if (p_src == NULL) {
-		src = (Rect) {0, 0, texture->width, texture->height};
-	}
-	else {
-		src = *p_src;
-	}
-
-	Rect dest;
-	if (p_dest == NULL) {
-		dest = (Rect) {0, 0, video.windows[0].width, video.windows[0].height};
-	}
-	else {
-		dest = *p_dest;
-	}
-
-	GL(glBindTexture(GL_TEXTURE_2D, texture->name));
-
-	glBegin(GL_TRIANGLE_FAN);
-
-	if (opt.doTint) {
-		glColor3ub(opt.tint.r, opt.tint.g, opt.tint.b);
-	}
-	else {
-		glColor3ub(255, 255, 255);
-	}
-
-	glTexCoord2f(
-		((float) src.x) / ((float) texture->actualWidth),
-		((float) src.y) / ((float) texture->actualHeight)
-	);
-	glVertex2i(dest.x, dest.y);
-	glTexCoord2f(
-		((float) (src.x + src.w)) / ((float) texture->actualWidth),
-		((float) src.y) / ((float) texture->actualHeight)
-	);
-	glVertex2i(dest.x + dest.w, dest.y);
-	glTexCoord2f(
-		((float) (src.x + src.w)) / ((float) texture->actualWidth),
-		((float) (src.y + src.h)) / ((float) texture->actualHeight)
-	);
-	glVertex2i(dest.x + dest.w, dest.y + dest.h);
-	glTexCoord2f(
-		((float) src.x) / ((float) texture->actualWidth),
-		((float) (src.y + src.h)) / ((float) texture->actualHeight)
-	);
-	glVertex2i(dest.x, dest.y + dest.h);
-	GL(glEnd());
-
-	GL(glBindTexture(GL_TEXTURE_2D, 0));
+static Vec2 GetTextureSize(Texture* texture) {
+	BTexture* tex = (BTexture*) texture;
+	return (Vec2) {tex->width, tex->height};
 }
 
-void Backend_Begin(void) {
-	glViewport(0, 0, video.windows[0].aWidth, video.windows[0].aHeight);
-	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
-	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-
-	glDisable(GL_DEPTH_TEST);
-	glDisable(GL_CULL_FACE);
-	glEnable(GL_BLEND);
-	GL(glMatrixMode(GL_MODELVIEW));
-	GL(glLoadIdentity());
-	GL(glMatrixMode(GL_PROJECTION));
-	GL(glLoadIdentity());
-
-	float floatW = (float) video.windows[0].width;
-	float floatH = (float) video.windows[0].height;
-
-	GL(glOrtho(0.0, floatW, floatH, 0.0, -1.0, 1.0));
-}
-
-void Backend_Clear(uint8_t r, uint8_t g, uint8_t b) {
-	glClearColor(((float) r) / 256.0, ((float) g) / 256.0, ((float) b) / 256.0, 1.0f);
-	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+static void OnWindowResize(void) {
+	
 }
 
-void Backend_SetViewport(int x, int y, int w, int h) {
-	state.viewport.rect = (Rect) {x, y, w, h};
-	GL(glScissor(x, video.windows[0].height - 1 - (y + h), w, h));
+static void Render(void) {
+	
 }
 
-void Backend_EnableViewport(bool enable) {
-	state.viewport.enabled = enable;
-
-	if (enable) {
-		GL(glEnable(GL_SCISSOR_TEST));
-	}
-	else {
-		GL(glDisable(GL_SCISSOR_TEST));
-	}
-}
-
-void Backend_RenderRect(Rect rect, Colour colour) {
-	GL(glDisable(GL_TEXTURE_2D));
-	glBegin(GL_TRIANGLE_FAN);
-
-	if (state.alpha) {
-		glColor4ub(colour.r, colour.g, colour.b, colour.a);
-	}
-	else {
-		glColor3ub(colour.r, colour.g, colour.b);
-	}
-
-	glVertex2i(rect.x,          rect.y);
-	glVertex2i(rect.x + rect.w, rect.y);
-	glVertex2i(rect.x + rect.w, rect.y + rect.h);
-	glVertex2i(rect.x,          rect.y + rect.h);
-	GL(glEnd());
-	GL(glEnable(GL_TEXTURE_2D));
-}
-
-void Backend_RenderLine(Vec2 a, Vec2 b, Colour colour) {
-	GL(glDisable(GL_TEXTURE_2D));
-	glBegin(GL_LINES);
-	glColor3ub(colour.r, colour.g, colour.b);
-	glVertex2i(a.x, a.y);
-	glVertex2i(b.x, b.y);
-	GL(glEnd());
-	GL(glEnable(GL_TEXTURE_2D));
-}
-
-void Backend_EnableAlpha(bool enable) {
-	state.alpha = enable;
-
-	if (enable) {
-		GL(glEnable(GL_BLEND));
-	}
-	else {
-		GL(glDisable(GL_BLEND));
-	}
-}
-
-void Backend_FinishRender(void) {
-	GL(glFinish());
-	SDL_GL_SwapWindow(video.windows[0].window);
-}
-
-BackendViewport Backend_SaveViewport(void) {
-	return state.viewport;
-}
-
-void Backend_RestoreViewport(BackendViewport viewport) {
-	state.viewport = viewport;
-
-	Backend_SetViewport(
-		state.viewport.rect.x, state.viewport.rect.y,
-		state.viewport.rect.w, state.viewport.rect.h
-	);
-	Backend_EnableViewport(state.viewport.enabled);
+Backend Backend_GLLegacy(void) {
+	return (Backend) {
+		.init           = &Init,
+		.free           = &Free,
+		.loadTexture    = &LoadTexture,
+		.freeTexture    = &FreeTexture,
+		.getTextureSize = &GetTextureSize,
+		.onWindowResize = &OnWindowResize,
+		.render         = &Render
+	};
 }
 
 #endif




diff --git a/source/backends/glLegacy.h b/source/backends/glLegacy.h
index 0e5275fc0b61e9cb26cf8c92ed3e17c4edeec0c8..e89aba4d67784b718526dc50357953ec18ca57f6 100644
--- a/source/backends/glLegacy.h
+++ b/source/backends/glLegacy.h
@@ -2,33 +2,8 @@ #ifndef YT_BACKENDS_GL_LEGACY_H
 #define YT_BACKENDS_GL_LEGACY_H
 
 #include "../common.h"
+#include "../backend.h"
 
-#define GL_GLEXT_PROTOTYPES
-#ifdef YT_BACKEND_GL_11
-	#ifdef PLATFORM_OSX
-		#include <OpenGL/gl.h>
-		#include <OpenGL/glext.h>
-	#else
-		#include <GL/gl.h>
-		#include <GL/glext.h>
-	#endif
-#endif
-
-#ifndef USE_KHR_DEBUG
-    #if !defined(NDEBUG) && defined(GL_KHR_debug) && GL_KHR_debug
-        #define USE_KHR_DEBUG 1
-    #else
-        #define USE_KHR_DEBUG 0
-    #endif
-#endif
-
-typedef struct {
-	bool   used;
-	GLuint name;
-	int    width;
-	int    height;
-	int    actualWidth;
-	int    actualHeight;
-} Texture;
+Backend Backend_GLLegacy(void);
 
 #endif




diff --git a/source/backends/soft.c b/source/backends/soft.c
new file mode 100644
index 0000000000000000000000000000000000000000..bb7573138c565991bc5596f62c30381e6ea07ab2
--- /dev/null
+++ b/source/backends/soft.c
@@ -0,0 +1,196 @@
+#include <stdio.h>
+#include "../log.h"
+#include "../mem.h"
+#include "../font.h"
+#include "../theme.h"
+#include "../screen.h"
+#include "../backend.h"
+
+#include "stub.h"
+
+typedef struct {
+	bool      used;
+	int       width;
+	int       height;
+	int       aWidth;
+	int       aHeight;
+	uint32_t* pixels;
+} BTexture;
+
+typedef struct {
+	BTexture  textures[32];
+	uint32_t* buffer;
+	int       bufferW;
+	int       bufferH;
+
+	// SDL stuff
+	SDL_Renderer* renderer;
+	SDL_Texture*  texture;
+} State;
+
+static State state;
+
+static void DrawPixel(int x, int y, uint32_t colour) {
+	if ((x < 0) || (y < 0) || (y >= state.bufferH) || (x >= state.bufferW)) return;
+
+	state.buffer[(y * state.bufferW) + x] = colour;
+}
+
+static void Init(bool beforeWindow) {
+	if (beforeWindow) return;
+
+	state.renderer = SDL_CreateRenderer(
+		video.windows[0].window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC
+	);
+
+	if (!state.renderer) {
+		Error("Failed to init renderer: %s", SDL_GetError());
+	}
+
+	state.texture = SDL_CreateTexture(
+		state.renderer, SDL_PIXELFORMAT_ABGR8888,
+		SDL_TEXTUREACCESS_STREAMING, video.windows[0].width, video.windows[0].height
+	);
+
+	if (state.texture == NULL) {
+		fprintf(stderr, "Failed to create texture: %s\n", SDL_GetError());
+		exit(1);
+	}
+
+	for (size_t i = 0; i < sizeof(state.textures) / sizeof(BTexture); ++ i) {
+		state.textures[i].used = false;
+	}
+
+	state.buffer  = SafeMalloc(video.windows[0].width * video.windows[0].height * sizeof(uint32_t));
+	state.bufferW = video.windows[0].width;
+	state.bufferH = video.windows[0].height;
+}
+
+static void Free(void) {
+	for (size_t i = 0; i < sizeof(state.textures) / sizeof(BTexture); ++ i) {
+		if (state.textures[i].used) {
+			free(state.textures[i].pixels);
+		}
+	}
+
+	free(state.buffer);
+
+	SDL_DestroyTexture(state.texture);
+	SDL_DestroyRenderer(state.renderer);
+}
+
+static uint32_t ColourToPixel(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
+	return r | (g << 8) | (b << 16) | (a << 24);
+	//return r + (rand() % 20) | (g + (rand() % 20) << 8) | (b + (rand() % 20) << 16) | (a << 24);
+}
+
+static Texture* LoadTexture(uint8_t* data, int w, int h, int aW, int aH, int ch) {
+	uint32_t* pixels = SafeMalloc(w * h * sizeof(uint32_t));
+
+	for (int i = 0; i < w * h; ++ i, data += ch) {
+		pixels[i] = ColourToPixel(data[0], data[1], data[2], ch == 4? data[3] : 255);
+	}
+
+	for (size_t i = 0; i < sizeof(state.textures) / sizeof(BTexture); ++ i) {
+		if (!state.textures[i].used) {
+			state.textures[i].used    = true;
+			state.textures[i].width   = w;
+			state.textures[i].height  = h;
+			state.textures[i].aWidth  = aW;
+			state.textures[i].aHeight = aH;
+			state.textures[i].pixels  = pixels;
+			return (Texture*) &state.textures[i];
+		}
+	}
+
+	Error("No more room for textures");
+	return NULL;
+}
+
+static void FreeTexture(Texture* texture) {
+	BTexture* tex = (BTexture*) texture;
+	free(tex->pixels);
+	tex->used = false;
+}
+
+static Vec2 GetTextureSize(Texture* texture) {
+	BTexture* tex = (BTexture*) texture;
+	return (Vec2) {tex->width, tex->height};
+}
+
+static void OnWindowResize(void) {
+	
+}
+
+static void FillRect(int x, int y, int w, int h, Colour colour) {
+	for (int iy = 0; iy < h; ++ iy) {
+		for (int ix = 0; ix < w; ++ ix) {
+			DrawPixel(ix + x, iy + y, ColourToPixel(colour.r, colour.g, colour.b, colour.a));
+		}
+	}
+}
+
+static void Blit(BTexture* texture, int x, int y, Rect src, Colour tint) {
+	(void) tint; // TODO
+
+	for (int iy = 0; iy < src.h; ++ iy) {
+		for (int ix = 0; ix < src.w; ++ ix) {
+			if ((src.x + ix >= texture->width) || (src.y + iy >= texture->height)) continue;
+
+			int sx = ix + src.x;
+			int sy = iy + src.y;
+
+			// DrawPixel(ix + x, iy + y, texture->pixels[(sy * texture->width) + sx]);
+			DrawPixel(ix + x, iy + y, texture->pixels[(sy * texture->width) + sx]);
+		}
+	}
+}
+
+static void Render(void) {
+	for (int i = 0; i < state.bufferW * state.bufferH; ++ i) {
+		state.buffer[i] = ColourToPixel(theme.bg.r, theme.bg.g, theme.bg.b, 255);
+	}
+
+	int cw;
+	int ch;
+	Font_CharSize(&screen.font, &cw, &ch);
+
+	for (int y = 0; y < screen.buffer.height; ++ y) {
+		for (int x = 0; x < screen.buffer.width; ++ x) {
+			int cx = x * cw;
+			int cy = y * ch;
+
+			BufCell* cell = Buffer_Get(&screen.buffer, x, y);
+
+			ColourRGB fg = Screen_GetColour(true,  cell->fgMode, cell->fg);
+			ColourRGB bg = Screen_GetColour(false, cell->bgMode, cell->bg);
+
+			FillRect(cx, cy, cw, ch, (Colour) {bg.r, bg.g, bg.b, 255});
+
+			Rect src;
+			src.x = ((int) (ch % 16)) * cw;
+			src.y = ((int) (ch / 16)) * ch;
+			src.w = cw;
+			src.h = ch;
+
+			Blit((BTexture*) screen.font.v.bm.texture, cx, cy, src, (Colour) {fg.r, fg.g, fg.b, 255});
+		}
+	}
+
+	SDL_RenderClear(state.renderer);
+	SDL_UpdateTexture(state.texture, NULL, state.buffer, state.bufferW * 4);
+	SDL_RenderCopy(state.renderer, state.texture, NULL, NULL);
+	SDL_RenderPresent(state.renderer);
+}
+
+Backend Backend_Soft(void) {
+	return (Backend) {
+		.init           = &Init,
+		.free           = &Free,
+		.loadTexture    = &LoadTexture,
+		.freeTexture    = &FreeTexture,
+		.getTextureSize = &GetTextureSize,
+		.onWindowResize = &OnWindowResize,
+		.render         = &Render
+	};
+}




diff --git a/source/backends/soft.h b/source/backends/soft.h
new file mode 100644
index 0000000000000000000000000000000000000000..2d75ecb7b31966491cedf3e62f659bac45715f06
--- /dev/null
+++ b/source/backends/soft.h
@@ -0,0 +1,6 @@
+#ifndef YT_BACKEND_SOFT_H
+#define YT_BACKEND_SOFT_H
+
+Backend Backend_Soft(void);
+
+#endif




diff --git a/source/backends/stub.c b/source/backends/stub.c
index cef9bcf20d6656bc713220bb20785b174ff581dd..6d7dfe19e8f2b7b5174ed71d944f0d5eec673443 100644
--- a/source/backends/stub.c
+++ b/source/backends/stub.c
@@ -1,143 +1,50 @@
 #include "../backend.h"
 
-#ifdef YT_BACKEND_STUB
 #include "stub.h"
 
-void Backend_Init(bool beforeWindow) {
-	(void) beforeWindow;
-}
+static void Init(bool beforeWindow) {
 
-void Backend_Free(void) {
-	
 }
 
-void Backend_SetTarget(Window* window) {
-	(void) window;
+static void Free(void) {
+
 }
 
-Texture* Backend_LoadTexture(uint8_t* data, int width, int height, int ch) {
+static Texture* LoadTexture(uint8_t* data, int w, int h, int aW, int aH, int ch) {
 	(void) data;
-	(void) width;
-	(void) height;
+	(void) w;
+	(void) h;
+	(void) aW;
+	(void) aH;
 	(void) ch;
+
 	return NULL;
 }
 
-void Backend_FreeTexture(Texture* texture) {
+static void FreeTexture(Texture* texture) {
 	(void) texture;
 }
 
-Vec2 Backend_GetTextureSize(Texture* texture) {
-	(void) texture;
+static Vec2 GetTextureSize(Texture* texture) {
 	return (Vec2) {0, 0};
 }
 
-void Backend_RenderScene(void) {
+static void OnWindowResize(void) {
 	
 }
 
-void Backend_OnMapFree(void) {
-	
-}
-
-void Backend_OnWindowResize(void) {
+static void Render(void) {
 	
 }
 
-void Backend_RenderModel(Model* model, ModelRenderOpt* opt) {
-	(void) model;
-	(void) opt;
-}
-
-void Backend_UseHoldModel(const char* path, ModelRenderOpt opt) {
-	(void) path;
-	(void) opt;
+Backend Backend_Stub(void) {
+	return (Backend) {
+		.init           = &Init,
+		.free           = &Free,
+		.loadTexture    = &LoadTexture,
+		.freeTexture    = &FreeTexture,
+		.getTextureSize = &GetTextureSize,
+		.onWindowResize = &OnWindowResize,
+		.render         = &Render
+	};
 }
-
-void Backend_DrawTexture(
-	Texture* texture, TextureRenderOpt* p_opt, Rect* p_src, Rect* p_dest
-) {
-	(void) texture;
-
-	TextureRenderOpt opt;
-	if (p_opt == NULL) {
-		opt.doTint = false;
-	}
-	else {
-		opt = *p_opt;
-	}
-
-	Rect src;
-	if (p_src == NULL) {
-		src = (Rect) {0, 0, texture->width, texture->height};
-	}
-	else {
-		src = *p_src;
-	}
-
-	Rect dest;
-	if (p_dest == NULL) {
-		dest = (Rect) {0, 0, video.width, video.height};
-	}
-	else {
-		dest = *p_dest;
-	}
-}
-
-void Backend_Begin(void) {
-	
-}
-
-void Backend_Begin2D(bool originTop) {
-	(void) originTop;
-}
-
-void Backend_Clear(uint8_t r, uint8_t g, uint8_t b) {
-	(void) r;
-	(void) g;
-	(void) b;
-}
-
-void Backend_SetViewport(int x, int y, int w, int h) {
-	(void) x;
-	(void) y;
-	(void) w;
-	(void) h;
-}
-
-void Backend_EnableViewport(bool enable) {
-	(void) enable;
-}
-
-void Backend_RenderRect(Rect rect, Colour colour) {
-	(void) rect;
-	(void) colour;
-}
-
-void Backend_RenderLine(Vec2 a, Vec2 b, Colour colour) {
-	(void) a;
-	(void) b;
-	(void) colour;
-}
-
-void Backend_EnableAlpha(bool enable) {
-	(void) enable;
-}
-
-void Backend_InitSkybox(void) {
-	
-}
-
-void Backend_FinishRender(void) {
-	
-}
-
-BackendViewport Backend_SaveViewport(void) {
-	return (BackendViewport) {};
-}
-
-void Backend_RestoreViewport(BackendViewport viewport) {
-	(void) viewport;
-}
-
-#endif




diff --git a/source/backends/stub.h b/source/backends/stub.h
index 77d457fc539a168878dc66b12a87fa2dce5163da..02e23172f70774b75d485e8102649e261a911c2a 100644
--- a/source/backends/stub.h
+++ b/source/backends/stub.h
@@ -1,8 +1,6 @@
 #ifndef YT_BACKEND_STUB_H
 #define YT_BACKEND_STUB_H
 
-typedef struct {
-	int empty;
-} Texture;
+Backend Backend_Stub(void);
 
 #endif




diff --git a/source/bitmapFont.c b/source/bitmapFont.c
index c222e275de8aadebb696cf9ca79fea5104866713..ea110c77efee3033816b6bd29b220b2651c29b6c 100644
--- a/source/bitmapFont.c
+++ b/source/bitmapFont.c
@@ -26,22 +26,22 @@ void BMFont_Free(BMFont* font) {
 	Backend_FreeTexture(font->texture);
 }
 
-void BMFont_RenderChar(BMFont* font, char ch, int x, int y, ColourRGB colour) {
-	Rect dest;
-	dest.x = x;
-	dest.y = y;
-	dest.w = font->charWidth;
-	dest.h = font->charHeight;
-
-	Rect src;
-	src.x = ((int) (ch % 16)) * font->charWidth;
-	src.y = ((int) (ch / 16)) * font->charHeight;
-	src.w = font->charWidth;
-	src.h = font->charHeight;
-
-	TextureRenderOpt opt;
-	opt.doTint = true;
-	opt.tint   = (Colour) {colour.r, colour.g, colour.b, 255};
-
-	Backend_DrawTexture(font->texture, &opt, &src, &dest);
-}
+// void BMFont_RenderChar(BMFont* font, char ch, int x, int y, ColourRGB colour) {
+// 	Rect dest;
+// 	dest.x = x;
+// 	dest.y = y;
+// 	dest.w = font->charWidth;
+// 	dest.h = font->charHeight;
+// 
+// 	Rect src;
+// 	src.x = ((int) (ch % 16)) * font->charWidth;
+// 	src.y = ((int) (ch / 16)) * font->charHeight;
+// 	src.w = font->charWidth;
+// 	src.h = font->charHeight;
+// 
+// 	TextureRenderOpt opt;
+// 	opt.doTint = true;
+// 	opt.tint   = (Colour) {colour.r, colour.g, colour.b, 255};
+// 
+// 	Backend_DrawTexture(font->texture, &opt, &src, &dest);
+// }




diff --git a/source/bitmapFont.h b/source/bitmapFont.h
index c0b408155d3fdc775cf08a7e2031123a7342c696..b32ebfef0c13cc3345891643d7a9a39ab1ecbc9d 100644
--- a/source/bitmapFont.h
+++ b/source/bitmapFont.h
@@ -12,6 +12,5 @@ } BMFont;
 
 BMFont BMFont_Load(const char* path, bool* success);
 void   BMFont_Free(BMFont* font);
-void   BMFont_RenderChar(BMFont* font, char ch, int x, int y, ColourRGB colour);
 
 #endif




diff --git a/source/font.c b/source/font.c
new file mode 100644
index 0000000000000000000000000000000000000000..ea4649d176ff5fea3620fc215e8769607bf62e92
--- /dev/null
+++ b/source/font.c
@@ -0,0 +1,30 @@
+#include <assert.h>
+#include "font.h"
+
+Font Font_FromBMFont(BMFont bm) {
+	Font ret;
+	ret.type = FONT_BITMAP;
+	ret.v.bm = bm;
+	return ret;
+}
+
+void Font_Free(Font* font) {
+	switch (font->type) {
+		case FONT_BITMAP: {
+			BMFont_Free(&font->v.bm);
+			break;
+		}
+		default: assert(0);
+	}
+}
+
+void Font_CharSize(Font* font, int* w, int* h) {
+	switch (font->type) {
+		case FONT_BITMAP: {
+			if (w) *w = font->v.bm.charWidth;
+			if (h) *h = font->v.bm.charHeight;
+			break;
+		}
+		default: assert(0);
+	}
+}




diff --git a/source/font.h b/source/font.h
new file mode 100644
index 0000000000000000000000000000000000000000..0b85f5d75bb9adbc92f5899febbedb421f8da29a
--- /dev/null
+++ b/source/font.h
@@ -0,0 +1,24 @@
+#ifndef YT_TEXT_H
+#define YT_TEXT_H
+
+#include "char.h"
+#include "bitmapFont.h"
+
+enum {
+	FONT_BITMAP = 0,
+	FONT_TTF
+};
+
+typedef struct {
+	int type;
+
+	union {
+		BMFont bm;
+	} v;
+} Font;
+
+Font Font_FromBMFont(BMFont bm);
+void Font_Free(Font* font);
+void Font_CharSize(Font* font, int* w, int* h);
+
+#endif




diff --git a/source/main.c b/source/main.c
index aa9f267a558405d678b52d18aae41f062bdb5c06..00de404068ffb890046e9a822813284e647fea25 100644
--- a/source/main.c
+++ b/source/main.c
@@ -1,6 +1,34 @@
 #include "app.h"
+#include "log.h"
+#include "backend.h"
+
+#ifdef YT_BACKEND_GL_11
+	#include "backends/glLegacy.h"
+#endif
+
+#include "backends/soft.h"
+#include "backends/stub.h"
 
 int main(void) {
+	struct {
+		const char* name;
+		Backend     backend;
+	} backends[] = {
+		#ifdef YT_BACKEND_GL_11
+			{"gl1.1", Backend_GLLegacy()},
+		#endif
+		{"soft", Backend_Soft()},
+		{"stub", Backend_Stub()}
+	};
+
+	Log("Available backends:");
+	for (int i = 0; i < sizeof(backends) / sizeof(*backends); ++ i) {
+		Log("- %s", backends[i].name);
+	}
+	Log("Selecting '%s'", backends[0].name);
+
+	Backend_Use(backends[0].backend);
+
 	App_Init();
 
 	while (app.running) {




diff --git a/source/screen.c b/source/screen.c
index 74f2d1947ad82614ccc73e3d3cc8a54c112bc99c..91ff0e30648f3712ead9f7bf7521dd38c23617a7 100644
--- a/source/screen.c
+++ b/source/screen.c
@@ -32,7 +32,7 @@ 	Font_Free(&screen.font);
 	Buffer_Free(&screen.buffer);
 }
 
-static ColourRGB GetColour(bool fg, uint8_t mode, BufCol col) {
+ColourRGB Screen_GetColour(bool fg, uint8_t mode, BufCol col) {
 	switch (mode) {
 		case BUF_DEFAULT: return fg? theme.fg : theme.bg;
 		case BUF_16COL:   return theme.pal16[col.idx % 16];
@@ -44,25 +44,25 @@ 	assert(0);
 }
 
 void Screen_Render(void) {
-	int cw;
-	int ch;
-	Font_CharSize(&screen.font, &cw, &ch);
-
-	for (int y = 0; y < screen.buffer.height; ++ y) {
-		for (int x = 0; x < screen.buffer.width; ++ x) {
-			int cx = x * cw;
-			int cy = y * ch;
-
-			BufCell* cell = Buffer_Get(&screen.buffer, x, y);
-
-			ColourRGB fg = GetColour(true,  cell->fgMode, cell->fg);
-			ColourRGB bg = GetColour(false, cell->bgMode, cell->bg);
-
-			Rect   rect = {cx, cy, cw, ch};
-			Colour c    = (Colour) {bg.r, bg.g, bg.b, 255};
-			Backend_RenderRect(rect, c);
-
-			Font_RenderChar(&screen.font, cell->ch, cx, cy, fg);
-		}
-	}
+// 	int cw;
+// 	int ch;
+// 	Font_CharSize(&screen.font, &cw, &ch);
+// 
+// 	for (int y = 0; y < screen.buffer.height; ++ y) {
+// 		for (int x = 0; x < screen.buffer.width; ++ x) {
+// 			int cx = x * cw;
+// 			int cy = y * ch;
+// 
+// 			BufCell* cell = Buffer_Get(&screen.buffer, x, y);
+// 
+// 			ColourRGB fg = GetColour(true,  cell->fgMode, cell->fg);
+// 			ColourRGB bg = GetColour(false, cell->bgMode, cell->bg);
+// 
+// 			Rect   rect = {cx, cy, cw, ch};
+// 			Colour c    = (Colour) {bg.r, bg.g, bg.b, 255};
+// 			Backend_RenderRect(rect, c);
+// 
+// 			Font_RenderChar(&screen.font, cell->ch, cx, cy, fg);
+// 		}
+// 	}
 }




diff --git a/source/screen.h b/source/screen.h
index 5cbe75da0bd9da871c2c05fd275d4413012d6b6b..f7e08e93fc8d019cc90989be6556d2e6f58b0652 100644
--- a/source/screen.h
+++ b/source/screen.h
@@ -1,7 +1,7 @@
 #ifndef YT_SCREEN_H
 #define YT_SCREEN_H
 
-#include "text.h"
+#include "font.h"
 #include "buffer.h"
 
 typedef struct {
@@ -11,8 +11,9 @@ } Screen;
 
 extern Screen screen;
 
-void Screen_Init(void);
-void Screen_Free(void);
-void Screen_Render(void);
+void      Screen_Init(void);
+void      Screen_Free(void);
+ColourRGB Screen_GetColour(bool fg, uint8_t mode, BufCol col);
+void      Screen_Render(void);
 
 #endif




diff --git a/source/text.c b/source/text.c
deleted file mode 100644
index 2783f76001aef709349c90c1a73167b645ace6f1..0000000000000000000000000000000000000000
--- a/source/text.c
+++ /dev/null
@@ -1,55 +0,0 @@
-#include <assert.h>
-#include "text.h"
-
-Font Font_FromBMFont(BMFont bm) {
-	Font ret;
-	ret.type = FONT_BITMAP;
-	ret.v.bm = bm;
-	return ret;
-}
-
-void Font_Free(Font* font) {
-	switch (font->type) {
-		case FONT_BITMAP: {
-			BMFont_Free(&font->v.bm);
-			break;
-		}
-		default: assert(0);
-	}
-}
-
-void Font_CharSize(Font* font, int* w, int* h) {
-	switch (font->type) {
-		case FONT_BITMAP: {
-			if (w) *w = font->v.bm.charWidth;
-			if (h) *h = font->v.bm.charHeight;
-			break;
-		}
-		default: assert(0);
-	}
-}
-
-void Font_RenderChar(Font* font, UChar ch, int x, int y, ColourRGB colour) {
-	switch (font->type) {
-		case FONT_BITMAP: {
-			if (ch > 255) {
-				ch = '?';
-			}
-
-			BMFont_RenderChar(&font->v.bm, (char) ch, x, y, colour);
-			break;
-		}
-		default: assert(0);
-	}
-}
-
-// void Font_RenderString(Font* font, const char* str, int x, int y) {
-// 	int cw;
-// 	Font_CharSize(font, &cw, NULL);
-// 
-// 	while (*str) {
-// 		Font_RenderChar(font, *str, x, y);
-// 		++ str;
-// 		x += cw;
-// 	}
-// }




diff --git a/source/text.h b/source/text.h
deleted file mode 100644
index a26131ef8fd1751c9fc90b9339645d768dc98dd3..0000000000000000000000000000000000000000
--- a/source/text.h
+++ /dev/null
@@ -1,25 +0,0 @@
-#ifndef YT_TEXT_H
-#define YT_TEXT_H
-
-#include "char.h"
-#include "bitmapFont.h"
-
-enum {
-	FONT_BITMAP = 0,
-	FONT_TTF
-};
-
-typedef struct {
-	int type;
-
-	union {
-		BMFont bm;
-	} v;
-} Font;
-
-Font Font_FromBMFont(BMFont bm);
-void Font_Free(Font* font);
-void Font_CharSize(Font* font, int* w, int* h);
-void Font_RenderChar(Font* font, UChar ch, int x, int y, ColourRGB colour);
-
-#endif