From 5ed83e7d0f6a9618ffcd5cdab0b69f4a01a654b9 Mon Sep 17 00:00:00 2001 From: Raphael Texier Date: Thu, 6 Feb 2025 14:00:27 +0100 Subject: [PATCH 1/7] add basic OSD --- components/retro-go/rg_display.c | 123 ++++++++++++++----- components/retro-go/rg_display.h | 8 +- components/retro-go/rg_gui.c | 197 ++++++++++++++++++++----------- components/retro-go/rg_gui.h | 11 +- components/retro-go/rg_system.c | 28 ++++- launcher/main/gui.c | 20 ++-- retro-core/main/main_sms.c | 2 +- 7 files changed, 274 insertions(+), 115 deletions(-) diff --git a/components/retro-go/rg_display.c b/components/retro-go/rg_display.c index 117f3e3ae..802fcabc4 100644 --- a/components/retro-go/rg_display.c +++ b/components/retro-go/rg_display.c @@ -10,13 +10,17 @@ static rg_task_t *display_task_queue; static rg_display_counters_t counters; static rg_display_config_t config; -static rg_surface_t *osd; static rg_surface_t *border; static rg_display_t display; static int16_t map_viewport_to_source_x[RG_SCREEN_WIDTH + 1]; static int16_t map_viewport_to_source_y[RG_SCREEN_HEIGHT + 1]; static uint32_t screen_line_checksum[RG_SCREEN_HEIGHT + 1]; +// OSD Surface Management +static rg_surface_t *osd_surface = NULL; +static rg_rect_t osd_rect; // Store the OSD's position and size +static bool osd_enabled = false; + #define LINE_IS_REPEATED(Y) (map_viewport_to_source_y[(Y)] == map_viewport_to_source_y[(Y) - 1]) // This is to avoid flooring a number that is approximated to .9999999 and be explicit about it #define FLOAT_TO_INT(x) ((int)((x) + 0.1f)) @@ -36,6 +40,48 @@ static const char *SETTING_CUSTOM_ZOOM = "DispCustomZoom"; #include "drivers/display/dummy.h" #endif +void rg_display_set_osd_surface(rg_surface_t *surface, rg_rect_t rect) +{ + // Free the old surface if it exists + if (osd_surface != NULL) { + rg_surface_free(osd_surface); + } + + osd_surface = surface; + osd_rect = rect; // Store the position and size + if (surface != NULL) + { // Only clear if a surface is provided + uint16_t *buffer = surface->data; // Treat the buffer as 16-bit values + for (int i = 0; i < surface->width * surface->height; i++) + buffer[i] = C_TRANSPARENT; // Assign the black color directly // C_TRANSPARENT + } + + osd_enabled = (surface != NULL); // Enable OSD if surface is valid +} + +rg_surface_t* rg_display_get_osd_surface() +{ + return osd_surface; +} + +void rg_display_set_osd_enabled(bool enabled) +{ + osd_enabled = enabled; + rg_display_force_redraw(); +} + +bool rg_display_is_osd_enabled() +{ + return osd_enabled; +} + +void deinit_osd() +{ + rg_surface_free(osd_surface); // Free the OSD surface + osd_surface = NULL; + osd_enabled = false; +} + static inline unsigned blend_pixels(unsigned a, unsigned b) { // Fast path (taken 80-90% of the time) @@ -205,13 +251,46 @@ static inline void write_update(const rg_surface_t *update) lines_remaining -= lines_to_copy; } - if (osd != NULL) + if (osd_enabled && osd_surface != NULL) { // TODO: Draw on screen display. By default it should be bottom left which is fine // for both virtual keyboard and info labels. Maybe make it configurable later... + int *buffer = osd_surface->data; + RG_ASSERT_ARG(buffer); + + // Clipping + int width = RG_MIN(osd_rect.width, display.screen.width - osd_rect.left); + int height = RG_MIN(osd_rect.height, display.screen.height - osd_rect.top); + + // This can happen when left or top is out of bound + if (width < 0 || height < 0) + return; + + lcd_set_window(osd_rect.left + display.screen.margin_left, osd_rect.top + display.screen.margin_top, width, height); + + for (size_t y = 0; y < height;) + { + uint16_t *lcd_buffer = lcd_get_buffer(LCD_BUFFER_LENGTH); + size_t num_lines = RG_MIN(LCD_BUFFER_LENGTH / width, height - y); + + // Copy line by line because stride may not match width + for (size_t line = 0; line < num_lines; ++line) + { + uint16_t *src = (void *)buffer + ((y + line) * osd_rect.width * 2); + uint16_t *dst = lcd_buffer + (line * width); + for (size_t i = 0; i < width; ++i){ + if(src[i] != C_TRANSPARENT) // only overwrite pixels that aren't transparent + dst[i] = (src[i] >> 8) | (src[i] << 8); + } + } + + lcd_send_buffer(lcd_buffer, width * num_lines); + y += num_lines; + } + lcd_sync(); } - if (lines_updated > draw_height * 0.80f) + if (lines_updated > display.screen.height * 0.80f) counters.fullFrames++; else counters.partFrames++; @@ -220,8 +299,6 @@ static inline void write_update(const rg_surface_t *update) static void update_viewport_scaling(void) { - int screen_width = display.screen.width; - int screen_height = display.screen.height; int src_width = display.source.width; int src_height = display.source.height; int new_width = src_width; @@ -229,16 +306,16 @@ static void update_viewport_scaling(void) if (config.scaling == RG_DISPLAY_SCALING_FULL) { - new_width = screen_width; - new_height = screen_height; + new_width = display.screen.width; + new_height = display.screen.height; } else if (config.scaling == RG_DISPLAY_SCALING_FIT) { - new_width = FLOAT_TO_INT(screen_height * ((float)src_width / src_height)); - new_height = screen_height; - if (new_width > screen_width) { - new_width = screen_width; - new_height = FLOAT_TO_INT(screen_width * ((float)src_height / src_width)); + new_width = FLOAT_TO_INT(display.screen.height * ((float)src_width / src_height)); + new_height = display.screen.height; + if (new_width > display.screen.width) { + new_width = display.screen.width; + new_height = FLOAT_TO_INT(display.screen.width * ((float)src_height / src_width)); } } else if (config.scaling == RG_DISPLAY_SCALING_ZOOM) @@ -251,8 +328,8 @@ static void update_viewport_scaling(void) new_width &= ~1; new_height &= ~1; - display.viewport.left = (screen_width - new_width) / 2; - display.viewport.top = (screen_height - new_height) / 2; + display.viewport.left = (display.screen.width - new_width) / 2; + display.viewport.top = (display.screen.height - new_height) / 2; display.viewport.width = new_width; display.viewport.height = new_height; @@ -266,9 +343,9 @@ static void update_viewport_scaling(void) memset(screen_line_checksum, 0, sizeof(screen_line_checksum)); - for (int x = 0; x < screen_width; ++x) + for (int x = 0; x < display.screen.width; ++x) map_viewport_to_source_x[x] = FLOAT_TO_INT(x * display.viewport.step_x); - for (int y = 0; y < screen_height; ++y) + for (int y = 0; y < display.screen.height; ++y) map_viewport_to_source_y[y] = FLOAT_TO_INT(y * display.viewport.step_y); RG_LOGI("%dx%d@%.3f => %dx%d@%.3f left:%d top:%d step_x:%.2f step_y:%.2f", src_width, src_height, @@ -285,9 +362,9 @@ static bool load_border_file(const char *filename) if (filename && (border = rg_surface_load_image_file(filename, 0))) { - if (border->width != rg_display_get_width() || border->height != rg_display_get_height()) + if (border->width != display.screen.width || border->height != display.screen.height) { - rg_surface_t *resized = rg_surface_resize(border, rg_display_get_width(), rg_display_get_height()); + rg_surface_t *resized = rg_surface_resize(border, display.screen.width, display.screen.height); if (resized) { rg_surface_free(border); @@ -350,16 +427,6 @@ rg_display_counters_t rg_display_get_counters(void) return counters; } -int rg_display_get_width(void) -{ - return display.screen.width; -} - -int rg_display_get_height(void) -{ - return display.screen.height; -} - void rg_display_set_scaling(display_scaling_t scaling) { config.scaling = RG_MIN(RG_MAX(0, scaling), RG_DISPLAY_SCALING_COUNT - 1); diff --git a/components/retro-go/rg_display.h b/components/retro-go/rg_display.h index adbbfb1ca..70a74cbaa 100644 --- a/components/retro-go/rg_display.h +++ b/components/retro-go/rg_display.h @@ -99,6 +99,12 @@ typedef struct #include "rg_surface.h" +void rg_display_set_osd_surface(rg_surface_t *surface, rg_rect_t rect); +rg_surface_t* rg_display_get_osd_surface(); +void rg_display_set_osd_enabled(bool enabled); +bool rg_display_is_osd_enabled(); +void deinit_osd(); + void rg_display_init(void); void rg_display_deinit(void); void rg_display_write_rect(int left, int top, int width, int height, int stride, const uint16_t *buffer, uint32_t flags); @@ -111,8 +117,6 @@ void rg_display_submit(const rg_surface_t *update, uint32_t flags); rg_display_counters_t rg_display_get_counters(void); const rg_display_t *rg_display_get_info(void); -int rg_display_get_width(void); -int rg_display_get_height(void); void rg_display_set_scaling(display_scaling_t scaling); display_scaling_t rg_display_get_scaling(void); diff --git a/components/retro-go/rg_gui.c b/components/retro-go/rg_gui.c index 2bf8ec85b..094329497 100644 --- a/components/retro-go/rg_gui.c +++ b/components/retro-go/rg_gui.c @@ -16,7 +16,6 @@ static struct uint16_t *screen_buffer, *draw_buffer; size_t draw_buffer_size; int screen_width, screen_height; - int screen_safezone; // rg_rect_t struct { const rg_font_t *font; @@ -103,13 +102,8 @@ static int get_vertical_position(int y_pos, int height) void rg_gui_init(void) { - gui.screen_width = rg_display_get_width(); - gui.screen_height = rg_display_get_height(); - #ifdef RG_SCREEN_HAS_ROUND_CORNERS - gui.screen_safezone = 20; - #else - gui.screen_safezone = 0; - #endif + gui.screen_width = rg_display_get_info()->screen.width; + gui.screen_height = rg_display_get_info()->screen.height; gui.draw_buffer = get_draw_buffer(gui.screen_width, 18, C_BLACK); rg_gui_set_language_id(rg_settings_get_number(NS_GLOBAL, SETTING_LANGUAGE, RG_LANG_EN)); rg_gui_set_font(rg_settings_get_number(NS_GLOBAL, SETTING_FONTTYPE, RG_FONT_VERA_12)); @@ -244,22 +238,39 @@ void rg_gui_set_surface(rg_surface_t *surface) gui.screen_buffer = surface ? surface->data : NULL; } -void rg_gui_copy_buffer(int left, int top, int width, int height, int stride, const void *buffer) +void rg_gui_copy_buffer(rg_surface_t *dest_surface, int left, int top, int width, int height, int stride, const void *buffer) { left = get_horizontal_position(left, width); top = get_vertical_position(top, height); - if (gui.screen_buffer) + uint16_t *target_buffer; + int target_width; + int target_height; + + if (dest_surface) + { + target_buffer = dest_surface->data; + target_width = dest_surface->width; + target_height = dest_surface->height; + } + else + { + target_buffer = gui.screen_buffer; + target_width = gui.screen_width; + target_height = gui.screen_height; + } + + if (target_buffer) { if (stride < width) stride = width * 2; - width = RG_MIN(width, gui.screen_width - left); - height = RG_MIN(height, gui.screen_height - top); + width = RG_MIN(width, target_width - left); + height = RG_MIN(height, target_height - top); for (int y = 0; y < height; ++y) { - uint16_t *dst = gui.screen_buffer + (top + y) * gui.screen_width + left; + uint16_t *dst = target_buffer + (top + y) * target_width + left; const uint16_t *src = (void *)buffer + y * stride; for (int x = 0; x < width; ++x) if (src[x] != C_TRANSPARENT) @@ -364,7 +375,7 @@ static size_t get_glyph(uint32_t *output, const rg_font_t *font, int points, int return glyph_width; } -rg_rect_t rg_gui_draw_text(int x_pos, int y_pos, int width, const char *text, // const rg_font_t *font, +rg_rect_t rg_gui_draw_text(rg_surface_t *dest_surface, int x_pos, int y_pos, int width, const char *text, rg_color_t color_fg, rg_color_t color_bg, uint32_t flags) { int padding = (flags & RG_TEXT_NO_PADDING) ? 0 : 1; @@ -468,7 +479,7 @@ rg_rect_t rg_gui_draw_text(int x_pos, int y_pos, int width, const char *text, // } if (!(flags & RG_TEXT_DUMMY_DRAW)) - rg_gui_copy_buffer(x_pos, y_pos + y_offset, draw_width, line_height, 0, draw_buffer); + rg_gui_copy_buffer(dest_surface, x_pos, y_pos + y_offset, draw_width, line_height, 0, draw_buffer); y_offset += line_height; @@ -479,7 +490,8 @@ rg_rect_t rg_gui_draw_text(int x_pos, int y_pos, int width, const char *text, // return (rg_rect_t){x_pos, y_pos, draw_width, y_offset}; } -void rg_gui_draw_rect(int x_pos, int y_pos, int width, int height, int border_size, + +void rg_gui_draw_rect(rg_surface_t *dest_surface, int x_pos, int y_pos, int width, int height, int border_size, rg_color_t border_color, rg_color_t fill_color) { if (width <= 0 || height <= 0) @@ -492,10 +504,10 @@ void rg_gui_draw_rect(int x_pos, int y_pos, int width, int height, int border_si { uint16_t *draw_buffer = get_draw_buffer(border_size, RG_MAX(width, height), border_color); - rg_gui_copy_buffer(x_pos, y_pos, width, border_size, 0, draw_buffer); // Top - rg_gui_copy_buffer(x_pos, y_pos + height - border_size, width, border_size, 0, draw_buffer); // Bottom - rg_gui_copy_buffer(x_pos, y_pos, border_size, height, 0, draw_buffer); // Left - rg_gui_copy_buffer(x_pos + width - border_size, y_pos, border_size, height, 0, draw_buffer); // Right + rg_gui_copy_buffer(dest_surface, x_pos, y_pos, width, border_size, 0, draw_buffer); // Top + rg_gui_copy_buffer(dest_surface, x_pos, y_pos + height - border_size, width, border_size, 0, draw_buffer); // Bottom + rg_gui_copy_buffer(dest_surface, x_pos, y_pos, border_size, height, 0, draw_buffer); // Left + rg_gui_copy_buffer(dest_surface, x_pos + width - border_size, y_pos, border_size, height, 0, draw_buffer); // Right x_pos += border_size; y_pos += border_size; @@ -507,7 +519,7 @@ void rg_gui_draw_rect(int x_pos, int y_pos, int width, int height, int border_si { uint16_t *draw_buffer = get_draw_buffer(width, RG_MIN(height, 16), fill_color); for (int y = 0; y < height; y += 16) - rg_gui_copy_buffer(x_pos, y_pos + y, width, RG_MIN(height - y, 16), 0, draw_buffer); + rg_gui_copy_buffer(dest_surface, x_pos, y_pos + y, width, RG_MIN(height - y, 16), 0, draw_buffer); } } @@ -516,19 +528,19 @@ void rg_gui_draw_image(int x_pos, int y_pos, int width, int height, bool resampl if (img && resample && (width && height) && (width != img->width || height != img->height)) { rg_image_t *new_img = rg_surface_resize(img, width, height); - rg_gui_copy_buffer(x_pos, y_pos, width, height, new_img->width * 2, new_img->data); + rg_gui_copy_buffer(NULL, x_pos, y_pos, width, height, new_img->width * 2, new_img->data); rg_surface_free(new_img); } else if (img) { int draw_width = width ? RG_MIN(width, img->width) : img->width; int draw_height = height ? RG_MIN(height, img->height) : img->height; - rg_gui_copy_buffer(x_pos, y_pos, draw_width, draw_height, img->width * 2, img->data); + rg_gui_copy_buffer(NULL, x_pos, y_pos, draw_width, draw_height, img->width * 2, img->data); } else // We fill a rect to show something is missing instead of abort... { - rg_gui_draw_rect(x_pos, y_pos, width, height, 2, C_RED, C_BLACK); - // rg_gui_draw_text(x_pos + 2, y_pos + 2, width - 4, "No image", C_DIM_GRAY, C_BLACK, 0); + rg_gui_draw_rect(NULL, x_pos, y_pos, width, height, 2, C_RED, C_BLACK); + // rg_gui_draw_text(NULL, x_pos + 2, y_pos + 2, width - 4, "No image", C_DIM_GRAY, C_BLACK, 0); } } @@ -540,11 +552,15 @@ void rg_gui_draw_icons(void) int bar_height = txt.height; int icon_height = RG_MAX(8, bar_height - 4); int icon_top = RG_MAX(0, (bar_height - icon_height - 1) / 2); - int right = gui.screen_safezone; + int right = 0; if (battery.present) { - right += 22; + #ifdef RG_SCREEN_HAS_ROUND_CORNERS + right += 42; // This is to shift the battery icon a bit on the left + #else + right += 22; // Regular rectangle screen + #endif int width = 16; int height = icon_height; @@ -556,10 +572,10 @@ void rg_gui_draw_icons(void) rg_color_t color_border = C_SILVER; rg_color_t color_empty = C_BLACK; - rg_gui_draw_rect(x_pos, y_pos, width + 2, height, 1, color_border, C_NONE); - rg_gui_draw_rect(x_pos + width + 2, y_pos + 2, 2, height - 4, 1, color_border, C_NONE); - rg_gui_draw_rect(x_pos + 1, y_pos + 1, width_fill, height - 2, 0, 0, color_fill); - rg_gui_draw_rect(x_pos + 1 + width_fill, y_pos + 1, width - width_fill, height - 2, 0, 0, color_empty); + rg_gui_draw_rect(NULL, x_pos, y_pos, width + 2, height, 1, color_border, C_NONE); + rg_gui_draw_rect(NULL, x_pos + width + 2, y_pos + 2, 2, height - 4, 1, color_border, C_NONE); + rg_gui_draw_rect(NULL, x_pos + 1, y_pos + 1, width_fill, height - 2, 0, 0, color_fill); + rg_gui_draw_rect(NULL, x_pos + 1 + width_fill, y_pos + 1, width - width_fill, height - 2, 0, 0, color_empty); } if (network.state > RG_NETWORK_DISCONNECTED) @@ -576,13 +592,13 @@ void rg_gui_draw_icons(void) rg_color_t color_border = (network.state == RG_NETWORK_CONNECTED) ? C_SILVER : C_DIM_GRAY; y_pos += height * 0.6; - rg_gui_draw_rect(x_pos, y_pos, seg_width, height * 0.4, 1, color_border, color_fill); + rg_gui_draw_rect(NULL, x_pos, y_pos, seg_width, height * 0.4, 1, color_border, color_fill); x_pos += seg_width + 2; y_pos -= height * 0.3; - rg_gui_draw_rect(x_pos, y_pos, seg_width, height * 0.7, 1, color_border, color_fill); + rg_gui_draw_rect(NULL, x_pos, y_pos, seg_width, height * 0.7, 1, color_border, color_fill); x_pos += seg_width + 2; y_pos -= height * 0.3; - rg_gui_draw_rect(x_pos, y_pos, seg_width, height * 1.0, 1, color_border, color_fill); + rg_gui_draw_rect(NULL, x_pos, y_pos, seg_width, height * 1.0, 1, color_border, color_fill); } if (gui.show_clock) @@ -596,15 +612,14 @@ void rg_gui_draw_icons(void) struct tm *time = localtime(&time_sec); sprintf(buffer, "%02d:%02d", time->tm_hour, time->tm_min); - rg_gui_draw_text(x_pos, y_pos, 0, buffer, C_SILVER, gui.screen_buffer ? C_TRANSPARENT : C_BLACK, 0); + rg_gui_draw_text(NULL, x_pos, y_pos, 0, buffer, C_SILVER, gui.screen_buffer ? C_TRANSPARENT : C_BLACK, 0); } } void rg_gui_draw_hourglass(void) { - rg_display_write_rect( - get_horizontal_position(RG_GUI_CENTER, image_hourglass.width), - get_vertical_position(RG_GUI_CENTER, image_hourglass.height), + rg_display_write_rect((gui.screen_width / 2) - (image_hourglass.width / 2), + (gui.screen_height / 2) - (image_hourglass.height / 2), image_hourglass.width, image_hourglass.height, image_hourglass.width * 2, @@ -636,9 +651,8 @@ void rg_gui_draw_status_bars(void) else snprintf(footer, max_len, "Retro-Go %s", app->version); - // FIXME: Respect gui.safezone (draw black background full screen_width, but pad the text if needed) - rg_gui_draw_text(0, RG_GUI_TOP, gui.screen_width, header, C_WHITE, C_BLACK, 0); - rg_gui_draw_text(0, RG_GUI_BOTTOM, gui.screen_width, footer, C_WHITE, C_BLACK, 0); + rg_gui_draw_text(NULL, 0, RG_GUI_TOP, gui.screen_width, header, C_WHITE, C_BLACK, 0); + rg_gui_draw_text(NULL, 0, RG_GUI_BOTTOM, gui.screen_width, footer, C_WHITE, C_BLACK, 0); rg_gui_draw_icons(); } @@ -722,8 +736,8 @@ void rg_gui_draw_dialog(const char *title, const rg_gui_option_t *options, int s if (title) { int width = inner_width + row_padding_x * 2; - rg_gui_draw_text(x, y, width, title, gui.style.box_header, gui.style.box_background, RG_TEXT_ALIGN_CENTER); - rg_gui_draw_rect(x, y + font_height, width, 6, 0, 0, gui.style.box_background); + rg_gui_draw_text(NULL, x, y, width, title, gui.style.box_header, gui.style.box_background, RG_TEXT_ALIGN_CENTER); + rg_gui_draw_rect(NULL, x, y + font_height, width, 6, 0, 0, gui.style.box_background); y += font_height + 6; } @@ -778,49 +792,49 @@ void rg_gui_draw_dialog(const char *title, const rg_gui_option_t *options, int s } else if (options[i].value) { - rg_gui_draw_text(xx, yy, col1_width, options[i].label, fg, bg, 0); - rg_gui_draw_text(xx + col1_width, yy, sep_width, ": ", fg, bg, 0); - height = rg_gui_draw_text(xx + col1_width + sep_width, yy, col2_width, options[i].value, fg, bg, RG_TEXT_MULTILINE).height; - rg_gui_draw_rect(xx, yy + font_height, inner_width - col2_width, height - font_height, 0, 0, bg); + rg_gui_draw_text(NULL, xx, yy, col1_width, options[i].label, fg, bg, 0); + rg_gui_draw_text(NULL, xx + col1_width, yy, sep_width, ": ", fg, bg, 0); + height = rg_gui_draw_text(NULL, xx + col1_width + sep_width, yy, col2_width, options[i].value, fg, bg, RG_TEXT_MULTILINE).height; + rg_gui_draw_rect(NULL, xx, yy + font_height, inner_width - col2_width, height - font_height, 0, 0, bg); } else { - height = rg_gui_draw_text(xx, yy, inner_width, options[i].label, fg, bg, RG_TEXT_MULTILINE).height; + height = rg_gui_draw_text(NULL, xx, yy, inner_width, options[i].label, fg, bg, RG_TEXT_MULTILINE).height; } - rg_gui_draw_rect(x, yy, row_padding_x, height, 0, 0, bg); - rg_gui_draw_rect(xx + inner_width, yy, row_padding_x, height, 0, 0, bg); - rg_gui_draw_rect(x, y, inner_width + row_padding_x * 2, row_padding_y, 0, 0, bg); - rg_gui_draw_rect(x, yy + height, inner_width + row_padding_x * 2, row_padding_y, 0, 0, bg); + rg_gui_draw_rect(NULL, x, yy, row_padding_x, height, 0, 0, bg); + rg_gui_draw_rect(NULL, xx + inner_width, yy, row_padding_x, height, 0, 0, bg); + rg_gui_draw_rect(NULL, x, y, inner_width + row_padding_x * 2, row_padding_y, 0, 0, bg); + rg_gui_draw_rect(NULL, x, yy + height, inner_width + row_padding_x * 2, row_padding_y, 0, 0, bg); y += height + row_padding_y * 2; } if (y < (box_y + box_height)) { - rg_gui_draw_rect(box_x, y, box_width, (box_y + box_height) - y, 0, 0, gui.style.box_background); + rg_gui_draw_rect(NULL, box_x, y, box_width, (box_y + box_height) - y, 0, 0, gui.style.box_background); } - rg_gui_draw_rect(box_x, box_y, box_width, box_height, box_padding, gui.style.box_background, C_NONE); - rg_gui_draw_rect(box_x - 1, box_y - 1, box_width + 2, box_height + 2, 1, gui.style.box_border, C_NONE); + rg_gui_draw_rect(NULL, box_x, box_y, box_width, box_height, box_padding, gui.style.box_background, C_NONE); + rg_gui_draw_rect(NULL, box_x - 1, box_y - 1, box_width + 2, box_height + 2, 1, gui.style.box_border, C_NONE); // Basic scroll indicators are overlayed at the end... if (top_i > 0) { int x = box_x + box_width - 10; int y = box_y + box_padding + 2; - rg_gui_draw_rect(x + 0, y - 0, 6, 2, 0, 0, gui.style.scrollbar); - rg_gui_draw_rect(x + 1, y - 2, 4, 2, 0, 0, gui.style.scrollbar); - rg_gui_draw_rect(x + 2, y - 4, 2, 2, 0, 0, gui.style.scrollbar); + rg_gui_draw_rect(NULL, x + 0, y - 0, 6, 2, 0, 0, gui.style.scrollbar); + rg_gui_draw_rect(NULL, x + 1, y - 2, 4, 2, 0, 0, gui.style.scrollbar); + rg_gui_draw_rect(NULL, x + 2, y - 4, 2, 2, 0, 0, gui.style.scrollbar); } if (i < options_count) { int x = box_x + box_width - 10; int y = box_y + box_height - 6; - rg_gui_draw_rect(x + 0, y - 4, 6, 2, 0, 0, gui.style.scrollbar); - rg_gui_draw_rect(x + 1, y - 2, 4, 2, 0, 0, gui.style.scrollbar); - rg_gui_draw_rect(x + 2, y - 0, 2, 2, 0, 0, gui.style.scrollbar); + rg_gui_draw_rect(NULL, x + 0, y - 4, 6, 2, 0, 0, gui.style.scrollbar); + rg_gui_draw_rect(NULL, x + 1, y - 2, 4, 2, 0, 0, gui.style.scrollbar); + rg_gui_draw_rect(NULL, x + 2, y - 0, 2, 2, 0, 0, gui.style.scrollbar); } } @@ -1077,7 +1091,7 @@ void rg_gui_draw_keyboard(const rg_keyboard_map_t *map, size_t cursor) char buf[2] = {0}; - rg_gui_draw_rect(x_pos, y_pos, width, height, 2, gui.style.box_border, gui.style.box_background); + rg_gui_draw_rect(NULL, x_pos, y_pos, width, height, 2, gui.style.box_border, gui.style.box_background); for (size_t i = 0; i < map->columns * map->rows; ++i) { @@ -1086,7 +1100,7 @@ void rg_gui_draw_keyboard(const rg_keyboard_map_t *map, size_t cursor) if (!map->data[i]) continue; buf[0] = map->data[i]; - rg_gui_draw_text(x + 1, y + 1, 14, buf, C_BLACK, i == cursor ? C_CYAN : C_IVORY, RG_TEXT_ALIGN_CENTER); + rg_gui_draw_text(NULL, x + 1, y + 1, 14, buf, C_BLACK, i == cursor ? C_CYAN : C_IVORY, RG_TEXT_ALIGN_CENTER); } } @@ -1788,9 +1802,9 @@ static rg_gui_event_t slot_select_cb(rg_gui_option_t *option, rg_gui_event_t eve color = C_RED; } rg_gui_draw_image(0, margin, gui.screen_width, gui.screen_height - margin * 2, true, preview); - rg_gui_draw_rect(0, margin, gui.screen_width, gui.screen_height - margin * 2, border, color, C_NONE); - rg_gui_draw_rect(border, margin + border, gui.screen_width - border * 2, gui.style.font_height * 2 + 6, 0, C_BLACK, C_BLACK); - rg_gui_draw_text(border + 60, margin + border + 5, gui.screen_width - border * 2 - 120, buffer, C_WHITE, C_BLACK, RG_TEXT_ALIGN_CENTER|RG_TEXT_BIGGER|RG_TEXT_NO_PADDING); + rg_gui_draw_rect(NULL, 0, margin, gui.screen_width, gui.screen_height - margin * 2, border, color, C_NONE); + rg_gui_draw_rect(NULL, border, margin + border, gui.screen_width - border * 2, gui.style.font_height * 2 + 6, 0, C_BLACK, C_BLACK); + rg_gui_draw_text(NULL, border + 60, margin + border + 5, gui.screen_width - border * 2 - 120, buffer, C_WHITE, C_BLACK, RG_TEXT_ALIGN_CENTER|RG_TEXT_BIGGER|RG_TEXT_NO_PADDING); rg_surface_free(preview); } else if (event == RG_DIALOG_ENTER) @@ -1868,3 +1882,54 @@ void rg_gui_game_menu(void) rg_audio_set_mute(false); } + +void rg_gui_battery_indicator(rg_surface_t *osd){ + rg_battery_t battery = rg_input_read_battery(); + + if (battery.present && battery.level < 213) // 10% + { + rg_rect_t txt = TEXT_RECT("00:00", 0); + int bar_height = txt.height; + int icon_height = RG_MAX(8, bar_height - 4); + int icon_top = RG_MAX(0, (bar_height - icon_height - 1) / 2); + int height = icon_height; + int y_pos = icon_top; + int width = 16; + int width_fill = width / 100.f * battery.level; + + #ifdef RG_SCREEN_HAS_ROUND_CORNERS + int x_pos = -42; // This is to shift the battery icon a bit on the left + #else + int x_pos = -22; // Regular rectangle screen + #endif + + rg_color_t color_fill = C_RED; + rg_color_t color_border = C_SILVER; + rg_color_t color_empty = C_BLACK; + + rg_gui_draw_rect(osd, x_pos, y_pos, width + 2, height, 1, color_border, C_NONE); + rg_gui_draw_rect(osd, x_pos + width + 2, y_pos + 2, 2, height - 4, 1, color_border, C_NONE); + rg_gui_draw_rect(osd, x_pos + 1, y_pos + 1, width_fill, height - 2, 0, 0, color_fill); + rg_gui_draw_rect(osd, x_pos + 1 + width_fill, y_pos + 1, width - width_fill, height - 2, 0, 0, color_empty); + } +} + +void rg_gui_draw_status_bars_osd(rg_surface_t *osd) +{ + size_t max_len = osd->width / 8; + char header[max_len]; + + const rg_app_t *app = rg_system_get_app(); + rg_stats_t stats = rg_system_get_counters(); + + if (!app->initialized || app->isLauncher) + return; + + snprintf(header, max_len, "SPEED: %d%% (%d %d) / BUSY: %d%%", + (int)round(stats.totalFPS / app->tickRate * 100.f), + (int)round(stats.totalFPS), + (int)app->frameskip, + (int)round(stats.busyPercent)); + + rg_gui_draw_text(osd, 0, RG_GUI_TOP, osd->width, header, C_WHITE, C_BLACK, 0); +} \ No newline at end of file diff --git a/components/retro-go/rg_gui.h b/components/retro-go/rg_gui.h index ca5a1db15..4082777fa 100644 --- a/components/retro-go/rg_gui.h +++ b/components/retro-go/rg_gui.h @@ -90,7 +90,7 @@ struct rg_gui_option_s #define RG_DIALOG_CANCELLED -0x7654321 -#define TEXT_RECT(text, max) rg_gui_draw_text(-(max), 0, 0, (text), 0, 0, RG_TEXT_MULTILINE|RG_TEXT_DUMMY_DRAW) +#define TEXT_RECT(text, max) rg_gui_draw_text(NULL, -(max), 0, 0, (text), 0, 0, RG_TEXT_MULTILINE|RG_TEXT_DUMMY_DRAW) void rg_gui_init(void); bool rg_gui_set_language_id(int index); @@ -101,11 +101,11 @@ const char *rg_gui_get_theme_name(void); rg_image_t *rg_gui_get_theme_image(const char *name); rg_color_t rg_gui_get_theme_color(const char *section, const char *key, rg_color_t default_value); rg_image_t *rg_gui_load_image_file(const char *path); -void rg_gui_copy_buffer(int left, int top, int width, int height, int stride, const void *buffer); +void rg_gui_copy_buffer(rg_surface_t *dest_surface, int left, int top, int width, int height, int stride, const void *buffer); -rg_rect_t rg_gui_draw_text(int x_pos, int y_pos, int width, const char *text, // const rg_font_t *font, +rg_rect_t rg_gui_draw_text(rg_surface_t *dest_surface, int x_pos, int y_pos, int width, const char *text, // const rg_font_t *font, rg_color_t color_fg, rg_color_t color_bg, uint32_t flags); -void rg_gui_draw_rect(int x_pos, int y_pos, int width, int height, int border_size, +void rg_gui_draw_rect(rg_surface_t *dest_surface, int x_pos, int y_pos, int width, int height, int border_size, rg_color_t border_color, rg_color_t fill_color); void rg_gui_draw_icons(void); void rg_gui_draw_dialog(const char *header, const rg_gui_option_t *options, int sel); @@ -127,6 +127,9 @@ void rg_gui_game_menu(void); void rg_gui_about_menu(void); void rg_gui_debug_menu(void); +void rg_gui_battery_indicator(rg_surface_t *osd); +void rg_gui_draw_status_bars_osd(rg_surface_t *osd); + // Creates a 565LE color from C_RGB(255, 255, 255) #define C_RGB(r, g, b) ((((r) >> 3) << 11) | (((g) >> 2) << 5) | (((b) & 0x1F))) diff --git a/components/retro-go/rg_system.c b/components/retro-go/rg_system.c index fb94d2b7e..cf19fa1ed 100644 --- a/components/retro-go/rg_system.c +++ b/components/retro-go/rg_system.c @@ -243,7 +243,19 @@ static void update_indicators(void) } static void system_monitor_task(void *arg) -{ +{ + rg_surface_t *osd = NULL; + if(!app.isLauncher) + { + osd = rg_surface_create(280, 20, RG_PIXEL_565_LE, MEM_SLOW); + + // Create a rect to store the osd position and size + rg_rect_t osd_rect = {0, 0, 280, 20}; // Example: top-left corner + + // Set the OSD surface and position + rg_display_set_osd_surface(osd, osd_rect); + } + int64_t nextLoopTime = 0; time_t prevTime = time(NULL); @@ -256,11 +268,19 @@ static void system_monitor_task(void *arg) update_statistics(); // update_indicators(); // Implicitly called by rg_system_set_indicator below - rg_battery_t battery = rg_input_read_battery(); + + #ifdef RG_GPIO_LED // TODO: The flashing should eventually be handled by update_indicators instead of here... rg_system_set_indicator(RG_INDICATOR_POWER_LOW, (battery.present && battery.level <= 2.f && !rg_system_get_indicator(RG_INDICATOR_POWER_LOW))); + #endif + + if(osd){ + rg_gui_draw_status_bars_osd(osd); + rg_gui_battery_indicator(osd); + } + // Try to avoid complex conversions that could allocate, prefer rounding/ceiling if necessary. rg_system_log(RG_LOG_DEBUG, NULL, "STACK:%d, HEAP:%d+%d (%d+%d), BUSY:%d%%, FPS:%d (%d+%d+%d), BATT:%d\n", @@ -301,7 +321,7 @@ static void system_monitor_task(void *arg) { const char *message = "App unresponsive... Hold MENU to quit!"; // Drawing at this point isn't safe. But the alternative is being frozen... - rg_gui_draw_text(RG_GUI_CENTER, RG_GUI_CENTER, 0, message, C_RED, C_BLACK, RG_TEXT_BIGGER); + rg_gui_draw_text(NULL, RG_GUI_CENTER, RG_GUI_CENTER, 0, message, C_RED, C_BLACK, RG_TEXT_BIGGER); if (!rg_input_wait_for_key(RG_KEY_MENU, false, 2000)) RG_PANIC("Application terminated!"); // We're not in a nice state, don't normal exit } @@ -1294,7 +1314,7 @@ bool rg_emu_save_state(uint8_t slot) { // Save succeeded, let's take a pretty screenshot for the launcher! char *filename = rg_emu_get_path(RG_PATH_SCREENSHOT + slot, app.romPath); - rg_emu_screenshot(filename, rg_display_get_width() / 2, 0); + rg_emu_screenshot(filename, rg_display_get_info()->screen.width / 2, 0); free(filename); emu_update_save_slot(slot); } diff --git a/launcher/main/gui.c b/launcher/main/gui.c index ef4297097..b6bbf2628 100644 --- a/launcher/main/gui.c +++ b/launcher/main/gui.c @@ -38,8 +38,8 @@ void gui_init(bool cold_boot) .start_screen = rg_settings_get_number(NS_APP, SETTING_START_SCREEN, START_SCREEN_AUTO), .show_preview = rg_settings_get_number(NS_APP, SETTING_SHOW_PREVIEW, PREVIEW_MODE_SAVE_COVER), .scroll_mode = rg_settings_get_number(NS_APP, SETTING_SCROLL_MODE, SCROLL_MODE_CENTER), - .width = rg_display_get_width(), - .height = rg_display_get_height(), + .width = rg_display_get_info()->screen.width, + .height = rg_display_get_info()->screen.height, }; // Auto: Show carousel on cold boot, browser on warm boot (after cleanly exiting an emulator) gui.browse = gui.start_screen == START_SCREEN_BROWSER || (gui.start_screen == START_SCREEN_AUTO && !cold_boot); @@ -434,7 +434,7 @@ void gui_draw_background(tab_t *tab, int shade) if (tab->background) rg_gui_draw_image(0, 0, gui.width, gui.height, false, tab->background); else - rg_gui_draw_rect(0, 0, gui.width, gui.height, 0, 0, gui.theme->background); + rg_gui_draw_rect(NULL, 0, 0, gui.width, gui.height, 0, 0, gui.theme->background); } void gui_draw_header(tab_t *tab, int offset) @@ -448,17 +448,17 @@ void gui_draw_header(tab_t *tab, int offset) if (tab->banner) rg_gui_draw_image(LOGO_WIDTH + 1, offset + 8, 0, HEADER_HEIGHT - 8, false, tab->banner); else - rg_gui_draw_text(LOGO_WIDTH + 8, offset + 8, 0, tab->desc, gui.theme->foreground, C_TRANSPARENT, RG_TEXT_BIGGER); + rg_gui_draw_text(NULL, LOGO_WIDTH + 8, offset + 8, 0, tab->desc, gui.theme->foreground, C_TRANSPARENT, RG_TEXT_BIGGER); } void gui_draw_tab_indicator(void) { char buffer[64] = {0}; memset(buffer, '-', gui.tabs_count); - rg_gui_draw_text(RG_GUI_CENTER, RG_GUI_BOTTOM, 0, buffer, C_DIM_GRAY, C_TRANSPARENT, RG_TEXT_BIGGER|RG_TEXT_MONOSPACE); + rg_gui_draw_text(NULL, RG_GUI_CENTER, RG_GUI_BOTTOM, 0, buffer, C_DIM_GRAY, C_TRANSPARENT, RG_TEXT_BIGGER|RG_TEXT_MONOSPACE); memset(buffer, ' ', gui.tabs_count); buffer[gui.selected_tab] = '-'; - rg_gui_draw_text(RG_GUI_CENTER, RG_GUI_BOTTOM, 0, buffer, C_SNOW, C_TRANSPARENT, RG_TEXT_BIGGER|RG_TEXT_MONOSPACE); + rg_gui_draw_text(NULL, RG_GUI_CENTER, RG_GUI_BOTTOM, 0, buffer, C_SNOW, C_TRANSPARENT, RG_TEXT_BIGGER|RG_TEXT_MONOSPACE); } void gui_draw_status(tab_t *tab) @@ -468,8 +468,8 @@ void gui_draw_status(tab_t *tab) char *txt_left = tab->status[tab->status[1].left[0] ? 1 : 0].left; char *txt_right = tab->status[tab->status[1].right[0] ? 1 : 0].right; - rg_gui_draw_text(status_x, status_y, gui.width - status_x, txt_right, gui.theme->foreground, C_TRANSPARENT, RG_TEXT_ALIGN_LEFT); - rg_gui_draw_text(status_x, status_y, 0, txt_left, gui.theme->foreground, C_TRANSPARENT, RG_TEXT_ALIGN_RIGHT); + rg_gui_draw_text(NULL, status_x, status_y, gui.width - status_x, txt_right, gui.theme->foreground, C_TRANSPARENT, RG_TEXT_ALIGN_LEFT); + rg_gui_draw_text(NULL, status_x, status_y, 0, txt_left, gui.theme->foreground, C_TRANSPARENT, RG_TEXT_ALIGN_RIGHT); rg_gui_draw_icons(); } @@ -487,7 +487,7 @@ void gui_draw_list(tab_t *tab) { char buffer[64]; snprintf(buffer, 63, "[%s]", tab->navpath); - top += rg_gui_draw_text(0, top, gui.width, buffer, gui.theme->foreground, C_TRANSPARENT, 0).height; + top += rg_gui_draw_text(NULL, 0, top, gui.width, buffer, gui.theme->foreground, C_TRANSPARENT, 0).height; } top += ((gui.height - top) - (lines * line_height)) / 2; @@ -506,7 +506,7 @@ void gui_draw_list(tab_t *tab) int idx = line_offset + i; int selected = idx == list->cursor; char *label = (idx >= 0 && idx < list->length) ? list->items[idx].text : ""; - top += rg_gui_draw_text(0, top, gui.width, label, fg[selected], bg[selected], 0).height; + top += rg_gui_draw_text(NULL, 0, top, gui.width, label, fg[selected], bg[selected], 0).height; } } diff --git a/retro-core/main/main_sms.c b/retro-core/main/main_sms.c index d24382f92..0a53ca2e3 100644 --- a/retro-core/main/main_sms.c +++ b/retro-core/main/main_sms.c @@ -219,7 +219,7 @@ void sms_main(void) if (joystick & RG_KEY_START) { - rg_gui_draw_text(RG_GUI_CENTER, RG_GUI_CENTER, 0, _("To start, try: 1 or * or #"), C_YELLOW, C_BLACK, RG_TEXT_BIGGER); + rg_gui_draw_text(NULL, RG_GUI_CENTER, RG_GUI_CENTER, 0, _("To start, try: 1 or * or #"), C_YELLOW, C_BLACK, RG_TEXT_BIGGER); rg_audio_set_mute(true); int key = rg_input_read_keyboard(&coleco_keyboard); rg_audio_set_mute(false); From 48b961914c12889963265df3a265164c4b75927f Mon Sep 17 00:00:00 2001 From: Raphael Texier Date: Thu, 6 Feb 2025 14:19:16 +0100 Subject: [PATCH 2/7] adding some comments --- components/retro-go/rg_display.c | 4 ++++ components/retro-go/rg_system.c | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/components/retro-go/rg_display.c b/components/retro-go/rg_display.c index 802fcabc4..62f8e79b8 100644 --- a/components/retro-go/rg_display.c +++ b/components/retro-go/rg_display.c @@ -40,6 +40,8 @@ static const char *SETTING_CUSTOM_ZOOM = "DispCustomZoom"; #include "drivers/display/dummy.h" #endif +// TODO : make it more user-friendly -> instead of specifying a rect and a surface +// just specify the corner and the dimensions (width and height) void rg_display_set_osd_surface(rg_surface_t *surface, rg_rect_t rect) { // Free the old surface if it exists @@ -268,6 +270,8 @@ static inline void write_update(const rg_surface_t *update) lcd_set_window(osd_rect.left + display.screen.margin_left, osd_rect.top + display.screen.margin_top, width, height); + // TODO : find a way to get the background pixels + // TODO : only draw the osd when the "background surface" has changed for (size_t y = 0; y < height;) { uint16_t *lcd_buffer = lcd_get_buffer(LCD_BUFFER_LENGTH); diff --git a/components/retro-go/rg_system.c b/components/retro-go/rg_system.c index cf19fa1ed..ef715deb6 100644 --- a/components/retro-go/rg_system.c +++ b/components/retro-go/rg_system.c @@ -245,7 +245,7 @@ static void update_indicators(void) static void system_monitor_task(void *arg) { rg_surface_t *osd = NULL; - if(!app.isLauncher) + if(!app.isLauncher) // not working { osd = rg_surface_create(280, 20, RG_PIXEL_565_LE, MEM_SLOW); From 417ab3da582c76c234050c6a0f883583c57805e6 Mon Sep 17 00:00:00 2001 From: Raphael Texier Date: Tue, 11 Feb 2025 18:58:20 +0100 Subject: [PATCH 3/7] rg_display: update osd init function to use corners --- components/retro-go/rg_display.c | 84 ++++++++++++++--------- components/retro-go/rg_display.h | 18 ++++- components/retro-go/rg_system.c | 10 +-- retro-core/components/snes9x/src/memmap.c | 2 +- 4 files changed, 71 insertions(+), 43 deletions(-) diff --git a/components/retro-go/rg_display.c b/components/retro-go/rg_display.c index 62f8e79b8..07d70d6f7 100644 --- a/components/retro-go/rg_display.c +++ b/components/retro-go/rg_display.c @@ -17,9 +17,8 @@ static int16_t map_viewport_to_source_y[RG_SCREEN_HEIGHT + 1]; static uint32_t screen_line_checksum[RG_SCREEN_HEIGHT + 1]; // OSD Surface Management -static rg_surface_t *osd_surface = NULL; -static rg_rect_t osd_rect; // Store the OSD's position and size static bool osd_enabled = false; +static rg_osd_t osd; #define LINE_IS_REPEATED(Y) (map_viewport_to_source_y[(Y)] == map_viewport_to_source_y[(Y) - 1]) // This is to avoid flooring a number that is approximated to .9999999 and be explicit about it @@ -42,28 +41,53 @@ static const char *SETTING_CUSTOM_ZOOM = "DispCustomZoom"; // TODO : make it more user-friendly -> instead of specifying a rect and a surface // just specify the corner and the dimensions (width and height) -void rg_display_set_osd_surface(rg_surface_t *surface, rg_rect_t rect) +rg_surface_t *rg_display_init_osd(rg_corner_t corner, int width, int height, bool has_transparency) { - // Free the old surface if it exists - if (osd_surface != NULL) { - rg_surface_free(osd_surface); - } + // clipping if osd > display + width = RG_MIN(width, display.screen.width); + height = RG_MIN(height, display.screen.height); + + osd.has_transparency = has_transparency; - osd_surface = surface; - osd_rect = rect; // Store the position and size - if (surface != NULL) - { // Only clear if a surface is provided - uint16_t *buffer = surface->data; // Treat the buffer as 16-bit values - for (int i = 0; i < surface->width * surface->height; i++) - buffer[i] = C_TRANSPARENT; // Assign the black color directly // C_TRANSPARENT + int left, top; + switch (corner) + { + case CORNER_TOP_LEFT: + left = 0; + top = 0; + break; + case CORNER_TOP_RIGHT: + left = display.screen.width - width; + top = 0; + break; + case CORNER_BOTTOM_LEFT: + left = 0; + top = display.screen.height - height; + break; + case CORNER_BOTTOM_RIGHT: + left = display.screen.width - width; + top = display.screen.height - height; + break; + default: + left = 0; + top = 0; + break; } - osd_enabled = (surface != NULL); // Enable OSD if surface is valid -} + osd.surface = rg_surface_create(width, height, RG_PIXEL_565_LE, MEM_SLOW); -rg_surface_t* rg_display_get_osd_surface() -{ - return osd_surface; + osd.left = left; + osd.top = top; + + if (has_transparency) + { // we fill the background with the "transparent color" + uint16_t *buffer = osd.surface->data; // Treat the buffer as 16-bit values + for (int i = 0; i < osd.surface->width * osd.surface->height; i++) + buffer[i] = C_TRANSPARENT; // Assign the C_TRANSPARENT color directly to the background + } + + osd_enabled = 1; // Enable OSD if surface is valid + return osd.surface; } void rg_display_set_osd_enabled(bool enabled) @@ -79,8 +103,7 @@ bool rg_display_is_osd_enabled() void deinit_osd() { - rg_surface_free(osd_surface); // Free the OSD surface - osd_surface = NULL; + rg_surface_free(osd.surface); // Free the OSD surface osd_enabled = false; } @@ -253,25 +276,22 @@ static inline void write_update(const rg_surface_t *update) lines_remaining -= lines_to_copy; } - if (osd_enabled && osd_surface != NULL) + if (osd_enabled) { // TODO: Draw on screen display. By default it should be bottom left which is fine // for both virtual keyboard and info labels. Maybe make it configurable later... - int *buffer = osd_surface->data; + int *buffer = osd.surface->data; RG_ASSERT_ARG(buffer); - // Clipping - int width = RG_MIN(osd_rect.width, display.screen.width - osd_rect.left); - int height = RG_MIN(osd_rect.height, display.screen.height - osd_rect.top); + int width = osd.surface->width; + int height = osd.surface->height; - // This can happen when left or top is out of bound - if (width < 0 || height < 0) - return; + int top = osd.top; + int left = osd.left; - lcd_set_window(osd_rect.left + display.screen.margin_left, osd_rect.top + display.screen.margin_top, width, height); + lcd_set_window(osd.left + display.screen.margin_left, osd.top + display.screen.margin_top, width, height); // TODO : find a way to get the background pixels - // TODO : only draw the osd when the "background surface" has changed for (size_t y = 0; y < height;) { uint16_t *lcd_buffer = lcd_get_buffer(LCD_BUFFER_LENGTH); @@ -280,7 +300,7 @@ static inline void write_update(const rg_surface_t *update) // Copy line by line because stride may not match width for (size_t line = 0; line < num_lines; ++line) { - uint16_t *src = (void *)buffer + ((y + line) * osd_rect.width * 2); + uint16_t *src = (void *)buffer + ((y + line) * osd.surface->width * 2); uint16_t *dst = lcd_buffer + (line * width); for (size_t i = 0; i < width; ++i){ if(src[i] != C_TRANSPARENT) // only overwrite pixels that aren't transparent diff --git a/components/retro-go/rg_display.h b/components/retro-go/rg_display.h index 70a74cbaa..dd045fc24 100644 --- a/components/retro-go/rg_display.h +++ b/components/retro-go/rg_display.h @@ -99,8 +99,22 @@ typedef struct #include "rg_surface.h" -void rg_display_set_osd_surface(rg_surface_t *surface, rg_rect_t rect); -rg_surface_t* rg_display_get_osd_surface(); +typedef struct +{ + rg_surface_t *surface; + bool has_transparency; + int top, left; +} rg_osd_t; + +typedef enum +{ + CORNER_TOP_LEFT = 0, + CORNER_TOP_RIGHT, + CORNER_BOTTOM_LEFT, + CORNER_BOTTOM_RIGHT +} rg_corner_t; + +rg_surface_t *rg_display_init_osd(rg_corner_t corner, int width, int height, bool has_transparency); void rg_display_set_osd_enabled(bool enabled); bool rg_display_is_osd_enabled(); void deinit_osd(); diff --git a/components/retro-go/rg_system.c b/components/retro-go/rg_system.c index ef715deb6..a4182f890 100644 --- a/components/retro-go/rg_system.c +++ b/components/retro-go/rg_system.c @@ -247,13 +247,8 @@ static void system_monitor_task(void *arg) rg_surface_t *osd = NULL; if(!app.isLauncher) // not working { - osd = rg_surface_create(280, 20, RG_PIXEL_565_LE, MEM_SLOW); - - // Create a rect to store the osd position and size - rg_rect_t osd_rect = {0, 0, 280, 20}; // Example: top-left corner - - // Set the OSD surface and position - rg_display_set_osd_surface(osd, osd_rect); + // get the OSD surface + osd = rg_display_init_osd(CORNER_TOP_LEFT, 280, 15, 1); } int64_t nextLoopTime = 0; @@ -280,7 +275,6 @@ static void system_monitor_task(void *arg) rg_gui_draw_status_bars_osd(osd); rg_gui_battery_indicator(osd); } - // Try to avoid complex conversions that could allocate, prefer rounding/ceiling if necessary. rg_system_log(RG_LOG_DEBUG, NULL, "STACK:%d, HEAP:%d+%d (%d+%d), BUSY:%d%%, FPS:%d (%d+%d+%d), BATT:%d\n", diff --git a/retro-core/components/snes9x/src/memmap.c b/retro-core/components/snes9x/src/memmap.c index a646df51a..1798515ed 100644 --- a/retro-core/components/snes9x/src/memmap.c +++ b/retro-core/components/snes9x/src/memmap.c @@ -609,7 +609,7 @@ void InitROM(bool Interleaved) Sanitize(Memory.ROMId, sizeof(Memory.ROMId)); Sanitize(Memory.CompanyId, sizeof(Memory.CompanyId)); - printf("Rom loaded: name: %s, id: %s, company: %s, size: %dKB\n", Memory.ROMName, Memory.ROMId, Memory.CompanyId, Memory.CalculatedSize / 1024); + printf("Rom loaded: name: %s, id: %s, company: %s, size: %ldKB\n", Memory.ROMName, Memory.ROMId, Memory.CompanyId, Memory.CalculatedSize / 1024); Settings.ForceHeader = Settings.ForceHiROM = Settings.ForceLoROM = Settings.ForceInterleaved = Settings.ForceNoHeader = Settings.ForceNotInterleaved = Settings.ForceInterleaved2 = false; } From 7a11bdee99f35d55926e09889ef3c8ef383c132a Mon Sep 17 00:00:00 2001 From: Raphael Texier Date: Tue, 11 Feb 2025 23:46:44 +0100 Subject: [PATCH 4/7] rg_display: osd support transparent background --- components/retro-go/rg_display.c | 48 +++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/components/retro-go/rg_display.c b/components/retro-go/rg_display.c index 07d70d6f7..df4715904 100644 --- a/components/retro-go/rg_display.c +++ b/components/retro-go/rg_display.c @@ -280,18 +280,21 @@ static inline void write_update(const rg_surface_t *update) { // TODO: Draw on screen display. By default it should be bottom left which is fine // for both virtual keyboard and info labels. Maybe make it configurable later... - int *buffer = osd.surface->data; + const int *buffer = osd.surface->data; RG_ASSERT_ARG(buffer); - int width = osd.surface->width; - int height = osd.surface->height; + const int width = osd.surface->width; + const int height = osd.surface->height; - int top = osd.top; - int left = osd.left; + const int min_width = RG_MIN(draw_width, osd.surface->width); + const int min_height = RG_MIN(draw_height, osd.surface->height); + + const int x0 = RG_MAX(draw_left, osd.left); + const int y0 = RG_MAX(draw_top, osd.top); lcd_set_window(osd.left + display.screen.margin_left, osd.top + display.screen.margin_top, width, height); - // TODO : find a way to get the background pixels + // FIXME: only redraw when background has changed for (size_t y = 0; y < height;) { uint16_t *lcd_buffer = lcd_get_buffer(LCD_BUFFER_LENGTH); @@ -300,11 +303,36 @@ static inline void write_update(const rg_surface_t *update) // Copy line by line because stride may not match width for (size_t line = 0; line < num_lines; ++line) { - uint16_t *src = (void *)buffer + ((y + line) * osd.surface->width * 2); + uint16_t *viewport = (void *)data + map_viewport_to_source_y[y + line] * stride; + uint16_t *osd_buffer = (void *)buffer + (y + line) * osd.surface->width * 2; uint16_t *dst = lcd_buffer + (line * width); - for (size_t i = 0; i < width; ++i){ - if(src[i] != C_TRANSPARENT) // only overwrite pixels that aren't transparent - dst[i] = (src[i] >> 8) | (src[i] << 8); + for (size_t i = 0; i < width; ++i) + { + if(osd.has_transparency && osd_buffer[i] == C_TRANSPARENT) + { + if((y+line)>=y0 && (i-draw_left)>=x0 && (y+line)<=min_height && (i-draw_left)<=min_width) + { // the pixel is within the display surface and within the OSD + switch(format) + { + case RG_PIXEL_PALETTE: + dst[i] = palette[viewport[map_viewport_to_source_x[i]]]; + break; + case RG_PIXEL_565_LE: + dst[i] = (viewport[map_viewport_to_source_x[i]] >> 8) | (viewport[map_viewport_to_source_x[i]] << 8); + break; + default: + dst[i] = viewport[map_viewport_to_source_x[i]]; + } + } + else + { + dst[i] = 0x0000; // the ideal would be to put a "background" color + } + } + else + { + dst[i] = (osd_buffer[i] >> 8) | (osd_buffer[i] << 8); + } } } From 33e814f708b587cc69d8f1004e9ae7ed7f953b23 Mon Sep 17 00:00:00 2001 From: Raphael Texier Date: Wed, 12 Feb 2025 00:23:08 +0100 Subject: [PATCH 5/7] rg_gui: small tweaks for transparency support --- components/retro-go/rg_gui.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/components/retro-go/rg_gui.c b/components/retro-go/rg_gui.c index 094329497..bb8169a10 100644 --- a/components/retro-go/rg_gui.c +++ b/components/retro-go/rg_gui.c @@ -273,8 +273,17 @@ void rg_gui_copy_buffer(rg_surface_t *dest_surface, int left, int top, int width uint16_t *dst = target_buffer + (top + y) * target_width + left; const uint16_t *src = (void *)buffer + y * stride; for (int x = 0; x < width; ++x) - if (src[x] != C_TRANSPARENT) + // FIXME: this is a bit wanky but basically it is used to make the difference between + // drawings to the OSD and to the gui buffer wich C_TRANSPARENT are handled differently + if (dest_surface) + { dst[x] = src[x]; + } + else + { + if (src[x] != C_TRANSPARENT) + dst[x] = src[x]; + } } } else @@ -1931,5 +1940,6 @@ void rg_gui_draw_status_bars_osd(rg_surface_t *osd) (int)app->frameskip, (int)round(stats.busyPercent)); - rg_gui_draw_text(osd, 0, RG_GUI_TOP, osd->width, header, C_WHITE, C_BLACK, 0); + rg_gui_draw_rect(osd, 0, RG_GUI_TOP, osd->width, osd->height, 0, 0, C_TRANSPARENT); + rg_gui_draw_text(osd, 0, RG_GUI_TOP, osd->width, header, C_WHITE, C_TRANSPARENT, 0); } \ No newline at end of file From 2498a891140e6c435efd9a9884ab36cf50624d18 Mon Sep 17 00:00:00 2001 From: Raphael Texier Date: Fri, 14 Mar 2025 00:52:11 +0100 Subject: [PATCH 6/7] rg_display: improved rendering compatibility with the differents formats available --- components/retro-go/rg_display.c | 63 ++++++++++++++++---------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/components/retro-go/rg_display.c b/components/retro-go/rg_display.c index df4715904..9df9362d0 100644 --- a/components/retro-go/rg_display.c +++ b/components/retro-go/rg_display.c @@ -297,46 +297,47 @@ static inline void write_update(const rg_surface_t *update) // FIXME: only redraw when background has changed for (size_t y = 0; y < height;) { - uint16_t *lcd_buffer = lcd_get_buffer(LCD_BUFFER_LENGTH); + uint16_t *line_buffer = lcd_get_buffer(LCD_BUFFER_LENGTH); size_t num_lines = RG_MIN(LCD_BUFFER_LENGTH / width, height - y); // Copy line by line because stride may not match width for (size_t line = 0; line < num_lines; ++line) { - uint16_t *viewport = (void *)data + map_viewport_to_source_y[y + line] * stride; uint16_t *osd_buffer = (void *)buffer + (y + line) * osd.surface->width * 2; - uint16_t *dst = lcd_buffer + (line * width); - for (size_t i = 0; i < width; ++i) - { - if(osd.has_transparency && osd_buffer[i] == C_TRANSPARENT) - { - if((y+line)>=y0 && (i-draw_left)>=x0 && (y+line)<=min_height && (i-draw_left)<=min_width) - { // the pixel is within the display surface and within the OSD - switch(format) - { - case RG_PIXEL_PALETTE: - dst[i] = palette[viewport[map_viewport_to_source_x[i]]]; - break; - case RG_PIXEL_565_LE: - dst[i] = (viewport[map_viewport_to_source_x[i]] >> 8) | (viewport[map_viewport_to_source_x[i]] << 8); - break; - default: - dst[i] = viewport[map_viewport_to_source_x[i]]; - } - } - else - { - dst[i] = 0x0000; // the ideal would be to put a "background" color - } - } - else - { - dst[i] = (osd_buffer[i] >> 8) | (osd_buffer[i] << 8); + uint16_t *dst = line_buffer + (line * width); + + #define OSD_TO_SCREEN(PTR_TYPE, BACKGROUND_PIXEL) { \ + PTR_TYPE *viewport = (PTR_TYPE *)data; \ + if(y + line >= draw_top){ \ + viewport += ((map_viewport_to_source_y[y + line - draw_top] * stride) >> 1); \ + } \ + else{ \ + viewport += ((map_viewport_to_source_y[y + line] * stride) >> 1); \ + } \ + for (size_t x = 0; x < width; ++x){ \ + if(osd.has_transparency && osd_buffer[x] == C_TRANSPARENT){ \ + if((y+line)>=y0 && (x-draw_left)>=x0 && (y+line)<=min_height && (x-draw_left)<=min_width){\ + dst[x] = BACKGROUND_PIXEL; \ + } \ + else{ \ + dst[x] = 0x0000; \ + } \ + } \ + else{ \ + dst[x] = (osd_buffer[x] >> 8) | (osd_buffer[x] << 8); \ + } \ + } \ } - } + + if (format & RG_PIXEL_PALETTE) + OSD_TO_SCREEN(uint8_t, palette[viewport[map_viewport_to_source_x[x - draw_left]]]) + else if (format == RG_PIXEL_565_LE) + OSD_TO_SCREEN(uint16_t, (viewport[map_viewport_to_source_x[x - draw_left]] >> 8) | (viewport[map_viewport_to_source_x[x - draw_left]] << 8)) + else + OSD_TO_SCREEN(uint16_t, viewport[map_viewport_to_source_x[x - draw_left]]) } - lcd_send_buffer(lcd_buffer, width * num_lines); + lcd_send_buffer(line_buffer, width * num_lines); y += num_lines; } lcd_sync(); From af507051cfde68ec14966d2b6aa643e3da64ca36 Mon Sep 17 00:00:00 2001 From: Raphael Texier Date: Tue, 18 Mar 2025 14:37:55 +0100 Subject: [PATCH 7/7] rg_display: reverse some changes --- components/retro-go/rg_display.c | 38 +++++++++++++++-------- components/retro-go/rg_display.h | 2 ++ launcher/main/gui.c | 4 +-- retro-core/components/snes9x/src/memmap.c | 2 +- 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/components/retro-go/rg_display.c b/components/retro-go/rg_display.c index 9df9362d0..669067290 100644 --- a/components/retro-go/rg_display.c +++ b/components/retro-go/rg_display.c @@ -343,7 +343,7 @@ static inline void write_update(const rg_surface_t *update) lcd_sync(); } - if (lines_updated > display.screen.height * 0.80f) + if (lines_updated > draw_height * 0.80f) counters.fullFrames++; else counters.partFrames++; @@ -352,6 +352,8 @@ static inline void write_update(const rg_surface_t *update) static void update_viewport_scaling(void) { + int screen_width = display.screen.width; + int screen_height = display.screen.height; int src_width = display.source.width; int src_height = display.source.height; int new_width = src_width; @@ -359,16 +361,16 @@ static void update_viewport_scaling(void) if (config.scaling == RG_DISPLAY_SCALING_FULL) { - new_width = display.screen.width; + new_width = screen_width; new_height = display.screen.height; } else if (config.scaling == RG_DISPLAY_SCALING_FIT) { - new_width = FLOAT_TO_INT(display.screen.height * ((float)src_width / src_height)); - new_height = display.screen.height; - if (new_width > display.screen.width) { - new_width = display.screen.width; - new_height = FLOAT_TO_INT(display.screen.width * ((float)src_height / src_width)); + new_width = FLOAT_TO_INT(screen_height * ((float)src_width / src_height)); + new_height = screen_height; + if (new_width > screen_width) { + new_width = screen_width; + new_height = FLOAT_TO_INT(screen_width * ((float)src_height / src_width)); } } else if (config.scaling == RG_DISPLAY_SCALING_ZOOM) @@ -381,8 +383,8 @@ static void update_viewport_scaling(void) new_width &= ~1; new_height &= ~1; - display.viewport.left = (display.screen.width - new_width) / 2; - display.viewport.top = (display.screen.height - new_height) / 2; + display.viewport.left = (screen_width - new_width) / 2; + display.viewport.top = (screen_height - new_height) / 2; display.viewport.width = new_width; display.viewport.height = new_height; @@ -396,9 +398,9 @@ static void update_viewport_scaling(void) memset(screen_line_checksum, 0, sizeof(screen_line_checksum)); - for (int x = 0; x < display.screen.width; ++x) + for (int x = 0; x < screen_width; ++x) map_viewport_to_source_x[x] = FLOAT_TO_INT(x * display.viewport.step_x); - for (int y = 0; y < display.screen.height; ++y) + for (int y = 0; y < screen_height; ++y) map_viewport_to_source_y[y] = FLOAT_TO_INT(y * display.viewport.step_y); RG_LOGI("%dx%d@%.3f => %dx%d@%.3f left:%d top:%d step_x:%.2f step_y:%.2f", src_width, src_height, @@ -415,9 +417,9 @@ static bool load_border_file(const char *filename) if (filename && (border = rg_surface_load_image_file(filename, 0))) { - if (border->width != display.screen.width || border->height != display.screen.height) + if (border->width != rg_display_get_width() || border->height != rg_display_get_height()) { - rg_surface_t *resized = rg_surface_resize(border, display.screen.width, display.screen.height); + rg_surface_t *resized = rg_surface_resize(border, rg_display_get_width(), rg_display_get_height()); if (resized) { rg_surface_free(border); @@ -480,6 +482,16 @@ rg_display_counters_t rg_display_get_counters(void) return counters; } +int rg_display_get_width(void) +{ + return display.screen.width; +} + +int rg_display_get_height(void) +{ + return display.screen.height; +} + void rg_display_set_scaling(display_scaling_t scaling) { config.scaling = RG_MIN(RG_MAX(0, scaling), RG_DISPLAY_SCALING_COUNT - 1); diff --git a/components/retro-go/rg_display.h b/components/retro-go/rg_display.h index dd045fc24..3bf043c7d 100644 --- a/components/retro-go/rg_display.h +++ b/components/retro-go/rg_display.h @@ -131,6 +131,8 @@ void rg_display_submit(const rg_surface_t *update, uint32_t flags); rg_display_counters_t rg_display_get_counters(void); const rg_display_t *rg_display_get_info(void); +int rg_display_get_width(void); +int rg_display_get_height(void); void rg_display_set_scaling(display_scaling_t scaling); display_scaling_t rg_display_get_scaling(void); diff --git a/launcher/main/gui.c b/launcher/main/gui.c index b6bbf2628..b84bd10df 100644 --- a/launcher/main/gui.c +++ b/launcher/main/gui.c @@ -38,8 +38,8 @@ void gui_init(bool cold_boot) .start_screen = rg_settings_get_number(NS_APP, SETTING_START_SCREEN, START_SCREEN_AUTO), .show_preview = rg_settings_get_number(NS_APP, SETTING_SHOW_PREVIEW, PREVIEW_MODE_SAVE_COVER), .scroll_mode = rg_settings_get_number(NS_APP, SETTING_SCROLL_MODE, SCROLL_MODE_CENTER), - .width = rg_display_get_info()->screen.width, - .height = rg_display_get_info()->screen.height, + .width = rg_display_get_width(), + .height = rg_display_get_height(), }; // Auto: Show carousel on cold boot, browser on warm boot (after cleanly exiting an emulator) gui.browse = gui.start_screen == START_SCREEN_BROWSER || (gui.start_screen == START_SCREEN_AUTO && !cold_boot); diff --git a/retro-core/components/snes9x/src/memmap.c b/retro-core/components/snes9x/src/memmap.c index 1798515ed..a646df51a 100644 --- a/retro-core/components/snes9x/src/memmap.c +++ b/retro-core/components/snes9x/src/memmap.c @@ -609,7 +609,7 @@ void InitROM(bool Interleaved) Sanitize(Memory.ROMId, sizeof(Memory.ROMId)); Sanitize(Memory.CompanyId, sizeof(Memory.CompanyId)); - printf("Rom loaded: name: %s, id: %s, company: %s, size: %ldKB\n", Memory.ROMName, Memory.ROMId, Memory.CompanyId, Memory.CalculatedSize / 1024); + printf("Rom loaded: name: %s, id: %s, company: %s, size: %dKB\n", Memory.ROMName, Memory.ROMId, Memory.CompanyId, Memory.CalculatedSize / 1024); Settings.ForceHeader = Settings.ForceHiROM = Settings.ForceLoROM = Settings.ForceInterleaved = Settings.ForceNoHeader = Settings.ForceNotInterleaved = Settings.ForceInterleaved2 = false; }