add the text primitive, finally

This commit is contained in:
wanp 2024-08-22 23:41:52 -03:00
parent 0f03c18806
commit 9892bf71dc
13 changed files with 329 additions and 34 deletions

View File

@ -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

View File

@ -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

Binary file not shown.

View File

@ -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 */

View File

@ -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;

View File

@ -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);

View File

@ -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(&current->circle);
break;
case PRIMITIVE_2D_TEXT:
render_text(&current->text);
break;
}
}
}

View File

@ -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 */

View File

@ -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
View 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

View File

@ -1,5 +0,0 @@
#include "text.h"
#include "SDL.h"
#include "SDL_ttf.h"

View File

@ -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

View File

@ -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>