summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--3de.c45
-rw-r--r--Makefile32
-rw-r--r--config.h7
-rw-r--r--error.h11
-rw-r--r--maths.c115
-rw-r--r--maths.h17
-rw-r--r--memory.c222
-rw-r--r--memory.h56
-rw-r--r--plat.c413
-rw-r--r--plat.h58
-rw-r--r--rect.c46
-rw-r--r--rect.h11
-rw-r--r--render.c207
-rw-r--r--render.h48
15 files changed, 1292 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..60455eb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+tags
+*.d
+*.o
+3de
diff --git a/3de.c b/3de.c
new file mode 100644
index 0000000..1b88ae3
--- /dev/null
+++ b/3de.c
@@ -0,0 +1,45 @@
+#include "config.h"
+#include "memory.h"
+#include "plat.h"
+#include "render.h"
+
+#include <stdio.h>
+
+int entrypoint(int argc, const char** argv, Arena* a) {
+ App* app;
+ Renderer r;
+ Heap h;
+ FPS f;
+ char buf[32];
+ Colour blue = make_aliceblue();
+ (void)argc;
+ (void)argv;
+ init_heap(
+ &h,
+ arena_alloc(a, app_memory_size),
+ app_memory_size
+ );
+ app = new_app(&h, 640, 480, "3D Engine");
+ if (!app) return app->err;
+ init_fps(&f, 20);
+ while (app->o) {
+ fps_begin(&f);
+ while (f.now >= f.next) {
+ app_begin(app);
+ ren_begin(&r, app->fb, app->w, app->h);
+ ren_clear(&r);
+ sprintf(buf, "FPS: %d", app->fps);
+ ren_text(&r, blue, 3, 3, buf);
+ sprintf(buf, "CAP: %d", f.fps);
+ ren_text(&r, blue, 3, 13, buf);
+ sprintf(buf, "MOUSE: %d, %d", app->mx, app->my);
+ ren_text(&r, blue, 3, 23, buf);
+ ren_end(&r);
+ app_end(app);
+ fps_update(&f);
+ }
+ fps_end(&f);
+ }
+ deinit_app(app);
+ return 0;
+}
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..8b774cd
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,32 @@
+compiler = gcc
+linker = gcc
+cflags = -I./ -g -DDEBUG -Dplat_x11 -Dplat_x86 \
+-Dplat_posix -Dallocation_default_alignment=8 \
+-Wall -Wextra -pedantic -std=c90
+lflags = -lX11
+target = 3de
+
+sources = \
+ 3de.c \
+ maths.c \
+ memory.c \
+ plat.c \
+ rect.c \
+ render.c
+
+objects = $(sources:%.c=%.o)
+
+all: $(target)
+
+$(objects): %.o : %.c
+ $(compiler) -MMD -MF $(basename $@).d $(cflags) -o $@ -c $<
+
+$(target): $(objects)
+ $(linker) $(objects) -o $@ $(lflags)
+
+clean:
+ rm *.d
+ rm *.o
+ rm $(target)
+
+-include $(sources:%.c=%.d)
diff --git a/config.h b/config.h
new file mode 100644
index 0000000..96094dd
--- /dev/null
+++ b/config.h
@@ -0,0 +1,7 @@
+#ifndef config_h
+#define config_h
+
+#define memory_size (1024 * 1024 * 32)
+#define app_memory_size (1024 * 1024 * 16)
+
+#endif
diff --git a/error.h b/error.h
new file mode 100644
index 0000000..8ef761b
--- /dev/null
+++ b/error.h
@@ -0,0 +1,11 @@
+#ifndef error_h
+#define error_h
+
+typedef enum {
+ error_none,
+ error_assertion_failed,
+ error_platform_error,
+ error_out_of_memory
+} Error;
+
+#endif
diff --git a/maths.c b/maths.c
new file mode 100644
index 0000000..d0bccf3
--- /dev/null
+++ b/maths.c
@@ -0,0 +1,115 @@
+#include "maths.h"
+
+#define sin_table_count 256
+
+static const int sin_table[sin_table_count] = {
+ 0, 25, 50, 75,
+ 100, 125, 150, 175,
+ 200, 224, 249, 273,
+ 297, 321, 345, 369,
+ 392, 415, 438, 460,
+ 483, 505, 526, 548,
+ 569, 590, 610, 630,
+ 650, 669, 688, 706,
+ 724, 742, 759, 775,
+ 792, 807, 822, 837,
+ 851, 865, 878, 891,
+ 903, 915, 926, 936,
+ 946, 955, 964, 972,
+ 980, 987, 993, 999,
+ 1004, 1009, 1013, 1016,
+ 1019, 1021, 1023, 1024,
+ 1024, 1024, 1023, 1021,
+ 1019, 1016, 1013, 1009,
+ 1004, 999, 993, 987,
+ 980, 972, 964, 955,
+ 946, 936, 926, 915,
+ 903, 891, 878, 865,
+ 851, 837, 822, 807,
+ 792, 775, 759, 742,
+ 724, 706, 688, 669,
+ 650, 630, 610, 590,
+ 569, 548, 526, 505,
+ 483, 460, 438, 415,
+ 392, 369, 345, 321,
+ 297, 273, 249, 224,
+ 200, 175, 150, 125,
+ 100, 75, 50, 25,
+ 0, -25, -50, -75,
+ -100, -125, -150, -175,
+ -200, -224, -249, -273,
+ -297, -321, -345, -369,
+ -392, -415, -438, -460,
+ -483, -505, -526, -548,
+ -569, -590, -610, -630,
+ -650, -669, -688, -706,
+ -724, -742, -759, -775,
+ -792, -807, -822, -837,
+ -851, -865, -878, -891,
+ -903, -915, -926, -936,
+ -946, -955, -964, -972,
+ -980, -987, -993, -999,
+ -1004, -1009, -1013, -1016,
+ -1019, -1021, -1023, -1024,
+ -1024, -1024, -1023, -1021,
+ -1019, -1016, -1013, -1009,
+ -1004, -999, -993, -987,
+ -980, -972, -964, -955,
+ -946, -936, -926, -915,
+ -903, -891, -878, -865,
+ -851, -837, -822, -807,
+ -792, -775, -759, -742,
+ -724, -706, -688, -669,
+ -650, -630, -610, -590,
+ -569, -548, -526, -505,
+ -483, -460, -438, -415,
+ -392, -369, -345, -321,
+ -297, -273, -249, -224,
+ -200, -175, -150, -125,
+ -100, -75, -50, -25
+};
+
+int fsqrt(int n) {
+ int lo, hi, mid, i, mm;
+ if (n <= 0) { return -1; }
+ lo = mini(1, n);
+ hi = maxi(1, n);
+ while ((100 << fbits * lo * lo) >> fbits * 2)
+ lo *= (10 << fbits) >> fbits;
+ while ((5 * hi * hi) >> fbits * 2 > n)
+ hi *= (1 << fbits) / 10;
+ for (i = 0; i < 100; i++) {
+ mid = ((lo + hi) * (1 << fbits)) / (2 << fbits);
+ mm = mid * mid >> fbits;
+ if (mm == n) return mid;
+ if (mm > n) hi = mid;
+ else lo = mid;
+ }
+ return mid;
+}
+
+int absolute(int x) {
+ return x >= 0 ? x : -x;
+}
+
+int fsin(int t) {
+ int index, sign;
+ sign = t >= 0 ? 1 : -1;
+ t = absolute(t);
+ index = (t * (sin_table_count) / ((1 << fbits) * 2));
+ index = index % sin_table_count;
+ return (sign * sin_table[index]) / 2;
+}
+
+int fcos(int t) {
+ return fsin((1 << fbits) / 2 - t);
+}
+
+int ftan(int t) {
+ return (fsin(t) << fbits) / fcos(t);
+}
+
+int flerp(int a, int b, int t) {
+ return a + ((t * (b - a)) >> fbits);
+}
+
diff --git a/maths.h b/maths.h
new file mode 100644
index 0000000..92861ba
--- /dev/null
+++ b/maths.h
@@ -0,0 +1,17 @@
+#ifndef maths_h
+#define maths_h
+
+#define mini(a_, b_) ((a_) < (b_) ? (a_) : (b_))
+#define maxi(a_, b_) ((a_) > (b_) ? (a_) : (b_))
+#define clamp(v_, min_, max_) (maxi(min_, mini(max_, v_)))
+
+#define fbits 9
+
+int absolute(int x);
+int fsin(int t);
+int fcos(int t);
+int ftan(int t);
+int flerp(int a, int b, int t);
+int fsqrt(int v);
+
+#endif
diff --git a/memory.c b/memory.c
new file mode 100644
index 0000000..2d7e566
--- /dev/null
+++ b/memory.c
@@ -0,0 +1,222 @@
+#include "memory.h"
+#include "plat.h"
+#include <stdint.h>
+#include <stddef.h>
+
+int aligned(void* p, int a) {
+ return (uintptr_t)p % a == 0;
+}
+
+int align_size(int s, int a) {
+ return (s + (a - 1)) & -a;
+}
+
+static uintptr_t align_address(
+ uintptr_t ad,
+ size_t al
+) {
+ size_t m;
+ m = al - 1;
+ return (ad + m) & ~m;
+}
+
+void init_arena(
+ Arena* a,
+ void* mem,
+ int size
+) {
+ a->buf = mem;
+ a->size = size;
+ a->ptr = 0;
+}
+
+void clear_arena(Arena* a) {
+ a->ptr = 0;
+}
+
+void* imp_arena_alloc(
+ Arena* a,
+ int size
+) {
+ assert(a->ptr + size < a->size);
+ return &a->buf[a->ptr += size];
+}
+
+void* arena_alloc(
+ Arena* a,
+ int size
+) {
+ return arena_alloc_aligned(
+ a,
+ size,
+ allocation_default_alignment
+ );
+}
+
+void* arena_alloc_aligned(
+ Arena* a,
+ int size,
+ int align
+) {
+ void* p;
+ p = imp_arena_alloc(
+ a,
+ size
+ );
+ return (void*)align_address((uintptr_t)p, align);
+}
+
+void init_heap(
+ Heap* h,
+ void* mem,
+ int size
+) {
+ int* fb;
+ assert(aligned(mem, 4));
+ assert(size > 8);
+ h->buf = mem;
+ h->size = size;
+ h->blocks = 1;
+ fb = (int*)h->buf;
+ fb[0] = size;
+}
+
+void* imp2_heap_alloc(
+ Heap* h,
+ int size
+) {
+ int o, i;
+ int hs = sizeof(int);
+ int as = align_size(size + hs, hs);
+ int f = ~((unsigned)-1 >> 1);
+ for (i = o = 0; i < h->blocks; i++) {
+ int* phdr = (int*)&h->buf[o];
+ int hdr = *phdr, bs;
+ assert(aligned(phdr, sizeof hdr));
+ bs = hdr & ~f;
+ if (~hdr & f) {
+ if (as == bs) {
+ phdr[0] |= 1;
+ return phdr + 1;
+ } else {
+ int ns = bs - as;
+ if (ns > hs) {
+ int* nhdr = &phdr[ns / 4];
+ phdr[0] = ns;
+ nhdr[0] = as | f;
+ h->blocks++;
+ return &nhdr[1];
+ }
+ }
+ } else
+ o += bs;
+ }
+ return 0;
+}
+
+void* imp_heap_alloc(Heap* h, int s) {
+ void* p = imp2_heap_alloc(h, s);
+ if (!p) {
+ heap_defrag(h);
+ p = imp2_heap_alloc(h, s);
+ }
+ return p;
+}
+
+void imp_heap_free(Heap* h, void* p) {
+ assert((char*)p > h->buf);
+ assert((char*)p < h->buf + h->size);
+ (void)h;
+ ((int*)p)[-1] &= (unsigned)-1 >> 1;
+}
+
+void* heap_alloc_aligned(
+ Heap* h,
+ int size,
+ int align
+) {
+ unsigned char* p, * a;
+ ptrdiff_t shift;
+ size += (int)align;
+ p = imp_heap_alloc(h, size);
+ if (!p) { return 0; }
+ a = (unsigned char*)align_address((uintptr_t)p, align);
+ a += align * (unsigned)(p == a);
+ shift = a - p;
+ a[-1] = shift & 0xff;
+ return a;
+}
+
+void heap_free_aligned(Heap* h, void* p) {
+ unsigned char* a;
+ ptrdiff_t shift;
+ a = p;
+ shift = a[-1];
+ shift += 256 * shift == 0;
+ a -= shift;
+ imp_heap_free(h, a);
+}
+
+void heap_defrag(Heap* h) {
+ int i, o, mtc;
+ int f = ~((unsigned)-1 >> 1);
+ for (i = o = mtc = 0; i < h->blocks; i++) {
+ int* phdr = (int*)&h->buf[o];
+ int hdr = *phdr, bs, m, mc;
+ assert(aligned(phdr, sizeof hdr));
+ bs = hdr & ~f;
+ if (~hdr & f) {
+ for (
+ m = bs, mc = 0, i++;
+ i < h->blocks;
+ i++, mc++
+ ) {
+ int mhdr = *(int*)&h->buf[o + m];
+ if (~mhdr & f)
+ m += mhdr & ~f;
+ else
+ break;
+ }
+ i--;
+ bs = m;
+ phdr[0] = bs;
+ mtc += mc;
+ }
+ o += bs;
+ }
+ h->blocks -= mtc;
+}
+
+void* heap_alloc(
+ Heap* h,
+ int size
+) {
+ return heap_alloc_aligned(
+ h,
+ size,
+ allocation_default_alignment
+ );
+}
+
+void heap_free(
+ Heap* h,
+ void* p
+) {
+ heap_free_aligned(h, p);
+}
+
+/*
+void print_blocks(Heap* h) {
+ int i, o;
+ int fb = ~((unsigned)-1 >> 1);
+ for (i = o = 0; i < h->blocks; i++) {
+ int b = *(int*)&h->buf[o];
+ int bs = b & ~fb;
+ int f = ~b & fb;
+ printf("%s %d\n", f? "free": " ", bs);
+ o += bs;
+ }
+ assert(o == h->size);
+}
+*/
+
diff --git a/memory.h b/memory.h
new file mode 100644
index 0000000..1dc5021
--- /dev/null
+++ b/memory.h
@@ -0,0 +1,56 @@
+#ifndef memory_h
+#define memory_h
+
+int aligned(void* p, int a);
+int align_size(int s, int a);
+
+typedef struct {
+ char* buf;
+ int size, ptr;
+} Arena;
+
+void init_arena(
+ Arena* a,
+ void* mem,
+ int size
+);
+void clear_arena(Arena* a);
+void* arena_alloc(
+ Arena* a,
+ int size
+);
+void* arena_alloc_aligned(
+ Arena* a,
+ int size,
+ int align
+);
+
+typedef struct {
+ char* buf;
+ int size, blocks;
+} Heap;
+
+void init_heap(
+ Heap* h,
+ void* mem,
+ int size
+);
+void* heap_alloc(
+ Heap* h,
+ int size
+);
+void* heap_alloc_aligned(
+ Heap* h,
+ int size,
+ int align
+);
+void heap_free_aligned(
+ Heap* h,
+ void* p
+);
+void heap_free(
+ Heap* h,
+ void* p
+);
+void heap_defrag(Heap* h);
+#endif
diff --git a/plat.c b/plat.c
new file mode 100644
index 0000000..e0d72be
--- /dev/null
+++ b/plat.c
@@ -0,0 +1,413 @@
+#include "config.h"
+#include "error.h"
+#include "memory.h"
+#include "plat.h"
+
+#ifdef plat_posix
+
+#define _POSIX_SOURCE
+#define _GNU_SOURCE
+#include <stdarg.h>
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <time.h>
+
+extern int isatty(int);
+extern int fileno(FILE*);
+
+int imp_assert(
+ int val,
+ const char* expr,
+ const char* file,
+ int line
+) {
+ if (!val) {
+ print_err(
+ "%d:%s: Assertion failed: %s.\n",
+ line,
+ file,
+ expr
+ );
+ pbreak(error_assertion_failed);
+ return 0;
+ }
+ return 1;
+}
+
+void print(const char* fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ vfprintf(stdout, fmt, args);
+ va_end(args);
+}
+
+void print_err(const char* fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+
+ if (isatty(fileno(stderr))) {
+ fprintf(stderr, "\033[31;31m");
+ }
+
+ vfprintf(stderr, fmt, args);
+
+ if (isatty(fileno(stderr))) {
+ fprintf(stderr, "\033[0m");
+ }
+
+ va_end(args);
+}
+
+void print_war(const char* fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+
+ if (isatty(fileno(stderr))) {
+ fprintf(stderr, "\033[31;35m");
+ }
+
+ vfprintf(stderr, fmt, args);
+
+ if (isatty(fileno(stderr))) {
+ fprintf(stderr, "\033[0m");
+ }
+
+ va_end(args);
+}
+
+void pbreak(Error code) {
+#if defined(DEBUG) && defined(plat_x86)
+ __asm__("int3;");
+ (void)code;
+#else
+ exit(code);
+#endif
+}
+
+static clockid_t global_clock;
+static unsigned long global_freq;
+
+void init_timer(void) {
+ global_clock = CLOCK_REALTIME;
+ global_freq = 1000000000;
+#if defined(_POSIX_MONOTONIC_CLOCK)
+ if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
+ global_clock = CLOCK_MONOTONIC;
+ }
+#endif
+}
+
+unsigned long get_timer(void) {
+ struct timespec ts;
+ clock_gettime(global_clock, &ts);
+ return
+ (unsigned long)ts.tv_sec * global_freq +
+ (unsigned long)ts.tv_nsec;
+}
+
+void sleep_ns(unsigned long ns) {
+ struct timespec t = { 0 };
+ t.tv_nsec = ns;
+ nanosleep(&t, &t);
+}
+
+void init_fps(FPS* f, int mpf) {
+ f->now = get_timer();
+ f->next = f->now;
+ f->mpf = mpf;
+ f->pt = 0;
+ f->ct = -1;
+}
+
+void fps_begin(FPS* f) {
+ f->now = get_timer();
+}
+
+void fps_end(FPS* f) {
+ unsigned long ts = f->next - f->now;
+ if (ts > 0) {
+ sleep_ns(ts);
+ }
+}
+
+void fps_update(FPS* f) {
+ f->next += f->mpf * 1000000;
+ f->pt = f->ct;
+ f->ct = get_timer();
+ f->fps = 1000000000 / (f->ct - f->pt);
+}
+
+#include <stdlib.h>
+
+extern int entrypoint(int, const char**, Arena*);
+
+int main(int argc, const char** argv) {
+ Arena a;
+ void* mem = malloc(memory_size);
+ if (!mem) {
+ print_err("Out of memory.\n");
+ return error_out_of_memory;
+ }
+ init_timer();
+ init_arena(
+ &a,
+ mem,
+ memory_size
+ );
+ return entrypoint(argc, argv, &a);
+}
+
+#endif
+
+#ifdef plat_x11
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include <stdlib.h>
+
+typedef struct {
+ Display* d;
+ Window wi;
+ GC gc;
+ int w, h;
+ unsigned long begin, end;
+ XImage* bb;
+ Colour* bbp;
+ Atom wm_delete;
+} App_Internal;
+
+void init_rendering(
+ App* a,
+ App_Internal* i,
+ XWindowAttributes* wa
+) {
+ Colour* p, * fb;
+ int w = i->w = wa->width;
+ int h = i->h = wa->height;
+ int tw = (w + a->s) / a->s;
+ int th = (h + a->s) / a->s;
+ a->w = tw;
+ a->h = th;
+ if (i->bb) {
+ heap_free(a->heap, a->fb);
+ XDestroyImage(i->bb);
+ }
+ p = malloc(sizeof *p * w * h);
+ fb = heap_alloc(a->heap, sizeof *p * tw * th);
+ if (!p || !fb) {
+ print_err("Out of memory.\n");
+ pbreak(error_out_of_memory);
+ }
+ a->fb = fb;
+ i->bb = XCreateImage(
+ i->d,
+ wa->visual,
+ wa->depth,
+ ZPixmap,
+ 0,
+ (char*)p,
+ w,
+ h,
+ 32,
+ w * sizeof *p
+ );
+ if (!i->bb) {
+ print_err("Failed to create X11 backbuffer.\n");
+ pbreak(error_platform_error);
+ }
+ i->bbp = p;
+}
+
+void init_window(App* a, App_Internal* i, const char* n) {
+ Window root, w;
+ Display* d;
+ XWindowAttributes wa;
+ unsigned rm, bm;
+ i->bb = 0;
+ i->bbp = 0;
+ d = i->d;
+ root = DefaultRootWindow(d);
+ w = XCreateSimpleWindow(
+ d,
+ root,
+ 0,
+ 0,
+ a->w * a->s,
+ a->h * a->s,
+ 0,
+ WhitePixel(d, 0),
+ BlackPixel(d, 0)
+ );
+ i->wi = w;
+ i->wm_delete = XInternAtom(
+ d,
+ "WM_DELETE_WINDOW",
+ 0
+ );
+ XSetWMProtocols(d, w, &i->wm_delete, 1);
+ XStoreName(d, w, n);
+ XSelectInput(
+ d,
+ w,
+ ExposureMask |
+ KeyPressMask |
+ KeyReleaseMask |
+ PointerMotionMask |
+ ButtonPressMask |
+ ButtonReleaseMask
+ );
+ XMapRaised(d, w);
+ i->gc = XCreateGC(d, w, 0, 0);
+ if (!i->gc) {
+ print_err("Failed to create X graphics context.\n");
+ pbreak(error_platform_error);
+ }
+ XGetWindowAttributes(d, w, &wa);
+ if (wa.depth != 24 && wa.depth != 32) {
+ print_err("Only true colour displays are supported.\n");
+ pbreak(error_platform_error);
+ }
+ rm = wa.visual->red_mask & 0x1000000;
+ bm = wa.visual->blue_mask & 0xffffff;
+ if ((rm == 0xff && bm == 0xff0000)) {
+ print_war("Detected BGR. Colours will look fucked.\n");
+ }
+ init_rendering(a, i, &wa);
+}
+
+App* new_app(Heap* mem, int w, int h, const char* n) {
+ App* a;
+ App_Internal* i;
+ a = heap_alloc(mem, sizeof *a + sizeof *i);
+ i = (App_Internal*)(&a[1]);
+ a->heap = mem;
+ a->s = 2;
+ a->o = 0;
+ a->err = error_none;
+ a->w = w;
+ a->h = h;
+ i->d = XOpenDisplay(0);
+ if (!i->d) {
+ print_err("Failed to open X11 display.\n");
+ pbreak(error_platform_error);
+ }
+ init_window(a, i, n);
+ a->o = 1;
+ return a;
+}
+
+void deinit_app(App* a) {
+ App_Internal* i = (App_Internal*)(&a[1]);
+ XDestroyImage(i->bb);
+ XFreeGC(i->d, i->gc);
+ XDestroyWindow(i->d, i->wi);
+ XCloseDisplay(i->d);
+ heap_free(a->heap, a);
+}
+
+void app_begin(App* a) {
+ App_Internal* i = (App_Internal*)(&a[1]);
+ Display* d = i->d;
+ Window w = i->wi;
+ i->begin = get_timer();
+ while (XPending(d)) {
+ XEvent e;
+ XWindowAttributes wa;
+ int nw, nh;
+ XNextEvent(d, &e);
+ switch (e.type) {
+ case ClientMessage:
+ if (
+ (Atom)e.xclient.data.l[0] ==
+ i->wm_delete
+ ) {
+ a->o = 0;
+ }
+ break;
+ case Expose:
+ XGetWindowAttributes(d, w, &wa);
+ nw = wa.width;
+ nh = wa.height;
+ if (
+ !i->bb ||
+ nw != i->w ||
+ nh != i->h
+ ) {
+ init_rendering(a, i, &wa);
+ }
+ break;
+ case MotionNotify:
+ a->mx = e.xmotion.x / a->s;
+ a->my = e.xmotion.y / a->s;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void app_rencpy(
+ App* a,
+ App_Internal* i,
+ int x,
+ int y,
+ int w,
+ int h
+) {
+ int rat_x, rat_y;
+ int rrat_x, rrat_y;
+ int sx, sy;
+ int ix, iy, sw, sh, dw, dh;
+ int dsx, dsy;
+ int dex, dey;
+ int pidx, fbits = 9;
+ Colour* dst;
+ const Colour* src;
+ unsigned char t;
+ sw = a->w;
+ sh = a->h;
+ dw = i->w;
+ dh = i->h;
+ rat_x = (sw << fbits * 2) / (dw << fbits);
+ rat_y = (sh << fbits * 2) / (dh << fbits);
+ rrat_x = (dw << fbits * 2) / (sw << fbits);
+ rrat_y = (dh << fbits * 2) / (sh << fbits);
+ dsx = (x * rrat_x) >> fbits;
+ dsy = (y * rrat_y) >> fbits;
+ dex = ((x + w) * rrat_x) >> fbits;
+ dey = ((y + h) * rrat_y) >> fbits;
+ dst = i->bbp;
+ src = a->fb;
+ for (iy = dsy; iy < dey; iy++) {
+ sy = (rat_y * (iy << fbits)) >> (fbits * 2);
+ for (ix = dsx; ix < dex; ix++) {
+ sx = (rat_x * (ix << fbits)) >> (fbits * 2);
+ pidx = sx + sy * sw;
+ dst = &i->bbp[ix + iy * dw];
+ *dst = src[pidx];
+ t = dst->r;
+ dst->r = dst->b;
+ dst->b = t;
+ }
+ }
+ XPutImage(
+ i->d,
+ i->wi,
+ i->gc,
+ i->bb,
+ dsx, dsy,
+ dsx, dsy,
+ dex - dsx, dey - dsy
+ );
+}
+
+void app_end(App* a) {
+ App_Internal* i = (App_Internal*)(&a[1]);
+ app_rencpy(a, i, 0, 0, a->w, a->h);
+ i->end = get_timer();
+ a->fps = 1000000000 / (i->end - i->begin);
+}
+
+#endif
diff --git a/plat.h b/plat.h
new file mode 100644
index 0000000..7e4f445
--- /dev/null
+++ b/plat.h
@@ -0,0 +1,58 @@
+#ifndef plat_h
+#define plat_h
+
+#include "error.h"
+#include "memory.h"
+#include "render.h"
+
+#ifdef assert
+#undef assert
+#endif
+
+#define assert(expr) \
+ imp_assert( \
+ expr, \
+ #expr, \
+ __FILE__, \
+ __LINE__ \
+ )
+
+int imp_assert(
+ int val,
+ const char* expr,
+ const char* file,
+ int line
+);
+
+void print(const char* fmt, ...);
+void print_err(const char* fmt, ...);
+void print_war(const char* fmt, ...);
+void pbreak(Error code);
+
+typedef struct {
+ int mpf;
+ int fps;
+ unsigned long pt, ct;
+ unsigned long now, next;
+} FPS;
+
+void init_fps(FPS* f, int mpf);
+void fps_begin(FPS* f);
+void fps_end(FPS* f);
+void fps_update(FPS* f);
+
+typedef struct {
+ int w, h, s, o, mpf;
+ int fps;
+ int mx, my;
+ Error err;
+ Colour* fb;
+ Heap* heap;
+} App;
+
+App* new_app(Heap* mem, int w, int h, const char* n);
+void deinit_app(App* a);
+void app_begin(App* a);
+void app_end(App* a);
+
+#endif
diff --git a/rect.c b/rect.c
new file mode 100644
index 0000000..c72c66e
--- /dev/null
+++ b/rect.c
@@ -0,0 +1,46 @@
+#include "maths.h"
+#include "rect.h"
+
+Rect* rect_clip(Rect* r, const Rect* c) {
+ int n;
+ int x = r->x, y = r->y;
+ if ((n = c->x - r->x) > 0) {
+ r->w -= n;
+ r->x += n;
+ }
+ if ((n = c->y - r->y) > 0) {
+ r->h -= n;
+ r->y += n;
+ }
+ if ((n = x + r->w - (c->x + c->w)) > 0)
+ r->w -= n;
+ if ((n = y + r->h - (c->y + c->h)) > 0)
+ r->h -= n;
+ return r;
+}
+
+Rect* rect_clips(Rect* r, Rect* s, const Rect* c) {
+ int n;
+ int x = r->x, y = r->y;
+ if ((n = c->x - r->x) > 0) {
+ r->w -= n;
+ r->x += n;
+ s->w -= n;
+ s->x += n;
+ }
+ if ((n = c->y - r->y) > 0) {
+ r->h -= n;
+ r->y += n;
+ s->h -= n;
+ s->y += n;
+ }
+ if ((n = x + r->w - (c->x + c->w)) > 0) {
+ r->w -= n;
+ s->w -= n;
+ }
+ if ((n = y + r->h - (c->y + c->h)) > 0) {
+ r->h -= n;
+ s->h -= n;
+ }
+ return r;
+}
diff --git a/rect.h b/rect.h
new file mode 100644
index 0000000..b055607
--- /dev/null
+++ b/rect.h
@@ -0,0 +1,11 @@
+#ifndef rect_h
+#define rect_h
+
+typedef struct {
+ int x, y, w, h;
+} Rect;
+
+Rect* rect_clip(Rect* r, const Rect* c);
+Rect* rect_clips(Rect* r, Rect* s, const Rect* c);
+
+#endif
diff --git a/render.c b/render.c
new file mode 100644
index 0000000..74da997
--- /dev/null
+++ b/render.c
@@ -0,0 +1,207 @@
+#include "render.h"
+
+static int font_w = 960;
+static unsigned font_data[] = {
+ 0x00000000, 0x00003000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x0cc0c000, 0x8000301b, 0x60601803, 0x00000000, 0x20000000,
+ 0x0780c078, 0x83f0e01e, 0xe0783f07, 0x60000001, 0x1e018000,
+ 0x07e0c078, 0xe3f87e1f, 0xe0cc1e0f, 0x1e638f01, 0x1f0c6318,
+ 0x07e1f07e, 0xc330fc1e, 0x30cc318c, 0x021e0fc3, 0x000101e0,
+ 0x00e00000, 0x0000e000, 0xc00e0007, 0x3c038c00, 0x00000000,
+ 0x00000000, 0x00001000, 0x00000000, 0x30380000, 0x0000c070,
+ 0x0cc1e000, 0xc630f81b, 0xc0301806, 0x00000cc0, 0x30000000,
+ 0x0cc0f0cc, 0xc030f033, 0x30cc330c, 0x30000003, 0x33030000,
+ 0x8cc1e0cc, 0xc230cc33, 0xc0cc3308, 0x0c630600, 0x318ce3b8,
+ 0x0cc318cc, 0xc330b433, 0x30cc318c, 0x06060cc3, 0x00038180,
+ 0x00c00018, 0x8000c000, 0x000c000d, 0x30030000, 0x00000000,
+ 0x00000000, 0x00001800, 0x00000000, 0x300c0000, 0x0409e0c0,
+ 0x8481e000, 0xc3300c3f, 0x80180c06, 0x000c0781, 0x18000000,
+ 0x0c00c0ec, 0xc1f0d830, 0x30cc1800, 0x180c0303, 0x30060000,
+ 0x8cc330ec, 0xc0318c01, 0xc0cc3180, 0x0c330600, 0x318de3f8,
+ 0x0cc318cc, 0xc3303003, 0x3078318c, 0x0c060643, 0x0006c180,
+ 0x07c0f018, 0x81e0f81e, 0xf06c3701, 0x30330f00, 0x1e07c1f8,
+ 0x0ee37076, 0xc330fc3e, 0x30c6318c, 0x300c0fc3, 0x0e0f20c0,
+ 0x0001e000, 0x8180781b, 0x80180013, 0x000c1fe1, 0x0c000000,
+ 0x0600c0dc, 0xc300cc1c, 0xe0780c07, 0x0c0c0303, 0x180c03f0,
+ 0x87c330ec, 0xc1f18c01, 0xc0fc0187, 0x0c1f0600, 0x318fe358,
+ 0x07c3187c, 0xc330300e, 0xe030358c, 0x18060301, 0x000c6180,
+ 0x0cc18030, 0xc330cc33, 0xc0dc1987, 0x301b0c00, 0x330cc358,
+ 0x0dc198cc, 0xc3301803, 0x306c358c, 0x00060643, 0x1b060180,
+ 0x0000c000, 0xc0c0c01b, 0x8018001e, 0x003f0781, 0x060003f0,
+ 0x0300c0cc, 0xc301fc30, 0x00cc0c0c, 0x0c000003, 0x0c0c0000,
+ 0x8cc3f00c, 0xc0318c01, 0xc0cc3980, 0x0c330660, 0x318f6318,
+ 0x06c3980c, 0xc3303018, 0xc078358c, 0x30060180, 0x00000180,
+ 0x0cc1f000, 0x83f0cc03, 0xc0cc1981, 0x301f0c00, 0x330cc358,
+ 0x00c198cc, 0xc330181e, 0x3038358c, 0x300c0303, 0x318000c0,
+ 0x80000000, 0xc6607c3f, 0xc030000c, 0x300c0cc0, 0x03030000,
+ 0x0180c0cc, 0xc330c033, 0x30cc0c0c, 0x180c0303, 0x000603f0,
+ 0x8cc3308c, 0xc230cc33, 0xc0cc3300, 0x8c630660, 0x318e6318,
+ 0x0cc1f00c, 0x83303033, 0xc0cc1f07, 0x600609c0, 0x00000180,
+ 0x0cc19800, 0x8030cc33, 0xc0cc1f01, 0x30330cc0, 0x330cc358,
+ 0x00c198cc, 0x8330d830, 0xe06c1f07, 0x300c0981, 0x318000c0,
+ 0x0000c000, 0x8630301b, 0x6060001b, 0x300c0000, 0x01830000,
+ 0x0fc3f078, 0x81e0c01e, 0xe0780c07, 0x300c0301, 0x0c030000,
+ 0x07e33078, 0xe3f87e1f, 0xe0cc3e01, 0xfe6383c1, 0x1f0c6318,
+ 0x0ce3c01e, 0x01e0781e, 0xe0cc1b03, 0xc01e0fc1, 0x000001e0,
+ 0x07e3f000, 0xc1e0f81e, 0xf0ce1803, 0xfc338783, 0x1e0cc318,
+ 0x01e1f07c, 0x06e0701f, 0x80c61b03, 0x30380fc1, 0x3f800070,
+ 0x0000c000, 0x0000301b, 0x00000000, 0x18000000, 0x00800000,
+ 0x00000000, 0x00000000, 0x00000000, 0x60060000, 0x00018000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x80000000, 0x7f800000,
+ 0x00000000, 0x00000000, 0x00000f00, 0x00000000, 0x00000000,
+ 0x0001800c, 0x00000000, 0xf0000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x0003c01e, 0x00000000, 0x00000000, 0x00000000, 0x00000000
+};
+
+Colour make_colour(unsigned rgb, unsigned char a) {
+ Colour r;
+ r.r = (unsigned char)(rgb >> 16);
+ r.g = (unsigned char)(rgb >> 8);
+ r.b = (unsigned char)rgb;
+ r.a = a;
+ return r;
+}
+
+Colour make_red(void) {
+ return make_colour(0xff0000, 0xff);
+}
+
+Colour make_green(void) {
+ return make_colour(0x00ff00, 0xff);
+}
+
+Colour make_blue(void) {
+ return make_colour(0x0000ff, 0xff);
+}
+
+Colour make_cyan(void) {
+ return make_colour(0x00ffff, 0xff);
+}
+
+Colour make_pink(void) {
+ return make_colour(0xff00ff, 0xff);
+}
+
+Colour make_yellow(void) {
+ return make_colour(0xffff00, 0xff);
+}
+
+Colour make_aliceblue(void) {
+ return make_colour(0xf0f8ff, 0xff);
+}
+
+Colour blend(Colour dst, Colour src) {
+ int ima;
+ ima = 0xff - src.a;
+ dst.r = (unsigned char)(((src.r * src.a) + (dst.r * ima)) >> 8);
+ dst.g = (unsigned char)(((src.g * src.a) + (dst.g * ima)) >> 8);
+ dst.b = (unsigned char)(((src.b * src.a) + (dst.b * ima)) >> 8);
+ return dst;
+}
+
+Colour blend_mod(Colour dst, Colour src, Colour mod) {
+ int ima;
+ src.a = (src.a * mod.a) >> 8;
+ ima = 0xff - src.a;
+ dst.r = (unsigned char)(((src.r * mod.r * src.a) >> 16) + ((dst.r * ima) >> 8));
+ dst.g = (unsigned char)(((src.g * mod.g * src.a) >> 16) + ((dst.g * ima) >> 8));
+ dst.b = (unsigned char)(((src.b * mod.b * src.a) >> 16) + ((dst.b * ima) >> 8));
+ return dst;
+}
+
+
+void ren_begin(Renderer* r, Colour* t, int w, int h) {
+ r->t = t;
+ r->w = w;
+ r->h = h;
+ r->clip.x = 0;
+ r->clip.y = 0;
+ r->clip.w = w;
+ r->clip.h = h;
+}
+
+void ren_end(Renderer* r) {
+ (void)r;
+}
+
+void ren_clear(Renderer* r) {
+ Colour* d = r->t, b = { 0 };
+ int i = 0, e = r->w * r->h;
+ for (i = 0; i < e; i++, d++)
+ *d = b;
+}
+
+void ren_clearc(Renderer* r, Colour c) {
+ Colour* d = r->t;
+ int i = 0, e = r->w * r->h;
+ for (i = 0; i < e; i++, d++)
+ *d = c;
+}
+
+void ren_clip(Renderer* r, const Rect* c) {
+ r->clip = *c;
+}
+
+void ren_point(Renderer* r, Colour c, int x, int y) {
+ if (x < 0) return;
+ if (y < 0) return;
+ if (x >= r->w) return;
+ if (y >= r->h) return;
+ r->t[x + y * r->w] = c;
+}
+
+void ren_char(
+ Renderer* r,
+ Colour c,
+ int x,
+ int y,
+ char ch
+) {
+ int i, j, k, l, ex, ey, s;
+ Colour* dst;
+ Rect re = { 0, 0, 10, 10 };
+ Rect sub = { 0, 0, 10, 10 };
+ re.x = x;
+ re.y = y;
+ sub.x = (ch - ' ') * 10;
+ rect_clips(&re, &sub, &r->clip);
+ ex = re.x + re.w;
+ ey = re.y + re.h;
+ dst = r->t + (re.x + re.y * r->w);
+ s = r->w - re.w;
+ for (j = re.y, l = sub.y; j < ey; j++, l++) {
+ for (i = re.x, k = sub.x; i < ex; i++, k++) {
+ int si = k + l * font_w;
+ unsigned bits = font_data[si / 32];
+ int bit = bits & (1 << si % 32);
+ if (bit)
+ *dst = blend(*dst, c);
+ dst++;
+ }
+ dst += s;
+ }
+}
+
+void ren_text(
+ Renderer* r,
+ Colour c,
+ int x,
+ int y,
+ const char* t
+) {
+ const char* s;
+ for (s = t; *s; s++, x += 10)
+ ren_char(r, c, x, y, *s);
+}
+
diff --git a/render.h b/render.h
new file mode 100644
index 0000000..36bafe4
--- /dev/null
+++ b/render.h
@@ -0,0 +1,48 @@
+#ifndef render_h
+#define render_h
+
+#include "rect.h"
+
+typedef struct {
+ unsigned char r, g, b, a;
+} Colour;
+
+Colour make_colour(unsigned rgb, unsigned char a);
+Colour make_red(void);
+Colour make_green(void);
+Colour make_blue(void);
+Colour make_cyan(void);
+Colour make_pink(void);
+Colour make_yellow(void);
+Colour make_aliceblue(void);
+Colour blend(Colour dst, Colour src);
+Colour blend_mod(Colour dst, Colour src, Colour mod);
+
+typedef struct {
+ Colour* t;
+ int w, h;
+ Rect clip;
+} Renderer;
+
+void ren_begin(Renderer* r, Colour* t, int w, int h);
+void ren_end(Renderer* r);
+void ren_clear(Renderer* r);
+void ren_clearc(Renderer* r, Colour c);
+void ren_clip(Renderer* r, const Rect* c);
+void ren_point(Renderer* r, Colour c, int x, int y);
+void ren_char(
+ Renderer* r,
+ Colour c,
+ int x,
+ int y,
+ char ch
+);
+void ren_text(
+ Renderer* r,
+ Colour c,
+ int x,
+ int y,
+ const char* t
+);
+
+#endif