diff --git a/gamehelpers.d b/gamehelpers.d
new file mode 100644
index 0000000..ecccc67
--- /dev/null
+++ b/gamehelpers.d
@@ -0,0 +1,220 @@
+// WORK IN PROGRESS
+
+/**
+	An add-on for simpledisplay.d, joystick.d, and simpleaudio.d
+	that includes helper functions for writing games (and perhaps
+	other multimedia programs).
+
+	Usage example:
+
+	---
+	class MyGame {
+		/// Called when it is time to redraw the frame
+		/// it will try for a particular FPS
+		final void drawFrame() {
+
+		}
+
+		final void update(Duration deltaTime) {
+
+		}
+
+		final void fillAudioBuffer(short[] buffer) {
+
+		}
+	}
+
+	void main() {
+		auto game = new MyGame();
+
+		runGame(game, maxRedrawRate, maxUpdateRate);
+	}
+	---
+
+	It provides an audio thread, input scaffold, and helper functions.
+
+
+	The MyGame handler is actually a template, so you don't have virtual
+	function indirection and not all functions are required. The interfaces
+	are just to help you get the signatures right, they don't work at
+	runtime.
+*/
+module arsd.gamehelpers;
+
+public import arsd.color;
+public import simpledisplay;
+
+import std.math;
+
+/// The max rates are given in executions per second
+/// Redraw will never be called unless there has been at least one update
+void runGame(T)(T game, int maxRedrawRate, int maxUpdateRate) {
+
+}
+
+/// Simple class for putting a TrueColorImage in as an OpenGL texture.
+///
+/// Doesn't do mipmapping btw.
+final class OpenGlTexture {
+	private uint _tex;
+	private int _width;
+	private int _height;
+	private float _texCoordWidth;
+	private float _texCoordHeight;
+
+	void bind() {
+		glBindTexture(GL_TEXTURE_2D, _tex);
+	}
+
+	// For easy 2d drawing of it
+	void draw(Point where, int width = 0, int height = 0, float rotation = 0.0, Color bg = Color.white) {
+		glPushMatrix();
+		glTranslatef(where.x, where.y, 0);
+		glRotatef(rotation, 0,0, 1);
+
+		if(width == 0)
+			width = this.originalImageWidth;
+		if(height == 0)
+			height = this.originalImageHeight;
+
+		glColor4f(cast(float)bg.r/255.0, cast(float)bg.g/255.0, cast(float)bg.b/255.0, cast(float)bg.a / 255.0);
+		glBindTexture(GL_TEXTURE_2D, _tex);
+		glBegin(GL_QUADS); 
+			glTexCoord2f(0, 0); 				glVertex2i(0, 0);
+			glTexCoord2f(texCoordWidth, 0); 		glVertex2i(width, 0); 
+			glTexCoord2f(texCoordWidth, texCoordHeight); 	glVertex2i(width, height); 
+			glTexCoord2f(0, texCoordHeight); 		glVertex2i(0, height); 
+		glEnd();
+
+		glBindTexture(GL_TEXTURE_2D, 0); // unbind the texture
+
+		glPopMatrix();
+	}
+
+	/// Use for glTexCoord2f
+	float texCoordWidth() { return _texCoordWidth; }
+	float texCoordHeight() { return _texCoordHeight; } /// ditto
+
+	/// Returns the texture ID
+	uint tex() { return _tex; }
+
+	/// Returns the size of the image
+	int originalImageWidth() { return _width; }
+	int originalImageHeight() { return _height; } /// ditto
+
+	/// Make a texture from an image.
+	this(TrueColorImage from) {
+		assert(from.width > 0 && from.height > 0);
+
+		_width = from.width;
+		_height = from.height;
+
+		auto _texWidth = _width;
+		auto _texHeight = _height;
+
+		const(ubyte)[] data = from.imageData.bytes;
+
+		// gotta round them to the nearest power of two which means padding the image
+		if((_texWidth & (_texWidth - 1)) || (_texHeight & (_texHeight - 1))) {
+			_texWidth = nextPowerOfTwo(_texWidth);
+			_texHeight = nextPowerOfTwo(_texHeight);
+
+			auto n = new ubyte[](_texWidth * _texHeight * 4);
+			auto size = from.width * 4;
+			auto advance = _texWidth * 4;
+			int at = 0;
+			int at2 = 0;
+			foreach(y; 0 .. from.height) {
+				n[at .. at + size] = from.imageData.bytes[at2 .. at2+ size];
+				at += advance;
+				at2 += size;
+			}
+
+			data = n[];
+
+			// the rest of data will be initialized to zeros automatically which is fine.
+		}
+
+		glGenTextures(1, &_tex);
+		glBindTexture(GL_TEXTURE_2D, tex);
+
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+
+		glTexImage2D(
+			GL_TEXTURE_2D,
+			0,
+			GL_RGBA,
+			_texWidth, // needs to be power of 2
+			_texHeight,
+			0,
+			GL_RGBA,
+			GL_UNSIGNED_BYTE,
+			data.ptr);
+
+		assert(!glGetError());
+
+		_texCoordWidth = cast(float) _width / _texWidth;
+		_texCoordHeight = cast(float) _height / _texHeight;
+	}
+
+	/// Generates from text. Requires stb_truetype.d
+	/// pass a pointer to the TtfFont as the first arg (it is template cuz of lazy importing, not because it actually works with different types)
+	this(T, FONT)(FONT* font, int size, in T[] text) if(is(T == char)) {
+		assert(font !is null);
+		int width, height;
+		auto data = font.renderString(text, size, width, height);
+		auto image = new TrueColorImage(width, height);
+		int pos = 0;
+		foreach(y; 0 .. height)
+		foreach(x; 0 .. width) {
+			image.imageData.bytes[pos++] = 255;
+			image.imageData.bytes[pos++] = 255;
+			image.imageData.bytes[pos++] = 255;
+			image.imageData.bytes[pos++] = data[0];
+			data = data[1 .. $];
+		}
+		assert(data.length == 0);
+
+		this(image);
+	}
+
+	~this() {
+		glDeleteTextures(1, &_tex);
+	}
+}
+
+
+// Some math helpers
+
+int nextPowerOfTwo(int v) {
+	v--;
+	v |= v >> 1;
+	v |= v >> 2;
+	v |= v >> 4;
+	v |= v >> 8;
+	v |= v >> 16;
+	v++;
+	return v;
+}
+
+void crossProduct(
+	float u1, float u2, float u3,
+	float v1, float v2, float v3,
+	out float s1, out float s2, out float s3)
+{
+	s1 = u2 * v3 - u3 * v2;
+	s2 = u3 * v1 - u1 * v3;
+	s3 = u1 * v2 - u2 * v1;
+}
+
+void rotateAboutAxis(
+	float theta, // in RADIANS
+	float x, float y, float z,
+	float u, float v, float w,
+	out float xp, out float yp, out float zp)
+{
+	xp = u * (u*x + v*y + w*z) * (1 - cos(theta)) + x * cos(theta) + (-w*y + v*z) * sin(theta);
+	yp = v * (u*x + v*y + w*z) * (1 - cos(theta)) + y * cos(theta) + (w*x - u*z) * sin(theta);
+	zp = w * (u*x + v*y + w*z) * (1 - cos(theta)) + z * cos(theta) + (-v*x + u*y) * sin(theta);
+}