summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkdx <kikoodx@paranoici.org>2023-04-16 07:27:42 +0200
committerkdx <kikoodx@paranoici.org>2023-04-16 07:28:17 +0200
commit049269bf70e7143e1932f2f115192dbbc27477f4 (patch)
tree8d5822704289f4a1205642fe7819de5296197cc5
downloadpx-049269bf70e7143e1932f2f115192dbbc27477f4.tar.gz
initial commit
-rw-r--r--.gitignore4
-rw-r--r--Tupfile15
-rw-r--r--Tupfile.ini0
-rw-r--r--compile_flags.txt4
-rwxr-xr-xrun.sh2
-rw-r--r--src/TZR.c887
-rw-r--r--src/TZR.h370
-rw-r--r--src/main.c25
-rw-r--r--src/px.c43
-rw-r--r--src/px.h19
10 files changed, 1369 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5fb9550
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+/.tup
+/build
+/indexed
+/indexed
diff --git a/Tupfile b/Tupfile
new file mode 100644
index 0000000..b79ebff
--- /dev/null
+++ b/Tupfile
@@ -0,0 +1,15 @@
+CC = zig cc
+LD = $(CC) -fuse-ld=mold
+CFLAGS = -std=c99 -Wall -Wextra -Wno-override-init
+LDFLAGS = -lm -lSDL2 -lSDL2_image -lSDL2_mixer
+
+# codebase
+#: map/*.tmj |> tiled2c %f embed_%B_tmj >%o |> build/embed_%B_tmj.c
+#: map/*.tsj |> tiled2c %f embed_%B_tsj >%o |> build/embed_%B_tsj.c
+: foreach src/*.c |> $(CC) $(CFLAGS) -c -o %o %f |> build/%B.o
+: foreach build/*.c |> tcc -Isrc -c -o %o %f |> build/%B.o
+: build/*.o |> $(LD) -o %o %f $(LDFLAGS) |> build/px
+
+# assets
+#: foreach res/*.png |> qoiconv %f %o |> build/res/%B.qoi
+#: foreach res/*.wav |> cp %f %o |> build/%f
diff --git a/Tupfile.ini b/Tupfile.ini
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Tupfile.ini
diff --git a/compile_flags.txt b/compile_flags.txt
new file mode 100644
index 0000000..f45cb29
--- /dev/null
+++ b/compile_flags.txt
@@ -0,0 +1,4 @@
+-Wall
+-Wextra
+-Wno-initializer-overrides
+-std=c99
diff --git a/run.sh b/run.sh
new file mode 100755
index 0000000..fdfd6c6
--- /dev/null
+++ b/run.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+tup && build/px
diff --git a/src/TZR.c b/src/TZR.c
new file mode 100644
index 0000000..63d829d
--- /dev/null
+++ b/src/TZR.c
@@ -0,0 +1,887 @@
+#include <math.h>
+#include <SDL2/SDL_error.h>
+#include <SDL2/SDL_events.h>
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_image.h>
+#include <SDL2/SDL_log.h>
+#include <SDL2/SDL_mixer.h>
+#include <SDL2/SDL_mouse.h>
+#include <SDL2/SDL_render.h>
+#include <SDL2/SDL_rwops.h>
+#include <SDL2/SDL_scancode.h>
+#include <SDL2/SDL_timer.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/stat.h>
+#include "TZR.h"
+#include <unistd.h>
+
+/* sources/drain.h */
+
+static void *drain_error(void)
+{
+ perror("TZR_LoadResourceTyped:drain");
+ return NULL;
+}
+
+static char *drain(FILE *fp, size_t *size)
+{
+ if (fseek(fp, 0, SEEK_END) < 0)
+ return drain_error();
+ *size = ftell(fp);
+ if (fseek(fp, 0, SEEK_SET) < 0)
+ return drain_error();
+ char *data = malloc(*size);
+ if (data == NULL)
+ return drain_error();
+ if (fread(data, 1, *size, fp) != *size)
+ return free(data), drain_error();
+ return data;
+}
+
+/* sources/sdl_error.h */
+
+static int sdl_error(int rv)
+{
+ SDL_Log("%s\n", SDL_GetError());
+ return rv;
+}
+/* sources/sdl_rect_to_rect.h */
+
+static void TZR_RectFromSDLRect(TZR_Rect *dest, const SDL_Rect *src)
+{
+ dest->from.x = src->x;
+ dest->from.y = src->y;
+ dest->to.x = src->x + src->w - 1;
+ dest->to.y = src->y + src->h - 1;
+}
+/* sources/globals.c */
+
+TZR_Config ___tzr_config = {0};
+TZR_Color ___tzr_color = {0.0f, 0.0f, 0.0f, 0.0f, 1.0f};
+TZR_Resource *___tzr_resources = NULL;
+size_t ___tzr_resources_capacity = 0;
+size_t ___tzr_resources_size = 0;
+SDL_Window *___tzr_window = NULL;
+SDL_Renderer *___tzr_renderer = NULL;
+SDL_Texture *___tzr_target = NULL;
+SDL_Texture *___tzr_target_light = NULL;
+unsigned long ___tzr_tick = 0;
+unsigned long ___tzr_next_time = 0;
+unsigned long ___tzr_min_dt = 0;
+int ___tzr_should_quit = 0;
+int ___tzr_mouse_x = 0;
+int ___tzr_mouse_y = 0;
+TZR_KeyState ___tzr_keystates[SDL_NUM_SCANCODES] = {0};
+TZR_KeyState ___tzr_mousestates[256] = {0}; //doc says than mouse button is u8
+float ___tzr_scale = 1.0;
+int ___tzr_off_x = 1.0;
+int ___tzr_off_y = 1.0;
+/* sources/TZR_CycleEvents.c */
+
+void
+next_state(TZR_KeyState *keystate)
+{
+ switch (*keystate) {
+ case TZR_KEYSTATE_RELEASE:
+ case TZR_KEYSTATE_UP:
+ *keystate = TZR_KEYSTATE_UP;
+ break;
+ case TZR_KEYSTATE_PRESS:
+ case TZR_KEYSTATE_DOWN:
+ *keystate = TZR_KEYSTATE_DOWN;
+ break;
+ default:
+ break;
+ }
+}
+
+void
+TZR_CycleEvents(void)
+{
+ TZR_ResourcesWatch();
+ for (int i = 0; i < SDL_NUM_SCANCODES; i++)
+ next_state(&___tzr_keystates[i]);
+ for (int i = 0; i < 256; i++)
+ next_state(&___tzr_mousestates[i]);
+ TZR_Event e;
+ while (TZR_PollEvent(&e))
+ ;
+}
+/* sources/TZR_DestroyResource.c */
+
+void
+TZR_DestroyResource(TZR_Resource *res, int free_path)
+{
+ if (res->path != NULL && free_path)
+ free(res->path);
+ switch (res->type) {
+ case TZR_RES_RAW:
+ if (res->raw.data != NULL)
+ free(res->raw.data);
+ break;
+ case TZR_RES_IMAGE:
+ if (res->image.ptr != NULL)
+ SDL_DestroyTexture(res->image.ptr);
+ break;
+ case TZR_RES_SOUND:
+ if (res->sound.ptr != NULL)
+ Mix_FreeChunk(res->sound.ptr);
+ break;
+ default:
+ fprintf(stderr, "unknown resource type %u\n", res->type);
+ break;
+ }
+}
+
+/* sources/TZR_DirectResourceLoad.c */
+
+int
+TZR_DirectResourceLoad(TZR_Resource *res, const void *data, int size)
+{
+ switch (res->type) {
+ case TZR_RES_RAW:
+ res->raw.size = size;
+ res->raw.data = malloc(size);
+ if (res->raw.data == NULL) {
+ perror("TZR_DirectResourceLoad");
+ return -1;
+ }
+ memcpy(res->raw.data, data, size);
+ break;
+ case TZR_RES_IMAGE: {
+ SDL_RWops *const rw = SDL_RWFromConstMem(data, size);
+ if (rw == NULL)
+ return sdl_error(-1);
+ SDL_Surface *const surf = IMG_Load_RW(rw, 0);
+ SDL_RWclose(rw);
+ if (surf == NULL)
+ return sdl_error(-1);
+ SDL_Texture *const tex =
+ SDL_CreateTextureFromSurface(___tzr_renderer, surf);
+ SDL_FreeSurface(surf);
+ if (tex == NULL)
+ return sdl_error(-1);
+ if (SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND) < 0) {
+ SDL_DestroyTexture(tex);
+ return sdl_error(-1);
+ }
+ if (SDL_QueryTexture(tex, NULL, NULL,
+ &res->image.width, &res->image.height)) {
+ SDL_DestroyTexture(tex);
+ return sdl_error(-1);
+ }
+ res->image.ptr = tex;
+ } break;
+ case TZR_RES_SOUND: {
+ if (!___tzr_config.mixer) {
+ SDL_Log("audio mixer disabled, skip loading");
+ return -1;
+ }
+ SDL_RWops *const rw = SDL_RWFromConstMem(data, size);
+ if (rw == NULL)
+ return sdl_error(-1);
+ Mix_Chunk *const chunk = Mix_LoadWAV_RW(rw, 0);
+ SDL_RWclose(rw);
+ if (chunk == NULL)
+ return sdl_error(-1);
+ res->sound.ptr = chunk;
+ } break;
+ default:
+ fprintf(stderr, "invalid type\n");
+ return -1;
+ }
+ return 0;
+}
+/* sources/TZR_DrawBegin.c */
+
+int
+TZR_DrawBegin(void)
+{
+ if (SDL_SetRenderTarget(___tzr_renderer, ___tzr_target) < 0)
+ return sdl_error(-1);
+ if (SDL_SetRenderDrawBlendMode(___tzr_renderer, SDL_BLENDMODE_BLEND))
+ return sdl_error(-1);
+ return 0;
+}
+/* sources/TZR_DrawClear.c */
+
+int
+TZR_DrawClear(void)
+{
+ if (SDL_RenderClear(___tzr_renderer) < 0)
+ return sdl_error(-1);
+ return 0;
+}
+/* sources/TZR_DrawEnd.c */
+
+int
+TZR_DrawEnd(void)
+{
+ if (SDL_SetRenderTarget(___tzr_renderer, NULL) < 0)
+ return sdl_error(-1);
+ if (SDL_SetRenderDrawColor(___tzr_renderer, 0, 0, 0, 255) < 0)
+ return sdl_error(-1);
+ if (SDL_RenderClear(___tzr_renderer) < 0)
+ return sdl_error(-1);
+
+ int win_w, win_h;
+ SDL_GetWindowSize(___tzr_window, &win_w, &win_h);
+ win_w = win_w ? win_w : 1;
+ win_h = win_h ? win_h : 1;
+
+ const float ratio_w = win_w / (float)___tzr_config.width;
+ const float ratio_h = win_h / (float)___tzr_config.height;
+ ___tzr_scale = (ratio_w < ratio_h) ? ratio_w : ratio_h;
+ if (___tzr_scale > 1.0f && ___tzr_config.pixel_perfect)
+ ___tzr_scale = (int)___tzr_scale;
+ ___tzr_off_x = (win_w - ___tzr_config.width * ___tzr_scale) / 2;
+ ___tzr_off_y = (win_h - ___tzr_config.height * ___tzr_scale) / 2;
+ const SDL_Rect dest = {
+ ___tzr_off_x,
+ ___tzr_off_y,
+ ___tzr_config.width * ___tzr_scale,
+ ___tzr_config.height * ___tzr_scale
+ };
+
+ if (SDL_RenderCopy(___tzr_renderer, ___tzr_target, NULL, &dest) < 0)
+ return sdl_error(-1);
+ if (___tzr_config.light_system) {
+ if (SDL_SetTextureBlendMode(___tzr_target_light,
+ SDL_BLENDMODE_MOD) < 0)
+ return sdl_error(-1);
+ if (SDL_RenderCopy(___tzr_renderer, ___tzr_target_light, NULL,
+ &dest) < 0)
+ return sdl_error(-1);
+ }
+ if (___tzr_config.target_fps > 0) {
+ ___tzr_next_time += ___tzr_min_dt;
+ const unsigned long cur_time = SDL_GetTicks64();
+ if (___tzr_next_time <= cur_time)
+ ___tzr_next_time = cur_time;
+ else
+ SDL_Delay(___tzr_next_time - cur_time);
+ }
+ SDL_RenderPresent(___tzr_renderer);
+ ___tzr_tick += 1;
+ return 0;
+}
+/* sources/TZR_DrawImage.c */
+
+int
+_TZR_DrawImage(const TZR_DrawImageArgs *args)
+{
+ if (args->id == 0) {
+ fprintf(stderr, "args->id is 0\n");
+ return -1;
+ }
+
+ if (TZR_GetResourceType(args->id) != TZR_RES_IMAGE) {
+ fprintf(stderr, "%u isn't an image\n", args->id);
+ return -1;
+ }
+ TZR_Resource *const res = TZR_GetResourcePointer(args->id);
+ const TZR_Image *const img = &res->image;
+
+ if (SDL_SetTextureColorMod(img->ptr, ___tzr_color.r * 255,
+ ___tzr_color.g * 255, ___tzr_color.b * 255))
+ return sdl_error(-1);
+ if (SDL_SetTextureAlphaMod(img->ptr, ___tzr_color.a * 255))
+ return sdl_error(-1);
+
+ float scale_x = args->sx;
+ float scale_y = args->sy;
+
+ int simg_width = (args->w == INT_MIN) ? img->width : args->w;
+ if (simg_width < 0) {
+ simg_width *= -1;
+ scale_x *= -1;
+ }
+ if (simg_width + args->ix > img->width)
+ simg_width = img->width - args->ix;
+
+ int simg_height = (args->h == INT_MIN) ? img->height : args->h;
+ if (simg_height < 0) {
+ simg_height *= -1;
+ scale_y *= -1;
+ }
+ if (simg_height + args->iy > img->height)
+ simg_height = img->height - args->iy;
+
+ int flip = 0;
+ flip |= SDL_FLIP_HORIZONTAL * ((scale_x < 0.0f) ^ args->flip_x);
+ flip |= SDL_FLIP_VERTICAL * ((scale_y < 0.0f) ^ args->flip_y);
+ const SDL_Rect src = {
+ args->ix,
+ args->iy,
+ simg_width,
+ simg_height
+ };
+
+ const int width = simg_width * fabs(scale_x);
+ const int height = simg_height * fabs(scale_y);
+ const int x = args->x - (scale_x < 0.0f ? width : 0);
+ const int y = args->y - (scale_y < 0.0f ? height : 0);
+
+ const SDL_Rect dest = {
+ x - (args->center ? (simg_width * scale_x / 2) : 0),
+ y - (args->center ? (simg_height * scale_y / 2) : 0),
+ width,
+ height
+ };
+ if (SDL_RenderCopyEx(___tzr_renderer, img->ptr, &src, &dest,
+ args->r * 360, NULL, flip) < 0)
+ return sdl_error(-1);
+ TZR_Rect area;
+ TZR_RectFromSDLRect(&area, &dest);
+ return 0;
+}
+/* sources/TZR_DrawLine.c */
+
+int
+TZR_DrawLine(int x0, int y0, int x1, int y1)
+{
+ if (SDL_RenderDrawLine(___tzr_renderer, x0, y0, x1, y1) < 0)
+ return sdl_error(-1);
+ return 0;
+}
+/* sources/TZR_DrawPoint.c */
+
+int
+TZR_DrawPoint(int x, int y)
+{
+ if (SDL_RenderDrawPoint(___tzr_renderer, x, y) < 0)
+ return sdl_error(-1);
+ return 0;
+}
+/* sources/TZR_DrawRectangle.c */
+
+int
+_TZR_DrawRectangle(const TZR_DrawRectangleArgs *args)
+{
+ const SDL_Rect rect = {
+ args->x - args->center * (args->w / 2),
+ args->y - args->center * (args->h / 2),
+ args->w,
+ args->h
+ };
+ if (args->fill) {
+ if (SDL_RenderFillRect(___tzr_renderer, &rect) < 0)
+ return sdl_error(-1);
+ } else {
+ if (SDL_RenderDrawRect(___tzr_renderer, &rect) < 0)
+ return sdl_error(-1);
+ }
+ return 0;
+}
+/* sources/TZR_DrawSetColor8.c */
+
+int
+TZR_DrawSetColor8(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
+{
+ ___tzr_color.r = (float)r / 255.0f;
+ ___tzr_color.g = (float)g / 255.0f;
+ ___tzr_color.b = (float)b / 255.0f;
+ ___tzr_color.a = (float)a / 255.0f;
+ if (SDL_SetRenderDrawColor(___tzr_renderer, r, g, b, a) < 0)
+ return sdl_error(-1);
+ return 0;
+}
+/* sources/TZR_DrawSetColor.c */
+
+int
+_TZR_DrawSetColor(const TZR_Color *color)
+{
+ ___tzr_color.r = (color->r < 0.0f) ? ___tzr_color.r : color->r;
+ ___tzr_color.g = (color->g < 0.0f) ? ___tzr_color.g : color->g;
+ ___tzr_color.b = (color->b < 0.0f) ? ___tzr_color.b : color->b;
+ ___tzr_color.a = (color->a < 0.0f) ? ___tzr_color.a : color->a;
+ const uint8_t r = ___tzr_color.r * 255;
+ const uint8_t g = ___tzr_color.g * 255;
+ const uint8_t b = ___tzr_color.b * 255;
+ const uint8_t a = ___tzr_color.a * 255;
+ if (SDL_SetRenderDrawColor(___tzr_renderer, r, g, b, a) < 0)
+ return sdl_error(-1);
+ return 0;
+}
+/* sources/TZR_GetImageHeight.c */
+
+int
+TZR_GetImageHeight(TZR_Uint id)
+{
+ return TZR_GetResourcePointer(id)->image.height;
+}
+/* sources/TZR_GetImageWidth.c */
+
+int
+TZR_GetImageWidth(TZR_Uint id)
+{
+ return TZR_GetResourcePointer(id)->image.width;
+}
+/* sources/TZR_GetKeyState.c */
+
+TZR_KeyState
+TZR_GetKeyState(int scancode)
+{
+ return ___tzr_keystates[scancode];
+}
+/* sources/TZR_GetMousePosition.c */
+
+void
+TZR_GetMousePosition(int *x, int *y)
+{
+ if (x != NULL)
+ *x = ___tzr_mouse_x;
+ if (y != NULL)
+ *y = ___tzr_mouse_y;
+ TZR_ScreenTransform(x, y);
+}
+/* sources/TZR_GetRawResource.c */
+
+TZR_Raw *
+TZR_GetRawResource(TZR_Uint id)
+{
+ return &TZR_GetResourcePointer(id)->raw;
+}
+/* sources/TZR_GetResourcePath.c */
+
+const char *
+TZR_GetResourcePath(TZR_Uint id)
+{
+ return TZR_GetResourcePointer(id)->path;
+}
+/* sources/TZR_GetResourcePointer.c */
+
+TZR_Resource *
+TZR_GetResourcePointer(TZR_Uint id)
+{
+ return &___tzr_resources[id - 1];
+}
+/* sources/TZR_GetResourceType.c */
+
+TZR_ResourceType
+TZR_GetResourceType(TZR_Uint id)
+{
+ return TZR_GetResourcePointer(id)->type;
+}
+/* sources/TZR_GetTick.c */
+
+unsigned long
+TZR_GetTick(void)
+{
+ return ___tzr_tick;
+}
+/* sources/TZR_Init.c */
+
+static int
+_sdl_error(void)
+{
+ sdl_error(-1);
+ TZR_Quit();
+ return -1;
+}
+
+int
+_TZR_Init(const TZR_Config *config)
+{
+ memcpy(&___tzr_config, config, sizeof(TZR_Config));
+
+ if (SDL_Init(SDL_INIT_VIDEO) < 0)
+ return _sdl_error();
+ if (IMG_Init(IMG_INIT_PNG * ___tzr_config.png_loading) < 0)
+ return _sdl_error();
+ if (___tzr_config.mixer && Mix_Init(MIX_INIT_FLAC) != MIX_INIT_FLAC) {
+ SDL_Log("%s", Mix_GetError());
+ ___tzr_config.mixer = false;
+ }
+
+ char *const basepath = SDL_GetBasePath();
+ if (basepath == NULL)
+ return _sdl_error();
+ const int chdir_rv = chdir(basepath);
+ SDL_free(basepath);
+ if (chdir_rv < 0)
+ return perror("TZR_Init"), -1;
+
+ ___tzr_window = SDL_CreateWindow("TZR", SDL_WINDOWPOS_UNDEFINED,
+ SDL_WINDOWPOS_UNDEFINED,
+ ___tzr_config.width,
+ ___tzr_config.height,
+ SDL_WINDOW_RESIZABLE);
+ if (___tzr_window == NULL)
+ return _sdl_error();
+
+ ___tzr_renderer = SDL_CreateRenderer(___tzr_window, -1,
+ SDL_RENDERER_ACCELERATED);
+ if (___tzr_renderer == NULL)
+ return _sdl_error();
+
+ ___tzr_target = SDL_CreateTexture(___tzr_renderer,
+ SDL_PIXELFORMAT_RGB888,
+ SDL_TEXTUREACCESS_TARGET,
+ ___tzr_config.width,
+ ___tzr_config.height);
+ if (___tzr_target == NULL)
+ return _sdl_error();
+
+ if (___tzr_config.light_system) {
+ ___tzr_target_light = SDL_CreateTexture(___tzr_renderer,
+ SDL_PIXELFORMAT_RGB888,
+ SDL_TEXTUREACCESS_TARGET,
+ ___tzr_config.width,
+ ___tzr_config.height);
+ if (___tzr_target_light == NULL)
+ return _sdl_error();
+ }
+
+ SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, 0);
+ ___tzr_tick = 0;
+
+ if (___tzr_config.target_fps > 0) {
+ ___tzr_min_dt = 1000 / ___tzr_config.target_fps;
+ ___tzr_next_time = SDL_GetTicks64();
+ }
+
+ if (!___tzr_config.show_cursor)
+ SDL_ShowCursor(SDL_FALSE);
+ ___tzr_mouse_x = ___tzr_config.width / 2;
+ ___tzr_mouse_y = ___tzr_config.height / 2;
+
+ /* Setup audio. */
+ if (___tzr_config.mixer) {
+ if (Mix_OpenAudio(48000, MIX_DEFAULT_FORMAT, 8, 1024) < 0) {
+ sdl_error(0);
+ Mix_Quit();
+ ___tzr_config.mixer = false;
+ }
+ }
+ return 0;
+}
+/* sources/TZR_IsKeyDown.c */
+
+bool
+TZR_IsKeyDown(int scancode)
+{
+ const TZR_KeyState state = TZR_GetKeyState(scancode);
+ return (state == TZR_KEYSTATE_DOWN || state == TZR_KEYSTATE_PRESS);
+}
+/* sources/TZR_IsKeyPressed.c */
+
+bool
+TZR_IsKeyPressed(int scancode)
+{
+ return (TZR_GetKeyState(scancode) == TZR_KEYSTATE_PRESS);
+}
+/* sources/TZR_IsKeyReleased.c */
+
+bool
+TZR_IsKeyReleased(int scancode)
+{
+ if (TZR_GetKeyState(scancode) == TZR_KEYSTATE_RELEASE)
+ return true;
+ return false;
+}
+/* sources/TZR_IsKeyUp.c */
+
+bool
+TZR_IsKeyUp(int scancode)
+{
+ const TZR_KeyState state = TZR_GetKeyState(scancode);
+ return (state == TZR_KEYSTATE_UP || state == TZR_KEYSTATE_RELEASE);
+}
+/* sources/TZR_LightBegin.c */
+
+int
+TZR_LightBegin(void)
+{
+ if (!___tzr_config.light_system) {
+ fprintf(stderr, "light system disabled\n");
+ return -1;
+ }
+ if (SDL_SetRenderDrawBlendMode(___tzr_renderer, SDL_BLENDMODE_ADD) < 0)
+ return sdl_error(-1);
+ if (SDL_SetRenderTarget(___tzr_renderer, ___tzr_target_light) < 0)
+ return sdl_error(-1);
+ return 0;
+}
+/* sources/TZR_LightEnd.c */
+
+int
+TZR_LightEnd(void)
+{
+ return ___tzr_config.light_system ? 0 : -1;
+}
+/* sources/TZR_LoadResource.c */
+
+static TZR_ResourceType
+deduce_type(const char *path)
+{
+ const char *const path_extension = strrchr(path, '.');
+ if (path_extension == NULL)
+ return TZR_RES_RAW;
+ if (strcasecmp(path_extension, ".bmp") == 0 ||
+ strcasecmp(path_extension, ".png") == 0 ||
+ strcasecmp(path_extension, ".qoi") == 0)
+ return TZR_RES_IMAGE;
+ if (strcasecmp(path_extension, ".wav") == 0)
+ return TZR_RES_SOUND;
+ return TZR_RES_RAW;
+}
+
+TZR_Uint
+TZR_LoadResource(const char *path)
+{
+ return TZR_LoadResourceTyped(deduce_type(path), path);
+}
+/* sources/TZR_LoadResourceFromMemory.c */
+
+static int
+reserve_ressources(size_t size)
+{
+ if (size <= ___tzr_resources_capacity)
+ return 0;
+ size_t target = 16;
+ while (target < size * sizeof(TZR_Resource))
+ target *= 2;
+ TZR_Resource *new_resources;
+ if (___tzr_resources == NULL)
+ new_resources = malloc(target);
+ else
+ new_resources = realloc(___tzr_resources, target);
+ if (new_resources == NULL) {
+ perror("TZR_LoadResourceFromMemory:reserve_ressources");
+ return 1;
+ }
+ ___tzr_resources = new_resources;
+ return 0;
+}
+
+TZR_Uint
+TZR_LoadResourceFromMemory(TZR_ResourceType type, const void *data, int size)
+{
+ if (reserve_ressources(___tzr_resources_size + 1)) {
+ fprintf(stderr, "failed to reserve for new ressource\n");
+ return 0;
+ }
+ TZR_Resource *const res = &___tzr_resources[___tzr_resources_size];
+ res->type = type;
+ res->path = NULL;
+ if (TZR_DirectResourceLoad(res, data, size))
+ return 0;
+ ___tzr_resources_size += 1;
+ return ___tzr_resources_size;
+}
+/* sources/TZR_LoadResourceTyped.c */
+
+TZR_Uint
+TZR_LoadResourceTyped(TZR_ResourceType type, const char *path)
+{
+ for (TZR_Uint i = 0; i < ___tzr_resources_size; i++)
+ if (___tzr_resources[i].type == type &&
+ strcmp(___tzr_resources[i].path, path) == 0)
+ return i + 1;
+ FILE *const fp = fopen(path, "rb");
+ if (fp == NULL) {
+ perror(path);
+ return 0;
+ }
+ size_t size;
+ char *const data = drain(fp, &size);
+ fclose(fp);
+ if (data == NULL)
+ return 0;
+ const TZR_Uint id = TZR_LoadResourceFromMemory(type, data, size);
+ free(data);
+ if (id == 0)
+ return 0;
+ TZR_Resource *const res = TZR_GetResourcePointer(id);
+
+ const size_t path_len = strlen(path);
+ struct stat st = {0};
+ (void)stat(path, &st);
+ res->path = malloc(path_len + 1);
+ res->mtime = 0;
+ if (res->path == NULL)
+ perror("TZR_LoadResourceTyped");
+ else
+ strcpy(res->path, path);
+ return id;
+}
+/* sources/TZR_PlaySound.c */
+
+int
+_TZR_PlaySound(const TZR_PlaySoundArgs *args)
+{
+ if (args->id == 0) {
+ fprintf(stderr, "args->id is 0\n");
+ return -1;
+ }
+
+ if (TZR_GetResourceType(args->id) != TZR_RES_SOUND) {
+ fprintf(stderr, "%u isn't a sound\n", args->id);
+ return -1;
+ }
+
+ TZR_Sound *const sound = &TZR_GetResourcePointer(args->id)->sound;
+ Mix_VolumeChunk(sound->ptr, args->volume * MIX_MAX_VOLUME);
+ if (Mix_PlayChannel(-1, sound->ptr, args->loop) < 0)
+ return sdl_error(-1);
+ return 0;
+}
+/* sources/TZR_PollEvent.c */
+
+int
+TZR_PollEvent(TZR_Event *e)
+{
+ TZR_Event discard;
+ if (e == NULL)
+ e = &discard;
+
+ SDL_Event se;
+ while (SDL_PollEvent(&se)) switch (se.type) {
+ case SDL_QUIT:
+ e->type = TZR_EV_QUIT;
+ ___tzr_should_quit = 1;
+ return 1;
+ case SDL_KEYDOWN:
+ if (se.key.repeat)
+ break;
+ e->type = TZR_EV_KEYDOWN;
+ e->button = se.key.keysym.scancode;
+ ___tzr_keystates[e->button] = TZR_KEYSTATE_PRESS;
+ break;
+ case SDL_KEYUP:
+ e->type = TZR_EV_KEYUP;
+ e->button = se.key.keysym.scancode;
+ ___tzr_keystates[e->button] = TZR_KEYSTATE_RELEASE;
+ break;
+ case SDL_MOUSEBUTTONDOWN:
+ ___tzr_mouse_x = se.button.x;
+ ___tzr_mouse_y = se.button.y;
+ ___tzr_mousestates[se.button.button] = TZR_KEYSTATE_PRESS;
+ break;
+ case SDL_MOUSEBUTTONUP:
+ ___tzr_mouse_x = se.button.x;
+ ___tzr_mouse_y = se.button.y;
+ ___tzr_mousestates[se.button.button] = TZR_KEYSTATE_RELEASE;
+ break;
+ case SDL_MOUSEMOTION:
+ ___tzr_mouse_x = se.motion.x;
+ ___tzr_mouse_y = se.motion.y;
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+/* sources/TZR_Quit.c */
+
+void
+TZR_Quit(void)
+{
+ for (size_t i = 0; i < ___tzr_resources_size; i++)
+ TZR_DestroyResource(&___tzr_resources[i], 1);
+ if (___tzr_resources != NULL)
+ free(___tzr_resources);
+ ___tzr_resources = NULL;
+ ___tzr_resources_size = 0;
+ ___tzr_resources_capacity = 0;
+ if (___tzr_target_light != NULL) {
+ SDL_DestroyTexture(___tzr_target_light);
+ ___tzr_target_light = NULL;
+ }
+ if (___tzr_target != NULL) {
+ SDL_DestroyTexture(___tzr_target);
+ ___tzr_target = NULL;
+ }
+ if (___tzr_renderer != NULL) {
+ SDL_DestroyRenderer(___tzr_renderer);
+ ___tzr_renderer = NULL;
+ }
+ if (___tzr_window != NULL) {
+ SDL_DestroyWindow(___tzr_window);
+ ___tzr_window = NULL;
+ }
+ if (___tzr_config.mixer) {
+ Mix_CloseAudio();
+ Mix_Quit();
+ }
+ IMG_Quit();
+ SDL_Quit();
+}
+/* sources/TZR_ReloadResource.c */
+
+int
+TZR_ReloadResource(TZR_Uint id)
+{
+ TZR_Resource *const res = TZR_GetResourcePointer(id);
+ if (res->path == NULL) {
+ fprintf(stderr, "resource path of %u is NULL\n", id);
+ return -1;
+ }
+ FILE *const fp = fopen(res->path, "rb");
+ if (fp == NULL) {
+ perror(res->path);
+ return -1;
+ }
+ size_t size;
+ char *const data = drain(fp, &size);
+ fclose(fp);
+ if (data == NULL)
+ return -1;
+ TZR_Resource new_res;
+ memcpy(&new_res, res, sizeof(TZR_Resource));
+ const int load_rv = TZR_DirectResourceLoad(&new_res, data, size);
+ free(data);
+ if (load_rv)
+ return -1;
+ TZR_DestroyResource(res, 0);
+ memcpy(res, &new_res, sizeof(TZR_Resource));
+ return 0;
+}
+/* sources/TZR_ResourcesWatch.c */
+
+void
+TZR_ResourcesWatch(void)
+{
+ for (TZR_Uint i = 0; i < ___tzr_resources_size; i++) {
+ TZR_Resource *const res = TZR_GetResourcePointer(i + 1);
+ if (res->path == NULL)
+ continue;
+ struct stat st = {0};
+ (void)stat(res->path, &st);
+ if (st.st_mtime != res->mtime) {
+ if (res->mtime != 0)
+ TZR_ReloadResource(i + 1);
+ res->mtime = st.st_mtime;
+ }
+ }
+}
+/* sources/TZR_ScreenTransform.c */
+
+void
+TZR_ScreenTransform(int *x, int *y)
+{
+ if (___tzr_scale == 0.0)
+ return;
+ if (x != NULL) {
+ *x -= ___tzr_off_x;
+ *x /= ___tzr_scale;
+ }
+ if (y != NULL) {
+ *y -= ___tzr_off_y;
+ *y /= ___tzr_scale;
+ }
+}
+/* sources/TZR_ShouldQuit.c */
+
+int
+TZR_ShouldQuit(void)
+{
+ return ___tzr_should_quit;
+}
+
+/* commit hash: 6370f11b5a93a00045969fa4637db46d5e792dae */
diff --git a/src/TZR.h b/src/TZR.h
new file mode 100644
index 0000000..672a3c3
--- /dev/null
+++ b/src/TZR.h
@@ -0,0 +1,370 @@
+#pragma once
+#include <limits.h>
+#include <SDL2/SDL_main.h>
+#include <SDL2/SDL_mixer.h>
+#include <SDL2/SDL_render.h>
+#include <SDL2/SDL_scancode.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+/* headers/TZR_types.h */
+
+enum TZR_ResourceType {
+ TZR_RES_RAW,
+ TZR_RES_IMAGE,
+ TZR_RES_SOUND,
+ ___TZR_RES_COUNT
+};
+
+enum TZR_EventType {
+ TZR_EV_QUIT,
+ TZR_EV_KEYDOWN,
+ TZR_EV_KEYUP
+};
+
+enum TZR_KeyState {
+ TZR_KEYSTATE_UP,
+ TZR_KEYSTATE_DOWN,
+ TZR_KEYSTATE_RELEASE,
+ TZR_KEYSTATE_PRESS
+};
+
+typedef unsigned int TZR_Uint;
+typedef struct TZR_Config TZR_Config;
+typedef struct TZR_Point TZR_Point;
+typedef struct TZR_Color TZR_Color;
+typedef struct TZR_Rect TZR_Rect;
+typedef enum TZR_ResourceType TZR_ResourceType;
+typedef struct TZR_Raw TZR_Raw;
+typedef struct TZR_Image TZR_Image;
+typedef struct TZR_Sound TZR_Sound;
+typedef struct TZR_Resource TZR_Resource;
+typedef enum TZR_EventType TZR_EventType;
+typedef struct TZR_Event TZR_Event;
+typedef struct TZR_DrawImageArgs TZR_DrawImageArgs;
+typedef struct TZR_DrawRectangleArgs TZR_DrawRectangleArgs;
+typedef struct TZR_PlaySoundArgs TZR_PlaySoundArgs;
+typedef enum TZR_KeyState TZR_KeyState;
+
+struct TZR_Config {
+ int _;
+ int width;
+ int height;
+ int target_fps;
+ bool pixel_perfect;
+ bool show_cursor;
+ bool mixer;
+ bool png_loading;
+ bool light_system;
+ const char *title;
+};
+
+struct TZR_Point {
+ int x;
+ int y;
+};
+
+struct TZR_Color {
+ int _;
+ float r;
+ float g;
+ float b;
+ float a;
+};
+
+struct TZR_Rect {
+ TZR_Point from;
+ TZR_Point to;
+};
+
+struct TZR_Raw {
+ void *data;
+ size_t size;
+};
+
+struct TZR_Image {
+ int width;
+ int height;
+ SDL_Texture *ptr;
+};
+
+struct TZR_Sound {
+ Mix_Chunk *ptr;
+};
+
+struct TZR_Resource {
+ TZR_ResourceType type;
+ char *path; /* allocated and freed by TZR; can be NULL */
+ long mtime;
+ union {
+ TZR_Raw raw; /* raw file data */
+ TZR_Image image;
+ TZR_Sound sound;
+ };
+};
+
+struct TZR_Event {
+ TZR_EventType type;
+ int button;
+};
+
+struct TZR_DrawImageArgs {
+ int _;
+ TZR_Uint id;
+ int x;
+ int y;
+ int ix;
+ int iy;
+ int w;
+ int h;
+ float r;
+ float sx;
+ float sy;
+ bool center;
+ bool flip_x;
+ bool flip_y;
+};
+
+struct TZR_DrawRectangleArgs {
+ int _;
+ int x;
+ int y;
+ int w;
+ int h;
+ bool fill;
+ bool center;
+};
+
+struct TZR_PlaySoundArgs {
+ int _;
+ int id;
+ int loop;
+ float volume;
+};
+/* headers/TZR_globals.h */
+
+extern TZR_Config ___tzr_config;
+extern TZR_Color ___tzr_color;
+extern TZR_Resource *___tzr_resources;
+extern size_t ___tzr_resources_capacity;
+extern size_t ___tzr_resources_size;
+extern SDL_Window *___tzr_window;
+extern SDL_Renderer *___tzr_renderer;
+extern SDL_Texture *___tzr_target;
+extern SDL_Texture *___tzr_target_light;
+extern unsigned long ___tzr_tick;
+extern unsigned long ___tzr_next_time;
+extern unsigned long ___tzr_min_dt;
+extern int ___tzr_should_quit;
+extern int ___tzr_mouse_x;
+extern int ___tzr_mouse_y;
+extern TZR_KeyState ___tzr_keystates[SDL_NUM_SCANCODES];
+extern TZR_KeyState ___tzr_mousestates[256];
+extern float ___tzr_scale;
+extern int ___tzr_off_x;
+extern int ___tzr_off_y;
+/* headers/TZR_resource.h */
+
+#define TZR_RES TZR_LoadResource
+
+/* Return 0 on error. */
+#if __STDC_VERSION__ > 201710L
+[[nodiscard]]
+#endif
+TZR_Uint TZR_LoadResourceFromMemory(TZR_ResourceType type, const void *data, int size);
+#if __STDC_VERSION__ > 201710L
+[[nodiscard]]
+#endif
+TZR_Uint TZR_LoadResourceTyped(TZR_ResourceType type, const char *path);
+
+/* Return 0 on error; try to autodetect resource type from file extension. */
+#if __STDC_VERSION__ > 201710L
+[[nodiscard]]
+#endif
+TZR_Uint TZR_LoadResource(const char *path);
+
+/* Doesn't handle invalid ID. Pointer might get invalidated by resource
+ * creation calls, use with caution. */
+TZR_Resource *TZR_GetResourcePointer(TZR_Uint id);
+
+/* Doesn't handle invalid ID. Pointer might get invalidated by resource
+ * creation calls, use with caution. */
+TZR_Raw *TZR_GetRawResource(TZR_Uint id);
+
+/* Doesn't handle invalid ID. */
+TZR_ResourceType TZR_GetResourceType(TZR_Uint id);
+
+/* Doesn't handle invalid ID. */
+const char *TZR_GetResourcePath(TZR_Uint id);
+
+/* Doesn't handle invalid ID. */
+int TZR_GetImageWidth(TZR_Uint id);
+
+/* Doesn't handle invalid ID. */
+int TZR_GetImageHeight(TZR_Uint id);
+
+/* Reload ressource. Returns -1 on error and doesn't apply changes. */
+int TZR_ReloadResource(TZR_Uint id);
+
+/* Watch for changes on managed resources and hotreload if appropriate. */
+void TZR_ResourcesWatch(void);
+
+/* Used internaly. */
+#if __STDC_VERSION__ > 201710L
+[[nodiscard]]
+#endif
+int TZR_DirectResourceLoad(TZR_Resource *res, const void *data, int size);
+void TZR_DestroyResource(TZR_Resource *res, int free_path);
+/* headers/TZR_sound.h */
+
+#define TZR_PlaySound(...) _TZR_PlaySound(&(const TZR_PlaySoundArgs){ \
+ .id=0, .loop=0, .volume=1.0f, ._=0, __VA_ARGS__ })
+#ifdef TZR_PARANOID
+#if __STDC_VERSION__ > 201710L
+[[nodiscard]]
+#endif
+#endif
+int _TZR_PlaySound(const TZR_PlaySoundArgs *args);
+/* headers/TZR_events.h */
+
+/* Write event data to `e`. Return 0 when event queue is empty. */
+int TZR_PollEvent(TZR_Event *e);
+
+/* Drain queued events with TZR_PollEvent and call TZR_ResourcesWatch. */
+void TZR_CycleEvents(void);
+
+void TZR_GetMousePosition(int *x, int *y);
+/* headers/TZR_render.h */
+
+/* All draw calls should happen between TZR_DrawBegin and TZR_DrawEnd.
+ * Return -1 on error. */
+#if __STDC_VERSION__ > 201710L
+[[nodiscard]]
+#endif
+int TZR_DrawBegin(void);
+
+/* Return -1 on error. */
+#if __STDC_VERSION__ > 201710L
+[[nodiscard]]
+#endif
+int TZR_DrawEnd(void);
+
+/* Return -1 on error. */
+#define TZR_DrawSetColor(...) _TZR_DrawSetColor(&(const TZR_Color){ \
+ .r=-1.0f, .g=-1.0f, .b=-1.0f, .a=-1.0f, ._=0, __VA_ARGS__ })
+#ifdef TZR_PARANOID
+#if __STDC_VERSION__ > 201710L
+[[nodiscard]]
+#endif
+#endif
+int _TZR_DrawSetColor(const TZR_Color *color);
+
+/* Return -1 on error. */
+int TZR_DrawSetColor8(uint8_t r, uint8_t g, uint8_t b, uint8_t a);
+
+/* Return -1 on error. */
+#ifdef TZR_PARANOID
+#if __STDC_VERSION__ > 201710L
+[[nodiscard]]
+#endif
+#endif
+int TZR_DrawClear(void);
+
+/* Return -1 on error. Draw point on `x`;`y` in the framebuffer. */
+#ifdef TZR_PARANOID
+#if __STDC_VERSION__ > 201710L
+[[nodiscard]]
+#endif
+#endif
+int TZR_DrawPoint(int x, int y);
+
+/* Return -1 on error. Draw line between `x0`;`y0` and `x1`;`y1` in the
+ * framebuffer. */
+#ifdef TZR_PARANOID
+#if __STDC_VERSION__ > 201710L
+[[nodiscard]]
+#endif
+#endif
+int TZR_DrawLine(int x0, int y0, int x1, int y1);
+
+/* Return -1 on error. Draw rectangle at `x`;`y` position of size `w`x`h` in the
+ * framebuffer. */
+#define TZR_DrawRectangle(...) _TZR_DrawRectangle( \
+ &(const TZR_DrawRectangleArgs){ \
+ .x=0, .y=0, .w=0, .h=0, .fill=false, .center=false, ._=0, __VA_ARGS__ })
+#ifdef TZR_PARANOID
+#if __STDC_VERSION__ > 201710L
+[[nodiscard]]
+#endif
+#endif
+int _TZR_DrawRectangle(const TZR_DrawRectangleArgs *args);
+
+/* Return -1 on error. Draw texture ressource `id` at `x`;`y` position of
+ * the framebuffer. */
+#define TZR_DrawImage(...) _TZR_DrawImage(&(const TZR_DrawImageArgs){ \
+ .x=0, .y=0, .ix=0, .iy=0, .w=INT_MIN, .h=INT_MIN, .r=0.0f, .sx=1.0f, \
+ .sy=1.0f, .center=false, .flip_x=false, .flip_y=false, ._=0, \
+ __VA_ARGS__ })
+#ifdef TZR_PARANOID
+#if __STDC_VERSION__ > 201710L
+[[nodiscard]]
+#endif
+#endif
+int _TZR_DrawImage(const TZR_DrawImageArgs *args);
+
+void TZR_ScreenTransform(int *x, int *y);
+/* headers/TZR_light.h */
+
+#if __STDC_VERSION__ > 201710L
+[[nodiscard]]
+#endif
+int TZR_LightBegin(void);
+
+#if __STDC_VERSION__ > 201710L
+[[nodiscard]]
+#endif
+int TZR_LightEnd(void);
+/* headers/TZR_keystate.h */
+
+TZR_KeyState TZR_GetKeyState(int scancode);
+bool TZR_IsKeyDown(int scancode);
+bool TZR_IsKeyUp(int scancode);
+bool TZR_IsKeyReleased(int scancode);
+bool TZR_IsKeyPressed(int scancode);
+/* headers/TZR.h */
+
+/* TZR manages all loaded resources internally and tries to avoid duplicate.
+ * A resource can be loaded multiple times if asked, identification doesn't rely
+ * on filepath. In doubt, use TZR_LoadResource ONCE per asset and store the ID.
+ * Resources are exposed as ID instead of pointer, as a future proof measure in
+ * case TZR needs to rearrange memory at runtime in the future. */
+
+#define TZR_Init(...) _TZR_Init(&(const TZR_Config){ \
+ .width=256, \
+ .height=224, \
+ .target_fps=60, \
+ .pixel_perfect=true, \
+ .show_cursor=false, \
+ .mixer=true, \
+ .png_loading=true, \
+ .light_system=false, \
+ .title="TZR", \
+ ._=0, __VA_ARGS__ })
+
+/* Should be called only once before usage of any other function unless
+ * otherwise specified. On error this calls TZR_Quit and returns -1.
+ * `config` will be duplicated internally and can be safely discarded. */
+#if __STDC_VERSION__ > 201710L
+[[nodiscard]]
+#endif
+int _TZR_Init(const TZR_Config *config);
+
+/* Destroy and free everything created by TZR. */
+void TZR_Quit(void);
+
+/* Return non-zero value when a quit event has been received. */
+int TZR_ShouldQuit(void);
+
+unsigned long TZR_GetTick(void);
+
+/* commit hash: 6370f11b5a93a00045969fa4637db46d5e792dae */
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..b542d80
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,25 @@
+#include "TZR.h"
+#include "px.h"
+
+int
+main(void)
+{
+ if (TZR_Init(128, 128, 60, true, false, false, false, false, "indexed"))
+ return 1;
+ if (atexit(TZR_Quit)) {
+ TZR_Quit();
+ return 1;
+ }
+
+ while (!TZR_ShouldQuit()) {
+ TZR_CycleEvents();
+
+ pxCls(1);
+
+ TZR_DrawBegin();
+ pxRender();
+ TZR_DrawEnd();
+ }
+
+ return 0;
+}
diff --git a/src/px.c b/src/px.c
new file mode 100644
index 0000000..f7a89cd
--- /dev/null
+++ b/src/px.c
@@ -0,0 +1,43 @@
+#include "px.h"
+#include "TZR.h"
+
+PxCol pxbuf[PX_WIDTH * PX_HEIGHT] = {0};
+PxPal pxpal[256] = {
+ /* PICO-8 palette */
+ {0x00, 0x00, 0x00},
+ {0x1d, 0x2b, 0x53},
+ {0x7e, 0x25, 0x53},
+ {0x00, 0x87, 0x51},
+ {0xab, 0x52, 0x36},
+ {0x5f, 0x57, 0x4f},
+ {0xc2, 0xc3, 0xc7},
+ {0xff, 0xf1, 0xe8},
+ {0xff, 0x00, 0x4d},
+ {0xff, 0xa3, 0x00},
+ {0xff, 0xec, 0x27},
+ {0x00, 0xe4, 0x36},
+ {0x29, 0xad, 0xff},
+ {0x83, 0x76, 0x9c},
+ {0xff, 0x77, 0xa8},
+ {0xff, 0xcc, 0xaa},
+};
+
+void
+pxRender(void)
+{
+ for (int y = 0; y < PX_HEIGHT; y += 1) {
+ for (int x = 0; x < PX_WIDTH; x += 1) {
+ const PxCol col = pxbuf[x + y * PX_WIDTH];
+ TZR_DrawSetColor8(pxpal[col].r, pxpal[col].g,
+ pxpal[col].b, 255);
+ TZR_DrawPoint(x, y);
+ }
+ }
+}
+
+void
+pxCls(PxCol c)
+{
+ for (unsigned i = 0; i < sizeof(pxbuf); i++)
+ pxbuf[i] = c;
+}
diff --git a/src/px.h b/src/px.h
new file mode 100644
index 0000000..4429182
--- /dev/null
+++ b/src/px.h
@@ -0,0 +1,19 @@
+#pragma once
+#include <stdint.h>
+
+enum {
+ PX_WIDTH = 128,
+ PX_HEIGHT = 128
+};
+
+typedef uint8_t PxCol;
+
+typedef struct {
+ uint8_t r, g, b;
+} PxPal;
+
+extern PxCol pxbuf[PX_WIDTH * PX_HEIGHT];
+extern PxPal pxpal[256];
+
+void pxRender(void);
+void pxCls(PxCol c);