tuxanci2-engine
Game engine for tuxanci2
Log | Files | Refs | README | LICENSE
commit 106e2181f50e476d4c99ec649bfaf93a71295c40
parent ef8efbb66c24fc07fbb3e71acfb8f209b6de1f9c
Author: Connor Thomson <connor@gnufault.org>
Date:   Mon, 19 Oct 2026 08:24:53 -0700

Optimize it

Diffstat:
Minclude/ta_image.h | 3++-
Minclude/ta_types.h | 5+++++
Msrc/screens/ta_screen_loading.c | 31+++++++++++++++++++++++--------
Msrc/screens/ta_screen_main_menu.c | 2+-
Msrc/ta_button.c | 29+++++++++++++++++------------
Msrc/ta_image.c | 322+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/ta_text.c | 201++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
7 files changed, 423 insertions(+), 170 deletions(-)

diff --git a/include/ta_image.h b/include/ta_image.h @@ -3,7 +3,8 @@ #include "ta_types.h" -ta_bool ta_image_draw(const ta_uint8 *data, ta_uint32 length, float x, float y, float width, float height, ta_uint16 frame); +ta_bool ta_image_draw(const ta_uint8 *data, ta_uint32 length, ta_fixed x, ta_fixed y, ta_fixed width, ta_fixed height, ta_uint16 frame); +ta_bool ta_image_prepare(const ta_uint8 *data, ta_uint32 length, ta_fixed width, ta_fixed height, ta_uint16 frame); ta_bool ta_image_init(const ta_uint8 *data, ta_uint32 length); ta_bool ta_image_get_size(const ta_uint8 *data, ta_uint32 length, ta_uint32 *width, ta_uint32 *height); void ta_image_release(void); diff --git a/include/ta_types.h b/include/ta_types.h @@ -30,4 +30,9 @@ typedef _Bool ta_bool; #define ta_true 1 #define ta_false 0 +typedef ta_uint32 ta_fixed; + +#define TA_FIXED_ONE 65536u +#define TA_FIXED(value) ((ta_fixed)((value) * 65536.0 + 0.5)) + #endif // TA_STDINT_H diff --git a/src/screens/ta_screen_loading.c b/src/screens/ta_screen_loading.c @@ -3,11 +3,12 @@ #include "ta_fb.h" #include "ta_image.h" -#define TA_LOADING_LOGO_SIZE 0.3f +#define TA_LOADING_LOGO_SIZE TA_FIXED(0.3) static ta_bool logo_loaded; static ta_bool logo_shown; static ta_bool background_loaded; +static ta_bool background_scaled; static void draw_loading_logo(void) { ta_uint32 width; @@ -17,13 +18,21 @@ static void draw_loading_logo(void) { return; } - float scale_x = (float)ta_fb.width * TA_LOADING_LOGO_SIZE / width; - float scale_y = (float)ta_fb.height * TA_LOADING_LOGO_SIZE / height; - float scale = scale_x < scale_y ? scale_x : scale_y; - float draw_width = (float)width * scale / ta_fb.width; - float draw_height = (float)height * scale / ta_fb.height; - float x = (1.0f - draw_width) * 0.5f; - float y = (1.0f - draw_height) * 0.5f; + ta_uint64 frame_width = ta_fb.width; + ta_uint64 frame_height = ta_fb.height; + ta_fixed draw_width; + ta_fixed draw_height; + + if (frame_width * height <= frame_height * width) { + draw_width = TA_LOADING_LOGO_SIZE; + draw_height = (ta_fixed)((TA_LOADING_LOGO_SIZE * (ta_uint64)height * frame_width) / ((ta_uint64)width * frame_height)); + } else { + draw_height = TA_LOADING_LOGO_SIZE; + draw_width = (ta_fixed)((TA_LOADING_LOGO_SIZE * (ta_uint64)width * frame_height) / ((ta_uint64)height * frame_width)); + } + + ta_fixed x = (TA_FIXED_ONE - draw_width) / 2; + ta_fixed y = (TA_FIXED_ONE - draw_height) / 2; ta_image_draw(ta_file_loading_logo, ta_file_loading_logo_len, x, y, draw_width, draw_height, 0); } @@ -32,6 +41,7 @@ void ta_screen_loading_init(void) { logo_loaded = ta_false; logo_shown = ta_false; background_loaded = ta_false; + background_scaled = ta_false; } ta_bool ta_screen_loading_update(void) { @@ -54,6 +64,11 @@ ta_bool ta_screen_loading_update(void) { return ta_true; } + if (!background_scaled) { + background_scaled = ta_image_prepare(ta_file_bg, ta_file_bg_len, TA_FIXED_ONE, TA_FIXED_ONE, 0); + return ta_true; + } + ta_screen_current = TA_SCEEEN_MAIN_MENU; return ta_true; diff --git a/src/screens/ta_screen_main_menu.c b/src/screens/ta_screen_main_menu.c @@ -27,7 +27,7 @@ ta_bool ta_screen_main_menu_update(void) { needs_redraw = ta_false; - if (!ta_image_draw(ta_file_bg, ta_file_bg_len, 0.0f, 0.0f, 1.0f, 1.0f, 0)) { + if (!ta_image_draw(ta_file_bg, ta_file_bg_len, 0, 0, TA_FIXED_ONE, TA_FIXED_ONE, 0)) { ta_fb_clear(&ta_fb); } diff --git a/src/ta_button.c b/src/ta_button.c @@ -113,11 +113,16 @@ void ta_button_lists_draw(const ta_button_lists *list) { return; } - double width_scale = (double)ta_fb.width / 800.0; - double height_scale = (double)ta_fb.height / 600.0; - double scale = width_scale < height_scale ? width_scale : height_scale; - ta_uint32 font_size = (ta_uint32)(TA_BUTTON_FONT_SIZE * scale + 0.5); - ta_int32 spacing = (ta_int32)(TA_BUTTON_SPACING * scale + 0.5); + ta_uint32 numerator = (ta_uint32)ta_fb.width; + ta_uint32 denominator = 800; + + if ((ta_uint64)ta_fb.width * 600 >= (ta_uint64)ta_fb.height * 800) { + numerator = (ta_uint32)ta_fb.height; + denominator = 600; + } + + ta_uint32 font_size = (TA_BUTTON_FONT_SIZE * numerator * 2 + denominator) / (denominator * 2); + ta_int32 spacing = (ta_int32)((TA_BUTTON_SPACING * numerator * 2 + denominator) / (denominator * 2)); if (font_size == 0) { font_size = 1; @@ -127,10 +132,10 @@ void ta_button_lists_draw(const ta_button_lists *list) { spacing = 1; } - double progress = 1.0; + ta_int32 progress = 512; if (list->animation_active) { - progress = (double)list->animation_frame / TA_BUTTON_ANIMATION_FRAMES; - progress = progress * progress * (3.0 - 2.0 * progress); + ta_int32 frame = (ta_int32)list->animation_frame; + progress = frame * frame * (24 - 2 * frame); } ta_int32 center_y = (ta_int32)(ta_fb.height / 2); @@ -143,13 +148,13 @@ void ta_button_lists_draw(const ta_button_lists *list) { ta_text_measure(list->buttons[i].label, font_size, &text_width, &text_height); ta_int32 row_from = (ta_int32)i - (ta_int32)list->animation_from; - ta_int32 y = center_y + (ta_int32)(row_from * spacing - selected_shift * spacing * progress) - (ta_int32)(text_height / 2); + ta_int32 y = center_y + (row_from * spacing * 512 - selected_shift * spacing * progress) / 512 - (ta_int32)(text_height / 2); if (y < 0 || (ta_size)y >= ta_fb.height || text_width > ta_fb.width) { continue; } - double selection = i == list->animation_from ? 1.0 - progress : 0.0; + ta_int32 selection = i == list->animation_from ? 512 - progress : 0; if (i == list->selected) { selection += progress; } @@ -157,9 +162,9 @@ void ta_button_lists_draw(const ta_button_lists *list) { ta_uint32 pulse_position = list->pulse_frame; ta_uint32 pulse_distance = pulse_position <= TA_BUTTON_PULSE_HALF_FRAMES ? pulse_position : TA_BUTTON_PULSE_HALF_FRAMES * 2 - pulse_position; ta_uint8 pulse_blue = (ta_uint8)(TA_BUTTON_COLOR_SELECTED - ((TA_BUTTON_COLOR_SELECTED - TA_BUTTON_COLOR_PULSE_BLUE_MIN) * pulse_distance / TA_BUTTON_PULSE_HALF_FRAMES)); - ta_uint8 red = (ta_uint8)(TA_BUTTON_COLOR_IDLE + (TA_BUTTON_COLOR_SELECTED - TA_BUTTON_COLOR_IDLE) * selection + 0.5); + ta_uint8 red = (ta_uint8)((TA_BUTTON_COLOR_IDLE * 512 + (TA_BUTTON_COLOR_SELECTED - TA_BUTTON_COLOR_IDLE) * selection + 256) / 512); ta_uint8 green = red; - ta_uint8 blue = (ta_uint8)(TA_BUTTON_COLOR_IDLE + (pulse_blue - TA_BUTTON_COLOR_IDLE) * selection + 0.5); + ta_uint8 blue = (ta_uint8)((TA_BUTTON_COLOR_IDLE * 512 + ((ta_int32)pulse_blue - TA_BUTTON_COLOR_IDLE) * selection + 256) / 512); ta_uint32 x = (ta_uint32)(ta_fb.width - text_width) / 2; ta_text_draw_outline(list->buttons[i].label, x, (ta_uint32)y, text_width, text_height, red, green, blue, 1, 0, 0, 0); diff --git a/src/ta_image.c b/src/ta_image.c @@ -5,6 +5,17 @@ #define TAIF_FLAG_ANIMATED 0x04 #define TAIF_WINDOW_SIZE 4096 +typedef struct { + ta_uint8 window[TAIF_WINDOW_SIZE]; + ta_uint32 stream_position; + ta_uint32 window_position; + ta_uint32 filled; + ta_uint32 match_distance; + ta_uint32 match_remaining; + ta_uint8 item_flags; + ta_uint8 flags_remaining; +} ta_image_decoder; + typedef struct ta_image_cache { const ta_uint8 *data; ta_uint32 length; @@ -13,10 +24,13 @@ typedef struct ta_image_cache { ta_uint32 stride; ta_uint32 frame_size; ta_uint16 frame_count; - ta_uint16 requested_frame; ta_uint16 palette_entries; + ta_uint32 frames_decoded; ta_uint8 flags; ta_uint8 palette[256][3]; + const ta_uint8 *stream; + ta_uint32 stream_length; + ta_image_decoder decoder; ta_uint8 *pixels; ta_uint8 *scaled; ta_uint32 scaled_width; @@ -31,7 +45,7 @@ static ta_uint16 read_le16(const ta_uint8 *data) { return (ta_uint16)((ta_uint16)data[0] | ((ta_uint16)data[1] << 8)); } -static ta_bool decode_image(const ta_uint8 *data, ta_uint32 length, ta_uint16 requested_frame, ta_image_cache *image) { +static ta_bool parse_image(const ta_uint8 *data, ta_uint32 length, ta_image_cache *image) { if (data == 0 || length < 11 || data[0] != 'T' || data[1] != 'A' || data[2] != 'I' || data[3] != 'F' || data[4] != 1) { return ta_false; } @@ -67,174 +81,160 @@ static ta_bool decode_image(const ta_uint8 *data, ta_uint32 length, ta_uint16 re header_size = 14; } - if (requested_frame >= frame_count) { - return ta_false; - } - ta_uint64 stride = eight_bpp ? width : (width + 1) / 2; ta_uint64 frame_size = stride * height; ta_uint64 max_size = (ta_uint64)(ta_size)-1; ta_uint64 palette_size = (ta_uint64)palette_entries * 3; - if (frame_size == 0 || frame_size > max_size || header_size + palette_size > length) { + if (frame_size == 0 || frame_size > max_size || frame_size > 0xffffffffu || header_size + palette_size > length) { return ta_false; } const ta_uint8 *palette = data + header_size; - const ta_uint8 *stream = palette + palette_size; - ta_uint32 stream_length = length - (ta_uint32)(header_size + palette_size); - ta_uint8 *previous = (ta_uint8 *)ta_malloc((ta_size)frame_size); - ta_uint8 *current = (ta_uint8 *)ta_malloc((ta_size)frame_size); - ta_uint8 *decoded_frame = (ta_uint8 *)ta_malloc((ta_size)frame_size); - - if (previous == 0 || current == 0 || decoded_frame == 0) { - ta_free(previous); - ta_free(current); - ta_free(decoded_frame); + + image->pixels = (ta_uint8 *)ta_malloc((ta_size)frame_size); + if (image->pixels == 0) { return ta_false; } for (ta_uint32 i = 0; i < (ta_uint32)frame_size; i++) { - previous[i] = 0; + image->pixels[i] = 0; } - ta_uint8 window[TAIF_WINDOW_SIZE]; - ta_uint32 stream_position = 0; - ta_uint32 window_position = 0; - ta_uint64 produced = 0; - ta_uint64 total_size = frame_size * frame_count; - ta_bool valid = ta_true; - ta_uint32 frame_position = 0; - ta_uint32 frame_index = 0; - ta_uint32 match_distance = 0; - ta_uint32 match_remaining = 0; - ta_uint8 item_flags = 0; - ta_uint8 flags_remaining = 0; - - while (produced < total_size && valid) { + image->data = data; + image->length = length; + image->width = width; + image->height = height; + image->stride = (ta_uint32)stride; + image->frame_size = (ta_uint32)frame_size; + image->frame_count = frame_count; + image->palette_entries = (ta_uint16)palette_entries; + image->flags = flags; + image->stream = palette + palette_size; + image->stream_length = length - (ta_uint32)(header_size + palette_size); + + for (ta_uint32 i = 0; i < palette_entries; i++) { + for (ta_uint32 channel = 0; channel < 3; channel++) { + image->palette[i][channel] = palette[i * 3 + channel]; + } + } + + return ta_true; +} + +static void decoder_reset(ta_image_cache *image) { + ta_image_decoder *decoder = &image->decoder; + + decoder->stream_position = 0; + decoder->window_position = 0; + decoder->filled = 0; + decoder->match_distance = 0; + decoder->match_remaining = 0; + decoder->item_flags = 0; + decoder->flags_remaining = 0; + + for (ta_uint32 i = 0; i < image->frame_size; i++) { + image->pixels[i] = 0; + } + + ta_free(image->scaled); + + image->scaled = 0; + image->frames_decoded = 0; +} + +static ta_bool decode_next_frame(ta_image_cache *image) { + ta_image_decoder *decoder = &image->decoder; + const ta_uint8 *stream = image->stream; + ta_uint32 stream_length = image->stream_length; + ta_uint32 frame_size = image->frame_size; + ta_uint32 position = 0; + + while (position < frame_size) { ta_uint8 value; - if (match_remaining != 0) { - ta_uint32 source_position = (window_position + TAIF_WINDOW_SIZE - match_distance) & (TAIF_WINDOW_SIZE - 1); - value = window[source_position]; - match_remaining--; + + if (decoder->match_remaining != 0) { + ta_uint32 source_position = (decoder->window_position + TAIF_WINDOW_SIZE - decoder->match_distance) & (TAIF_WINDOW_SIZE - 1); + + value = decoder->window[source_position]; + decoder->match_remaining--; } else { - if (flags_remaining == 0) { - if (stream_position >= stream_length) { - valid = ta_false; - break; + if (decoder->flags_remaining == 0) { + if (decoder->stream_position >= stream_length) { + return ta_false; } - item_flags = stream[stream_position++]; - flags_remaining = 8; + decoder->item_flags = stream[decoder->stream_position++]; + decoder->flags_remaining = 8; } - ta_bool literal = (item_flags & 1) != 0; - item_flags >>= 1; - flags_remaining--; + ta_bool literal = (decoder->item_flags & 1) != 0; + + decoder->item_flags >>= 1; + decoder->flags_remaining--; if (literal) { - if (stream_position >= stream_length) { - valid = ta_false; - - break; + if (decoder->stream_position >= stream_length) { + return ta_false; } - value = stream[stream_position++]; + value = stream[decoder->stream_position++]; } else { - if (stream_length - stream_position < 2) { - valid = ta_false; - break; + if (stream_length - decoder->stream_position < 2) { + return ta_false; } - ta_uint8 b0 = stream[stream_position++]; - ta_uint8 b1 = stream[stream_position++]; - match_distance = ((((ta_uint32)b1 >> 4) << 8) | b0) + 1; - match_remaining = (b1 & 0x0f) + 3; - - if (match_distance > produced) { - valid = ta_false; - break; + ta_uint8 b0 = stream[decoder->stream_position++]; + ta_uint8 b1 = stream[decoder->stream_position++]; + + decoder->match_distance = ((((ta_uint32)b1 >> 4) << 8) | b0) + 1; + decoder->match_remaining = (b1 & 0x0f) + 3; + + if (decoder->match_distance > decoder->filled) { + return ta_false; } - + continue; } } - current[frame_position++] = value; - window[window_position] = value; - window_position = (window_position + 1) & (TAIF_WINDOW_SIZE - 1); - produced++; + image->pixels[position++] ^= value; + decoder->window[decoder->window_position] = value; + decoder->window_position = (decoder->window_position + 1) & (TAIF_WINDOW_SIZE - 1); - if (frame_position == (ta_uint32)frame_size) { - for (ta_uint32 i = 0; i < (ta_uint32)frame_size; i++) { - if ((flags & TAIF_FLAG_ANIMATED) != 0) { - current[i] ^= previous[i]; - } - } + if (decoder->filled < TAIF_WINDOW_SIZE) { + decoder->filled++; + } + } - for (ta_uint32 row = 0; row < height && valid; row++) { - for (ta_uint32 column = 0; column < width; column++) { - ta_uint8 index; - - if (eight_bpp) { - index = current[row * (ta_uint32)stride + column]; - } else { - ta_uint8 packed = current[row * (ta_uint32)stride + column / 2]; - index = (column & 1) == 0 ? packed >> 4 : packed & 0x0f; - } - - if ((transparent && index != 0 && index > palette_entries) || (!transparent && index >= palette_entries)) { - valid = ta_false; - - break; - } - } - } + ta_bool eight_bpp = (image->flags & TAIF_FLAG_8BPP) != 0; + ta_bool transparent = (image->flags & TAIF_FLAG_TRANSPARENT) != 0; + ta_uint32 max_index = transparent ? image->palette_entries : (ta_uint32)image->palette_entries - 1; + ta_uint32 full_bytes = eight_bpp ? image->width : image->width / 2; + ta_bool odd_column = !eight_bpp && (image->width & 1) != 0; - if (!valid) { - break; - } + for (ta_uint32 row = 0; row < image->height; row++) { + const ta_uint8 *line = image->pixels + (ta_size)row * image->stride; - if (frame_index == requested_frame) { - for (ta_uint32 i = 0; i < (ta_uint32)frame_size; i++) { - decoded_frame[i] = current[i]; + for (ta_uint32 i = 0; i < full_bytes; i++) { + if (eight_bpp) { + if (line[i] > max_index) { + return ta_false; } + } else if ((ta_uint32)(line[i] >> 4) > max_index || (ta_uint32)(line[i] & 0x0f) > max_index) { + return ta_false; } - - ta_uint8 *swap = previous; - previous = current; - current = swap; - frame_index++; - frame_position = 0; } - } - - ta_free(previous); - ta_free(current); - if (!valid || produced != total_size) { - ta_free(decoded_frame); - - return ta_false; + if (odd_column && (ta_uint32)(line[full_bytes] >> 4) > max_index) { + return ta_false; + } } - image->data = data; - image->length = length; - image->width = width; - image->height = height; - image->stride = (ta_uint32)stride; - image->frame_size = (ta_uint32)frame_size; - image->frame_count = frame_count; - image->requested_frame = requested_frame; - image->palette_entries = (ta_uint16)palette_entries; - image->flags = flags; - image->pixels = decoded_frame; + ta_free(image->scaled); - for (ta_uint32 i = 0; i < palette_entries; i++) { - for (ta_uint32 channel = 0; channel < 3; channel++) { - image->palette[i][channel] = palette[i * 3 + channel]; - } - } + image->scaled = 0; + image->frames_decoded++; return ta_true; } @@ -260,7 +260,14 @@ ta_bool ta_image_init(const ta_uint8 *data, ta_uint32 length) { } *image = (ta_image_cache){0}; - if (!decode_image(data, length, 0, image)) { + + if (!parse_image(data, length, image)) { + ta_free(image); + return ta_false; + } + + if (!decode_next_frame(image)) { + ta_free(image->pixels); ta_free(image); return ta_false; } @@ -288,23 +295,22 @@ static ta_image_cache *get_image(const ta_uint8 *data, ta_uint32 length, ta_uint } ta_image_cache *image = find_image(data, length); - if (image->requested_frame == frame) { - return image; - } - - ta_image_cache decoded = {0}; - if (!decode_image(data, length, frame, &decoded)) { + if (frame >= image->frame_count) { return 0; } - ta_uint8 *old_pixels = image->pixels; - ta_uint8 *old_scaled = image->scaled; - ta_image_cache *next = image->next; - *image = decoded; - image->next = next; + ta_uint32 wanted = (ta_uint32)frame + 1; - ta_free(old_pixels); - ta_free(old_scaled); + if (image->frames_decoded > wanted) { + decoder_reset(image); + } + + while (image->frames_decoded < wanted) { + if (!decode_next_frame(image)) { + decoder_reset(image); + return 0; + } + } return image; } @@ -476,15 +482,39 @@ static ta_bool prepare_scaled(ta_image_cache *image, ta_uint32 width, ta_uint32 return ta_true; } -ta_bool ta_image_draw(const ta_uint8 *data, ta_uint32 length, float x, float y, float width, float height, ta_uint16 frame) { - if (data == 0 || length == 0 || x != x || y != y || width != width || height != height || x < 0.0f || y < 0.0f || x > 1.0f || y > 1.0f || width <= 0.0f || height <= 0.0f || width > 1.0f || height > 1.0f || ta_fb.buffers[1] == 0 || (ta_fb.bpp != 8 && ta_fb.bpp != 32)) { +ta_bool ta_image_prepare(const ta_uint8 *data, ta_uint32 length, ta_fixed width, ta_fixed height, ta_uint16 frame) { + if (data == 0 || length == 0 || width == 0 || height == 0 || width > TA_FIXED_ONE || height > TA_FIXED_ONE || ta_fb.buffers[1] == 0 || (ta_fb.bpp != 8 && ta_fb.bpp != 32)) { + return ta_false; + } + + ta_uint32 pixel_width = (ta_uint32)(((ta_uint64)width * ta_fb.width) >> 16); + ta_uint32 pixel_height = (ta_uint32)(((ta_uint64)height * ta_fb.height) >> 16); + + if (pixel_width == 0 || pixel_height == 0) { + return ta_false; + } + + ta_image_cache *image = get_image(data, length, frame); + if (image == 0) { + return ta_false; + } + + if ((image->flags & TAIF_FLAG_TRANSPARENT) != 0) { + return ta_true; + } + + return prepare_scaled(image, pixel_width, pixel_height); +} + +ta_bool ta_image_draw(const ta_uint8 *data, ta_uint32 length, ta_fixed x, ta_fixed y, ta_fixed width, ta_fixed height, ta_uint16 frame) { + if (data == 0 || length == 0 || x > TA_FIXED_ONE || y > TA_FIXED_ONE || width == 0 || height == 0 || width > TA_FIXED_ONE || height > TA_FIXED_ONE || ta_fb.buffers[1] == 0 || (ta_fb.bpp != 8 && ta_fb.bpp != 32)) { return ta_false; } - ta_uint32 pixel_x = (ta_uint32)((double)x * ta_fb.width); - ta_uint32 pixel_y = (ta_uint32)((double)y * ta_fb.height); - ta_uint32 pixel_width = (ta_uint32)((double)width * ta_fb.width); - ta_uint32 pixel_height = (ta_uint32)((double)height * ta_fb.height); + ta_uint32 pixel_x = (ta_uint32)(((ta_uint64)x * ta_fb.width) >> 16); + ta_uint32 pixel_y = (ta_uint32)(((ta_uint64)y * ta_fb.height) >> 16); + ta_uint32 pixel_width = (ta_uint32)(((ta_uint64)width * ta_fb.width) >> 16); + ta_uint32 pixel_height = (ta_uint32)(((ta_uint64)height * ta_fb.height) >> 16); if (pixel_width == 0 || pixel_height == 0) { return ta_false; diff --git a/src/ta_text.c b/src/ta_text.c @@ -24,6 +24,10 @@ typedef struct { ta_uint8 border_red; ta_uint8 border_green; ta_uint8 border_blue; + ta_uint8 *mask; + ta_int32 mask_x0; + ta_int32 mask_y0; + ta_int32 mask_stride; } ta_text_ctx; typedef struct { @@ -534,6 +538,19 @@ static void put_pixel(const ta_text_ctx *ctx, ta_int32 pixel_x, ta_int32 pixel_y } } +static void emit_pixel(const ta_text_ctx *ctx, ta_int32 pixel_x, ta_int32 pixel_y, ta_uint32 alpha) { + if (ctx->mask != 0) { + ta_uint8 *target = ctx->mask + (ta_size)(pixel_y - ctx->mask_y0) * (ta_size)ctx->mask_stride + (ta_size)(pixel_x - ctx->mask_x0); + ta_uint32 old = *target; + + *target = (ta_uint8)(old + alpha - DIV255(old * alpha)); + + return; + } + + put_pixel(ctx, pixel_x, pixel_y, alpha); +} + static ta_text_pt transform_point(ta_int32 font_x, ta_int32 font_y, ta_int32 origin_x, ta_int32 origin_y, ta_int32 scale) { ta_text_pt point; @@ -790,7 +807,7 @@ static void draw_glyph(const ta_text_ctx *ctx, ta_uint32 glyph_id, ta_int32 pen_ ta_uint32 alpha = ((ta_uint32)amount * 255 + 0x8000) >> 16; if (alpha) { - put_pixel(ctx, region_x0 + column, region_y0 + row_index, alpha); + emit_pixel(ctx, region_x0 + column, region_y0 + row_index, alpha); } } } @@ -874,7 +891,7 @@ static void draw_text_pass(const char *text, ta_int32 x, ta_int32 y, const ta_te } } -static void draw_text(const char *text, ta_int32 x, ta_int32 y, const ta_text_ctx *ctx) { +static void draw_text_direct(const char *text, ta_int32 x, ta_int32 y, const ta_text_ctx *ctx) { if (ctx->border_thickness != 0) { ta_text_ctx border_ctx = *ctx; @@ -897,6 +914,184 @@ static void draw_text(const char *text, ta_int32 x, ta_int32 y, const ta_text_ct draw_text_pass(text, x, y, ctx); } +#define TEXT_CACHE_ENTRIES 8 +#define TEXT_CACHE_MAX_TEXT 64 +#define TEXT_CACHE_MAX_MASK 262144 + +typedef struct { + char text[TEXT_CACHE_MAX_TEXT + 1]; + ta_int32 scale; + ta_int32 x0; + ta_int32 y0; + ta_int32 width; + ta_int32 height; + ta_uint8 *mask; +} ta_text_entry; + +static ta_text_entry text_cache[TEXT_CACHE_ENTRIES]; +static ta_uint32 text_cache_next; + +static ta_bool same_text(const char *a, const char *b) { + while (*a && *a == *b) { + a++; + b++; + } + + return *a == *b; +} + +static ta_text_entry *text_cache_build(const char *text, ta_int32 scale) { + ta_uint32 length = 0; + + while (text[length]) { + if (++length > TEXT_CACHE_MAX_TEXT) { + return 0; + } + } + + ta_uint32 units; + ta_uint32 lines; + + measure_units(text, &units, &lines); + + ta_int32 padding = (ta_int32)(((ta_uint64)font.units_per_em * (ta_uint64)scale) >> 16) / 2 + 2; + ta_int32 text_width = (ta_int32)(((ta_uint64)units * (ta_uint64)scale + (ONE - 1)) >> 16); + ta_int32 text_height = (ta_int32)(((ta_uint64)lines * line_height_units() * (ta_uint64)scale + (ONE - 1)) >> 16); + ta_int32 width = text_width + padding * 2; + ta_int32 height = text_height + padding * 2; + + if (width <= 0 || height <= 0 || (ta_uint64)width * (ta_uint64)height > TEXT_CACHE_MAX_MASK) { + return 0; + } + + ta_size mask_size = (ta_size)width * (ta_size)height; + ta_uint8 *mask = (ta_uint8 *)ta_malloc(mask_size); + + if (mask == 0) { + return 0; + } + + for (ta_size i = 0; i < mask_size; i++) { + mask[i] = 0; + } + + ta_text_ctx ctx; + + ctx.scale = scale; + ctx.clip_x0 = -padding; + ctx.clip_y0 = -padding; + ctx.clip_x1 = -padding + width; + ctx.clip_y1 = -padding + height; + ctx.red = 0; + ctx.green = 0; + ctx.blue = 0; + ctx.border_thickness = 0; + ctx.border_red = 0; + ctx.border_green = 0; + ctx.border_blue = 0; + ctx.mask = mask; + ctx.mask_x0 = -padding; + ctx.mask_y0 = -padding; + ctx.mask_stride = width; + + draw_text_pass(text, 0, 0, &ctx); + + ta_text_entry *entry = &text_cache[text_cache_next]; + + text_cache_next = (text_cache_next + 1) % TEXT_CACHE_ENTRIES; + + ta_free(entry->mask); + + for (ta_uint32 i = 0; i <= length; i++) { + entry->text[i] = text[i]; + } + + entry->scale = scale; + entry->x0 = -padding; + entry->y0 = -padding; + entry->width = width; + entry->height = height; + entry->mask = mask; + + return entry; +} + +static ta_text_entry *text_cache_get(const char *text, ta_int32 scale) { + for (ta_uint32 i = 0; i < TEXT_CACHE_ENTRIES; i++) { + if (text_cache[i].mask != 0 && text_cache[i].scale == scale && same_text(text_cache[i].text, text)) { + return &text_cache[i]; + } + } + + return text_cache_build(text, scale); +} + +static void composite_mask(const ta_text_entry *entry, const ta_text_ctx *ctx, ta_int32 x, ta_int32 y) { + ta_int32 destination_x = x + entry->x0; + ta_int32 destination_y = y + entry->y0; + ta_int32 column_start = ctx->clip_x0 - destination_x; + ta_int32 column_end = ctx->clip_x1 - destination_x; + ta_int32 row_start = ctx->clip_y0 - destination_y; + ta_int32 row_end = ctx->clip_y1 - destination_y; + + if (column_start < 0) { + column_start = 0; + } + + if (row_start < 0) { + row_start = 0; + } + + if (column_end > entry->width) { + column_end = entry->width; + } + + if (row_end > entry->height) { + row_end = entry->height; + } + + for (ta_int32 row = row_start; row < row_end; row++) { + const ta_uint8 *source = entry->mask + (ta_size)row * (ta_size)entry->width; + + for (ta_int32 column = column_start; column < column_end; column++) { + if (source[column] != 0) { + put_pixel(ctx, destination_x + column, destination_y + row, source[column]); + } + } + } +} + +static void draw_text(const char *text, ta_int32 x, ta_int32 y, const ta_text_ctx *ctx) { + const ta_text_entry *entry = text_cache_get(text, ctx->scale); + + if (entry == 0) { + draw_text_direct(text, x, y, ctx); + + return; + } + + if (ctx->border_thickness != 0) { + ta_text_ctx border_ctx = *ctx; + + border_ctx.red = ctx->border_red; + border_ctx.green = ctx->border_green; + border_ctx.blue = ctx->border_blue; + ta_int32 thickness = ctx->border_thickness; + + for (ta_int32 offset_y = -thickness; offset_y <= thickness; offset_y++) { + for (ta_int32 offset_x = -thickness; offset_x <= thickness; offset_x++) { + if (offset_x == 0 && offset_y == 0) { + continue; + } + + composite_mask(entry, &border_ctx, x + offset_x, y + offset_y); + } + } + } + + composite_mask(entry, ctx, x, y); +} + static void draw_text_fitted(const char *text, ta_uint32 x, ta_uint32 y, ta_uint32 width, ta_uint32 height, ta_uint8 red, ta_uint8 green, ta_uint8 blue, ta_uint8 border_thickness, ta_uint8 border_red, ta_uint8 border_green, ta_uint8 border_blue) { if (!text || width == 0 || height == 0 || !fb_usable() || !font_ready()) { return; @@ -945,6 +1140,7 @@ static void draw_text_fitted(const char *text, ta_uint32 x, ta_uint32 y, ta_uint ctx.border_red = border_red; ctx.border_green = border_green; ctx.border_blue = border_blue; + ctx.mask = 0; draw_text(text, (ta_int32)x, (ta_int32)y, &ctx); } @@ -990,6 +1186,7 @@ void ta_text_draw_size(const char *text, ta_uint32 x, ta_uint32 y, ta_uint32 siz ctx.border_red = 0; ctx.border_green = 0; ctx.border_blue = 0; + ctx.mask = 0; draw_text(text, (ta_int32)x, (ta_int32)y, &ctx); }