diff --git a/README.rst b/README.rst index dc8ba9ab..eb11f59a 100644 --- a/README.rst +++ b/README.rst @@ -16,11 +16,12 @@ Installation Dependencies ^^^^^^^^^^^^ -- SDL2 >= 2.0.5, SDL2_ttf, SDL2_mixer, SDL2_image +- SDL2 >= 2.0.5, SDL2_mixer, SDL2_image - zlib - libzip >= 1.0 - libpng >= 1.5.0 - libjpeg +- freetype2 - OpenGL >= 3.3 Build-only dependencies diff --git a/meson.build b/meson.build index 7a019370..3f42a533 100644 --- a/meson.build +++ b/meson.build @@ -94,6 +94,7 @@ foreach flag : [ '-Wunreachable-code-loop-increment', '-Wgnu', '-Wcast-align', + '-Wcast-align=strict', ] if cc.has_argument(flag) taisei_c_warnargs += flag @@ -105,21 +106,21 @@ taisei_c_args += taisei_c_warnargs static = get_option('static') dep_sdl2 = dependency('sdl2', version : '>=2.0.5', required : true, static : static) -dep_sdl2_ttf = dependency('SDL2_ttf', required : true, static : static) dep_sdl2_mixer = dependency('SDL2_mixer', required : false, static : static) dep_sdl2_image = dependency('SDL2_image', required : true, static : static) dep_zlib = dependency('zlib', required : true, static : static) dep_png = dependency('libpng', version : '>=1.5', required : true, static : static) dep_zip = dependency('libzip', version : '>=1.0', required : false, static : static) +dep_freetype = dependency('freetype2', required : true, static : static) dep_cglm = subproject('cglm').get_variable('cglm_dep') dep_m = cc.find_library('m', required : false) taisei_deps = [ dep_sdl2, - dep_sdl2_ttf, dep_sdl2_image, dep_zlib, dep_png, + dep_freetype, dep_m, dep_cglm, ] diff --git a/resources/shader/hud_text.frag.glsl b/resources/shader/hud_text.frag.glsl deleted file mode 100644 index 3b3fb5ca..00000000 --- a/resources/shader/hud_text.frag.glsl +++ /dev/null @@ -1,41 +0,0 @@ -#version 330 core - -#include "interface/standard.glslh" - -UNIFORM(1) vec4 colorAtop; -UNIFORM(2) vec4 colorAbot; -UNIFORM(3) vec4 colorBtop; -UNIFORM(4) vec4 colorBbot; -UNIFORM(5) vec4 colortint; -UNIFORM(6) float split; - -void main(void) { - vec4 texel = texture(tex, texCoord); - - float vsplit; - vec4 cAt; - vec4 cAb; - vec4 cBt; - vec4 cBb; - - // branching on a uniform is fine - if(split < 0.0) { - vsplit = -split; - cAt = colorBtop; - cAb = colorBbot; - cBt = colorAtop; - cBb = colorAbot; - } else { - vsplit = split; - cAt = colorAtop; - cAb = colorAbot; - cBt = colorBtop; - cBb = colorBbot; - } - - fragColor = texel * colortint * ( - texCoord.x >= vsplit ? - mix(cBt, cBb, texCoord.y) : - mix(cAt, cAb, texCoord.y) - ); -} diff --git a/resources/shader/hud_text.prog b/resources/shader/hud_text.prog deleted file mode 100644 index 14e3b8c9..00000000 --- a/resources/shader/hud_text.prog +++ /dev/null @@ -1,2 +0,0 @@ - -glsl_objects = standard.vert hud_text.frag diff --git a/resources/shader/interface/sprite.glslh b/resources/shader/interface/sprite.glslh index 3b030795..d9602491 100644 --- a/resources/shader/interface/sprite.glslh +++ b/resources/shader/interface/sprite.glslh @@ -15,13 +15,18 @@ ATTRIBUTE(2) vec2 vertTexCoord; /* * Per-instance attributes */ -ATTRIBUTE(3) mat4 spriteVMTransform; +ATTRIBUTE(3) mat4 spriteVMTransform; // 4 // 5 // 6 -ATTRIBUTE(7) vec4 spriteRGBA; -ATTRIBUTE(8) vec4 spriteTexRegion; -ATTRIBUTE(9) float spriteCustomParam; +ATTRIBUTE(7) mat4 spriteTexTransform; +// 8 +// 9 +// 10 +ATTRIBUTE(11) vec4 spriteRGBA; +ATTRIBUTE(12) vec4 spriteTexRegion; +ATTRIBUTE(13) vec2 spriteDimensions; +ATTRIBUTE(14) float spriteCustomParam; #endif #ifdef FRAG_STAGE @@ -30,10 +35,12 @@ OUT(0) vec4 fragColor; UNIFORM(0) sampler2D tex; -VARYING(0) vec2 texCoordRaw; -VARYING(1) vec2 texCoord; -VARYING(2) vec4 texRegion; -VARYING(3) vec4 color; -VARYING(4) float customParam; +VARYING(0) vec2 texCoordRaw; +VARYING(1) vec2 texCoord; +VARYING(2) vec2 texCoordOverlay; +VARYING(3) vec4 texRegion; +VARYING(4) vec4 color; +VARYING(5) vec2 dimensions; +VARYING(6) float customParam; #endif diff --git a/resources/shader/lib/sprite_default.vert.glslh b/resources/shader/lib/sprite_default.vert.glslh index 6c3663a6..083f57cf 100644 --- a/resources/shader/lib/sprite_default.vert.glslh +++ b/resources/shader/lib/sprite_default.vert.glslh @@ -19,10 +19,18 @@ void main(void) { texCoord = uv_to_region(spriteTexRegion, vertTexCoord); #endif + #ifdef SPRITE_OUT_TEXCOORD_OVERLAY + texCoordOverlay = (spriteTexTransform * vec4(vertTexCoord, 0.0, 1.0)).xy; + #endif + #ifdef SPRITE_OUT_TEXREGION texRegion = spriteTexRegion; #endif + #ifdef SPRITE_OUT_DIMENSIONS + dimensions = spriteDimensions; + #endif + #ifdef SPRITE_OUT_CUSTOM customParam = spriteCustomParam; #endif diff --git a/resources/shader/meson.build b/resources/shader/meson.build index 6ceca43f..38ff8255 100644 --- a/resources/shader/meson.build +++ b/resources/shader/meson.build @@ -13,7 +13,6 @@ glsl_files = files( 'boss_zoom.frag.glsl', 'glitch.frag.glsl', 'graph.frag.glsl', - 'hud_text.frag.glsl', 'ingame_menu.frag.glsl', 'laser_generic.vert.glsl', 'marisa_laser.frag.glsl', @@ -39,11 +38,16 @@ glsl_files = files( 'sprite_youmu_myon_shot.frag.glsl', 'stage6_sky.frag.glsl', 'stage6_sky.vert.glsl', - 'stagetext.frag.glsl', 'standard.frag.glsl', 'standard.vert.glsl', 'standardnotex.frag.glsl', 'standardnotex.vert.glsl', + 'text_default.frag.glsl', + 'text_default.vert.glsl', + 'text_example.frag.glsl', + 'text_example.vert.glsl', + 'text_hud.frag.glsl', + 'text_stagetext.frag.glsl', 'texture_post_load.frag.glsl', 'tower_light.frag.glsl', 'tower_light.vert.glsl', diff --git a/resources/shader/spellcard_walloftext.frag.glsl b/resources/shader/spellcard_walloftext.frag.glsl index 5e76d294..efa359c3 100644 --- a/resources/shader/spellcard_walloftext.frag.glsl +++ b/resources/shader/spellcard_walloftext.frag.glsl @@ -13,6 +13,7 @@ void main(void) { pos = mod(pos,vec2(1.0+(0.01/w),1.0)); pos *= vec2(w,h); vec4 clr = texture(tex, pos); + clr.gb = clr.rr; clr.a *= float(pos.x < w && pos.y < h)*(2.*t-t*t); fragColor = clr; diff --git a/resources/shader/stagetext.frag.glsl b/resources/shader/stagetext.frag.glsl deleted file mode 100644 index 3d3c632f..00000000 --- a/resources/shader/stagetext.frag.glsl +++ /dev/null @@ -1,21 +0,0 @@ -#version 330 core - -#include "lib/render_context.glslh" -#include "interface/standard.glslh" - -UNIFORM(1) sampler2D trans; -UNIFORM(2) vec3 color; -UNIFORM(3) float t; - -void main(void) { - vec2 pos = texCoordRaw; - vec2 f = pos-vec2(0.5,0.5); - pos += (f*0.05*sin(10.0*(length(f)+t)))*(1.0-t); - - pos = clamp(pos,0.0,1.0); - vec4 texel = texture(tex, (r_textureMatrix*vec4(pos,0.0,1.0)).xy); - - texel.a *= clamp((texture(trans, pos).r+0.5)*2.5*t-0.5, 0.0, 1.0); - fragColor = vec4(color,1.0)*texel; -} - diff --git a/resources/shader/stagetext.prog b/resources/shader/stagetext.prog deleted file mode 100644 index 8425bc28..00000000 --- a/resources/shader/stagetext.prog +++ /dev/null @@ -1,2 +0,0 @@ - -glsl_objects = standard.vert stagetext.frag diff --git a/resources/shader/text_default.frag.glsl b/resources/shader/text_default.frag.glsl new file mode 100644 index 00000000..2127cbaa --- /dev/null +++ b/resources/shader/text_default.frag.glsl @@ -0,0 +1,7 @@ +#version 330 core + +#include "interface/sprite.glslh" + +void main(void) { + fragColor = color * vec4(1, 1, 1, texture(tex, texCoord).r); +} diff --git a/resources/shader/text_default.prog b/resources/shader/text_default.prog new file mode 100644 index 00000000..361bff37 --- /dev/null +++ b/resources/shader/text_default.prog @@ -0,0 +1 @@ +glsl_objects = text_default.vert text_default.frag diff --git a/resources/shader/text_default.vert.glsl b/resources/shader/text_default.vert.glsl new file mode 100644 index 00000000..c2aa4a19 --- /dev/null +++ b/resources/shader/text_default.vert.glsl @@ -0,0 +1,7 @@ +#version 330 core + +#define SPRITE_OUT_COLOR +#define SPRITE_OUT_TEXCOORD +#define SPRITE_OUT_TEXCOORD_OVERLAY + +#include "lib/sprite_default.vert.glslh" diff --git a/resources/shader/text_example.frag.glsl b/resources/shader/text_example.frag.glsl new file mode 100644 index 00000000..3944e3fd --- /dev/null +++ b/resources/shader/text_example.frag.glsl @@ -0,0 +1,30 @@ +#version 330 core + +#include "lib/render_context.glslh" +#include "interface/sprite.glslh" +#include "lib/util.glslh" + +void main(void) { + vec2 tc = texCoord; + vec2 tc_overlay = texCoordOverlay; + + // Transform local glyph texture coordinates. + tc *= dimensions; + tc.x += 5 * sin(3 * tc_overlay.y + customParam); + tc /= dimensions; + + // Compute alpha multiplier used to chop off unwanted texels from the atlas. + float a = float(tc.x >= 0 && tc.x <= 1 && tc.y >= 0 && tc.y <= 1); + + // Map local coordinates to the region of the atlas texture that contains our glyph. + vec2 tc_atlas = uv_to_region(texRegion, tc); + + // Display the glyph. + fragColor = color * vec4(1, 1, 1, texture(tex, tc_atlas).r * a); + + // Visualize global overlay coordinates. You could use them to span a texture across all glyphs. + fragColor *= vec4(tc_overlay.x, tc_overlay.y, 0, 1); + + // Visualize the local coordinates. + // fragColor = vec4(mod(tc.x, 1), float(length(tc - vec2(0.5, 0.5)) < 0.5), mod(tc.y, 1), 1.0); +} diff --git a/resources/shader/text_example.prog b/resources/shader/text_example.prog new file mode 100644 index 00000000..aeff717c --- /dev/null +++ b/resources/shader/text_example.prog @@ -0,0 +1 @@ +glsl_objects = text_example.vert text_example.frag diff --git a/resources/shader/text_example.vert.glsl b/resources/shader/text_example.vert.glsl new file mode 100644 index 00000000..fadc81cc --- /dev/null +++ b/resources/shader/text_example.vert.glsl @@ -0,0 +1,35 @@ +#version 330 core + +#include "lib/defs.glslh" +#include "lib/render_context.glslh" +#include "lib/util.glslh" +#include "interface/sprite.glslh" + +void main(void) { + // Enlarge the quad to make some room for effects. + float scale = 2; + vec2 pos = vertPos * scale; + gl_Position = r_projectionMatrix * spriteVMTransform * vec4(pos, 0.0, 1.0); + + // Adjust texture coordinates so that the glyph remains in the center, unaffected by the scaling factor. + // Some extra code is required in the fragment shader to chop off the unwanted bits of the texture. + vec2 tc = vertTexCoord * scale - vec2(0.5 * (scale - 1)); + + // Pass tc not mapped to the texture region, because we want to do per-fragment transformations on it. + texCoord = tc; + + // Pass the normalized texture region, so that we can map texCoord to it later in the fragment shader. + texRegion = spriteTexRegion; + + // Global overlay coordinates for this primitive. + texCoordOverlay = (spriteTexTransform * vec4(tc, 0.0, 1.0)).xy; + + // Fragment shader needs to know the sprite dimensions so that it can denormalize texCoord for processing. + dimensions = spriteDimensions; + + // Arbitrary custom parameter provided by the application. You can use it to pass e.g. times/frames. + customParam = spriteCustomParam; + + // Should be obvious. + color = spriteRGBA; +} diff --git a/resources/shader/text_hud.frag.glsl b/resources/shader/text_hud.frag.glsl new file mode 100644 index 00000000..3ec305ed --- /dev/null +++ b/resources/shader/text_hud.frag.glsl @@ -0,0 +1,9 @@ +#version 330 core + +#include "lib/render_context.glslh" +#include "interface/sprite.glslh" + +void main(void) { + vec4 texel = texture(tex, texCoord); + fragColor = vec4(color.rgb, color.a * texel.r) * mix(1.0, 0.8, texCoordOverlay.y); +} diff --git a/resources/shader/text_hud.prog b/resources/shader/text_hud.prog new file mode 100644 index 00000000..8952308c --- /dev/null +++ b/resources/shader/text_hud.prog @@ -0,0 +1,2 @@ + +glsl_objects = text_default.vert text_hud.frag diff --git a/resources/shader/text_stagetext.frag.glsl b/resources/shader/text_stagetext.frag.glsl new file mode 100644 index 00000000..8d37bbe0 --- /dev/null +++ b/resources/shader/text_stagetext.frag.glsl @@ -0,0 +1,39 @@ +#version 330 core + +#include "lib/render_context.glslh" +#include "interface/sprite.glslh" +#include "lib/util.glslh" + +UNIFORM(1) sampler2D trans; + +float tc_mask(vec2 tc) { + return float(tc.x >= 0 && tc.x <= 1 && tc.y >= 0 && tc.y <= 1); +} + +void main(void) { + float t = customParam; + vec2 tc = texCoord; + vec2 tc_overlay = texCoordOverlay; + vec2 f = tc_overlay - vec2(0.5); + + tc *= dimensions; + tc += 5 * f * sin(10 * (length(f) + t)) * (1 - t); + tc /= dimensions; + + float a = tc_mask(tc); + + vec4 texel = texture(tex, uv_to_region(texRegion, tc)); + vec4 textfrag = vec4(color.rgb, a * color.a * texel.r); + + tc -= vec2(1) / dimensions; + a = tc_mask(tc); + + texel = texture(tex, uv_to_region(texRegion, tc)); + vec4 shadowfrag = vec4(vec3(0), a * color.a * texel.r); + + fragColor = textfrag; + fragColor = mix(shadowfrag, textfrag, sqrt(textfrag.a)); + + tc_overlay = clamp(tc_overlay,0.01,0.99); // The overlay coordinates are outside of [0,1] in the padding region, so we make sure there are no wrap around artifacts when a bit of text is distorted to this region. + fragColor.a *= clamp((texture(trans, tc_overlay).r + 0.5) * 2.5 * t-0.5, 0.0, 1.0); +} diff --git a/resources/shader/text_stagetext.prog b/resources/shader/text_stagetext.prog new file mode 100644 index 00000000..f9e9a463 --- /dev/null +++ b/resources/shader/text_stagetext.prog @@ -0,0 +1,2 @@ + +glsl_objects = text_example.vert text_stagetext.frag diff --git a/src/audio_common.c b/src/audio_common.c index 336bee68..c968bbe0 100644 --- a/src/audio_common.c +++ b/src/audio_common.c @@ -18,7 +18,7 @@ CurrentBGM current_bgm = { .name = NULL }; static char *saved_bgm; -static Hashtable *sfx_volumes; +static ht_str2int_t sfx_volumes; static struct enqueued_sound { LIST_INTERFACE(struct enqueued_sound); @@ -173,7 +173,6 @@ static void bgm_cfg_volume_callback(ConfigIndex idx, ConfigValue v) { } static bool store_sfx_volume(const char *key, const char *val, void *data) { - Hashtable *ht = data; int vol = atoi(val); if(vol < 0 || vol > 128) { @@ -184,14 +183,15 @@ static bool store_sfx_volume(const char *key, const char *val, void *data) { log_debug("Default volume for %s is now %i", key, vol); if(vol != DEFAULT_SFX_VOLUME) { - hashtable_set_string(ht, key, (void*)(intptr_t)vol); + ht_set(&sfx_volumes, key, vol); } + return true; } static void load_config_files(void) { - sfx_volumes = hashtable_new_stringkeys(); - parse_keyvalue_file_cb(SFX_PATH_PREFIX "volumes.conf", store_sfx_volume, sfx_volumes); + ht_create(&sfx_volumes); + parse_keyvalue_file_cb(SFX_PATH_PREFIX "volumes.conf", store_sfx_volume, NULL); } static inline char* get_bgm_desc(char *name) { @@ -201,13 +201,7 @@ static inline char* get_bgm_desc(char *name) { } int get_default_sfx_volume(const char *sfx) { - void *v = hashtable_get_string(sfx_volumes, sfx); - - if(v != NULL) { - return (intptr_t)v; - } - - return DEFAULT_SFX_VOLUME; + return ht_get(&sfx_volumes, sfx, DEFAULT_SFX_VOLUME); } void resume_bgm(void) { @@ -315,9 +309,5 @@ void audio_init(void) { void audio_shutdown(void) { audio_backend_shutdown(); - - if(sfx_volumes) { - hashtable_free(sfx_volumes); - sfx_volumes = NULL; - } + ht_destroy(&sfx_volumes); } diff --git a/src/boss.c b/src/boss.c index fb1955b4..50c35314 100644 --- a/src/boss.c +++ b/src/boss.c @@ -39,24 +39,30 @@ Boss* create_boss(char *name, char *ani, char *dialog, complex pos) { return buf; } -void draw_boss_text(Alignment align, float x, float y, const char *text, Font *fnt, Color clr) { - Color color_saved = r_color_current(); +void draw_boss_text(Alignment align, float x, float y, const char *text, const char *fnt, Color clr) { + ShaderProgram *sh_prev = r_shader_current(); + r_shader("text_default"); + text_draw(text, &(TextParams) { + .pos = { x + 1, y + 1 }, + .color = derive_color(rgb(0, 0, 0), CLRMASK_A, clr), + .font = fnt, + .align = align, + }); - r_shader_standard(); - r_color(derive_color(rgb(0, 0, 0), CLRMASK_A, clr)); - draw_text(align, x+1, y+1, text, fnt); - r_color(clr); - draw_text(align, x, y, text, fnt); - r_shader("sprite_default"); - - r_color(color_saved); + text_draw(text, &(TextParams) { + .pos = { x, y }, + .color = clr, + .font = fnt, + .align = align, + }); + r_shader_ptr(sh_prev); } void spell_opening(Boss *b, int time) { complex x0 = VIEWPORT_W/2+I*VIEWPORT_H/3.5; float f = clamp((time-40.)/60.,0,1); complex x = x0 + (VIEWPORT_W+I*35 - x0) * f*(f+1)*0.5; - int strw = stringwidth(b->current->name,_fonts.standard); + int strw = text_width(get_font("standard"), b->current->name, 0); bool cullcap_saved = r_capability_current(RCAP_CULL_FACE); r_disable(RCAP_CULL_FACE); @@ -66,7 +72,7 @@ void spell_opening(Boss *b, int time) { float scale = f+1.*(1-f)*(1-f)*(1-f); r_mat_scale(scale,scale,1); r_mat_rotate_deg(360*f,1,1,0); - draw_boss_text(AL_Right, strw/2*(1-f), 0, b->current->name, _fonts.standard, rgb(1, 1, 1)); + draw_boss_text(ALIGN_RIGHT, strw/2*(1-f), 0, b->current->name, "standard", rgb(1, 1, 1)); r_mat_pop(); r_capability(RCAP_CULL_FACE, cullcap_saved); @@ -276,7 +282,7 @@ static void ent_draw_boss(EntityInterface *ent) { if(boss->current->type == AT_Move && global.frames - boss->current->starttime > 0 && boss_attack_is_final(boss, boss->current)) return; - draw_boss_text(AL_Left, 10, 20, boss->name, _fonts.standard, rgb(1, 1, 1)); + draw_boss_text(ALIGN_LEFT, 10, 20, boss->name, "standard", rgb(1, 1, 1)); if(!boss->current) return; @@ -298,16 +304,19 @@ static void ent_draw_boss(EntityInterface *ent) { } snprintf(buf, sizeof(buf), "%.2f", remaining); - draw_boss_text(AL_Center, VIEWPORT_W - 24, 10, buf, _fonts.standard, textclr); + draw_boss_text(ALIGN_CENTER, VIEWPORT_W - 24, 10, buf, "standard", textclr); StageProgress *p = get_spellstage_progress(boss->current, NULL, false); if(p) { float a = clamp((global.frames - boss->current->starttime - 60) / 60.0, 0, 1); snprintf(buf, sizeof(buf), "%u / %u", p->num_cleared, p->num_played); - draw_boss_text(AL_Right, - VIEWPORT_W + stringwidth(buf, _fonts.small) * pow(1 - a, 2), - 35 + stringheight(buf, _fonts.small), - buf, _fonts.small, rgba(1, 1, 1, a) + + Font *font = get_font("small"); + + draw_boss_text(ALIGN_RIGHT, + VIEWPORT_W + text_width(font, buf, 0) * pow(1 - a, 2), + 35 + text_height(font, buf, 0), + buf, "small", rgba(1, 1, 1, a) ); } diff --git a/src/credits.c b/src/credits.c index bb148e0b..c9fdd432 100644 --- a/src/credits.c +++ b/src/credits.c @@ -236,11 +236,11 @@ static double entry_height(CreditsEntry *e, double *head, double *body) { if(*(e->data[0]) == '*') { total += *head = get_tex("yukkureimu")->h * CREDITS_YUKKURI_SCALE; } else { - total += *head = font_line_spacing(_fonts.mainmenu); + total += *head = font_get_lineskip(get_font("big")); } if(e->lines > 1) { - total += *body += (e->lines - 0.5) * font_line_spacing(_fonts.standard); + total += *body += (e->lines - 0.5) * font_get_lineskip(get_font("standard")); } } @@ -323,9 +323,14 @@ static void credits_draw_entry(CreditsEntry *e) { r_mat_pop(); r_mat_translate(0, yukkuri_spr->h * CREDITS_YUKKURI_SCALE * 0.5, 0); } else { - Font *font = i ? _fonts.standard : _fonts.mainmenu; - draw_text(AL_Center, 0, 0, e->data[i], font); - r_mat_translate(0, font_line_spacing(font), 0); + Font *font = get_font(i ? "standard" : "big"); + r_shader("text_default"); + text_draw(e->data[i], &(TextParams) { + .align = ALIGN_CENTER, + .font_ptr = font, + }); + r_shader_standard(); + r_mat_translate(0, font_get_lineskip(font), 0); } } diff --git a/src/dialog.c b/src/dialog.c index 836ba957..554c8715 100644 --- a/src/dialog.c +++ b/src/dialog.c @@ -120,7 +120,11 @@ void draw_dialog(Dialog *dialog) { if(dialog->messages[dialog->pos].side == Right) r_color3(0.6,0.6,1); - draw_text_auto_wrapped(AL_Center, VIEWPORT_W/2, VIEWPORT_H-110, dialog->messages[dialog->pos].msg, VIEWPORT_W * 0.85, _fonts.standard); + r_shader("text_default"); + text_draw_wrapped(dialog->messages[dialog->pos].msg, VIEWPORT_W * 0.85, &(TextParams) { + .pos = { VIEWPORT_W/2, VIEWPORT_H-110 }, + .align = ALIGN_CENTER + }); if(dialog->messages[dialog->pos].side == Right) r_color3(1,1,1); diff --git a/src/ending.c b/src/ending.c index ba2e71d8..bf87e285 100644 --- a/src/ending.c +++ b/src/ending.c @@ -161,7 +161,13 @@ static void ending_draw(Ending *e) { draw_sprite_p(SCREEN_W/2, SCREEN_H/2, e->entries[e->pos].sprite); } - draw_text_auto_wrapped(AL_Center, SCREEN_W/2, VIEWPORT_H*4/5, e->entries[e->pos].msg, SCREEN_W * 0.85, _fonts.standard); + r_shader("text_default"); + text_draw_wrapped(e->entries[e->pos].msg, SCREEN_W * 0.85, &(TextParams) { + .pos = { SCREEN_W/2, VIEWPORT_H*4/5 }, + .align = ALIGN_CENTER, + }); + r_shader_standard(); + r_color4(1,1,1,1); } diff --git a/src/hashtable.c b/src/hashtable.c index 5f4d545f..e310cc8c 100644 --- a/src/hashtable.c +++ b/src/hashtable.c @@ -8,530 +8,18 @@ #include "taisei.h" +#define HT_IMPL #include "hashtable.h" -#include "list.h" #include "util.h" -#include -#include -#include -#include +uint32_t (*htutil_hashfunc_string)(uint32_t crc, const char *str); -// #define HT_USE_MUTEX - -typedef struct HashtableElement { - LIST_INTERFACE(struct HashtableElement); - void *data; - void *key; - hash_t hash; -} HashtableElement; - -struct Hashtable { - HashtableElement **table; - HTCmpFunc cmp_func; - HTHashFunc hash_func; - HTCopyFunc copy_func; - HTFreeFunc free_func; - size_t num_elements; - size_t table_size; - size_t hash_mask; - - struct { - SDL_mutex *mutex; - SDL_cond *cond; - uint readers; - bool writing; - } sync; -}; - -typedef struct HashtableIterator { - Hashtable *hashtable; - size_t bucketnum; - HashtableElement *elem; -} HashtableIterator; - -/* - * Generic functions - */ - -Hashtable* hashtable_new(HTCmpFunc cmp_func, HTHashFunc hash_func, HTCopyFunc copy_func, HTFreeFunc free_func) { - Hashtable *ht = malloc(sizeof(Hashtable)); - size_t size = HT_MIN_SIZE; - - if(!cmp_func) { - cmp_func = hashtable_cmpfunc_ptr; - } - - if(!copy_func) { - copy_func = hashtable_copyfunc_ptr; - } - - ht->table = calloc(size, sizeof(HashtableElement*)); - ht->table_size = size; - ht->hash_mask = size - 1; - ht->num_elements = 0; - ht->cmp_func = cmp_func; - ht->hash_func = hash_func; - ht->copy_func = copy_func; - ht->free_func = free_func; - - ht->sync.writing = false; - ht->sync.readers = 0; - ht->sync.mutex = SDL_CreateMutex(); - ht->sync.cond = SDL_CreateCond(); - - assert(ht->hash_func != NULL); - - return ht; -} - -static void hashtable_begin_write(Hashtable *ht) { - SDL_LockMutex(ht->sync.mutex); - - while(ht->sync.writing || ht->sync.readers) { - SDL_CondWait(ht->sync.cond, ht->sync.mutex); - } - - ht->sync.writing = true; - SDL_UnlockMutex(ht->sync.mutex); -} - -static void hashtable_end_write(Hashtable *ht) { - SDL_LockMutex(ht->sync.mutex); - ht->sync.writing = false; - SDL_CondBroadcast(ht->sync.cond); - SDL_UnlockMutex(ht->sync.mutex); -} - -static void hashtable_begin_read(Hashtable *ht) { - SDL_LockMutex(ht->sync.mutex); - - while(ht->sync.writing) { - SDL_CondWait(ht->sync.cond, ht->sync.mutex); - } - - ++ht->sync.readers; - SDL_UnlockMutex(ht->sync.mutex); -} - -static void hashtable_end_read(Hashtable *ht) { - SDL_LockMutex(ht->sync.mutex); - - if(!--ht->sync.readers) { - SDL_CondSignal(ht->sync.cond); - } - - SDL_UnlockMutex(ht->sync.mutex); -} - -void hashtable_lock(Hashtable *ht) { - assert(ht != NULL); - hashtable_begin_read(ht); -} - -void hashtable_unlock(Hashtable *ht) { - assert(ht != NULL); - hashtable_end_read(ht); -} - -static void* hashtable_delete_callback(List **vlist, List *velem, void *vht) { - Hashtable *ht = vht; - HashtableElement *elem = (HashtableElement*)velem; - - if(ht->free_func) { - ht->free_func(elem->key); - } - - free(list_unlink(vlist, velem)); - return NULL; -} - -static void hashtable_unset_all_internal(Hashtable *ht) { - for(size_t i = 0; i < ht->table_size; ++i) { - list_foreach((ht->table + i), hashtable_delete_callback, ht); - } - - ht->num_elements = 0; -} - -void hashtable_unset_all(Hashtable *ht) { - assert(ht != NULL); - hashtable_begin_write(ht); - hashtable_unset_all_internal(ht); - hashtable_end_write(ht); -} - -void hashtable_free(Hashtable *ht) { - if(!ht) { - return; - } - - hashtable_unset_all(ht); - - SDL_DestroyCond(ht->sync.cond); - SDL_DestroyMutex(ht->sync.mutex); - - free(ht->table); - free(ht); -} - -static HashtableElement* hashtable_get_internal(Hashtable *ht, void *key, hash_t hash) { - HashtableElement *elems = ht->table[hash & ht->hash_mask]; - - for(HashtableElement *e = elems; e; e = e->next) { - if(hash == e->hash && ht->cmp_func(key, e->key)) { - return e; - } - } - - return NULL; -} - -void* hashtable_get(Hashtable *ht, void *key) { - assert(ht != NULL); - - hash_t hash = ht->hash_func(key); - HashtableElement *elem; - void *data; - - hashtable_begin_read(ht); - elem = hashtable_get_internal(ht, key, hash); - data = elem ? elem->data : NULL; - hashtable_end_read(ht); - - return data; -} - -void* hashtable_get_unsafe(Hashtable *ht, void *key) { - assert(ht != NULL); - - hash_t hash = ht->hash_func(key); - HashtableElement *elem; - void *data; - - elem = hashtable_get_internal(ht, key, hash); - data = elem ? elem->data : NULL; - - return data; -} - -static bool hashtable_set_internal(Hashtable *ht, HashtableElement **table, size_t hash_mask, hash_t hash, void *key, void *data, void* (*datafunc)(void*), bool allow_overwrite, void **val) { - size_t idx = hash & hash_mask; - HashtableElement *elems = table[idx], *elem; - void *result = NULL; - - for(HashtableElement *e = elems; e; e = e->next) { - if(hash == e->hash && ht->cmp_func(key, e->key)) { - if(!allow_overwrite) { - if(val != NULL) { - *val = e->data; - } - - return result; - } - - if(ht->free_func) { - ht->free_func(e->key); - } - - free(list_unlink(&elems, e)); - ht->num_elements--; - break; - } - } - - if(datafunc != NULL) { - data = datafunc(data); - } - - if(val != NULL) { - *val = data; - } - - if(data) { - elem = malloc(sizeof(HashtableElement)); - ht->copy_func(&elem->key, key); - elem->hash = ht->hash_func(elem->key); - elem->data = data; - list_push(&elems, elem); - ht->num_elements++; - result = data; - } - - table[idx] = elems; - return result; -} - -static void hashtable_resize(Hashtable *ht, size_t new_size) { - assert(new_size != ht->table_size); - HashtableElement **new_table = calloc(new_size, sizeof(HashtableElement*)); - size_t new_hash_mask = new_size - 1; - - for(size_t i = 0; i < ht->table_size; ++i) { - for(HashtableElement *e = ht->table[i]; e; e = e->next) { - hashtable_set_internal(ht, new_table, new_hash_mask, e->hash, e->key, e->data, NULL, true, NULL); - } - } - - hashtable_unset_all_internal(ht); - free(ht->table); - - ht->table = new_table; - - log_debug("Resized hashtable at %p: %"PRIuMAX" -> %"PRIuMAX"", - (void*)ht, (uintmax_t)ht->table_size, (uintmax_t)new_size); - // hashtable_print_stringkeys(ht); - - ht->table_size = new_size; - ht->hash_mask = new_hash_mask; -} - -void hashtable_set(Hashtable *ht, void *key, void *data) { - assert(ht != NULL); - - hash_t hash = ht->hash_func(key); - - hashtable_begin_write(ht); - hashtable_set_internal(ht, ht->table, ht->hash_mask, hash, key, data, NULL, true, NULL); - - if(ht->num_elements == ht->table_size) { - hashtable_resize(ht, ht->table_size * 2); - } - - hashtable_end_write(ht); -} - -bool hashtable_try_set(Hashtable *ht, void *key, void *data, void* (*datafunc)(void*), void **val) { - assert(ht != NULL); - - hash_t hash = ht->hash_func(key); - - hashtable_begin_write(ht); - bool result = hashtable_set_internal(ht, ht->table, ht->hash_mask, hash, key, data, datafunc, false, val); - - if(ht->num_elements == ht->table_size) { - hashtable_resize(ht, ht->table_size * 2); - } - - hashtable_end_write(ht); - return result; -} - -void hashtable_unset(Hashtable *ht, void *key) { - hashtable_set(ht, key, NULL); -} - -void hashtable_unset_deferred(Hashtable *ht, void *key, ListContainer **list) { - assert(ht != NULL); - ListContainer *c = list_push(list, list_wrap_container(NULL)); - ht->copy_func(&c->data, key); -} - -void hashtable_unset_deferred_now(Hashtable *ht, ListContainer **list) { - ListContainer *next; - assert(ht != NULL); - - for(ListContainer *c = *list; c; c = next) { - next = c->next; - hashtable_unset(ht, c->data); - - if(ht->free_func) { - ht->free_func(c->data); - } - - free(list_unlink(list, c)); - } -} - -/* - * Iteration functions - */ - -void* hashtable_foreach(Hashtable *ht, HTIterCallback callback, void *arg) { - assert(ht != NULL); - - void *ret = NULL; - - hashtable_begin_read(ht); - - for(size_t i = 0; i < ht->table_size; ++i) { - for(HashtableElement *e = ht->table[i]; e; e = e->next) { - ret = callback(e->key, e->data, arg); - if(ret) { - hashtable_end_read(ht); - return ret; - } - } - } - - hashtable_end_read(ht); - - return ret; -} - -HashtableIterator* hashtable_iter(Hashtable *ht) { - assert(ht != NULL); - HashtableIterator *iter = malloc(sizeof(HashtableIterator)); - iter->hashtable = ht; - iter->bucketnum = (size_t)-1; - return iter; -} - -bool hashtable_iter_next(HashtableIterator *iter, void **out_key, void **out_data) { - Hashtable *ht = iter->hashtable; - - if(iter->bucketnum == (size_t)-1) { - iter->bucketnum = 0; - iter->elem = ht->table[iter->bucketnum]; +void htutil_init(void) { + if(SDL_HasSSE42()) { + log_info("Using SSE4.2-accelerated CRC32 as the string hash function"); + htutil_hashfunc_string = crc32str_sse42; } else { - iter->elem = iter->elem->next; - } - - while(!iter->elem) { - if(++iter->bucketnum == ht->table_size) { - free(iter); - return false; - } - - iter->elem = ht->table[iter->bucketnum]; - } - - if(out_key) { - *out_key = iter->elem->key; - } - - if(out_data) { - *out_data = iter->elem->data; - } - - return true; -} - -/* - * Convenience functions for hashtables with string keys - */ - -bool hashtable_cmpfunc_string(void *str1, void *str2) { - return !strcmp((const char*)str1, (const char*)(str2)); -} - -hash_t hashtable_hashfunc_string(void *vstr) { - return crc32str(0, (const char*)vstr); -} - -hash_t hashtable_hashfunc_string_sse42(void *vstr) { - return crc32str_sse42(0, (const char*)vstr); -} - -void hashtable_copyfunc_string(void **dst, void *src) { - *dst = malloc(strlen((char*)src) + 1); - strcpy(*dst, src); -} - -// #define hashtable_freefunc_string free - -Hashtable* hashtable_new_stringkeys(void) { - return hashtable_new( - hashtable_cmpfunc_string, - SDL_HasSSE42() ? hashtable_hashfunc_string_sse42 : hashtable_hashfunc_string, - hashtable_copyfunc_string, - hashtable_freefunc_string - ); -} - -void* hashtable_get_string(Hashtable *ht, const char *key) { - return hashtable_get(ht, (void*)key); -} - -void hashtable_set_string(Hashtable *ht, const char *key, void *data) { - hashtable_set(ht, (void*)key, data); -} - -void hashtable_unset_string(Hashtable *ht, const char *key) { - hashtable_unset(ht, (void*)key); -} - -/* - * Misc convenience functions - */ - -void* hashtable_iter_free_data(void *key, void *data, void *arg) { - free(data); - return NULL; -} - -bool hashtable_cmpfunc_ptr(void *p1, void *p2) { - return p1 == p2; -} - -void hashtable_copyfunc_ptr(void **dst, void *src) { - *dst = src; -} - -hash_t hashtable_hashfunc_ptr(void *val) { - hash_t x = (uintptr_t)val & ((1u << 31u) - 1u); - x = ((x >> 16) ^ x) * 0x45d9f3b; - x = ((x >> 16) ^ x) * 0x45d9f3b; - x = (x >> 16) ^ x; - return x; -} - -/* - * Diagnostic functions - */ - -void hashtable_get_stats(Hashtable *ht, HashtableStats *stats) { - assert(ht != NULL); - assert(stats != NULL); - - memset(stats, 0, sizeof(HashtableStats)); - - for(size_t i = 0; i < ht->table_size; ++i) { - int elems = 0; - - for(HashtableElement *e = ht->table[i]; e; e = e->next) { - ++elems; - ++stats->num_elements; - } - - if(elems > 1) { - stats->collisions += elems - 1; - } - - if(!elems) { - ++stats->free_buckets; - } else if(elems > stats->max_per_bucket) { - stats->max_per_bucket = elems; - } + log_info("Using software fallback CRC32 as the string hash function"); + htutil_hashfunc_string = crc32str; } } - -size_t hashtable_get_approx_overhead(Hashtable *ht) { - size_t o = sizeof(Hashtable) + sizeof(HashtableElement*) * ht->table_size; - - for(HashtableIterator *i = hashtable_iter(ht); hashtable_iter_next(i, NULL, NULL);) { - o += sizeof(HashtableElement); - } - - return o; -} - -void hashtable_print_stringkeys(Hashtable *ht) { - HashtableStats stats; - hashtable_get_stats(ht, &stats); - - log_debug("------ %p:", (void*)ht); - for(size_t i = 0; i < ht->table_size; ++i) { - log_debug("[bucket %"PRIuMAX"] %p", (uintmax_t)i, (void*)ht->table[i]); - - for(HashtableElement *e = ht->table[i]; e; e = e->next) { - log_debug(" -- %s (%"PRIuMAX"): %p", (char*)e->key, (uintmax_t)e->hash, e->data); - } - } - - log_debug( - "%i total elements, %i unused buckets, %i collisions, max %i elems per bucket, %lu approx overhead", - stats.num_elements, stats.free_buckets, stats.collisions, stats.max_per_bucket, - (ulong)hashtable_get_approx_overhead(ht) - ); -} diff --git a/src/hashtable.h b/src/hashtable.h index 4e016b32..d5ea3fe6 100644 --- a/src/hashtable.h +++ b/src/hashtable.h @@ -9,67 +9,55 @@ #pragma once #include "taisei.h" -#include - -#include "list.h" - -#define HT_MIN_SIZE 16 - -typedef struct Hashtable Hashtable; -typedef struct HashtableIterator HashtableIterator; -typedef struct HashtableStats HashtableStats; +/* + * hash_t + * + * Type of the hash values. + */ typedef uint32_t hash_t; -struct HashtableStats { - uint free_buckets; - uint collisions; - uint max_per_bucket; - uint num_elements; -}; +/* + * htutil_init + * + * One-time setup function. + */ +void htutil_init(void); -typedef bool (*HTCmpFunc)(void *key1, void *key2); -typedef hash_t (*HTHashFunc)(void *key); -typedef void (*HTCopyFunc)(void **dstkey, void *srckey); -typedef void (*HTFreeFunc)(void *key); -typedef void* (*HTIterCallback)(void *key, void *data, void *arg); +/* + * htutil_hashfunc_uint32 + * + * Hash function for 32-bit integers + * NOTE: assuming hash_t stays uint32_t, this function has no collisions. + */ +static inline attr_must_inline hash_t htutil_hashfunc_uint32(uint32_t x) { + x = ((x >> 16) ^ x) * 0x45d9f3b; + x = ((x >> 16) ^ x) * 0x45d9f3b; + x = (x >> 16) ^ x; + return (hash_t)x; +} -Hashtable* hashtable_new(HTCmpFunc cmp_func, HTHashFunc hash_func, HTCopyFunc copy_func, HTFreeFunc free_func); -void hashtable_free(Hashtable *ht); -void* hashtable_get(Hashtable *ht, void *key) attr_hot; -void* hashtable_get_unsafe(Hashtable *ht, void *key) attr_hot; -void hashtable_set(Hashtable *ht, void *key, void *data); -bool hashtable_try_set(Hashtable *ht, void *key, void *data, void* (*datafunc)(void*), void **val); -void hashtable_unset(Hashtable *ht, void *key); -void hashtable_unset_deferred(Hashtable *ht, void *key, ListContainer **list); -void hashtable_unset_deferred_now(Hashtable *ht, ListContainer **list); -void hashtable_unset_all(Hashtable *ht); +/* + * htutil_hashfunc_uint64 + * + * Hash function for 64-bit integers. + */ +static inline attr_must_inline hash_t htutil_hashfunc_uint64(uint64_t x) { + x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9); + x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb); + x = x ^ (x >> 31); + return (hash_t)x; +} -void* hashtable_foreach(Hashtable *ht, HTIterCallback callback, void *arg); +/* + * htutil_hashfunc_string + * + * Hash function for null-terminated strings. + */ +extern uint32_t (*htutil_hashfunc_string)(uint32_t crc, const char *str); -// THIS IS NOT THREAD-SAFE. You have to use hashtable_lock/unlock to make it so. -HashtableIterator* hashtable_iter(Hashtable *ht); -bool hashtable_iter_next(HashtableIterator *iter, void **out_key, void **out_data); +// Import public declarations for the predefined hashtable types. +#define HT_DECL +#include "hashtable_predefs.inc.h" -bool hashtable_cmpfunc_string(void *str1, void *str2) attr_hot; -hash_t hashtable_hashfunc_string(void *vstr) attr_hot; -hash_t hashtable_hashfunc_string_sse42(void *vstr) attr_hot; -void hashtable_copyfunc_string(void **dst, void *src); -#define hashtable_freefunc_string free -Hashtable* hashtable_new_stringkeys(void); - -void* hashtable_get_string(Hashtable *ht, const char *key); -void hashtable_set_string(Hashtable *ht, const char *key, void *data); -void hashtable_unset_string(Hashtable *ht, const char *key); -void hashtable_unset_deferred_string(Hashtable *ht, const char *key); - -void* hashtable_iter_free_data(void *key, void *data, void *arg); -bool hashtable_cmpfunc_ptr(void *p1, void *p2); -void hashtable_copyfunc_ptr(void **dst, void *src); -hash_t hashtable_hashfunc_ptr(void *val); - -void hashtable_print_stringkeys(Hashtable *ht); -size_t hashtable_get_approx_overhead(Hashtable *ht); -void hashtable_get_stats(Hashtable *ht, HashtableStats *stats); - -void hashtable_lock(Hashtable *ht); -void hashtable_unlock(Hashtable *ht); +// NOTE: For the hashtable API, see hashtable.inc.h +// NOTE: For type-generic wrappers around the API, see hashtable_predefs.inc.h diff --git a/src/hashtable.inc.h b/src/hashtable.inc.h new file mode 100644 index 00000000..6e73fe44 --- /dev/null +++ b/src/hashtable.inc.h @@ -0,0 +1,1045 @@ + +#include "taisei.h" + +#include "hashtable.h" +#include "list.h" +#include "util/assert.h" +#include "log.h" + +#include +#include + +/******************************************************************************\ + * ,. * + * ``` `.,:::,. `;'';,;'.` * + * `,`,;'''''';.+###############''''''' ,.` * + * ,.''.:''''+@@####################'';,' `: * + * ,`',;'''+#@@#########################++;, * + * :,';+++@@#############################:,: * + * .::,;++@@###############################; * + * .,:+;+,,,:+##@##########@##############+:, * + * +,:+#@@##'::::++#######@###@##@##+'::,;'#` * + * @++:@@@@@#@@@@###@#@##@##'@@##@@##@@@##@## * + * .+'++@@@@##@@@@#@#@@#+#@@#:'@##@@@@@#@#@@## * + * .,+;+'+@@@##@@@@@+@+@@;';@@.`:#@;';@##@#@#@@#, * + * :#:+'+@@@#@@@@@:+;:@::':@,``.:@,+::@:@#@@#@#; * + * '::+'@@@@@@@@#`,+`++';.`````:``+++,+:@@@.;@; * + * +@'+;@@@@#:,;;,`, ''';`````````+''+:.';.: '` * + * +@@@@@@@#:+',:`.,,.````````````.,,,.,:''. * + * '@@@@@@@':'+'+`....```````````````...:'+ Type-safe generics? In my C? * + * ;@@@@@@@':'+''`````````.`````````````,'+ * + * :#@@@@@@@:'+''`````````::::''````````:;; It's more likely than you think. * + * .;@@@@@@@@++,'`````````'::::.````````;:`: * + * .@@#@@@@#;'.:.`````````':'``````````@.: * + * ,#@,@@;@##@##``````````````````````@@#, [FREE HEADER CHECK] * + * :.@ +# @##@@# :``````````````````;@:@@: * + * # '``.@:,## :`````````````.+@#,..@: * + * ` '` @ `::,,,:::` ` +; . ;' * + * : + * +\******************************************************************************/ + +/***********************\ + * User-defined macros * +\***********************/ + +/* + * HT_SUFFIX + * + * A name tag associated with your custom hashtable type. + * You must define this to a valid, unique identifier. + * Whenever you see XXX appear as part of an identifier in the documentation, + * substitute it for the value of HT_SUFFIX. + * + * Example: + * + * #define HT_SUFFIX str2ptr + */ +#ifndef HT_SUFFIX + #error HT_SUFFIX not defined +#endif + +/* + * HT_KEY_TYPE + * + * The type of hashtable keys. This may be virtually anything, except for + * incomplete types. It is possible to use a pointer to a heap-allocated + * data structure here that is to be used as the actual key (see below). This + * is useful if you want to use strings, for example. + * + * Example: + * + * #define HT_KEY_TYPE char* + */ +#ifndef HT_KEY_TYPE + #error HT_KEY_TYPE not defined +#endif + +/* + * HT_KEY_CONST + * + * Optional. + * + * If defined, the 'const' qualifier will be prepended to the key type where + * appropriate. Useful for pointer types. + */ +#ifdef HT_KEY_CONST + #undef HT_KEY_CONST + #define HT_KEY_CONST const +#else + #define HT_KEY_CONST +#endif + +/* + * HT_KEY_TYPE + * + * The type of hashtable keys. This must be a complete, non-array type. + * + * Example: + * + * #define HT_VALUE_TYPE void* + */ +#ifndef HT_VALUE_TYPE + #error HT_VALUE_TYPE not defined +#endif + +/* + * HT_FUNC_HASH_KEY(key) + * + * A function-like macro used to compute an integer hash based on key. It must + * produce consistent results for every possible key, and collisions should be + * minimized to improve performance. + * + * Type of the key parameter is the same as HT_KEY_TYPE. + * Type of the result expression should be hash_t. + * + * Example: + * + * #define HT_FUNC_HASH_KEY(key) htutil_hashfunc_string(key) + */ +#ifndef HT_FUNC_HASH_KEY + #error HT_FUNC_HASH_KEY not defined +#endif + +/* + * HT_FUNC_KEYS_EQUAL(key1, key2) + * + * Optional. + * + * A function-like macro used to test two keys for equality. Note that if two + * keys are considered equal, they must also hash to the same value. However, + * the converse doesn't apply: equality of hashes doesn't imply equality of keys. + * + * Type of the key1 and key2 parameters is the same as HT_KEY_TYPE. + * The result expression must be true if the keys are equal, false otherwise. + * + * If no definition is provided, the standard == operator is used. + * + * Example: + * + * #define HT_FUNC_KEYS_EQUAL(key1, key2) (!strcmp(key1, key2)) + */ +#ifndef HT_FUNC_KEYS_EQUAL + #define HT_FUNC_KEYS_EQUAL(key1, key2) ((key1) == (key2)) +#endif + +/* + * HT_FUNC_COPY_KEY(dst, src) + * + * Optional. + * + * A function-like macro that specifies how copying a key should be handled. + * This is useful with pointers to variably-sized structures, such as strings. + * + * Type of src is the same as HT_KEY_TYPE. + * dst is a pointer to HT_KEY_TYPE. + * Type of the result expression is ignored. + * + * If no definition is provided, the key is simply copied by value. + * + * Example: + * + * #define HT_FUNC_COPY_KEY(dst, src) (*(dst) = strdup(src)) + */ +#ifndef HT_FUNC_COPY_KEY + #define HT_FUNC_COPY_KEY(dst, src) (*(dst) = (src)) +#endif + +/* + * HT_FUNC_FREE_KEY(key) + * + * Optional. + * + * A function-like macro used to free any resources allocated for a key, if any. + * Use this to clean up after after any allocations done by HT_FUNC_COPY_KEY. + * + * Example: + * + * #define HT_FUNC_FREE_KEY(key) free(key) + */ +#ifndef HT_FUNC_FREE_KEY + #define HT_FUNC_FREE_KEY(key) ((void)(key)) +#endif + +/* + * HT_THREAD_SAFE + * + * Optional. + * + * If defined, most hashtable operations will be guarded by a readers-writer lock, + * protecting them from data races when used by multiple threads. This has some + * impact on performance and memory usage, however. + * + * Some additional APIs are provided in this mode, as well as unsafe versions of + * some of the core APIs. They are documented below. + * + * The unsafe variants behave identically to their core counterparts, but they + * completely bypass the locking mechanism, making them somewhat faster. + * + * Example: + * + * #define HT_THREAD_SAFE + */ +#ifndef HT_THREAD_SAFE + // no default needed +#endif + +/* + * HT_DECL, HT_IMPL + * + * You must define at least one of these macros. + * + * If HT_DECL is defined, this header will generate declarations of the API types + * and functions. This should be used in headers. + * + * If HT_IMPL is defined, then HT_DECL is implied, and definitions of the API + * functions are also generated. + * + * * Example: + * + * #define HT_IMPL + * #include "hashtable.inc.h" + */ +#if !defined(HT_DECL) && !defined(HT_IMPL) + #error neither HT_DECL nor HT_IMPL defined +#elif defined(HT_IMPL) && !defined(HT_DECL) + #define HT_DECL +#endif + +/* + * HT_MIN_SIZE + * + * Optional. + * + * How many buckets to allocate initially. Higher values increase memory usage, + * but may improve performance. Must be a power of 2. + */ +#ifndef HT_MIN_SIZE + #define HT_MIN_SIZE 32 +#endif + +// TODO: static_assert HT_MIN_SIZE is a power of 2 + +/* + * The following macros comprise the core of the templating machinery. + * They are used to construct identifiers augmented with HT_SUFFIX. + */ + +#define _HT_NAME_INNER1(suffix, name) ht_##suffix##name +#define _HT_NAME_INNER2(suffix, name) _HT_NAME_INNER1(suffix, name) +#define HT_NAME(name) _HT_NAME_INNER2(HT_SUFFIX, name) + +#define _HT_PRIV_NAME_INNER1(suffix, name) _ht_##suffix##name +#define _HT_PRIV_NAME_INNER2(suffix, name) _HT_PRIV_NAME_INNER1(suffix, name) +#define HT_PRIV_NAME(name) _HT_PRIV_NAME_INNER2(HT_SUFFIX, name) + +#define HT_TYPE(name) HT_NAME(_##name##_t) +#define HT_FUNC(name) HT_NAME(_##name) +#define HT_PRIV_FUNC(name) HT_PRIV_NAME(_##name) +#define HT_BASETYPE HT_NAME(_t) + +#define HT_DECLARE_FUNC(return_type, name, arguments) return_type HT_FUNC(name) arguments +#define HT_DECLARE_PRIV_FUNC(return_type, name, arguments) static return_type HT_PRIV_FUNC(name) arguments +#define HT_INLINE inline attr_must_inline + +/****************\ + * Declarations * +\****************/ +#ifdef HT_DECL + +/* + * ht_XXX_t + * + * A structure representing a hash table. + * All its fields are considered private and should be left unmolested, + * please use the API functiosn instead of manipulating these directly. + */ +typedef struct HT_BASETYPE HT_BASETYPE; + +/* + * ht_XXX_key_t + * + * An alias for the key type (HT_KEY_TYPE). + */ +typedef HT_KEY_TYPE HT_TYPE(key); + +/* + * ht_XXX_const_key_t + * + * An alias for the key type (HT_KEY_TYPE). + * + * If HT_KEY_CONST is defined, this type has the 'const' qualifier. + */ +typedef const HT_KEY_TYPE HT_TYPE(const_key); + +/* + * ht_XXX_value_t + * + * An alias for the value type (HT_VALUE_TYPE). + */ +typedef HT_VALUE_TYPE HT_TYPE(value); + +/* + * ht_XXX_key_list_t + * + * A list of keys. + */ +typedef struct HT_TYPE(key_list) HT_TYPE(key_list); + +/* + * ht_XXX_foreach_callback_t + * + * Pointer to a callback function for use with ht_XXX_foreach(). + */ +typedef void* (*HT_TYPE(foreach_callback))(HT_TYPE(const_key) key, HT_TYPE(value) data, void *arg); + +/* + * ht_XXX_iter_t + * + * An iterator structure. See ht_XXX_iter_begin(). + */ +typedef struct HT_TYPE(iter) HT_TYPE(iter); + +/* + * Forward declaration of the private element struct. + */ +typedef struct HT_TYPE(element) HT_TYPE(element); + +/* + * Definition for ht_XXX_key_list_t. + */ +struct HT_TYPE(key_list) { + LIST_INTERFACE(HT_TYPE(key_list)); + HT_TYPE(key) key; +}; + +/* + * Definition for ht_XXX_t. + * All of these fields are to be considered private. + */ +struct HT_BASETYPE { + HT_TYPE(element) **table; + size_t num_elements; + size_t table_size; + size_t hash_mask; + +#ifdef HT_THREAD_SAFE + struct { + SDL_mutex *mutex; + SDL_cond *cond; + uint readers; + bool writing; + } sync; +#endif +}; + +/* + * Definition for ht_XXX_iter_t. + */ +struct HT_TYPE(iter) { + HT_BASETYPE *hashtable; + HT_TYPE(key) key; + HT_TYPE(value) value; + bool has_data; + + struct { + size_t bucketnum; + HT_TYPE(element) *elem; + } private; +}; + +/* + * void ht_XXX_create(ht_XXX_t *ht); + * + * Initialize a hashtable structure. Must be called before any other API function. + */ +HT_DECLARE_FUNC(void, create, (HT_BASETYPE *ht)) + attr_nonnull(1); + +/* + * void ht_XXX_destroy(ht_XXX_t *ht); + * + * Destroy a hashtable, freeing all resources allocated to it. + * Other API functions, except for ht_XXX_create(), must not be called after this. + * Does not free the memory pointed to by [ht], as it's not necessarily heap-allocated. + */ +HT_DECLARE_FUNC(void, destroy, (HT_BASETYPE *ht)) + attr_nonnull(1); + +/* + * ht_XXX_t* ht_XXX_new(void); + * + * Convenience function; allocates and initializes a new hashtable structure. + * You must free() it manually when you're done with it (but don't forget to + * ht_*_destroy() it as well). + * + * Returns the allocated hashtable structure. + */ +static HT_INLINE attr_returns_nonnull attr_nodiscard +HT_DECLARE_FUNC(HT_BASETYPE*, new, (void)) { + HT_BASETYPE *ht = calloc(1, sizeof(HT_BASETYPE)); + HT_FUNC(create)(ht); + return ht; +} + +#ifdef HT_THREAD_SAFE + +/* + * void ht_XXX_lock(ht_XXX_t *ht); + * + * Acquire a read lock on a hashtable. The hashtable is guarateed to stay unmodified + * while this lock is held. You must not try to modify it from the same thread; that + * will result in a deadlock. Other threads are able to read the hashtable while the + * lock is held. + */ +HT_DECLARE_FUNC(void, lock, (HT_BASETYPE *ht)) + attr_nonnull(1); + +/* + * void ht_XXX_unlock(ht_XXX_t *ht); + * + * Release a read lock on a hashtable previously acquired via ht_XXX_unlock(). + */ +HT_DECLARE_FUNC(void, unlock, (HT_BASETYPE *ht)) + attr_nonnull(1); + +#endif // HT_THREAD_SAFE + +/* + * ht_XXX_value_t ht_XXX_get(ht_XXX_t *ht, ht_XXX_const_key_t key, ht_XXX_const_value_t fallback); + * + * Retrieve a value associated with [key]. If there is no association, [fallback] will + * be returned instead. + */ +HT_DECLARE_FUNC(HT_TYPE(value), get, (HT_BASETYPE *ht, HT_TYPE(const_key) key, HT_TYPE(value) fallback)) + attr_nonnull(1); + +#ifdef HT_THREAD_SAFE +/* + * ht_XXX_value_t ht_XXX_get_unsafe(ht_XXX_t *ht, ht_XXX_const_key_t key, ht_XXX_const_value_t fallback); + * + * A non-thread-safe version of ht_XXX_get(). + */ +HT_DECLARE_FUNC(HT_TYPE(value), get_unsafe, (HT_BASETYPE *ht, HT_TYPE(const_key) key, HT_TYPE(value) fallback)) + attr_nonnull(1); +#endif // HT_THREAD_SAFE + +/* + * bool ht_XXX_lookup(ht_XXX_t *ht, ht_XXX_const_key_t key, ht_XXX_value_t *out_value); + * + * Check whether a key exists in the hashtable. If it does and [out_value] is not NULL, + * then the value associated with it will be also copied into *out_value. + * + * Returns true if an entry is found, false otherwise. + */ +HT_DECLARE_FUNC(bool, lookup, (HT_BASETYPE *ht, HT_TYPE(const_key) key, HT_TYPE(value) *out_value)) + attr_nonnull(1) attr_nodiscard; + +#ifdef HT_THREAD_SAFE +/* + * bool ht_XXX_lookup_unsafe(ht_XXX_t *ht, ht_XXX_const_key_t key, ht_XXX_value_t *out_value); + * + * A non-thread-safe version of ht_XXX_lookup(). + */ +HT_DECLARE_FUNC(bool, lookup_unsafe, (HT_BASETYPE *ht, HT_TYPE(const_key) key, HT_TYPE(value) *out_value)) + attr_nonnull(1) attr_nodiscard; +#endif // HT_THREAD_SAFE + +/* + * void ht_XXX_set(ht_XXX_t *ht, ht_XXX_const_key_t key, ht_XXX_const_value_t value); + * + * Store a key-value pair in the hashtable. + * If this key already has a value associated with it, it will be overwritten. + * + * Returns true if a new key was inserted, false if a previous value was overwritten. + */ +HT_DECLARE_FUNC(bool, set, (HT_BASETYPE *ht, HT_TYPE(const_key) key, HT_TYPE(value) value)) + attr_nonnull(1); + +/* + * bool ht_XXX_try_set(ht_XXX_t ht, ht_XXX_const_key_t key, ht_XXX_const_value_t value, ht_XXX_value_t (*value_transform)(ht_XXX_value_t), ht_XXX_value_t *out_value); + * + * See if [key] exists in the hashtable, and then: + * + * if key exists, then: + * if out_value is not NULL, then: + * store value associated with key in *out_value; + * + * return false; + * else: + * if value_transform is not NULL, then: + * newValue = value_transform(value); + * else + * newValue = value; + * + * if out_value is not NULL, then: + * store newValue in *out_value; + * + * associate key with newValue; + * + * return true; + * + * With HT_THREAD_SAFE defined, this is an atomic operation: the algorithm holds + * a write lock for its whole duration. + */ +HT_DECLARE_FUNC(bool, try_set, (HT_BASETYPE *ht, HT_TYPE(const_key) key, HT_TYPE(value) value, HT_TYPE(value) (*value_transform)(HT_TYPE(value)), HT_TYPE(value) *out_value)) + attr_nonnull(1) attr_nodiscard; + +/* + * bool ht_XXX_unset(ht_XXX_t *ht, ht_XXX_const_key_t key); + * + * If there's a value associated with key, remove that association and return true. + * Otherwise, return false. + */ +HT_DECLARE_FUNC(bool, unset, (HT_BASETYPE *ht, HT_TYPE(const_key) key)) + attr_nonnull(1); + +/* + * void ht_XXX_unset_list(ht_XXX_t *ht, const ht_XXX_key_list_t *keylist); + * + * Functionally equality to calling ht_XXX_unset() for every key in keylist, but + * more efficient. + */ +HT_DECLARE_FUNC(void, unset_list, (HT_BASETYPE *ht, const HT_TYPE(key_list) *key_list)) + attr_nonnull(1); + +/* + * void ht_XXX_unset_all(ht_XXX_t *ht); + * + * Empty the hashtable completely. + * Functionally equivalent to calling ht_XXX_unset() for every key in the table, + * but more efficient. + */ +HT_DECLARE_FUNC(void, unset_all, (HT_BASETYPE *ht)) + attr_nonnull(1); + +/* + * void* ht_XXX_foreach(ht_XXX_t *ht, ht_XXX_callback_t callback, void *arg); + * + * Call callback(key, value, arg) for each key-value pair in the hashtable. + * + * If the callback returns anything other than a NULL pointer, the loop is broken + * early, and this function returns whatever the callback returned. + * + * Otherwise, this function returns NULL once every pair has been processed. + * + * WARNING: Do not try to modify the hashtable from inside the callback. + */ +HT_DECLARE_FUNC(void*, foreach, (HT_BASETYPE *ht, HT_TYPE(foreach_callback) callback, void *arg)) + attr_nonnull(1, 2); + +/* + * void ht_XXX_iter_begin(ht_XXX_t *ht, ht_XXX_iter_t *iter); + * void ht_XXX_iter_next(ht_XXX_iter_t *iter); + * void ht_XXX_iter_end(ht_XXX_iter_t *iter); + * + * These functions are used to iterate over the key-value pairs stored in the + * hashtable without having to provide a callback function. + * + * Declare a ht_XXX_iter_t structure and use ht_XXX_iter_begin() to initialize it. + * Then, if iter->has_data is true, iter->key and iter->value are set to the key + * and value of the first pair in the hashtable, respectively. + * + * While iter->has_data is true, you can call ht_XXX_iter_next() to advance the + * iterator to the next pair. If there are no more pairs, ht_XXX_iter_next() sets + * iter->has_data to false. Otherwise, iter->key and iter->data are updated. + * Calling ht_XXX_iter_next() has no effect if iter->has_data is already false. + * + * You must call ht_XXX_iter_end() when you are done iterating. + * + * Example: + * + * ht_XXX_iter_t iter; + * ht_XXX_iter_begin(&my_hashtable, &iter); + * + * while(iter.has_data) { + * do_something_with(iter.key, iter.value); + * + * if(some_condition) { + * break; + * } + * + * ht_XXX_iter_next(&iter); + * } + * + * ht_XXX_iter_end(&iter); + * + * WARNING: Do not try to modify the hashtable while iterating over its contents. + * + * If HT_THREAD_SAFE is defined, ht_XXX_iter_begin() acquires a read lock, and + * ht_XXX_iter_end() releases it. Thus, the hashtable is guarateed to stay + * unmodified while any threads have an iterator active. + */ +HT_DECLARE_FUNC(void, iter_begin, (HT_BASETYPE *ht, HT_TYPE(iter) *iter)) + attr_nonnull(1, 2); + +HT_DECLARE_FUNC(void, iter_next, (HT_TYPE(iter) *iter)) + attr_nonnull(1); + +HT_DECLARE_FUNC(void, iter_end, (HT_TYPE(iter) *iter)) + attr_nonnull(1); + +#endif // HT_DECL + +/*******************\ + * Implementations * +\*******************/ +#ifdef HT_IMPL + +struct HT_TYPE(element) { + LIST_INTERFACE(HT_TYPE(element)); + HT_TYPE(key) key; + HT_TYPE(value) value; + hash_t hash; +}; + +HT_DECLARE_PRIV_FUNC(void, begin_write, (HT_BASETYPE *ht)) { + #ifdef HT_THREAD_SAFE + SDL_LockMutex(ht->sync.mutex); + + while(ht->sync.writing || ht->sync.readers) { + SDL_CondWait(ht->sync.cond, ht->sync.mutex); + } + + ht->sync.writing = true; + SDL_UnlockMutex(ht->sync.mutex); + #endif +} + +HT_DECLARE_PRIV_FUNC(void, end_write, (HT_BASETYPE *ht)) { + #ifdef HT_THREAD_SAFE + SDL_LockMutex(ht->sync.mutex); + ht->sync.writing = false; + SDL_CondBroadcast(ht->sync.cond); + SDL_UnlockMutex(ht->sync.mutex); + #endif +} + +HT_DECLARE_PRIV_FUNC(void, begin_read, (HT_BASETYPE *ht)) { + #ifdef HT_THREAD_SAFE + SDL_LockMutex(ht->sync.mutex); + + while(ht->sync.writing) { + SDL_CondWait(ht->sync.cond, ht->sync.mutex); + } + + ++ht->sync.readers; + SDL_UnlockMutex(ht->sync.mutex); + #endif +} + +HT_DECLARE_PRIV_FUNC(void, end_read, (HT_BASETYPE *ht)) { + #ifdef HT_THREAD_SAFE + SDL_LockMutex(ht->sync.mutex); + + if(!--ht->sync.readers) { + SDL_CondSignal(ht->sync.cond); + } + + SDL_UnlockMutex(ht->sync.mutex); + #endif +} + +#ifdef HT_THREAD_SAFE +HT_DECLARE_FUNC(void, lock, (HT_BASETYPE *ht)) { + HT_PRIV_FUNC(begin_read)(ht); +} + +HT_DECLARE_FUNC(void, unlock, (HT_BASETYPE *ht)) { + HT_PRIV_FUNC(end_read)(ht); +} +#endif // HT_THREAD_SAFE + +HT_DECLARE_FUNC(void, create, (HT_BASETYPE *ht)) { + size_t size = HT_MIN_SIZE; + + ht->table = calloc(size, sizeof(HT_TYPE(element) *)); + ht->table_size = size; + ht->hash_mask = size - 1; + ht->num_elements = 0; + + #ifdef HT_THREAD_SAFE + ht->sync.writing = false; + ht->sync.readers = 0; + ht->sync.mutex = SDL_CreateMutex(); + ht->sync.cond = SDL_CreateCond(); + #endif +} + +HT_DECLARE_FUNC(void, destroy, (HT_BASETYPE *ht)) { + HT_FUNC(unset_all)(ht); + #ifdef HT_THREAD_SAFE + SDL_DestroyCond(ht->sync.cond); + SDL_DestroyMutex(ht->sync.mutex); + #endif + free(ht->table); +} + +HT_DECLARE_PRIV_FUNC(HT_TYPE(element)*, find_element, (HT_BASETYPE *ht, HT_TYPE(const_key) key, hash_t hash)) { + HT_TYPE(element) *elems = ht->table[hash & ht->hash_mask]; + + for(HT_TYPE(element) *e = elems; e; e = e->next) { + if(hash == e->hash && HT_FUNC_KEYS_EQUAL(key, e->key)) { + return e; + } + } + + return NULL; +} + +HT_DECLARE_FUNC(HT_TYPE(value), get, (HT_BASETYPE *ht, HT_TYPE(const_key) key, HT_TYPE(value) fallback)) { + hash_t hash = HT_FUNC_HASH_KEY(key); + HT_TYPE(element) *elem; + HT_TYPE(value) value; + + HT_PRIV_FUNC(begin_read)(ht); + elem = HT_PRIV_FUNC(find_element)(ht, key, hash); + value = elem ? elem->value : fallback; + HT_PRIV_FUNC(end_read)(ht); + + return value; +} + +#ifdef HT_THREAD_SAFE +HT_DECLARE_FUNC(HT_TYPE(value), get_unsafe, (HT_BASETYPE *ht, HT_TYPE(const_key) key, HT_TYPE(value) fallback)) { + hash_t hash = HT_FUNC_HASH_KEY(key); + HT_TYPE(element) *elem = HT_PRIV_FUNC(find_element)(ht, key, hash); + return elem ? elem->value : fallback; +} +#endif // HT_THREAD_SAFE + +HT_DECLARE_FUNC(bool, lookup, (HT_BASETYPE *ht, HT_TYPE(const_key) key, HT_TYPE(value) *out_value)) { + hash_t hash = HT_FUNC_HASH_KEY(key); + HT_TYPE(element) *elem; + bool found = false; + + HT_PRIV_FUNC(begin_read)(ht); + elem = HT_PRIV_FUNC(find_element)(ht, key, hash); + + if(elem != NULL) { + if(out_value != NULL) { + *out_value = elem->value; + } + + found = true; + } + + HT_PRIV_FUNC(end_read)(ht); + + return found; +} + +#ifdef HT_THREAD_SAFE +HT_DECLARE_FUNC(bool, lookup_unsafe, (HT_BASETYPE *ht, HT_TYPE(const_key) key, HT_TYPE(value) *out_value)) { + hash_t hash = HT_FUNC_HASH_KEY(key); + HT_TYPE(element) *elem = HT_PRIV_FUNC(find_element)(ht, key, hash); + + if(elem != NULL) { + if(out_value != NULL) { + *out_value = elem->value; + } + + return true; + } + + return false; +} +#endif // HT_THREAD_SAFE + +HT_DECLARE_PRIV_FUNC(void*, delete_callback, (List **vlist, List *velem, void *vht)) { + HT_TYPE(element) *elem = (HT_TYPE(element) *) velem; + HT_FUNC_FREE_KEY(elem->key); + free(list_unlink(vlist, velem)); + return NULL; +} + +HT_DECLARE_PRIV_FUNC(void, unset_all, (HT_BASETYPE *ht)) { + for(size_t i = 0; i < ht->table_size; ++i) { + list_foreach((ht->table + i), HT_PRIV_FUNC(delete_callback), ht); + } + + ht->num_elements = 0; +} + +HT_DECLARE_FUNC(void, unset_all, (HT_BASETYPE *ht)) { + HT_PRIV_FUNC(begin_write)(ht); + HT_PRIV_FUNC(unset_all)(ht); + HT_PRIV_FUNC(end_write)(ht); +} + +HT_DECLARE_FUNC(bool, unset, (HT_BASETYPE *ht, HT_TYPE(const_key) key)) { + HT_TYPE(element) *elem; + hash_t hash = HT_FUNC_HASH_KEY(key); + bool success = false; + + HT_PRIV_FUNC(begin_write)(ht); + elem = HT_PRIV_FUNC(find_element)(ht, key, hash); + + if(elem) { + HT_TYPE(element) **elist = ht->table + (hash & ht->hash_mask); + HT_FUNC_FREE_KEY(elem->key); + free(list_unlink(elist, elem)); + --ht->num_elements; + success = true; + } + HT_PRIV_FUNC(end_write)(ht); + + return success; +} + +HT_DECLARE_FUNC(void, unset_list, (HT_BASETYPE *ht, const HT_TYPE(key_list) *key_list)) { + HT_PRIV_FUNC(begin_write)(ht); + + for(const HT_TYPE(key_list) *i = key_list; i; i = i->next) { + hash_t hash = HT_FUNC_HASH_KEY(i->key); + HT_TYPE(element) *elem = HT_PRIV_FUNC(find_element)(ht, i->key, hash); + + if(elem) { + HT_TYPE(element) **elist = ht->table + (hash & ht->hash_mask); + HT_FUNC_FREE_KEY(elem->key); + free(list_unlink(elist, elem)); + --ht->num_elements; + } + } + + HT_PRIV_FUNC(end_write)(ht); +} + +HT_DECLARE_PRIV_FUNC(bool, set, ( + HT_BASETYPE *ht, + HT_TYPE(element) **table, + size_t hash_mask, + hash_t hash, + HT_TYPE(const_key) key, + HT_TYPE(value) value, + HT_TYPE(value) (*transform_value)(HT_TYPE(value)), + bool allow_overwrite, + HT_TYPE(value) *out_value +)) { + size_t idx = hash & hash_mask; + HT_TYPE(element) **elems = table + idx, *elem = NULL; + + for(HT_TYPE(element) *e = *elems; e; e = e->next) { + if(hash == e->hash && HT_FUNC_KEYS_EQUAL(key, e->key)) { + if(!allow_overwrite) { + if(out_value != NULL) { + *out_value = e->value; + } + + return false; + } + + elem = e; + break; + } + } + + if(transform_value != NULL) { + value = transform_value(value); + } + + if(out_value != NULL) { + *out_value = value; + } + + if(elem == NULL) { + elem = malloc(sizeof(*elem)); + HT_FUNC_COPY_KEY(&elem->key, key); + elem->hash = hash; // HT_FUNC_HASH_KEY(elem->key); + elem->value = value; + list_push(elems, elem); + ++ht->num_elements; + return true; + } + + elem->value = value; + return false; +} + +HT_DECLARE_PRIV_FUNC(void, check_elem_count, (HT_BASETYPE *ht)) { + #ifdef DEBUG + size_t num_elements = 0; + for(size_t i = 0; i < ht->table_size; ++i) { + for(HT_TYPE(element) *e = ht->table[i]; e; e = e->next) { + ++num_elements; + } + } + assert(num_elements == ht->num_elements); + #endif // DEBUG +} + +HT_DECLARE_PRIV_FUNC(void, resize, (HT_BASETYPE *ht, size_t new_size)) { + assert(new_size != ht->table_size); + HT_TYPE(element) **new_table = calloc(new_size, sizeof(*new_table)); + size_t new_hash_mask = new_size - 1; + size_t num_elements = ht->num_elements; + + HT_PRIV_FUNC(check_elem_count)(ht); + + for(size_t i = 0; i < ht->table_size; ++i) { + for(HT_TYPE(element) *e = ht->table[i]; e; e = e->next) { + HT_PRIV_FUNC(set)(ht, new_table, new_hash_mask, e->hash, e->key, e->value, NULL, true, NULL); + } + } + + HT_PRIV_FUNC(unset_all)(ht); + ht->num_elements = num_elements; + + free(ht->table); + ht->table = new_table; + + log_debug( + "Resized hashtable at %p: %"PRIuMAX" -> %"PRIuMAX"", + (void*)ht, (uintmax_t)ht->table_size, (uintmax_t)new_size + ); + + ht->table_size = new_size; + ht->hash_mask = new_hash_mask; + + HT_PRIV_FUNC(check_elem_count)(ht); +} + +HT_DECLARE_FUNC(bool, set, (HT_BASETYPE *ht, HT_TYPE(const_key) key, HT_TYPE(value) value)) { + hash_t hash = HT_FUNC_HASH_KEY(key); + + HT_PRIV_FUNC(begin_write)(ht); + bool result = HT_PRIV_FUNC(set)(ht, ht->table, ht->hash_mask, hash, key, value, NULL, true, NULL); + + if(ht->num_elements == ht->table_size) { + HT_PRIV_FUNC(resize)(ht, ht->table_size * 2); + assert(ht->num_elements == ht->table_size / 2); + } + HT_PRIV_FUNC(end_write)(ht); + + return result; +} + +HT_DECLARE_FUNC(bool, try_set, (HT_BASETYPE *ht, HT_TYPE(const_key) key, HT_TYPE(value) value, HT_TYPE(value) (*value_transform)(HT_TYPE(value)), HT_TYPE(value) *out_value)) { + hash_t hash = HT_FUNC_HASH_KEY(key); + + HT_PRIV_FUNC(begin_write)(ht); + bool result = HT_PRIV_FUNC(set)(ht, ht->table, ht->hash_mask, hash, key, value, value_transform, false, out_value); + + if(ht->num_elements == ht->table_size) { + HT_PRIV_FUNC(resize)(ht, ht->table_size * 2); + assert(ht->num_elements == ht->table_size / 2); + } + + HT_PRIV_FUNC(end_write)(ht); + return result; +} + +HT_DECLARE_FUNC(void*, foreach, (HT_BASETYPE *ht, HT_TYPE(foreach_callback) callback, void *arg)) { + void *ret = NULL; + + HT_PRIV_FUNC(begin_read)(ht); + + for(size_t i = 0; i < ht->table_size; ++i) { + for(HT_TYPE(element) *e = ht->table[i]; e; e = e->next) { + ret = callback(e->key, e->value, arg); + if(ret != NULL) { + goto end; + } + } + } + +end: + HT_PRIV_FUNC(end_read)(ht); + return ret; +} + +HT_DECLARE_PRIV_FUNC(void, iter_advance, (HT_BASETYPE *ht, HT_TYPE(iter) *iter)) { + while(!iter->private.elem) { + if(++iter->private.bucketnum == ht->table_size) { + iter->has_data = false; + return; + } + + iter->private.elem = ht->table[iter->private.bucketnum]; + } + + iter->key = iter->private.elem->key; + iter->value = iter->private.elem->value; +} + +HT_DECLARE_FUNC(void, iter_begin, (HT_BASETYPE *ht, HT_TYPE(iter) *iter)) { + HT_PRIV_FUNC(begin_read)(ht); + memset(iter, 0, sizeof(*iter)); + iter->hashtable = ht; + iter->private.elem = *ht->table; + iter->has_data = true; + HT_PRIV_FUNC(iter_advance)(ht, iter); +} + +HT_DECLARE_FUNC(void, iter_next, (HT_TYPE(iter) *iter)) { + if(!iter->has_data) { + return; + } + + iter->private.elem = iter->private.elem->next; + HT_PRIV_FUNC(iter_advance)(iter->hashtable, iter); + return; +} + +HT_DECLARE_FUNC(void, iter_end, (HT_TYPE(iter) *iter)) { + HT_PRIV_FUNC(end_read)(iter->hashtable); +} + +#endif // HT_IMPL + +/***********\ + * Cleanup * +\***********/ + +// Generated with: +// $ sed -e 's/.*#define\s*//' -e 's/[^A-Z0-9_].*//' -e '/^$/d' -e 's/^/#undef /' hashtable.inc.h | sort -u +// I'm sure this can be done much more efficiently, but I don't care to figure it out. + +// It is intentional that it also catches the examples in comments. +// The user-supplied macros have to be #undef'd too. + +#undef HT_BASETYPE +#undef HT_DECL +#undef HT_DECLARE_FUNC +#undef HT_DECLARE_PRIV_FUNC +#undef HT_FUNC +#undef HT_FUNC_COPY_KEY +#undef HT_FUNC_FREE_KEY +#undef HT_FUNC_HASH_KEY +#undef HT_FUNC_KEYS_EQUAL +#undef HT_IMPL +#undef HT_INLINE +#undef HT_KEY_CONST +#undef HT_KEY_TYPE +#undef HT_MIN_SIZE +#undef HT_NAME +#undef HT_PRIV_FUNC +#undef HT_PRIV_NAME +#undef HT_SUFFIX +#undef HT_THREAD_SAFE +#undef HT_TYPE +#undef HT_VALUE_CONST +#undef HT_VALUE_TYPE +#undef _HT_NAME_INNER1 +#undef _HT_NAME_INNER2 +#undef _HT_PRIV_NAME_INNER1 +#undef _HT_PRIV_NAME_INNER2 diff --git a/src/hashtable_incproxy.inc.h b/src/hashtable_incproxy.inc.h new file mode 100644 index 00000000..d5a057ec --- /dev/null +++ b/src/hashtable_incproxy.inc.h @@ -0,0 +1,10 @@ + +#ifdef _HT_IMPL + #define HT_IMPL +#endif + +#ifdef _HT_DECL + #define HT_DECL +#endif + +#include "hashtable.inc.h" diff --git a/src/hashtable_predefs.inc.h b/src/hashtable_predefs.inc.h new file mode 100644 index 00000000..30b7f3b9 --- /dev/null +++ b/src/hashtable_predefs.inc.h @@ -0,0 +1,191 @@ + +// Sets up the predefined hashtable types. +// Add more as necessary. + +#ifdef HT_IMPL + #define _HT_IMPL +#endif + +#ifdef HT_DECL + #define _HT_DECL +#endif + +/* + * str2ptr + * + * Maps strings to void pointers. + */ +#define HT_SUFFIX str2ptr +#define HT_KEY_TYPE char* +#define HT_VALUE_TYPE void* +#define HT_FUNC_FREE_KEY(key) free(key) +#define HT_FUNC_KEYS_EQUAL(key1, key2) (!strcmp(key1, key2)) +#define HT_FUNC_HASH_KEY(key) htutil_hashfunc_string(0, key) +#define HT_FUNC_COPY_KEY(dst, src) (*(dst) = strdup(src)) +#define HT_KEY_CONST +#define HT_VALUE_CONST +#include "hashtable_incproxy.inc.h" + +/* + * str2ptr_ts + * + * Maps strings to void pointers (thread-safe). + */ +#define HT_SUFFIX str2ptr_ts +#define HT_KEY_TYPE char* +#define HT_VALUE_TYPE void* +#define HT_FUNC_FREE_KEY(key) free(key) +#define HT_FUNC_KEYS_EQUAL(key1, key2) (!strcmp(key1, key2)) +#define HT_FUNC_HASH_KEY(key) htutil_hashfunc_string(0, key) +#define HT_FUNC_COPY_KEY(dst, src) (*(dst) = strdup(src)) +#define HT_KEY_CONST +#define HT_VALUE_CONST +#define HT_THREAD_SAFE +#include "hashtable_incproxy.inc.h" + +/* + * str2int + * + * Maps strings to 64-bit integers. + */ +#define HT_SUFFIX str2int +#define HT_KEY_TYPE char* +#define HT_VALUE_TYPE int64_t +#define HT_FUNC_FREE_KEY(key) free(key) +#define HT_FUNC_KEYS_EQUAL(key1, key2) (!strcmp(key1, key2)) +#define HT_FUNC_HASH_KEY(key) htutil_hashfunc_string(0, key) +#define HT_FUNC_COPY_KEY(dst, src) (*(dst) = strdup(src)) +#define HT_KEY_CONST +#include "hashtable_incproxy.inc.h" + +/* + * str2int_ts + * + * Maps strings to 64-bit integers. + */ +#define HT_SUFFIX str2int_ts +#define HT_KEY_TYPE char* +#define HT_VALUE_TYPE int64_t +#define HT_FUNC_FREE_KEY(key) free(key) +#define HT_FUNC_KEYS_EQUAL(key1, key2) (!strcmp(key1, key2)) +#define HT_FUNC_HASH_KEY(key) htutil_hashfunc_string(0, key) +#define HT_FUNC_COPY_KEY(dst, src) (*(dst) = strdup(src)) +#define HT_KEY_CONST +#define HT_THREAD_SAFE +#include "hashtable_incproxy.inc.h" + +/* + * int2int + * + * Maps 64-bit integers to 64-bit integers. + */ +#define HT_SUFFIX int2int +#define HT_KEY_TYPE int64_t +#define HT_VALUE_TYPE int64_t +#define HT_FUNC_HASH_KEY(key) htutil_hashfunc_uint64((uint64_t)(key)) +#include "hashtable_incproxy.inc.h" + +/* + * int2int_ts + * + * Maps 64-bit integers to 64-bit integers (thread-safe). + */ +#define HT_SUFFIX int2int_ts +#define HT_KEY_TYPE int64_t +#define HT_VALUE_TYPE int64_t +#define HT_FUNC_HASH_KEY(key) htutil_hashfunc_uint64((uint64_t)(key)) +#define HT_THREAD_SAFE +#include "hashtable_incproxy.inc.h" + +/* + * C11 generic selection witchcraft. + */ + +#define _HT_GENERIC_MAP(suffix, name) ht_##suffix##_t : ht_##suffix##_##name + +// Add all the non-thread-safe types here. +#define _HT_GENERICLIST_NONTS(name) \ + _HT_GENERIC_MAP(str2ptr, name), \ + _HT_GENERIC_MAP(str2int, name), \ + _HT_GENERIC_MAP(int2int, name) + +// Add all the thread-safe types here. +#define _HT_GENERICLIST_TS(name) \ + _HT_GENERIC_MAP(str2ptr_ts, name), \ + _HT_GENERIC_MAP(str2int_ts, name), \ + _HT_GENERIC_MAP(int2int_ts, name) + +#define _HT_GENERIC(ht_expr, name) (_Generic((ht_expr), \ + _HT_GENERICLIST_NONTS(name), \ + _HT_GENERICLIST_TS(name) \ +)) + +#define _HT_GENERIC_TS(ht_expr, name) (_Generic((ht_expr), \ + _HT_GENERICLIST_TS(name) \ +)) + +/* + * Type-generic wrappers around the core API. + * + * For the core API documentation, see hashtable.inc.h + * + * To find documentation about a macro called "ht_foo", search hashtable.inc.h + * for "ht_XXX_foo" (the XXX is literal). + */ + +#define ht_create(ht) \ + _HT_GENERIC(*(ht), create) (ht) + +#define ht_destroy(ht) \ + _HT_GENERIC(*(ht), destroy) (ht) + +#define ht_lock(ht) \ + _HT_GENERIC_TS(*(ht), lock) (ht) + +#define ht_unlock(ht) \ + _HT_GENERIC(*(ht), unlock) (ht) + +#define ht_get(ht, key, default) \ + _HT_GENERIC(*(ht), get) (ht, key, default) + +#define ht_get_unsafe(ht, key, default) \ + _HT_GENERIC_TS(*(ht), get_unsafe) (ht, key, default) + +#define ht_lookup(ht, key, out_value) \ + _HT_GENERIC(*(ht), lookup) (ht, key, out_value) + +#define ht_lookup_unsafe(ht, key, out_value) \ + _HT_GENERIC_TS(*(ht), lookup_unsafe) (ht, key, out_value) + +#define ht_set(ht, key, value) \ + _HT_GENERIC(*(ht), set) (ht, key, value) + +#define ht_try_set(ht, key, value, value_transform, out_value) \ + _HT_GENERIC(*(ht), try_set) (ht, key, value, value_transform, out_value) + +#define ht_unset(ht, key) \ + _HT_GENERIC(*(ht), unset) (ht, key) + +#define ht_unset_list(ht, keylist) \ + _HT_GENERIC(*(ht), unset_list) (ht, keylist) + +#define ht_unset_all(ht) \ + _HT_GENERIC(*(ht), unset_all) (ht) + +#define ht_foreach(ht, callback, arg) \ + _HT_GENERIC(*(ht), foreach) (ht, callback, arg) + +#define ht_iter_begin(ht, iter) \ + _HT_GENERIC(*(ht), iter_begin) (ht, iter) + +#define ht_iter_next(iter) \ + _HT_GENERIC(*((iter)->hashtable), iter_next) (iter) + +#define ht_iter_end(iter) \ + _HT_GENERIC(*((iter)->hashtable), iter_end) (iter) + +/* + * Cleanup + */ +#undef _HT_IMPL +#undef _HT_DECL diff --git a/src/main.c b/src/main.c index a0a74c63..a71ff827 100644 --- a/src/main.c +++ b/src/main.c @@ -212,6 +212,8 @@ int main(int argc, char **argv) { } free_cli_action(&a); + + htutil_init(); vfs_setup(false); if(headless) { diff --git a/src/menu/charselect.c b/src/menu/charselect.c index 64f227e7..d5760100 100644 --- a/src/menu/charselect.c +++ b/src/menu/charselect.c @@ -102,12 +102,16 @@ void draw_char_menu(MenuData *menu) { r_mat_push(); + r_shader("text_default"); if(menu->entries[i].drawdata != 0) { r_mat_translate(0,-300*menu->entries[i].drawdata, 0); r_mat_rotate_deg(180*menu->entries[i].drawdata, 1,0,0); } - draw_text(AL_Center, 0, 0, name, _fonts.mainmenu); + text_draw(name, &(TextParams) { + .align = ALIGN_CENTER, + .font = "big", + }); r_mat_pop(); if(menu->entries[i].drawdata) { @@ -116,7 +120,11 @@ void draw_char_menu(MenuData *menu) { r_color4(1,1,1,1); } - draw_text(AL_Center, 0, 70, title, _fonts.standard); + text_draw(title, &(TextParams) { + .align = ALIGN_CENTER, + .pos = { 0, 70 }, + }); + r_shader_standard(); r_mat_pop(); } @@ -133,7 +141,12 @@ void draw_char_menu(MenuData *menu) { r_color4(1,1,1,1); } - draw_text(AL_Center, 0, 200+40*i, mode->name, _fonts.standard); + r_shader("text_default"); + text_draw(mode->name, &(TextParams) { + .align = ALIGN_CENTER, + .pos = { 0, 200+40*i }, + }); + r_shader_standard(); } r_mat_pop(); diff --git a/src/menu/common.c b/src/menu/common.c index 3d863a97..05a93961 100644 --- a/src/menu/common.c +++ b/src/menu/common.c @@ -125,8 +125,15 @@ void draw_menu_selector(float x, float y, float w, float h, float t) { } void draw_menu_title(MenuData *m, char *title) { - r_color4(1, 1, 1, 1); - draw_text(AL_Right, (stringwidth(title, _fonts.mainmenu) + 10) * (1.0-menu_fade(m)), 30, title, _fonts.mainmenu); + ShaderProgram *sh_prev = r_shader_current(); + r_shader("text_default"); + text_draw(title, &(TextParams) { + .pos = { (text_width(get_font("big"), title, 0) + 10) * (1.0 - menu_fade(m)), 30 }, + .align = ALIGN_RIGHT, + .font = "big", + .color = rgb(1, 1, 1), + }); + r_shader_ptr(sh_prev); } void draw_menu_list(MenuData *m, float x, float y, void (*draw)(void*, int, int)) { @@ -153,10 +160,14 @@ void draw_menu_list(MenuData *m, float x, float y, void (*draw)(void*, int, int) r_color4(0.9 + ia * 0.1, 0.6 + ia * 0.4, 0.2 + ia * 0.8, (0.7 + 0.3 * a)*o); } - if(draw && i < m->ecount-1) + if(draw && i < m->ecount-1) { draw(e, i, m->ecount); - else if(e->name) - draw_text(AL_Left, 20 - e->drawdata, 20*i, e->name, _fonts.standard); + } else if(e->name) { + ShaderProgram *sh_prev = r_shader_current(); + r_shader("text_default"); + text_draw(e->name, &(TextParams) { .pos = { 20 - e->drawdata, 20*i } }); + r_shader_ptr(sh_prev); + } } r_mat_pop(); @@ -175,7 +186,7 @@ void animate_menu_list_entries(MenuData *m) { void animate_menu_list(MenuData *m) { MenuEntry *s = m->entries + m->cursor; - int w = stringwidth(s->name, _fonts.standard); + int w = text_width(get_font("standard"), s->name, 0); m->drawdata[0] += (10 + w/2.0 - m->drawdata[0])/10.0; m->drawdata[1] += (w*2 - m->drawdata[1])/10.0; diff --git a/src/menu/difficultyselect.c b/src/menu/difficultyselect.c index e7431556..bfc644be 100644 --- a/src/menu/difficultyselect.c +++ b/src/menu/difficultyselect.c @@ -68,8 +68,11 @@ void draw_difficulty_menu(MenuData *menu) { r_draw_quad(); r_mat_pop(); r_color3(1,1,1); - r_shader_standard(); - draw_text(AL_Left, 40+35*menu->drawdata[0], -12, menu->entries[menu->cursor].name, _fonts.standard); + + r_shader("text_default"); + text_draw(menu->entries[menu->cursor].name, &(TextParams) { + .pos = { 40+35*menu->drawdata[0], -12 }, + }); r_shader("sprite_default"); r_mat_pop(); diff --git a/src/menu/ingamemenu.c b/src/menu/ingamemenu.c index 92c8eb59..87f8e3af 100644 --- a/src/menu/ingamemenu.c +++ b/src/menu/ingamemenu.c @@ -150,7 +150,7 @@ void draw_ingame_menu_bg(MenuData *menu, float f) { void update_ingame_menu(MenuData *menu) { menu->drawdata[0] += (menu->cursor*35 - menu->drawdata[0])/7.0; - menu->drawdata[1] += (stringwidth(menu->entries[menu->cursor].name, _fonts.standard) - menu->drawdata[1])/10.0; + menu->drawdata[1] += (text_width(get_font("standard"), menu->entries[menu->cursor].name, 0) - menu->drawdata[1])/10.0; } void draw_ingame_menu(MenuData *menu) { @@ -164,14 +164,18 @@ void draw_ingame_menu(MenuData *menu) { draw_menu_selector(0, menu->drawdata[0], menu->drawdata[1]*2, 41, menu->frames); + ShaderProgram *sh_prev = r_shader_current(); + r_shader("text_default"); if(menu->context) { float s = 0.3 + 0.2 * sin(menu->frames/10.0); r_color4(1-s/2, 1-s/2, 1-s, 1-menu_fade(menu)); - draw_text(AL_Center, 0, -2 * 35, menu->context, _fonts.standard); + text_draw(menu->context, &(TextParams) { + .align = ALIGN_CENTER, + .pos = { 0, -2 * 35 }, + }); } - int i; - for(i = 0; i < menu->ecount; i++) { + for(int i = 0; i < menu->ecount; i++) { if(menu->entries[i].action) { float s = 0, t = 0.7; if(i == menu->cursor) { @@ -184,12 +188,16 @@ void draw_ingame_menu(MenuData *menu) { r_color4(0.5, 0.5, 0.5, 0.5 * (1-menu_fade(menu))); } - draw_text(AL_Center, 0, i*35, menu->entries[i].name, _fonts.standard); + text_draw(menu->entries[i].name, &(TextParams) { + .align = ALIGN_CENTER, + .pos = { 0, i * 35 }, + }); } r_color4(1,1,1,1); r_mat_pop(); r_mat_pop(); + r_shader_ptr(sh_prev); stage_draw_hud(); } diff --git a/src/menu/mainmenu.c b/src/menu/mainmenu.c index a8ca5859..f8d661ed 100644 --- a/src/menu/mainmenu.c +++ b/src/menu/mainmenu.c @@ -102,7 +102,7 @@ void draw_main_menu_bg(MenuData* menu) { } static void update_main_menu(MenuData *menu) { - menu->drawdata[1] += (stringwidth(menu->entries[menu->cursor].name, _fonts.mainmenu) - menu->drawdata[1])/10.0; + menu->drawdata[1] += (text_width(get_font("big"), menu->entries[menu->cursor].name, 0) - menu->drawdata[1])/10.0; menu->drawdata[2] += (35*menu->cursor - menu->drawdata[2])/10.0; for(int i = 0; i < menu->ecount; i++) { @@ -117,7 +117,7 @@ void draw_main_menu(MenuData *menu) { r_mat_push(); r_mat_translate(0, SCREEN_H-270, 0); draw_menu_selector(50 + menu->drawdata[1]/2, menu->drawdata[2], 1.5 * menu->drawdata[1], 64, menu->frames); - + r_shader("text_default"); for(int i = 0; i < menu->ecount; i++) { float s = 5*sin(menu->frames/80.0 + 20*i); @@ -129,7 +129,12 @@ void draw_main_menu(MenuData *menu) { r_color4(1, 0.7 + a, 0.4 + a, 0.7); } - draw_text(AL_Left, 50 + s, 35*i, menu->entries[i].name, _fonts.mainmenu); + text_draw(menu->entries[i].name, &(TextParams) { + .pos = { 50 + s, 35*i }, + .font = "big", + // .shader = "text_example", + // .custom = time_get() + }); } r_mat_pop(); @@ -170,26 +175,43 @@ void draw_main_menu(MenuData *menu) { r_mat_pop(); } - r_shader_standard(); + r_shader("text_default"); r_capability(RCAP_CULL_FACE, cullcap_saved); r_blend(BLEND_ALPHA); char version[32]; snprintf(version, sizeof(version), "v%s", TAISEI_VERSION); - draw_text(AL_Right,SCREEN_W-5,SCREEN_H-10,version,_fonts.small); + text_draw(TAISEI_VERSION, &(TextParams) { + .align = ALIGN_RIGHT, + .pos = { SCREEN_W-5, SCREEN_H-10 }, + .font = "small", + }); + r_shader_standard(); } void draw_loading_screen(void) { preload_resource(RES_TEXTURE, "loading", RESF_PERMANENT); set_ortho(SCREEN_W, SCREEN_H); fill_screen("loading"); - draw_text(AL_Right,SCREEN_W-5,SCREEN_H-10,TAISEI_VERSION,_fonts.small); + /* + text_draw(TAISEI_VERSION, &(TextParams) { + .align = ALIGN_RIGHT, + .pos = { SCREEN_W-5, SCREEN_H-10 }, + .font = "small", + .shader = "text_default", + }); + */ video_swap_buffers(); } void menu_preload(void) { difficulty_preload(); + preload_resources(RES_FONT, RESF_PERMANENT, + "big", + "small", + NULL); + preload_resources(RES_TEXTURE, RESF_PERMANENT, "menu/mainmenubg", NULL); diff --git a/src/menu/options.c b/src/menu/options.c index 636279b8..9e840168 100644 --- a/src/menu/options.c +++ b/src/menu/options.c @@ -772,7 +772,10 @@ void draw_options_menu(MenuData *menu) { r_color4(0.9 + ia * 0.1, 0.6 + ia * 0.4, 0.2 + ia * 0.8, (0.7 + 0.3 * a) * alpha); } - draw_text(AL_Left, (1 + (bind ? bind->pad : 0)) * 20 - e->drawdata, 20*i, e->name, _fonts.standard); + r_shader("text_default"); + text_draw(e->name, &(TextParams) { + .pos = { (1 + (bind ? bind->pad : 0)) * 20 - e->drawdata, 20*i } + }); if(bind) { int j, origin = SCREEN_W - 220; @@ -787,13 +790,16 @@ void draw_options_menu(MenuData *menu) { if(bind->valrange_max) { char tmp[16]; // who'd use a 16-digit number here anyway? snprintf(tmp, 16, "%d", bind_getvalue(bind)); - draw_text(AL_Right, origin, 20*i, tmp, _fonts.standard); + text_draw(tmp, &(TextParams) { .pos = { origin, 20*i }, .align = ALIGN_RIGHT }); } else if(bind->configentry == CONFIG_PRACTICE_POWER) { int stars = PLR_MAX_POWER / 100; + r_shader_standard(); draw_stars(origin - 20 * (stars - 0.5), 20*i, val, 0, stars, 100, alpha, 20); + r_shader("text_default"); } else for(j = bind->displaysingle? val : bind->valcount-1; (j+1) && (!bind->displaysingle || j == val); --j) { - if(j != bind->valcount-1 && !bind->displaysingle) - origin -= stringwidth(bind->values[j+1], _fonts.standard) + 5; + if(j != bind->valcount-1 && !bind->displaysingle) { + origin -= text_width(get_font("standard"), bind->values[j+1], 0) + 5; + } if(val == j) { r_color4(0.9, 0.6, 0.2, alpha); @@ -801,7 +807,7 @@ void draw_options_menu(MenuData *menu) { r_color4(0.5,0.5,0.5,0.7 * alpha); } - draw_text(AL_Right, origin, 20*i, bind->values[j], _fonts.standard); + text_draw(bind->values[j], &(TextParams) { .pos = { origin, 20*i }, .align = ALIGN_RIGHT }); } break; } @@ -809,7 +815,10 @@ void draw_options_menu(MenuData *menu) { case BT_KeyBinding: { if(bind->blockinput) { r_color4(0.5, 1, 0.5, 1); - draw_text(AL_Right, origin, 20*i, "Press a key to assign, ESC to cancel", _fonts.standard); + text_draw("Press a key to assign, ESC to cancel", &(TextParams) { + .pos = { origin, 20*i }, + .align = ALIGN_RIGHT, + }); } else { const char *txt = SDL_GetScancodeName(config_get_int(bind->configentry)); @@ -817,12 +826,18 @@ void draw_options_menu(MenuData *menu) { txt = "Unknown"; } - draw_text(AL_Right, origin, 20*i, txt, _fonts.standard); + text_draw(txt, &(TextParams) { + .pos = { origin, 20*i }, + .align = ALIGN_RIGHT, + }); } if(!caption_drawn) { r_color4(1,1,1,0.7); - draw_text(AL_Center, (SCREEN_W - 200)/2, 20*(i-1), "Controls", _fonts.standard); + text_draw("Controls", &(TextParams) { + .pos = { (SCREEN_W - 200)/2, 20*(i-1) }, + .align = ALIGN_CENTER, + }); caption_drawn = 1; } break; @@ -846,13 +861,16 @@ void draw_options_menu(MenuData *menu) { if(bind->valrange_max > 0) { snprintf(buf, sizeof(buf), "#%i: %s", bind->selected + 1, gamepad_device_name(bind->selected)); - shorten_text_up_to_width(buf, (SCREEN_W - 220) / 2, _fonts.standard); + text_shorten(get_font("standard"), buf, (SCREEN_W - 220) / 2); txt = buf; } else { txt = "No devices available"; } - draw_text(AL_Right, origin, 20*i, txt, _fonts.standard); + text_draw(txt, &(TextParams) { + .pos = { origin, 20*i }, + .align = ALIGN_RIGHT, + }); } break; @@ -864,16 +882,25 @@ void draw_options_menu(MenuData *menu) { if(bind->blockinput) { r_color4(0.5, 1, 0.5, 1); - draw_text(AL_Right, origin, 20*i, - is_axis ? "Move an axis to assign, Back to cancel" - : "Press a button to assign, Back to cancel", - _fonts.standard); + char *text = is_axis ? "Move an axis to assign, Back to cancel" + : "Press a button to assign, Back to cancel"; + + text_draw(text, &(TextParams) { + .pos = { origin, 20*i }, + .align = ALIGN_RIGHT, + }); } else if(config_get_int(bind->configentry) >= 0) { int id = config_get_int(bind->configentry); const char *name = (is_axis ? gamepad_axis_name(id) : gamepad_button_name(id)); - draw_text(AL_Right, origin, 20*i, name, _fonts.standard); + text_draw(name, &(TextParams) { + .pos = { origin, 20*i }, + .align = ALIGN_RIGHT, + }); } else { - draw_text(AL_Right, origin, 20*i, "Unbound", _fonts.standard); + text_draw("Unbound", &(TextParams) { + .pos = { origin, 20*i }, + .align = ALIGN_RIGHT, + }); } break; } @@ -882,10 +909,16 @@ void draw_options_menu(MenuData *menu) { if(bind->blockinput) { r_color4(0.5, 1, 0.5, 1.0); if(*bind->strvalue) { - draw_text(AL_Right, origin, 20*i, bind->strvalue, _fonts.standard); + text_draw(bind->strvalue, &(TextParams) { + .pos = { origin, 20*i }, + .align = ALIGN_RIGHT, + }); } } else { - draw_text(AL_Right, origin, 20*i, config_get_str(bind->configentry), _fonts.standard); + text_draw(config_get_str(bind->configentry), &(TextParams) { + .pos = { origin, 20*i }, + .align = ALIGN_RIGHT, + }); } break; } @@ -903,8 +936,11 @@ void draw_options_menu(MenuData *menu) { h = m->height; } - snprintf(tmp, 16, "%dx%d", w, h); - draw_text(AL_Right, origin, 20*i, tmp, _fonts.standard); + snprintf(tmp, 16, "%d×%d", w, h); + text_draw(tmp, &(TextParams) { + .pos = { origin, 20*i }, + .align = ALIGN_RIGHT, + }); break; } @@ -926,7 +962,10 @@ void draw_options_menu(MenuData *menu) { r_mat_push(); r_mat_translate(origin - (w+cw) * 0.5, 20 * i, 0); - draw_text(AL_Right, -((w+cw) * 0.5 + 10), 0, tmp, _fonts.standard); + text_draw(tmp, &(TextParams) { + .pos = { -((w+cw) * 0.5 + 10), 0 }, + .align = ALIGN_RIGHT, + }); r_shader_standard_notex(); r_mat_push(); r_mat_scale(w+cw, h, 1); @@ -938,14 +977,14 @@ void draw_options_menu(MenuData *menu) { r_color4(0.9, 0.6, 0.2, alpha); r_draw_quad(); r_mat_pop(); - r_shader_standard(); + r_shader("text_default"); break; } } } } - + r_shader_standard(); r_mat_pop(); } diff --git a/src/menu/replayview.c b/src/menu/replayview.c index cc20aebe..7f18d7c7 100644 --- a/src/menu/replayview.c +++ b/src/menu/replayview.c @@ -163,7 +163,7 @@ static void replayview_draw_submenu_bg(float width, float height, float alpha) { r_color4(0.1, 0.1, 0.1, 0.7 * alpha); r_shader_standard_notex(); r_draw_quad(); - r_shader_standard(); + r_shader("text_default"); r_mat_pop(); } @@ -171,14 +171,14 @@ static void replayview_draw_messagebox(MenuData* m) { // this context is shared with the parent menu ReplayviewContext *ctx = m->context; float alpha = 1 - ctx->sub_fade; - float height = font_line_spacing(_fonts.standard) * 2; - float width = stringwidth(m->entries->name, _fonts.standard) + 64; + float height = font_get_lineskip(get_font("standard")) * 2; + float width = text_width(get_font("standard"), m->entries->name, 0) + 64; replayview_draw_submenu_bg(width, height, alpha); r_mat_push(); r_mat_translate(SCREEN_W*0.5, SCREEN_H*0.5, 0); r_color4(0.9, 0.6, 0.2, alpha); - draw_text(AL_Center, 0, 0, m->entries->name, _fonts.standard); + text_draw(m->entries->name, &(TextParams) { .align = ALIGN_CENTER }); r_mat_pop(); } @@ -205,7 +205,7 @@ static void replayview_draw_stagemenu(MenuData *m) { r_color4(0.9 + ia * 0.1, 0.6 + ia * 0.4, 0.2 + ia * 0.8, (0.7 + 0.3 * a) * alpha); } - draw_text(AL_Center, 0, 20*i, e->name, _fonts.standard); + text_draw(m->entries->name, &(TextParams) { .align = ALIGN_CENTER, .pos = { 0, 20*i } }); } r_mat_pop(); @@ -232,19 +232,19 @@ static void replayview_drawitem(void *n, int item, int cnt) { for(j = 0; j < i; ++j) o += sizes[j] * (SCREEN_W - 210)/columns; - Alignment a = AL_Center; + Alignment a = ALIGN_CENTER; // hell yeah, another loop-switch sequence switch(i) { case 0: - a = AL_Left; + a = ALIGN_LEFT; time_t t = rpy->stages[0].seed; struct tm* timeinfo = localtime(&t); strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M", timeinfo); break; case 1: - a = AL_Center; + a = ALIGN_CENTER; strlcpy(tmp, rpy->playername, sizeof(tmp)); break; @@ -266,7 +266,7 @@ static void replayview_drawitem(void *n, int item, int cnt) { break; case 4: - a = AL_Right; + a = ALIGN_RIGHT; if(rpy->numstages == 1) { StageInfo *stg = stage_get(rpy->stages[0].stage); @@ -281,15 +281,15 @@ static void replayview_drawitem(void *n, int item, int cnt) { break; } - shorten_text_up_to_width(tmp, csize, _fonts.standard); + text_shorten(get_font("standard"), tmp, csize); switch(a) { - case AL_Center: o += csize * 0.5 - stringwidth(tmp, _fonts.standard) * 0.5; break; - case AL_Right: o += csize - stringwidth(tmp, _fonts.standard); break; - default: break; + case ALIGN_CENTER: o += csize * 0.5 - text_width(get_font("standard"), tmp, 0) * 0.5; break; + case ALIGN_RIGHT: o += csize - text_width(get_font("standard"), tmp, 0); break; + default: break; } - draw_text(AL_Left, o + 10, 20*item, tmp, _fonts.standard); + text_draw(tmp, &(TextParams) { .pos = { o + 10, 20 * item } }); } } @@ -330,6 +330,7 @@ static void replayview_draw(MenuData *m) { ReplayviewContext *ctx = m->context; draw_options_menu_bg(m); + r_shader("text_default"); draw_menu_title(m, "Replays"); draw_menu_list(m, 100, 100, replayview_drawitem); @@ -337,6 +338,7 @@ static void replayview_draw(MenuData *m) { if(ctx->submenu) { ctx->submenu->draw(ctx->submenu); } + r_shader_standard(); } int replayview_cmp(const void *a, const void *b) { diff --git a/src/menu/savereplay.c b/src/menu/savereplay.c index e393c921..58f999c4 100644 --- a/src/menu/savereplay.c +++ b/src/menu/savereplay.c @@ -50,7 +50,11 @@ static void draw_saverpy_menu(MenuData *m) { r_mat_push(); r_color4(1, 1, 1, 1); r_mat_translate(SCREEN_W/2, SCREEN_H/2 - 100, 0); - draw_text(AL_Center, 0, 0, "Save Replay?", _fonts.mainmenu); + text_draw("Save Replay?", &(TextParams) { + .font = "big", + .align = ALIGN_CENTER, + .shader = "text_default", + }); r_mat_translate(0, 100, 0); for(int i = 0; i < m->ecount; i++) { @@ -64,8 +68,14 @@ static void draw_saverpy_menu(MenuData *m) { r_color4(0.9 + ia * 0.1, 0.6 + ia * 0.4, 0.2 + ia * 0.8, 0.7 + 0.3 * a); } - if(e->name) - draw_text(AL_Center, -50 + 100 * i, 0, e->name, _fonts.mainmenu); + if(e->name) { + text_draw(e->name, &(TextParams) { + .font = "big", + .align = ALIGN_CENTER, + .pos = { -50 + 100 * i, 0 }, + .shader = "text_default" + }); + } } r_mat_pop(); diff --git a/src/player.c b/src/player.c index 879f84b3..84fccf50 100644 --- a/src/player.c +++ b/src/player.c @@ -79,7 +79,7 @@ void player_free(Player *plr) { static void player_full_power(Player *plr) { play_sound("full_power"); stage_clear_hazards(CLEAR_HAZARDS_ALL); - stagetext_add("Full Power!", VIEWPORT_W * 0.5 + VIEWPORT_H * 0.33 * I, AL_Center, &_fonts.mainmenu, rgb(1, 1, 1), 0, 60, 20, 20); + stagetext_add("Full Power!", VIEWPORT_W * 0.5 + VIEWPORT_H * 0.33 * I, ALIGN_CENTER, get_font("big"), rgb(1, 1, 1), 0, 60, 20, 20); } bool player_set_power(Player *plr, short npow) { diff --git a/src/renderer/api.h b/src/renderer/api.h index 382a840e..5593a86f 100644 --- a/src/renderer/api.h +++ b/src/renderer/api.h @@ -49,6 +49,8 @@ typedef enum MatrixMode { typedef enum TextureType { TEX_TYPE_DEFAULT, + TEX_TYPE_R, + TEX_TYPE_RG, TEX_TYPE_RGB, TEX_TYPE_RGBA, TEX_TYPE_DEPTH, @@ -407,7 +409,7 @@ void r_texture_create(Texture *tex, const TextureParams *params) attr_nonnull(1, void r_texture_fill(Texture *tex, void *image_data) attr_nonnull(1, 2); void r_texture_fill_region(Texture *tex, uint x, uint y, uint w, uint h, void *image_data) attr_nonnull(1, 6); void r_texture_invalidate(Texture *tex) attr_nonnull(1); -void r_texture_replace(Texture *tex, TextureType type, uint w, uint h, void *image_data) attr_nonnull(1, 5); +void r_texture_replace(Texture *tex, TextureType type, uint w, uint h, void *image_data) attr_nonnull(1); void r_texture_destroy(Texture *tex) attr_nonnull(1); void r_texture_ptr(uint unit, Texture *tex); diff --git a/src/renderer/common/sprite_batch.c b/src/renderer/common/sprite_batch.c index 84285bec..88af3deb 100644 --- a/src/renderer/common/sprite_batch.c +++ b/src/renderer/common/sprite_batch.c @@ -14,8 +14,10 @@ typedef struct SpriteAttribs { mat4 transform; + mat4 tex_transform; float rgba[4]; FloatRect texrect; + float sprite_size[2]; float custom; } SpriteAttribs; @@ -43,6 +45,10 @@ static struct SpriteBatchState { } _r_sprite_batch; void _r_sprite_batch_init(void) { + #ifdef DEBUG + preload_resource(RES_FONT, "monotiny", RESF_PERMANENT); + #endif + size_t sz_vert = sizeof(GenericModelVertex); size_t sz_attr = sizeof(SpriteAttribs); @@ -51,18 +57,23 @@ void _r_sprite_batch_init(void) { VertexAttribFormat fmt[] = { // Per-vertex attributes (for the static models buffer, bound at 0) - { { 3, VA_FLOAT, VA_CONVERT_FLOAT, 0 }, sz_vert, VERTEX_OFS(position), 0 }, - { { 3, VA_FLOAT, VA_CONVERT_FLOAT, 0 }, sz_vert, VERTEX_OFS(normal), 0 }, - { { 2, VA_FLOAT, VA_CONVERT_FLOAT, 0 }, sz_vert, VERTEX_OFS(uv), 0 }, + { { 3, VA_FLOAT, VA_CONVERT_FLOAT, 0 }, sz_vert, VERTEX_OFS(position), 0 }, + { { 3, VA_FLOAT, VA_CONVERT_FLOAT, 0 }, sz_vert, VERTEX_OFS(normal), 0 }, + { { 2, VA_FLOAT, VA_CONVERT_FLOAT, 0 }, sz_vert, VERTEX_OFS(uv), 0 }, // Per-instance attributes (for our own sprites buffer, bound at 1) - { { 4, VA_FLOAT, VA_CONVERT_FLOAT, 1 }, sz_attr, INSTANCE_OFS(transform[0]), 1 }, - { { 4, VA_FLOAT, VA_CONVERT_FLOAT, 1 }, sz_attr, INSTANCE_OFS(transform[1]), 1 }, - { { 4, VA_FLOAT, VA_CONVERT_FLOAT, 1 }, sz_attr, INSTANCE_OFS(transform[2]), 1 }, - { { 4, VA_FLOAT, VA_CONVERT_FLOAT, 1 }, sz_attr, INSTANCE_OFS(transform[3]), 1 }, - { { 4, VA_FLOAT, VA_CONVERT_FLOAT, 1 }, sz_attr, INSTANCE_OFS(rgba), 1 }, - { { 4, VA_FLOAT, VA_CONVERT_FLOAT, 1 }, sz_attr, INSTANCE_OFS(texrect), 1 }, - { { 1, VA_FLOAT, VA_CONVERT_FLOAT, 1 }, sz_attr, INSTANCE_OFS(custom), 1 }, + { { 4, VA_FLOAT, VA_CONVERT_FLOAT, 1 }, sz_attr, INSTANCE_OFS(transform[0]), 1 }, + { { 4, VA_FLOAT, VA_CONVERT_FLOAT, 1 }, sz_attr, INSTANCE_OFS(transform[1]), 1 }, + { { 4, VA_FLOAT, VA_CONVERT_FLOAT, 1 }, sz_attr, INSTANCE_OFS(transform[2]), 1 }, + { { 4, VA_FLOAT, VA_CONVERT_FLOAT, 1 }, sz_attr, INSTANCE_OFS(transform[3]), 1 }, + { { 4, VA_FLOAT, VA_CONVERT_FLOAT, 1 }, sz_attr, INSTANCE_OFS(tex_transform[0]), 1 }, + { { 4, VA_FLOAT, VA_CONVERT_FLOAT, 1 }, sz_attr, INSTANCE_OFS(tex_transform[1]), 1 }, + { { 4, VA_FLOAT, VA_CONVERT_FLOAT, 1 }, sz_attr, INSTANCE_OFS(tex_transform[2]), 1 }, + { { 4, VA_FLOAT, VA_CONVERT_FLOAT, 1 }, sz_attr, INSTANCE_OFS(tex_transform[3]), 1 }, + { { 4, VA_FLOAT, VA_CONVERT_FLOAT, 1 }, sz_attr, INSTANCE_OFS(rgba), 1 }, + { { 4, VA_FLOAT, VA_CONVERT_FLOAT, 1 }, sz_attr, INSTANCE_OFS(texrect), 1 }, + { { 2, VA_FLOAT, VA_CONVERT_FLOAT, 1 }, sz_attr, INSTANCE_OFS(sprite_size), 1 }, + { { 1, VA_FLOAT, VA_CONVERT_FLOAT, 1 }, sz_attr, INSTANCE_OFS(custom), 1 }, }; #undef VERTEX_OFS @@ -171,6 +182,7 @@ void r_flush_sprites(void) { static void _r_sprite_batch_add(Sprite *spr, const SpriteParams *params, VertexBuffer *vbuf) { SpriteAttribs alignas(32) attribs; r_mat_current(MM_MODELVIEW, attribs.transform); + r_mat_current(MM_TEXTURE, attribs.tex_transform); float scale_x = params->scale.x ? params->scale.x : 1; float scale_y = params->scale.y ? params->scale.y : scale_x; @@ -213,6 +225,8 @@ static void _r_sprite_batch_add(Sprite *spr, const SpriteParams *params, VertexB attribs.texrect.h *= -1; } + attribs.sprite_size[0] = spr->w; + attribs.sprite_size[1] = spr->h; attribs.custom = params->custom; r_vertex_buffer_append(vbuf, sizeof(attribs), &attribs); @@ -321,24 +335,26 @@ void _r_sprite_batch_end_frame(void) { return; } - Color clr_saved = r_color_current(); - ShaderProgram *prog_saved = r_shader_current(); - r_shader_standard(); - r_color4(1, 1, 1, 1); + r_flush_sprites(); static char buf[512]; snprintf(buf, sizeof(buf), "%6i sprites %6i flushes %9.02f spr/flush %6i best %6i worst", _r_sprite_batch.frame_stats.sprites, _r_sprite_batch.frame_stats.flushes, - (double)_r_sprite_batch.frame_stats.sprites / _r_sprite_batch.frame_stats.flushes, + _r_sprite_batch.frame_stats.sprites / (double)_r_sprite_batch.frame_stats.flushes, _r_sprite_batch.frame_stats.best_batch, _r_sprite_batch.frame_stats.worst_batch ); - draw_text(AL_Left, 0, font_line_spacing(_fonts.monotiny), buf, _fonts.monotiny); + Font *font = get_font("monotiny"); + text_draw(buf, &(TextParams) { + .pos = { 0, font_get_lineskip(font) }, + .font_ptr = font, + .color = rgb(1, 1, 1), + .shader = "text_default", + }); + memset(&_r_sprite_batch.frame_stats, 0, sizeof(_r_sprite_batch.frame_stats)); - r_color(clr_saved); - r_shader_ptr(prog_saved); #endif } diff --git a/src/renderer/gl33/core.c b/src/renderer/gl33/core.c index eaf5fac0..07d69714 100644 --- a/src/renderer/gl33/core.c +++ b/src/renderer/gl33/core.c @@ -211,6 +211,7 @@ static void gl33_init_context(SDL_Window *window) { r_cull(CULL_BACK); r_blend(BLEND_ALPHA); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glPixelStorei(GL_PACK_ALIGNMENT, 1); glGetIntegerv(GL_VIEWPORT, &R.viewport.x); @@ -768,7 +769,7 @@ static Color gl33_clear_color_current(void) { static void gl33_viewport_rect(IntRect rect) { if(memcmp(&R.viewport, &rect, sizeof(IntRect))) { - r_flush_sprites(); + r_flush_sprites(); // FIXME: have the sprite batch track this instead. memcpy(&R.viewport, &rect, sizeof(IntRect)); glViewport(rect.x, rect.y, rect.w, rect.h); } diff --git a/src/renderer/gl33/shader_program.c b/src/renderer/gl33/shader_program.c index 83bfe550..78fff0c3 100644 --- a/src/renderer/gl33/shader_program.c +++ b/src/renderer/gl33/shader_program.c @@ -15,7 +15,7 @@ #include "../api.h" Uniform* gl33_shader_uniform(ShaderProgram *prog, const char *uniform_name) { - return hashtable_get_string(prog->uniforms, uniform_name); + return ht_get(&prog->uniforms, uniform_name, NULL); } UniformType gl33_uniform_type(Uniform *uniform) { @@ -112,9 +112,9 @@ static void gl33_commit_uniform(Uniform *uniform) { assert(!memcmp(uniform->cache.commited.data, uniform->cache.pending.data, uniform->cache.commited.size)); } -static void* gl33_sync_uniform(void *key, void *data, void *arg) { +static void* gl33_sync_uniform(const char *key, void *value, void *arg) { attr_unused const char *name = key; - Uniform *uniform = data; + Uniform *uniform = value; if(uniform->cache.update_count < 1) { return NULL; @@ -137,7 +137,7 @@ static void* gl33_sync_uniform(void *key, void *data, void *arg) { } void gl33_sync_uniforms(ShaderProgram *prog) { - hashtable_foreach(prog->uniforms, gl33_sync_uniform, NULL); + ht_foreach(&prog->uniforms, gl33_sync_uniform, NULL); } void gl33_uniform(Uniform *uniform, uint count, const void *data) { @@ -165,7 +165,7 @@ static bool cache_uniforms(ShaderProgram *prog) { int maxlen = 0; GLint unicount; - prog->uniforms = hashtable_new_stringkeys(); + ht_create(&prog->uniforms); glGetProgramiv(prog->gl_handle, GL_ACTIVE_UNIFORMS, &unicount); glGetProgramiv(prog->gl_handle, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxlen); @@ -217,7 +217,7 @@ static bool cache_uniforms(ShaderProgram *prog) { } uni.location = loc; - hashtable_set_string(prog->uniforms, name, memdup(&uni, sizeof(uni))); + ht_set(&prog->uniforms, name, memdup(&uni, sizeof(uni))); log_debug("%s = %i", name, loc); } @@ -315,7 +315,7 @@ static void print_info_log(GLuint prog) { } } -static void* free_uniform(void *key, void *data, void *arg) { +static void* free_uniform(const char *key, void *data, void *arg) { Uniform *uniform = data; free(uniform->cache.commited.data); free(uniform->cache.pending.data); @@ -327,8 +327,8 @@ static void unload_shader_program(void *vprog) { ShaderProgram *prog = vprog; gl33_shader_deleted(prog); glDeleteProgram(prog->gl_handle); - hashtable_foreach(prog->uniforms, free_uniform, NULL); - hashtable_free(prog->uniforms); + ht_foreach(&prog->uniforms, free_uniform, NULL); + ht_destroy(&prog->uniforms); free(prog); } diff --git a/src/renderer/gl33/shader_program.h b/src/renderer/gl33/shader_program.h index 121a26ab..18cb021f 100644 --- a/src/renderer/gl33/shader_program.h +++ b/src/renderer/gl33/shader_program.h @@ -18,7 +18,7 @@ struct ShaderProgram { GLuint gl_handle; - Hashtable *uniforms; + ht_str2ptr_t uniforms; }; typedef void (*UniformSetter)(Uniform *uniform, uint count, const void *data); diff --git a/src/renderer/gl33/texture.c b/src/renderer/gl33/texture.c index adc72924..d38c9183 100644 --- a/src/renderer/gl33/texture.c +++ b/src/renderer/gl33/texture.c @@ -39,7 +39,9 @@ static GLuint r_wrap_to_gl_wrap(TextureWrapMode mode) { GLTextureTypeInfo* gl33_texture_type_info(TextureType type) { static GLTextureTypeInfo map[] = { [TEX_TYPE_DEFAULT] = { GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, 4 }, - [TEX_TYPE_RGB] = { GL_RGB8, GL_RGBA, GL_UNSIGNED_BYTE, 3 }, + [TEX_TYPE_R] = { GL_R8, GL_RED, GL_UNSIGNED_BYTE, 1 }, + [TEX_TYPE_RG] = { GL_RG8, GL_RG, GL_UNSIGNED_BYTE, 2 }, + [TEX_TYPE_RGB] = { GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE, 3 }, [TEX_TYPE_RGBA] = { GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, 4 }, [TEX_TYPE_DEPTH] = { GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 1 }, }; diff --git a/src/renderer/gles20/core.c b/src/renderer/gles20/core.c index 578a1c70..6738aace 100644 --- a/src/renderer/gles20/core.c +++ b/src/renderer/gles20/core.c @@ -52,6 +52,8 @@ static void gles30_init(void) { GLTextureTypeInfo* gles20_texture_type_info(TextureType type) { static GLTextureTypeInfo map[] = { [TEX_TYPE_DEFAULT] = { GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, 4 }, + [TEX_TYPE_R] = { GL_RED, GL_RED, GL_UNSIGNED_BYTE, 1 }, + [TEX_TYPE_RG] = { GL_RG, GL_RG, GL_UNSIGNED_BYTE, 2 }, [TEX_TYPE_RGB] = { GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, 4 }, [TEX_TYPE_RGBA] = { GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, 4 }, [TEX_TYPE_DEPTH] = { GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, 2 }, diff --git a/src/resource/animation.c b/src/resource/animation.c index 9eebbe36..f215df4c 100644 --- a/src/resource/animation.c +++ b/src/resource/animation.c @@ -173,12 +173,12 @@ static bool animation_parse_callback(const char *key, const char *value, void *d return false; } - hashtable_set_string(ani->sequences,key,seq); + ht_set(&ani->sequences, key, seq); return true; } -static void *free_sequence_callback(void *key, void *data, void *arg) { - AniSequence *seq = (AniSequence *)data; +static void *free_sequence_callback(const char *key, void *data, void *arg) { + AniSequence *seq = data; free(seq->frames); free(seq); @@ -191,20 +191,20 @@ void* load_animation_begin(const char *filename, uint flags) { strcpy(name, basename); Animation *ani = calloc(1, sizeof(Animation)); - ani->sequences = hashtable_new_stringkeys(); + ht_create(&ani->sequences); if(!parse_keyvalue_file_cb(filename, animation_parse_callback, ani)) { - hashtable_foreach(ani->sequences, free_sequence_callback, NULL); - hashtable_free(ani->sequences); + ht_foreach(&ani->sequences, free_sequence_callback, NULL); + ht_destroy(&ani->sequences); free(ani); free(basename); return NULL; } if(ani->sprite_count <= 0) { - log_warn("Animation sprite count of '%s' must be positive integer?", name); - hashtable_foreach(ani->sequences, free_sequence_callback, NULL); - hashtable_free(ani->sequences); + log_warn("Animation sprite count of '%s', must be positive integer", name); + ht_foreach(&ani->sequences, free_sequence_callback, NULL); + ht_destroy(&ani->sequences); free(ani); free(basename); return NULL; @@ -216,18 +216,29 @@ void* load_animation_begin(const char *filename, uint flags) { return data; } -static void *check_sequence_frame_callback(void *key, void *value, void *arg) { - intptr_t sprite_count = (intptr_t)arg; +union check_sequence_frame_callback_arg { + int sprite_count, errors; +}; + +static void *check_sequence_frame_callback(const char *key, void *value, void *varg) { AniSequence *seq = value; + union check_sequence_frame_callback_arg *arg = varg; + int sprite_count = arg->sprite_count; int errors = 0; + for(int i = 0; i < seq->length; i++) { if(seq->frames[i].spriteidx >= sprite_count) { - log_warn("Animation sequence %s: Sprite index %d is higher than sprite_count.",(char *)key,seq->frames[i].spriteidx); + log_warn("Animation sequence %s: Sprite index %d is higher than sprite_count.", key, seq->frames[i].spriteidx); errors++; } } - return (void *)(intptr_t)errors; + if(errors) { + arg->errors = errors; + return arg; + } + + return NULL; } void* load_animation_end(void *opaque, const char *filename, uint flags) { @@ -256,7 +267,9 @@ void* load_animation_end(void *opaque, const char *filename, uint flags) { ani->sprites[i] = res->data; } - if(hashtable_foreach(ani->sequences, check_sequence_frame_callback, (void *)(intptr_t)ani->sprite_count) != 0) { + union check_sequence_frame_callback_arg arg = { ani->sprite_count }; + + if(ht_foreach(&ani->sequences, check_sequence_frame_callback, &arg) != NULL) { unload_animation(ani); ani = NULL; } @@ -269,8 +282,8 @@ void* load_animation_end(void *opaque, const char *filename, uint flags) { void unload_animation(void *vani) { Animation *ani = vani; - hashtable_foreach(ani->sequences, free_sequence_callback, NULL); - hashtable_free(ani->sequences); + ht_foreach(&ani->sequences, free_sequence_callback, NULL); + ht_destroy(&ani->sequences); free(ani->sprites); free(ani); } @@ -280,7 +293,8 @@ Animation *get_ani(const char *name) { } AniSequence *get_ani_sequence(Animation *ani, const char *seqname) { - AniSequence *seq = hashtable_get_string(ani->sequences,seqname); + AniSequence *seq = ht_get(&ani->sequences, seqname, NULL); + if(seq == NULL) { log_fatal("Sequence '%s' not found.",seqname); } diff --git a/src/resource/animation.h b/src/resource/animation.h index d28564e7..bf16f6fa 100644 --- a/src/resource/animation.h +++ b/src/resource/animation.h @@ -23,8 +23,7 @@ typedef struct AniSequence { } AniSequence; typedef struct Animation { - Hashtable *sequences; - + ht_str2ptr_t sequences; Sprite **sprites; Sprite transformed_sprite; // temporary sprite used to apply animation transformations. int sprite_count; diff --git a/src/resource/bgm_mixer.c b/src/resource/bgm_mixer.c index 8cac541d..c4eccfe9 100644 --- a/src/resource/bgm_mixer.c +++ b/src/resource/bgm_mixer.c @@ -52,29 +52,25 @@ void* load_bgm_begin(const char *path, uint flags) { mus->impl = imus; if(strendswith(path, ".bgm")) { - Hashtable *bgm = parse_keyvalue_file(path); + char *intro = NULL; + char *loop = NULL; - if(!bgm) { + if(!parse_keyvalue_file_with_spec(path, (KVSpec[]) { + { "artist" }, // don’t print a warning because this field is supposed to be here + { "intro", .out_str = &intro }, + { "loop", .out_str = &loop }, + { "title", .out_str = &mus->title }, + { "loop_point", .out_double = &imus->loop_point }, + { NULL } + })) { log_warn("Failed to parse bgm config '%s'", path); } else { - imus->intro = load_mix_music(hashtable_get_string(bgm, "intro")); - imus->loop = load_mix_music(hashtable_get_string(bgm, "loop")); - - char *loop_point = hashtable_get_string(bgm, "loop_point"); - - if(loop_point) { - imus->loop_point = strtod(loop_point, NULL); - } - - mus->title = hashtable_get_string(bgm, "title"); - - if(mus->title) { - mus->title = strdup(mus->title); - } - - hashtable_foreach(bgm, hashtable_iter_free_data, NULL); - hashtable_free(bgm); + imus->intro = load_mix_music(intro); + imus->loop = load_mix_music(loop); } + + free(intro); + free(loop); } else { imus->loop = load_mix_music(path); } diff --git a/src/resource/font.c b/src/resource/font.c index 6b7a10ec..5b918944 100644 --- a/src/resource/font.c +++ b/src/resource/font.c @@ -8,78 +8,143 @@ #include "taisei.h" +#include +#include FT_FREETYPE_H + #include "font.h" -#include "global.h" #include "util.h" -#include "objectpool.h" -#include "objectpool_util.h" +#include "util/rectpack.h" +#include "config.h" #include "video.h" +#include "events.h" +#include "renderer/api.h" -#define CACHE_EXPIRE_TIME 1000 +static void init_fonts(void); +static void post_init_fonts(void); +static void shutdown_fonts(void); +static char* font_path(const char*); +static bool check_font_path(const char*); +static void* load_font_begin(const char*, uint); +static void* load_font_end(void*, const char*, uint); +static void unload_font(void*); -#ifdef DEBUG -// #define VERBOSE_CACHE_LOG -#endif +ResourceHandler font_res_handler = { + .type = RES_FONT, + .typename = "font", + .subdir = FONT_PATH_PREFIX, -#ifdef VERBOSE_CACHE_LOG -#define CACHELOG(fmt, ...) log_debug(fmt, __VA_ARGS__) -#else -#define CACHELOG(fmt, ...) -#endif - -typedef struct CacheEntry { - OBJECT_INTERFACE(struct CacheEntry); - - SDL_Surface *surf; - int width; - int height; - uint32_t ref_time; - - struct { - // to simplify invalidation - Hashtable *ht; - char *ht_key; - } owner; -} CacheEntry; - -typedef struct FontRenderer { - Texture tex; - Sprite sprite; - float quality; -} FontRenderer; - -static ObjectPool *cache_pool; -static CacheEntry *cache_entries; -static FontRenderer font_renderer; - -struct Font { - TTF_Font *ttf; - Hashtable *cache; - char *source_path; - int base_size; + .procs = { + .init = init_fonts, + .post_init = post_init_fonts, + .shutdown = shutdown_fonts, + .find = font_path, + .check = check_font_path, + .begin_load = load_font_begin, + .end_load = load_font_end, + .unload = unload_font, + }, }; -struct Fonts _fonts; +// Handy routines for converting from fixed point +// From SDL_ttf +#define FT_FLOOR(X) (((X) & -64) / 64) +#define FT_CEIL(X) ((((X) + 63) & -64) / 64) -static void fontrenderer_init(float quality); -static void fontrenderer_free(void); -static void reload_fonts(float quality); +#undef FTERRORS_H_ +#define FT_ERRORDEF(e, v, s) { e, s }, +#undef FT_ERROR_START_LIST +#undef FT_ERROR_END_LIST + +static const struct ft_error_def { + FT_Error err_code; + const char *err_msg; +} ft_errors[] = { + #include FT_ERRORS_H + { 0, NULL } +}; + +static const char* ft_error_str(FT_Error err_code) { + for(const struct ft_error_def *e = ft_errors; e->err_msg; ++e) { + if(e->err_code == err_code) { + return e->err_msg; + } + } + + return "Unknown error"; +} + +typedef struct Glyph { + Sprite sprite; + GlyphMetrics metrics; + ulong ft_index; +} Glyph; + +typedef struct SpriteSheet { + LIST_INTERFACE(struct SpriteSheet); + Texture tex; + RectPack *rectpack; + uint glyphs; +} SpriteSheet; + +typedef LIST_ANCHOR(SpriteSheet) SpriteSheetAnchor; + +struct Font { + char *source_path; + Glyph *glyphs; + SpriteSheetAnchor spritesheets; + FT_Face face; + long base_face_idx; + int base_size; + uint glyphs_allocated; + uint glyphs_used; + ht_int2int_t charcodes_to_glyph_ofs; + ht_int2int_t ftindex_to_glyph_ofs; + FontMetrics metrics; +}; + +static struct { + FT_Library lib; + ShaderProgram *default_shader; + Texture render_tex; + RenderTarget render_buf; + + struct { + SDL_mutex *new_face; + SDL_mutex *done_face; + } mutex; +} globals; + +static double global_font_scale(void) { + return sanitize_scale(video.quality_factor * config_get_float(CONFIG_TEXT_QUALITY)); +} + +static void reload_fonts(double quality); static bool fonts_event(SDL_Event *event, void *arg) { - reload_fonts(video.quality_factor * config_get_float(CONFIG_TEXT_QUALITY)); + reload_fonts(global_font_scale()); return false; } static void fonts_quality_config_callback(ConfigIndex idx, ConfigValue v) { config_set_float(idx, v.f); - reload_fonts(video.quality_factor * v.f); + reload_fonts(global_font_scale()); +} + +static void try_create_mutex(SDL_mutex **mtx) { + if((*mtx = SDL_CreateMutex()) == NULL) { + log_sdl_error("SDL_CreateMutex"); + } } static void init_fonts(void) { - TTF_Init(); - memset(&font_renderer, 0, sizeof(font_renderer)); - cache_pool = OBJPOOL_ALLOC(CacheEntry, 512); - fontrenderer_init(video.quality_factor * config_get_float(CONFIG_TEXT_QUALITY)); + FT_Error err; + + try_create_mutex(&globals.mutex.new_face); + try_create_mutex(&globals.mutex.done_face); + + if((err = FT_Init_FreeType(&globals.lib))) { + log_fatal("FT_Init_FreeType() failed: %s", ft_error_str(err)); + } events_register_handler(&(EventHandler) { fonts_event, NULL, EPRIO_SYSTEM, MAKE_TAISEI_EVENT(TE_VIDEO_MODE_CHANGED) @@ -89,28 +154,34 @@ static void init_fonts(void) { preload_resources(RES_FONT, RESF_PERMANENT, "standard", - "big", - "small", - "hud", - "mono", - "monosmall", - "monotiny", NULL); - _fonts.standard = get_resource(RES_FONT, "standard", 0)->data; - _fonts.mainmenu = get_resource(RES_FONT, "big", 0)->data; - _fonts.small = get_resource(RES_FONT, "small", 0)->data; - _fonts.hud = get_resource(RES_FONT, "hud", 0)->data; - _fonts.mono = get_resource(RES_FONT, "mono", 0)->data; - _fonts.monosmall = get_resource(RES_FONT, "monosmall", 0)->data; - _fonts.monotiny = get_resource(RES_FONT, "monotiny", 0)->data; + // WARNING: Preloading the default shader here is unsafe. + + r_texture_create(&globals.render_tex, &(TextureParams) { + .filter = { TEX_FILTER_LINEAR, TEX_FILTER_LINEAR }, + .wrap = { TEX_WRAP_CLAMP, TEX_WRAP_CLAMP }, + .type = TEX_TYPE_R, + .stream = true, + .width = 1024, + .height = 1024, + }); + + r_target_create(&globals.render_buf); + r_target_attach(&globals.render_buf, &globals.render_tex, RENDERTARGET_ATTACHMENT_COLOR0); } -static void uninit_fonts(void) { +static void post_init_fonts(void) { + globals.default_shader = get_resource_data(RES_SHADER_PROGRAM, "text_default", RESF_PERMANENT | RESF_PRELOAD); +} + +static void shutdown_fonts(void) { + r_texture_destroy(&globals.render_tex); + r_target_destroy(&globals.render_buf); events_unregister_handler(fonts_event); - fontrenderer_free(); - TTF_Quit(); - objpool_free(cache_pool); + FT_Done_FreeType(globals.lib); + SDL_DestroyMutex(globals.mutex.new_face); + SDL_DestroyMutex(globals.mutex.done_face); } static char* font_path(const char *name) { @@ -121,28 +192,342 @@ bool check_font_path(const char *path) { return strstartswith(path, FONT_PATH_PREFIX) && strendswith(path, FONT_EXTENSION); } -static TTF_Font* load_ttf(char *vfspath, int size) { +static ulong ftstream_read(FT_Stream stream, ulong offset, uchar *buffer, ulong count) { + SDL_RWops *rwops = stream->descriptor.pointer; + ulong error = count ? 0 : 1; + + if(SDL_RWseek(rwops, offset, RW_SEEK_SET) < 0) { + log_warn("Can't seek in stream (%s)", (char*)stream->pathname.pointer); + return error; + } + + return SDL_RWread(rwops, buffer, 1, count); +} + +static void ftstream_close(FT_Stream stream) { + log_debug("%s", (char*)stream->pathname.pointer); + SDL_RWclose((SDL_RWops*)stream->descriptor.pointer); +} + +static FT_Error FT_Open_Face_Thread_Safe(FT_Library library, const FT_Open_Args *args, FT_Long face_index, FT_Face *aface) { + FT_Error err; + SDL_LockMutex(globals.mutex.new_face); + err = FT_Open_Face(library, args, face_index, aface); + SDL_UnlockMutex(globals.mutex.new_face); + return err; +} + +static FT_Error FT_Done_Face_Thread_Safe(FT_Face face) { + FT_Error err; + SDL_LockMutex(globals.mutex.done_face); + err = FT_Done_Face(face); + SDL_UnlockMutex(globals.mutex.done_face); + return err; +} + +static FT_Face load_font_face(char *vfspath, long index) { char *syspath = vfs_repr(vfspath, true); SDL_RWops *rwops = vfs_open(vfspath, VFS_MODE_READ | VFS_MODE_SEEKABLE); if(!rwops) { - log_fatal("VFS error: %s", vfs_get_error()); + log_warn("VFS error: %s", vfs_get_error()); + free(syspath); + return NULL; } - // XXX: what would be the best rounding strategy here? - size = rint(size * font_renderer.quality); + FT_Stream ftstream = calloc(1, sizeof(*ftstream)); + ftstream->descriptor.pointer = rwops; + ftstream->pathname.pointer = syspath; + ftstream->read = ftstream_read; + ftstream->close = ftstream_close; + ftstream->size = SDL_RWsize(rwops); - TTF_Font *f = TTF_OpenFontRW(rwops, true, size); + FT_Open_Args ftargs; + memset(&ftargs, 0, sizeof(ftargs)); + ftargs.flags = FT_OPEN_STREAM; + ftargs.stream = ftstream; - if(!f) { - log_warn("Failed to load TTF font '%s' @ %i: %s", syspath, size, TTF_GetError()); + FT_Face face; + FT_Error err; + + if((err = FT_Open_Face_Thread_Safe(globals.lib, &ftargs, index, &face))) { + log_warn("Failed to load font '%s' (face %li): FT_Open_Face() failed: %s", syspath, index, ft_error_str(err)); + free(syspath); + free(ftstream); + return NULL; } - log_info("Loaded '%s' @ %i", syspath, size); + if(!(FT_IS_SCALABLE(face))) { + log_warn("Font '%s' (face %li) is not scalable. This is not supported, sorry. Load aborted", syspath, index); + FT_Done_Face_Thread_Safe(face); + free(syspath); + free(ftstream); + return NULL; + } - free(syspath); - return f; + log_info("Loaded font '%s' (face %li)", syspath, index); + + return face; +} + +static FT_Error set_font_size(Font *fnt, uint pxsize, double scale) { + FT_Error err = FT_Err_Ok; + + assert(fnt != NULL); + assert(fnt->face != NULL); + + FT_Fixed fixed_scale = round(scale * (2 << 15)); + pxsize = FT_MulFix(pxsize * 64, fixed_scale); + + if((err = FT_Set_Char_Size(fnt->face, 0, pxsize, 0, 0))) { + log_warn("FT_Set_Char_Size(%u) failed: %s", pxsize, ft_error_str(err)); + return err; + } + + // Based on SDL_ttf + FT_Face face = fnt->face; + fixed_scale = face->size->metrics.y_scale; + fnt->metrics.ascent = FT_CEIL(FT_MulFix(face->ascender, fixed_scale)); + fnt->metrics.descent = FT_CEIL(FT_MulFix(face->descender, fixed_scale)); + fnt->metrics.max_glyph_height = fnt->metrics.ascent - fnt->metrics.descent; + fnt->metrics.lineskip = FT_CEIL(FT_MulFix(face->height, fixed_scale)); + fnt->metrics.scale = scale; + + return err; +} + +// TODO: Figure out sensible values for these; maybe make them depend on font size in some way. +#define SS_WIDTH 1024 +#define SS_HEIGHT 1024 + +static SpriteSheet* add_spritesheet(SpriteSheetAnchor *spritesheets) { + SpriteSheet *ss = calloc(1, sizeof(SpriteSheet)); + ss->rectpack = rectpack_new(SS_WIDTH, SS_HEIGHT); + + r_texture_create(&ss->tex, &(TextureParams) { + .width = SS_WIDTH, + .height = SS_HEIGHT, + .type = TEX_TYPE_R, + .filter.upscale = TEX_FILTER_LINEAR, + .filter.downscale = TEX_FILTER_LINEAR, + .wrap.s = TEX_WRAP_CLAMP, + .wrap.t = TEX_WRAP_CLAMP, + }); + + // FIXME: + // + // This code just zeroes the texture out. + // + // We should add an r_texture_clear function to hide this monstrosity. + // It would also allow a non-GL backend to have a different implementation, + // if we ever get around to writing one. + // + // To future generations: if such a function is already in the renderer API, + // but this crap is still here, please convert it. + RenderTarget *prev_target = r_target_current(); + RenderTarget atlast_rt; + Color cc_prev = r_clear_color_current(); + r_target_create(&atlast_rt); + r_target_attach(&atlast_rt, &ss->tex, RENDERTARGET_ATTACHMENT_COLOR0); + r_target(&atlast_rt); + r_clear_color4(0, 0, 0, 0); + r_clear(CLEAR_COLOR); + r_target(prev_target); + r_clear_color(cc_prev); + r_target_destroy(&atlast_rt); + + alist_append(spritesheets, ss); + return ss; +} + +// The padding is needed to prevent glyph edges from bleeding in due to linear filtering. +#define GLYPH_SPRITE_PADDING 1 + +static bool add_glyph_to_spritesheet(Font *font, Glyph *glyph, FT_Bitmap bitmap, SpriteSheet *ss) { + uint padded_w = bitmap.width + 2 * GLYPH_SPRITE_PADDING; + uint padded_h = bitmap.rows + 2 * GLYPH_SPRITE_PADDING; + Rect sprite_pos; + + if(!rectpack_add(ss->rectpack, padded_w, padded_h, &sprite_pos)) { + return false; + } + + complex ofs = GLYPH_SPRITE_PADDING * (1+I); + sprite_pos.bottom_right += ofs; + sprite_pos.top_left += ofs; + + r_texture_fill_region( + &ss->tex, + rect_x(sprite_pos), + rect_y(sprite_pos), + bitmap.width, + bitmap.rows, + bitmap.buffer + ); + + glyph->sprite.tex = &ss->tex; + glyph->sprite.w = glyph->metrics.width; // bitmap.width / font->scale; + glyph->sprite.h = glyph->metrics.height; // bitmap.rows / font->scale; + glyph->sprite.tex_area.x = rect_x(sprite_pos); + glyph->sprite.tex_area.y = rect_y(sprite_pos); + glyph->sprite.tex_area.w = bitmap.width; + glyph->sprite.tex_area.h = bitmap.rows; + + ++ss->glyphs; + + return true; +} + +static bool add_glyph_to_spritesheets(Font *font, Glyph *glyph, FT_Bitmap bitmap, SpriteSheetAnchor *spritesheets) { + bool result; + + for(SpriteSheet *ss = spritesheets->first; ss; ss = ss->next) { + if((result = add_glyph_to_spritesheet(font, glyph, bitmap, ss))) { + return result; + } + } + + return add_glyph_to_spritesheet(font, glyph, bitmap, add_spritesheet(spritesheets)); +} + +static const char *const pixmode_name(FT_Pixel_Mode mode) { + switch(mode) { + case FT_PIXEL_MODE_NONE : return "FT_PIXEL_MODE_NONE"; + case FT_PIXEL_MODE_MONO : return "FT_PIXEL_MODE_MONO"; + case FT_PIXEL_MODE_GRAY : return "FT_PIXEL_MODE_GRAY"; + case FT_PIXEL_MODE_GRAY2 : return "FT_PIXEL_MODE_GRAY2"; + case FT_PIXEL_MODE_GRAY4 : return "FT_PIXEL_MODE_GRAY4"; + case FT_PIXEL_MODE_LCD : return "FT_PIXEL_MODE_LCD"; + case FT_PIXEL_MODE_LCD_V : return "FT_PIXEL_MODE_LCD_V"; + // case FT_PIXEL_MODE_RGBA: return "FT_PIXEL_MODE_RGBA"; + default : return "FT_PIXEL_MODE_UNKNOWN"; + } +} + +static void delete_spritesheet(SpriteSheetAnchor *spritesheets, SpriteSheet *ss) { + r_texture_destroy(&ss->tex); + rectpack_free(ss->rectpack); + alist_unlink(spritesheets, ss); + free(ss); +} + +static Glyph* load_glyph(Font *font, FT_UInt gindex, SpriteSheetAnchor *spritesheets) { + log_debug("Loading glyph 0x%08x", gindex); + + if(++font->glyphs_used == font->glyphs_allocated) { + font->glyphs_allocated *= 2; + font->glyphs = realloc(font->glyphs, sizeof(Glyph) * font->glyphs_allocated); + } + + Glyph *glyph = font->glyphs + font->glyphs_used - 1; + + FT_Error err = FT_Load_Glyph(font->face, gindex, FT_LOAD_RENDER | FT_LOAD_TARGET_LIGHT); + + if(err) { + log_warn("FT_Load_Glyph(%u) failed: %s", gindex, ft_error_str(err)); + --font->glyphs_used; + return NULL; + } + + glyph->metrics.bearing_x = FT_FLOOR(font->face->glyph->metrics.horiBearingX); + glyph->metrics.bearing_y = FT_FLOOR(font->face->glyph->metrics.horiBearingY); + glyph->metrics.width = FT_CEIL(font->face->glyph->metrics.width); + glyph->metrics.height = FT_CEIL(font->face->glyph->metrics.height); + glyph->metrics.advance = FT_CEIL(font->face->glyph->metrics.horiAdvance); + + if(font->face->glyph->bitmap.buffer == NULL) { + // Some glyphs may be invisible, but we still need the metrics data for them (e.g. space) + memset(&glyph->sprite, 0, sizeof(Sprite)); + } else { + if(font->face->glyph->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY) { + log_warn( + "Glyph %u returned bitmap with pixel format %s. Only %s is supported, sorry. Ignoring", + gindex, + pixmode_name(font->face->glyph->bitmap.pixel_mode), + pixmode_name(FT_PIXEL_MODE_GRAY) + ); + --font->glyphs_used; + return NULL; + } + + if(!add_glyph_to_spritesheets(font, glyph, font->face->glyph->bitmap, spritesheets)) { + log_warn( + "Glyph %u can't fit into any spritesheets (padded bitmap size: %ux%u; max spritesheet size: %ux%u)", + gindex, + font->face->glyph->bitmap.width + 2 * GLYPH_SPRITE_PADDING, + font->face->glyph->bitmap.rows + 2 * GLYPH_SPRITE_PADDING, + SS_WIDTH, + SS_HEIGHT + ); + --font->glyphs_used; + return NULL; + } + } + + glyph->ft_index = gindex; + return glyph; +} + +static Glyph* get_glyph(Font *fnt, charcode_t cp) { + int64_t ofs; + + if(!ht_lookup(&fnt->charcodes_to_glyph_ofs, cp, &ofs)) { + Glyph *glyph; + uint ft_index = FT_Get_Char_Index(fnt->face, cp); + log_debug("Glyph for charcode 0x%08lx not cached", cp); + + if(ft_index == 0 && cp != UNICODE_UNKNOWN) { + log_debug("Font has no glyph for charcode 0x%08lx", cp); + glyph = get_glyph(fnt, UNICODE_UNKNOWN); + ofs = glyph ? (ptrdiff_t)(glyph - fnt->glyphs) : -1; + } else { + if(ht_lookup(&fnt->ftindex_to_glyph_ofs, ft_index, &ofs)) { + glyph = fnt->glyphs + ofs; + } else { + glyph = load_glyph(fnt, ft_index, &fnt->spritesheets); + ofs = glyph ? (ptrdiff_t)(glyph - fnt->glyphs) : -1; + ht_set(&fnt->ftindex_to_glyph_ofs, ft_index, ofs); + } + } + + ht_set(&fnt->charcodes_to_glyph_ofs, cp, ofs); + } + + return ofs < 0 ? NULL : fnt->glyphs + ofs; +} + +attr_nonnull(1) +static void wipe_glyph_cache(Font *font) { + ht_unset_all(&font->charcodes_to_glyph_ofs); + ht_unset_all(&font->ftindex_to_glyph_ofs); + + for(SpriteSheet *ss = font->spritesheets.first, *next; ss; ss = next) { + next = ss->next; + delete_spritesheet(&font->spritesheets, ss); + } + + font->glyphs_used = 0; +} + +static void free_font_resources(Font *font) { + if(font->face) { + FT_Stream stream = font->face->stream; + FT_Done_Face_Thread_Safe(font->face); + + if(stream) { + free(stream->pathname.pointer); + free(stream); + } + } + + wipe_glyph_cache(font); + + ht_destroy(&font->charcodes_to_glyph_ofs); + ht_destroy(&font->ftindex_to_glyph_ofs); + + free(font->source_path); + free(font->glyphs); } void* load_font_begin(const char *path, uint flags) { @@ -152,350 +537,499 @@ void* load_font_begin(const char *path, uint flags) { if(!parse_keyvalue_file_with_spec(path, (KVSpec[]){ { "source", .out_str = &font.source_path }, { "size", .out_int = &font.base_size }, + { "face", .out_long = &font.base_face_idx }, { NULL } })) { log_warn("Failed to parse font file '%s'", path); return NULL; } - font.ttf = load_ttf(font.source_path, font.base_size); + ht_create(&font.charcodes_to_glyph_ofs); + ht_create(&font.ftindex_to_glyph_ofs); - if(!font.ttf) { - free(font.source_path); + if(!(font.face = load_font_face(font.source_path, font.base_face_idx))) { + free_font_resources(&font); return NULL; } - font.cache = hashtable_new_stringkeys(); + if(set_font_size(&font, font.base_size, global_font_scale())) { + free_font_resources(&font); + return NULL; + } + + font.glyphs_allocated = 32; + font.glyphs = calloc(font.glyphs_allocated, sizeof(Glyph)); return memdup(&font, sizeof(font)); } void* load_font_end(void *opaque, const char *path, uint flags) { - return opaque; -} + Font *font = opaque; -static void free_font(Font *font); + if(font == NULL) { + return NULL; + } + + return font; +} void unload_font(void *vfont) { - free_font(vfont); + free_font_resources(vfont); + free(vfont); } -ResourceHandler font_res_handler = { - .type = RES_FONT, - .typename = "font", - .subdir = FONT_PATH_PREFIX, - - .procs = { - .init = init_fonts, - .shutdown = uninit_fonts, - .find = font_path, - .check = check_font_path, - .begin_load = load_font_begin, - .end_load = load_font_end, - .unload = unload_font, - }, +struct rlfonts_arg { + double quality; }; -static void free_cache_entry(CacheEntry *e) { - if(!e) { - return; - } - - if(e->surf) { - SDL_FreeSurface(e->surf); - } - - CACHELOG("Wiping cache entry %p [%s]", (void*)e, e->owner.ht_key); - - free(e->owner.ht_key); - list_unlink(&cache_entries, e); - objpool_release(cache_pool, (ObjectInterface*)e); -} - -static CacheEntry* get_cache_entry(Font *font, const char *text) { - CacheEntry *e = hashtable_get_unsafe(font->cache, (void*)text); - - if(!e) { - if(objpool_is_full(cache_pool)) { - CacheEntry *oldest = cache_entries; - - for(CacheEntry *e = cache_entries->next; e; e = e->next) { - if(e->ref_time < oldest->ref_time) { - oldest = e; - } - } - - hashtable_unset_string(oldest->owner.ht, oldest->owner.ht_key); - free_cache_entry(oldest); - } - - e = (CacheEntry*)objpool_acquire(cache_pool); - list_push(&cache_entries, e); - hashtable_set_string(font->cache, text, e); - e->owner.ht = font->cache; - e->owner.ht_key = strdup(text); - - CACHELOG("New entry for text: [%s]", text); - } - - e->ref_time = SDL_GetTicks(); - return e; -} - -void update_font_cache(void) { - uint32_t now = SDL_GetTicks(); - - CacheEntry *next; - for(CacheEntry *e = cache_entries; e; e = next) { - next = e->next; - - if(now - e->ref_time > CACHE_EXPIRE_TIME) { - hashtable_unset(e->owner.ht, e->owner.ht_key); - free_cache_entry(e); - } - } -} - -static void fontrenderer_init_tex(Texture *tex) { - r_texture_create(tex, &(TextureParams) { - .type = TEX_TYPE_RGBA, - .filter = { - .upscale = TEX_FILTER_LINEAR, - .downscale = TEX_FILTER_LINEAR, - }, - .wrap = { - .s = TEX_WRAP_CLAMP, - .t = TEX_WRAP_CLAMP, - }, - .width = 1, - .height = 1, - .stream = true, - }); -} - -static void fontrenderer_init(float quality) { - font_renderer.quality = quality = sanitize_scale(quality); - fontrenderer_init_tex(&font_renderer.tex); - font_renderer.sprite.tex = &font_renderer.tex; -} - -static void fontrenderer_free(void) { - r_texture_destroy(&font_renderer.tex); -} - -static void fontrenderer_upload(SDL_Surface *surf) { - assert(surf != NULL); - - r_texture_replace(&font_renderer.tex, font_renderer.tex.type, surf->w, surf->h, surf->pixels); - - font_renderer.sprite.tex_area.w = surf->w; - font_renderer.sprite.tex_area.h = surf->h; - font_renderer.sprite.w = font_renderer.sprite.tex_area.w / font_renderer.quality; - font_renderer.sprite.h = font_renderer.sprite.tex_area.h / font_renderer.quality; -} - -static SDL_Surface* fontrender_render(const char *text, Font *font) { - CacheEntry *e = get_cache_entry(font, text); - SDL_Surface *surf = e->surf; - - if(surf) { - return surf; - } - - CACHELOG("Rendering text: [%s]", text); - surf = e->surf = TTF_RenderUTF8_Blended(font->ttf, text, (SDL_Color){255, 255, 255}); - - if(!surf) { - log_fatal("TTF_RenderUTF8_Blended() failed: %s", TTF_GetError()); - } - - return surf; -} - -static void free_font_cache(Hashtable *cache) { - CacheEntry *e; - - for(HashtableIterator *i = hashtable_iter(cache); hashtable_iter_next(i, 0, (void**)&e);) { - free_cache_entry(e); - } -} - -static void free_font(Font *font) { - TTF_CloseFont(font->ttf); - free_font_cache(font->cache); - hashtable_free(font->cache); - free(font->source_path); - free(font); -} - -static void reload_font(Font *font) { - TTF_CloseFont(font->ttf); - free_font_cache(font->cache); - hashtable_unset_all(font->cache); - font->ttf = load_ttf(font->source_path, font->base_size); +attr_nonnull(1) +static void reload_font(Font *font, double quality) { + wipe_glyph_cache(font); + set_font_size(font, font->base_size, quality); } static void* reload_font_callback(const char *name, Resource *res, void *varg) { - reload_font((Font*)res->data); + struct rlfonts_arg *a = varg; + reload_font((Font*)res->data, a->quality); return NULL; } -static void reload_fonts(float quality) { - if(font_renderer.quality == sanitize_scale(quality)) { - return; - } - - fontrenderer_free(); - fontrenderer_init(quality); - resource_for_each(RES_FONT, reload_font_callback, NULL); +static void reload_fonts(double quality) { + resource_for_each(RES_FONT, reload_font_callback, &(struct rlfonts_arg) { quality }); } -static void draw_text_line(Alignment align, float x, float y, const char *text, Font *font) { - float m = 1.0 / font_renderer.quality; - bool adjust = !(align & AL_Flag_NoAdjust); - align &= 0xf; +static inline int apply_kerning(Font *font, uint prev_index, Glyph *gthis) { + FT_Vector kvec; - fontrenderer_upload(fontrender_render(text, font)); + if(!FT_Get_Kerning(font->face, prev_index, gthis->ft_index, FT_KERNING_DEFAULT, &kvec)) { + return kvec.x >> 6; + } - // XXX: all of these hacks are probably broken + return 0; +} - int w = font_renderer.sprite.tex_area.w; - int h = font_renderer.sprite.tex_area.h; +int text_width_raw(Font *font, const char *text, uint maxlines) { + const char *tptr = text; + uint prev_glyph_idx = 0; + bool keming = FT_HAS_KERNING(font->face); + uint numlines = 0; + int x = 0; + int width = 0; - if(adjust) { - switch(align) { - case AL_Center: + while(*tptr) { + uint32_t uchar = utf8_getch(&tptr); + + if(uchar == '\n') { + if(++numlines == maxlines) { break; - // w/2 is integer division and must be done first - case AL_Left: - x += m*(w/2); - break; - case AL_Right: - x -= m*(w/2); - break; - default: - log_fatal("Invalid alignment %x", align); + } + + if(x > width) { + width = x; + } + + x = 0; + continue; } - // if textures are odd pixeled, align them for ideal sharpness. + Glyph *glyph = get_glyph(font, uchar); - if(w&1) { - x += 0.5; + if(glyph == NULL) { + continue; } - if(h&1) { - y += 0.5; + if(keming && prev_glyph_idx) { + x += apply_kerning(font, prev_glyph_idx, glyph); } - } else { - switch(align) { - case AL_Center: + + x += glyph->metrics.advance; + prev_glyph_idx = glyph->ft_index; + } + + if(x > width) { + width = x; + } + + return width; +} + +void text_bbox(Font *font, const char *text, uint maxlines, BBox *bbox) { + const char *tptr = text; + uint prev_glyph_idx = 0; + bool keming = FT_HAS_KERNING(font->face); + uint numlines = 0; + + memset(bbox, 0, sizeof(*bbox)); + int x = 0, y = 0; + + while(*tptr) { + uint32_t uchar = utf8_getch(&tptr); + + if(uchar == '\n') { + if(++numlines == maxlines) { break; - case AL_Left: - x += m*(w/2.0); - break; - case AL_Right: - x -= m*(w/2.0); - break; - default: - log_fatal("Invalid alignment %x", align); + } + + x = 0; + y += font->metrics.lineskip; + + continue; + } + + Glyph *glyph = get_glyph(font, uchar); + + if(glyph == NULL) { + continue; + } + + if(keming && prev_glyph_idx) { + x += apply_kerning(font, prev_glyph_idx, glyph); + } + + int g_x0 = x + glyph->metrics.bearing_x; + int g_x1 = g_x0 + glyph->metrics.width; + + bbox->x.max = max(bbox->x.max, g_x0); + bbox->x.max = max(bbox->x.max, g_x1); + bbox->x.min = min(bbox->x.min, g_x0); + bbox->x.min = min(bbox->x.min, g_x1); + + int g_y0 = y - glyph->metrics.bearing_y; + int g_y1 = g_y0 + glyph->metrics.height; + + bbox->y.max = max(bbox->y.max, g_y0); + bbox->y.max = max(bbox->y.max, g_y1); + bbox->y.min = min(bbox->y.min, g_y0); + bbox->y.min = min(bbox->y.min, g_y1); + + prev_glyph_idx = glyph->ft_index; + x += glyph->metrics.advance; + } +} + +double text_width(Font *font, const char *text, uint maxlines) { + return text_width_raw(font, text, maxlines) / font->metrics.scale; +} + +int text_height_raw(Font *font, const char *text, uint maxlines) { + // FIXME: I'm not sure this is correct. Perhaps it should consider max_glyph_height at least? + + uint text_lines = 1; + const char *tptr = text; + + while((tptr = strchr(tptr, '\n'))) { + if(text_lines++ == maxlines) { + break; } } - draw_sprite_p(x, y, &font_renderer.sprite); + return font->metrics.lineskip * text_lines; } -void draw_text(Alignment align, float x, float y, const char *text, Font *font) { - assert(text != NULL); - - if(!*text) { - return; - } - - char *nl; - char *buf = malloc(strlen(text)+1); - strcpy(buf, text); - - if((nl = strchr(buf, '\n')) != NULL && strlen(nl) > 1) { - draw_text(align, x, y + 20, nl+1, font); - *nl = '\0'; - } - - draw_text_line(align, x, y, buf, font); - free(buf); +double text_height(Font *font, const char *text, uint maxlines) { + return text_height_raw(font, text, maxlines) / font->metrics.scale; } -void render_text(const char *text, Font *font, Sprite *out_spr) { - fontrenderer_upload(fontrender_render(text, font)); - memcpy(out_spr, &font_renderer.sprite, sizeof(Sprite)); -} +static inline void adjust_xpos(Font *font, const char *text, Alignment align, double x_orig, double *x) { + double line_width; -void draw_text_auto_wrapped(Alignment align, float x, float y, const char *text, int width, Font *font) { - char buf[strlen(text) * 2]; - wrap_text(buf, sizeof(buf), text, width, font); - draw_text(align, x, y, buf, font); -} + switch(align) { + case ALIGN_LEFT: { + *x = x_orig; + break; + } -static void string_dimensions(char *s, Font *font, int *w, int *h) { - CacheEntry *e = get_cache_entry(font, s); + case ALIGN_RIGHT: { + line_width = text_width_raw(font, text, 1); + *x = x_orig - line_width; + break; + } - if(e->width <= 0 || e->height <= 0) { - TTF_SizeUTF8(font->ttf, s, &e->width, &e->height); - CACHELOG("Got size %ix%i for text: [%s]", e->width, e->height, s); - } - - if(w) { - *w = e->width; - } - - if(h) { - *h = e->height; + case ALIGN_CENTER: { + line_width = text_width_raw(font, text, 1); + *x = x_orig - line_width * 0.5; + break; + } } } -int stringwidth(char *s, Font *font) { - int w; - string_dimensions(s, font, &w, NULL); - return w / font_renderer.quality; +Font* get_font(const char *font) { + return get_resource_data(RES_FONT, font, RESF_DEFAULT); } -int stringheight(char *s, Font *font) { - int h; - string_dimensions(s, font, NULL, &h); - return h / font_renderer.quality; +ShaderProgram* text_get_default_shader(void) { + return globals.default_shader; } -int charwidth(char c, Font *font) { - char s[2]; - s[0] = c; - s[1] = 0; - return stringwidth(s, font); +// #define TEXT_DRAW_BBOX + +attr_returns_nonnull +static Font* font_from_params(const TextParams *params) { + Font *font = params->font_ptr; + + if(font == NULL) { + if(params->font != NULL) { + font = get_font(params->font); + } else { + font = get_font("standard"); + } + } + + assert(font != NULL); + return font; } -int font_line_spacing(Font *font) { - return TTF_FontLineSkip(font->ttf) / font_renderer.quality; +attr_nonnull(1, 2, 3) +static double _text_draw(Font *font, const char *text, const TextParams *params) { + SpriteParams sp = { .sprite = NULL }; + BBox bbox; + double x = params->pos.x; + double y = params->pos.y; + double iscale = 1 / font->metrics.scale; + + text_bbox(font, text, 0, &bbox); + sp.shader_ptr = params->shader_ptr; + + if(sp.shader_ptr == NULL) { + // don't defer this to r_draw_sprite; we don't want to call r_shader_get more than necessary. + + if(params->shader != NULL) { + sp.shader_ptr = r_shader_get(params->shader); + } else { + sp.shader_ptr = r_shader_current(); + } + } + + sp.color = params->color; + sp.blend = params->blend; + sp.custom = params->custom; + + if(sp.color == 0) { + // XXX: sprite batch code defaults this to rgb(1, 1, 1) + sp.color = r_color_current(); + } + + MatrixMode mm_prev = r_mat_mode_current(); + r_mat_mode(MM_MODELVIEW); + r_mat_push(); + r_mat_translate(x, y, 0); + r_mat_scale(iscale, iscale, 1); + x = y = 0; + + double x_orig = x; + adjust_xpos(font, text, params->align, x_orig, &x); + + double bbox_w = bbox.x.max - bbox.x.min; + double bbox_h = bbox.y.max - bbox.y.min; + + #ifdef TEXT_DRAW_BBOX + // TODO: align this correctly in the multi-line case + double bbox_x_mid = x + bbox.x.min + bbox_w * 0.5; + double bbox_y_mid = y + bbox.y.min - font->metrics.descent + bbox_h * 0.5; + + r_state_push(); + r_shader_standard_notex(); + r_mat_push(); + r_mat_translate(bbox_x_mid, bbox_y_mid, 0); + r_mat_scale(bbox_w, bbox_h, 0); + r_color(multiply_colors(rgba(1, 1, 1, 0.5), r_color_current())); + r_draw_quad(); + r_mat_pop(); + r_state_pop(); + #endif + + r_mat_mode(MM_TEXTURE); + r_mat_push(); + r_mat_translate(0, 0.0, 0); + r_mat_scale(1/bbox_w, 1/bbox_h, 1.0); + r_mat_translate(-bbox.x.min - (x - x_orig), -bbox.y.min + font->metrics.descent, 0); + + bool keming = FT_HAS_KERNING(font->face); + uint prev_glyph_idx = 0; + const char *tptr = text; + + while(*tptr) { + uint32_t uchar = utf8_getch(&tptr); + + if(uchar == '\n') { + adjust_xpos(font, tptr, params->align, x_orig, &x); + y += font->metrics.lineskip; + continue; + } + + Glyph *glyph = get_glyph(font, uchar); + + if(glyph == NULL) { + continue; + } + + if(keming && prev_glyph_idx) { + x += apply_kerning(font, prev_glyph_idx, glyph); + } + + if(glyph->sprite.tex != NULL) { + sp.sprite_ptr = &glyph->sprite; + sp.pos.x = x + glyph->metrics.bearing_x + glyph->sprite.w * 0.5; + sp.pos.y = y - glyph->metrics.bearing_y + glyph->sprite.h * 0.5 - font->metrics.descent; + + // HACK/FIXME: Glyphs have their sprite w/h unadjusted for scale. + // We have to temporarily fix that up here so that the shader gets resolution-independent dimensions. + float w_saved = sp.sprite_ptr->w; + float h_saved = sp.sprite_ptr->h; + sp.sprite_ptr->w /= font->metrics.scale; + sp.sprite_ptr->h /= font->metrics.scale; + sp.scale.both = font->metrics.scale; + + r_mat_push(); + r_mat_translate(sp.pos.x - x_orig, sp.pos.y, 0); + r_mat_scale(w_saved, h_saved, 1.0); + r_mat_translate(-0.5, -0.5, 0); + + if(params->glyph_callback.func != NULL) { + params->glyph_callback.func(font, uchar, &sp, params->glyph_callback.userdata); + } + + r_draw_sprite(&sp); + + // HACK/FIXME: See above. + sp.sprite_ptr->w = w_saved; + sp.sprite_ptr->h = h_saved; + + r_mat_pop(); + } + + x += glyph->metrics.advance; + prev_glyph_idx = glyph->ft_index; + } + + r_mat_pop(); + r_mat_mode(MM_MODELVIEW); + r_mat_pop(); + r_mat_mode(mm_prev); + + return x_orig + (x - x_orig) / font->metrics.scale; } -void shorten_text_up_to_width(char *s, float width, Font *font) { - while(stringwidth(s, font) > width) { - int l = strlen(s); +double text_draw(const char *text, const TextParams *params) { + return _text_draw(font_from_params(params), text, params); +} + +double text_draw_wrapped(const char *text, double max_width, const TextParams *params) { + Font *font = font_from_params(params); + char buf[strlen(text) * 2 + 1]; + text_wrap(font, text, max_width, buf, sizeof(buf)); + return _text_draw(font, buf, params); +} + +void text_render(const char *text, Font *font, Sprite *out_sprite, BBox *out_bbox) { + text_bbox(font, text, 0, out_bbox); + + int bbox_width = out_bbox->x.max - out_bbox->x.min; + int bbox_height = out_bbox->y.max - out_bbox->y.min; + + if(bbox_height < font->metrics.max_glyph_height) { + out_bbox->y.min -= font->metrics.max_glyph_height - bbox_height; + bbox_height = out_bbox->y.max - out_bbox->y.min; + } + + Texture *tex = &globals.render_tex; + + int tex_new_w = bbox_width; // max(tex->w, bbox_width); + int tex_new_h = bbox_height; // max(tex->h, bbox_height); + + if(tex_new_w != tex->w || tex_new_h != tex->h) { + log_info( + "Resizing texture: %ix%i --> %ix%i", + tex->w, tex->h, + tex_new_w, tex_new_h + ); + + r_texture_replace(tex, TEX_TYPE_R, tex_new_w, tex_new_h, NULL); + } + + r_state_push(); + + r_target(&globals.render_buf); + r_clear_color4(0, 0, 0, 0); + r_clear(CLEAR_COLOR); + + r_blend(BLEND_ALPHA); + r_enable(RCAP_CULL_FACE); + r_cull(CULL_FRONT); + r_disable(RCAP_DEPTH_TEST); + + r_mat_mode(MM_MODELVIEW); + r_mat_push(); + r_mat_identity(); + + r_mat_mode(MM_PROJECTION); + r_mat_push(); + r_mat_identity(); + // XXX: y-flipped because that's how our textures are... + r_mat_ortho(0, tex->w, 0, tex->h, -100, 100); + r_viewport(0, 0, tex->w, tex->h); + + r_mat_mode(MM_TEXTURE); + r_mat_push(); + r_mat_identity(); + + // HACK: Coordinates are in texel space, font scale must not be used. + // This probably should be exposed in the text_draw API. + double fontscale = font->metrics.scale; + font->metrics.scale = 1; + + text_draw(text, &(TextParams) { + .font_ptr = font, + .pos = { -out_bbox->x.min, -out_bbox->y.min + font->metrics.descent }, + .color = rgb(1, 1, 1), + .shader = "text_default", + }); + + font->metrics.scale = fontscale; + r_flush_sprites(); + + // r_mat_mode(MM_TEXTURE); + r_mat_pop(); + + r_mat_mode(MM_PROJECTION); + r_mat_pop(); + + r_mat_mode(MM_MODELVIEW); + r_mat_pop(); + + r_state_pop(); + + out_sprite->tex = tex; + out_sprite->tex_area.w = bbox_width; + out_sprite->tex_area.h = bbox_height; + out_sprite->tex_area.x = 0; + out_sprite->tex_area.y = 0; + out_sprite->w = bbox_width / font->metrics.scale; + out_sprite->h = bbox_height / font->metrics.scale; +} + +void text_shorten(Font *font, char *text, double width) { + // TODO: rewrite this to use utf8_getch + + assert(!strchr(text, '\n')); + + while(text_width(font, text, 0) > width) { + int l = strlen(text); if(l <= 1) { return; } --l; - s[l] = 0; + text[l] = 0; for(int i = 0; i < min(3, l); ++i) { - s[l - i - 1] = '.'; + text[l - i - 1] = '.'; } } } -void wrap_text(char *buf, size_t bufsize, const char *src, int width, Font *font) { - assert(buf != NULL); - assert(src != NULL); - assert(font != NULL); +void text_wrap(Font *font, const char *src, double width, char *buf, size_t bufsize) { assert(bufsize > strlen(src) + 1); assert(width > 0); @@ -515,7 +1049,7 @@ void wrap_text(char *buf, size_t bufsize, const char *src, int width, Font *font } if(*curline) { - curwidth = stringwidth(curline, font); + curwidth = text_width(font, curline, 0); } else { curwidth = 0; } @@ -525,14 +1059,14 @@ void wrap_text(char *buf, size_t bufsize, const char *src, int width, Font *font strcat(tmpbuf, " "); strcat(tmpbuf, next); - int totalwidth = stringwidth(tmpbuf, font); + double totalwidth = text_width(font, tmpbuf, 0); if(totalwidth > width) { if(curwidth == 0) { log_fatal( "Single word '%s' won't fit on one line. " - "Word width: %i, max width: %i, source string: %s", - next, stringwidth(next, font), width, src + "Word width: %g, max width: %g, source string: %s", + next, text_width(font, tmpbuf, 0), width, src ); } @@ -542,9 +1076,26 @@ void wrap_text(char *buf, size_t bufsize, const char *src, int width, Font *font if(*curline) { strlcat(buf, " ", bufsize); } - } strlcat(buf, next, bufsize); } } + +const FontMetrics* font_get_metrics(Font *font) { + return &font->metrics; +} + +double font_get_lineskip(Font *font) { + return font->metrics.lineskip / font->metrics.scale; +} + +const GlyphMetrics* font_get_char_metrics(Font *font, charcode_t c) { + Glyph *g = get_glyph(font, c); + + if(!g) { + return NULL; + } + + return &g->metrics; +} diff --git a/src/resource/font.h b/src/resource/font.h index 6d84107f..f7ebb4a3 100644 --- a/src/resource/font.h +++ b/src/resource/font.h @@ -9,52 +9,111 @@ #pragma once #include "taisei.h" -#include - #include "sprite.h" #include "hashtable.h" #include "resource.h" +#include "renderer/api.h" typedef enum { - AL_Center, - AL_Left, - AL_Right + ALIGN_LEFT = 0, // must be 0 + ALIGN_CENTER, + ALIGN_RIGHT, } Alignment; -enum { - AL_Flag_NoAdjust = 0x10, -}; - +typedef ulong charcode_t; typedef struct Font Font; -void draw_text(Alignment align, float x, float y, const char *text, Font *font); -void draw_text_auto_wrapped(Alignment align, float x, float y, const char *text, int width, Font *font); -void render_text(const char *text, Font *font, Sprite *out_spr); +typedef struct FontMetrics { + int ascent; + int descent; + int max_glyph_height; + int lineskip; + double scale; +} FontMetrics; -int stringwidth(char *s, Font *font); -int stringheight(char *s, Font *font); -int charwidth(char c, Font *font); +typedef struct GlyphMetrics { + int bearing_x; + int bearing_y; + int width; + int height; + int advance; +} GlyphMetrics; -int font_line_spacing(Font *font); +// TODO: maybe move this into util/geometry.h +typedef struct BBox { + struct { + int min; + int max; + } x; -void shorten_text_up_to_width(char *s, float width, Font *font); -void wrap_text(char *buf, size_t bufsize, const char *src, int width, Font *font); + struct { + int min; + int max; + } y; +} BBox; -extern struct Fonts { - union { - struct { - Font *standard; - Font *mainmenu; - Font *small; - Font *hud; - Font *mono; - Font *monosmall; - Font *monotiny; - }; +typedef void (*GlyphDrawCallback)(Font *font, charcode_t charcode, SpriteParams *spr_params, void *userdata); - Font *first; - }; -} _fonts; +typedef struct TextParams { + const char *font; + Font *font_ptr; + const char *shader; + ShaderProgram *shader_ptr; + struct { + GlyphDrawCallback func; + void *userdata; + } glyph_callback; + struct { double x, y; } pos; + Color color; + float custom; + BlendMode blend; + Alignment align; +} TextParams; + +Font* get_font(const char *font) + attr_nonnull(1); + +ShaderProgram* text_get_default_shader(void) + attr_returns_nonnull; + +const FontMetrics* font_get_metrics(Font *font) + attr_nonnull(1) attr_returns_nonnull; + +double font_get_lineskip(Font *font) + attr_nonnull(1); + +const GlyphMetrics* font_get_char_metrics(Font *font, charcode_t c) + attr_nonnull(1); + +double text_draw(const char *text, const TextParams *params) + attr_nonnull(1, 2); + +double text_draw_wrapped(const char *text, double max_width, const TextParams *params) + attr_nonnull(1, 3); + +void text_render(const char *text, Font *font, Sprite *out_sprite, BBox *out_bbox) + attr_nonnull(1, 2, 3, 4); + +void text_shorten(Font *font, char *text, double width) + attr_nonnull(1, 2); + +void text_wrap(Font *font, const char *src, double width, char *buf, size_t bufsize) + attr_nonnull(1, 2, 4); + +void text_bbox(Font *font, const char *text, uint maxlines, BBox *bbox) + attr_nonnull(1, 2, 4); + +int text_width_raw(Font *font, const char *text, uint maxlines) + attr_nonnull(1, 2); + +double text_width(Font *font, const char *text, uint maxlines) + attr_nonnull(1, 2); + +int text_height_raw(Font *font, const char *text, uint maxlines) + attr_nonnull(1, 2); + +double text_height(Font *font, const char *text, uint maxlines) + attr_nonnull(1, 2); extern ResourceHandler font_res_handler; diff --git a/src/resource/resource.c b/src/resource/resource.c index 029b60e0..524961ea 100644 --- a/src/resource/resource.c +++ b/src/resource/resource.c @@ -67,10 +67,6 @@ typedef struct ResourceAsyncLoadData { void *opaque; } ResourceAsyncLoadData; -struct ResourceHandlerPrivate { - Hashtable *mapping; -}; - Resources resources; static SDL_threadID main_thread_id; // TODO: move this somewhere else @@ -85,16 +81,19 @@ static inline ResourceHandler* get_ires_handler(InternalResource *ires) { static void alloc_handler(ResourceHandler *h) { assert(h != NULL); - h->private = calloc(1, sizeof(ResourceHandlerPrivate)); - h->private->mapping = hashtable_new_stringkeys(); + ht_create(&h->private.mapping); } static const char* type_name(ResourceType type) { return get_handler(type)->typename; } -static void* datafunc_begin_load_resource(void *arg) { - ResourceType type = (intptr_t)arg; +struct valfunc_arg { + ResourceType type; +}; + +static void* valfunc_begin_load_resource(void* arg) { + ResourceType type = ((struct valfunc_arg*)arg)->type; InternalResource *ires = calloc(1, sizeof(InternalResource)); ires->res.type = type; @@ -107,7 +106,8 @@ static void* datafunc_begin_load_resource(void *arg) { static bool try_begin_load_resource(ResourceType type, const char *name, InternalResource **out_ires) { ResourceHandler *handler = get_handler(type); - return hashtable_try_set(handler->private->mapping, (char*)name, (void*)(uintptr_t)type, datafunc_begin_load_resource, (void**)out_ires); + struct valfunc_arg arg = { type }; + return ht_try_set(&handler->private.mapping, name, &arg, valfunc_begin_load_resource, (void**)out_ires); } static void load_resource_finish(InternalResource *ires, void *opaque, const char *path, const char *name, char *allocated_path, char *allocated_name, ResourceFlags flags); @@ -337,7 +337,9 @@ Resource* get_resource(ResourceType type, const char *name, ResourceFlags flags) Resource *res; if(flags & RESF_UNSAFE) { - ires = hashtable_get_unsafe(get_handler(type)->private->mapping, (char*)name); + // FIXME: I'm not sure we actually need this functionality. + + ires = ht_get_unsafe(&get_handler(type)->private.mapping, name, NULL); if(ires != NULL && ires->status == RES_STATUS_LOADED) { return &ires->res; @@ -478,25 +480,40 @@ static void* preload_shaders(const char *path, void *arg) { return NULL; } -struct resource_for_each_arg { - void *(*callback)(const char *name, Resource *res, void *arg); - void *arg; -}; - -static void* resource_for_each_ht_adapter(void *key, void *data, void *varg) { - const char *name = key; - InternalResource *ires = data; - struct resource_for_each_arg *arg = varg; - return arg->callback(name, &ires->res, arg->arg); -} - void* resource_for_each(ResourceType type, void* (*callback)(const char *name, Resource *res, void *arg), void *arg) { - ResourceHandler *handler = get_handler(type); - struct resource_for_each_arg htarg = { callback, arg }; - return hashtable_foreach(handler->private->mapping, resource_for_each_ht_adapter, &htarg); + ht_str2ptr_ts_iter_t iter; + ht_iter_begin(&get_handler(type)->private.mapping, &iter); + void *result = NULL; + + while(iter.has_data) { + InternalResource *ires = iter.value; + char *key = iter.key; + + ht_iter_next(&iter); + + if(ires->res.data == NULL) { + continue; + } + + if((result = callback(key, &ires->res, arg)) != NULL) { + break; + } + } + + ht_iter_end(&iter); + return result; } void load_resources(void) { + for(uint i = 0; i < RES_NUMTYPES; ++i) { + ResourceHandler *h = get_handler(i); + assert(h != NULL); + + if(h->procs.post_init) { + h->procs.post_init(); + } + } + menu_preload(); if(env_get("TAISEI_PRELOAD_SHADERS", 0)) { @@ -506,33 +523,41 @@ void load_resources(void) { } void free_resources(bool all) { + ht_str2ptr_ts_iter_t iter; + for(ResourceType type = 0; type < RES_NUMTYPES; ++type) { ResourceHandler *handler = get_handler(type); - - char *name; InternalResource *ires; - ListContainer *unset_list = NULL; + ht_str2ptr_ts_key_list_t *unset_list = NULL, *unset_entry; + + ht_iter_begin(&handler->private.mapping, &iter); + + for(; iter.has_data; ht_iter_next(&iter)) { + ires = iter.value; - hashtable_lock(handler->private->mapping); - for(HashtableIterator *i = hashtable_iter(handler->private->mapping); hashtable_iter_next(i, (void**)&name, (void**)&ires);) { if(!all && (ires->res.flags & RESF_PERMANENT)) { continue; } - list_push(&unset_list, list_wrap_container(name)); + unset_entry = calloc(1, sizeof(*unset_entry)); + unset_entry->key = iter.key; + list_push(&unset_list, unset_entry); } - hashtable_unlock(handler->private->mapping); - for(ListContainer *c; (c = list_pop(&unset_list));) { - char *tmp = c->data; + ht_iter_end(&iter); + + for(ht_str2ptr_ts_key_list_t *c; (c = list_pop(&unset_list));) { + char *tmp = c->key; char name[strlen(tmp) + 1]; strcpy(name, tmp); - ires = hashtable_get_string(handler->private->mapping, name); + ires = ht_get(&handler->private.mapping, name, NULL); + assert(ires != NULL); + attr_unused ResourceFlags flags = ires->res.flags; if(!all) { - hashtable_unset_string(handler->private->mapping, name); + ht_unset(&handler->private.mapping, name); } unload_resource(ires); @@ -551,8 +576,7 @@ void free_resources(bool all) { handler->procs.shutdown(); } - hashtable_free(handler->private->mapping); - free(handler->private); + ht_destroy(&handler->private.mapping); } } diff --git a/src/resource/resource.h b/src/resource/resource.h index f2b96b5a..4249ed9f 100644 --- a/src/resource/resource.h +++ b/src/resource/resource.h @@ -63,12 +63,13 @@ typedef void* (*ResourceEndLoadProc)(void *opaque, const char *path, uint flags) typedef void (*ResourceUnloadProc)(void *res); // Called during resource subsystem initialization -typedef void (*ResourceInit)(void); +typedef void (*ResourceInitProc)(void); + +// Called after the resources and rendering subsystems have been initialized +typedef void (*ResourcePostInitProc)(void); // Called during resource subsystem shutdown -typedef void (*ResourceShutdown)(void); - -typedef struct ResourceHandlerPrivate ResourceHandlerPrivate; +typedef void (*ResourceShutdownProc)(void); typedef struct ResourceHandler { ResourceType type; @@ -83,11 +84,14 @@ typedef struct ResourceHandler { ResourceBeginLoadProc begin_load; ResourceEndLoadProc end_load; ResourceUnloadProc unload; - ResourceInit init; - ResourceShutdown shutdown; + ResourceInitProc init; + ResourcePostInitProc post_init; + ResourceShutdownProc shutdown; } procs; - ResourceHandlerPrivate *private; + struct { + ht_str2ptr_ts_t mapping; + } private; } ResourceHandler; typedef struct Resource { diff --git a/src/resource/sprite.c b/src/resource/sprite.c index f2a5f1a2..ae9c7115 100644 --- a/src/resource/sprite.c +++ b/src/resource/sprite.c @@ -60,7 +60,7 @@ void* load_sprite_begin(const char *path, uint flags) { return state; } - if(!parse_keyvalue_file_with_spec(path, (KVSpec[]){ + if(!parse_keyvalue_file_with_spec(path, (KVSpec[]) { { "texture", .out_str = &state->texture_name }, { "region_x", .out_float = &spr->tex_area.x }, { "region_y", .out_float = &spr->tex_area.y }, diff --git a/src/stage.c b/src/stage.c index 61546732..f4e8e52d 100644 --- a/src/stage.c +++ b/src/stage.c @@ -510,8 +510,8 @@ static void stage_preload(void) { } static void display_stage_title(StageInfo *info) { - stagetext_add(info->title, VIEWPORT_W/2 + I * (VIEWPORT_H/2-40), AL_Center, &_fonts.mainmenu, rgb(1, 1, 1), 50, 85, 35, 35); - stagetext_add(info->subtitle, VIEWPORT_W/2 + I * (VIEWPORT_H/2), AL_Center, &_fonts.standard, rgb(1, 1, 1), 60, 85, 35, 35); + stagetext_add(info->title, VIEWPORT_W/2 + I * (VIEWPORT_H/2-40), ALIGN_CENTER, get_font("big"), rgb(1, 1, 1), 50, 85, 35, 35); + stagetext_add(info->subtitle, VIEWPORT_W/2 + I * (VIEWPORT_H/2), ALIGN_CENTER, get_font("standard"), rgb(1, 1, 1), 60, 85, 35, 35); } void stage_start_bgm(const char *bgm) { @@ -526,7 +526,7 @@ void stage_start_bgm(const char *bgm) { if(current_bgm.title && current_bgm.started_at >= 0 && (!old_title || strcmp(current_bgm.title, old_title))) { char txt[strlen(current_bgm.title) + 6]; snprintf(txt, sizeof(txt), "BGM: %s", current_bgm.title); - stagetext_add(txt, VIEWPORT_W-15 + I * (VIEWPORT_H-20), AL_Right, &_fonts.standard, rgb(1, 1, 1), 30, 180, 35, 35); + stagetext_add(txt, VIEWPORT_W-15 + I * (VIEWPORT_H-20), ALIGN_RIGHT, get_font("standard"), rgb(1, 1, 1), 30, 180, 35, 35); } free(old_title); diff --git a/src/stagedraw.c b/src/stagedraw.c index 2463cc1c..51b854d0 100644 --- a/src/stagedraw.c +++ b/src/stagedraw.c @@ -26,13 +26,15 @@ static struct { struct { ShaderProgram *shader; - Uniform *u_colorAtop; - Uniform *u_colorAbot; - Uniform *u_colorBtop; - Uniform *u_colorBbot; - Uniform *u_colortint; - Uniform *u_split; + Font *font; + + struct { + Color active; + Color inactive; + Color label; + } color; } hud_text; + bool framerate_graphs; bool objpool_stats; PostprocessShader *viewport_pp; @@ -40,7 +42,13 @@ static struct { #ifdef DEBUG Sprite dummy; #endif -} stagedraw; +} stagedraw = { + .hud_text.color = { + .active = RGBA(1.00, 1.00, 1.00, 1.00), + .inactive = RGBA(0.70, 0.70, 0.70, 0.70), + .label = RGBA(0.70, 0.70, 0.70, 0.70), + } +}; void stage_draw_preload(void) { preload_resources(RES_POSTPROCESS, RESF_OPTIONAL, @@ -57,28 +65,21 @@ void stage_draw_preload(void) { NULL); preload_resources(RES_SHADER_PROGRAM, RESF_PERMANENT, - "stagetext", + "text_hud", + "text_stagetext", "ingame_menu", "sprite_circleclipped_indicator", - "hud_text", #ifdef DEBUG "sprite_filled_circle", #endif NULL); - stagedraw.hud_text.shader = r_shader_get("hud_text"); - stagedraw.hud_text.u_colorAtop = r_shader_uniform(stagedraw.hud_text.shader, "colorAtop"); - stagedraw.hud_text.u_colorAbot = r_shader_uniform(stagedraw.hud_text.shader, "colorAbot"); - stagedraw.hud_text.u_colorBtop = r_shader_uniform(stagedraw.hud_text.shader, "colorBtop"); - stagedraw.hud_text.u_colorBbot = r_shader_uniform(stagedraw.hud_text.shader, "colorBbot"); - stagedraw.hud_text.u_colortint = r_shader_uniform(stagedraw.hud_text.shader, "colortint"); - stagedraw.hud_text.u_split = r_shader_uniform(stagedraw.hud_text.shader, "split"); - - r_uniform_ptr(stagedraw.hud_text.u_colorAtop, 1, (float[]){ 0.70, 0.70, 0.70, 0.70 }); - r_uniform_ptr(stagedraw.hud_text.u_colorAbot, 1, (float[]){ 0.50, 0.50, 0.50, 0.50 }); - r_uniform_ptr(stagedraw.hud_text.u_colorBtop, 1, (float[]){ 1.00, 1.00, 1.00, 1.00 }); - r_uniform_ptr(stagedraw.hud_text.u_colorBbot, 1, (float[]){ 0.80, 0.80, 0.80, 0.80 }); - r_uniform_ptr(stagedraw.hud_text.u_colortint, 1, (float[]){ 1.00, 1.00, 1.00, 1.00 }); + preload_resources(RES_FONT, RESF_PERMANENT, + "hud", + "mono", + "small", + "monosmall", + NULL); stagedraw.framerate_graphs = env_get("TAISEI_FRAMERATE_GRAPHS", GRAPHS_DEFAULT); stagedraw.objpool_stats = env_get("TAISEI_OBJPOOL_STATS", OBJPOOLSTATS_DEFAULT); @@ -89,7 +90,16 @@ void stage_draw_preload(void) { NULL); } + if(stagedraw.objpool_stats) { + preload_resources(RES_FONT, RESF_PERMANENT, + "monotiny", + NULL); + } + stagedraw.viewport_pp = get_resource_data(RES_POSTPROCESS, "viewport", RESF_OPTIONAL); + stagedraw.hud_text.shader = r_shader_get("text_hud"); + stagedraw.hud_text.font = get_font("hud"); + r_shader_standard(); #ifdef DEBUG @@ -177,7 +187,12 @@ static void apply_shader_rules(ShaderRule *shaderrules, FBOPair *fbos) { static void draw_wall_of_text(float f, const char *txt) { Sprite spr; - render_text(txt, _fonts.standard, &spr); + BBox bbox; + + text_render(txt, get_font("standard"), &spr, &bbox); + + // FIXME: The shader currently assumes that the sprite takes up the entire texture. + // If it could handle any arbitrary sprite, then text_render wouldn't have to resize // the texture per every new string of text. float w = VIEWPORT_W; float h = VIEWPORT_H; @@ -515,45 +530,92 @@ void stage_draw_scene(StageInfo *stage) { stage_draw_hud(); } -static inline void stage_draw_hud_power_value(float ypos, char *buf, size_t bufsize) { - r_uniform_ptr(stagedraw.hud_text.u_split, 1, (float[]) { -0.25 }); - snprintf(buf, bufsize, "%i.%02i", global.plr.power / 100, global.plr.power % 100); - draw_text(AL_Right, 170, (int)ypos, buf, _fonts.mono); - r_uniform_ptr(stagedraw.hud_text.u_split, 1, (float[]) { 0.0 }); +struct glyphcb_state { + Color color; +}; + +static void draw_powerval_callback(Font *font, charcode_t charcode, SpriteParams *spr_params, void *userdata) { + struct glyphcb_state *st = userdata; + + if(charcode == '.') { + st->color = stagedraw.hud_text.color.inactive; + } + + spr_params->color = st->color; } -static float split_for_digits(uint32_t val, int maxdigits) { - int digits = val ? log10(val) + 1 : 0; - int remdigits = maxdigits - digits; - return max(0, (float)remdigits/maxdigits); +static void draw_numeric_callback(Font *font, charcode_t charcode, SpriteParams *spr_params, void *userdata) { + struct glyphcb_state *st = userdata; + + if(charcode != '0') { + st->color = stagedraw.hud_text.color.active; + } + + spr_params->color = st->color; +} + +static inline void stage_draw_hud_power_value(float ypos, char *buf, size_t bufsize) { + snprintf(buf, bufsize, "%i.%02i", global.plr.power / 100, global.plr.power % 100); + text_draw(buf, &(TextParams) { + .pos = { 170, ypos }, + .font = "mono", + .align = ALIGN_RIGHT, + .glyph_callback = { + draw_powerval_callback, + &(struct glyphcb_state) { stagedraw.hud_text.color.active }, + } + }); } static void stage_draw_hud_score(Alignment a, float xpos, float ypos, char *buf, size_t bufsize, uint32_t score) { snprintf(buf, bufsize, "%010u", score); - r_uniform_ptr(stagedraw.hud_text.u_split, 1, (float[]) { split_for_digits(score, 10) }); - draw_text(a, (int)xpos, (int)ypos, buf, _fonts.mono); + text_draw(buf, &(TextParams) { + .pos = { xpos, ypos }, + .font = "mono", + .align = ALIGN_RIGHT, + .glyph_callback = { + draw_numeric_callback, + &(struct glyphcb_state) { stagedraw.hud_text.color.inactive }, + } + }); } static void stage_draw_hud_scores(float ypos_hiscore, float ypos_score, char *buf, size_t bufsize) { - stage_draw_hud_score(AL_Right, 170, (int)ypos_hiscore, buf, bufsize, progress.hiscore); - stage_draw_hud_score(AL_Right, 170, (int)ypos_score, buf, bufsize, global.plr.points); - r_uniform_ptr(stagedraw.hud_text.u_split, 1, (float[]) { 0 }); + stage_draw_hud_score(ALIGN_RIGHT, 170, (int)ypos_hiscore, buf, bufsize, progress.hiscore); + stage_draw_hud_score(ALIGN_RIGHT, 170, (int)ypos_score, buf, bufsize, global.plr.points); } -static void stage_draw_hud_objpool_stats(float x, float y, float width, Font *font) { +static void stage_draw_hud_objpool_stats(float x, float y, float width) { ObjectPool **last = &stage_object_pools.first + (sizeof(StageObjectPools)/sizeof(ObjectPool*) - 1); + Font *font = get_font("monotiny"); + ShaderProgram *sh_prev = r_shader_current(); + r_shader("text_default"); for(ObjectPool **pool = &stage_object_pools.first; pool <= last; ++pool) { ObjectPoolStats stats; char buf[32]; objpool_get_stats(*pool, &stats); snprintf(buf, sizeof(buf), "%zu | %5zu", stats.usage, stats.peak_usage); - draw_text(AL_Left | AL_Flag_NoAdjust, (int)x, (int)y, stats.tag, font); - draw_text(AL_Right | AL_Flag_NoAdjust, (int)(x + width), (int)y, buf, font); + // draw_text(ALIGN_LEFT | AL_Flag_NoAdjust, (int)x, (int)y, stats.tag, font); + // draw_text(ALIGN_RIGHT | AL_Flag_NoAdjust, (int)(x + width), (int)y, buf, font); + // y += stringheight(buf, font) * 1.1; - y += stringheight(buf, font) * 1.1; + text_draw(stats.tag, &(TextParams) { + .pos = { x, y }, + .font_ptr = font, + .align = ALIGN_LEFT, + }); + + text_draw(buf, &(TextParams) { + .pos = { x + width, y }, + .font_ptr = font, + .align = ALIGN_RIGHT, + }); + + y += font_get_lineskip(font); } + r_shader_ptr(sh_prev); } struct labels_s { @@ -580,25 +642,31 @@ static void draw_graph(float x, float y, float w, float h) { r_mat_pop(); } +static void draw_label(const char *label_str, double y_ofs, struct labels_s* labels) { + text_draw(label_str, &(TextParams) { + .font_ptr = stagedraw.hud_text.font, + .shader_ptr = stagedraw.hud_text.shader, + .pos = { labels->x.ofs, y_ofs }, + .color = stagedraw.hud_text.color.label, + }); +} + void stage_draw_hud_text(struct labels_s* labels) { char buf[64]; + Font *font; r_shader_ptr(stagedraw.hud_text.shader); - r_uniform_ptr(stagedraw.hud_text.u_split, 1, (float[]) { 0 }); - r_uniform_ptr(stagedraw.hud_text.u_colortint, 1, (float[]) { 1.00, 1.00, 1.00, 1.00 }); // Labels - r_uniform_ptr(stagedraw.hud_text.u_colortint, 1, (float[]) { 0.70, 0.70, 0.70, 0.70 }); - draw_text(AL_Left, labels->x.ofs, labels->y.hiscore, "Hi-Score:", _fonts.hud); - draw_text(AL_Left, labels->x.ofs, labels->y.score, "Score:", _fonts.hud); - draw_text(AL_Left, labels->x.ofs, labels->y.lives, "Lives:", _fonts.hud); - draw_text(AL_Left, labels->x.ofs, labels->y.bombs, "Spells:", _fonts.hud); - draw_text(AL_Left, labels->x.ofs, labels->y.power, "Power:", _fonts.hud); - draw_text(AL_Left, labels->x.ofs, labels->y.graze, "Graze:", _fonts.hud); - r_uniform_ptr(stagedraw.hud_text.u_colortint, 1, (float[]) { 1.00, 1.00, 1.00, 1.00 }); + draw_label("Hi-Score:", labels->y.hiscore, labels); + draw_label("Score:", labels->y.score, labels); + draw_label("Lives:", labels->y.lives, labels); + draw_label("Spells:", labels->y.bombs, labels); + draw_label("Power:", labels->y.power, labels); + draw_label("Graze:", labels->y.graze, labels); if(stagedraw.objpool_stats) { - stage_draw_hud_objpool_stats(labels->x.ofs, labels->y.graze + 32, 250, _fonts.monotiny); + stage_draw_hud_objpool_stats(labels->x.ofs, labels->y.graze + 32, 250); } // Score/Hi-Score values @@ -607,8 +675,8 @@ void stage_draw_hud_text(struct labels_s* labels) { // Lives and Bombs (N/A) if(global.stage->type == STAGE_SPELL) { r_color4(1, 1, 1, 0.7); - draw_text(AL_Left, -6, labels->y.lives, "N/A", _fonts.hud); - draw_text(AL_Left, -6, labels->y.bombs, "N/A", _fonts.hud); + text_draw("N/A", &(TextParams) { .pos = { -6, labels->y.lives }, .font_ptr = stagedraw.hud_text.font }); + text_draw("N/A", &(TextParams) { .pos = { -6, labels->y.bombs }, .font_ptr = stagedraw.hud_text.font }); r_color4(1, 1, 1, 1.0); } @@ -617,9 +685,15 @@ void stage_draw_hud_text(struct labels_s* labels) { // Graze value snprintf(buf, sizeof(buf), "%05i", global.plr.graze); - r_uniform_ptr(stagedraw.hud_text.u_split, 1, (float[]) { split_for_digits(global.plr.graze, 5) }); - draw_text(AL_Left, -6, (int)(labels->y.graze + labels->y.mono_ofs), buf, _fonts.mono); - r_uniform_ptr(stagedraw.hud_text.u_split, 1, (float[]) { 0 }); + text_draw(buf, &(TextParams) { + .pos = { -6, labels->y.graze }, + .shader_ptr = stagedraw.hud_text.shader, + .font = "mono", + .glyph_callback = { + draw_numeric_callback, + &(struct glyphcb_state) { stagedraw.hud_text.color.inactive }, + } + }); // Warning: pops outer matrix! r_mat_pop(); @@ -644,25 +718,36 @@ void stage_draw_hud_text(struct labels_s* labels) { } #endif - draw_text(AL_Right | AL_Flag_NoAdjust, SCREEN_W, rint(SCREEN_H - 0.5 * stringheight(buf, _fonts.monosmall)), buf, _fonts.monosmall); + // draw_text(ALIGN_RIGHT | AL_Flag_NoAdjust, SCREEN_W, rint(SCREEN_H - 0.5 * stringheight(buf, _fonts.monosmall)), buf, _fonts.monosmall); + font = get_font("monosmall"); + + text_draw(buf, &(TextParams) { + .align = ALIGN_RIGHT, + .pos = { SCREEN_W, SCREEN_H - 0.5 * text_height(font, buf, 0) }, + .font_ptr = font, + }); if(global.replaymode == REPLAY_PLAY) { + r_shader("text_hud"); // XXX: does it make sense to use the monospace font here? snprintf(buf, sizeof(buf), "Replay: %s (%i fps)", global.replay.playername, global.replay_stage->fps); - int x = 0, y = SCREEN_H - 0.5 * stringheight(buf, _fonts.monosmall); + int x = 0, y = SCREEN_H - 0.5 * text_height(font, buf, 0); - r_uniform_ptr(stagedraw.hud_text.u_colortint, 1, (float[]) { 0.50, 0.50, 0.50, 0.50 }); - draw_text(AL_Left, x, y, buf, _fonts.monosmall); - r_uniform_ptr(stagedraw.hud_text.u_colortint, 1, (float[]) { 1.00, 1.00, 1.00, 1.00 }); + x += text_draw(buf, &(TextParams) { + .pos = { x, y }, + .font_ptr = font, + .color = stagedraw.hud_text.color.inactive, + }); if(global.replay_stage->desynced) { - x += stringwidth(buf, _fonts.monosmall); strlcpy(buf, " (DESYNCED)", sizeof(buf)); - r_uniform_ptr(stagedraw.hud_text.u_colortint, 1, (float[]) { 1.00, 0.20, 0.20, 0.60 }); - draw_text(AL_Left, x, y, buf, _fonts.monosmall); - r_uniform_ptr(stagedraw.hud_text.u_colortint, 1, (float[]) { 1.00, 1.00, 1.00, 1.00 }); + text_draw(buf, &(TextParams) { + .pos = { x, y }, + .font_ptr = font, + .color = rgba(1.00, 0.20, 0.20, 0.60), + }); } } #ifdef PLR_DPS_STATS @@ -697,11 +782,21 @@ void stage_draw_hud_text(struct labels_s* labels) { } } - snprintf(buf, sizeof(buf), "Avg DPS: %.02f", totaldmg / (framespan / (double)FPS)); - r_uniform_ptr(stagedraw.hud_text.u_split, 1, (float[]) { 8.0 / strlen(buf) }); - float text_h = stringheight(buf, _fonts.monosmall); - float text_y = rint(SCREEN_H - 0.5 * text_h); - draw_text(AL_Left, 0, text_y, buf, _fonts.monosmall); + snprintf(buf, sizeof(buf), "%.02f", totaldmg / (framespan / (double)FPS)); + double text_h = text_height(font, buf, 0); + double x = 0, y = SCREEN_H - 0.5 * text_h; + + x += text_draw("Avg DPS: ", &(TextParams) { + .pos = { x, y }, + .font_ptr = font, + .color = stagedraw.hud_text.color.inactive, + }); + + text_draw(buf, &(TextParams) { + .pos = { x, y }, + .font_ptr = font, + .color = stagedraw.hud_text.color.active, + }); r_shader("graph"); r_uniform_vec3("color_low", 1.0, 0.0, 0.0); @@ -768,7 +863,7 @@ void stage_draw_hud(void) { .x.ofs = -75, // XXX: is there a more robust way to level the monospace font with the label one? - .y.mono_ofs = -0.5, + // .y.mono_ofs = 0.5, }; const float label_height = 33; @@ -816,9 +911,11 @@ void stage_draw_hud(void) { // Power stars draw_stars(0, labels.y.power, global.plr.power / 100, global.plr.power % 100, PLR_MAX_POWER / 100, 100, 1, 20); + ShaderProgram *sh_prev = r_shader_current(); + r_shader("text_default"); // God Mode indicator if(global.plr.iddqd) { - draw_text(AL_Left, -70, 475, "GOD MODE", _fonts.mainmenu); + text_draw("GOD MODE", &(TextParams) { .pos = { -70, 475 }, .font = "big" }); } // Extra Spell indicator @@ -829,14 +926,16 @@ void stage_draw_hud(void) { r_color4(0.3, 0.6, 0.7, 0.7 * s); r_mat_rotate_deg(-25 + 360 * (1-s2), 0, 0, 1); r_mat_scale(s2, s2, 0); - draw_text(AL_Center, 1, 1, "Extra Spell!", _fonts.mainmenu); - draw_text(AL_Center, -1, -1, "Extra Spell!", _fonts.mainmenu); + + text_draw("Extra Spell!", &(TextParams) { .pos = { 1, 1 }, .font = "big", .align = ALIGN_CENTER }); + text_draw("Extra Spell!", &(TextParams) { .pos = { -1, -1 }, .font = "big", .align = ALIGN_CENTER }); r_color4(1, 1, 1, s); - draw_text(AL_Center, 0, 0, "Extra Spell!", _fonts.mainmenu); + text_draw("Extra Spell!", &(TextParams) { .pos = { 0, 0 }, .font = "big", .align = ALIGN_CENTER }); r_color4(1, 1, 1, 1); r_mat_pop(); } + r_shader_ptr(sh_prev); // Warning: pops matrix! stage_draw_hud_text(&labels); diff --git a/src/stages/stage1_events.c b/src/stages/stage1_events.c index 58cb2c32..fbfaa5d7 100644 --- a/src/stages/stage1_events.c +++ b/src/stages/stage1_events.c @@ -656,7 +656,7 @@ void cirno_snow_halation(Boss *c, int time) { }; if(cheater < sizeof(text)/sizeof(text[0])) { - stagetext_add(text[cheater],global.boss->pos+100*I,AL_Center,&_fonts.hud,rgb(1,1,1),0,100,10,20); + stagetext_add(text[cheater], global.boss->pos+100*I, ALIGN_CENTER, get_font("hud"), rgb(1,1,1), 0, 100, 10, 20); cheater++; } } diff --git a/src/stages/stage6_events.c b/src/stages/stage6_events.c index aca2e33e..df1d6255 100644 --- a/src/stages/stage6_events.c +++ b/src/stages/stage6_events.c @@ -897,7 +897,7 @@ void elly_paradigm_shift(Boss *b, int t) { AT(100) { if(global.stage->type != STAGE_SPELL) { stage_start_bgm("stage6boss_phase2"); - stagetext_add("Paradigm Shift!", VIEWPORT_W/2+I*(VIEWPORT_H/2+64), AL_Center, &_fonts.mainmenu, rgb(1, 1, 1), 0, 120, 10, 30); + stagetext_add("Paradigm Shift!", VIEWPORT_W/2+I*(VIEWPORT_H/2+64), ALIGN_CENTER, get_font("big"), rgb(1, 1, 1), 0, 120, 10, 30); } elly_spawn_baryons(b->pos); @@ -2507,7 +2507,7 @@ void elly_theory(Boss *b, int time) { AT(symmetrytime) { play_sound("charge_generic"); play_sound("boom"); - stagetext_add("Symmetry broken!", VIEWPORT_W/2+I*VIEWPORT_H/4,AL_Center,&_fonts.mainmenu,rgb(1,1,1),0,100,10,20); + stagetext_add("Symmetry broken!", VIEWPORT_W/2+I*VIEWPORT_H/4, ALIGN_CENTER, get_font("big"), rgb(1,1,1), 0,100,10,20); global.shake_view=10; global.shake_view_fade=1; @@ -2537,15 +2537,15 @@ void elly_theory(Boss *b, int time) { AT(yukawatime) { play_sound("charge_generic"); - stagetext_add("Coupling the Higgs!", VIEWPORT_W/2+I*VIEWPORT_H/4,AL_Center,&_fonts.mainmenu,rgb(1,1,1),0,100,10,20); + stagetext_add("Coupling the Higgs!", VIEWPORT_W/2+I*VIEWPORT_H/4, ALIGN_CENTER, get_font("big"), rgb(1,1,1), 0,100,10,20); global.shake_view=10; global.shake_view_fade=1; } AT(breaktime) { play_sound("charge_generic"); - stagetext_add("Perturbation theory", VIEWPORT_W/2+I*VIEWPORT_H/4,AL_Center,&_fonts.mainmenu,rgb(1,1,1),0,100,10,20); - stagetext_add("breaking down!", VIEWPORT_W/2+I*VIEWPORT_H/4+30*I,AL_Center,&_fonts.mainmenu,rgb(1,1,1),0,100,10,20); + stagetext_add("Perturbation theory", VIEWPORT_W/2+I*VIEWPORT_H/4, ALIGN_CENTER, get_font("big"), rgb(1,1,1), 0,100,10,20); + stagetext_add("breaking down!", VIEWPORT_W/2+I*VIEWPORT_H/4+30*I, ALIGN_CENTER, get_font("big"), rgb(1,1,1), 0,100,10,20); global.shake_view=10; global.shake_view_fade=1; } diff --git a/src/stagetext.c b/src/stagetext.c index 4a0d7722..35f75aef 100644 --- a/src/stagetext.c +++ b/src/stagetext.c @@ -16,7 +16,7 @@ static StageText *textlist = NULL; #define NUM_PLACEHOLDER "........................" -StageText* stagetext_add(const char *text, complex pos, Alignment align, Font **font, Color clr, int delay, int lifetime, int fadeintime, int fadeouttime) { +StageText* stagetext_add(const char *text, complex pos, Alignment align, Font *font, Color clr, int delay, int lifetime, int fadeintime, int fadeouttime) { StageText *t = malloc(sizeof(StageText)); list_append(&textlist, t); @@ -24,13 +24,13 @@ StageText* stagetext_add(const char *text, complex pos, Alignment align, Font ** t->font = font; t->pos = pos; t->align = align; + t->color = clr; t->time.spawn = global.frames + delay; - t->time.life = lifetime + fadeouttime; t->time.fadein = fadeintime; t->time.fadeout = fadeouttime; + t->time.life = lifetime + fadeouttime; - parse_color_array(clr, t->clr); memset(&t->custom, 0, sizeof(t->custom)); return t; @@ -40,7 +40,7 @@ void stagetext_numeric_predraw(StageText *txt, int t, float a) { snprintf(txt->text, sizeof(NUM_PLACEHOLDER), "%i", (int)((intptr_t)txt->custom.data1 * pow(a, 5))); } -StageText* stagetext_add_numeric(int n, complex pos, Alignment align, Font **font, Color clr, int delay, int lifetime, int fadeintime, int fadeouttime) { +StageText* stagetext_add_numeric(int n, complex pos, Alignment align, Font *font, Color clr, int delay, int lifetime, int fadeintime, int fadeouttime) { StageText *t = stagetext_add(NUM_PLACEHOLDER, pos, align, font, clr, delay, lifetime, fadeintime, fadeouttime); t->custom.data1 = (void*)(intptr_t)n; t->custom.predraw = stagetext_numeric_predraw; @@ -74,17 +74,25 @@ static void stagetext_draw_single(StageText *txt) { txt->custom.predraw(txt, t, 1.0 - f); } + r_state_push(); r_texture(1, "titletransition"); - r_shader("stagetext"); + r_shader("text_stagetext"); r_uniform_int("trans", 1); - r_uniform_float("t", 1.0 - f); - r_uniform_vec3("color", 0, 0, 0); - draw_text(txt->align, creal(txt->pos)+10*f*f+1, cimag(txt->pos)+10*f*f+1, txt->text, *txt->font); - r_uniform("color", 1, txt->clr); - draw_text(txt->align, creal(txt->pos)+10*f*f, cimag(txt->pos)+10*f*f, txt->text, *txt->font); + TextParams params = { 0 }; + params.font_ptr = txt->font; + params.align = txt->align; + params.blend = BLEND_ALPHA; + params.shader_ptr = r_shader_current(); - r_shader_standard(); + params.custom = 1.0-f; + params.pos.x = creal(txt->pos)+10*f*f; + params.pos.y = cimag(txt->pos)+10*f*f; + + params.color = txt->color; + text_draw(txt->text, ¶ms); + + r_state_pop(); } void stagetext_draw(void) { @@ -98,7 +106,7 @@ static void stagetext_table_push(StageTextTable *tbl, StageText *txt, bool updat list_append(&tbl->elems, list_wrap_container(txt)); if(update_pos) { - tbl->pos += stringheight(txt->text, *txt->font) * I; + tbl->pos += text_height(txt->font, txt->text, 0) * I; } tbl->delay += 5; @@ -114,7 +122,7 @@ void stagetext_begin_table(StageTextTable *tbl, const char *title, Color titlecl tbl->fadeouttime = fadeouttime; tbl->delay = delay; - StageText *txt = stagetext_add(title, tbl->pos, AL_Center, &_fonts.mainmenu, titleclr, tbl->delay, lifetime, fadeintime, fadeouttime); + StageText *txt = stagetext_add(title, tbl->pos, ALIGN_CENTER, get_font("big"), titleclr, tbl->delay, lifetime, fadeintime, fadeouttime); stagetext_table_push(tbl, txt, true); } @@ -129,19 +137,19 @@ void stagetext_end_table(StageTextTable *tbl) { } static void stagetext_table_add_label(StageTextTable *tbl, const char *title) { - StageText *txt = stagetext_add(title, tbl->pos - tbl->width * 0.5, AL_Left, &_fonts.standard, tbl->clr, tbl->delay, tbl->lifetime, tbl->fadeintime, tbl->fadeouttime); + StageText *txt = stagetext_add(title, tbl->pos - tbl->width * 0.5, ALIGN_LEFT, get_font("standard"), tbl->clr, tbl->delay, tbl->lifetime, tbl->fadeintime, tbl->fadeouttime); stagetext_table_push(tbl, txt, false); } void stagetext_table_add(StageTextTable *tbl, const char *title, const char *val) { stagetext_table_add_label(tbl, title); - StageText *txt = stagetext_add(val, tbl->pos + tbl->width * 0.5, AL_Right, &_fonts.standard, tbl->clr, tbl->delay, tbl->lifetime, tbl->fadeintime, tbl->fadeouttime); + StageText *txt = stagetext_add(val, tbl->pos + tbl->width * 0.5, ALIGN_RIGHT, get_font("standard"), tbl->clr, tbl->delay, tbl->lifetime, tbl->fadeintime, tbl->fadeouttime); stagetext_table_push(tbl, txt, true); } void stagetext_table_add_numeric(StageTextTable *tbl, const char *title, int n) { stagetext_table_add_label(tbl, title); - StageText *txt = stagetext_add_numeric(n, tbl->pos + tbl->width * 0.5, AL_Right, &_fonts.standard, tbl->clr, tbl->delay, tbl->lifetime, tbl->fadeintime, tbl->fadeouttime); + StageText *txt = stagetext_add_numeric(n, tbl->pos + tbl->width * 0.5, ALIGN_RIGHT, get_font("standard"), tbl->clr, tbl->delay, tbl->lifetime, tbl->fadeintime, tbl->fadeouttime); stagetext_table_push(tbl, txt, true); } @@ -152,7 +160,7 @@ void stagetext_table_add_numeric_nonzero(StageTextTable *tbl, const char *title, } void stagetext_table_add_separator(StageTextTable *tbl) { - tbl->pos += I * 0.5 * stringheight("Love Live", _fonts.standard); + tbl->pos += I * 0.5 * font_get_lineskip(get_font("standard")); } void stagetext_table_test(void) { diff --git a/src/stagetext.h b/src/stagetext.h index d6c8b24d..0c275d25 100644 --- a/src/stagetext.h +++ b/src/stagetext.h @@ -23,11 +23,11 @@ struct StageText { LIST_INTERFACE(StageText); char *text; - Font **font; + Font *font; complex pos; Alignment align; - float clr[4]; + Color color; struct { int spawn; @@ -45,8 +45,8 @@ struct StageText { void stagetext_free(void); void stagetext_draw(void); -StageText* stagetext_add(const char *text, complex pos, Alignment align, Font **font, Color clr, int delay, int lifetime, int fadeintime, int fadeouttime); -StageText* stagetext_add_numeric(int n, complex pos, Alignment align, Font **font, Color clr, int delay, int lifetime, int fadeintime, int fadeouttime); +StageText* stagetext_add(const char *text, complex pos, Alignment align, Font *font, Color clr, int delay, int lifetime, int fadeintime, int fadeouttime); +StageText* stagetext_add_numeric(int n, complex pos, Alignment align, Font *font, Color clr, int delay, int lifetime, int fadeintime, int fadeouttime); struct StageTextTable { complex pos; diff --git a/src/util/compat.h b/src/util/compat.h index 0a964a72..ff4dcb5f 100644 --- a/src/util/compat.h +++ b/src/util/compat.h @@ -113,6 +113,33 @@ typedef signed char schar; #undef complex #define complex _Complex double +// In case the C11 CMPLX macro is not present, try our best to provide a substitute +#if !defined CMPLX + #undef HAS_BUILTIN_COMPLEX + + #if defined __has_builtin + #if __has_builtin(__builtin_complex) + #define HAS_BUILTIN_COMPLEX + #endif + #else + #if defined __GNUC__ && defined __GNUC_MINOR__ + #if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)) + #define HAS_BUILTIN_COMPLEX + #endif + #endif + #endif + + #if defined HAS_BUILTIN_COMPLEX + #define CMPLX(re,im) __builtin_complex((double)(re), (double)(im)) + #elif defined __clang__ + #define CMPLX(re,im) (__extension__ (_Complex double){(double)(re), (double)(im)}) + #elif defined _Imaginary_I + #define CMPLX(re,im) (_Complex double)((double)(re) + _Imaginary_I * (double)(im)) + #else + #define CMPLX(re,im) (_Complex double)((double)(re) + _Complex_I * (double)(im)) + #endif +#endif + // Meeded for mingw; presumably yet again defined somewhere in windoze headers. #undef min #undef max diff --git a/src/util/geometry.c b/src/util/geometry.c index d5161653..be311af5 100644 --- a/src/util/geometry.c +++ b/src/util/geometry.c @@ -10,6 +10,8 @@ #include "geometry.h" +#include + bool point_in_ellipse(complex p, Ellipse e) { double Xp = creal(p); double Yp = cimag(p); @@ -76,3 +78,107 @@ bool lineseg_ellipse_intersect(LineSegment seg, Ellipse e) { Circle c = { .radius = creal(e.axes) / 2 }; return lineseg_circle_intersect(seg, c) >= 0; } + +bool rect_in_rect(Rect inner, Rect outer) { + return + rect_left(inner) >= rect_left(outer) && + rect_right(inner) <= rect_right(outer) && + rect_top(inner) >= rect_top(outer) && + rect_bottom(inner) <= rect_bottom(outer); +} + +bool rect_rect_intersect(Rect r1, Rect r2, bool edges) { + if( + rect_bottom(r1) < rect_top(r2) || + rect_top(r1) > rect_bottom(r2) || + rect_left(r1) > rect_right(r2) || + rect_right(r1) < rect_left(r2) + ) { + // Not even touching + return false; + } + + if(!edges && ( + rect_bottom(r1) == rect_top(r2) || + rect_top(r1) == rect_bottom(r2) || + rect_left(r1) == rect_right(r2) || + rect_right(r1) == rect_left(r2) + )) { + // Discard edge intersects + return false; + } + + if( + (rect_left(r1) == rect_right(r2) && rect_bottom(r1) == rect_top(r2)) || + (rect_left(r1) == rect_right(r2) && rect_bottom(r2) == rect_top(r1)) || + (rect_left(r2) == rect_right(r1) && rect_bottom(r1) == rect_top(r2)) || + (rect_left(r2) == rect_right(r1) && rect_bottom(r2) == rect_top(r1)) + ) { + // Discard corner intersects + return false; + } + + return true; +} + +bool rect_rect_intersection(Rect r1, Rect r2, bool edges, Rect *out) { + if(!rect_rect_intersect(r1, r2, edges)) { + return false; + } + + out->top_left = CMPLX( + fmax(rect_left(r1), rect_left(r2)), + fmax(rect_top(r1), rect_top(r2)) + ); + + out->bottom_right = CMPLX( + fmin(rect_right(r1), rect_right(r2)), + fmin(rect_bottom(r1), rect_bottom(r2)) + ); + + return true; +} + +bool rect_join(Rect *r1, Rect r2) { + if(rect_in_rect(r2, *r1)) { + return true; + } + + if(rect_in_rect(*r1, r2)) { + memcpy(r1, &r2, sizeof(r2)); + return true; + } + + if(!rect_rect_intersect(*r1, r2, true)) { + return false; + } + + if(rect_left(*r1) == rect_left(r2) && rect_right(*r1) == rect_right(r2)) { + // r2 is directly above/below r1 + double y_min = fmin(rect_top(*r1), rect_top(r2)); + double y_max = fmax(rect_bottom(*r1), rect_bottom(r2)); + + r1->top_left = CMPLX(creal(r1->top_left), y_min); + r1->bottom_right = CMPLX(creal(r1->bottom_right), y_max); + + return true; + } + + if(rect_top(*r1) == rect_top(r2) && rect_bottom(*r1) == rect_bottom(r2)) { + // r2 is directly left/right to r1 + double x_min = fmin(rect_left(*r1), rect_left(r2)); + double x_max = fmax(rect_right(*r1), rect_right(r2)); + + r1->top_left = CMPLX(x_min, cimag(r1->top_left)); + r1->bottom_right = CMPLX(x_max, cimag(r1->bottom_right)); + + return true; + } + + return false; +} + +void rect_set_xywh(Rect *rect, double x, double y, double w, double h) { + rect->top_left = CMPLX(x, y); + rect->bottom_right = CMPLX(w, h) + rect->top_left; +} diff --git a/src/util/geometry.h b/src/util/geometry.h index 11851635..117d96ac 100644 --- a/src/util/geometry.h +++ b/src/util/geometry.h @@ -47,3 +47,61 @@ typedef struct Rect { bool point_in_ellipse(complex p, Ellipse e) attr_const; double lineseg_circle_intersect(LineSegment seg, Circle c) attr_const; bool lineseg_ellipse_intersect(LineSegment seg, Ellipse e) attr_const; + +static inline attr_must_inline attr_const +double rect_x(Rect r) { + return creal(r.top_left); +} + +static inline attr_must_inline attr_const +double rect_y(Rect r) { + return cimag(r.top_left); +} + +static inline attr_must_inline attr_const +double rect_width(Rect r) { + return creal(r.bottom_right) - creal(r.top_left); +} + +static inline attr_must_inline attr_const +double rect_height(Rect r) { + return cimag(r.bottom_right) - cimag(r.top_left); +} + +static inline attr_must_inline attr_const +double rect_top(Rect r) { + return cimag(r.top_left); +} + +static inline attr_must_inline attr_const +double rect_bottom(Rect r) { + return cimag(r.bottom_right); +} + +static inline attr_must_inline attr_const +double rect_left(Rect r) { + return creal(r.top_left); +} + +static inline attr_must_inline attr_const +double rect_right(Rect r) { + return creal(r.bottom_right); +} + +static inline attr_must_inline attr_const +double rect_area(Rect r) { + return rect_width(r) * rect_height(r); +} + +static inline attr_must_inline +void rect_move(Rect *r, complex pos) { + complex vector = pos - r->top_left; + r->top_left += vector; + r->bottom_right += vector; +} + +bool rect_in_rect(Rect inner, Rect outer) attr_const; +bool rect_rect_intersect(Rect r1, Rect r2, bool edges) attr_const; +bool rect_rect_intersection(Rect r1, Rect r2, bool edges, Rect *out) attr_pure attr_nonnull(4); +bool rect_join(Rect *r1, Rect r2) attr_pure attr_nonnull(1); +void rect_set_xywh(Rect *rect, double x, double y, double w, double h) attr_nonnull(1); diff --git a/src/util/kvparser.c b/src/util/kvparser.c index cd6a50d5..244965e3 100644 --- a/src/util/kvparser.c +++ b/src/util/kvparser.c @@ -85,31 +85,17 @@ bool parse_keyvalue_file_cb(const char *filename, KVCallback callback, void *dat } static bool kvcallback_hashtable(const char *key, const char *val, void *data) { - Hashtable *ht = data; - hashtable_set_string(ht, key, strdup((void*)val)); + ht_str2ptr_t *ht = data; + ht_set(ht, key, strdup(val)); return true; } -Hashtable* parse_keyvalue_stream(SDL_RWops *strm) { - Hashtable *ht = hashtable_new_stringkeys(); - - if(!parse_keyvalue_stream_cb(strm, kvcallback_hashtable, ht)) { - hashtable_free(ht); - ht = NULL; - } - - return ht; +bool parse_keyvalue_stream(SDL_RWops *strm, ht_str2ptr_t *ht) { + return parse_keyvalue_stream_cb(strm, kvcallback_hashtable, ht); } -Hashtable* parse_keyvalue_file(const char *filename) { - Hashtable *ht = hashtable_new_stringkeys(); - - if(!parse_keyvalue_file_cb(filename, kvcallback_hashtable, ht)) { - hashtable_free(ht); - ht = NULL; - } - - return ht; +bool parse_keyvalue_file(const char *filename, ht_str2ptr_t *ht) { + return parse_keyvalue_file_cb(filename, kvcallback_hashtable, ht); } static bool kvcallback_spec(const char *key, const char *val, void *data) { @@ -121,7 +107,11 @@ static bool kvcallback_spec(const char *key, const char *val, void *data) { } if(s->out_int) { - *s->out_int = strtol(val, NULL, 10); + *s->out_int = strtol(val, NULL, 0); + } + + if(s->out_long) { + *s->out_long = strtol(val, NULL, 0); } if(s->out_float) { diff --git a/src/util/kvparser.h b/src/util/kvparser.h index b43eeba3..8a41c718 100644 --- a/src/util/kvparser.h +++ b/src/util/kvparser.h @@ -22,13 +22,14 @@ typedef struct KVSpec { char **out_str; int *out_int; + long *out_long; double *out_double; float *out_float; } KVSpec; bool parse_keyvalue_stream_cb(SDL_RWops *strm, KVCallback callback, void *data); bool parse_keyvalue_file_cb(const char *filename, KVCallback callback, void *data); -Hashtable* parse_keyvalue_stream(SDL_RWops *strm); -Hashtable* parse_keyvalue_file(const char *filename); +bool parse_keyvalue_stream(SDL_RWops *strm, ht_str2ptr_t *hashtable); +bool parse_keyvalue_file(const char *filename, ht_str2ptr_t *hashtable); bool parse_keyvalue_stream_with_spec(SDL_RWops *strm, KVSpec *spec); bool parse_keyvalue_file_with_spec(const char *filename, KVSpec *spec); diff --git a/src/util/meson.build b/src/util/meson.build index 4d3ff58c..29c94ce2 100644 --- a/src/util/meson.build +++ b/src/util/meson.build @@ -9,6 +9,7 @@ util_src = files( 'kvparser.c', 'miscmath.c', 'pngcruft.c', + 'rectpack.c', 'stringops.c', ) diff --git a/src/util/rectpack.c b/src/util/rectpack.c new file mode 100644 index 00000000..b50e5ac2 --- /dev/null +++ b/src/util/rectpack.c @@ -0,0 +1,173 @@ +/* + * This software is licensed under the terms of the MIT-License + * See COPYING for further information. + * --- + * Copyright (c) 2011-2018, Lukas Weber . + * Copyright (c) 2012-2018, Andrei Alexeyev . + */ + +#include "taisei.h" + +#include "rectpack.h" +#include "util.h" + +typedef struct Section { + LIST_INTERFACE(struct Section); + Rect rect; +} Section; + +typedef struct RectPack { + double width; + double height; + Section *sections; +} RectPack; + +static void add_section(RectPack *rp, Rect *srect) { + Section *s = calloc(1, sizeof(Section)); + memcpy(&s->rect, srect, sizeof(*srect)); + list_push(&rp->sections, s); +} + +RectPack* rectpack_new(double width, double height) { + RectPack *rp = calloc(1, sizeof(RectPack)); + rp->width = width; + rp->height = height; + rectpack_reset(rp); + return rp; +} + +static void delete_all_sections(RectPack *rp) { + list_free_all(&rp->sections); +} + +void rectpack_reset(RectPack *rp) { + delete_all_sections(rp); + add_section(rp, &(Rect) { CMPLX(0, 0), CMPLX(rp->width, rp->height) }); +} + +void rectpack_free(RectPack *rp) { + delete_all_sections(rp); + free(rp); +} + +static bool fits_surface(RectPack *rp, double width, double height) { + assert(width > 0); + assert(height > 0); + + return width <= rp->width && height <= rp->height; +} + +static double section_fitness(Section *s, double w, double h) { + double sw = rect_width(s->rect); + double sh = rect_height(s->rect); + + if(w > sw || h > sh) { + return NAN; + } + + return fmin(sw - w, sh - h); +} + +static Section* select_fittest_section(RectPack *rp, double width, double height, double *out_fitness) { + Section *ret = NULL; + double fitness = INFINITY; + + for(Section *s = rp->sections; s; s = s->next) { + double f = section_fitness(s, width, height); + + if(!isnan(f) && f < fitness) { + ret = s; + fitness = f; + } + } + + if(ret != NULL && out_fitness != NULL) { + *out_fitness = fitness; + } + + return ret; +} + +static void split_horizontal(RectPack *rp, Section *s, double width, double height) { + Rect r = { 0 }; + + if(height < rect_height(s->rect)) { + rect_set_xywh(&r, + rect_x(s->rect), + rect_y(s->rect) + height, + rect_width(s->rect), + rect_height(s->rect) - height + ); + add_section(rp, &r); + } + + if(width < rect_width(s->rect)) { + rect_set_xywh(&r, + rect_x(s->rect) + width, + rect_y(s->rect), + rect_width(s->rect) - width, + height + ); + add_section(rp, &r); + } +} + +static void split_vertical(RectPack *rp, Section *s, double width, double height) { + Rect r = { 0 }; + + if(height < rect_height(s->rect)) { + rect_set_xywh(&r, + rect_x(s->rect), + rect_y(s->rect) + height, + width, + rect_height(s->rect) - height + ); + add_section(rp, &r); + } + + if(width < rect_width(s->rect)) { + rect_set_xywh(&r, + rect_x(s->rect) + width, + rect_y(s->rect), + rect_width(s->rect) - width, + rect_height(s->rect) + ); + add_section(rp, &r); + } +} + +static void split(RectPack *rp, Section *s, double width, double height) { + + /* + if(rect_width(s->rect) - width < rect_height(s->rect) - height) { + split_vertical(rp, s, width, height); + } else { + split_horizontal(rp, s, width, height); + } + */ + + if(width * (rect_height(s->rect) - height) <= height * (rect_width(s->rect) - width)) { + split_horizontal(rp, s, width, height); + } else { + split_vertical(rp, s, width, height); + } +} + +bool rectpack_add(RectPack *rp, double width, double height, Rect *out_rect) { + if(!fits_surface(rp, width, height)) { + return false; + } + + Section *s = select_fittest_section(rp, width, height, NULL); + + if(s == NULL) { + return false; + } + + list_unlink(&rp->sections, s); + split(rp, s, width, height); + rect_set_xywh(out_rect, rect_x(s->rect), rect_y(s->rect), width, height); + free(s); + + return true; +} diff --git a/src/util/rectpack.h b/src/util/rectpack.h new file mode 100644 index 00000000..171ef800 --- /dev/null +++ b/src/util/rectpack.h @@ -0,0 +1,18 @@ +/* + * This software is licensed under the terms of the MIT-License + * See COPYING for further information. + * --- + * Copyright (c) 2011-2018, Lukas Weber . + * Copyright (c) 2012-2018, Andrei Alexeyev . + */ + +#include "taisei.h" + +#include "geometry.h" + +typedef struct RectPack RectPack; + +RectPack* rectpack_new(double width, double height); +void rectpack_reset(RectPack *rp); +void rectpack_free(RectPack *rp); +bool rectpack_add(RectPack *rp, double width, double height, Rect *out_rect); diff --git a/src/util/stringops.c b/src/util/stringops.c index b8aea411..0528c201 100644 --- a/src/util/stringops.c +++ b/src/util/stringops.c @@ -180,6 +180,13 @@ char* strappend(char **dst, char *src) { return *dst; } + +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + #define UCS4_ID "UCS-4BE" +#else + #define UCS4_ID "UCS-4LE" +#endif + uint32_t* ucs4chr(const uint32_t *ucs4, uint32_t chr) { for(; *ucs4 != chr; ++ucs4) { if(!*ucs4) { @@ -198,14 +205,14 @@ size_t ucs4len(const uint32_t *ucs4) { uint32_t* utf8_to_ucs4(const char *utf8) { assert(utf8 != NULL); - uint32_t *ucs4 = (uint32_t*)(void*)SDL_iconv_string("UCS-4", "UTF-8", utf8, strlen(utf8) + 1); + uint32_t *ucs4 = (uint32_t*)(void*)SDL_iconv_string(UCS4_ID, "UTF-8", utf8, strlen(utf8) + 1); assert(ucs4 != NULL); return ucs4; } char* ucs4_to_utf8(const uint32_t *ucs4) { assert(ucs4 != NULL); - char *utf8 = SDL_iconv_string("UTF-8", "UCS-4", (const char*)ucs4, sizeof(uint32_t) * (ucs4len(ucs4) + 1)); + char *utf8 = SDL_iconv_string("UTF-8", UCS4_ID, (const char*)ucs4, sizeof(uint32_t) * (ucs4len(ucs4) + 1)); assert(utf8 != NULL); return utf8; } @@ -315,3 +322,94 @@ size_t filename_timestamp(char *buf, size_t buf_size, SystemTime systime) { return snprintf(buf, buf_size, "%s-%s", buf_datetime, buf_msecs); } + +uint32_t utf8_getch(const char **src) { + // Ported from SDL_ttf and slightly modified + + assert(*src != NULL); + + const uint8_t *p = *(const uint8_t**)src; + size_t left = 0; + bool overlong = false; + uint32_t ch = UNICODE_UNKNOWN; + + if(**src == 0) { + return UNICODE_UNKNOWN; + } + + if(p[0] >= 0xFC) { + if((p[0] & 0xFE) == 0xFC) { + if(p[0] == 0xFC && (p[1] & 0xFC) == 0x80) { + overlong = true; + } + ch = p[0] & 0x01; + left = 5; + } + } else if(p[0] >= 0xF8) { + if((p[0] & 0xFC) == 0xF8) { + if(p[0] == 0xF8 && (p[1] & 0xF8) == 0x80) { + overlong = true; + } + ch = p[0] & 0x03; + left = 4; + } + } else if(p[0] >= 0xF0) { + if((p[0] & 0xF8) == 0xF0) { + if(p[0] == 0xF0 && (p[1] & 0xF0) == 0x80) { + overlong = true; + } + ch = p[0] & 0x07; + left = 3; + } + } else if(p[0] >= 0xE0) { + if((p[0] & 0xF0) == 0xE0) { + if(p[0] == 0xE0 && (p[1] & 0xE0) == 0x80) { + overlong = true; + } + ch = p[0] & 0x0F; + left = 2; + } + } else if(p[0] >= 0xC0) { + if((p[0] & 0xE0) == 0xC0) { + if((p[0] & 0xDE) == 0xC0) { + overlong = true; + } + ch = p[0] & 0x1F; + left = 1; + } + } else { + if((p[0] & 0x80) == 0x00) { + ch = p[0]; + } + } + + ++*src; + + while(left > 0 && **src != 0) { + ++p; + + if((p[0] & 0xC0) != 0x80) { + ch = UNICODE_UNKNOWN; + break; + } + + ch <<= 6; + ch |= (p[0] & 0x3F); + + ++*src; + --left; + } + + if( + overlong || + left > 0 || + (ch >= 0xD800 && ch <= 0xDFFF) || + (ch == 0xFFFE || ch == 0xFFFF) || + ch > 0x10FFFF + ) { + ch = UNICODE_UNKNOWN; + } + + return ch; +} + diff --git a/src/util/stringops.h b/src/util/stringops.h index 318d0ac8..310472c1 100644 --- a/src/util/stringops.h +++ b/src/util/stringops.h @@ -12,6 +12,10 @@ #include #include +#define UNICODE_UNKNOWN 0xFFFD +#define UNICODE_BOM_NATIVE 0xFEFF +#define UNICODE_BOM_SWAPPED 0xFFFE + #undef strlcat #define strlcat SDL_strlcat @@ -43,6 +47,8 @@ size_t ucs4len(const uint32_t *ucs4); uint32_t* utf8_to_ucs4(const char *utf8); char* ucs4_to_utf8(const uint32_t *ucs4); +uint32_t utf8_getch(const char **src) attr_nonnull(1); + uint32_t crc32str(uint32_t crc, const char *str); // XXX: Not sure if this is the appropriate header for this diff --git a/src/vfs/union.c b/src/vfs/union.c index 0a792ba9..6f687d5f 100644 --- a/src/vfs/union.c +++ b/src/vfs/union.c @@ -80,7 +80,7 @@ static VFSNode* vfs_union_locate(VFSNode *node, const char *path) { } typedef struct VFSUnionIterData { - Hashtable *visited; + ht_str2int_t visited; // XXX: this may not be the most efficient implementation of a "set" structure... ListContainer *current; void *opaque; } VFSUnionIterData; @@ -93,10 +93,7 @@ static const char* vfs_union_iter(VFSNode *node, void **opaque) { i = malloc(sizeof(VFSUnionIterData)); i->current = node->_members_; i->opaque = NULL; - - // XXX: this may not be the most efficient implementation of a "set" structure... - i->visited = hashtable_new_stringkeys(); - + ht_create(&i->visited); *opaque = i; } @@ -111,11 +108,11 @@ static const char* vfs_union_iter(VFSNode *node, void **opaque) { continue; } - if(hashtable_get_string(i->visited, r)) { + if(ht_get(&i->visited, r, false)) { continue; } - hashtable_set_string(i->visited, r, (void*)true); + ht_set(&i->visited, r, true); break; } @@ -126,7 +123,7 @@ static void vfs_union_iter_stop(VFSNode *node, void **opaque) { VFSUnionIterData *i = *opaque; if(i) { - hashtable_free(i->visited); + ht_destroy(&i->visited); free(i); } diff --git a/src/vfs/vdir.c b/src/vfs/vdir.c index 3248215d..7f754c53 100644 --- a/src/vfs/vdir.c +++ b/src/vfs/vdir.c @@ -10,16 +10,16 @@ #include "vdir.h" -#define _contents_ data1 +#define VDIR_TABLE(vdir) ((ht_str2ptr_t*)((vdir)->data1)) static void vfs_vdir_attach_node(VFSNode *vdir, const char *name, VFSNode *node) { - VFSNode *oldnode = hashtable_get_string(vdir->_contents_, name); + VFSNode *oldnode = ht_get(VDIR_TABLE(vdir), name, NULL); if(oldnode) { vfs_decref(oldnode); } - hashtable_set_string(vdir->_contents_, name, node); + ht_set(VDIR_TABLE(vdir), name, node); } static VFSNode* vfs_vdir_locate(VFSNode *vdir, const char *path) { @@ -30,7 +30,7 @@ static VFSNode* vfs_vdir_locate(VFSNode *vdir, const char *path) { strcpy(mutpath, path); vfs_path_split_left(mutpath, &primpath, &subpath); - if((node = hashtable_get_string(vdir->_contents_, mutpath))) { + if((node = ht_get(VDIR_TABLE(vdir), mutpath, NULL))) { return vfs_locate(node, subpath); } @@ -38,21 +38,21 @@ static VFSNode* vfs_vdir_locate(VFSNode *vdir, const char *path) { } static const char* vfs_vdir_iter(VFSNode *vdir, void **opaque) { - char *ret = NULL; + ht_str2ptr_iter_t *iter = *opaque; - if(!*opaque) { - *opaque = hashtable_iter(vdir->_contents_); + if(!iter) { + iter = calloc(1, sizeof(*iter)); + ht_iter_begin(VDIR_TABLE(vdir), iter); + } else { + ht_iter_next(iter); } - if(!hashtable_iter_next((HashtableIterator*)*opaque, (void**)&ret, NULL)) { - *opaque = NULL; - } - - return ret; + return iter->has_data ? iter->key : NULL; } static void vfs_vdir_iter_stop(VFSNode *vdir, void **opaque) { if(*opaque) { + ht_iter_end((ht_str2ptr_iter_t*)*opaque); free(*opaque); *opaque = NULL; } @@ -66,15 +66,18 @@ static VFSInfo vfs_vdir_query(VFSNode *vdir) { } static void vfs_vdir_free(VFSNode *vdir) { - Hashtable *ht = vdir->_contents_; - HashtableIterator *i; - VFSNode *child; + ht_str2ptr_t *ht = VDIR_TABLE(vdir); + ht_str2ptr_iter_t iter; - for(i = hashtable_iter(ht); hashtable_iter_next(i, NULL, (void**)&child);) { - vfs_decref(child); + ht_iter_begin(ht, &iter); + + for(; iter.has_data; ht_iter_next(&iter)) { + vfs_decref(iter.value); } - hashtable_free(ht); + ht_iter_end(&iter); + ht_destroy(ht); + free(ht); } static bool vfs_vdir_mount(VFSNode *vdir, const char *mountpoint, VFSNode *subtree) { @@ -90,12 +93,12 @@ static bool vfs_vdir_mount(VFSNode *vdir, const char *mountpoint, VFSNode *subtr static bool vfs_vdir_unmount(VFSNode *vdir, const char *mountpoint) { VFSNode *mountee; - if(!(mountee = hashtable_get_string(vdir->_contents_, mountpoint))) { + if(!(mountee = ht_get(VDIR_TABLE(vdir), mountpoint, NULL))) { vfs_set_error("Mountpoint '%s' doesn't exist", mountpoint); return false; } - hashtable_unset_string(vdir->_contents_, mountpoint); + ht_unset(VDIR_TABLE(vdir), mountpoint); vfs_decref(mountee); return true; } @@ -131,5 +134,5 @@ static VFSNodeFuncs vfs_funcs_vdir = { void vfs_vdir_init(VFSNode *node) { node->funcs = &vfs_funcs_vdir; - node->_contents_ = hashtable_new_stringkeys(); + node->data1 = ht_str2ptr_new(); } diff --git a/src/vfs/zipfile.c b/src/vfs/zipfile.c index 795c06f5..fa8ff505 100644 --- a/src/vfs/zipfile.c +++ b/src/vfs/zipfile.c @@ -152,7 +152,7 @@ static void vfs_zipfile_free(VFSNode *node) { vfs_decref(zdata->source); } - hashtable_free(zdata->pathmap); + ht_destroy(&zdata->pathmap); free(zdata); } } @@ -186,9 +186,9 @@ static char* vfs_zipfile_repr(VFSNode *node) { static VFSNode* vfs_zipfile_locate(VFSNode *node, const char *path) { VFSZipFileTLS *tls = vfs_zipfile_get_tls(node, true); VFSZipFileData *zdata = node->data1; - zip_int64_t idx = (zip_int64_t)((intptr_t)hashtable_get_string(zdata->pathmap, path) - 1); + int64_t idx; - if(idx < 0) { + if(!ht_lookup(&zdata->pathmap, path, &idx)) { idx = zip_name_locate(tls->zip, path, 0); } @@ -294,9 +294,10 @@ static VFSNodeFuncs vfs_funcs_zipfile = { static void vfs_zipfile_init_pathmap(VFSNode *node) { VFSZipFileData *zdata = node->data1; VFSZipFileTLS *tls = vfs_zipfile_get_tls(node, true); - zdata->pathmap = hashtable_new_stringkeys(); zip_int64_t num = zip_get_num_entries(tls->zip, 0); + ht_create(&zdata->pathmap); + for(zip_int64_t i = 0; i < num; ++i) { const char *original = zip_get_name(tls->zip, i, 0); char normalized[strlen(original) + 1]; @@ -311,7 +312,7 @@ static void vfs_zipfile_init_pathmap(VFSNode *node) { } if(strcmp(original, normalized)) { - hashtable_set_string(zdata->pathmap, normalized, (void*)((intptr_t)i + 1)); + ht_set(&zdata->pathmap, normalized, i); } } } diff --git a/src/vfs/zipfile_impl.h b/src/vfs/zipfile_impl.h index 0fa74fc2..434104ce 100644 --- a/src/vfs/zipfile_impl.h +++ b/src/vfs/zipfile_impl.h @@ -23,7 +23,7 @@ typedef struct VFSZipFileTLS { typedef struct VFSZipFileData { VFSNode *source; - Hashtable *pathmap; + ht_str2int_t pathmap; SDL_TLSID tls_id; } VFSZipFileData;