diff --git a/gl/gl_mgr.sv b/gl/gl_mgr.sv
index 6dbc9d027a4147fb5455eafe1c2f68a6cfe5ede5..73f6eaa1def2b390ee7764065a4f7888fe0449f0 100644
--- a/gl/gl_mgr.sv
+++ b/gl/gl_mgr.sv
@@ -106,8 +106,11 @@ module gl_mgr (
 	// Paint Write
 	logic fb_PAINT_REQ;
 	logic [9:0] fb_PAINT_X, fb_PAINT_Y;
+	logic [9:0] fb_PAINT_X_san, fb_PAINT_Y_san;
 	logic [15:0] fb_PAINT_RGB16;
 	logic fb_PAINT_READY;
+	assign fb_PAINT_X_san = (fb_PAINT_X>10'd639) ? 10'd639 : fb_PAINT_X;
+	assign fb_PAINT_Y_san = (fb_PAINT_Y>10'd479) ? 10'd479 : fb_PAINT_Y;
 	gl_frame_buffer frame_buffer(
 		.CLK          (CLOCK),
 		.RESET        (RESET),
@@ -122,8 +125,8 @@ module gl_mgr (
 		.GL_DATA      (fb_GL_RGB),
 		.GL_READY     (fb_GL_READY),
 		.PAINT_REQ    (fb_PAINT_REQ),
-		.PAINT_X      (fb_PAINT_X),
-		.PAINT_Y      (fb_PAINT_Y),
+		.PAINT_X      (fb_PAINT_X_san),
+		.PAINT_Y      (fb_PAINT_Y_san),
 		.PAINT_RGB16  (fb_PAINT_RGB16),
 		.PAINT_READY  (fb_PAINT_READY),
 		.BUF_ACTIVE   (paint_buffer),
diff --git a/gl/painters/gl_painter_circle.sv b/gl/painters/gl_painter_circle.sv
index 408c37c07090c953e76392c96ccd076419cbc3d7..214025cdc7c619a74cfba2da2c241e87be18b608 100644
--- a/gl/painters/gl_painter_circle.sv
+++ b/gl/painters/gl_painter_circle.sv
@@ -37,6 +37,7 @@ module gl_painter_circle (
 	assign fb_PAINT_RGB16 = color;
 	assign RC_ADDR = {fb_PAINT_X, fb_PAINT_Y[8:0], PAINT_BUFFER};
 	assign RC_DATA_WR = 1'b1;
+	assign RC_WE = fb_PAINT_REQ;
 
 	always_ff @(posedge CLOCK) begin
 		if(RESET | ~EN) begin
@@ -72,7 +73,6 @@ module gl_painter_circle (
 		xlen_in = xlen;
 		color_in = color;
 		DONE = 1'b0;
-		RC_WE = 1'b0;
 		paint_req_in = 1'b0;
 
 		case (state)
@@ -88,7 +88,6 @@ module gl_painter_circle (
 
 			s_axis: begin
 				paint_req_in = 1'b1;
-				RC_WE = 1'b1;
 				if(fb_PAINT_READY) begin
 					// Value written, move to next pixel
 					x_in = x + 1;
@@ -127,7 +126,6 @@ module gl_painter_circle (
 
 			s_paint: begin
 				paint_req_in = 1'b1;
-				RC_WE = 1'b1;
 				if(fb_PAINT_READY) begin
 					// Value written, move to next pixel
 					x_in = x + 1;
diff --git a/gl/painters/gl_painter_image.sv b/gl/painters/gl_painter_image.sv
index 61a585201b6aaed94b7101642ee5f860bb3d6bb0..a7d53d27dd6654c5d1195689ea502fbe08547550 100644
--- a/gl/painters/gl_painter_image.sv
+++ b/gl/painters/gl_painter_image.sv
@@ -50,6 +50,7 @@ module gl_painter_image (
 
 	assign RC_ADDR = {fb_PAINT_X, fb_PAINT_Y[8:0], PAINT_BUFFER};
 	assign RC_DATA_WR = 1'b1;
+	assign RC_WE = fb_PAINT_REQ;
 
 	assign fb_GL_ADDR = img_ptr;
 
@@ -84,7 +85,6 @@ module gl_painter_image (
 		color_in = color;
 		i_in = i;
 		DONE = 1'b0;
-		RC_WE = 1'b0;
 		paint_req_in = 1'b0;
 		read_req_in = 1'b0;
 		img_ptr_in = img_ptr;
@@ -132,7 +132,6 @@ module gl_painter_image (
 
 			s_paint: begin
 				paint_req_in = 1'b1;
-				RC_WE = 1'b1;
 				if(fb_PAINT_READY) begin
 					// Value written, move to next location
 					i_in = i + 1;
diff --git a/gl/painters/gl_painter_polygon.sv b/gl/painters/gl_painter_polygon.sv
index 9647b64733d36e4f52cef2d59316b4c14a2433e2..a9b50a1deb3ef50b483443f3da5aab5d74c3c251 100644
--- a/gl/painters/gl_painter_polygon.sv
+++ b/gl/painters/gl_painter_polygon.sv
@@ -35,8 +35,10 @@ module gl_painter_polygon (
 	assign fb_PAINT_RGB16 = C0;
 	assign fb_PAINT_X = x;
 	assign fb_PAINT_Y = y;
+
 	assign RC_ADDR = {fb_PAINT_X, fb_PAINT_Y[8:0], PAINT_BUFFER};
 	assign RC_DATA_WR = 1'b1;
+	assign RC_WE = fb_PAINT_REQ;
 
 	// Inclined line scanner instances
 	logic [9:0] l_x1, l_y1, l_x2, l_y2, r_x1, r_y1, r_x2, r_y2;
@@ -81,7 +83,6 @@ module gl_painter_polygon (
 		y_in = y;
 		xbeg_in = xbeg;
 		DONE = 1'b0;
-		RC_WE = 1'b0;
 		paint_req_in = 1'b0;
 		l_lim_in = l_lim;
 		r_lim_in = r_lim;
@@ -96,7 +97,6 @@ module gl_painter_polygon (
 
 			s_scan_left: begin
 				paint_req_in = 1'b1;
-				RC_WE = 1'b1;
 				if(fb_PAINT_READY) begin
 					// Value written, move to next pixel
 					x_in = x - 1;
@@ -111,7 +111,6 @@ module gl_painter_polygon (
 
 			s_scan_right: begin
 				paint_req_in = 1'b1;
-				RC_WE = 1'b1;
 				if(fb_PAINT_READY) begin
 					// Value written, move to next pixel
 					x_in = x + 1;
diff --git a/gl/painters/gl_painter_rect.sv b/gl/painters/gl_painter_rect.sv
index 6f0288b5a32ebe69a2fb95f247af4e0799bd8df2..d545eb43fbd365eb6d1fa0e35857a649c31269d3 100644
--- a/gl/painters/gl_painter_rect.sv
+++ b/gl/painters/gl_painter_rect.sv
@@ -36,8 +36,10 @@ module gl_painter_rect (
 	assign fb_PAINT_X = x;
 	assign fb_PAINT_Y = y;
 	assign fb_PAINT_RGB16 = color;
+
 	assign RC_ADDR = {x, y[8:0], PAINT_BUFFER};
 	assign RC_DATA_WR = 1'b1;
+	assign RC_WE = fb_PAINT_REQ;
 
 	always_ff @(posedge CLOCK) begin
 		if(RESET | ~EN) begin
@@ -63,7 +65,6 @@ module gl_painter_rect (
 		y_in = y;
 		color_in = color;
 		DONE = 1'b0;
-		RC_WE = 1'b0;
 		paint_req_in = 1'b0;
 
 		case (state)
@@ -76,7 +77,6 @@ module gl_painter_rect (
 			end
 			s_paint: begin
 				paint_req_in = 1'b1;
-				RC_WE = 1'b1;
 				if(fb_PAINT_READY) begin
 					// Value written, move to next pixel
 					y_in = y + 1;
diff --git a/gl/painters/gl_painter_ring.sv b/gl/painters/gl_painter_ring.sv
index bccb30da215e25d4739de10ba5281a614112bf24..10c586e9eee67ea29c5bda533e6cb3cfea091633 100644
--- a/gl/painters/gl_painter_ring.sv
+++ b/gl/painters/gl_painter_ring.sv
@@ -33,8 +33,10 @@ module gl_painter_ring (
 	logic[2:0] quadrant, quadrant_in;
 
 	assign fb_PAINT_RGB16 = CSTROKE;
+
 	assign RC_ADDR = {fb_PAINT_X, fb_PAINT_Y[8:0], PAINT_BUFFER};
 	assign RC_DATA_WR = 1'b1;
+	assign RC_WE = fb_PAINT_REQ;
 
 	always_ff @(posedge CLOCK) begin
 		if(RESET | ~EN) begin
@@ -67,7 +69,6 @@ module gl_painter_ring (
 		y_in = y;
 		xlen_in = xlen;
 		DONE = 1'b0;
-		RC_WE = 1'b0;
 		paint_req_in = 1'b0;
 
 		case (state)
@@ -82,7 +83,6 @@ module gl_painter_ring (
 
 			s_axis: begin
 				paint_req_in = 1'b1;
-				RC_WE = 1'b1;
 				if(fb_PAINT_READY) begin
 					// Value written, move to next pixel
 					x_in = x + 1;
@@ -117,7 +117,6 @@ module gl_painter_ring (
 
 			s_paint: begin
 				paint_req_in = 1'b1;
-				RC_WE = 1'b1;
 				if(fb_PAINT_READY) begin
 					// Value written, move to next pixel
 					x_in = x + 1;
diff --git a/software/osu_main/src/gl/font.c b/software/osu_main/src/gl/font.c
new file mode 100644
index 0000000000000000000000000000000000000000..613aee2ff78722bece680b87bb8ffcedd056e6c3
--- /dev/null
+++ b/software/osu_main/src/gl/font.c
@@ -0,0 +1,61 @@
+#include "font.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "io.h"
+#include "gl.h"
+#include "regs.h"
+
+void gl_text(gl_text_ctx *context, const char *str, int x, int y) {
+	int idx;
+	for (char *c = str; *c; c++) {
+		idx = c - ' ';
+		if (idx < 0) {
+			// Render char 32 (unknown symbol)
+			context->x_[0][context->count_[0]] = x;
+			context->y_[0][context->count_[0]] = y;
+			context->count_[0]++;
+			x += context->font->widths[0];
+		} else if (idx == 0) {
+			// Skip
+			x += context->font->widths[0];
+		} else {
+			// Render char
+			context->x_[idx][context->count_[idx]] = x;
+			context->y_[idx][context->count_[idx]] = y;
+			context->count_[idx]++;
+			x += context->font->widths[idx];
+		}
+	}
+}
+
+void gl_text_flush(gl_text_ctx *context) {
+	gl_wait();
+	for (int c = 0; c < 96; c++) {
+		GL_IOWR(GL_ARGS, 0, context->font->font[i]);
+		GL_IOWR(GL_ARGS, 1, gl_make_point(context->font->widths[i], context->font->height));
+		int argptr = 2, cptr = context->count_[c];
+		while (cptr --> 0) {
+			GL_IOWR(GL_ARGS, argptr++, gl_make_point(context->x_[cptr], context->y_[cptr]));
+			if (argptr == 8) {
+				// Flush
+				GL_IOWR(GL_COMMAND, 0, GL_CMD_IMAGE);
+				gl_wait();
+				GL_IOWR(GL_COMMAND, 0, GL_CMD_NOOP);
+			}
+		}
+		if (argptr != 2) {
+			// Remaining undrawn characters. Flush
+			GL_IOWR(GL_ARGS, argptr, -1);
+			GL_IOWR(GL_COMMAND, 0, GL_CMD_IMAGE);
+			gl_wait();
+			GL_IOWR(GL_COMMAND, 0, GL_CMD_NOOP);
+		}
+	}
+}
+
+void gl_text_clear(gl_text_ctx *context) {
+	memset(context->count_, 0, sizeof(context->count_));
+}
diff --git a/software/osu_main/src/gl/font.h b/software/osu_main/src/gl/font.h
new file mode 100644
index 0000000000000000000000000000000000000000..b4b875b1a8fe16deecbb8df2a73eed43b986f443
--- /dev/null
+++ b/software/osu_main/src/gl/font.h
@@ -0,0 +1,17 @@
+#ifndef FONT_H_
+#define FONT_H_
+
+#include "../sdcard/osufs/osufs.h"
+#include <inttypes.h>
+
+typedef struct gl_text_ctx_t {
+	osu_font *font;
+	uint8_t count_[96];
+	uint16_t x_[96][128], y_[96][128];
+} gl_text_ctx;
+
+void gl_text(gl_text_ctx *context, const char *str, int x, int y);
+void gl_text_flush(gl_text_ctx *context);
+void gl_text_clear(gl_text_ctx *context);
+
+#endif