tuxanci2-engine
Game engine for tuxanci2
Log | Files | Refs | README | LICENSE
commit 0d1a0c35551fdb0c9143766ec2725a6ac11c7c26
parent c355f59444071497ce7247559a6d61a07afa4dc9
Author: Connor Thomson <connor@gnufault.org>
Date:   Tue, 20 Oct 2026 19:58:08 -0700

Add hardware acceleration support & more

Diffstat:
Minclude/ta_api.h | 1+
Minclude/ta_button.h | 4+++-
Ainclude/ta_display.h | 44++++++++++++++++++++++++++++++++++++++++++++
Minclude/ta_fb.h | 11+++--------
Minclude/ta_text.h | 19+++++++++++++++----
Minclude/ta_types.h | 2++
Mplatforms/dreamcast/dreamcast.c | 149+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Aplatforms/n64/.gitignore | 1+
Mplatforms/n64/n64.c | 41+++++++++++++++++++++++++++++++----------
Mplatforms/x11/x11.c | 4++++
Mplatforms/xbox/xbox.c | 8++++----
Msrc/screens/ta_screen_loading.c | 7++++---
Msrc/screens/ta_screen_main_menu.c | 17+++++++++++++++--
Msrc/ta_button.c | 99+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Asrc/ta_display.c | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/ta_fb.c | 31+++++++++++++++++++++++++++++--
Msrc/ta_image.c | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Msrc/ta_main.c | 5++++-
Msrc/ta_text.c | 414+++++++++++++++++++++++++++-----------------------------------------------------
19 files changed, 644 insertions(+), 435 deletions(-)

diff --git a/include/ta_api.h b/include/ta_api.h @@ -19,6 +19,7 @@ ta_bool ta_main_update(void); // Misc stuff that you need to implement/wrap void *ta_malloc(ta_size size); +void *ta_realloc(void *ptr, ta_size new_size); void ta_free(void *ptr); void ta_exit(int status); diff --git a/include/ta_button.h b/include/ta_button.h @@ -1,6 +1,7 @@ #ifndef TA_BUTTON_H #define TA_BUTTON_H +#include "ta_text.h" #include "ta_types.h" typedef void (*ta_button_callback)(void); @@ -8,6 +9,8 @@ typedef void (*ta_button_callback)(void); typedef struct { const char *label; ta_button_callback callback; + ta_text text_idle; + ta_text text_selected; } ta_button; typedef struct { @@ -16,7 +19,6 @@ typedef struct { ta_size selected; ta_size animation_from; ta_uint32 animation_frame; - ta_uint32 pulse_frame; ta_bool animation_active; ta_bool up_was_down; ta_bool down_was_down; diff --git a/include/ta_display.h b/include/ta_display.h @@ -0,0 +1,44 @@ +#ifndef TA_DISPLAY_H +#define TA_DISPLAY_H + +#include "ta_types.h" + +typedef enum { + TA_DISPLAY_PIXEL_FORMAT_8, + TA_DISPLAY_PIXEL_FORMAT_16, + TA_DISPLAY_PIXEL_FORMAT_32 +} ta_display_pixel_format; + +typedef struct { + ta_uint16 width, height; + ta_uint8 bpp; + ta_size size; + void *buffers[2]; + ta_display_pixel_format format; + ta_bool sprite_mode; +} ta_display_t; + +typedef struct { + ta_uint16 x, y; + ta_uint16 width, height; + ta_display_pixel_format format; + ta_uint32 _pad; + ta_uint8 pixels[]; +} ta_display_sprite; + +typedef struct { + ta_display_sprite **sprites; + ta_uint8 count; + ta_uint8 capacity; +} ta_display_sprites_t; + +extern ta_display_t *ta_display; +extern ta_display_sprites_t *ta_display_sprites; + +ta_bool ta_display_init(void); +void ta_display_deinit(void); +ta_bool ta_display_sprite_add(ta_display_sprite *sprite); +void ta_display_sprite_free(ta_display_sprite *sprite); +void ta_display_sprites_clear(void); + +#endif // TA_DISPLAY_H diff --git a/include/ta_fb.h b/include/ta_fb.h @@ -1,16 +1,11 @@ #ifndef TA_FB_H #define TA_FB_H -#include "ta_types.h" +#include "ta_display.h" -typedef struct { - ta_size width, height; - ta_size bpp; - ta_size size; - void *buffers[2]; -} ta_fbs; +typedef ta_display_t ta_fbs; -extern ta_fbs ta_fb; +#define ta_fb (*ta_display) ta_size ta_fb_init(ta_fbs *fb); void ta_fb_swap(ta_fbs *fb); diff --git a/include/ta_text.h b/include/ta_text.h @@ -1,11 +1,22 @@ #ifndef TA_TEXT_H #define TA_TEXT_H +#include "ta_display.h" #include "ta_types.h" -void ta_text_draw(const char *text, ta_uint32 x, ta_uint32 y, ta_uint32 width, ta_uint32 height, ta_uint8 red, ta_uint8 green, ta_uint8 blue); -void ta_text_draw_outline(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); -void ta_text_draw_size(const char *text, ta_uint32 x, ta_uint32 y, ta_uint32 size, ta_uint8 red, ta_uint8 green, ta_uint8 blue); -void ta_text_measure(const char *text, ta_uint32 size, ta_uint32 *width, ta_uint32 *height); +typedef struct { + const char *text; + ta_uint32 x, y; + ta_uint8 size; + ta_uint8 red, green, blue; + ta_uint8 border_width; + ta_uint8 border_red, border_green, border_blue; + ta_uint32 width, height; + ta_display_sprite *sprite; +} ta_text; + +void ta_text_rasterize(ta_text *text); +void ta_text_draw(ta_text *text); +void ta_text_destroy(ta_text *text); #endif // TA_TEXT_H diff --git a/include/ta_types.h b/include/ta_types.h @@ -3,6 +3,8 @@ #include "ta_config.h" +#define TA_NULL ((void*)0) + typedef unsigned char ta_uint8; typedef unsigned short ta_uint16; typedef unsigned int ta_uint32; diff --git a/platforms/dreamcast/dreamcast.c b/platforms/dreamcast/dreamcast.c @@ -11,10 +11,21 @@ KOS_INIT_FLAGS(INIT_DEFAULT); #define SCREEN_WIDTH 320 #define SCREEN_HEIGHT 240 #define WINDOW_BPP 32 -#define TEXTURE_WIDTH 512 -#define TEXTURE_HEIGHT 256 +#define TARGET_FPS 60 +#define FRAME_TIME_MS (1000 / TARGET_FPS) -alignas(WINDOW_BPP) static uint16_t texture_pixels[TEXTURE_WIDTH * TEXTURE_HEIGHT]; +typedef struct { + pvr_ptr_t texture; + uint16_t *pixels; +} sprite_texture; + +static uint32_t next_power_of_two(uint32_t value) { + uint32_t result = 1; + while (result < value) { + result <<= 1; + } + return result; +} static void update_controller_input(void) { maple_device_t *controller = maple_enum_type(0, MAPLE_FUNC_CONTROLLER); @@ -44,84 +55,110 @@ static void update_controller_input(void) { } } -static void convert_framebuffer(void) { - const uint32_t *source = (const uint32_t *)ta_fb.buffers[0]; +static void render_sprites(void) { + sprite_texture textures[255] = {{0}}; + pvr_scene_begin(); - for (size_t y = 0; y < SCREEN_HEIGHT; y++) { - for (size_t x = 0; x < SCREEN_WIDTH; x++) { - uint32_t pixel = source[y * SCREEN_WIDTH + x]; - uint16_t red = (uint16_t)((pixel >> 16) & 0xff); - uint16_t green = (uint16_t)((pixel >> 8) & 0xff); - uint16_t blue = (uint16_t)(pixel & 0xff); + pvr_list_begin(PVR_LIST_TR_POLY); + for (uint8_t i = 0; i < ta_display_sprites->count; i++) { + ta_display_sprite *sprite = ta_display_sprites->sprites[i]; + uint32_t texture_width = next_power_of_two(sprite->width); + uint32_t texture_height = next_power_of_two(sprite->height); + size_t texture_size = (size_t)texture_width * texture_height * sizeof(uint16_t); + uint16_t *pixels = (uint16_t *)ta_malloc(texture_size); + if (!pixels) { + continue; + } - texture_pixels[y * TEXTURE_WIDTH + x] = (uint16_t)(((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3)); + for (uint32_t y = 0; y < texture_height; y++) { + for (uint32_t x = 0; x < texture_width; x++) { + uint32_t pixel = 0; + if (x < sprite->width && y < sprite->height) { + pixel = ((const uint32_t *)sprite->pixels)[(size_t)y * sprite->width + x]; + } + + uint16_t alpha = (uint16_t)((pixel & 0xff) >> 4); + uint16_t red = (uint16_t)(((pixel >> 24) & 0xff) >> 4); + uint16_t green = (uint16_t)(((pixel >> 16) & 0xff) >> 4); + uint16_t blue = (uint16_t)(((pixel >> 8) & 0xff) >> 4); + pixels[(size_t)y * texture_width + x] = (uint16_t)((alpha << 12) | (red << 8) | (green << 4) | blue); + } } - } -} -static void render_frame(const pvr_sprite_hdr_t *header) { - pvr_sprite_txr_t sprite = { - .flags = PVR_CMD_VERTEX_EOL, - .ax = 0.0f, - .ay = SCREEN_HEIGHT, - .az = 1.0f, - .bx = 0.0f, - .by = 0.0f, - .bz = 1.0f, - .cx = SCREEN_WIDTH, - .cy = 0.0f, - .cz = 1.0f, - .dx = SCREEN_WIDTH, - .dy = SCREEN_HEIGHT, - .dummy = 0, - .auv = PVR_PACK_16BIT_UV(0.0f, (float)SCREEN_HEIGHT / TEXTURE_HEIGHT), - .buv = PVR_PACK_16BIT_UV(0.0f, 0.0f), - .cuv = PVR_PACK_16BIT_UV((float)SCREEN_WIDTH / TEXTURE_WIDTH, 0.0f) - }; + pvr_ptr_t texture = pvr_mem_malloc(texture_size); + if (!texture) { + ta_free(pixels); + continue; + } + + pvr_txr_load(pixels, texture, texture_size); + textures[i].texture = texture; + textures[i].pixels = pixels; + + pvr_poly_cxt_t context; + pvr_poly_hdr_t header; + pvr_sprite_cxt_txr(&context, PVR_LIST_TR_POLY, PVR_TXRFMT_ARGB4444 | PVR_TXRFMT_NONTWIDDLED, texture_width, texture_height, texture, PVR_FILTER_BILINEAR); + pvr_sprite_compile(&header, &context); + pvr_prim(&header, sizeof(header)); + + pvr_sprite_txr_t quad = { + .flags = PVR_CMD_VERTEX_EOL, + .ax = (float)sprite->x, + .ay = (float)(sprite->y + sprite->height), + .az = 1.0f, + .bx = (float)sprite->x, + .by = (float)sprite->y, + .bz = 1.0f, + .cx = (float)(sprite->x + sprite->width), + .cy = (float)sprite->y, + .cz = 1.0f, + .dx = (float)(sprite->x + sprite->width), + .dy = (float)(sprite->y + sprite->height), + .dummy = 0, + .auv = PVR_PACK_16BIT_UV(0.0f, (float)sprite->height / texture_height), + .buv = PVR_PACK_16BIT_UV(0.0f, 0.0f), + .cuv = PVR_PACK_16BIT_UV((float)sprite->width / texture_width, 0.0f) + }; + pvr_prim(&quad, sizeof(quad)); + } - pvr_scene_begin(); - pvr_list_begin(PVR_LIST_OP_POLY); - pvr_prim(header, sizeof(*header)); - pvr_prim(&sprite, sizeof(sprite)); pvr_list_finish(); pvr_scene_finish(); pvr_wait_render_done(); + + for (uint8_t i = 0; i < ta_display_sprites->count; i++) { + pvr_mem_free(textures[i].texture); + ta_free(textures[i].pixels); + } } int main(void) { - pvr_ptr_t texture; - pvr_sprite_cxt_t context; - pvr_sprite_hdr_t header; - vid_set_mode(DM_320x240, PM_RGB565); if (pvr_init_defaults() < 0) { return 1; } - texture = pvr_mem_malloc(sizeof(texture_pixels)); - if (!texture) { - pvr_shutdown(); - return 1; - } - pvr_set_bg_color(0.0f, 0.0f, 0.0f); - pvr_sprite_cxt_txr(&context, PVR_LIST_OP_POLY, PVR_TXRFMT_RGB565 | PVR_TXRFMT_NONTWIDDLED, TEXTURE_WIDTH, TEXTURE_HEIGHT, texture, PVR_FILTER_NONE); - pvr_sprite_compile(&header, &context); - ta_fb.width = SCREEN_WIDTH; - ta_fb.height = SCREEN_HEIGHT; - ta_fb.bpp = WINDOW_BPP; + ta_display->width = SCREEN_WIDTH; + ta_display->height = SCREEN_HEIGHT; + ta_display->bpp = WINDOW_BPP; + ta_display->sprite_mode = ta_true; ta_main_init(); while (1) { + uint64_t frame_start = timer_ms_gettime64(); + update_controller_input(); ta_main_update(); - convert_framebuffer(); - pvr_txr_load(texture_pixels, texture, sizeof(texture_pixels)); - render_frame(&header); + render_sprites(); + + uint64_t frame_time = timer_ms_gettime64() - frame_start; + if (frame_time < FRAME_TIME_MS) { + thd_sleep((int)(FRAME_TIME_MS - frame_time)); + } } - pvr_mem_free(texture); pvr_shutdown(); return 0; } diff --git a/platforms/n64/.gitignore b/platforms/n64/.gitignore @@ -0,0 +1 @@ +tuxanci2.z64 diff --git a/platforms/n64/n64.c b/platforms/n64/n64.c @@ -1,13 +1,22 @@ #include "ta_api.h" #include <display.h> +#include <rdpq.h> +#include <rdpq_attach.h> +#include <rdpq_mode.h> +#include <rdpq_tex.h> #include <joypad.h> #include <surface.h> +#include <n64sys.h> +#include <timer.h> #include <stdlib.h> #include <stdint.h> +#include <malloc.h> #define SCREEN_WIDTH 320 #define SCREEN_HEIGHT 240 #define WINDOW_BPP 32 +#define TARGET_FPS 60 +#define FRAME_TIME_MS (1000 / TARGET_FPS) static void update_controller_input(void) { joypad_buttons_t buttons = joypad_get_buttons(JOYPAD_PORT_1); @@ -45,35 +54,47 @@ static void update_controller_input(void) { int main(void) { display_init(RESOLUTION_320x240, DEPTH_32_BPP, 2, GAMMA_NONE, FILTERS_RESAMPLE); + rdpq_init(); joypad_init(); - ta_fb.width = SCREEN_WIDTH; - ta_fb.height = SCREEN_HEIGHT; - ta_fb.bpp = WINDOW_BPP; + ta_display->width = SCREEN_WIDTH; + ta_display->height = SCREEN_HEIGHT; + ta_display->bpp = WINDOW_BPP; + ta_display->sprite_mode = ta_true; ta_main_init(); while (1) { + uint32_t frame_start = get_ticks_ms(); surface_t *display = display_get(); joypad_poll(); update_controller_input(); ta_main_update(); - const ta_uint32 *source = (const ta_uint32 *)ta_fb.buffers[0]; - ta_uint32 *destination = (ta_uint32 *)display->buffer; - - for (ta_uint32 i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT; i++) { - destination[i] = (source[i] << 8) | 0xff; + rdpq_attach_clear(display, NULL); + rdpq_set_mode_standard(); + rdpq_mode_filter(FILTER_POINT); + rdpq_mode_combiner(RDPQ_COMBINER_TEX); + rdpq_mode_blender(RDPQ_BLENDER_MULTIPLY); + for (ta_uint8 i = 0; i < ta_display_sprites->count; i++) { + ta_display_sprite *sprite = ta_display_sprites->sprites[i]; + surface_t texture = surface_make_linear(sprite->pixels, FMT_RGBA32, sprite->width, sprite->height); + data_cache_hit_writeback(sprite->pixels, (uint32_t)sprite->width * sprite->height * sizeof(uint32_t)); + rdpq_tex_blit(&texture, sprite->x, sprite->y, NULL); } + rdpq_detach_show(); - display_show(display); + uint32_t frame_time = get_ticks_ms() - frame_start; + if (frame_time < FRAME_TIME_MS) { + wait_ms(FRAME_TIME_MS - frame_time); + } } return 0; } void *ta_malloc(ta_size size) { - return malloc(size); + return memalign(8, size); } void ta_free(void *ptr) { diff --git a/platforms/x11/x11.c b/platforms/x11/x11.c @@ -190,6 +190,10 @@ void *ta_malloc(ta_size size) { return malloc(size); } +void *ta_realloc(void *ptr, ta_size new_size) { + return realloc(ptr, new_size); +} + void ta_free(void *ptr) { free(ptr); } diff --git a/platforms/xbox/xbox.c b/platforms/xbox/xbox.c @@ -59,9 +59,9 @@ int main(void) { return 1; } - ta_fb.width = SCREEN_WIDTH; - ta_fb.height = SCREEN_HEIGHT; - ta_fb.bpp = WINDOW_BPP; + ta_display->width = SCREEN_WIDTH; + ta_display->height = SCREEN_HEIGHT; + ta_display->bpp = WINDOW_BPP; ta_main_init(); SDL_GameController *controller = open_controller(); @@ -88,7 +88,7 @@ int main(void) { ta_main_update(); uint32_t *dest = (uint32_t *)XVideoGetFB(); - uint32_t *src = (uint32_t *)ta_fb.buffers[0]; + uint32_t *src = (uint32_t *)ta_display->buffers[0]; if (dest && src) { for (int i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT; i++) { diff --git a/src/screens/ta_screen_loading.c b/src/screens/ta_screen_loading.c @@ -1,3 +1,4 @@ +#include "ta_display.h" #include "ta_fb.h" #include "ta_files.h" #include "ta_image.h" @@ -18,8 +19,8 @@ static void draw_loading_logo(void) { return; } - ta_uint64 frame_width = ta_fb.width; - ta_uint64 frame_height = ta_fb.height; + ta_uint64 frame_width = ta_display->width; + ta_uint64 frame_height = ta_display->height; ta_fixed draw_width; ta_fixed draw_height; @@ -45,7 +46,7 @@ void ta_screen_loading_init(void) { } ta_bool ta_screen_loading_update(void) { - ta_fb_clear(&ta_fb); + ta_fb_clear(ta_display); if (!logo_loaded) { logo_loaded = ta_image_init(ta_file_loading_logo, ta_file_loading_logo_len); diff --git a/src/screens/ta_screen_main_menu.c b/src/screens/ta_screen_main_menu.c @@ -1,4 +1,5 @@ #include "ta_button.h" +#include "ta_display.h" #include "ta_fb.h" #include "ta_files.h" #include "ta_image.h" @@ -7,9 +8,11 @@ static ta_button_lists menu; static ta_bool needs_redraw; +static ta_bool background_in_sprites; void ta_screen_main_menu_init(void) { needs_redraw = ta_true; + background_in_sprites = ta_false; ta_button_lists_init(&menu); ta_button_lists_add(&menu, "Play", 0); @@ -27,8 +30,18 @@ ta_bool ta_screen_main_menu_update(void) { needs_redraw = ta_false; - 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); + if (ta_display->sprite_mode && background_in_sprites && ta_display_sprites != 0 && ta_display_sprites->count >= 1) { + while (ta_display_sprites->count > 1) { + ta_display_sprite_free(ta_display_sprites->sprites[ta_display_sprites->count - 1]); + } + } else { + ta_display_sprites_clear(); + + if (!ta_image_draw(ta_file_bg, ta_file_bg_len, 0, 0, TA_FIXED_ONE, TA_FIXED_ONE, 0)) { + ta_fb_clear(ta_display); + } else { + background_in_sprites = ta_display->sprite_mode; + } } ta_button_lists_draw(&menu); diff --git a/src/ta_button.c b/src/ta_button.c @@ -7,8 +7,20 @@ #define TA_BUTTON_SPACING 48 #define TA_BUTTON_COLOR_IDLE 118 #define TA_BUTTON_COLOR_SELECTED 255 -#define TA_BUTTON_COLOR_PULSE_BLUE_MIN 96 -#define TA_BUTTON_PULSE_HALF_FRAMES 24 + +static ta_uint32 scale_to_display(ta_uint32 value) { + ta_uint32 numerator = (ta_uint32)ta_display->width; + ta_uint32 denominator = 800; + + if ((ta_uint64)ta_display->width * 600 >= (ta_uint64)ta_display->height * 800) { + numerator = (ta_uint32)ta_display->height; + denominator = 600; + } + + ta_uint32 scaled = (value * numerator * 2 + denominator) / (denominator * 2); + + return scaled == 0 ? 1 : scaled; +} void ta_button_lists_init(ta_button_lists *list) { if (list == 0) { @@ -20,7 +32,6 @@ void ta_button_lists_init(ta_button_lists *list) { list->selected = 0; list->animation_from = 0; list->animation_frame = 0; - list->pulse_frame = 0; list->animation_active = ta_false; list->up_was_down = ta_false; list->down_was_down = ta_false; @@ -45,6 +56,29 @@ ta_bool ta_button_lists_add(ta_button_lists *list, const char *label, ta_button_ buttons[list->size].label = label; buttons[list->size].callback = callback; + buttons[list->size].text_idle.text = label; + buttons[list->size].text_idle.size = (ta_uint8)(scale_to_display(TA_BUTTON_FONT_SIZE) > 255 ? 255 : scale_to_display(TA_BUTTON_FONT_SIZE)); + buttons[list->size].text_idle.red = TA_BUTTON_COLOR_IDLE; + buttons[list->size].text_idle.green = TA_BUTTON_COLOR_IDLE; + buttons[list->size].text_idle.blue = TA_BUTTON_COLOR_IDLE; + buttons[list->size].text_idle.border_width = 1; + buttons[list->size].text_idle.border_red = 0; + buttons[list->size].text_idle.border_green = 0; + buttons[list->size].text_idle.border_blue = 0; + buttons[list->size].text_idle.sprite = 0; + ta_text_rasterize(&buttons[list->size].text_idle); + + buttons[list->size].text_selected.text = label; + buttons[list->size].text_selected.size = (ta_uint8)(scale_to_display(TA_BUTTON_FONT_SIZE) > 255 ? 255 : scale_to_display(TA_BUTTON_FONT_SIZE)); + buttons[list->size].text_selected.red = TA_BUTTON_COLOR_SELECTED; + buttons[list->size].text_selected.green = TA_BUTTON_COLOR_SELECTED; + buttons[list->size].text_selected.blue = TA_BUTTON_COLOR_SELECTED; + buttons[list->size].text_selected.border_width = 1; + buttons[list->size].text_selected.border_red = 0; + buttons[list->size].text_selected.border_green = 0; + buttons[list->size].text_selected.border_blue = 0; + buttons[list->size].text_selected.sprite = 0; + ta_text_rasterize(&buttons[list->size].text_selected); ta_free(list->buttons); @@ -59,8 +93,7 @@ ta_bool ta_button_lists_update(ta_button_lists *list) { return ta_false; } - list->pulse_frame = (list->pulse_frame + 1) % (TA_BUTTON_PULSE_HALF_FRAMES * 2); - + ta_bool moved = ta_false; ta_bool up_down = ta_keyboard_is_down(KEY_UP); ta_bool down_down = ta_keyboard_is_down(KEY_DOWN); ta_bool enter_down = ta_keyboard_is_down(KEY_ENTER); @@ -98,6 +131,7 @@ ta_bool ta_button_lists_update(ta_button_lists *list) { if (list->selected != list->animation_from) { list->animation_frame = 0; list->animation_active = ta_true; + moved = ta_true; } } @@ -105,7 +139,7 @@ ta_bool ta_button_lists_update(ta_button_lists *list) { list->buttons[list->selected].callback(); } - return ta_true; + return moved; } void ta_button_lists_draw(const ta_button_lists *list) { @@ -113,24 +147,7 @@ void ta_button_lists_draw(const ta_button_lists *list) { return; } - 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; - } - - if (spacing == 0) { - spacing = 1; - } + ta_int32 spacing = (ta_int32)scale_to_display(TA_BUTTON_SPACING); ta_int32 progress = 512; if (list->animation_active) { @@ -138,36 +155,23 @@ void ta_button_lists_draw(const ta_button_lists *list) { progress = frame * frame * (24 - 2 * frame); } - ta_int32 center_y = (ta_int32)(ta_fb.height / 2); + ta_int32 center_y = (ta_int32)(ta_display->height / 2); ta_int32 selected_shift = (ta_int32)list->selected - (ta_int32)list->animation_from; for (ta_size i = 0; i < list->size; i++) { - ta_uint32 text_width = 0; - ta_uint32 text_height = 0; - - ta_text_measure(list->buttons[i].label, font_size, &text_width, &text_height); + ta_button *btn = &((ta_button_lists *)list)->buttons[i]; + ta_text *txt = (i == list->selected) ? &btn->text_selected : &btn->text_idle; ta_int32 row_from = (ta_int32)i - (ta_int32)list->animation_from; - ta_int32 y = center_y + (row_from * spacing * 512 - selected_shift * spacing * progress) / 512 - (ta_int32)(text_height / 2); + ta_int32 y = center_y + (row_from * spacing * 512 - selected_shift * spacing * progress) / 512 - (ta_int32)(txt->height / 2); - if (y < 0 || (ta_size)y >= ta_fb.height || text_width > ta_fb.width) { + if (y < 0 || (ta_size)y >= ta_display->height || txt->width > ta_display->width) { continue; } - ta_int32 selection = i == list->animation_from ? 512 - progress : 0; - if (i == list->selected) { - selection += progress; - } - - 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 * 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 * 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); + txt->x = (ta_uint32)(ta_display->width - txt->width) / 2; + txt->y = (ta_uint32)y; + ta_text_draw(txt); } } @@ -176,6 +180,11 @@ void ta_button_lists_destroy(ta_button_lists *list) { return; } + for (ta_size i = 0; i < list->size; i++) { + ta_text_destroy(&list->buttons[i].text_idle); + ta_text_destroy(&list->buttons[i].text_selected); + } + ta_free(list->buttons); ta_button_lists_init(list); } diff --git a/src/ta_display.c b/src/ta_display.c @@ -0,0 +1,107 @@ +#include "ta_display.h" +#include "ta_api.h" + +static ta_display_t default_display; +ta_display_t *ta_display = &default_display; +ta_display_sprites_t *ta_display_sprites; + +ta_bool ta_display_init(void) { + if (ta_display_sprites) { + return ta_true; + } + + ta_display_sprites = ta_malloc(sizeof(*ta_display_sprites)); + if (!ta_display_sprites) { + return ta_false; + } + + ta_display_sprites->count = 0; + ta_display_sprites->capacity = 0; + ta_display_sprites->sprites = TA_NULL; + + return ta_true; +} + +void ta_display_deinit(void) { + if (ta_display_sprites) { + for (ta_uint8 i = 0; i < ta_display_sprites->count; i++) { + ta_free(ta_display_sprites->sprites[i]); + } + + ta_free(ta_display_sprites->sprites); + ta_free(ta_display_sprites); + ta_display_sprites = TA_NULL; + } + + ta_free(ta_display->buffers[0]); + ta_free(ta_display->buffers[1]); + ta_display->buffers[0] = TA_NULL; + ta_display->buffers[1] = TA_NULL; + ta_display->size = 0; +} + +ta_bool ta_display_sprite_add(ta_display_sprite *sprite) { + if (!sprite || !ta_display_sprites) { + return ta_false; + } + + ta_display_sprites_t *list = ta_display_sprites; + + if (list->count == list->capacity) { + if (list->capacity == 255) { + return ta_false; + } + + ta_uint16 new_capacity = list->capacity ? (ta_uint16)list->capacity * 2 : 8; + if (new_capacity > 255) { + new_capacity = 255; + } + + ta_display_sprite **new_sprites = ta_malloc(new_capacity * sizeof(ta_display_sprite *)); + if (!new_sprites) { + return ta_false; + } + + for (ta_uint8 i = 0; i < list->count; i++) { + new_sprites[i] = list->sprites[i]; + } + + ta_free(list->sprites); + list->sprites = new_sprites; + list->capacity = (ta_uint8)new_capacity; + } + + list->sprites[list->count++] = sprite; + return ta_true; +} + +void ta_display_sprite_free(ta_display_sprite *sprite) { + if (!sprite) { + return; + } + + if (ta_display_sprites) { + for (ta_uint8 i = 0; i < ta_display_sprites->count; i++) { + if (ta_display_sprites->sprites[i] == sprite) { + for (ta_uint8 j = i; j < ta_display_sprites->count - 1; j++) { + ta_display_sprites->sprites[j] = ta_display_sprites->sprites[j + 1]; + } + + ta_display_sprites->count--; + break; + } + } + } + + ta_free(sprite); +} + +void ta_display_sprites_clear(void) { + if (!ta_display_sprites) { + return; + } + + while (ta_display_sprites->count != 0) { + ta_display_sprite_free(ta_display_sprites->sprites[ta_display_sprites->count - 1]); + } +} diff --git a/src/ta_fb.c b/src/ta_fb.c @@ -1,7 +1,5 @@ #include "ta_api.h" -ta_fbs ta_fb; - ta_size ta_fb_init(ta_fbs *fb) { if (fb == 0 || fb->width == 0 || fb->height == 0 || fb->bpp == 0 || fb->bpp % 8 != 0) { return 0; @@ -14,6 +12,11 @@ ta_size ta_fb_init(ta_fbs *fb) { } ta_size size = fb->width * fb->height * bytes_per_pixel; + ta_free(fb->buffers[0]); + ta_free(fb->buffers[1]); + fb->buffers[0] = 0; + fb->buffers[1] = 0; + void *first = ta_malloc(size); if (first == 0) { return 0; @@ -28,16 +31,40 @@ ta_size ta_fb_init(ta_fbs *fb) { fb->size = size; fb->buffers[0] = first; fb->buffers[1] = second; + if (fb->bpp == 8) { + fb->format = TA_DISPLAY_PIXEL_FORMAT_8; + } else if (fb->bpp == 16) { + fb->format = TA_DISPLAY_PIXEL_FORMAT_16; + } else { + fb->format = TA_DISPLAY_PIXEL_FORMAT_32; + } return size; } void ta_fb_swap(ta_fbs *fb) { + if (!fb || !fb->buffers[0] || !fb->buffers[1]) { + return; + } + void *temp = fb->buffers[0]; fb->buffers[0] = fb->buffers[1]; fb->buffers[1] = temp; } void ta_fb_clear(ta_fbs *fb) { + if (!fb) { + return; + } + + if (fb->sprite_mode) { + ta_display_sprites_clear(); + return; + } + + if (!fb->buffers[1] || fb->size == 0) { + return; + } + ta_size *dest = (ta_size *)fb->buffers[1]; ta_size num_words = fb->size / sizeof(ta_size); diff --git a/src/ta_image.c b/src/ta_image.c @@ -427,11 +427,11 @@ static void copy_bytes(ta_uint8 *destination, const ta_uint8 *source, ta_size co } static ta_bool prepare_scaled(ta_image_cache *image, ta_uint32 width, ta_uint32 height) { - if (image->scaled != 0 && image->scaled_width == width && image->scaled_height == height && image->scaled_bpp == ta_fb.bpp) { + if (image->scaled != 0 && image->scaled_width == width && image->scaled_height == height && image->scaled_bpp == ta_display->bpp) { return ta_true; } - ta_size bytes_per_pixel = ta_fb.bpp / 8; + ta_size bytes_per_pixel = ta_display->bpp / 8; ta_uint8 *scaled = (ta_uint8 *)ta_malloc((ta_size)width * height * bytes_per_pixel); ta_image_axis *columns = (ta_image_axis *)ta_malloc((ta_size)width * sizeof(*columns)); @@ -462,7 +462,7 @@ static ta_bool prepare_scaled(ta_image_cache *image, ta_uint32 width, ta_uint32 green = (green + 32768) >> 16; blue = (blue + 32768) >> 16; - if (ta_fb.bpp == 32) { + if (ta_display->bpp == 32) { *(ta_uint32 *)output = (red << 16) | (green << 8) | blue; output += 4; } else { @@ -477,18 +477,18 @@ static ta_bool prepare_scaled(ta_image_cache *image, ta_uint32 width, ta_uint32 image->scaled = scaled; image->scaled_width = width; image->scaled_height = height; - image->scaled_bpp = (ta_uint8)ta_fb.bpp; + image->scaled_bpp = (ta_uint8)ta_display->bpp; return ta_true; } 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)) { + if (data == 0 || length == 0 || width == 0 || height == 0 || width > TA_FIXED_ONE || height > TA_FIXED_ONE || (!ta_display->sprite_mode && ta_display->buffers[1] == 0) || (ta_display->bpp != 8 && ta_display->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); + ta_uint32 pixel_width = (ta_uint32)(((ta_uint64)width * ta_display->width) >> 16); + ta_uint32 pixel_height = (ta_uint32)(((ta_uint64)height * ta_display->height) >> 16); if (pixel_width == 0 || pixel_height == 0) { return ta_false; @@ -507,14 +507,14 @@ ta_bool ta_image_prepare(const ta_uint8 *data, ta_uint32 length, ta_fixed width, } 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)) { + 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_display->sprite_mode && ta_display->buffers[1] == 0) || (ta_display->sprite_mode && (!ta_display_sprites || ta_display->bpp != 32)) || (ta_display->bpp != 8 && ta_display->bpp != 32)) { return ta_false; } - 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); + ta_uint32 pixel_x = (ta_uint32)(((ta_uint64)x * ta_display->width) >> 16); + ta_uint32 pixel_y = (ta_uint32)(((ta_uint64)y * ta_display->height) >> 16); + ta_uint32 pixel_width = (ta_uint32)(((ta_uint64)width * ta_display->width) >> 16); + ta_uint32 pixel_height = (ta_uint32)(((ta_uint64)height * ta_display->height) >> 16); if (pixel_width == 0 || pixel_height == 0) { return ta_false; @@ -528,12 +528,12 @@ ta_bool ta_image_draw(const ta_uint8 *data, ta_uint32 length, ta_fixed x, ta_fix ta_uint32 visible_width = 0; ta_uint32 visible_height = 0; - if (pixel_x < ta_fb.width) { - visible_width = pixel_width > ta_fb.width - pixel_x ? (ta_uint32)(ta_fb.width - pixel_x) : pixel_width; + if (pixel_x < ta_display->width) { + visible_width = pixel_width > ta_display->width - pixel_x ? (ta_uint32)(ta_display->width - pixel_x) : pixel_width; } - if (pixel_y < ta_fb.height) { - visible_height = pixel_height > ta_fb.height - pixel_y ? (ta_uint32)(ta_fb.height - pixel_y) : pixel_height; + if (pixel_y < ta_display->height) { + visible_height = pixel_height > ta_display->height - pixel_y ? (ta_uint32)(ta_display->height - pixel_y) : pixel_height; } if (visible_width == 0 || visible_height == 0) { @@ -542,14 +542,83 @@ ta_bool ta_image_draw(const ta_uint8 *data, ta_uint32 length, ta_fixed x, ta_fix ta_bool transparent = (image->flags & TAIF_FLAG_TRANSPARENT) != 0; + if (ta_display->sprite_mode) { + ta_size max_size = (ta_size)-1; + if (visible_width > max_size / sizeof(ta_uint32) / visible_height) { + return ta_false; + } + + ta_size pixel_count = (ta_size)visible_width * visible_height; + ta_display_sprite *sprite = (ta_display_sprite *)ta_malloc(sizeof(*sprite) + pixel_count * sizeof(ta_uint32)); + if (!sprite) { + return ta_false; + } + + sprite->x = (ta_uint16)pixel_x; + sprite->y = (ta_uint16)pixel_y; + sprite->width = (ta_uint16)visible_width; + sprite->height = (ta_uint16)visible_height; + sprite->format = TA_DISPLAY_PIXEL_FORMAT_32; + ta_uint32 *pixels = (ta_uint32 *)sprite->pixels; + + if (!transparent && prepare_scaled(image, pixel_width, pixel_height)) { + const ta_uint32 *source = (const ta_uint32 *)image->scaled; + for (ta_uint32 image_y = 0; image_y < visible_height; image_y++) { + for (ta_uint32 image_x = 0; image_x < visible_width; image_x++) { + ta_uint32 color = source[(ta_size)image_y * pixel_width + image_x]; + pixels[(ta_size)image_y * visible_width + image_x] = (color << 8) | 0xff; + } + } + } else { + ta_image_axis *columns = (ta_image_axis *)ta_malloc((ta_size)visible_width * sizeof(*columns)); + if (!columns) { + ta_free(sprite); + return ta_false; + } + + for (ta_uint32 image_x = 0; image_x < visible_width; image_x++) { + columns[image_x] = axis_position(image_x, pixel_width, image->width); + } + + for (ta_uint32 image_y = 0; image_y < visible_height; image_y++) { + ta_image_axis row = axis_position(image_y, pixel_height, image->height); + for (ta_uint32 image_x = 0; image_x < visible_width; image_x++) { + ta_uint32 red; + ta_uint32 green; + ta_uint32 blue; + ta_uint32 alpha = sample_image(image, columns[image_x], row, &red, &green, &blue); + + if (alpha == 0) { + pixels[(ta_size)image_y * visible_width + image_x] = 0; + } else { + red = (red + alpha / 2) / alpha; + green = (green + alpha / 2) / alpha; + blue = (blue + alpha / 2) / alpha; + ta_uint32 output_alpha = (alpha * 255 + 32768) >> 16; + pixels[(ta_size)image_y * visible_width + image_x] = (red << 24) | (green << 16) | (blue << 8) | output_alpha; + } + } + } + + ta_free(columns); + } + + if (!ta_display_sprite_add(sprite)) { + ta_free(sprite); + return ta_false; + } + + return ta_true; + } + if (!transparent && prepare_scaled(image, pixel_width, pixel_height)) { - ta_size bytes_per_pixel = ta_fb.bpp / 8; + ta_size bytes_per_pixel = ta_display->bpp / 8; for (ta_uint32 image_y = 0; image_y < visible_height; image_y++) { const ta_uint8 *source = image->scaled + (ta_size)image_y * pixel_width * bytes_per_pixel; - ta_uint8 *destination = (ta_uint8 *)ta_fb.buffers[1] + ((ta_size)(pixel_y + image_y) * ta_fb.width + pixel_x) * bytes_per_pixel; + ta_uint8 *destination = (ta_uint8 *)ta_display->buffers[1] + ((ta_size)(pixel_y + image_y) * ta_display->width + pixel_x) * bytes_per_pixel; - if (ta_fb.bpp == 8) { + if (ta_display->bpp == 8) { copy_bytes(destination, source, visible_width); } else { const ta_uint32 *source32 = (const ta_uint32 *)source; @@ -575,7 +644,7 @@ ta_bool ta_image_draw(const ta_uint8 *data, ta_uint32 length, ta_fixed x, ta_fix for (ta_uint32 image_y = 0; image_y < visible_height; image_y++) { ta_image_axis row = axis_position(image_y, pixel_height, image->height); - ta_size row_start = (ta_size)(pixel_y + image_y) * ta_fb.width + pixel_x; + ta_size row_start = (ta_size)(pixel_y + image_y) * ta_display->width + pixel_x; for (ta_uint32 image_x = 0; image_x < visible_width; image_x++) { ta_uint32 red; @@ -590,8 +659,8 @@ ta_bool ta_image_draw(const ta_uint8 *data, ta_uint32 length, ta_fixed x, ta_fix ta_size destination_index = row_start + image_x; ta_uint32 inverse_alpha = 65536 - alpha_sum; - if (ta_fb.bpp == 32) { - ta_uint32 *pixels = (ta_uint32 *)ta_fb.buffers[1]; + if (ta_display->bpp == 32) { + ta_uint32 *pixels = (ta_uint32 *)ta_display->buffers[1]; ta_uint32 destination = pixels[destination_index]; red = (red + ((destination >> 16) & 0xff) * inverse_alpha + 32768) >> 16; @@ -600,7 +669,7 @@ ta_bool ta_image_draw(const ta_uint8 *data, ta_uint32 length, ta_fixed x, ta_fix pixels[destination_index] = (destination & 0xff000000u) | (red << 16) | (green << 8) | blue; } else { - ta_uint8 *pixels = (ta_uint8 *)ta_fb.buffers[1]; + ta_uint8 *pixels = (ta_uint8 *)ta_display->buffers[1]; ta_uint32 destination = pixels[destination_index]; red = (red + (((destination >> 5) & 7) * 255 / 7) * inverse_alpha + 32768) >> 16; diff --git a/src/ta_main.c b/src/ta_main.c @@ -3,7 +3,9 @@ #include "ta_screen.h" void ta_main_init(void) { - ta_fb_init(&ta_fb); + if (!ta_display_init() || (!ta_display->sprite_mode && !ta_fb_init(ta_display))) { + ta_exit(1); + } ta_screen_init(); } @@ -19,6 +21,7 @@ ta_bool ta_main_update(void) { void ta_main_destroy(void) { ta_screen_destroy(); + ta_display_deinit(); ta_exit(0); } diff --git a/src/ta_text.c b/src/ta_text.c @@ -1,3 +1,4 @@ +#include "ta_text.h" #include "ta_api.h" #include "ta_files.h" @@ -24,10 +25,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_uint32 *sprite_pixels; + ta_int32 sprite_x; + ta_int32 sprite_y; + ta_int32 sprite_width; } ta_text_ctx; typedef struct { @@ -508,18 +509,34 @@ static ta_uint32 blend_channel(ta_uint32 destination, ta_uint32 source, ta_uint3 } static void put_pixel(const ta_text_ctx *ctx, ta_int32 pixel_x, ta_int32 pixel_y, ta_uint32 alpha) { - ta_size index = (ta_size)pixel_y * ta_fb.width + (ta_size)pixel_x; + ta_size index = (ta_size)(pixel_y - ctx->sprite_y) * (ta_size)ctx->sprite_width + (ta_size)(pixel_x - ctx->sprite_x); + ta_uint32 destination = ctx->sprite_pixels[index]; + ta_uint32 destination_alpha = destination & 255; + ta_uint32 inverse_alpha = 255 - alpha; + ta_uint32 output_alpha = alpha + (destination_alpha * inverse_alpha + 127) / 255; - if (ta_fb.bpp == 32) { - ta_uint32 *pixel = (ta_uint32 *)ta_fb.buffers[1] + index; + if (output_alpha == 0) { + return; + } + + ta_uint32 destination_red = (destination >> 24) & 255; + ta_uint32 destination_green = (destination >> 16) & 255; + ta_uint32 destination_blue = (destination >> 8) & 255; + ta_uint32 output_red = ((ta_uint32)ctx->red * alpha * 255 + destination_red * destination_alpha * inverse_alpha + output_alpha * 127) / (output_alpha * 255); + ta_uint32 output_green = ((ta_uint32)ctx->green * alpha * 255 + destination_green * destination_alpha * inverse_alpha + output_alpha * 127) / (output_alpha * 255); + ta_uint32 output_blue = ((ta_uint32)ctx->blue * alpha * 255 + destination_blue * destination_alpha * inverse_alpha + output_alpha * 127) / (output_alpha * 255); + + ctx->sprite_pixels[index] = (output_red << 24) | (output_green << 16) | (output_blue << 8) | output_alpha; +} + +static void blit_pixel(ta_size index, ta_uint32 red, ta_uint32 green, ta_uint32 blue, ta_uint32 alpha) { + if (ta_display->bpp == 32) { + ta_uint32 *pixel = (ta_uint32 *)ta_display->buffers[1] + index; ta_uint32 destination = *pixel; - ta_uint32 red = blend_channel((destination >> 16) & 255, ctx->red, alpha); - ta_uint32 green = blend_channel((destination >> 8) & 255, ctx->green, alpha); - ta_uint32 blue = blend_channel(destination & 255, ctx->blue, alpha); - *pixel = (destination & 0xFF000000u) | (red << 16) | (green << 8) | blue; + *pixel = (destination & 0xFF000000u) | (blend_channel((destination >> 16) & 255, red, alpha) << 16) | (blend_channel((destination >> 8) & 255, green, alpha) << 8) | blend_channel(destination & 255, blue, alpha); } else { - ta_uint8 *pixel = (ta_uint8 *)ta_fb.buffers[1] + index; + ta_uint8 *pixel = (ta_uint8 *)ta_display->buffers[1] + index; ta_uint32 destination = *pixel; ta_uint32 dest_red = (destination >> 5) & 7; ta_uint32 dest_green = (destination >> 2) & 7; @@ -529,24 +546,15 @@ static void put_pixel(const ta_text_ctx *ctx, ta_int32 pixel_x, ta_int32 pixel_y dest_green = (dest_green << 5) | (dest_green << 2) | (dest_green >> 1); dest_blue = dest_blue * 85; - ta_uint32 red = blend_channel(dest_red, ctx->red, alpha); - ta_uint32 green = blend_channel(dest_green, ctx->green, alpha); - ta_uint32 blue = blend_channel(dest_blue, ctx->blue, alpha); + red = blend_channel(dest_red, red, alpha); + green = blend_channel(dest_green, green, alpha); + blue = blend_channel(dest_blue, blue, alpha); *pixel = (ta_uint8)(((red >> 5) << 5) | ((green >> 5) << 2) | (blue >> 6)); } } 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); } @@ -850,14 +858,6 @@ static void measure_units(const char *text, ta_uint32 *max_width, ta_uint32 *lin *lines = line_count; } -static ta_bool fb_usable(void) { - return ta_fb.buffers[1] != 0 && (ta_fb.bpp == 32 || ta_fb.bpp == 8); -} - -static ta_int32 fb_limit(ta_size value) { - return value > MAX_COORD ? MAX_COORD : (ta_int32)value; -} - static void draw_text_pass(const char *text, ta_int32 x, ta_int32 y, const ta_text_ctx *ctx) { ta_int32 scale = ctx->scale; ta_int32 ascent = (ta_int32)((ta_int64)font.ascent * scale); @@ -913,304 +913,166 @@ static void draw_text_direct(const char *text, ta_int32 x, ta_int32 y, const ta_ 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++; +void ta_text_rasterize(ta_text *text) { + if (!text) { + return; } - return *a == *b; -} - -static ta_text_entry *text_cache_build(const char *text, ta_int32 scale) { - ta_uint32 length = 0; + ta_text_destroy(text); - while (text[length]) { - if (++length > TEXT_CACHE_MAX_TEXT) { - return 0; - } + if (!text->text || text->size == 0 || !font_ready()) { + return; } ta_uint32 units; ta_uint32 lines; - measure_units(text, &units, &lines); + measure_units(text->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; + ta_uint64 scale = unsigned_divide64((ta_uint64)text->size << 16, font.units_per_em); + ta_uint64 text_width = (units * scale + (ONE - 1)) >> 16; + ta_uint64 text_height = ((ta_uint64)lines * line_height_units() * scale + (ONE - 1)) >> 16; + ta_uint32 border = text->border_width; + ta_uint64 sprite_width = text_width + (ta_uint64)border * 2; + ta_uint64 sprite_height = text_height + (ta_uint64)border * 2; - if (width <= 0 || height <= 0 || (ta_uint64)width * (ta_uint64)height > TEXT_CACHE_MAX_MASK) { - return 0; + if (text_width == 0 || text_height == 0 || sprite_width > MAX_COORD || sprite_height > MAX_COORD) { + return; } - ta_size mask_size = (ta_size)width * (ta_size)height; - ta_uint8 *mask = (ta_uint8 *)ta_malloc(mask_size); + ta_size pixel_count = (ta_size)sprite_width * (ta_size)sprite_height; + ta_display_sprite *sprite = (ta_display_sprite *)ta_malloc(sizeof(*sprite) + pixel_count * sizeof(ta_uint32)); - if (mask == 0) { - return 0; - } - - for (ta_size i = 0; i < mask_size; i++) { - mask[i] = 0; + if (!sprite) { + return; } - 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); + sprite->x = 0; + sprite->y = 0; + sprite->width = (ta_uint16)sprite_width; + sprite->height = (ta_uint16)sprite_height; + sprite->format = TA_DISPLAY_PIXEL_FORMAT_32; + sprite->_pad = 0; - ta_text_entry *entry = &text_cache[text_cache_next]; + ta_uint32 *pixels = (ta_uint32 *)sprite->pixels; - 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]; + for (ta_size i = 0; i < pixel_count; i++) { + pixels[i] = 0; } - entry->scale = scale; - entry->x0 = -padding; - entry->y0 = -padding; - entry->width = width; - entry->height = height; - entry->mask = mask; + ta_text_ctx ctx; - return entry; + ctx.scale = (ta_int32)scale; + ctx.clip_x0 = 0; + ctx.clip_y0 = 0; + ctx.clip_x1 = (ta_int32)sprite_width; + ctx.clip_y1 = (ta_int32)sprite_height; + ctx.red = text->red; + ctx.green = text->green; + ctx.blue = text->blue; + ctx.border_thickness = text->border_width; + ctx.border_red = text->border_red; + ctx.border_green = text->border_green; + ctx.border_blue = text->border_blue; + ctx.sprite_pixels = pixels; + ctx.sprite_x = 0; + ctx.sprite_y = 0; + ctx.sprite_width = (ta_int32)sprite_width; + + draw_text_direct(text->text, (ta_int32)border, (ta_int32)border, &ctx); + + text->width = (ta_uint32)text_width; + text->height = (ta_uint32)text_height; + text->sprite = sprite; } -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]; - } +void ta_text_draw(ta_text *text) { + if (!text || !text->sprite) { + return; } - 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; + const ta_display_sprite *source = text->sprite; + ta_int32 destination_x = (ta_int32)text->x - (ta_int32)text->border_width; + ta_int32 destination_y = (ta_int32)text->y - (ta_int32)text->border_width; + ta_int32 column_start = destination_x < 0 ? -destination_x : 0; + ta_int32 row_start = destination_y < 0 ? -destination_y : 0; + ta_int32 column_end = (ta_int32)source->width; + ta_int32 row_end = (ta_int32)source->height; - if (column_start < 0) { - column_start = 0; + if (column_end > (ta_int32)ta_display->width - destination_x) { + column_end = (ta_int32)ta_display->width - destination_x; } - if (row_start < 0) { - row_start = 0; + if (row_end > (ta_int32)ta_display->height - destination_y) { + row_end = (ta_int32)ta_display->height - destination_y; } - if (column_end > entry->width) { - column_end = entry->width; + if (column_start >= column_end || row_start >= row_end) { + return; } - if (row_end > entry->height) { - row_end = entry->height; - } + const ta_uint32 *source_pixels = (const ta_uint32 *)source->pixels; - 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]); - } + if (ta_display->sprite_mode) { + if (!ta_display_sprites) { + return; } - } -} -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); + ta_uint32 visible_width = (ta_uint32)(column_end - column_start); + ta_uint32 visible_height = (ta_uint32)(row_end - row_start); + ta_display_sprite *copy = (ta_display_sprite *)ta_malloc(sizeof(*copy) + (ta_size)visible_width * visible_height * sizeof(ta_uint32)); - if (entry == 0) { - draw_text_direct(text, x, y, ctx); + if (!copy) { + return; + } - return; - } + copy->x = (ta_uint16)(destination_x + column_start); + copy->y = (ta_uint16)(destination_y + row_start); + copy->width = (ta_uint16)visible_width; + copy->height = (ta_uint16)visible_height; + copy->format = TA_DISPLAY_PIXEL_FORMAT_32; + copy->_pad = 0; - if (ctx->border_thickness != 0) { - ta_text_ctx border_ctx = *ctx; + ta_uint32 *copy_pixels = (ta_uint32 *)copy->pixels; - 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; - } + for (ta_uint32 row = 0; row < visible_height; row++) { + const ta_uint32 *line = source_pixels + (ta_size)(row_start + (ta_int32)row) * source->width + column_start; - composite_mask(entry, &border_ctx, x + offset_x, y + offset_y); + for (ta_uint32 column = 0; column < visible_width; column++) { + copy_pixels[(ta_size)row * visible_width + column] = line[column]; } } - } - - 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; - } - ta_int32 frame_width = fb_limit(ta_fb.width); - ta_int32 frame_height = fb_limit(ta_fb.height); + if (!ta_display_sprite_add(copy)) { + ta_free(copy); + } - if (x >= (ta_uint32)frame_width || y >= (ta_uint32)frame_height) { return; } - ta_uint32 text_width; - ta_uint32 lines; - - measure_units(text, &text_width, &lines); - - if (text_width == 0) { + if (ta_display->buffers[1] == 0 || (ta_display->bpp != 32 && ta_display->bpp != 8)) { return; } - ta_uint64 scale_by_height = unsigned_divide64((ta_uint64)height << 16, lines * line_height_units()); - ta_uint64 scale_by_width = unsigned_divide64((ta_uint64)width << 16, text_width); - ta_uint64 scale_cap = unsigned_divide64((ta_uint64)MAX_SIZE << 16, font.units_per_em); - ta_uint64 scale = scale_by_height < scale_by_width ? scale_by_height : scale_by_width; - - if (scale > scale_cap) { - scale = scale_cap; - } + for (ta_int32 row = row_start; row < row_end; row++) { + for (ta_int32 column = column_start; column < column_end; column++) { + ta_uint32 pixel = source_pixels[(ta_size)row * source->width + column]; + ta_uint32 alpha = pixel & 255; - if (scale == 0) { - return; + if (alpha != 0) { + blit_pixel((ta_size)(destination_y + row) * ta_display->width + (ta_size)(destination_x + column), (pixel >> 24) & 255, (pixel >> 16) & 255, (pixel >> 8) & 255, alpha); + } + } } - - ta_text_ctx ctx; - - ctx.scale = (ta_int32)scale; - ctx.clip_x0 = (ta_int32)x; - ctx.clip_y0 = (ta_int32)y; - ctx.clip_x1 = width > (ta_uint32)frame_width - x ? frame_width : (ta_int32)(x + width); - ctx.clip_y1 = height > (ta_uint32)frame_height - y ? frame_height : (ta_int32)(y + height); - ctx.red = red; - ctx.green = green; - ctx.blue = blue; - ctx.border_thickness = border_thickness; - 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); -} - -void ta_text_draw(const char *text, ta_uint32 x, ta_uint32 y, ta_uint32 width, ta_uint32 height, ta_uint8 red, ta_uint8 green, ta_uint8 blue) { - draw_text_fitted(text, x, y, width, height, red, green, blue, 0, 0, 0, 0); -} - -void ta_text_draw_outline(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) { - draw_text_fitted(text, x, y, width, height, red, green, blue, border_thickness, border_red, border_green, border_blue); } -void ta_text_draw_size(const char *text, ta_uint32 x, ta_uint32 y, ta_uint32 size, ta_uint8 red, ta_uint8 green, ta_uint8 blue) { - if (!text || size == 0 || !fb_usable() || !font_ready()) { +void ta_text_destroy(ta_text *text) { + if (!text) { return; } - if (size > MAX_SIZE) { - size = MAX_SIZE; - } - - ta_int32 frame_width = fb_limit(ta_fb.width); - ta_int32 frame_height = fb_limit(ta_fb.height); - - if (x >= (ta_uint32)frame_width || y >= (ta_uint32)frame_height) { - return; - } - - ta_text_ctx ctx; - - ctx.scale = (ta_int32)unsigned_divide64((ta_uint64)size << 16, font.units_per_em); - ctx.clip_x0 = (ta_int32)x; - ctx.clip_y0 = (ta_int32)y; - ctx.clip_x1 = frame_width; - ctx.clip_y1 = frame_height; - ctx.red = red; - ctx.green = green; - ctx.blue = blue; - ctx.border_thickness = 0; - 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); -} - -void ta_text_measure(const char *text, ta_uint32 size, ta_uint32 *width, ta_uint32 *height) { - ta_uint32 measured_width = 0; - ta_uint32 measured_height = 0; - - if (text && font_ready()) { - if (size > MAX_SIZE) { - size = MAX_SIZE; - } - - ta_uint32 units; - ta_uint32 lines; - ta_uint64 scale = unsigned_divide64((ta_uint64)size << 16, font.units_per_em); - - measure_units(text, &units, &lines); - - measured_width = (ta_uint32)(((ta_uint64)units * scale + (ONE - 1)) >> 16); - measured_height = (ta_uint32)(((ta_uint64)lines * line_height_units() * scale + (ONE - 1)) >> 16); - } - - if (width) { - *width = measured_width; - } - - if (height) { - *height = measured_height; - } + ta_free(text->sprite); + text->sprite = 0; + text->width = 0; + text->height = 0; }