From ba81077c2e0641c82efac498f9e6682b8de7d5bc Mon Sep 17 00:00:00 2001 From: Nunuhara Cabbage Date: Sun, 16 Apr 2023 13:36:27 -0700 Subject: [PATCH] Fix performance issue with PartsEngine text Don't allocate massive textures for each text object. Instead, allocate a texture for each character and then compose them onto an appropriately sized texture. Fixes a performance issue in Rance 01. --- include/gfx/font.h | 11 +++ src/parts/debug.c | 13 ++-- src/parts/parts.c | 6 +- src/parts/parts_internal.h | 16 ++++- src/parts/save.c | 4 +- src/parts/text.c | 135 +++++++++++++++++++++++++++---------- 6 files changed, 137 insertions(+), 48 deletions(-) diff --git a/include/gfx/font.h b/include/gfx/font.h index ee20a2e..1663846 100644 --- a/include/gfx/font.h +++ b/include/gfx/font.h @@ -142,4 +142,15 @@ float gfx_get_actual_font_size_round_down(unsigned face, float size); void gfx_print_text_style(struct text_style *style, int indent); +static inline float text_style_width(struct text_style *ts, const char *ch) +{ + return (gfx_size_char(ts, ch) + (ts->bold_width * 2) + ts->edge_left + ts->edge_right) + * ts->scale_x; +} + +static inline float text_style_height(struct text_style *ts) +{ + return ts->size + (ts->bold_width * 2) + ts->edge_up + ts->edge_down; +} + #endif /* SYSTEM4_FONT_H */ diff --git a/src/parts/debug.c b/src/parts/debug.c index a34dc29..41b3cc7 100644 --- a/src/parts/debug.c +++ b/src/parts/debug.c @@ -28,19 +28,22 @@ static void parts_cg_print(struct parts_cg *cg, int indent) static void parts_text_print(struct parts_text *text, int indent) { - indent_printf(indent, "text.lines = {"); + indent_printf(indent, "text.lines = {\n"); for (unsigned i = 0; i < text->nr_lines; i++) { - if (i > 0) - putchar(','); - printf("%u", text->lines[i].height); + struct string *s = parts_text_line_get(&text->lines[i]); + indent_printf(indent+1, "contents = \"%s\",\n", display_sjis0(s->text)); + indent_printf(indent+1, "width = %u,\n", text->lines[i].width); + indent_printf(indent+1, "height = %u,\n", text->lines[i].height); + free_string(s); } - printf("},\n"); + indent_printf(indent, "},\n"); indent_printf(indent, "text.line_space = %u,\n", text->line_space); indent_printf(indent, "text.cursor = "); gfx_print_point(&text->cursor); printf(",\n"); indent_printf(indent, "text.ts = "); gfx_print_text_style(&text->ts, indent); + printf("\n"); } static void parts_animation_print(struct parts_animation *anim, int indent) diff --git a/src/parts/parts.c b/src/parts/parts.c index 18da6f7..96ec265 100644 --- a/src/parts/parts.c +++ b/src/parts/parts.c @@ -143,11 +143,7 @@ static void parts_state_free(struct parts_state *state) free_string(state->cg.name); break; case PARTS_TEXT: - gfx_delete_texture(&state->common.texture); - for (int i = 0; i < state->text.nr_lines; i++) { - free_string(state->text.lines[i].text); - } - free(state->text.lines); + parts_text_free(&state->text); break; case PARTS_ANIMATION: for (unsigned i = 0; i < state->anim.nr_frames; i++) { diff --git a/src/parts/parts_internal.h b/src/parts/parts_internal.h index 2a8b4eb..a733bfa 100644 --- a/src/parts/parts_internal.h +++ b/src/parts/parts_internal.h @@ -72,9 +72,18 @@ struct sound_motion { bool played; }; +struct parts_text_char { + Texture t; + char ch[4]; + int advance; + Point off; +}; + struct parts_text_line { + struct parts_text_char *chars; + int nr_chars; unsigned height; - struct string *text; + unsigned width; }; enum parts_type { @@ -292,6 +301,11 @@ bool parts_gauge_set_cg_by_index(struct parts *parts, struct parts_gauge *g, int void parts_hgauge_set_rate(struct parts *parts, struct parts_gauge *g, float rate); void parts_vgauge_set_rate(struct parts *parts, struct parts_gauge *g, float rate); +// text.c +void parts_text_free(struct parts_text *t); +struct string *parts_text_line_get(struct parts_text_line *line); +struct string *parts_text_get(struct parts_text *t); + // render.c void parts_render_init(void); void parts_engine_dirty(void); diff --git a/src/parts/save.c b/src/parts/save.c index 11ccc47..0d17fa5 100644 --- a/src/parts/save.c +++ b/src/parts/save.c @@ -75,7 +75,9 @@ static void save_parts_text(struct iarray_writer *w, struct parts_text *text) iarray_write_text_style(w, &text->ts); iarray_write(w, text->nr_lines); for (unsigned i = 0; i < text->nr_lines; i++) { - iarray_write_string(w, text->lines[i].text); + struct string *s = parts_text_line_get(&text->lines[i]); + iarray_write_string(w, s); + free_string(s); } } diff --git a/src/parts/text.c b/src/parts/text.c index 0b645e7..425d50c 100644 --- a/src/parts/text.c +++ b/src/parts/text.c @@ -34,63 +34,126 @@ static int extract_sjis_char(const char *src, char *dst) return 1; } -static void parts_text_newline(struct parts_text *text) +struct string *parts_text_line_get(struct parts_text_line *line) { - const unsigned height = text->lines[text->nr_lines-1].height; - text->cursor = POINT(0, text->cursor.y + height + text->line_space); - text->lines = xrealloc_array(text->lines, text->nr_lines, text->nr_lines+1, sizeof(struct parts_text_line)); - text->lines[text->nr_lines].text = make_string("", 0); - text->nr_lines++; + struct string *s = make_string("", 0); + for (int i = 0; i < line->nr_chars; i++) { + string_append_cstr(&s, line->chars[i].ch, strlen(line->chars[i].ch)); + } + return s; +} + +struct string *parts_text_get(struct parts_text *t) +{ + struct string *s = make_string("", 0); + for (int i = 0; i < t->nr_lines; i++) { + if (i > 0) + string_push_back(&s, '\n'); + struct parts_text_line *line = &t->lines[i]; + for (int i = 0; i < line->nr_chars; i++) { + string_append_cstr(&s, line->chars[i].ch, strlen(line->chars[i].ch)); + } + } + return s; +} + +static Point text_style_offset(struct text_style *ts) +{ + int x = max(ts->bold_width, ts->edge_left) * ts->scale_x; + int y = max(ts->bold_width, ts->edge_up); + return (Point){x,y}; +} + +static const char *parts_text_append_char(struct parts_text *t, const char *str) +{ + if (*str == '\n') { + t->lines = xrealloc_array(t->lines, t->nr_lines, t->nr_lines + 1, + sizeof(struct parts_text_line)); + t->nr_lines++; + return str + 1; + } + + struct parts_text_line *line = &t->lines[t->nr_lines - 1]; + line->chars = xrealloc_array(line->chars, line->nr_chars, line->nr_chars + 1, + sizeof(struct parts_text_char)); + struct parts_text_char *ch = &line->chars[line->nr_chars++]; + + ch->off = text_style_offset(&t->ts); + int len = extract_sjis_char(str, ch->ch); + int width = text_style_width(&t->ts, ch->ch); + int height = text_style_height(&t->ts); + gfx_init_texture_rgba(&ch->t, width, height, (SDL_Color){0,0,0,0}); + ch->advance = gfx_render_text(&ch->t, 0, 0, ch->ch, &t->ts); + + line->width += width; + line->height = max(line->height, height); + return str + len; } void parts_text_append(struct parts *parts, struct parts_text *t, struct string *text) { - if (!t->common.texture.handle) { - gfx_init_texture_rgba(&t->common.texture, config.view_width, config.view_height, - (SDL_Color){ 0, 0, 0, 0 }); - } - if (!t->nr_lines) { t->lines = xcalloc(1, sizeof(struct parts_text_line)); - t->lines[0].height = 0; - t->lines[0].text = make_string("", 0); t->nr_lines = 1; } const char *msgp = text->text; while (*msgp) { - char c[4]; - int len = extract_sjis_char(msgp, c); - msgp += len; - - if (c[0] == '\n') { - parts_text_newline(t); - continue; - } - - t->cursor.x += gfx_render_text(&t->common.texture, t->cursor.x, t->cursor.y, c, &t->ts); - - const unsigned old_height = t->lines[t->nr_lines-1].height; - const unsigned new_height = t->ts.size; - struct parts_text_line *line = &t->lines[t->nr_lines-1]; - line->height = max(old_height, new_height); - string_append_cstr(&line->text, c, strlen(c)); + msgp = parts_text_append_char(t, msgp); } - parts_set_dims(parts, &t->common, t->cursor.x, t->cursor.y + t->lines[t->nr_lines-1].height); + + // calculate required dimensions of texture + int width = 0; + int height = 0; + for (int i = 0; i < t->nr_lines; i++) { + width = max(width, t->lines[i].width); + height += t->lines[i].height; + } + if (!width || !height) + return; + + // initialize texture + if (t->common.texture.handle) + gfx_delete_texture(&t->common.texture); + gfx_init_texture_rgba(&t->common.texture, width, height, (SDL_Color){0,0,0,0}); + + // copy glyph textures to main texture + t->cursor = POINT(0, 0); + for (int i = 0; i < t->nr_lines; i++) { + struct parts_text_line *line = &t->lines[i]; + for (int i = 0; i < line->nr_chars; i++) { + struct parts_text_char *ch = &line->chars[i]; + gfx_copy_with_alpha_map(&t->common.texture, + t->cursor.x, t->cursor.y, + &ch->t, 0, 0, ch->t.w, ch->t.h); + t->cursor.x += ch->advance; + } + t->cursor = POINT(0, t->cursor.y + line->height); + } + parts_set_dims(parts, &t->common, width, height); +} + +void parts_text_free(struct parts_text *t) +{ + gfx_delete_texture(&t->common.texture); + for (int i = 0; i < t->nr_lines; i++) { + struct parts_text_line *line = &t->lines[i]; + for (int i = 0; i < line->nr_chars; i++) { + gfx_delete_texture(&line->chars[i].t); + } + free(line->chars); + } + free(t->lines); } static void parts_text_clear(struct parts *parts, int state) { struct parts_text *text = parts_get_text(parts, state); - for (int i = 0; i < text->nr_lines; i++) { - free_string(text->lines[i].text); - } - free(text->lines); + parts_text_free(text); text->lines = NULL; text->nr_lines = 0; text->cursor.x = 0; text->cursor.y = 0; - gfx_delete_texture(&text->common.texture); } bool PE_SetText(int parts_no, struct string *text, int state) @@ -174,7 +237,7 @@ bool PE_SetPartsFontBoldWeight(int parts_no, float bold_weight, int state) if (!parts_state_valid(--state)) return false; - parts_get_text(parts_get(parts_no), state)->ts.weight = bold_weight * 1000; + parts_get_text(parts_get(parts_no), state)->ts.bold_width = bold_weight; return true; }