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:
Andrei Alexeyev 2018-06-30 00:36:51 +03:00 committed by GitHub
parent 433b9869e2
commit 322edd0dce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
75 changed files with 3620 additions and 1429 deletions

View file

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

View file

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

View file

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

View file

@ -1,2 +0,0 @@
glsl_objects = standard.vert hud_text.frag

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,2 +0,0 @@
glsl_objects = standard.vert stagetext.frag

View 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);
}

View file

@ -0,0 +1 @@
glsl_objects = text_default.vert text_default.frag

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

View 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);
}

View file

@ -0,0 +1 @@
glsl_objects = text_example.vert text_example.frag

View 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;
}

View 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);
}

View file

@ -0,0 +1,2 @@
glsl_objects = text_default.vert text_hud.frag

View 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);
}

View file

@ -0,0 +1,2 @@
glsl_objects = text_example.vert text_stagetext.frag

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

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

View file

@ -212,6 +212,8 @@ int main(int argc, char **argv) {
}
free_cli_action(&a);
htutil_init();
vfs_setup(false);
if(headless) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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" }, // dont 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);
}

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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, &params);
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) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -9,6 +9,7 @@ util_src = files(
'kvparser.c',
'miscmath.c',
'pngcruft.c',
'rectpack.c',
'stringops.c',
)

173
src/util/rectpack.c Normal file
View 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
View 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);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -23,7 +23,7 @@ typedef struct VFSZipFileTLS {
typedef struct VFSZipFileData {
VFSNode *source;
Hashtable *pathmap;
ht_str2int_t pathmap;
SDL_TLSID tls_id;
} VFSZipFileData;