From 9892bf71dc50ee1e2c0f05228214b8663910a544 Mon Sep 17 00:00:00 2001 From: wanp Date: Thu, 22 Aug 2024 23:41:52 -0300 Subject: [PATCH] add the text primitive, finally --- CMakeLists.txt | 1 - apps/testgame/scenes/title.c | 28 ++++ data/fonts/kenney-pixel.ttf | Bin 0 -> 28108 bytes townengine/config.h | 8 + townengine/context.h | 2 + townengine/main.c | 10 ++ townengine/rendering.c | 12 ++ townengine/rendering.h | 5 + townengine/rendering/internal_api.h | 14 ++ townengine/rendering/text.h | 248 ++++++++++++++++++++++++++++ townengine/text.c | 5 - townengine/text.h | 26 --- townengine/util.c | 4 +- 13 files changed, 329 insertions(+), 34 deletions(-) create mode 100755 data/fonts/kenney-pixel.ttf create mode 100644 townengine/rendering/text.h delete mode 100644 townengine/text.c delete mode 100644 townengine/text.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e19bba..e140346 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,7 +57,6 @@ set(TOWNENGINE_SOURCE_FILES townengine/util.c townengine/util.h townengine/rendering.c townengine/rendering.h townengine/input/input.c townengine/input.h - townengine/text.c townengine/text.h townengine/camera.c townengine/camera.h townengine/textures/textures.c diff --git a/apps/testgame/scenes/title.c b/apps/testgame/scenes/title.c index e532dc4..02b8fc6 100644 --- a/apps/testgame/scenes/title.c +++ b/apps/testgame/scenes/title.c @@ -17,6 +17,34 @@ static void title_tick(struct state *state) { m_sprite("/assets/title.png", ((t_frect) { (RENDER_BASE_WIDTH / 2) - (320 / 2), 64, 320, 128 })); + + + /* draw the tick count as an example of dynamic text */ + size_t text_str_len = snprintf(NULL, 0, "%ld", state->ctx->tick_count) + 1; + char *text_str = cmalloc(text_str_len); + snprintf(text_str, text_str_len, "%ld", state->ctx->tick_count); + + const char *font = "fonts/kenney-pixel.ttf"; + int text_h = 32; + int text_w = get_text_width(text_str, text_h, font); + + push_rectangle( + (t_frect) { + .x = 0, + .y = 0, + .w = (float)text_w, + .h = (float)text_h, + }, + (t_color) { 0, 0, 0, 255 } + ); + push_text( + text_str, + (t_fvec2){ 0, 0 }, + text_h, + (t_color) { 255, 255, 255, 255 }, + font + ); + free(text_str); } diff --git a/data/fonts/kenney-pixel.ttf b/data/fonts/kenney-pixel.ttf new file mode 100755 index 0000000000000000000000000000000000000000..0020733ddf10eb208e658a6f8b3a1ca65c8943ea GIT binary patch literal 28108 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 E5B0kam;e9( literal 0 HcmV?d00001 diff --git a/townengine/config.h b/townengine/config.h index 11d647f..273b8ba 100644 --- a/townengine/config.h +++ b/townengine/config.h @@ -1,6 +1,10 @@ #ifndef CONFIG_H #define CONFIG_H + +#include + + /* * this file is for configuration values which are to be set at * compile time. generally speaking, it's for things that would be unwise to @@ -28,6 +32,10 @@ #define AUDIO_FREQUENCY 48000 #define AUDIO_N_CHANNELS 2 +#define TEXT_FONT_TEXTURE_SIZE 1024 +#define TEXT_FONT_OVERSAMPLING 4 +#define TEXT_FONT_FILTERING GL_LINEAR + /* 1024 * 1024 */ /* #define UMKA_STACK_SIZE 1048576 */ diff --git a/townengine/context.h b/townengine/context.h index 3b078dd..f393c0f 100644 --- a/townengine/context.h +++ b/townengine/context.h @@ -2,6 +2,7 @@ #define CONTEXT_H +#include "rendering/internal_api.h" #include "textures/internal_api.h" #include "input.h" @@ -21,6 +22,7 @@ typedef struct context { struct primitive_2d *render_queue_2d; struct mesh_batch_item *uncolored_mesh_batches; + struct text_cache text_cache; struct audio_channel_item *audio_channels; SDL_AudioDeviceID audio_device; diff --git a/townengine/main.c b/townengine/main.c index a2d9840..48e6fcb 100644 --- a/townengine/main.c +++ b/townengine/main.c @@ -356,12 +356,14 @@ static bool initialize(void) { /* rendering */ /* these are dynamic arrays and will be allocated lazily by stb_ds */ ctx.render_queue_2d = NULL; + ctx.uncolored_mesh_batches = NULL; textures_cache_init(&ctx.texture_cache, ctx.window); if (TTF_Init() < 0) { CRY_SDL("SDL_ttf initialization failed."); goto fail; } + text_cache_init(&ctx.text_cache); /* input */ input_state_init(&ctx.input); @@ -391,7 +393,15 @@ static void clean_up(void) { input_state_deinit(&ctx.input); + /* if you're gonna remove this, it's also being done in rendering.c */ + for (size_t i = 0; i < arrlenu(ctx.render_queue_2d); ++i) { + if (ctx.render_queue_2d[i].type == PRIMITIVE_2D_TEXT) { + free(ctx.render_queue_2d[i].text.text); + } + } arrfree(ctx.render_queue_2d); + + text_cache_deinit(&ctx.text_cache); textures_cache_deinit(&ctx.texture_cache); SDL_DestroyMutex(game_object_mutex); diff --git a/townengine/rendering.c b/townengine/rendering.c index 6b2c969..f163c36 100644 --- a/townengine/rendering.c +++ b/townengine/rendering.c @@ -2,6 +2,7 @@ #include "rendering/sprites.h" #include "rendering/triangles.h" #include "rendering/circles.h" +#include "rendering/text.h" #include "textures/internal_api.h" #include "townengine/context.h" @@ -19,6 +20,14 @@ static t_matrix4 camera_look_at_matrix; void render_queue_clear(void) { + /* this doesn't even _deserve_ a TODO */ + /* if you're gonna remove it, this is also being done in main.c */ + for (size_t i = 0; i < arrlenu(ctx.render_queue_2d); ++i) { + if (ctx.render_queue_2d[i].type == PRIMITIVE_2D_TEXT) { + free(ctx.render_queue_2d[i].text.text); + } + } + /* since i don't intend to free the queues, */ /* it's faster and simpler to just "start over" */ /* and start overwriting the existing data */ @@ -103,6 +112,9 @@ static void render_2d(void) { case PRIMITIVE_2D_CIRCLE: render_circle(¤t->circle); break; + case PRIMITIVE_2D_TEXT: + render_text(¤t->text); + break; } } } diff --git a/townengine/rendering.h b/townengine/rendering.h index 2aa527e..1efeb18 100644 --- a/townengine/rendering.h +++ b/townengine/rendering.h @@ -34,6 +34,11 @@ void push_rectangle(t_frect rect, t_color color); /* pushes a filled circle onto the circle render queue */ void push_circle(t_fvec2 position, float radius, t_color color); +void text_cache_init(struct text_cache *cache); +void text_cache_deinit(struct text_cache *cache); +void push_text(char *string, t_fvec2 position, int height_px, t_color color, const char *font_path); +int get_text_width(char *string, int height_px, const char *font_path); + /* pushes a textured 3d triangle onto the render queue */ /* vertices are in absolute coordinates, relative to world origin */ /* texture coordinates are in pixels */ diff --git a/townengine/rendering/internal_api.h b/townengine/rendering/internal_api.h index 4049e52..f3afd13 100644 --- a/townengine/rendering/internal_api.h +++ b/townengine/rendering/internal_api.h @@ -34,10 +34,19 @@ struct circle_primitive { t_fvec2 position; }; +struct text_primitive { + t_color color; + t_fvec2 position; + char *text; + const char *font; + int height_px; +}; + enum primitive_2d_type { PRIMITIVE_2D_SPRITE, PRIMITIVE_2D_RECT, PRIMITIVE_2D_CIRCLE, + PRIMITIVE_2D_TEXT, }; struct primitive_2d { @@ -47,6 +56,7 @@ struct primitive_2d { struct sprite_primitive sprite; struct rect_primitive rect; struct circle_primitive circle; + struct text_primitive text; }; }; @@ -85,6 +95,10 @@ struct mesh_batch_item { struct mesh_batch value; }; +struct text_cache { + struct font_data **data; +}; + /* renders the background, then the primitives in all render queues */ void render(void); diff --git a/townengine/rendering/text.h b/townengine/rendering/text.h new file mode 100644 index 0000000..2f6d962 --- /dev/null +++ b/townengine/rendering/text.h @@ -0,0 +1,248 @@ +/* a rendering.c mixin */ +#ifndef TEXT_H +#define TEXT_H + + +#include "../util.h" +#include "townengine/config.h" +#include "townengine/context.h" + +#include +#include + + +#define ASCII_START 32 +#define ASCII_END 128 +#define NUM_DISPLAY_ASCII ((ASCII_END - ASCII_START) + 1) + + +struct font_data { + stbtt_packedchar char_data[NUM_DISPLAY_ASCII]; + stbtt_fontinfo info; + + const char *file_path; + unsigned char *file_bytes; + size_t file_bytes_len; + + GLuint texture; + + int height_px; + float scale_factor; + int ascent; + int descent; + int line_gap; +}; + + +static struct font_data *text_load_font_data(const char *path, int height_px) { + struct font_data *font_data = ccalloc(1, sizeof *font_data); + font_data->file_path = path; + font_data->height_px = height_px; + + unsigned char* bitmap = ccalloc(TEXT_FONT_TEXTURE_SIZE * TEXT_FONT_TEXTURE_SIZE, 1); + + { + unsigned char *buf = NULL; + int64_t buf_len = file_to_bytes(path, &buf); + stbtt_InitFont(&font_data->info, buf, stbtt_GetFontOffsetForIndex(buf, 0)); + + /* might as well get these now, for later */ + font_data->file_bytes = buf; + font_data->file_bytes_len = buf_len; + font_data->scale_factor = stbtt_ScaleForPixelHeight(&font_data->info, (float)height_px); + stbtt_GetFontVMetrics( + &font_data->info, + &font_data->ascent, + &font_data->descent, + &font_data->line_gap + ); + font_data->ascent = (int)((float)font_data->ascent * font_data->scale_factor); + font_data->descent = (int)((float)font_data->descent * font_data->scale_factor); + font_data->line_gap = (int)((float)font_data->line_gap * font_data->scale_factor); + + stbtt_pack_context pctx; + stbtt_PackBegin(&pctx, bitmap, TEXT_FONT_TEXTURE_SIZE, TEXT_FONT_TEXTURE_SIZE, 0, 1, NULL); + stbtt_PackSetOversampling(&pctx, TEXT_FONT_OVERSAMPLING, TEXT_FONT_OVERSAMPLING); + stbtt_PackFontRange(&pctx, buf, 0, (float)height_px, ASCII_START, NUM_DISPLAY_ASCII, font_data->char_data); + stbtt_PackEnd(&pctx); + } + + glGenTextures(1, &font_data->texture); + glBindTexture(GL_TEXTURE_2D, font_data->texture); + glTexImage2D( + GL_TEXTURE_2D, + 0, + GL_ALPHA, + TEXT_FONT_TEXTURE_SIZE, + TEXT_FONT_TEXTURE_SIZE, + 0, + GL_ALPHA, + GL_UNSIGNED_BYTE, + bitmap + ); + free(bitmap); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, TEXT_FONT_FILTERING); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, TEXT_FONT_FILTERING); + glBindTexture(GL_TEXTURE_2D, 0); + + + return font_data; +} + + +static void text_destroy_font_data(struct font_data *font_data) { + free(font_data->file_bytes); + glDeleteTextures(1, &font_data->texture); + free(font_data); +} + + +static void text_draw_with(struct font_data* font_data, char* text, t_fvec2 position, t_color color) { + glBindTexture(GL_TEXTURE_2D, font_data->texture); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDepthFunc(GL_ALWAYS); + glDepthMask(GL_FALSE); + glDisable(GL_ALPHA_TEST); + + glColor4ub(color.r, color.g, color.b, color.a); + + glBegin(GL_QUADS); + for (const char *p = text; *p != '\0'; ++p) { + const char c = *p; + + /* outside the range of what we want to display */ + //if (c < ASCII_START || c > ASCII_END) + if (c < ASCII_START) + continue; + + /* stb_truetype.h conveniently provides everything we need to draw here! */ + stbtt_aligned_quad quad; + stbtt_GetPackedQuad( + font_data->char_data, + TEXT_FONT_TEXTURE_SIZE, + TEXT_FONT_TEXTURE_SIZE, + c - ASCII_START, + &position.x, + &position.y, + &quad, + true + ); + + /* have to do this so the "origin" is at the top left */ + /* maybe there's a better way, or maybe this isn't a good idea... */ + /* who knows */ + quad.y0 += (float)font_data->ascent; + quad.y1 += (float)font_data->ascent; + + /* TODO: you know... */ + glTexCoord2f(quad.s0, quad.t0); + glVertex2f(quad.x0, quad.y0); + glTexCoord2f(quad.s1, quad.t0); + glVertex2f(quad.x1, quad.y0); + glTexCoord2f(quad.s1, quad.t1); + glVertex2f(quad.x1, quad.y1); + glTexCoord2f(quad.s0, quad.t1); + glVertex2f(quad.x0, quad.y1); + } + glEnd(); + + glColor4ub(255, 255, 255, 255); + glBindTexture(GL_TEXTURE_2D, 0); +} + + +static void ensure_font_cache(const char *font_path, int height_px) { + /* HACK: stupid, bad, don't do this */ + bool is_cached = false; + for (size_t i = 0; i < arrlenu(ctx.text_cache.data); ++i) { + struct font_data *font_data = ctx.text_cache.data[i]; + if ((strcmp(font_path, font_data->file_path) == 0) && height_px == font_data->height_px) { + is_cached = true; + break; + } + } + if (!is_cached) { + struct font_data *new_font_data = text_load_font_data(font_path, height_px); + arrput(ctx.text_cache.data, new_font_data); + } +} + + +static struct font_data *get_font_data(const char *font_path, int height_px) { + struct font_data *font_data = NULL; + for (size_t i = 0; i < arrlenu(ctx.text_cache.data); ++i) { + font_data = ctx.text_cache.data[i]; + if ((strcmp(font_path, font_data->file_path) == 0) && height_px == font_data->height_px) { + break; + } + } + return font_data; +} + + +static void render_text(const struct text_primitive *text) { + struct font_data *font_data = get_font_data(text->font, text->height_px); + text_draw_with(font_data, text->text, text->position, text->color); +} + + +void text_cache_init(struct text_cache *cache) { + arrsetlen(cache->data, 0); +} + + +void text_cache_deinit(struct text_cache *cache) { + for (size_t i = 0; i < arrlenu(ctx.text_cache.data); ++i) { + text_destroy_font_data(ctx.text_cache.data[i]); + } + + arrfree(cache->data); +} + + +void push_text(char *string, t_fvec2 position, int height_px, t_color color, const char *font_path) { + ensure_font_cache(font_path, height_px); + + /* the string might not be around by the time it's used, so copy it */ + /* TODO: arena */ + /* NOTE: can we trust strlen? */ + char *dup_string = cmalloc(strlen(string) + 1); + strcpy(dup_string, string); + + struct text_primitive text = { + .color = color, + .position = position, + .text = dup_string, + .font = font_path, + .height_px = height_px, + }; + + struct primitive_2d primitive = { + .type = PRIMITIVE_2D_TEXT, + .text = text, + }; + + arrput(ctx.render_queue_2d, primitive); +} + + +int get_text_width(char *string, int height_px, const char *font_path) { + ensure_font_cache(font_path, height_px); + struct font_data *font_data = get_font_data(font_path, height_px); + + int length = 0; + for (const char *p = string; *p != '\0'; ++p) { + const char c = *p; + int advance_width = 0; + int left_side_bearing = 0; + stbtt_GetCodepointHMetrics(&font_data->info, (int)c, &advance_width, &left_side_bearing); + length += advance_width; + } + + return (int)((float)length * font_data->scale_factor); +} + + +#endif diff --git a/townengine/text.c b/townengine/text.c deleted file mode 100644 index 6c36ea0..0000000 --- a/townengine/text.c +++ /dev/null @@ -1,5 +0,0 @@ -#include "text.h" - -#include "SDL.h" -#include "SDL_ttf.h" - diff --git a/townengine/text.h b/townengine/text.h deleted file mode 100644 index 9b7467c..0000000 --- a/townengine/text.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef TEXT_H -#define TEXT_H - - -#include "util.h" - - -struct text { - char *text; - t_color color; - int ptsize; -}; - - -struct text_cache_item { - char *key; - struct text *value; -}; - - -struct text_cache { - struct text_cache_item *hash; -}; - - -#endif diff --git a/townengine/util.c b/townengine/util.c index a598679..56cce38 100644 --- a/townengine/util.c +++ b/townengine/util.c @@ -5,12 +5,12 @@ #include #define STB_DS_IMPLEMENTATION #define STBDS_ASSERT SDL_assert -#define STBDS_REALLOC(c,p,s) crealloc(p, s) -#define STBDS_FREE(c,p) free(p) #include #define STB_RECT_PACK_IMPLEMENTATION #define STBRP_ASSERT SDL_assert #include +#define STB_TRUETYPE_IMPLEMENTATION +#include #include #include