Add hw acceleration, improve video performance
This commit is contained in:
parent
0a250b05f3
commit
7c5d82e124
13 changed files with 273 additions and 72 deletions
220
src/ui.c
220
src/ui.c
|
|
@ -12,6 +12,32 @@ static const char *FONT_CANDIDATES[] = {
|
|||
};
|
||||
|
||||
static void fill_rect(SDL_Renderer *renderer, const SDL_Rect *rect, SDL_Color color);
|
||||
static SDL_Texture *text_to_texture(SDL_Renderer *renderer, TTF_Font *font, const char *text, SDL_Color color, int *width, int *height);
|
||||
|
||||
static void text_texture_destroy(UiTextTexture *text_texture) {
|
||||
if (!text_texture) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (text_texture->texture) {
|
||||
SDL_DestroyTexture(text_texture->texture);
|
||||
}
|
||||
memset(text_texture, 0, sizeof(*text_texture));
|
||||
}
|
||||
|
||||
static int text_texture_init(UiTextTexture *text_texture,
|
||||
SDL_Renderer *renderer,
|
||||
TTF_Font *font,
|
||||
const char *text,
|
||||
SDL_Color color) {
|
||||
if (!text_texture) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(text_texture, 0, sizeof(*text_texture));
|
||||
text_texture->texture = text_to_texture(renderer, font, text, color, &text_texture->width, &text_texture->height);
|
||||
return text_texture->texture ? 0 : -1;
|
||||
}
|
||||
|
||||
static void set_draw_color(SDL_Renderer *renderer, SDL_Color color) {
|
||||
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
|
||||
|
|
@ -41,23 +67,18 @@ static SDL_Texture *text_to_texture(SDL_Renderer *renderer, TTF_Font *font, cons
|
|||
return texture;
|
||||
}
|
||||
|
||||
static void draw_text(SDL_Renderer *renderer, TTF_Font *font, const char *text, int x, int y, SDL_Color color) {
|
||||
SDL_Texture *texture;
|
||||
static void draw_cached_text(SDL_Renderer *renderer, const UiTextTexture *text_texture, int x, int y) {
|
||||
SDL_Rect dst;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
texture = text_to_texture(renderer, font, text, color, &width, &height);
|
||||
if (!texture) {
|
||||
if (!text_texture || !text_texture->texture) {
|
||||
return;
|
||||
}
|
||||
|
||||
dst.x = x;
|
||||
dst.y = y;
|
||||
dst.w = width;
|
||||
dst.h = height;
|
||||
SDL_RenderCopy(renderer, texture, NULL, &dst);
|
||||
SDL_DestroyTexture(texture);
|
||||
dst.w = text_texture->width;
|
||||
dst.h = text_texture->height;
|
||||
SDL_RenderCopy(renderer, text_texture->texture, NULL, &dst);
|
||||
}
|
||||
|
||||
static void fill_rect(SDL_Renderer *renderer, const SDL_Rect *rect, SDL_Color color) {
|
||||
|
|
@ -106,22 +127,20 @@ static void format_clock_label(char *buffer, size_t buffer_size, time_t now, int
|
|||
strftime(buffer, buffer_size, "%I:%M %p", &local_time);
|
||||
}
|
||||
|
||||
static void draw_timeline_header(SDL_Renderer *renderer, const UiFonts *fonts, SDL_Rect rect, time_t now) {
|
||||
char label[32];
|
||||
static void draw_timeline_header_cached(SDL_Renderer *renderer, const UiCache *cache, SDL_Rect rect) {
|
||||
int segments = 4;
|
||||
|
||||
draw_text(renderer, fonts->medium, "FRI 6/30", rect.x + 12, rect.y + 8, COLOR_TEXT_DARK);
|
||||
draw_cached_text(renderer, &cache->timeline_day, rect.x + 12, rect.y + 8);
|
||||
|
||||
for (int i = 0; i < segments; ++i) {
|
||||
int x = rect.x + (rect.w * i) / segments;
|
||||
format_clock_label(label, sizeof(label), now, (int) ((TIMELINE_VISIBLE_SECONDS / 60.0 / segments) * i));
|
||||
draw_text(renderer, fonts->small, label, x + 6, rect.y + 10, COLOR_TEXT_DARK);
|
||||
draw_cached_text(renderer, &cache->timeline_labels[i], x + 6, rect.y + 10);
|
||||
set_draw_color(renderer, COLOR_SLATE);
|
||||
SDL_RenderDrawLine(renderer, x, rect.y + rect.h - 4, x, rect.y + rect.h + 5 * 76);
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_footer_legend(SDL_Renderer *renderer, const UiFonts *fonts, int window_width, int window_height) {
|
||||
static void draw_footer_legend(SDL_Renderer *renderer, const UiCache *cache, int window_width, int window_height) {
|
||||
SDL_Rect footer = {0, window_height - 54, window_width, 54};
|
||||
SDL_Rect chip = {window_width / 2 - 170, window_height - 40, 24, 24};
|
||||
|
||||
|
|
@ -130,26 +149,26 @@ static void draw_footer_legend(SDL_Renderer *renderer, const UiFonts *fonts, int
|
|||
SDL_RenderDrawLine(renderer, footer.x, footer.y, footer.x + footer.w, footer.y);
|
||||
|
||||
fill_rect(renderer, &chip, COLOR_HIGHLIGHT_YELLOW);
|
||||
draw_text(renderer, fonts->medium, "A", chip.x + 7, chip.y + 2, COLOR_TEXT_DARK);
|
||||
draw_text(renderer, fonts->medium, "Time", chip.x + 34, chip.y + 1, COLOR_TEXT_DARK);
|
||||
draw_cached_text(renderer, &cache->footer_a, chip.x + 7, chip.y + 2);
|
||||
draw_cached_text(renderer, &cache->footer_time, chip.x + 34, chip.y + 1);
|
||||
|
||||
chip.x += 132;
|
||||
fill_rect(renderer, &chip, COLOR_DEEP_BLUE);
|
||||
draw_text(renderer, fonts->medium, "B", chip.x + 7, chip.y + 2, COLOR_TEXT_LIGHT);
|
||||
draw_text(renderer, fonts->medium, "Theme", chip.x + 34, chip.y + 1, COLOR_TEXT_DARK);
|
||||
draw_cached_text(renderer, &cache->footer_b, chip.x + 7, chip.y + 2);
|
||||
draw_cached_text(renderer, &cache->footer_theme, chip.x + 34, chip.y + 1);
|
||||
|
||||
chip.x += 136;
|
||||
fill_rect(renderer, &chip, COLOR_HINT_RED);
|
||||
draw_text(renderer, fonts->medium, "C", chip.x + 7, chip.y + 2, COLOR_TEXT_LIGHT);
|
||||
draw_text(renderer, fonts->medium, "Title", chip.x + 34, chip.y + 1, COLOR_TEXT_DARK);
|
||||
draw_cached_text(renderer, &cache->footer_c, chip.x + 7, chip.y + 2);
|
||||
draw_cached_text(renderer, &cache->footer_title, chip.x + 34, chip.y + 1);
|
||||
}
|
||||
|
||||
void ui_render_no_media(SDL_Renderer *renderer, const UiFonts *fonts) {
|
||||
void ui_render_no_media(SDL_Renderer *renderer, const UiCache *cache) {
|
||||
SDL_Rect full = {0, 0, WINDOW_WIDTH, WINDOW_HEIGHT};
|
||||
fill_rect(renderer, &full, COLOR_NAVY_DARK);
|
||||
draw_text(renderer, fonts->large, "Passport-C Media Player", 58, 80, COLOR_TEXT_LIGHT);
|
||||
draw_text(renderer, fonts->medium, "No channels found in ./media", 58, 136, COLOR_HIGHLIGHT_YELLOW);
|
||||
draw_text(renderer, fonts->small, "Add MP4 or MKV files to ./media and relaunch.", 58, 176, COLOR_PALE_BLUE);
|
||||
draw_cached_text(renderer, &cache->no_media_title, 58, 80);
|
||||
draw_cached_text(renderer, &cache->no_media_body, 58, 136);
|
||||
draw_cached_text(renderer, &cache->no_media_hint, 58, 176);
|
||||
}
|
||||
|
||||
void ui_render_fullscreen(SDL_Renderer *renderer, SDL_Texture *video_texture, int texture_width, int texture_height) {
|
||||
|
|
@ -163,10 +182,12 @@ void ui_render_guide(SDL_Renderer *renderer,
|
|||
int texture_width,
|
||||
int texture_height,
|
||||
const UiFonts *fonts,
|
||||
UiCache *cache,
|
||||
const ChannelList *channels,
|
||||
int active_channel,
|
||||
time_t app_start_time,
|
||||
time_t now) {
|
||||
Uint64 app_start_ticks,
|
||||
Uint64 now_ticks,
|
||||
time_t now_wall) {
|
||||
SDL_Rect full = {0, 0, WINDOW_WIDTH, WINDOW_HEIGHT};
|
||||
SDL_Rect header_card = {0, 0, 390, 168};
|
||||
SDL_Rect preview = {820, 0, 460, 210};
|
||||
|
|
@ -189,13 +210,22 @@ void ui_render_guide(SDL_Renderer *renderer,
|
|||
const Channel *current = &channels->items[active_channel];
|
||||
snprintf(detail, sizeof(detail), "%.0f min - %s", current->duration_seconds / 60.0, current->file_name);
|
||||
fill_rect(renderer, &(SDL_Rect){0, 0, 390, 38}, COLOR_HIGHLIGHT_YELLOW);
|
||||
draw_text(renderer, fonts->large, current->name, 26, 44, COLOR_TEXT_DARK);
|
||||
draw_text(renderer, fonts->medium, current->program_title, 20, 92, COLOR_TEXT_DARK);
|
||||
draw_text(renderer, fonts->small, detail, 20, 124, COLOR_TEXT_DARK);
|
||||
draw_text(renderer, fonts->medium, "Nick * 56", preview.x + preview.w - 126, preview.y + preview.h - 34, COLOR_TEXT_LIGHT);
|
||||
draw_cached_text(renderer, &cache->channels[active_channel].name_medium, 26, 52);
|
||||
draw_cached_text(renderer, &cache->channels[active_channel].program_medium_dark, 20, 92);
|
||||
draw_cached_text(renderer, &cache->channels[active_channel].detail_small_dark, 20, 124);
|
||||
draw_cached_text(renderer, &cache->preview_badge, preview.x + preview.w - 126, preview.y + preview.h - 34);
|
||||
}
|
||||
|
||||
draw_timeline_header(renderer, fonts, header_row, now);
|
||||
if (cache->timeline_label_slot != now_wall / 60) {
|
||||
char label[32];
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
format_clock_label(label, sizeof(label), now_wall, (int) ((TIMELINE_VISIBLE_SECONDS / 60.0 / 4) * i));
|
||||
text_texture_destroy(&cache->timeline_labels[i]);
|
||||
text_texture_init(&cache->timeline_labels[i], renderer, fonts->small, label, COLOR_TEXT_DARK);
|
||||
}
|
||||
cache->timeline_label_slot = now_wall / 60;
|
||||
}
|
||||
draw_timeline_header_cached(renderer, cache, header_row);
|
||||
|
||||
if (start_index < 0) {
|
||||
start_index = 0;
|
||||
|
|
@ -223,34 +253,132 @@ void ui_render_guide(SDL_Renderer *renderer,
|
|||
|
||||
{
|
||||
const Channel *channel = &channels->items[channel_index];
|
||||
char number[16];
|
||||
double live_position = channel_live_position(channel, app_start_time, now);
|
||||
double live_position = channel_live_position_precise(channel, app_start_ticks, now_ticks);
|
||||
double pixels_per_second = timeline_w / TIMELINE_VISIBLE_SECONDS;
|
||||
int block_x = timeline_x - (int) (live_position * pixels_per_second);
|
||||
int block_w = (int) (channel->duration_seconds * pixels_per_second);
|
||||
SDL_Rect block = {block_x, timeline_rect.y + 4, SDL_max(block_w, 48), timeline_rect.h - 8};
|
||||
|
||||
snprintf(number, sizeof(number), "%d", channel->number);
|
||||
draw_text(renderer, fonts->medium, channel->name, 20, row_rect.y + 12, COLOR_TEXT_LIGHT);
|
||||
draw_text(renderer, fonts->medium, number, 176, row_rect.y + 12, COLOR_TEXT_LIGHT);
|
||||
draw_text(renderer, fonts->small, channel->file_name, 20, row_rect.y + 38, COLOR_PALE_BLUE);
|
||||
draw_cached_text(renderer, &cache->channels[channel_index].name_medium, 20, row_rect.y + 12);
|
||||
draw_cached_text(renderer, &cache->channels[channel_index].number_medium, 176, row_rect.y + 12);
|
||||
draw_cached_text(renderer, &cache->channels[channel_index].file_small, 20, row_rect.y + 38);
|
||||
|
||||
fill_rect(renderer, &timeline_rect, COLOR_NAVY_DARK);
|
||||
SDL_RenderSetClipRect(renderer, &clip);
|
||||
fill_rect(renderer, &block, is_selected ? COLOR_HIGHLIGHT_YELLOW : COLOR_DEEP_BLUE);
|
||||
stroke_rect(renderer, &block, COLOR_PALE_BLUE);
|
||||
draw_text(renderer,
|
||||
is_selected ? fonts->medium : fonts->small,
|
||||
channel->program_title,
|
||||
block.x + 10,
|
||||
block.y + 10,
|
||||
is_selected ? COLOR_TEXT_DARK : COLOR_TEXT_LIGHT);
|
||||
draw_cached_text(renderer,
|
||||
is_selected ? &cache->channels[channel_index].program_medium_dark
|
||||
: &cache->channels[channel_index].program_small_light,
|
||||
block.x + 10,
|
||||
block.y + 10);
|
||||
SDL_RenderSetClipRect(renderer, NULL);
|
||||
stroke_rect(renderer, &timeline_rect, COLOR_SLATE);
|
||||
}
|
||||
}
|
||||
|
||||
draw_footer_legend(renderer, fonts, WINDOW_WIDTH, WINDOW_HEIGHT);
|
||||
draw_footer_legend(renderer, cache, WINDOW_WIDTH, WINDOW_HEIGHT);
|
||||
}
|
||||
|
||||
int ui_cache_init(UiCache *cache, SDL_Renderer *renderer, const UiFonts *fonts, const ChannelList *channels) {
|
||||
char buffer[256];
|
||||
|
||||
if (!cache || !renderer || !fonts) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(cache, 0, sizeof(*cache));
|
||||
cache->renderer = renderer;
|
||||
cache->timeline_label_slot = -1;
|
||||
cache->timeline_labels = calloc(4, sizeof(UiTextTexture));
|
||||
if (!cache->timeline_labels) {
|
||||
ui_cache_destroy(cache);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (text_texture_init(&cache->no_media_title, renderer, fonts->large, "Passport-C Media Player", COLOR_TEXT_LIGHT) != 0 ||
|
||||
text_texture_init(&cache->no_media_body, renderer, fonts->medium, "No channels found in ./media", COLOR_HIGHLIGHT_YELLOW) != 0 ||
|
||||
text_texture_init(&cache->no_media_hint, renderer, fonts->small, "Add MP4 or MKV files to ./media and relaunch.", COLOR_PALE_BLUE) != 0 ||
|
||||
text_texture_init(&cache->timeline_day, renderer, fonts->medium, "FRI 6/30", COLOR_TEXT_DARK) != 0 ||
|
||||
text_texture_init(&cache->footer_a, renderer, fonts->medium, "A", COLOR_TEXT_DARK) != 0 ||
|
||||
text_texture_init(&cache->footer_time, renderer, fonts->medium, "Time", COLOR_TEXT_DARK) != 0 ||
|
||||
text_texture_init(&cache->footer_b, renderer, fonts->medium, "B", COLOR_TEXT_LIGHT) != 0 ||
|
||||
text_texture_init(&cache->footer_theme, renderer, fonts->medium, "Theme", COLOR_TEXT_DARK) != 0 ||
|
||||
text_texture_init(&cache->footer_c, renderer, fonts->medium, "C", COLOR_TEXT_LIGHT) != 0 ||
|
||||
text_texture_init(&cache->footer_title, renderer, fonts->medium, "Title", COLOR_TEXT_DARK) != 0 ||
|
||||
text_texture_init(&cache->preview_badge, renderer, fonts->medium, "Nick * 56", COLOR_TEXT_LIGHT) != 0) {
|
||||
ui_cache_destroy(cache);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (channels && channels->count > 0) {
|
||||
cache->channels = calloc((size_t) channels->count, sizeof(UiChannelCache));
|
||||
if (!cache->channels) {
|
||||
ui_cache_destroy(cache);
|
||||
return -1;
|
||||
}
|
||||
cache->channel_count = channels->count;
|
||||
|
||||
for (int i = 0; i < channels->count; ++i) {
|
||||
const Channel *channel = &channels->items[i];
|
||||
snprintf(buffer, sizeof(buffer), "%d", channel->number);
|
||||
if (text_texture_init(&cache->channels[i].name_medium, renderer, fonts->medium, channel->name, COLOR_TEXT_LIGHT) != 0 ||
|
||||
text_texture_init(&cache->channels[i].number_medium, renderer, fonts->medium, buffer, COLOR_TEXT_LIGHT) != 0 ||
|
||||
text_texture_init(&cache->channels[i].file_small, renderer, fonts->small, channel->file_name, COLOR_PALE_BLUE) != 0 ||
|
||||
text_texture_init(&cache->channels[i].program_small_light, renderer, fonts->small, channel->program_title, COLOR_TEXT_LIGHT) != 0 ||
|
||||
text_texture_init(&cache->channels[i].program_medium_dark, renderer, fonts->medium, channel->program_title, COLOR_TEXT_DARK) != 0) {
|
||||
ui_cache_destroy(cache);
|
||||
return -1;
|
||||
}
|
||||
|
||||
snprintf(buffer, sizeof(buffer), "%.0f min - %s", channel->duration_seconds / 60.0, channel->file_name);
|
||||
if (text_texture_init(&cache->channels[i].detail_small_dark, renderer, fonts->small, buffer, COLOR_TEXT_DARK) != 0) {
|
||||
ui_cache_destroy(cache);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ui_cache_destroy(UiCache *cache) {
|
||||
if (!cache) {
|
||||
return;
|
||||
}
|
||||
|
||||
text_texture_destroy(&cache->no_media_title);
|
||||
text_texture_destroy(&cache->no_media_body);
|
||||
text_texture_destroy(&cache->no_media_hint);
|
||||
text_texture_destroy(&cache->timeline_day);
|
||||
text_texture_destroy(&cache->footer_a);
|
||||
text_texture_destroy(&cache->footer_time);
|
||||
text_texture_destroy(&cache->footer_b);
|
||||
text_texture_destroy(&cache->footer_theme);
|
||||
text_texture_destroy(&cache->footer_c);
|
||||
text_texture_destroy(&cache->footer_title);
|
||||
text_texture_destroy(&cache->preview_badge);
|
||||
|
||||
if (cache->timeline_labels) {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
text_texture_destroy(&cache->timeline_labels[i]);
|
||||
}
|
||||
free(cache->timeline_labels);
|
||||
}
|
||||
|
||||
if (cache->channels) {
|
||||
for (int i = 0; i < cache->channel_count; ++i) {
|
||||
text_texture_destroy(&cache->channels[i].name_medium);
|
||||
text_texture_destroy(&cache->channels[i].number_medium);
|
||||
text_texture_destroy(&cache->channels[i].file_small);
|
||||
text_texture_destroy(&cache->channels[i].program_small_light);
|
||||
text_texture_destroy(&cache->channels[i].program_medium_dark);
|
||||
text_texture_destroy(&cache->channels[i].detail_small_dark);
|
||||
}
|
||||
free(cache->channels);
|
||||
}
|
||||
|
||||
memset(cache, 0, sizeof(*cache));
|
||||
}
|
||||
|
||||
int ui_load_fonts(UiFonts *fonts) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue