Text rendering rewrite and optimizations; some refactoring (#129)
* wip font rendering stuff; hashtable monstrosity is temporary * various text rendering fixes/improvements * HashTables™ 3.0 * Add some comments to aid navigating the hashtable macro maze * overhaul text rendering API; add default and example shaders * text: implement text_render for spellcard effect; misc fixes * README: update dependencies Bye SDL_ttf, hello freetype2. * text_draw: fix resolution/scale-dependent bugs * make text_draw fallback to the current shader, fix hud and stagetext * repair the bgm loading * fix spell practice mode * fix walloftext forgot one site of text_draw earlier * fix wrapped text rendering * fix and simplify the hud text shader * dynamic glyph cache * implement font size change on window resize/quality setting change/etc. * rename text shaders for consistency * preloads for fonts and text shaders * make the stagetext shader look somewhat better * text_render: attempt to normalize height * small improvement for stagetext
This commit is contained in:
parent
433b9869e2
commit
322edd0dce
75 changed files with 3620 additions and 1429 deletions
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
]
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
|
||||
glsl_objects = standard.vert hud_text.frag
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
|
||||
glsl_objects = standard.vert stagetext.frag
|
7
resources/shader/text_default.frag.glsl
Normal file
7
resources/shader/text_default.frag.glsl
Normal file
|
@ -0,0 +1,7 @@
|
|||
#version 330 core
|
||||
|
||||
#include "interface/sprite.glslh"
|
||||
|
||||
void main(void) {
|
||||
fragColor = color * vec4(1, 1, 1, texture(tex, texCoord).r);
|
||||
}
|
1
resources/shader/text_default.prog
Normal file
1
resources/shader/text_default.prog
Normal file
|
@ -0,0 +1 @@
|
|||
glsl_objects = text_default.vert text_default.frag
|
7
resources/shader/text_default.vert.glsl
Normal file
7
resources/shader/text_default.vert.glsl
Normal file
|
@ -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"
|
30
resources/shader/text_example.frag.glsl
Normal file
30
resources/shader/text_example.frag.glsl
Normal file
|
@ -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);
|
||||
}
|
1
resources/shader/text_example.prog
Normal file
1
resources/shader/text_example.prog
Normal file
|
@ -0,0 +1 @@
|
|||
glsl_objects = text_example.vert text_example.frag
|
35
resources/shader/text_example.vert.glsl
Normal file
35
resources/shader/text_example.vert.glsl
Normal file
|
@ -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;
|
||||
}
|
9
resources/shader/text_hud.frag.glsl
Normal file
9
resources/shader/text_hud.frag.glsl
Normal file
|
@ -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);
|
||||
}
|
2
resources/shader/text_hud.prog
Normal file
2
resources/shader/text_hud.prog
Normal file
|
@ -0,0 +1,2 @@
|
|||
|
||||
glsl_objects = text_default.vert text_hud.frag
|
39
resources/shader/text_stagetext.frag.glsl
Normal file
39
resources/shader/text_stagetext.frag.glsl
Normal file
|
@ -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);
|
||||
}
|
2
resources/shader/text_stagetext.prog
Normal file
2
resources/shader/text_stagetext.prog
Normal file
|
@ -0,0 +1,2 @@
|
|||
|
||||
glsl_objects = text_example.vert text_stagetext.frag
|
|
@ -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);
|
||||
}
|
||||
|
|
45
src/boss.c
45
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)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
528
src/hashtable.c
528
src/hashtable.c
|
@ -8,530 +8,18 @@
|
|||
|
||||
#include "taisei.h"
|
||||
|
||||
#define HT_IMPL
|
||||
#include "hashtable.h"
|
||||
#include "list.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <zlib.h>
|
||||
#include <stdio.h>
|
||||
#include <SDL_mutex.h>
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
|
102
src/hashtable.h
102
src/hashtable.h
|
@ -9,67 +9,55 @@
|
|||
#pragma once
|
||||
#include "taisei.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#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
|
||||
|
|
1045
src/hashtable.inc.h
Normal file
1045
src/hashtable.inc.h
Normal file
File diff suppressed because it is too large
Load diff
10
src/hashtable_incproxy.inc.h
Normal file
10
src/hashtable_incproxy.inc.h
Normal file
|
@ -0,0 +1,10 @@
|
|||
|
||||
#ifdef _HT_IMPL
|
||||
#define HT_IMPL
|
||||
#endif
|
||||
|
||||
#ifdef _HT_DECL
|
||||
#define HT_DECL
|
||||
#endif
|
||||
|
||||
#include "hashtable.inc.h"
|
191
src/hashtable_predefs.inc.h
Normal file
191
src/hashtable_predefs.inc.h
Normal file
|
@ -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
|
|
@ -212,6 +212,8 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
|
||||
free_cli_action(&a);
|
||||
|
||||
htutil_init();
|
||||
vfs_setup(false);
|
||||
|
||||
if(headless) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 },
|
||||
};
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
1259
src/resource/font.c
1259
src/resource/font.c
File diff suppressed because it is too large
Load diff
|
@ -9,52 +9,111 @@
|
|||
#pragma once
|
||||
#include "taisei.h"
|
||||
|
||||
#include <SDL_ttf.h>
|
||||
|
||||
#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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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);
|
||||
|
|
251
src/stagedraw.c
251
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);
|
||||
|
||||
|
|
|
@ -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++;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
|
||||
#include "geometry.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -9,6 +9,7 @@ util_src = files(
|
|||
'kvparser.c',
|
||||
'miscmath.c',
|
||||
'pngcruft.c',
|
||||
'rectpack.c',
|
||||
'stringops.c',
|
||||
)
|
||||
|
||||
|
|
173
src/util/rectpack.c
Normal file
173
src/util/rectpack.c
Normal file
|
@ -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 <laochailan@web.de>.
|
||||
* Copyright (c) 2012-2018, Andrei Alexeyev <akari@alienslab.net>.
|
||||
*/
|
||||
|
||||
#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;
|
||||
}
|
18
src/util/rectpack.h
Normal file
18
src/util/rectpack.h
Normal file
|
@ -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 <laochailan@web.de>.
|
||||
* Copyright (c) 2012-2018, Andrei Alexeyev <akari@alienslab.net>.
|
||||
*/
|
||||
|
||||
#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);
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,10 @@
|
|||
#include <time.h>
|
||||
#include <SDL.h>
|
||||
|
||||
#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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ typedef struct VFSZipFileTLS {
|
|||
|
||||
typedef struct VFSZipFileData {
|
||||
VFSNode *source;
|
||||
Hashtable *pathmap;
|
||||
ht_str2int_t pathmap;
|
||||
SDL_TLSID tls_id;
|
||||
} VFSZipFileData;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue