diff options
| author | iamcheeseman <[email protected]> | 2026-05-08 19:30:44 -0400 |
|---|---|---|
| committer | iamcheeseman <[email protected]> | 2026-05-08 19:30:44 -0400 |
| commit | 5625a8626fe303748b205c80f87035593cf2f561 (patch) | |
| tree | 5e5a5adb6f1265358ae21ec47cd384362345df5a /teensy | |
Initial commit
Diffstat (limited to 'teensy')
| -rw-r--r-- | teensy/common.c | 30 | ||||
| -rw-r--r-- | teensy/common.h | 14 | ||||
| -rw-r--r-- | teensy/context.c | 51 | ||||
| -rw-r--r-- | teensy/context.h | 10 | ||||
| -rw-r--r-- | teensy/dyn_arr.c | 44 | ||||
| -rw-r--r-- | teensy/dyn_arr.h | 33 | ||||
| -rw-r--r-- | teensy/log.c | 33 | ||||
| -rw-r--r-- | teensy/log.h | 47 | ||||
| -rw-r--r-- | teensy/mem.c | 171 | ||||
| -rw-r--r-- | teensy/mem.h | 24 | ||||
| -rw-r--r-- | teensy/platform.h | 12 | ||||
| -rw-r--r-- | teensy/renderer.c | 227 | ||||
| -rw-r--r-- | teensy/renderer.h | 10 | ||||
| -rw-r--r-- | teensy/teensy.h | 108 |
14 files changed, 814 insertions, 0 deletions
diff --git a/teensy/common.c b/teensy/common.c new file mode 100644 index 0000000..4da4004 --- /dev/null +++ b/teensy/common.c @@ -0,0 +1,30 @@ +#include "common.h" + +#include <stdarg.h> +#include <stdio.h> + +char *read_file(const char *path, size_t *size_out) +{ + FILE *file = fopen(path, "r"); + if (!file) + ty_log_fatal(TY_ERR_IO, "could not open file '%s'", path); + + fseek(file, 0L, SEEK_END); + size_t size = ftell(file); + rewind(file); + + char *dat = ty_alloc(sizeof(char) * (size + 1)); + + size_t bytes_read = fread(dat, sizeof(char), size, file); + + if (bytes_read < size) { + ty_log_fatal(TY_ERR_IO, "could not read file '%s'", path); + } + + fclose(file); + + if (size_out) + *size_out = size; + + return dat; +} diff --git a/teensy/common.h b/teensy/common.h new file mode 100644 index 0000000..6e3e66f --- /dev/null +++ b/teensy/common.h @@ -0,0 +1,14 @@ +#ifndef COMMON_H_ +#define COMMON_H_ + +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + +#include "log.h" +#include "mem.h" + +#define TY_PI 3.14 +#define TY_DEG2RAD (180 / PI) + +#endif // COMMON_H_ diff --git a/teensy/context.c b/teensy/context.c new file mode 100644 index 0000000..7095fe3 --- /dev/null +++ b/teensy/context.c @@ -0,0 +1,51 @@ +#include "context.h" +#include "renderer.h" + +#include <math.h> + +#include "platform.h" + +struct ty_ctx ctx; +extern struct ty_renderer r; + +void ty_init(struct ty_creation_hints creation_hints) +{ + ty_init_mem(); + + ctx.creation_hints = creation_hints; + ctx.ticrate = 1.0 / creation_hints.ticrate; + ctx.prev_tic_ts = 0; + + ty_platform_init(&ctx); + ty_init_renderer(); +} + +void ty_deinit(void) +{ + ty_platform_deinit(); + ty_deinit_renderer(); + ty_deinit_mem(); +} + +bool ty_is_game_running(void) +{ + return !ty_platform_os_wants_quit(); +} + +double ty_get_time(void) +{ + return ty_platform_get_time(); +} + +int ty_tick(void) +{ + double current_time = ty_get_time(); + double time_since_tic = current_time - ctx.prev_tic_ts; + + int tics = time_since_tic / ctx.ticrate; + if (tics > 0) { + ctx.prev_tic_ts = current_time; + ty_platform_frame(r.screen); + } + return tics; +} diff --git a/teensy/context.h b/teensy/context.h new file mode 100644 index 0000000..0bdc5f9 --- /dev/null +++ b/teensy/context.h @@ -0,0 +1,10 @@ +#ifndef CONTEXT_H_ +#define CONTEXT_H_ + +#include "common.h" +#include "renderer.h" +#include "teensy.h" + +extern struct ty_ctx ctx; + +#endif // CONTEXT_H_ diff --git a/teensy/dyn_arr.c b/teensy/dyn_arr.c new file mode 100644 index 0000000..38e3b31 --- /dev/null +++ b/teensy/dyn_arr.c @@ -0,0 +1,44 @@ +#include "dyn_arr.h" + +#include <stdio.h> +#include <assert.h> + +void *_ty_list_create(size_t type_size, int init_cap) +{ + init_cap = init_cap < TY_LIST_MIN_CAP + ? TY_LIST_MIN_CAP + : init_cap; + + int *arr = (int*)ty_alloc(type_size * init_cap + sizeof(int) * 2); + + arr[0] = 0; + arr[1] = init_cap; + return (void*)(arr + 2); +} + +void *_ty_list_append_slot(size_t type_size, void **arr) +{ + int *cap = ty_list_cap_ptr(*arr); + int *len = ty_list_len_ptr(*arr); + + (*len)++; + + if (*len > *cap) { + *cap *= TY_LIST_GROW_RATE; + + void* base = ty_list_base(*arr); + base = ty_realloc(base, (type_size * *cap) + sizeof(int) * 2); + assert(base != NULL); // Just to handle the case + *arr = (void*)((int*)base + 2); + + len = ty_list_len_ptr(*arr); + } + + return (char*)*arr + ((*len - 1) * type_size); +} + +void ty_list_free(void *arr) +{ + void *base = ty_list_base(arr); + ty_free(base); +} diff --git a/teensy/dyn_arr.h b/teensy/dyn_arr.h new file mode 100644 index 0000000..7a45d4e --- /dev/null +++ b/teensy/dyn_arr.h @@ -0,0 +1,33 @@ +#ifndef DYN_ARR_H_ +#define DYN_ARR_H_ + +#include "common.h" + +#define TY_LIST_MIN_CAP 8 +#define TY_LIST_GROW_RATE 2 + +#define ty_list_create(T, init_capacity) \ + ((T*)_ty_list_create(sizeof(T), init_capacity)) + +#define ty_list_len_ptr(arr) ((int*)arr - 2) +#define ty_list_len(arr) (*ty_list_len_ptr(arr)) + +#define ty_list_cap_ptr(arr) ((int*)arr - 1) +#define ty_list_cap(arr) (*ty_list_cap_ptr(arr)) + +#define ty_list_base(arr) ((void*)ty_list_len_ptr(arr)) + +#define ty_list_append_slot(T, arr) \ + ((T*)_ty_list_append_slot(sizeof(T), (void**)arr)) + +#define ty_list_append(T, arr, elem) \ + (*ty_list_append_slot(T, arr) = elem) + +#define ty_list_clear(arr) \ + (*ty_list_len_ptr(arr) = 0) + +void *_ty_list_create(size_t type_size, int init_cap); +void *_ty_list_append_slot(size_t type_size, void **arr); +void ty_list_free(void *arr); + +#endif // DYN_ARR_H_ diff --git a/teensy/log.c b/teensy/log.c new file mode 100644 index 0000000..fbd96a2 --- /dev/null +++ b/teensy/log.c @@ -0,0 +1,33 @@ +#include "log.h" + +#include <stdarg.h> + +#include "common.h" + +void ty_log_msg( + FILE *file, + const char *src_name, + int src_line, + const char *type, + const char *fmt, + ... +) +{ + fprintf(file, "[%s] (%s:%d) ", type, src_name, src_line); + + va_list args; + va_start(args, fmt); + vfprintf(file, fmt, args); + va_end(args); + + putc('\n', file); +} + +void _ty_log_plain(FILE *file, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + vfprintf(file, fmt, args); + va_end(args); + putc('\n', file); +} diff --git a/teensy/log.h b/teensy/log.h new file mode 100644 index 0000000..cebf39b --- /dev/null +++ b/teensy/log.h @@ -0,0 +1,47 @@ +#ifndef LOG_H_ +#define LOG_H_ + +#include <stdio.h> +#include <stdlib.h> + +// Don't include common.h, because this should be included there + +#define ty_log_info(fmt, ...) \ + ty_log_msg(stdout, __FILE__, __LINE__, "info", fmt, ## __VA_ARGS__) +#define ty_log_warn(fmt, ...) \ + ty_log_msg(stderr, __FILE__, __LINE__, "warn", fmt, ## __VA_ARGS__) +#define ty_log_err(fmt, ...) \ + ty_log_msg(stderr, __FILE__, __LINE__, "error", fmt, ## __VA_ARGS__) +#define ty_log_fatal(ec, fmt, ...) \ + (ty_log_msg(stderr, __FILE__, __LINE__, "fatal", fmt, ## __VA_ARGS__), \ + exit(ec)) +#define ty_olog(fmt, ...) \ + _log_plain(stdout, fmt, ## __VA_ARGS__) +#define ty_elog(fmt, ...) \ + _log_plain(stderr, fmt, ## __VA_ARGS__) + +// To be passed as the exit code to log_fatal() + +// General errors +#define TY_ERR_MEM 0x01 +#define TY_ERR_IO 0x02 +#define TY_ERR_MEM_LEAK 0x03 +#define TY_PLATFORM_ERR 0x04 + +// Rendering errors +#define TY_ERR_GLFW_INIT 0x30 +#define TY_ERR_RENDERER_NOT_INIT 0x31 +#define TY_ERR_RENDER_OOB 0x32 // Render out-of-bounds + +void ty_log_msg( + FILE *file, + const char *src_name, + int src_line, + const char *type, + const char *fmt, + ... +); + +void _ty_log_plain(FILE *file, const char *fmt, ...); + +#endif // LOG_H_ diff --git a/teensy/mem.c b/teensy/mem.c new file mode 100644 index 0000000..2c938a6 --- /dev/null +++ b/teensy/mem.c @@ -0,0 +1,171 @@ +#include "mem.h" + +#include "common.h" +#include "dyn_arr.h" + +// Array to track temp allocations. +void **temp_allocations; + +#ifdef TEENSY_DEBUG + +#include <execinfo.h> + +#define BACKTRACE_SIZE 16 + +struct alloc { + void *ptr; + size_t size; + int backtrace_len; + char **backtrace; +}; + +int allocations_cap; +int allocations_len; +struct alloc *allocations; + +#endif // TODO TEENSY_DEBUG + +void ty_init_mem(void) +{ +#ifdef TEENSY_DEBUG + allocations_len = 0; + allocations_cap = 8; + allocations = malloc(sizeof(struct alloc) * 8); + if (!allocations) + ty_log_fatal(TY_ERR_MEM, "(%s) (track) ran out of memory", __func__); +#endif // TEENSY_DEBUG + + temp_allocations = ty_list_create(void*, 0); +} + +void ty_deinit_mem(void) +{ + ty_list_free(temp_allocations); +#ifdef TEENSY_DEBUG + fprintf(stderr, "%d allocations leaked\n", allocations_len); + for (int i = 0; i < allocations_len; i++) { + struct alloc *alloc = &allocations[i]; + fprintf( + stderr, + "leaked %zu bytes at %p\n", + alloc->size, + alloc->ptr + ); + for (int j = 0; j < alloc->backtrace_len; j++) { + fprintf(stderr, "\t%s\n", alloc->backtrace[j]); + } + putc('\n', stderr); + + free(alloc->backtrace); + } + free(allocations); + + if (allocations_len > 0) + exit(TY_ERR_MEM_LEAK); +#endif // TEENSY_DEBUG +} + +void ty_free_temp_allocs(void) +{ + for (int i = 0; i < ty_list_len(temp_allocations); i++) { + void *temp = temp_allocations[i]; + ty_free(temp); + } + ty_list_clear(temp_allocations); +} + +void *ty_alloc(size_t size) +{ + void *ptr = malloc(size); + if (!ptr) + ty_log_fatal(TY_ERR_MEM, "(%s) ran out of memory", __func__); + +#ifdef TEENSY_DEBUG + // Log allocation + struct alloc alloc; + void *bt[BACKTRACE_SIZE]; + int bt_len = backtrace(bt, BACKTRACE_SIZE); + char **symbols = backtrace_symbols(bt, bt_len); + + alloc.ptr = ptr; + alloc.size = size; + alloc.backtrace_len = bt_len; + alloc.backtrace = symbols; + + if (allocations_len + 1 > allocations_cap) { + allocations_cap *= 2; + allocations = realloc( + allocations, + sizeof(struct alloc) * allocations_cap + ); + if (!allocations) + ty_log_fatal( + TY_ERR_MEM, + "(%s) (track) ran out of memory", + __func__ + ); + } + + allocations[allocations_len++] = alloc; +#endif // TEENSY_DEBUG + + return ptr; +} + +void *ty_realloc(void *ptr, size_t new_size) +{ + if (new_size == 0) { + ty_free(ptr); + return NULL; + } + + void *new_ptr = realloc(ptr, new_size); + if (!new_ptr) + ty_log_fatal(TY_ERR_MEM, "(%s) ran out of memory", __func__); + +#ifdef TEENSY_DEBUG + for (int i = 0; i < allocations_len; i++) { + struct alloc *alloc = &allocations[i]; + if (alloc->ptr == ptr) { + alloc->ptr = new_ptr; + alloc->size = new_size; + break; + } + } +#endif // TEENSY_DEBUG + + return new_ptr; +} + +void *ty_talloc(size_t size) +{ + void *ptr = ty_alloc(size); + ty_list_append(void*, &temp_allocations, ptr); + return ptr; +} + +void ty_free(void *ptr) +{ + free(ptr); + +#ifdef TEENSY_DEBUG + for (int i = 0; i < allocations_len; i++) { + struct alloc *alloc = &allocations[i]; + if (alloc->ptr == ptr) { + free(alloc->backtrace); + *alloc = allocations[allocations_len - 1]; + allocations_len--; + break; + } + } +#endif // TEENSY_DEBUG +} + +int ty_alloc_count(void) +{ +#ifdef TEENSY_DEBUG + return allocations_len; +#else + return -1; +#endif +} diff --git a/teensy/mem.h b/teensy/mem.h new file mode 100644 index 0000000..61f3ee2 --- /dev/null +++ b/teensy/mem.h @@ -0,0 +1,24 @@ +#ifndef MEM_H_ +#define MEM_H_ + +#include <stdlib.h> + +// NOTE: Overrides GLFW allocator +void ty_init_mem(void); +void ty_deinit_mem(void); +void ty_free_temp_allocs(void); +// Temp allocation. Freed at the end of every frame. Do NOT realloc. +void *ty_talloc(size_t size); + +// These mem_* functions handle the case of a bad allocation. No need to check +// for NULL after allocating with these. In debug builds, they will also track +// allocations and report any memory still in use when the program exits. + +void *ty_alloc(size_t size); +void *ty_realloc(void *ptr, size_t new_size); +void ty_free(void *ptr); + +// Returns -1 in release builds. +int ty_alloc_count(void); + +#endif // MEM_H_ diff --git a/teensy/platform.h b/teensy/platform.h new file mode 100644 index 0000000..df3c5f8 --- /dev/null +++ b/teensy/platform.h @@ -0,0 +1,12 @@ +#ifndef PLATFORM_H_ +#define PLATFORM_H_ + +// This platform functions are to be implemented by a platform library. + +void ty_platform_init(struct ty_ctx *ctx); +void ty_platform_deinit(void); +void ty_platform_frame(struct ty_image img); +bool ty_platform_os_wants_quit(void); +double ty_platform_get_time(void); + +#endif // PLATFORM_H_ diff --git a/teensy/renderer.c b/teensy/renderer.c new file mode 100644 index 0000000..9e5f824 --- /dev/null +++ b/teensy/renderer.c @@ -0,0 +1,227 @@ +#include "renderer.h" + +#include <stdio.h> +#include <string.h> +#include <math.h> + +#include "context.h" +#include "dyn_arr.h" + +struct ty_renderer r; + +static +void renderer_init_check() +{ +#ifdef TEENSY_DEBUG + if ( + r.screen.data == NULL || + r.screen.width * r.screen.height == 0 + ) { + ty_log_fatal( + TY_ERR_RENDERER_NOT_INIT, + "renderer is not yet intialized" + ); + } +#endif +} + +static +void image_bounds_check(struct ty_image img, struct ty_vec2i pos) +{ +#ifdef TEENSY_DEBUG + if ( + pos.x < 0 || + pos.x >= img.width || + pos.y < 0 || + pos.y >= img.height + ) { + ty_log_fatal( + TY_ERR_RENDER_OOB, + "rendered at (%d, %d), when bounds are " + "(0, 0) to (%d, %d)", + pos.x, pos.y, + img.width, img.height + ); + } +#endif +} + +void ty_init_renderer(void) +{ + r.screen = ty_create_image( + ctx.creation_hints.win_width, + ctx.creation_hints.win_height, + NULL + ); +} + +void ty_deinit_renderer(void) +{ + renderer_init_check(); + + ty_free_image(r.screen); +} + +struct ty_image ty_create_image(int w, int h, const struct ty_color* data) +{ + struct ty_image img; + + img.width = w; + img.height = h; + + size_t size = sizeof(struct ty_color) * w * h; + img.data = ty_alloc(size); + if (data) + memcpy(img.data, data, size); + else + memset(img.data, 0, size); + + return img; +} + +void ty_free_image(struct ty_image img) +{ + ty_free(img.data); +} + +struct ty_color ty_img_get_pixel(struct ty_image img, struct ty_vec2i pos) +{ + image_bounds_check(img, pos); + return img.data[pos.y * img.width + pos.x]; +} + +void ty_img_set_pixel( + struct ty_image img, + struct ty_vec2i pos, + struct ty_color color +) { + image_bounds_check(img, pos); + img.data[pos.y * img.width + pos.x] = color; +} + +void ty_draw_clear(struct ty_color col) +{ + renderer_init_check(); + for (int i = 0; i < r.screen.width * r.screen.height; i++) { + r.screen.data[i] = col; + } +} + +void ty_draw_image(struct ty_image img, struct ty_vec2i pos) +{ + int x1 = fmin(fmax(floor(pos.x), 0), r.screen.width); + int y1 = fmin(fmax(floor(pos.y), 0), r.screen.height); + + int x2 = fmin(fmax(floor(pos.x + img.width), 0), r.screen.width); + int y2 = fmin(fmax(floor(pos.y + img.height), 0), r.screen.height); + + for (int dx = x1; dx < x2; dx++) { + for (int dy = y1; dy < y2; dy++) { + struct ty_vec2i tex_coord = ty_vec2i(dx - x1, dy - y1); + ty_img_set_pixel( + r.screen, + (struct ty_vec2i){dx, dy}, + ty_img_get_pixel(img, tex_coord) + ); + } + } +} + +void ty_draw_image_ex( + struct ty_image img, + struct ty_recti src, + struct ty_recti dst +) { + int x1 = fmin(fmax(floor(dst.x), 0), r.screen.width); + int y1 = fmin(fmax(floor(dst.y), 0), r.screen.height); + int x2 = fmin(fmax(floor(dst.x + dst.w), 0), r.screen.width); + int y2 = fmin(fmax(floor(dst.y + dst.h), 0), r.screen.height); + + for (int dx = x1; dx < x2; dx++) { + for (int dy = y1; dy < y2; dy++) { + int tex_x = (dx - x1) / (dst.w / src.w) + src.x; + int tex_y = (dy - y1) / (dst.h / src.h) + src.y; + + ty_img_set_pixel( + r.screen, + (struct ty_vec2i){dx, dy}, + ty_img_get_pixel(img, ty_vec2i(tex_x, tex_y)) + ); + } + } +} + +void ty_draw_rect(struct ty_recti rect, struct ty_color color) +{ + int x1 = fmin(fmax(floor(rect.x), 0), r.screen.width); + int y1 = fmin(fmax(floor(rect.y), 0), r.screen.height); + + int x2 = fmin(fmax(floor(rect.x + rect.w), 0), r.screen.width); + int y2 = fmin(fmax(floor(rect.y + rect.h), 0), r.screen.height); + + for (int dx = x1; dx < x2; dx++) { + for (int dy = y1; dy < y2; dy++) { + ty_img_set_pixel( + r.screen, + (struct ty_vec2i){dx, dy}, + color + ); + } + } +} + +void ty_draw_line( + struct ty_vec2i start, + struct ty_vec2i end, + struct ty_color color +) { + int dx = end.x - start.x; + int dy = end.y - start.y; + + if (abs(dx) > abs(dy)) { + if (start.x > end.x) { + int tmp = start.x; + start.x = end.x; + end.x = tmp; + + tmp = start.y; + start.y = end.y; + end.y = tmp; + } + + for (int x = start.x; x < end.x; x++) { + int y = start.y + dy * (x - start.x) / dx; + + ty_img_set_pixel( + r.screen, + (struct ty_vec2i){x, y}, + color + ); + } + } else { + if (start.y > end.y) { + int tmp = start.x; + start.x = end.x; + end.x = tmp; + + tmp = start.y; + start.y = end.y; + end.y = tmp; + } + + for (int y = start.y; y < end.y; y++) { + int x = start.x + dx * (y - start.y) / dy; + + ty_img_set_pixel( + r.screen, + ty_vec2i(x, y), + color + ); + } + } +} + +void ty_draw_end(void) +{ + renderer_init_check(); +} diff --git a/teensy/renderer.h b/teensy/renderer.h new file mode 100644 index 0000000..70eac72 --- /dev/null +++ b/teensy/renderer.h @@ -0,0 +1,10 @@ +#ifndef RENDERER_H_ +#define RENDERER_H_ + +#include "common.h" +#include "teensy.h" + +void ty_init_renderer(void); +void ty_deinit_renderer(void); + +#endif // RENDERER_H_ diff --git a/teensy/teensy.h b/teensy/teensy.h new file mode 100644 index 0000000..c8b8e9d --- /dev/null +++ b/teensy/teensy.h @@ -0,0 +1,108 @@ +#ifndef TEENSY_H_ +#define TEENSY_H_ + +#include "common.h" +#include "context.h" + +#define ty_vec2(x, y) ((struct ty_vec2){x, y}) +#define ty_vec2i(x, y) ((struct ty_vec2i){x, y}) +#define ty_color(R, G, B) ((struct ty_color){R, G, B}) + +#define TY_COLOR_BLACK ty_color(0, 0, 0 ) +#define TY_COLOR_RED ty_color(255, 0, 0 ) +#define TY_COLOR_GREEN ty_color(0, 255, 0 ) +#define TY_COLOR_YELLOW ty_color(255, 255, 0 ) +#define TY_COLOR_BLUE ty_color(0, 0, 255) +#define TY_COLOR_MAGENTA ty_color(255, 0, 255) +#define TY_COLOR_CYAN ty_color(0, 255, 255) +#define TY_COLOR_WHITE ty_color(255, 255, 255) + +struct ty_creation_hints { + int win_width; + int win_height; + const char *win_title; + int ticrate; +}; + +struct ty_ctx { + struct ty_creation_hints creation_hints; + double ticrate; + double prev_tic_ts; +}; + +struct ty_color { + uint8_t r; + uint8_t g; + uint8_t b; +}; + +struct ty_vec2 { + float x; + float y; +}; + +struct ty_vec2i { + int x; + int y; +}; + +struct ty_rect { + float x; + float y; + float w; + float h; +}; + +struct ty_recti { + int x; + int y; + int w; + int h; +}; + +struct ty_image { + struct ty_color *data; + int width; + int height; +}; + +struct ty_renderer { + struct ty_image screen; +}; + +void ty_init(struct ty_creation_hints creation_hints); +void ty_deinit(void); + +// Whether or not the main loop should continue executing. +bool ty_is_game_running(void); +// Gets the real time that the game has been running. +double ty_get_time(void); +// Handle events, tick game logic, etc. Returns the amount of times you should +// tic. +int ty_tick(void); + +struct ty_image ty_create_image(int w, int h, const struct ty_color* data); +void ty_free_image(struct ty_image img); +struct ty_color ty_img_get_pixel(struct ty_image img, struct ty_vec2i pos); +void ty_img_set_pixel( + struct ty_image img, + struct ty_vec2i pos, + struct ty_color color +); + +void ty_draw_clear(struct ty_color col); +void ty_draw_image(struct ty_image img, struct ty_vec2i pos); +void ty_draw_image_ex( + struct ty_image img, + struct ty_recti src, + struct ty_recti dst +); +void ty_draw_rect(struct ty_recti rect, struct ty_color color); +void ty_draw_line( + struct ty_vec2i start, + struct ty_vec2i end, + struct ty_color color +); +void ty_draw_end(void); + +#endif // TEENSY_H_ |
