From 5baaa77d5f448a3da58d4edeb52cc96649d031f7 Mon Sep 17 00:00:00 2001 From: Cameron Reikes Date: Tue, 23 May 2023 18:28:47 -0700 Subject: [PATCH] Optimize wrapped text function by not remeasuring previous text --- main.c | 241 +++++++++++++++++++++++++++++++++------------------------ 1 file changed, 138 insertions(+), 103 deletions(-) diff --git a/main.c b/main.c index d6940d4..95ec45b 100644 --- a/main.c +++ b/main.c @@ -1082,9 +1082,14 @@ void end_text_input(char *what_player_said_cstr) sg_image image_font = { 0 }; +Vec2 image_font_size = { 0 }; // this image's size is queried a lot, and img_size seems to be slow when profiled + float font_line_advance = 0.0f; const float font_size = 32.0; +float font_scale; +unsigned char *fontBuffer = 0; // font file data. Can't be freed until program quits, because used to get character width stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs +stbtt_fontinfo font; static struct @@ -1155,6 +1160,13 @@ Color blendalpha(Color c, float alpha) return to_return; } +// in pixels +Vec2 img_size(sg_image img) +{ + sg_image_info info = sg_query_image_info(img); + return V2((float)info.width, (float)info.height); +} + void init(void) { #ifdef WEB @@ -1200,7 +1212,7 @@ void init(void) size_t size = ftell(fontFile); /* how long is the file ? */ fseek(fontFile, 0, SEEK_SET); /* reset */ - unsigned char *fontBuffer = malloc(size); + fontBuffer = calloc(size, 1); fread(fontBuffer, size, 1, fontFile); fclose(fontFile); @@ -1230,17 +1242,17 @@ void init(void) } }); - stbtt_fontinfo font; + image_font_size = img_size(image_font); + stbtt_InitFont(&font, fontBuffer, 0); int ascent = 0; int descent = 0; int lineGap = 0; - float scale = stbtt_ScaleForPixelHeight(&font, font_size); + font_scale = stbtt_ScaleForPixelHeight(&font, font_size); stbtt_GetFontVMetrics(&font, &ascent, &descent, &lineGap); - font_line_advance = (float)(ascent - descent + lineGap) * scale * 0.75f; + font_line_advance = (float)(ascent - descent + lineGap) * font_scale * 0.75f; free(font_bitmap_rgba); - free(fontBuffer); } state.bind.vertex_buffers[0] = sg_make_buffer(&(sg_buffer_desc) @@ -1397,12 +1409,6 @@ Vec2 cam_offset() return to_return; } -// in pixels -Vec2 img_size(sg_image img) -{ - sg_image_info info = sg_query_image_info(img); - return V2((float)info.width, (float)info.height); -} #define IMG(img) img, full_region(img) @@ -1907,93 +1913,111 @@ typedef struct TextParams // returns bounds. To measure text you can set dry run to true and get the bounds AABB draw_text(TextParams t) { - size_t text_len = t.text.size; AABB bounds = { 0 }; - float y = 0.0; - float x = 0.0; - for (int i = 0; i < text_len; i++) + PROFILE_SCOPE("draw text") { - stbtt_aligned_quad q; - float old_y = y; - stbtt_GetBakedQuad(cdata, 512, 512, t.text.str[i]-32, &x, &y, &q, 1); - float difference = y - old_y; - y = old_y + difference; - - Vec2 size = V2(q.x1 - q.x0, q.y1 - q.y0); - if (t.text.str[i] == '\n') + size_t text_len = t.text.size; + float y = 0.0; + float x = 0.0; + for (int i = 0; i < text_len; i++) { + stbtt_aligned_quad q; + float old_y = y; + PROFILE_SCOPE("get baked quad") + stbtt_GetBakedQuad(cdata, 512, 512, t.text.str[i]-32, &x, &y, &q, 1); + float difference = y - old_y; + y = old_y + difference; + + Vec2 size = V2(q.x1 - q.x0, q.y1 - q.y0); + if (t.text.str[i] == '\n') + { #ifdef DEVTOOLS - y += font_size*0.75f; // arbitrary, only debug t.text has newlines - x = 0.0; + y += font_size*0.75f; // arbitrary, only debug t.text has newlines + x = 0.0; #else - assert(false); + assert(false); #endif - } - if (size.Y > 0.0 && size.X > 0.0) - { // spaces (and maybe other characters) produce quads of size 0 - Quad to_draw = { - .points = { - AddV2(V2(q.x0, -q.y0), V2(0.0f, 0.0f)), - AddV2(V2(q.x0, -q.y0), V2(size.X, 0.0f)), - AddV2(V2(q.x0, -q.y0), V2(size.X, -size.Y)), - AddV2(V2(q.x0, -q.y0), V2(0.0f, -size.Y)), - }, - }; - - for (int i = 0; i < 4; i++) - { - to_draw.points[i] = MulV2F(to_draw.points[i], t.scale); } + if (size.Y > 0.0 && size.X > 0.0) + { // spaces (and maybe other characters) produce quads of size 0 + Quad to_draw; + PROFILE_SCOPE("Calculate to draw quad") + { + to_draw = (Quad){ + .points = { + AddV2(V2(q.x0, -q.y0), V2(0.0f, 0.0f)), + AddV2(V2(q.x0, -q.y0), V2(size.X, 0.0f)), + AddV2(V2(q.x0, -q.y0), V2(size.X, -size.Y)), + AddV2(V2(q.x0, -q.y0), V2(0.0f, -size.Y)), + }, + }; + } - AABB font_atlas_region = (AABB) - { - .upper_left = V2(q.s0, q.t0), - .lower_right = V2(q.s1, q.t1), - }; - font_atlas_region.upper_left.X *= img_size(image_font).X; - font_atlas_region.lower_right.X *= img_size(image_font).X; - font_atlas_region.upper_left.Y *= img_size(image_font).Y; - font_atlas_region.lower_right.Y *= img_size(image_font).Y; - - for (int i = 0; i < 4; i++) - { - bounds.upper_left.X = fminf(bounds.upper_left.X, to_draw.points[i].X); - bounds.upper_left.Y = fmaxf(bounds.upper_left.Y, to_draw.points[i].Y); - bounds.lower_right.X = fmaxf(bounds.lower_right.X, to_draw.points[i].X); - bounds.lower_right.Y = fminf(bounds.lower_right.Y, to_draw.points[i].Y); - } + PROFILE_SCOPE("Scale points") + for (int i = 0; i < 4; i++) + { + to_draw.points[i] = MulV2F(to_draw.points[i], t.scale); + } - for (int i = 0; i < 4; i++) - { - to_draw.points[i] = AddV2(to_draw.points[i], t.pos); - } + AABB font_atlas_region = (AABB) + { + .upper_left = V2(q.s0, q.t0), + .lower_right = V2(q.s1, q.t1), + }; + PROFILE_SCOPE("Scaling font atlas region to img font size") + { + font_atlas_region.upper_left.X *= image_font_size.X; + font_atlas_region.lower_right.X *= image_font_size.X; + font_atlas_region.upper_left.Y *= image_font_size.Y; + font_atlas_region.lower_right.Y *= image_font_size.Y; + } - if (!t.dry_run) - { - Color col = t.color; - if (t.colors) + PROFILE_SCOPE("bounds computation") + for (int i = 0; i < 4; i++) + { + bounds.upper_left.X = fminf(bounds.upper_left.X, to_draw.points[i].X); + bounds.upper_left.Y = fmaxf(bounds.upper_left.Y, to_draw.points[i].Y); + bounds.lower_right.X = fmaxf(bounds.lower_right.X, to_draw.points[i].X); + bounds.lower_right.Y = fminf(bounds.lower_right.Y, to_draw.points[i].Y); + } + + PROFILE_SCOPE("shifting points") + for (int i = 0; i < 4; i++) { - col = t.colors[i]; + to_draw.points[i] = AddV2(to_draw.points[i], t.pos); } - if (false) // drop shadow, don't really like it - if (t.world_space) + if (!t.dry_run) + { + PROFILE_SCOPE("Actually drawing") { - Quad shadow_quad = to_draw; - for (int i = 0; i < 4; i++) + Color col = t.color; + if (t.colors) { - shadow_quad.points[i] = AddV2(shadow_quad.points[i], V2(0.0, -1.0)); + col = t.colors[i]; } - draw_quad((DrawParams) { t.world_space, shadow_quad, image_font, font_atlas_region, (Color) { 0.0f, 0.0f, 0.0f, 0.4f }, t.clip_to, .layer = LAYER_UI_FG, .do_clipping = t.do_clipping }); - } - draw_quad((DrawParams) { t.world_space, to_draw, image_font, font_atlas_region, col, t.clip_to, .layer = LAYER_UI_FG, .do_clipping = t.do_clipping }); + if (false) // drop shadow, don't really like it + if (t.world_space) + { + Quad shadow_quad = to_draw; + for (int i = 0; i < 4; i++) + { + shadow_quad.points[i] = AddV2(shadow_quad.points[i], V2(0.0, -1.0)); + } + draw_quad((DrawParams) { t.world_space, shadow_quad, image_font, font_atlas_region, (Color) { 0.0f, 0.0f, 0.0f, 0.4f }, t.clip_to, .layer = LAYER_UI_FG, .do_clipping = t.do_clipping }); + } + + draw_quad((DrawParams) { t.world_space, to_draw, image_font, font_atlas_region, col, t.clip_to, .layer = LAYER_UI_FG, .do_clipping = t.do_clipping }); + } + } } } + + bounds.upper_left = AddV2(bounds.upper_left, t.pos); + bounds.lower_right = AddV2(bounds.lower_right, t.pos); } - bounds.upper_left = AddV2(bounds.upper_left, t.pos); - bounds.lower_right = AddV2(bounds.lower_right, t.pos); return bounds; } @@ -2281,6 +2305,13 @@ Vec2 move_and_slide(MoveSlideParams p) return result_pos; } +float character_width(int ascii_letter, float text_scale) +{ + int advanceWidth; + stbtt_GetCodepointHMetrics(&font, ascii_letter, &advanceWidth, 0); + return (float)advanceWidth * font_scale * text_scale; +} + typedef struct { bool dry_run; @@ -2308,13 +2339,11 @@ float draw_wrapped_text(WrappedTextParams p) Color colors_to_draw[MAX_SENTENCE_LENGTH] = { 0 }; size_t chars_from_sentence = 0; AABB line_bounds = { 0 }; + float current_line_width = 0.0f; while (chars_from_sentence <= sentence_len) { - memset(line_to_draw, 0, MAX_SENTENCE_LENGTH); - memcpy(line_to_draw, sentence_to_draw, chars_from_sentence); - - line_bounds = draw_text((TextParams) { !p.screen_space, true, MD_S8CString(line_to_draw), cursor, BLACK, p.text_scale, p.clip_to, .do_clipping = p.do_clipping}); - if (line_bounds.lower_right.X > p.at_point.X + p.max_width) + //line_bounds = draw_text((TextParams) { !p.screen_space, true, MD_S8CString(line_to_draw), cursor, BLACK, p.text_scale, p.clip_to, .do_clipping = p.do_clipping}); + if (current_line_width >= p.max_width) { // too big if (chars_from_sentence <= 0) chars_from_sentence = 1; // @CREDIT(warehouse56) always draw at least one character, if there's not enough room @@ -2322,6 +2351,11 @@ float draw_wrapped_text(WrappedTextParams p) break; } chars_from_sentence += 1; + memset(line_to_draw, 0, MAX_SENTENCE_LENGTH); + memcpy(line_to_draw, sentence_to_draw, chars_from_sentence); + //AABB next_character_bounds = draw_text((TextParams) { !p.screen_space, true, MD_S8((MD_u8*)line_to_draw + chars_from_sentence - 1, 1), cursor, BLACK, p.text_scale, p.clip_to, .do_clipping = p.do_clipping}); + //current_line_width += aabb_size(next_character_bounds).x; + current_line_width += character_width( *(line_to_draw + chars_from_sentence - 1), p.text_scale); } if (chars_from_sentence > sentence_len) chars_from_sentence--; memset(line_to_draw, 0, MAX_SENTENCE_LENGTH); @@ -3455,30 +3489,30 @@ void frame(void) #ifdef DESKTOP MD_ArenaTemp scratch = MD_GetScratch(0, 0); - const char *argument = 0; - MD_String8List dialog_elems = {0}; - ActionKind act = ACT_none; - - it->times_talked_to++; - if(it->memories.data[it->memories.cur_index-1].context.eavesdropped_from_party) - { - MD_S8ListPushFmt(scratch.arena, &dialog_elems, "Responding to eavesdropped: "); - } - if(it->npc_kind == NPC_TheBlacksmith && it->standing != STANDING_JOINED) - { - assert(it->times_talked_to == 1); - act = ACT_joins_player; - MD_S8ListPushFmt(scratch.arena, &dialog_elems, "Joining you..."); - } - else - { - MD_S8ListPushFmt(scratch.arena, &dialog_elems, "%d times talked", it->times_talked_to); - } - MD_String8 mocked_ai_response = {0}; - if(false) { + const char *argument = 0; + MD_String8List dialog_elems = {0}; + ActionKind act = ACT_none; + + it->times_talked_to++; + if(it->memories.data[it->memories.cur_index-1].context.eavesdropped_from_party) + { + MD_S8ListPushFmt(scratch.arena, &dialog_elems, "Responding to eavesdropped: "); + } + if(it->npc_kind == NPC_TheBlacksmith && it->standing != STANDING_JOINED) + { + assert(it->times_talked_to == 1); + act = ACT_joins_player; + MD_S8ListPushFmt(scratch.arena, &dialog_elems, "Joining you..."); + } + else + { + MD_S8ListPushFmt(scratch.arena, &dialog_elems, "%d times talked", it->times_talked_to); + } + + MD_StringJoin join = {0}; MD_String8 dialog = MD_S8ListJoin(scratch.arena, dialog_elems, &join); if (argument) @@ -4360,6 +4394,7 @@ void frame(void) void cleanup(void) { + free(fontBuffer); sg_shutdown(); hmfree(imui_state); Log("Cleaning up\n");