OpenCores
URL https://opencores.org/ocsvn/forth-cpu/forth-cpu/trunk

Subversion Repositories forth-cpu

[/] [forth-cpu/] [trunk/] [gui.c] - Rev 5

Compare with Previous | Blame | View Log

/**@file      gui.c
 * @brief     GUI Simulator for the H2 SoC
 * @copyright Richard James Howe (2017-2019)
 * @license   MIT
 * @note It would have been better to make this in SDL, se
 * <https://www.libsdl.org/>.  */
 
#include "h2.h"
#include <assert.h>
#include <errno.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <math.h>
#include <string.h>
#include <stdint.h>
#include <inttypes.h>
#include <GL/gl.h>
#include <GL/glut.h>
#include <GL/freeglut_ext.h> /* for glutStrokeHeight */
#include <stdarg.h>
 
#define TRON (0)
 
/* ====================================== Utility Functions ==================================== */
 
#define PI               (3.1415926535897932384626433832795)
#define MAX(X, Y)        ((X) > (Y) ? (X) : (Y))
#define MIN(X, Y)        ((X) < (Y) ? (X) : (Y))
#define UNUSED(X)        ((void)(X))
#define X_MAX            (100.0)
#define X_MIN            (0.0)
#define Y_MAX            (100.0)
#define Y_MIN            (0.0)
#define LINE_WIDTH       (0.5)
#define CYCLE_MODE_FIXED (false)
#define CYCLE_INITIAL    (100000)
#define CYCLE_INCREMENT  (10000)
#define CYCLE_DECREMENT  (500)
#define CYCLE_MINIMUM    (10000)
#define CYCLE_HYSTERESIS (2.0)
#define TARGET_FPS       (30.0)
#define BACKGROUND_ON    (false)
#define SIM_HACKS        (true)
#define TRACE_FILE       ("trace.csv")
#define TRACE_BUFFER_LEN (16*4096)
 
typedef struct {
	double window_height;
	double window_width;
	double window_x_starting_position;
	double window_y_starting_position;
	double window_scale_x;
	double window_scale_y;
	void *font_scaled;
	uint64_t cycle_count;
	uint64_t cycles;
	volatile unsigned tick;
	unsigned arena_tick_ms;
	volatile bool halt_simulation;
	bool use_uart_input;
	bool debug_extra;
	bool step;
	bool debug_mode;
} world_t;
 
static world_t world = {
	.window_height               = 800.0,
	.window_width                = 800.0,
	.window_x_starting_position  = 60.0,
	.window_y_starting_position  = 20.0,
	.window_scale_x              = 1.0,
	.window_scale_y              = 1.0,
	.tick                        = 0,
	.halt_simulation             = false,
	.arena_tick_ms               = 30,
	.use_uart_input              = true,
	.debug_extra                 = false,
	.step                        = false,
	.debug_mode                  = false,
	.cycle_count                 = 0,
	.cycles                      = CYCLE_INITIAL,
	.font_scaled                 = GLUT_STROKE_MONO_ROMAN
};
 
static FILE *trace_file = NULL; /* NB. !NULL turns tracing on */
static char trace_buffer[TRACE_BUFFER_LEN];
 
typedef enum {
	TRIANGLE,
	SQUARE,
	PENTAGON,
	HEXAGON,
	SEPTAGON,
	OCTAGON,
	DECAGON,
	CIRCLE,
	INVALID_SHAPE
} shape_e;
 
typedef shape_e shape_t;
 
typedef struct {
	double x;
	double y;
} scale_t;
 
typedef struct {
	double x, y;
	bool draw_border;
	color_t color_text, color_box;
	double width, height;
} textbox_t;
 
typedef struct { /**@note it might be worth translating some functions to use points*/
	double x, y;
} point_t;
 
 
/**@bug not quite correct, arena_tick_ms is what we request, not want the arena
 * tick actually is */
static double seconds_to_ticks(const world_t *world, double s) {
	assert(world);
	return s * (1000. / (double)world->arena_tick_ms);
}
 
static double rad2deg(const double rad) {
	return (rad / (2.0 * PI)) * 360.0;
}
 
static void set_color(const color_t color, const bool light) {
	double ON = light ? 0.8 : 0.4;
	static const double OFF = 0.0;
	switch (color) {      /* RED  GRN  BLU */
	case WHITE:   glColor3f( ON,  ON,  ON);   break;
	case RED:     glColor3f( ON, OFF, OFF);   break;
	case YELLOW:  glColor3f( ON,  ON, OFF);   break;
	case GREEN:   glColor3f(OFF,  ON, OFF);   break;
	case CYAN:    glColor3f(OFF,  ON,  ON);   break;
	case BLUE:    glColor3f(OFF, OFF,  ON);   break;
	case MAGENTA: glColor3f( ON, OFF,  ON);   break;
	case BLACK:   glColor3f(OFF, OFF, OFF);   break;
	default:      fatal("invalid color '%d'", color);
	}
}
 
/* see: https://www.opengl.org/discussion_boards/showthread.php/160784-Drawing-Circles-in-OpenGL */
static void _draw_regular_polygon(
		const double x, const double y,
		const double orientation,
		const double radius, const double sides,
		const bool lines, const double thickness,
		const color_t color) {
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
		glLoadIdentity();
		glTranslatef(x, y, 0.0);
		glRotated(rad2deg(orientation), 0, 0, 1);
		set_color(color, true);
		if (lines) {
			glLineWidth(thickness);
			glBegin(GL_LINE_LOOP);
		} else {
			glBegin(GL_POLYGON);
		}
			for (double i = 0; i < 2.0 * PI; i += PI / sides)
				glVertex3d(cos(i) * radius, sin(i) * radius, 0.0);
		glEnd();
	glPopMatrix();
}
 
static void _draw_rectangle(
		const double x, const double y,
		const double width, const double height,
		const bool lines, const double thickness,
		const color_t color) {
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
		glLoadIdentity();
		glRasterPos2d(x, y);
		set_color(color, true);
		if (lines) {
			glLineWidth(thickness);
			glBegin(GL_LINE_LOOP);
		} else {
			glBegin(GL_POLYGON);
		}
		glVertex3d(x,       y,        0);
		glVertex3d(x+width, y,        0);
		glVertex3d(x+width, y+height, 0);
		glVertex3d(x,       y+height, 0);
		glEnd();
	glPopMatrix();
}
 
static void draw_rectangle_filled(const double x, const double y, const double width, const double height, const color_t color) {
	_draw_rectangle(x, y, width, height, false, 0, color);
}
 
static void draw_rectangle_line(const double x, const double y, const double width, const double height, const double thickness, const color_t color) {
	_draw_rectangle(x, y, width, height, true, thickness, color);
}
 
static double shape_to_sides(shape_t shape) {
	static const double sides[] =
	{
		[TRIANGLE] = 1.5,
		[SQUARE]   = 2,
		[PENTAGON] = 2.5,
		[HEXAGON]  = 3,
		[SEPTAGON] = 3.5,
		[OCTAGON]  = 4,
		[DECAGON]  = 5,
		[CIRCLE]   = 24
	};
	if (shape >= INVALID_SHAPE)
		fatal("invalid shape '%d'", shape);
	return sides[shape % INVALID_SHAPE];
}
 
static void draw_regular_polygon_filled(const double x, const double y, const double orientation, const double radius, const shape_t shape, const color_t color) {
	_draw_regular_polygon(x, y, orientation, radius, shape_to_sides(shape), false, 0, color);
}
 
static void draw_regular_polygon_line(const double x, const double y, const double orientation, const double radius, const shape_t shape, const double thickness, const color_t color) {
	_draw_regular_polygon(x, y, orientation, radius, shape_to_sides(shape), true, thickness, color);
}
 
static void draw_char(uint8_t c) {
	c = c >= 32 && c <= 127 ? c : '?';
	glutStrokeCharacter(world.font_scaled, c);
}
 
/* see: https://en.wikibooks.org/wiki/OpenGL_Programming/Modern_OpenGL_Tutorial_Text_Rendering_01
 *      https://stackoverflow.com/questions/538661/how-do-i-draw-text-with-glut-opengl-in-c
 *      https://stackoverflow.com/questions/20866508/using-glut-to-simply-print-text */
static int draw_block(const uint8_t *msg, const size_t len) {
	assert(msg);
	for (size_t i = 0; i < len; i++)
		draw_char(msg[i]);
	return len;
}
 
static int draw_string(const char *msg) {
	assert(msg);
	return draw_block((uint8_t*)msg, strlen(msg));
}
 
static scale_t font_attributes(void) {
	static bool initialized = false;  // @warning static!
	static scale_t scale = { 0., 0.}; // @warning static!
	if (initialized)
		return scale;
	scale.y = glutStrokeHeight(world.font_scaled);
	scale.x = glutStrokeWidth(world.font_scaled, 'M');
	initialized = true;
	return scale;
}
 
static void draw_vt100_char(
		const double x, const double y,
		const double scale_x, const double scale_y,
		const double orientation, const uint8_t c,
		const vt100_attribute_t *attr, const bool blink) {
	assert(attr);
	/*scale_t scale = font_attributes();
	double char_width  = scale.x / X_MAX;
       	double char_height = scale.y / Y_MAX;*/
 
	if (blink && attr->blink)
		return;
 
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
		glLoadIdentity();
		glTranslatef(x, y, 0.0);
		glScaled(scale_x, scale_y, 1.0);
		glRotated(rad2deg(orientation), 0, 0, 1);
		set_color(attr->foreground_color, attr->bold);
		draw_char(attr->conceal ? '*' : c);
		glEnd();
	glPopMatrix();
	if (BACKGROUND_ON)
		draw_rectangle_filled(x, y, 1.20, 1.55, attr->background_color);
}
 
static int draw_vt100_block(
		const double x, const double y,
		const double scale_x, const double scale_y,
		const double orientation, const uint8_t *msg,
		const size_t len, const vt100_attribute_t * const attr, const bool blink) {
	assert(attr);
	const scale_t scale = font_attributes();
	const double char_width = (scale.x / X_MAX)*1.1;
	for (size_t i = 0; i < len; i++)
		draw_vt100_char(x+char_width*i, y, scale_x, scale_y, orientation, msg[i], &attr[i], blink);
	return len;
}
 
static int draw_block_scaled(
		const double x, const double y,
		const double scale_x, const double scale_y,
		const double orientation, const uint8_t *msg, const size_t len, const color_t color) {
	assert(msg);
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
		glLoadIdentity();
		glTranslatef(x, y, 0.0);
		glScaled(scale_x, scale_y, 1.0);
		glRotated(rad2deg(orientation), 0, 0, 1);
		set_color(color, true);
		for (size_t i = 0; i < len; i++) {
			uint8_t c = msg[i];
			c = c >= 32 && c <= 127 ? c : '?';
			glutStrokeCharacter(world.font_scaled, c);
		}
		glEnd();
	glPopMatrix();
	return len;
}
 
static int draw_string_scaled(
		const double x, const double y,
		const double scale_x, const double scale_y,
		const double orientation, const char *msg, const color_t color) {
	assert(msg);
	return draw_block_scaled(x, y, scale_x, scale_y, orientation, (uint8_t*)msg, strlen(msg), color);
}
 
static int vdraw_text(const color_t color, const double x, const double y, const char *fmt, va_list ap) {
	int r = 0;
	assert(fmt);
	static const double scale_x = 0.011;
	static const double scale_y = 0.011;
 
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	set_color(color, true);
	glTranslatef(x, y, 0);
	glScaled(scale_x, scale_y, 1.0);
	while (*fmt) {
		char f = *fmt++;
		if ('%' != f) {
			glutStrokeCharacter(world.font_scaled, f);
			r++;
			continue;
		}
		switch ((f = *fmt++)) {
		case 'c':
		{
			char x[2] = {0, 0};
			x[0] = va_arg(ap, int);
			r += draw_string(x);
			break;
		}
		case 's':
		{
			char *s = va_arg(ap, char*);
			r += draw_string(s);
			break;
		}
		case 'x':
		{
			const unsigned d = va_arg(ap, unsigned);
			char s[64] = {0};
			sprintf(s, "%04x", d);
			r += draw_string(s);
			break;
		}
		case 'u':
		case 'd':
		{
			const int d = va_arg(ap, int);
			char s[64] = {0};
			sprintf(s, f == 'u' ? "%u": "%d", d);
			r += draw_string(s);
			break;
		}
		case 'f':
		{
			const double f = va_arg(ap, double);
			char s[512] = {0};
			sprintf(s, "%.2f", f);
			r += draw_string(s);
			break;
		}
		case 0:
		default:
			fatal("invalid format specifier '%c'", f);
		}
 
	}
	glPopMatrix();
	return r;
}
 
static void fill_textbox(textbox_t *t, const char *fmt, ...) {
	va_list ap;
	assert(t);
	assert(fmt);
 
	scale_t scale = font_attributes();
	const double char_width = scale.x / X_MAX;
	const double char_height = scale.y / Y_MAX;
	assert(t && fmt);
	va_start(ap, fmt);
	double r = vdraw_text(t->color_text, t->x, t->y - t->height, fmt, ap);
	r *= char_width * 1.11;
	r += 1;
	va_end(ap);
	t->width = MAX(t->width, r);
	t->height += (char_height); /*correct?*/
}
 
static void draw_textbox(textbox_t *t) {
	assert(t);
	scale_t scale = font_attributes();
	const double char_height = scale.y / Y_MAX;
	if (!(t->draw_border))
		return;
	draw_rectangle_line(t->x - LINE_WIDTH, t->y - t->height + char_height - 1, t->width, t->height + 1, LINE_WIDTH, t->color_box);
}
 
static bool detect_circle_circle_collision(
		const double ax, const double ay, const double aradius,
		const double bx, const double by, const double bradius) {
	const double dx = ax - bx;
	const double dy = ay - by;
	const double distance = hypot(dx, dy);
	return distance < (aradius + bradius);
}
 
/* ====================================== Utility Functions ==================================== */
 
/* ====================================== Simulator Objects ==================================== */
 
typedef struct {
	double x;
	double y;
	double angle;
	double radius;
	bool on;
} led_t;
 
static void draw_led(const led_t * const l) {
	assert(l);
	const double msz = l->radius * 0.75;
	const double off = (l->radius - msz) / 2.0;
	draw_rectangle_filled(l->x + off, l->y + off, msz, msz, l->on ? GREEN : RED);
	draw_rectangle_filled(l->x, l->y, l->radius, l->radius, BLUE);
}
 
typedef struct {
	double x;
	double y;
	double angle;
	double radius;
	bool on;
} switch_t;
 
static void draw_switch(const switch_t * const s) {
	assert(s);
	const double msz = s->radius * 0.6;
	const double off = (s->radius - msz) / 2.0;
	draw_rectangle_filled(s->x + off, s->on ? (s->y + s->radius) - off : s->y+off, msz, msz, s->on ? GREEN : RED);
	draw_rectangle_filled(s->x + off, s->y + off, msz, msz * 2., BLACK);
	draw_rectangle_filled(s->x, s->y, s->radius, s->radius * 2, BLUE);
}
 
typedef enum {
	LED_SEGMENT_A,
	LED_SEGMENT_B,
	LED_SEGMENT_C,
	LED_SEGMENT_D,
	LED_SEGMENT_E,
	LED_SEGMENT_F,
	LED_SEGMENT_G,
	LED_SEGMENT_DP,
} led_segment_e;
 
typedef struct {
	double x;
	double y;
	/*double angle;*/
	double width;
	double height;
	uint8_t segment;
} led_8_segment_t;
 
static uint8_t convert_to_segments(const uint8_t segment) {
	static const uint8_t c2s[16] = {
		0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07,
		0x7F, 0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71
	};
	return c2s[segment & 0xf];
}
 
#define SEG_CLR(SG,BIT) (((SG) & (1 << BIT)) ? RED : BLACK)
 
static void draw_led_8_segment(const led_8_segment_t * const l) {
	assert(l);
	const uint8_t sgs = convert_to_segments(l->segment);
 
	draw_rectangle_filled(l->x + l->width * 0.20, l->y + l->height * 0.45, l->width * 0.5, l->height * 0.1, SEG_CLR(sgs, LED_SEGMENT_G)); /* Center */
	draw_rectangle_filled(l->x + l->width * 0.20, l->y + l->height * 0.1,  l->width * 0.5, l->height * 0.1, SEG_CLR(sgs, LED_SEGMENT_D)); /* Bottom */
	draw_rectangle_filled(l->x + l->width * 0.20, l->y + l->height * 0.8,  l->width * 0.5, l->height * 0.1, SEG_CLR(sgs, LED_SEGMENT_A)); /* Top */
 
	draw_rectangle_filled(l->x + l->width * 0.05, l->y + l->height * 0.15, l->width * 0.1, l->height * 0.3, SEG_CLR(sgs, LED_SEGMENT_E)); /* Bottom Left */
	draw_rectangle_filled(l->x + l->width * 0.75, l->y + l->height * 0.15, l->width * 0.1, l->height * 0.3, SEG_CLR(sgs, LED_SEGMENT_C)); /* Bottom Right */
 
	draw_rectangle_filled(l->x + l->width * 0.05, l->y + l->height * 0.50, l->width * 0.1, l->height * 0.3, SEG_CLR(sgs, LED_SEGMENT_F)); /* Top Left */
	draw_rectangle_filled(l->x + l->width * 0.75, l->y + l->height * 0.50, l->width * 0.1, l->height * 0.3, SEG_CLR(sgs, LED_SEGMENT_B)); /* Top Right */
 
	draw_regular_polygon_filled(l->x + l->width * 0.9, l->y + l->height * 0.07, 0.0, sqrt(l->width*l->height)*.06, CIRCLE, SEG_CLR(sgs, LED_SEGMENT_DP));
 
	draw_rectangle_filled(l->x, l->y, l->width, l->height, WHITE);
}
 
typedef struct {
	double x;
	double y;
	double angle;
	double radius;
 
	bool up;
	bool down;
	bool left;
	bool right;
	bool center;
} dpad_t;
 
static void draw_dpad(const dpad_t * const d) {
	assert(d);
	draw_regular_polygon_filled(d->x + (d->radius*2.0), d->y,                   d->angle,            d->radius, TRIANGLE, d->right  ? GREEN : RED);
	draw_regular_polygon_filled(d->x - (d->radius*2.0), d->y,                   d->angle + (PI/3.0), d->radius, TRIANGLE, d->left   ? GREEN : RED);
	draw_regular_polygon_filled(d->x,                   d->y - (d->radius*2.0), d->angle - (PI/2.0), d->radius, TRIANGLE, d->down   ? GREEN : RED);
	draw_regular_polygon_filled(d->x,                   d->y + (d->radius*2.0), d->angle + (PI/2.0), d->radius, TRIANGLE, d->up     ? GREEN : RED);
	draw_regular_polygon_filled(d->x,                   d->y,                   d->angle,            d->radius, CIRCLE,   d->center ? GREEN : RED);
 
	draw_regular_polygon_line(d->x, d->y, d->angle, d->radius * 3.1, CIRCLE, LINE_WIDTH, WHITE);
}
 
typedef enum {
	DPAN_COL_NONE,
	DPAN_COL_RIGHT,
	DPAN_COL_LEFT,
	DPAN_COL_DOWN,
	DPAN_COL_UP,
	DPAN_COL_CENTER
} dpad_collision_e;
 
static dpad_collision_e dpad_collision(const dpad_t * const d, const double x, const double y, const double radius) {
	assert(d);
	if (detect_circle_circle_collision(x, y, radius, d->x + (d->radius*2.0), d->y,                   d->radius))
		return DPAN_COL_RIGHT;
	if (detect_circle_circle_collision(x, y, radius, d->x - (d->radius*2.0), d->y,                   d->radius))
		return DPAN_COL_LEFT;
	if (detect_circle_circle_collision(x, y, radius, d->x,                   d->y + (d->radius*2.0), d->radius))
		return DPAN_COL_UP;
	if (detect_circle_circle_collision(x, y, radius, d->x,                   d->y - (d->radius*2.0), d->radius))
		return DPAN_COL_DOWN;
	if (detect_circle_circle_collision(x, y, radius, d->x,                   d->y,                   d->radius))
		return DPAN_COL_CENTER;
	return DPAN_COL_NONE;
}
 
#define TERMINAL_WIDTH       (80)
#define TERMINAL_HEIGHT      (10)
#define TERMINAL_SIZE        (TERMINAL_WIDTH*TERMINAL_HEIGHT)
 
typedef struct {
	unsigned width;
	unsigned height;
	GLuint name;
	uint8_t *image;
} vt100_background_texture_t;
 
typedef struct {
	uint64_t blink_count;
	double x;
	double y;
	bool blink_on;
	color_t color;
	vt100_t vt100;
	vt100_background_texture_t *texture;
} terminal_t;
 
static void texture_background(const terminal_t * const t) {
	assert(t);
	const vt100_background_texture_t *v = t->texture;
	const vt100_t *vt = &t->vt100;
	uint8_t *const img = v->image;
	const unsigned h = v->height;
	const unsigned w = v->width;
 
	for (unsigned i = 0; i < h; i++) {
		uint8_t * const row = &img[i * 4];
		const unsigned ii = ((h - i - 1) * vt->height) / h;
		for (unsigned j = 0; j < w; j++) {
			uint8_t * const column = &row[j * h * 4];
			const unsigned jj = (vt->width * j) / w;
			const unsigned idx = jj + (ii * vt->width);
			column[0] = 255 * (vt->attributes[idx].background_color & 1);
			column[1] = 255 * (vt->attributes[idx].background_color & 2);
			column[2] = 255 * (vt->attributes[idx].background_color & 4);
			column[3] = 255;
		}
	}
}
 
/* See <http://www.glprogramming.com/red/chapter09.html> */
static void draw_texture(const terminal_t * const t, const bool update) {
	assert(t);
	vt100_background_texture_t *v = t->texture;
	if (!v)
		return;
 
	const scale_t scale = font_attributes();
	const double char_width  = scale.x / X_MAX;
       	const double char_height = scale.y / Y_MAX;
	const double x = t->x;
	const double y = t->y - (char_height * (t->vt100.height-1.0));
	const double width  = char_width  * t->vt100.width * 1.10;
	const double height = char_height * t->vt100.height;
 
	glEnable(GL_TEXTURE_2D);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
 
	if (update) {
		glClearColor (0.0, 0.0, 0.0, 0.0);
		glShadeModel(GL_FLAT);
		glEnable(GL_DEPTH_TEST);
 
		texture_background(t);
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
 
		glGenTextures(1, &v->name);
		glBindTexture(GL_TEXTURE_2D, v->name);
 
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
		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, v->width, v->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, v->image);
	}
 
	glBindTexture(GL_TEXTURE_2D, v->name);
	glMatrixMode(GL_MODELVIEW);
	glBegin(GL_QUADS);
		glTexCoord2f(1.0, 0.0); glVertex3f(x,       y+height, 0.0);
		glTexCoord2f(1.0, 1.0); glVertex3f(x+width, y+height, 0.0);
		glTexCoord2f(0.0, 1.0); glVertex3f(x+width, y,        0.0);
		glTexCoord2f(0.0, 0.0); glVertex3f(x,       y,        0.0);
	glEnd();
	glDisable(GL_TEXTURE_2D);
}
 
void draw_terminal(const world_t *world, terminal_t *t, const char * const name) {
	assert(world);
	assert(t);
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
 
	static const double scale_x = 0.011;
	static const double scale_y = 0.011;
	const vt100_t * const v = &t->vt100;
	const scale_t scale = font_attributes();
	const double now = world->tick - t->blink_count;
	const double char_width  = scale.x / X_MAX;
       	const double char_height = scale.y / Y_MAX;
	const size_t cursor_x = v->cursor % v->width;
	const size_t cursor_y = v->cursor / v->width;
 
	if (now > seconds_to_ticks(world, 1.0)) {
		t->blink_on = !(t->blink_on);
		t->blink_count = world->tick;
	}
 
	/**@note the cursor is deliberately in a different position compared to draw_vga(), due to how the VGA cursor behaves in hardware */
	if ((!(v->blinks) || t->blink_on) && v->cursor_on) /* fudge factor of 1.10? */
		draw_rectangle_filled(t->x + (char_width * 1.10 * (cursor_x)) , t->y - (char_height * cursor_y), char_width, char_height, WHITE);
 
 
	for (size_t i = 0; i < t->vt100.height; i++)
		draw_vt100_block(t->x, t->y - ((double)i * char_height), scale_x, scale_y, 0, v->m + (i*v->width), v->width, v->attributes + (i*v->width), t->blink_on);
	draw_string_scaled(t->x, t->y - (v->height * char_height), scale_x, scale_y, 0, name, t->color);
 
	/* fudge factor = 1/((1/scale_x)/X_MAX) ??? */
 
	glPopMatrix();
 
	draw_rectangle_line(t->x, t->y - (char_height * (v->height-1.0)), char_width * v->width * 1.10, char_height * v->height, LINE_WIDTH, t->color);
}
 
/* ====================================== Simulator Objects ==================================== */
 
/* ====================================== Simulator Instances ================================== */
 
#define SWITCHES_X       (10.0)
#define SWITCHES_SPACING (0.6)
#define SWITCHES_Y       (5.0)
#define SWITCHES_ANGLE   (0.0)
#define SWITCHES_RADIUS  (2.0)
#define SWITCHES_COUNT   (8)
 
static switch_t switches[SWITCHES_COUNT] = {
	{ .x = SWITCHES_X * SWITCHES_SPACING * 8.0, .y = SWITCHES_Y, .angle = SWITCHES_ANGLE, .radius = SWITCHES_RADIUS, .on = false },
	{ .x = SWITCHES_X * SWITCHES_SPACING * 7.0, .y = SWITCHES_Y, .angle = SWITCHES_ANGLE, .radius = SWITCHES_RADIUS, .on = false },
	{ .x = SWITCHES_X * SWITCHES_SPACING * 6.0, .y = SWITCHES_Y, .angle = SWITCHES_ANGLE, .radius = SWITCHES_RADIUS, .on = false },
	{ .x = SWITCHES_X * SWITCHES_SPACING * 5.0, .y = SWITCHES_Y, .angle = SWITCHES_ANGLE, .radius = SWITCHES_RADIUS, .on = false },
	{ .x = SWITCHES_X * SWITCHES_SPACING * 4.0, .y = SWITCHES_Y, .angle = SWITCHES_ANGLE, .radius = SWITCHES_RADIUS, .on = false },
	{ .x = SWITCHES_X * SWITCHES_SPACING * 3.0, .y = SWITCHES_Y, .angle = SWITCHES_ANGLE, .radius = SWITCHES_RADIUS, .on = false },
	{ .x = SWITCHES_X * SWITCHES_SPACING * 2.0, .y = SWITCHES_Y, .angle = SWITCHES_ANGLE, .radius = SWITCHES_RADIUS, .on = false },
	{ .x = SWITCHES_X * SWITCHES_SPACING * 1.0, .y = SWITCHES_Y, .angle = SWITCHES_ANGLE, .radius = SWITCHES_RADIUS, .on = false },
};
 
#define LEDS_X       (10.0)
#define LEDS_SPACING (0.6)
#define LEDS_Y       (10.0)
#define LEDS_ANGLE   (0.0)
#define LEDS_RADIUS  (2.0)
#define LEDS_COUNT   (8)
 
static led_t leds[LEDS_COUNT] = {
	{ .x = LEDS_X * LEDS_SPACING * 8.0, .y = LEDS_Y, .angle = LEDS_ANGLE, .radius = LEDS_RADIUS, .on = false },
	{ .x = LEDS_X * LEDS_SPACING * 7.0, .y = LEDS_Y, .angle = LEDS_ANGLE, .radius = LEDS_RADIUS, .on = false },
	{ .x = LEDS_X * LEDS_SPACING * 6.0, .y = LEDS_Y, .angle = LEDS_ANGLE, .radius = LEDS_RADIUS, .on = false },
	{ .x = LEDS_X * LEDS_SPACING * 5.0, .y = LEDS_Y, .angle = LEDS_ANGLE, .radius = LEDS_RADIUS, .on = false },
 
	{ .x = LEDS_X * LEDS_SPACING * 4.0, .y = LEDS_Y, .angle = LEDS_ANGLE, .radius = LEDS_RADIUS, .on = false },
	{ .x = LEDS_X * LEDS_SPACING * 3.0, .y = LEDS_Y, .angle = LEDS_ANGLE, .radius = LEDS_RADIUS, .on = false },
	{ .x = LEDS_X * LEDS_SPACING * 2.0, .y = LEDS_Y, .angle = LEDS_ANGLE, .radius = LEDS_RADIUS, .on = false },
	{ .x = LEDS_X * LEDS_SPACING * 1.0, .y = LEDS_Y, .angle = LEDS_ANGLE, .radius = LEDS_RADIUS, .on = false },
};
 
static dpad_t dpad = {
	.x       =  X_MAX - 8.0,
	.y       =  Y_MIN + 8.0,
	.angle   =  0.0,
	.radius  =  2.0,
	.up      =  false,
	.down    =  false,
	.left    =  false,
	.right   =  false,
	.center  =  false,
};
 
#define VGA_TEXTURE_WIDTH  (256)
#define VGA_TEXTURE_HEIGHT (256)
static uint8_t vga_background_image[VGA_TEXTURE_WIDTH*VGA_TEXTURE_HEIGHT*4];
 
static vt100_background_texture_t vga_background_texture = {
	.width  = VGA_TEXTURE_WIDTH,
	.height = VGA_TEXTURE_HEIGHT,
	.name   = 0,
	.image  = (uint8_t*)vga_background_image
};
 
static terminal_t vga_terminal = {
	.blink_count = 0,
	.x           = X_MIN + 2.0,
	.y           = Y_MAX - 8.0,
	.color       = GREEN,  /* WHITE */
	.blink_on    = false,
 
	.vt100  = {
		.width        = VGA_WIDTH,
		.height       = VGA_HEIGHT,
		.size         = VGA_WIDTH * VGA_HEIGHT,
		.cursor       = 0,
		.cursor_saved = 0,
		.state        = TERMINAL_NORMAL_MODE,
		.cursor_on    = true,
		.blinks       = false,
		.n1           = 1,
		.n2           = 1,
		.m            = { 0 },
		.attribute    = { 0 },
		.attributes   = { { 0 } },
	},
	.texture = &vga_background_texture
};
 
#define SEGMENT_COUNT   (4)
#define SEGMENT_SPACING (1.1)
#define SEGMENT_X       (50)
#define SEGMENT_Y       (3)
#define SEGMENT_WIDTH   (6)
#define SEGMENT_HEIGHT  (8)
 
static led_8_segment_t segments[SEGMENT_COUNT] = {
	{ .x = SEGMENT_X + (SEGMENT_SPACING * SEGMENT_WIDTH * 1.0), .y = SEGMENT_Y, .width = SEGMENT_WIDTH, .height = SEGMENT_HEIGHT, .segment = 0 },
	{ .x = SEGMENT_X + (SEGMENT_SPACING * SEGMENT_WIDTH * 2.0), .y = SEGMENT_Y, .width = SEGMENT_WIDTH, .height = SEGMENT_HEIGHT, .segment = 0 },
	{ .x = SEGMENT_X + (SEGMENT_SPACING * SEGMENT_WIDTH * 3.0), .y = SEGMENT_Y, .width = SEGMENT_WIDTH, .height = SEGMENT_HEIGHT, .segment = 0 },
	{ .x = SEGMENT_X + (SEGMENT_SPACING * SEGMENT_WIDTH * 4.0), .y = SEGMENT_Y, .width = SEGMENT_WIDTH, .height = SEGMENT_HEIGHT, .segment = 0 },
};
 
#define UART_TEXTURE_WIDTH  (256)
#define UART_TEXTURE_HEIGHT (256)
static uint8_t uart_background_image[UART_TEXTURE_WIDTH*UART_TEXTURE_HEIGHT*4];
 
static vt100_background_texture_t uart_background_texture = {
	.width  = UART_TEXTURE_WIDTH,
	.height = UART_TEXTURE_HEIGHT,
	.name   = 0,
	.image  = (uint8_t*)uart_background_image
};
 
static terminal_t uart_terminal = {
	.blink_count = 0,
	.x           = X_MIN + 2.0,
	.y           = Y_MIN + 28.5,
	.color       = BLUE,
	.blink_on    = false,
 
	.vt100 = {
		.width        = TERMINAL_WIDTH,
		.height       = TERMINAL_HEIGHT,
		.size         = TERMINAL_SIZE,
		.cursor       = 0,
		.cursor_saved = 0,
		.state        = TERMINAL_NORMAL_MODE,
		.cursor_on    = true,
		.n1           = 1,
		.n2           = 1,
		.blinks      = false,
		.m            = { 0 },
		.attribute    = { 0 },
		.attributes   = { { 0 } },
	},
	.texture = &uart_background_texture
};
 
static h2_t *h = NULL;
static h2_io_t *h2_io = NULL;
static fifo_t *uart_rx_fifo = NULL;
static fifo_t *uart_tx_fifo = NULL;
static fifo_t *ps2_rx_fifo = NULL;
 
/* ====================================== Simulator Instances ================================== */
 
/* ====================================== H2 I/O Handling ====================================== */
 
static uint16_t h2_io_get_gui(h2_soc_state_t * const soc, const uint16_t addr, bool *debug_on) {
	assert(soc);
	assert(ps2_rx_fifo);
	assert(uart_tx_fifo);
	assert(uart_rx_fifo);
 
	if (debug_on)
		*debug_on = false;
	switch (addr) {
	case iUart:
		return (fifo_is_empty(uart_tx_fifo) << UART_TX_FIFO_EMPTY_BIT)
			| (fifo_is_full(uart_tx_fifo)  << UART_TX_FIFO_FULL_BIT)
			| (fifo_is_empty(uart_rx_fifo) << UART_RX_FIFO_EMPTY_BIT)
			| (fifo_is_full(uart_rx_fifo)  << UART_RX_FIFO_FULL_BIT)
			| soc->uart_getchar_register;
	case iVT100:
		return (1u << UART_TX_FIFO_EMPTY_BIT)
			| (0u << UART_TX_FIFO_FULL_BIT)
			| (fifo_is_empty(ps2_rx_fifo) << UART_RX_FIFO_EMPTY_BIT)
			| (fifo_is_full(ps2_rx_fifo)  << UART_RX_FIFO_FULL_BIT)
			| soc->ps2_getchar_register;
	case iSwitches:
		return soc->switches;
	case iTimerDin: return soc->timer;
	case iMemDin:   return h2_io_memory_read_operation(soc);
	default:
		warning("invalid read from %04"PRIx16, addr);
		break;
	}
	return 0;
}
 
static void h2_io_set_gui(h2_soc_state_t *soc, const uint16_t addr, const uint16_t value, bool *debug_on) {
	assert(soc);
	assert(uart_tx_fifo);
	assert(uart_rx_fifo);
 
	if (debug_on)
		*debug_on = false;
 
	switch (addr) {
	case oUart:
		if (value & UART_TX_WE) {
			fifo_push(uart_tx_fifo, value);
		}
		if (value & UART_RX_RE) {
			uint8_t c = 0;
			fifo_pop(uart_rx_fifo, &c);
			soc->uart_getchar_register = c;
		}
		break;
	case oVT100:
		if (value & UART_TX_WE) {
			vt100_update(&vga_terminal.vt100, value & 0xff);
			vt100_update(&soc->vt100, value & 0xff);
		}
		if (value & UART_RX_RE) {
			uint8_t c = 0;
			fifo_pop(ps2_rx_fifo, &c);
			soc->ps2_getchar_register = c;
		}
		break;
	case oLeds:
		soc->leds          = value;
		for (size_t i = 0; i < LEDS_COUNT; i++)
			leds[i].on = value & (1 << i);
		break;
	case oTimerCtrl:
		soc->timer_control  = value;
		break;
	case o7SegLED:
		for (size_t i = 0; i < SEGMENT_COUNT; i++)
			segments[i].segment = (value >> ((SEGMENT_COUNT - i - 1) * 4)) & 0xf;
		soc->led_7_segments = value;
		break;
	case oIrcMask:    soc->irc_mask     = value; break;
	case oMemControl:
		{
			soc->mem_control    = value;
			const bool sram_cs  = soc->mem_control & SRAM_CHIP_SELECT;
			const bool oe       = soc->mem_control & FLASH_MEMORY_OE;
			const bool we       = soc->mem_control & FLASH_MEMORY_WE;
			if (sram_cs && !oe && we)
				soc->vram[(((uint32_t)(soc->mem_control & FLASH_MASK_ADDR_UPPER_MASK) << 16) | soc->mem_addr_low) >> 1] = soc->mem_dout;
			break;
		}
	case oMemAddrLow:  soc->mem_addr_low = value; break;
	case oMemDout:     soc->mem_dout     = value; break;
	case oUartTxBaud:  soc->uart_tx_baud = value; break;
	case oUartRxBaud:  soc->uart_rx_baud = value; break;
	case oUartControl: soc->uart_control = value; break;
	default:
		warning("invalid write to %04"PRIx16 ":%04"PRIx16, addr, value);
		break;
	}
}
 
/* ====================================== H2 I/O Handling ====================================== */
 
/* ====================================== Main Loop ============================================ */
 
static double fps(void) {
	static unsigned frame = 0, timebase = 0;
	static double fps = 0;
	const int time = glutGet(GLUT_ELAPSED_TIME);
	frame++;
	if (time - timebase > 1000) {
		fps = frame*1000.0/(time-timebase);
		timebase = time;
		frame = 0;
	}
	return fps;
}
 
static void draw_debug_info(const world_t *world, double fps, double x, double y) {
	textbox_t t = { .x = x, .y = y, .draw_border = true, .color_text = WHITE, .color_box = WHITE };
	assert(world);
	fifo_t *f = world->use_uart_input ? uart_rx_fifo : ps2_rx_fifo;
	const char *fifo_str = world->use_uart_input ? "UART" : "PS/2";
 
	fill_textbox(&t, "tick:               %u", world->tick);
	//fill_textbox(&t, "seconds:         %f", ticks_to_seconds(world->tick));
	fill_textbox(&t, "fps:                %f", fps);
 
	if (world->debug_extra) {
		char buf[256] = { 0 };
		fill_textbox(&t, "Mode:               %s", world->debug_mode ? "step" : "continue");
		fill_textbox(&t, "%s RX FIFO full:  %s",   fifo_str, fifo_is_full(f)  ? "true" : "false");
		fill_textbox(&t, "%s RX FIFO empty: %s",   fifo_str, fifo_is_empty(f) ? "true" : "false");
		fill_textbox(&t, "%s RX FIFO count: %u",   fifo_str, (unsigned)fifo_count(f));
		fill_textbox(&t, "UART TX FIFO full:  %s", fifo_is_full(uart_tx_fifo)  ? "true" : "false");
		fill_textbox(&t, "UART TX FIFO empty: %s", fifo_is_empty(uart_tx_fifo) ? "true" : "false");
		fill_textbox(&t, "UART TX FIFO count: %u", (unsigned)fifo_count(uart_tx_fifo));
 
		sprintf(buf, "%08lu", (unsigned long)(world->cycle_count));
		fill_textbox(&t, "cycles:             %s", buf);
		fill_textbox(&t, "cycles/tick:        %u", (unsigned)(world->cycles));
	}
	draw_textbox(&t);
}
 
static void fill_textbox_memory(textbox_t *t, const uint16_t * const m, const size_t length) {
	assert(t);
	assert(m);
	assert((length % 4) == 0);
	for (size_t i = 0; i < length; i+=4)
		fill_textbox(t, "%s%u: %x %x %x %x", i < 10 ? " " : "", i, m[i], m[i+1], m[i+2], m[i+3]);
}
 
static void draw_debug_h2_screen_1(h2_t *h, double x, double y) {
	assert(h);
	textbox_t t = { .x = x, .y = y, .draw_border = true, .color_text = WHITE, .color_box = WHITE };
	fill_textbox(&t, "H2 CPU State", h->tos);
	fill_textbox(&t, "tp: %u", h->tos);
	fill_textbox_memory(&t, h->dstk, STK_SIZE);
	fill_textbox(&t, "pc: %u", h->pc);
	fill_textbox(&t, "rp: %u (max %u)", h->rp, h->rpm);
	fill_textbox(&t, "dp: %u (max %u)", h->sp, h->spm);
	fill_textbox(&t, "ie: %s", h->ie ? "true" : "false");
	draw_textbox(&t);
}
 
static void draw_debug_h2_screen_2(const h2_t * const h, const double x, const double y) {
	textbox_t t = { .x = x, .y = y, .draw_border = true, .color_text = WHITE, .color_box = WHITE };
	assert(h);
	fill_textbox(&t, "H2 CPU Return Stack");
	fill_textbox_memory(&t, h->rstk, STK_SIZE);
	draw_textbox(&t);
}
 
static void draw_debug_h2_screen_3(const h2_io_t * const io, const double x, const double y) {
	textbox_t t = { .x = x, .y = y, .draw_border = true, .color_text = WHITE, .color_box = WHITE };
	assert(io);
	assert(io->soc);
	const h2_soc_state_t * const s = io->soc;
	fill_textbox(&t, "I/O");
	fill_textbox(&t, "LED             %x", (unsigned)s->leds);
	/*fill_textbox(&t, "VGA Cursor:     %x", (unsigned)s->vga_cursor);*/
	fill_textbox(&t, "Timer Control:  %x", (unsigned)s->timer_control);
	fill_textbox(&t, "Timer Count:    %x", (unsigned)s->timer);
	fill_textbox(&t, "IRQ Mask:       %x", (unsigned)s->irc_mask);
	fill_textbox(&t, "LED 7 Segments: %x", (unsigned)s->led_7_segments);
	fill_textbox(&t, "Switches:       %x", (unsigned)s->switches);
	fill_textbox(&t, "Memory Control: %x", (unsigned)s->mem_control);
	fill_textbox(&t, "Memory Address: %x", (unsigned)s->mem_addr_low);
	fill_textbox(&t, "Memory Output:  %x", (unsigned)s->mem_dout);
	fill_textbox(&t, "Wait:           %s", s->wait ? "yes" : "no");
	fill_textbox(&t, "Interrupt:      %s", s->interrupt ? "yes" : "no");
	fill_textbox(&t, "IRQ Selector:   %x", (unsigned)s->interrupt_selector);
	fill_textbox(&t, "");
	fill_textbox(&t, "Flash");
	fill_textbox(&t, "we:             %s", s->flash.we ? "on" : "off");
	fill_textbox(&t, "cs:             %s", s->flash.cs ? "on" : "off");
	fill_textbox(&t, "mode:           %x", (unsigned)s->flash.mode);
	fill_textbox(&t, "status:         %x", (unsigned)s->flash.status);
	fill_textbox(&t, "address arg 1:  %x", (unsigned)s->flash.arg1_address);
	fill_textbox(&t, "data            %x", (unsigned)s->flash.data);
	fill_textbox(&t, "cycle:          %x", (unsigned)s->flash.cycle);
	fill_textbox(&t, "UART Control");
	fill_textbox(&t, "UART TX Baud:   %x", (unsigned)s->uart_tx_baud);
	fill_textbox(&t, "UART RX Baud:   %x", (unsigned)s->uart_rx_baud);
	fill_textbox(&t, "UART Control:   %x", (unsigned)s->uart_control);
	draw_textbox(&t);
}
 
static void keyboard_handler(const unsigned char key, const int x, const int y) {
	UNUSED(x);
	UNUSED(y);
	assert(uart_tx_fifo);
	assert(ps2_rx_fifo);
	if (key == ESCAPE) {
		world.halt_simulation = true;
	} else {
		if (world.use_uart_input)
			fifo_push(uart_rx_fifo, key);
		else
			fifo_push(ps2_rx_fifo, key);
	}
}
 
static void keyboard_special_handler(const int key, const int x, const int y) {
	UNUSED(x);
	UNUSED(y);
	switch (key) {
	case GLUT_KEY_UP:    dpad.up    = true; break;
	case GLUT_KEY_LEFT:  dpad.left  = true; break;
	case GLUT_KEY_RIGHT: dpad.right = true; break;
	case GLUT_KEY_DOWN:  dpad.down  = true; break;
	case GLUT_KEY_F1:    switches[7].on = !(switches[7].on); break;
	case GLUT_KEY_F2:    switches[6].on = !(switches[6].on); break;
	case GLUT_KEY_F3:    switches[5].on = !(switches[5].on); break;
	case GLUT_KEY_F4:    switches[4].on = !(switches[4].on); break;
	case GLUT_KEY_F5:    switches[3].on = !(switches[3].on); break;
	case GLUT_KEY_F6:    switches[2].on = !(switches[2].on); break;
	case GLUT_KEY_F7:    switches[1].on = !(switches[1].on); break;
	case GLUT_KEY_F8:    switches[0].on = !(switches[0].on); break;
	case GLUT_KEY_F9:    world.step       = true;
			     world.debug_mode = true;
			     break;
	case GLUT_KEY_F10:   world.debug_mode     = !(world.debug_mode);     break;
	case GLUT_KEY_F11:   world.use_uart_input = !(world.use_uart_input); break;
	case GLUT_KEY_F12:   world.debug_extra    = !(world.debug_extra);    break;
	default:
		break;
	}
}
 
static void keyboard_special_up_handler(const int key, const int x, const int y) {
	UNUSED(x);
	UNUSED(y);
	switch (key) {
	case GLUT_KEY_UP:    dpad.up    = false; break;
	case GLUT_KEY_LEFT:  dpad.left  = false; break;
	case GLUT_KEY_RIGHT: dpad.right = false; break;
	case GLUT_KEY_DOWN:  dpad.down  = false; break;
	default:
		break;
	}
}
 
typedef struct {
	double x;
	double y;
} coordinate_t;
 
static double abs_diff(const double a, const double b) {
	return fabsl(fabsl(a) - fabsl(b));
}
 
static void resize_window(int w, int h) {
	double window_x_min, window_x_max, window_y_min, window_y_max;
	double scale, center;
	world.window_width  = w;
	world.window_height = h;
 
	glViewport(0, 0, w, h);
 
	w = (w == 0) ? 1 : w;
	h = (h == 0) ? 1 : h;
	if ((X_MAX - X_MIN) / w < (Y_MAX - Y_MIN) / h) {
		scale = ((Y_MAX - Y_MIN) / h) / ((X_MAX - X_MIN) / w);
		center = (X_MAX + X_MIN) / 2;
		window_x_min = center - (center - X_MIN) * scale;
		window_x_max = center + (X_MAX - center) * scale;
		world.window_scale_x = scale;
		window_y_min = Y_MIN;
		window_y_max = Y_MAX;
	} else {
		scale = ((X_MAX - X_MIN) / w) / ((Y_MAX - Y_MIN) / h);
		center = (Y_MAX + Y_MIN) / 2;
		window_y_min = center - (center - Y_MIN) * scale;
		window_y_max = center + (Y_MAX - center) * scale;
		world.window_scale_y = scale;
		window_x_min = X_MIN;
		window_x_max = X_MAX;
	}
 
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(window_x_min, window_x_max, window_y_min, window_y_max, -1, 1);
}
 
static coordinate_t pixels_to_coordinates(const world_t *world, const int x, const int y) {
	assert(world);
	const double xd = abs_diff(X_MAX, X_MIN);
	const double yd = abs_diff(Y_MAX, Y_MIN);
	const double xs = world->window_width  / world->window_scale_x;
	const double ys = world->window_height / world->window_scale_y;
	const coordinate_t c = {
		.x = Y_MIN + (xd * ((x - (world->window_width  - xs)/2.) / xs)),
		.y = Y_MAX - (yd * ((y - (world->window_height - ys)/2.) / ys))
	};
	return c;
}
 
static void mouse_handler(const int button, const int state, const int x, const int y) {
	const coordinate_t c = pixels_to_coordinates(&world, x, y);
 
	for (size_t i = 0; i < SWITCHES_COUNT; i++) {
		if (detect_circle_circle_collision(c.x, c.y, 0.1, switches[i].x, switches[i].y, switches[i].radius)) {
			if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
				switches[i].on = true;
			if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN)
				switches[i].on = false;
			return;
		}
	}
 
	if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
		switch (dpad_collision(&dpad, c.x, c.y, 0.1)) {
		case DPAN_COL_NONE:                       break;
		case DPAN_COL_RIGHT:  dpad.right  = true; break;
		case DPAN_COL_LEFT:   dpad.left   = true; break;
		case DPAN_COL_DOWN:   dpad.down   = true; break;
		case DPAN_COL_UP:     dpad.up     = true; break;
		case DPAN_COL_CENTER: dpad.center = true; break;
		}
	} else if (button == GLUT_LEFT_BUTTON && state == GLUT_UP) {
		dpad.right  = false;
		dpad.left   = false;
		dpad.down   = false;
		dpad.up     = false;
		dpad.center = false;
	}
}
 
static void timer_callback(const int value) {
	world.tick++;
	glutTimerFunc(world.arena_tick_ms, timer_callback, value);
}
 
static void update_switches(void) {
	h2_io->soc->switches = 0;
	for (size_t i = 0; i < SWITCHES_COUNT; i++)
		h2_io->soc->switches |= switches[i].on << i;
	h2_io->soc->switches |= dpad.center << (SWITCHES_COUNT+0);
	h2_io->soc->switches |= dpad.right  << (SWITCHES_COUNT+1);
	h2_io->soc->switches |= dpad.left   << (SWITCHES_COUNT+2);
	h2_io->soc->switches |= dpad.down   << (SWITCHES_COUNT+3);
	h2_io->soc->switches |= dpad.up     << (SWITCHES_COUNT+4);
}
 
static void draw_scene(void) {
	static uint64_t next = 0;  // @warning static!
	static uint64_t count = 0; // @warning static!
	double f = fps();
	if (world.halt_simulation)
		exit(EXIT_SUCCESS);
 
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
	draw_regular_polygon_line(X_MAX/2, Y_MAX/2, PI/4, sqrt(Y_MAX*Y_MAX/2)*0.99, SQUARE, LINE_WIDTH, WHITE);
 
	update_switches();
 
	if (next != world.tick) {
		unsigned long increment = 0;
		next = world.tick;
		count++;
		for (;!fifo_is_empty(uart_tx_fifo);) {
			uint8_t c = 0;
			fifo_pop(uart_tx_fifo, &c);
			vt100_update(&uart_terminal.vt100, c);
		}
 
		if (world.debug_mode && world.step)
			increment = 1;
		else if (!(world.debug_mode))
			increment = world.cycles;
 
		if (!CYCLE_MODE_FIXED && increment) {
			uint64_t n = world.cycles + (f > TARGET_FPS ? CYCLE_INCREMENT : -CYCLE_DECREMENT);
			if (f > (TARGET_FPS + CYCLE_HYSTERESIS)) {
				world.cycles = MIN(((uint64_t)-1), n);
			} else if (f < (TARGET_FPS - CYCLE_HYSTERESIS)) {
				world.cycles = MAX(CYCLE_MINIMUM, n);
			}
		}
 
		if (increment)
			if (h2_run(h, h2_io, stderr, increment, NULL, false, trace_file) < 0)
				world.halt_simulation = true;
 
		world.step = false;
		world.cycle_count += increment;
	}
	draw_debug_info(&world, f, X_MIN + X_MAX/40., Y_MAX - Y_MAX/40.);
	if (world.debug_extra) {
		draw_debug_h2_screen_1(h,     X_MIN + X_MAX/40., Y_MAX*0.70);
		draw_debug_h2_screen_2(h,     X_MAX / 3.0,       Y_MAX*0.70);
		draw_debug_h2_screen_3(h2_io, X_MAX / 1.55,  Y_MAX*0.70);
	} else {
		draw_terminal(&world, &vga_terminal, "VGA");
	}
 
	for (size_t i = 0; i < SWITCHES_COUNT; i++)
		draw_switch(&switches[i]);
 
	for (size_t i = 0; i < LEDS_COUNT; i++)
		draw_led(&leds[i]);
 
	for (size_t i = 0; i < SEGMENT_COUNT; i++)
		draw_led_8_segment(&segments[i]);
 
	draw_dpad(&dpad);
 
	draw_terminal(&world, &uart_terminal, world.use_uart_input ? "UART RX / TX" : "PS/2 KBD RX / UART TX");
 
	{
		textbox_t t = { .x = X_MAX-50, .y = Y_MAX-2, .draw_border = false, .color_text = WHITE, .color_box = WHITE };
		fill_textbox(&t, "EXIT/QUIT     ESCAPE");
		fill_textbox(&t, "SWITCHES     F-1...8");
		fill_textbox(&t, "SINGLE STEP      F-9");
	}
	{
		textbox_t t = { .x = X_MAX-25, .y = Y_MAX-2, .draw_border = false, .color_text = WHITE, .color_box = WHITE };
		fill_textbox(&t, "CPU PAUSE/RESUME F-10");
		fill_textbox(&t, "SWITCH INPUT     F-11");
		fill_textbox(&t, "CHANGE DISPLAY   F-12");
	}
 
	if (!world.debug_extra)
		draw_texture(&vga_terminal,  !(count % 2));
	draw_texture(&uart_terminal, !(count % 2));
 
	glFlush();
	glutSwapBuffers();
	glutPostRedisplay();
}
 
static void initialize_rendering(char *arg_0) {
	char *glut_argv[] = { arg_0, NULL };
	int glut_argc = 0;
	memset(uart_terminal.vt100.m, ' ', uart_terminal.vt100.size);
	glutInit(&glut_argc, glut_argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH );
	glutInitWindowPosition(world.window_x_starting_position, world.window_y_starting_position);
	glutInitWindowSize(world.window_width, world.window_height);
	glutCreateWindow("H2 Simulator (GUI)");
	glShadeModel(GL_FLAT);
	glEnable(GL_DEPTH_TEST);
	glutKeyboardFunc(keyboard_handler);
	glutSpecialFunc(keyboard_special_handler);
	glutSpecialUpFunc(keyboard_special_up_handler);
	glutMouseFunc(mouse_handler);
	glutReshapeFunc(resize_window);
	glutDisplayFunc(draw_scene);
	glutTimerFunc(world.arena_tick_ms, timer_callback, 0);
}
 
static void vt100_initialize(vt100_t * const v) {
	assert(v);
	memset(&v->attribute, 0, sizeof(v->attribute));
	v->attribute.foreground_color = WHITE;
	v->attribute.background_color = BLACK;
	for (size_t i = 0; i < v->size; i++)
		v->attributes[i] = v->attribute;
}
 
static void finalize(void) {
	nvram_save(h2_io, FLASH_INIT_FILE);
	h2_free(h);
	h2_io_free(h2_io);
	fifo_free(uart_tx_fifo);
	fifo_free(uart_rx_fifo);
	fifo_free(ps2_rx_fifo);
	if (trace_file)
		fclose(trace_file);
}
 
int main(int argc, char **argv) {
	FILE *hexfile = NULL;
	int r = 0;
 
	assert(Y_MAX > 0. && Y_MIN < Y_MAX && Y_MIN >= 0.);
	assert(X_MAX > 0. && X_MIN < X_MAX && X_MIN >= 0.);
 
	log_level = LOG_NOTE;
 
	if (argc != 2) {
		fprintf(stderr, "usage %s h2.hex\n", argv[0]);
		return -1;
	}
	hexfile = fopen_or_die(argv[1], "rb");
 
	h = h2_new(START_ADDR);
	r = h2_load(h, hexfile);
	fclose(hexfile);
	if (r < 0) {
		fprintf(stderr, "h2 load failed\n");
		goto fail;
	}
	h2_io      = h2_io_new();
	h2_io->in  = h2_io_get_gui;
	h2_io->out = h2_io_set_gui;
 
	{ /* attempt to load initial contents of VGA memory */
		errno = 0;
		FILE *vga_init = fopen(VGA_INIT_FILE, "rb");
		static uint16_t vga_initial_contents[VGA_BUFFER_LENGTH] = { 0 };
		assert(VGA_BUFFER_LENGTH <= VT100_MAX_SIZE);
		if (vga_init) {
			memory_load(vga_init, vga_initial_contents, VGA_BUFFER_LENGTH);
			for (size_t i = 0; i < VGA_BUFFER_LENGTH; i++) {
				vga_terminal.vt100.m[i] = vga_initial_contents[i];
				h2_io->soc->vt100.m[i]  = vga_initial_contents[i];
			}
			fclose(vga_init);
		} else {
			warning("could not load initial VGA memory file %s: %s", VGA_INIT_FILE, strerror(errno));
		}
		vt100_initialize(&vga_terminal.vt100);
		vt100_initialize(&uart_terminal.vt100);
	}
 
	uart_rx_fifo = fifo_new(UART_FIFO_DEPTH);
	uart_tx_fifo = fifo_new(UART_FIFO_DEPTH * 100); /** @note x100 to speed things up */
	ps2_rx_fifo  = fifo_new(8 /** @bug should be 1 - but this does not work, FIFO implementation needs correcting */);
 
	nvram_load_and_transfer(h2_io, FLASH_INIT_FILE, true);
 
	if (TRON) {
		errno = 0;
		trace_file = fopen(TRACE_FILE, "wb");
		if (trace_file)
			setvbuf(trace_file, trace_buffer, _IOFBF, TRACE_BUFFER_LEN);
		else
			warning("could not open %s for writing: %s", TRACE_FILE, strerror(errno));
	}
 
	atexit(finalize);
	initialize_rendering(argv[0]);
	glutMainLoop();
 
	return 0;
fail:
	h2_free(h);
	return -1;
}
 
 
 

Compare with Previous | Blame | View Log

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.