The first commit.
The first commit.

file:b/libs/OpenTK.dll (new)
 Binary files /dev/null and b/libs/OpenTK.dll differ
--- /dev/null
+++ b/sandbox/sandbox.sln
@@ -1,1 +1,18 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "sandbox", "sandbox\sandbox.csproj", "{89E37932-E22D-4D94-8438-ACEB73667456}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|x86 = Debug|x86
+		Release|x86 = Release|x86
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{89E37932-E22D-4D94-8438-ACEB73667456}.Debug|x86.ActiveCfg = Debug|x86
+		{89E37932-E22D-4D94-8438-ACEB73667456}.Debug|x86.Build.0 = Debug|x86
+		{89E37932-E22D-4D94-8438-ACEB73667456}.Release|x86.ActiveCfg = Release|x86
+		{89E37932-E22D-4D94-8438-ACEB73667456}.Release|x86.Build.0 = Release|x86
+	EndGlobalSection
+EndGlobal
 

--- /dev/null
+++ b/sandbox/sandbox/API/AGameModuleDependency.cs
@@ -1,1 +1,18 @@
+using System;
 
+namespace sandbox
+{
+	public class GameModuleDependencyAttribute : Attribute
+	{
+		public Type[] Dependencies { get; private set; }
+
+		public GameModuleDependencyAttribute(Type[] depends) : base()
+		{
+			this.Dependencies = depends;
+		}
+
+		public GameModuleDependencyAttribute() : this(new Type[] {}) {}
+	}
+}
+
+

--- /dev/null
+++ b/sandbox/sandbox/API/IGameModule.cs
@@ -1,1 +1,11 @@
+using System;
 
+namespace sandbox
+{
+	public interface IGameModule : IDisposable
+	{
+		void LoadModule();
+	}
+}
+
+

--- /dev/null
+++ b/sandbox/sandbox/API/IRenderModule.cs
@@ -1,1 +1,11 @@
+using System;
 
+namespace sandbox
+{
+	public interface IRenderModule
+	{
+		void OnRender();
+	}
+}
+
+

--- /dev/null
+++ b/sandbox/sandbox/DrawingFunctions.cs
@@ -1,1 +1,86 @@
+using System;
+using System.Drawing;
+using OpenTK;
+using OpenTK.Graphics;
+using OpenTK.Graphics.OpenGL;
+using OpenTK.Input;
 
+namespace sandbox
+{
+	public static class DrawingFunctions
+	{
+		public static void DrawTexturedQuadAtPoint(Texture2D tex, int x, int y, int z, int width, int height)
+		{
+			GL.BindTexture(TextureTarget.Texture2D, tex.ID);
+
+			GL.Begin(PrimitiveType.Triangles);
+
+			GL.Color4(1f, 1f, 1f, 1f);
+			GL.TexCoord2(0, 0); GL.Vertex3(x, y, z);
+			GL.TexCoord2(1, 1); GL.Vertex3(x + tex.Width, y + tex.Height, z);
+			GL.TexCoord2(0, 1); GL.Vertex3(x, y + tex.Height, z);
+
+			GL.TexCoord2(0, 0); GL.Vertex3(x, y, z);
+			GL.TexCoord2(1, 0); GL.Vertex3(x + tex.Width, y, z);
+			GL.TexCoord2(1, 1); GL.Vertex3(x + tex.Width, y + tex.Height, z);
+
+			GL.End();
+		}
+
+		public static void DrawTexturedQuadAtPoint(Texture2D tex, float x, float y, float z, float width, float height)
+		{
+			GL.BindTexture(TextureTarget.Texture2D, tex.ID);
+
+			GL.Begin(PrimitiveType.Triangles);
+
+			GL.Color4(1f, 1f, 1f, 1f);
+			GL.TexCoord2(0, 0); GL.Vertex3(x, y, z);
+			GL.TexCoord2(1, 1); GL.Vertex3(x + tex.Width, y + tex.Height, z);
+			GL.TexCoord2(0, 1); GL.Vertex3(x, y + tex.Height, z);
+
+			GL.TexCoord2(0, 0); GL.Vertex3(x, y, z);
+			GL.TexCoord2(1, 0); GL.Vertex3(x + tex.Width, y, z);
+			GL.TexCoord2(1, 1); GL.Vertex3(x + tex.Width, y + tex.Height, z);
+
+			GL.End();
+		}
+
+		public static void DrawTexturedQuadAtPoint(Texture2D tex, int x, int y, int z)
+		{
+			DrawTexturedQuadAtPoint(tex, x, y, z, tex.Width, tex.Height);
+		}
+
+		public static void DrawTexturedQuadAtPoint(Texture2D tex, float x, float y, float z)
+		{
+			DrawTexturedQuadAtPoint(tex, x, y, z, (float)tex.Width, (float)tex.Height);
+		}
+
+		public static void DrawTexturedQuadCenteredAtPoint(Texture2D tex, int x, int y, int z, int width, int height)
+		{
+			int fx = x - width / 2;
+			int fy = y - height / 2;
+
+			DrawTexturedQuadAtPoint(tex, fx, fy, z, width, height);
+		}
+
+		public static void DrawTexturedQuadCenteredAtPoint(Texture2D tex, float x, float y, float z, float width, float height)
+		{
+			float fx = x - (float)tex.Width / 2f;
+			float fy = y - (float)tex.Height / 2f;
+
+			DrawTexturedQuadAtPoint(tex, fx, fy, z, width, height);
+		}
+
+		public static void DrawTexturedQuadCenteredAtPoint(Texture2D tex, int x, int y, int z)
+		{
+			DrawTexturedQuadCenteredAtPoint(tex, x, y, z, tex.Width, tex.Height);
+		}
+
+		public static void DrawTexturedQuadCenteredAtPoint(Texture2D tex, float x, float y, float z)
+		{
+			DrawTexturedQuadCenteredAtPoint(tex, x, y, z, tex.Width, tex.Height);
+		}
+	}
+}
+
+

--- /dev/null
+++ b/sandbox/sandbox/Game.cs
@@ -1,1 +1,218 @@
-
+using System;
+using System.Drawing;
+using System.Collections.Generic;
+using OpenTK;
+using OpenTK.Graphics;
+using OpenTK.Graphics.OpenGL;
+using OpenTK.Input;
+
+namespace sandbox
+{
+	public class Game : IDisposable
+	{
+		public const double UPDATES_PER_SECOND = 60d;
+		public const double RENDERS_PER_SECOND = 60d;
+
+		public static Game TheGame { get; private set; }
+
+		public static int ScreenWidth
+		{
+			get
+			{
+				return TheGame.Width;
+			}
+		}
+
+		public static int ScreenHeight
+		{
+			get
+			{
+				return TheGame.Height;
+			}
+		}
+
+		public int Width { get; private set; }
+		public int Height { get; private set; }
+
+		private GameWindow Window { get; set; }
+
+		public ulong FrameNumber { get; private set; }
+
+		public event EventHandler<FrameEventArgs> UpdateFrame
+		{
+			add
+			{
+				this.Window.UpdateFrame += value;
+			}
+			remove
+			{
+				this.Window.UpdateFrame -= value;
+			}
+		}
+
+		public event EventHandler<FrameEventArgs> RenderFrame;
+
+		public IReadOnlyList<IGameModule> GameModules { get; private set; }
+		private List<IGameModule> gameModules;
+
+		private InputSubscription QuitButton;
+
+		private bool isDisposed;
+
+		public Game(int width, int height)
+		{
+			this.Window = new GameWindow(width, height, new GraphicsMode(32, 8, 0, 0));
+
+			this.Window.Load += this.OnWindowLoad;
+			this.Window.RenderFrame += this.OnRenderFrame;
+			this.Window.Resize += this.OnWindowResize;
+
+			this.Window.KeyDown += InputManager.Instance.OnKeyDown;
+			// this.Window.KeyPress += InputManager.Instance.OnKeyPress; //@TODO: Figure out KeyPress.
+			this.Window.KeyUp += InputManager.Instance.OnKeyUp;
+
+			this.gameModules = new List<IGameModule>();
+			this.GameModules = this.gameModules.AsReadOnly();
+
+			this.FrameNumber = 0u;
+
+			isDisposed = false;
+
+			TheGame = this;
+		}
+
+		public void Dispose()
+		{
+			foreach (IGameModule module in this.GameModules)
+			{
+				module.Dispose();
+			}
+
+			this.Window.Dispose();
+			TheGame = null;
+
+			isDisposed = true;
+		}
+
+		public void Run()
+		{
+			this.Window.Run(UPDATES_PER_SECOND, RENDERS_PER_SECOND);
+		}
+
+		public void Quit()
+		{
+			this.Window.Exit();
+		}
+
+		private void OnWindowLoad(object sender, EventArgs e)
+		{
+			Window.VSync = VSyncMode.Off;
+			Window.WindowBorder = WindowBorder.Fixed;
+			this.OnWindowResize(sender, e);
+
+			this.QuitButton = new InputSubscription(
+				"Quit Game",
+				Key.Escape,
+				(s, a) =>
+				{
+				},
+				(s, a) =>
+				{
+					this.Quit();
+				}
+			);
+
+			InputManager.Instance.RegisterInput(this.QuitButton);
+
+			var types = this.GetType().Assembly.DefinedTypes;
+
+			List<Type> modulesDeferredForDepends = new List<Type>();;
+
+			foreach (var type in types)
+			{
+				if (type.IsInterface)
+				{
+					continue;
+				}
+
+				foreach (var iface in type.GetInterfaces())
+				{
+					if (iface == typeof(IGameModule))
+					{
+						// Check Dependencies
+						bool hasDepends = false;
+						var attributes = type.GetCustomAttributes(typeof(GameModuleDependencyAttribute), true);
+
+						foreach (var attribute in attributes)
+						{
+							if (attribute is GameModuleDependencyAttribute)
+							{
+								hasDepends = true;
+								break;
+							}
+						}
+
+						if (hasDepends)
+						{
+							modulesDeferredForDepends.Add(type);
+							break;
+						}
+
+						// Activate module instance and load it.
+						IGameModule gameModule;
+						gameModule = (IGameModule)Activator.CreateInstance(type);
+
+						Console.WriteLine("Activating type '{0}'.", type.FullName);
+						this.gameModules.Add(gameModule);
+						gameModule.LoadModule();
+
+						break;
+					}
+				}
+			}
+		}
+
+		private void OnWindowResize(object sender, EventArgs e)
+		{
+			GL.Viewport(Window.ClientRectangle);
+
+			this.Width = Window.ClientRectangle.Width;
+			this.Height = Window.ClientRectangle.Height;
+		}
+
+		private void OnRenderFrame(object sender, FrameEventArgs e)
+		{
+			this.FrameNumber++;
+
+			GL.ClearColor(Color.DarkGray);
+			GL.ClearDepth(1);
+			GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit | ClearBufferMask.AccumBufferBit);
+
+			GL.Enable(EnableCap.Blend);
+			GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
+
+			GL.Enable(EnableCap.DepthTest);
+			GL.DepthFunc(DepthFunction.Lequal);
+
+			GL.Enable(EnableCap.Texture2D);
+
+
+			Matrix4 projMatrix = Matrix4.CreateOrthographicOffCenter(0, Window.Width, Window.Height, 0, 0, 1);
+
+			GL.MatrixMode(MatrixMode.Projection);
+			GL.LoadMatrix(ref projMatrix);
+
+			if (this.RenderFrame != null)
+			{
+				this.RenderFrame(sender, e);
+			}
+
+			GL.Disable(EnableCap.Blend);
+			GL.Disable(EnableCap.DepthTest);
+			GL.Disable(EnableCap.Texture2D);
+
+			Window.SwapBuffers();
+		}
+	}
+}
+

--- /dev/null
+++ b/sandbox/sandbox/Input/InputManager.cs
@@ -1,1 +1,134 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using OpenTK;
+using OpenTK.Input;
 
+namespace sandbox
+{
+	public class InputManager
+	{
+		public static InputManager Instance
+		{
+			get
+			{
+				if (_instance == null)
+				{
+					_instance = new InputManager();
+				}
+
+				return _instance;
+			}
+		}
+		private static InputManager _instance;
+
+		private Dictionary<Key, InputSubscription> inputLibrary;
+
+		public IReadOnlyDictionary<Key, InputSubscription> InputLibrary { get; private set; }
+
+		private KeyboardState lastKeyboardState;
+
+		private InputManager()
+		{
+			this.inputLibrary = new Dictionary<Key, InputSubscription>();
+
+			this.InputLibrary = new ReadOnlyDictionary<Key, InputSubscription>(this.inputLibrary);
+		}
+
+		public bool RegisterInput(InputSubscription sub)
+		{
+			// TODO: Key assignment here should check a loaded setting first.
+			// This is just a placeholder.
+			sub.KeyAssignment = sub.DefaultKey;
+
+			InputSubscription existingSub;
+			if (this.inputLibrary.TryGetValue(sub.KeyAssignment, out existingSub))
+			{
+				Console.Error.WriteLine(
+					"InputManager: Got request to assign '{0}' to key '{1}', but '{2}' is already assigned.  De-registering '{2}'.",
+					sub.InputName,
+					sub.KeyAssignment,
+					existingSub.InputName
+				);
+
+				this.DeregisterInput(existingSub);
+			}
+
+			this.inputLibrary[sub.KeyAssignment] = sub;
+
+			return true;
+		}
+
+		public bool DeregisterInput(InputSubscription sub)
+		{
+			InputSubscription actualSub;
+			if (!this.inputLibrary.TryGetValue(sub.KeyAssignment, out actualSub))
+			{
+				Console.Error.WriteLine(
+					"InputManager: Got request to deregister input '{0}', but it is not assigned to expected key '{1}'",
+					sub.InputName,
+					sub.KeyAssignment
+				);
+
+				return false;
+			}
+
+			if (actualSub != sub)
+			{
+				Console.Error.WriteLine(
+					"InputManager: Got request to deregister input '{0}', but found input '{2}' on key '{1}' instead",
+					sub.InputName,
+					sub.KeyAssignment,
+					actualSub.InputName
+				);
+
+				return false;
+			}
+
+			Key key = sub.KeyAssignment;
+
+			sub.KeyAssignment = Key.Unknown;
+
+			return this.inputLibrary.Remove(key);
+		}
+
+		public void ReassignInput(InputSubscription sub, Key newKey)
+		{
+			if (sub.KeyAssignment != Key.Unknown)
+			{
+				this.DeregisterInput(sub);
+			}
+
+			sub.KeyAssignment = newKey;
+			this.RegisterInput(sub);
+		}
+
+		public void OnKeyUp(object sender, KeyboardKeyEventArgs args)
+		{
+			InputSubscription keySubscription;
+			if (this.inputLibrary.TryGetValue(args.Key, out keySubscription))
+			{
+				keySubscription.KeyUpHandler.Invoke(sender, args);
+			}
+
+			this.lastKeyboardState = args.Keyboard;
+		}
+
+
+		public void OnKeyDown(object sender, KeyboardKeyEventArgs args)
+		{
+			InputSubscription keySubscription;
+			if (this.inputLibrary.TryGetValue(args.Key, out keySubscription))
+			{
+				if (keySubscription.SendRepeats || !lastKeyboardState.IsKeyDown(args.Key))
+				{
+					keySubscription.KeyDownHandler.Invoke(sender, args);
+				}
+			}
+
+			this.lastKeyboardState = args.Keyboard;
+		}
+	}
+}
+
+

--- /dev/null
+++ b/sandbox/sandbox/Input/InputSubscription.cs
@@ -1,1 +1,79 @@
+using System;
+using OpenTK;
+using OpenTK.Input;
 
+namespace sandbox
+{
+	public class InputSubscription
+	{
+		public string InputName { get; private set; }
+
+		public Key KeyAssignment { get; set; }
+		public Key DefaultKey { get; private set; }
+
+		public bool SendRepeats { get; private set; }
+
+		public Action<object, KeyboardKeyEventArgs> KeyUpHandler { get; private set; }
+		public Action<object, KeyPressEventArgs> KeyPressHandler { get; private set; }
+		public Action<object, KeyboardKeyEventArgs> KeyDownHandler { get; private set; }
+
+		public InputSubscription(
+			string inputName,
+			Key defaultKey,
+			bool sendRepeats,
+			Action<object, KeyboardKeyEventArgs> keyDown,
+			Action<object, KeyPressEventArgs> keyPress,
+			Action<object, KeyboardKeyEventArgs> keyUp
+		)
+		{
+			this.InputName = inputName;
+			this.DefaultKey = defaultKey;
+			this.SendRepeats = sendRepeats;
+
+			if (keyPress != null)
+			{
+				Console.Error.WriteLine(
+					"WARNING: InputSubscription: In Ctor for input '{0}' got non-null keyPress argument, but keyPress is NYI.",
+					inputName
+				);
+			}
+
+			if (keyDown == null)
+			{
+				throw new ArgumentNullException("InputSubscription: keyDown must not be null");
+			}
+
+			if (keyUp == null)
+			{
+				throw new ArgumentNullException("InputSubscription: keyUp must not be null");
+			}
+
+			this.KeyDownHandler = keyDown;
+			this.KeyPressHandler = keyPress;
+			this.KeyUpHandler = keyUp;
+		}
+
+		public InputSubscription(
+			string inputName,
+			Key defaultKey,
+			bool sendRepeats,
+			Action<object, KeyboardKeyEventArgs> keyDown,
+			Action<object, KeyboardKeyEventArgs> keyUp
+		)
+			: this(inputName, defaultKey, sendRepeats, keyDown, null, keyUp)
+		{
+		}
+
+		public InputSubscription(
+			string inputName,
+			Key defaultKey,
+			Action<object, KeyboardKeyEventArgs> keyDown,
+			Action<object, KeyboardKeyEventArgs> keyUp
+		)
+			: this(inputName, defaultKey, false, keyDown, null, keyUp)
+		{
+		}
+	}
+}
+
+

--- /dev/null
+++ b/sandbox/sandbox/JustSomeText.cs
@@ -1,1 +1,45 @@
+using System;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.Collections.Generic;
+using OpenTK;
+using OpenTK.Graphics;
+using OpenTK.Graphics.OpenGL;
+using OpenTK.Input;
 
+namespace sandbox
+{
+	public class JustSomeText : IGameModule
+	{
+		TextWriter SomeText;
+		SolidBrush TextBrush;
+
+		public void LoadModule()
+		{
+			TextBrush = new SolidBrush(Color.ForestGreen);
+
+			SomeText = new TextWriter(new Size(Game.TheGame.Width, Game.TheGame.Height), new Size(Game.TheGame.Width, Game.TheGame.Height));
+			SomeText.AddLine("Operation: Bouncing Yoda", new PointF(240, 160), TextBrush);
+
+			Game.TheGame.RenderFrame += OnRender;
+		}
+
+		public void Dispose()
+		{
+			SomeText.Dispose();
+		}
+
+		private void OnUpdate(object sender, FrameEventArgs e)
+		{
+			
+		}
+
+		private void OnRender(object sender, FrameEventArgs e)
+		{
+			Console.WriteLine("Frame {0}: JustSomeText.OnRender", Game.TheGame.FrameNumber);
+			SomeText.Draw();
+		}
+	}
+}
+
+

--- /dev/null
+++ b/sandbox/sandbox/Loaders.cs
@@ -1,1 +1,55 @@
+using System;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using OpenTK;
+using OpenTK.Graphics;
+using OpenTK.Graphics.OpenGL;
+using OpenTK.Input;
 
+namespace sandbox
+{
+	public static class Loaders
+	{
+		public static Texture2D LoadTexture(string filepath)
+		{
+			if (!File.Exists(filepath))
+			{
+				Console.Error.WriteLine("Could not load texture from path '{0}': file does not exist.", filepath);
+				Environment.Exit(1);
+			}
+
+			using (Stream textureStream = File.Open(filepath, FileMode.Open))
+			{
+				Bitmap bitmap = new Bitmap(textureStream);
+
+				int id = GL.GenTexture();
+
+				BitmapData bmpData = bitmap.LockBits(
+					                    new Rectangle(0, 0, bitmap.Width, bitmap.Height),
+					                    ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
+
+				GL.BindTexture(TextureTarget.Texture2D, id);
+
+				GL.TexImage2D(TextureTarget.Texture2D, 0,
+					PixelInternalFormat.Rgba,
+					bitmap.Width, bitmap.Height, 0,
+					OpenTK.Graphics.OpenGL.PixelFormat.Bgra,
+					PixelType.UnsignedByte,
+					bmpData.Scan0);
+
+				bitmap.UnlockBits(bmpData);
+
+				GL.TexParameter(TextureTarget.Texture2D,
+					TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
+
+				GL.TexParameter(TextureTarget.Texture2D,
+					TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
+
+				return new Texture2D(id, bitmap.Width, bitmap.Height);
+			}
+		}
+	}
+}
+
+

--- /dev/null
+++ b/sandbox/sandbox/Program.cs
@@ -1,1 +1,20 @@
+using System;
+using System.Drawing;
+using OpenTK;
+using OpenTK.Graphics;
+using OpenTK.Graphics.OpenGL;
+using OpenTK.Input;
 
+namespace sandbox
+{
+	class Program
+	{
+		public static void Main()
+		{
+			using (Game game = new Game(1280, 720))
+			{
+				game.Run();
+			}
+		}
+	}
+}

--- /dev/null
+++ b/sandbox/sandbox/TextWriter.cs
@@ -1,1 +1,135 @@
+using System;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.Collections.Generic;
+using OpenTK;
+using OpenTK.Graphics;
+using OpenTK.Graphics.OpenGL;
+using OpenTK.Input;
 
+// TODO: I don't really care for the way this is doing things, but it works OK for prototyping I guess.
+
+namespace sandbox
+{
+	class TextWriter
+	{
+		private readonly Font TextFont = new Font(FontFamily.GenericSansSerif, 14);
+		private readonly Bitmap TextBitmap;
+		private List<PointF> _positions;
+		private List<string> _lines;
+		private List<Brush> _colours;
+		private int _textureId;
+		private Size _clientSize;
+
+		public void Update(int ind, string newText)
+		{
+			if (ind < _lines.Count)
+			{
+				_lines[ind] = newText;
+				UpdateText();
+			}
+		}
+
+
+		public TextWriter(Size ClientSize, Size areaSize)
+		{
+			_positions = new List<PointF>();
+			_lines = new List<string>();
+			_colours = new List<Brush>();
+
+			TextBitmap = new Bitmap(areaSize.Width, areaSize.Height);
+			this._clientSize = ClientSize;
+			_textureId = CreateTexture();
+		}
+
+		private int CreateTexture()
+		{
+			int textureId;
+			GL.TexEnv(TextureEnvTarget.TextureEnv, TextureEnvParameter.TextureEnvMode, (float)TextureEnvMode.Replace);//Important, or wrong color on some computers
+			Bitmap bitmap = TextBitmap;
+			GL.GenTextures(1, out textureId);
+			GL.BindTexture(TextureTarget.Texture2D, textureId);
+
+			BitmapData data = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
+			GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, data.Width, data.Height, 0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0);
+			GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
+			GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
+
+			GL.Finish();
+			bitmap.UnlockBits(data);
+			return textureId;
+		}
+
+		public void Dispose()
+		{
+			if (_textureId > 0)
+				GL.DeleteTexture(_textureId);
+		}
+
+		public void Clear()
+		{
+			_lines.Clear();
+			_positions.Clear();
+			_colours.Clear();
+		}
+
+		public void AddLine(string s, PointF pos, Brush col)
+		{
+			_lines.Add(s);
+			_positions.Add(pos);
+			_colours.Add(col);
+			UpdateText();
+		}
+
+		public void UpdateText()
+		{
+			if (_lines.Count > 0)
+			{
+				using (Graphics gfx = Graphics.FromImage(TextBitmap))
+				{
+					// TODO: Maybe we could put this back if it was a background color or something?
+					// gfx.Clear(Color.Transparent);
+					gfx.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
+					for (int i = 0; i < _lines.Count; i++)
+					{
+						gfx.DrawString(_lines[i], TextFont, _colours[i], _positions[i]);
+					}
+					
+				}
+
+				System.Drawing.Imaging.BitmapData data = TextBitmap.LockBits(new Rectangle(0, 0, TextBitmap.Width, TextBitmap.Height),
+					System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
+				GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, TextBitmap.Width, TextBitmap.Height, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0);
+				TextBitmap.UnlockBits(data);
+			}
+		}
+
+		public void Draw()
+		{
+			GL.PushMatrix();
+			GL.LoadIdentity();
+
+			Matrix4 ortho_projection = Matrix4.CreateOrthographicOffCenter(0, _clientSize.Width, _clientSize.Height, 0, -1, 1);
+			GL.MatrixMode(MatrixMode.Projection);
+
+			GL.PushMatrix();
+			GL.LoadMatrix(ref ortho_projection);
+
+			GL.BindTexture(TextureTarget.Texture2D, _textureId);
+
+
+			GL.Begin(PrimitiveType.Quads);
+			GL.TexCoord2(0, 0); GL.Vertex2(0, 0);
+			GL.TexCoord2(1, 0); GL.Vertex2(TextBitmap.Width, 0);
+			GL.TexCoord2(1, 1); GL.Vertex2(TextBitmap.Width, TextBitmap.Height);
+			GL.TexCoord2(0, 1); GL.Vertex2(0, TextBitmap.Height);
+			GL.End();
+			GL.PopMatrix();
+
+			GL.MatrixMode(MatrixMode.Modelview);
+			GL.PopMatrix();
+		}
+	}
+}
+
+

--- /dev/null
+++ b/sandbox/sandbox/Texture2D.cs
@@ -1,1 +1,22 @@
+using System;
 
+namespace sandbox
+{
+	public class Texture2D
+	{
+		private int id;
+		private int width, height;
+
+		public int ID { get { return id; }}
+		public int Width { get { return width; }}
+		public int Height { get { return height; }}
+
+		public Texture2D(int id, int width, int height)
+		{
+			this.id = id;
+			this.width = width;
+			this.height = height;
+		}
+	}
+}
+

--- /dev/null
+++ b/sandbox/sandbox/Tools.cs
@@ -1,1 +1,58 @@
+using System;
+using System.Collections.Generic;
 
+namespace sandbox
+{
+	public static class Tools
+	{
+		public static T[] GetCustomAttributes<T>(this Type type, bool inherit) where T : Attribute
+		{
+			object[] attributes = type.GetCustomAttributes(typeof(T), inherit);
+
+			return (T[])attributes;
+		}
+
+		public static List<Type> GetModuleDependencies(this Type type)
+		{
+			List<Type> moduleDepends = new List<Type>();
+
+			foreach (var attribute in type.GetCustomAttributes<GameModuleDependencyAttribute>(true))
+			{
+				foreach (var dependency in attribute.Dependencies)
+				{
+					if (!moduleDepends.Contains(dependency))
+					{
+						moduleDepends.Add(dependency);
+					}
+				}
+			}
+
+			return moduleDepends;
+		}
+
+		public static bool HasDependency(this Type type, Type dependType)
+		{
+			foreach (Type depend in type.GetModuleDependencies())
+			{
+				if (depend == dependType)
+				{
+					return true;
+				}
+
+				if (depend.HasDependency(dependType))
+				{
+					return true;
+				}
+			}
+
+			return false;
+		}
+
+		public static bool HasCircularDependency(this Type type)
+		{
+			return type.HasDependency(type);
+		}
+	}
+}
+
+

--- /dev/null
+++ b/sandbox/sandbox/Yoda.cs
@@ -1,1 +1,161 @@
+using System;
+using OpenTK;
+using OpenTK.Graphics;
+using OpenTK.Graphics.OpenGL;
+using OpenTK.Input;
 
+namespace sandbox
+{
+	public class Yoda : IGameModule
+	{
+		private Texture2D yodaTexture;
+
+		private Vector2 yodaPos;
+		private float yodaSpd;
+		private Vector2 yodaVel;
+
+		private InputSubscription MoveUp;
+		private InputSubscription MoveRight;
+		private InputSubscription MoveDown;
+		private InputSubscription MoveLeft;
+
+		private bool movingUp;
+		private bool movingRight;
+		private bool movingDown;
+		private bool movingLeft;
+
+		public Yoda() {}
+
+		public void LoadModule()
+		{
+			yodaPos = Vector2.Zero;
+
+			yodaTexture = Loaders.LoadTexture("data/Yoda-276x276.jpg");
+			yodaPos = new Vector2(Game.ScreenWidth / 2f, Game.ScreenHeight / 2f);
+			yodaSpd = 5;
+			yodaVel = Vector2.Zero;
+
+			this.MoveUp = new InputSubscription(
+				"Move Up",
+				Key.Up,
+				(s, a) =>
+				{
+					this.movingUp = true;
+					UpdateCtrlVector();
+				},
+				(s, a) =>
+				{
+					this.movingUp = false;
+					UpdateCtrlVector();
+				}
+			);
+
+			this.MoveRight = new InputSubscription(
+				"Move Right",
+				Key.Right,
+				(s, a) =>
+				{
+					this.movingRight = true;
+					UpdateCtrlVector();
+				},
+				(s, a) =>
+				{
+					this.movingRight = false;
+					UpdateCtrlVector();
+				}
+			);
+
+			this.MoveDown = new InputSubscription(
+				"Move Down",
+				Key.Down,
+				(s, a) =>
+				{
+					this.movingDown = true;
+					UpdateCtrlVector();
+				},
+				(s, a) =>
+				{
+					this.movingDown = false;
+					UpdateCtrlVector();
+				}
+			);
+
+			this.MoveLeft = new InputSubscription(
+				"Move Left",
+				Key.Left,
+				(s, a) =>
+				{
+					this.movingLeft = true;
+					UpdateCtrlVector();
+				},
+				(s, a) =>
+				{
+					this.movingLeft = false;
+					UpdateCtrlVector();
+				}
+			);
+
+			InputManager.Instance.RegisterInput(MoveUp);
+			InputManager.Instance.RegisterInput(MoveRight);
+			InputManager.Instance.RegisterInput(MoveDown);
+			InputManager.Instance.RegisterInput(MoveLeft);
+
+			Game.TheGame.UpdateFrame += this.OnUpdate;
+			Game.TheGame.RenderFrame += this.OnRender;
+		}
+
+		public void Dispose()
+		{
+			GL.DeleteTexture(yodaTexture.ID);
+		}
+
+		private void UpdateCtrlVector()
+		{
+			Vector2 yodaCtrlVector = Vector2.Zero;
+
+			if (movingUp)
+				yodaCtrlVector.Y += 1;
+			if (movingRight)
+				yodaCtrlVector.X += 1;
+			if (movingDown)
+				yodaCtrlVector.Y -= 1;
+			if (movingLeft)
+				yodaCtrlVector.X -= 1;
+
+			yodaCtrlVector.Y = -yodaCtrlVector.Y;
+
+			if (yodaCtrlVector.LengthSquared != 0)
+			{
+				yodaVel = yodaCtrlVector.Normalized() * yodaSpd;
+			}
+			else
+			{
+				yodaVel = Vector2.Zero;
+			}
+		}
+
+		private void OnUpdate(object sender, FrameEventArgs e)
+		{
+			if (yodaVel.LengthSquared != 0)
+			{
+				yodaPos += yodaVel;
+
+				yodaPos.X %= Game.ScreenWidth;
+				yodaPos.Y %= Game.ScreenHeight;
+
+				if (yodaPos.X < 0)
+					yodaPos.X += Game.ScreenWidth;
+				if (yodaPos.Y < 0)
+					yodaPos.Y += Game.ScreenHeight;
+			}
+		}
+
+		private void OnRender(object sender, FrameEventArgs e)
+		{
+			Console.WriteLine("Frame {0}: Yoda.OnRender", Game.TheGame.FrameNumber);
+			DrawingFunctions.DrawTexturedQuadCenteredAtPoint(yodaTexture, yodaPos.X, yodaPos.Y, -1);
+		}
+	}
+}
+
+

--- /dev/null
+++ b/sandbox/sandbox/obj/x86/Debug/.NETFramework,Version=v4.5.AssemblyAttribute.cs
@@ -1,1 +1,3 @@
+// <autogenerated />
+[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.5", FrameworkDisplayName = "")]
 

--- /dev/null
+++ b/sandbox/sandbox/sandbox.csproj
@@ -1,1 +1,86 @@
-
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
+    <ProjectGuid>{89E37932-E22D-4D94-8438-ACEB73667456}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <RootNamespace>sandbox</RootNamespace>
+    <AssemblyName>sandbox</AssemblyName>
+    <UseMSBuildEngine>False</UseMSBuildEngine>
+    <StartupObject>sandbox.Program</StartupObject>
+    <CodePage>65001</CodePage>
+    <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug</OutputPath>
+    <DefineConstants>DEBUG;</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <PlatformTarget>x86</PlatformTarget>
+    <ConsolePause>false</ConsolePause>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
+    <DebugType>full</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release</OutputPath>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <PlatformTarget>x86</PlatformTarget>
+    <ConsolePause>false</ConsolePause>
+  </PropertyGroup>
+  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+  <ItemGroup>
+    <Compile Include="Program.cs" />
+    <Compile Include="Game.cs" />
+    <Compile Include="Texture2D.cs" />
+    <Compile Include="DrawingFunctions.cs" />
+    <Compile Include="Input\InputManager.cs" />
+    <Compile Include="Input\InputSubscription.cs" />
+    <Compile Include="Loaders.cs" />
+    <Compile Include="Yoda.cs" />
+    <Compile Include="API\IGameModule.cs" />
+    <Compile Include="API\IRenderModule.cs" />
+    <Compile Include="API\AGameModuleDependency.cs" />
+    <Compile Include="Tools.cs" />
+    <Compile Include="TextWriter.cs" />
+    <Compile Include="JustSomeText.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <Reference Include="System.Drawing" />
+    <Reference Include="System" />
+    <Reference Include="OpenTK">
+      <HintPath>..\..\libs\OpenTK.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="D:\Andy\Documents\Yoda-276x276.jpg">
+      <Link>data\Yoda-276x276.jpg</Link>
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+    <None Include="..\..\opengl4tutorials\OpenGLTutorial13\OpenGLTutorial13\data\AlternatingBrick-ColorMap.png">
+      <Link>data\AlternatingBrick-ColorMap.png</Link>
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+    <None Include="..\..\opengl4tutorials\OpenGLTutorial13\OpenGLTutorial13\data\AlternatingBrick-NormalMap.png">
+      <Link>data\AlternatingBrick-NormalMap.png</Link>
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+    <None Include="..\..\opengl4tutorials\OpenGLTutorial13\OpenGLTutorial13\data\font24.fnt">
+      <Link>data\font24.fnt</Link>
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+    <None Include="..\..\opengl4tutorials\OpenGLTutorial13\OpenGLTutorial13\data\font24.png">
+      <Link>data\font24.png</Link>
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+  </ItemGroup>
+  <ItemGroup>
+    <Folder Include="data\" />
+    <Folder Include="Input\" />
+    <Folder Include="API\" />
+  </ItemGroup>
+</Project>