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