add the text primitive, finally
This commit is contained in:
		| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -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); | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								data/fonts/kenney-pixel.ttf
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								data/fonts/kenney-pixel.ttf
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -1,6 +1,10 @@ | ||||
| #ifndef CONFIG_H | ||||
| #define CONFIG_H | ||||
|  | ||||
|  | ||||
| #include <glad/glad.h> | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * 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 */ | ||||
|  | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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 */ | ||||
|   | ||||
| @@ -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); | ||||
|  | ||||
|   | ||||
							
								
								
									
										248
									
								
								townengine/rendering/text.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										248
									
								
								townengine/rendering/text.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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 <glad/glad.h> | ||||
| #include <stb_truetype.h> | ||||
|  | ||||
|  | ||||
| #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 | ||||
| @@ -1,5 +0,0 @@ | ||||
| #include "text.h" | ||||
|  | ||||
| #include "SDL.h" | ||||
| #include "SDL_ttf.h" | ||||
|  | ||||
| @@ -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 | ||||
| @@ -5,12 +5,12 @@ | ||||
| #include <physfsrwops.h> | ||||
| #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 <stb_ds.h> | ||||
| #define STB_RECT_PACK_IMPLEMENTATION | ||||
| #define STBRP_ASSERT SDL_assert | ||||
| #include <stb_rect_pack.h> | ||||
| #define STB_TRUETYPE_IMPLEMENTATION | ||||
| #include <stb_truetype.h> | ||||
|  | ||||
| #include <stdlib.h> | ||||
| #include <stdarg.h> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user