/* RetroArch - A frontend for libretro. * Copyright (C) 2010-2014 - Hans-Kristian Arntzen * Copyright (C) 2011-2017 - Daniel De Matteis * Copyright (C) 2011-2017 - Higor Euripedes * Copyright (C) 2019-2021 - James Leaver * Copyright (C) 2021 - John Parton * * RetroArch is free software: you can redistribute it and/or modify it under the terms * of the GNU General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with RetroArch. * If not, see . */ #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_CONFIG_H #include "../../config.h" #endif #ifdef HAVE_MENU #include "../../menu/menu_driver.h" #endif #include "../../dingux/dingux_utils.h" #include "../../verbosity.h" #include "../../gfx/drivers_font_renderer/bitmap.h" #include "../../configuration.h" #include "../../retroarch.h" #if defined(DINGUX_BETA) #include "../../driver.h" #endif #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) #if defined(MIYOO) #define SDL_RS90_WIDTH 320 #define SDL_RS90_HEIGHT 240 #else #define SDL_RS90_WIDTH 240 #define SDL_RS90_HEIGHT 160 #endif #define SDL_RS90_NUM_FONT_GLYPHS 256 #if defined(MIYOO) #define SDL_RS90_SURFACE_FLAGS_VSYNC_ON (SDL_HWSURFACE | SDL_DOUBLEBUF | SDL_FULLSCREEN) #else #define SDL_RS90_SURFACE_FLAGS_VSYNC_ON (SDL_HWSURFACE | SDL_TRIPLEBUF | SDL_FULLSCREEN) #endif #define SDL_RS90_SURFACE_FLAGS_VSYNC_OFF (SDL_HWSURFACE | SDL_FULLSCREEN) typedef struct sdl_rs90_video sdl_rs90_video_t; struct sdl_rs90_video { SDL_Surface *screen; void (*scale_frame16)(sdl_rs90_video_t *vid, uint16_t *src, unsigned width, unsigned height, unsigned src_pitch); void (*scale_frame32)(sdl_rs90_video_t *vid, uint32_t *src, unsigned width, unsigned height, unsigned src_pitch); /* Scaling/padding/cropping parameters */ unsigned content_width; unsigned content_height; unsigned frame_width; unsigned frame_height; unsigned frame_padding_x; unsigned frame_padding_y; unsigned frame_crop_x; unsigned frame_crop_y; bool rgb32; bool menu_active; bool was_in_menu; bool mode_valid; retro_time_t last_frame_time; retro_time_t ff_frame_time_min; enum dingux_rs90_softfilter_type softfilter_type; #if defined(DINGUX_BETA) enum dingux_refresh_rate refresh_rate; #endif bool vsync; bool keep_aspect; bool scale_integer; bool quitting; bitmapfont_lut_t *osd_font; uint32_t font_colour32; uint16_t font_colour16; uint16_t menu_texture[SDL_RS90_WIDTH * SDL_RS90_HEIGHT]; }; /* Image interpolation START */ static void sdl_rs90_scale_frame16_integer(sdl_rs90_video_t *vid, uint16_t *src, unsigned width, unsigned height, unsigned src_pitch) { /* 16 bit - divide pitch by 2 */ size_t in_stride = (size_t)(src_pitch >> 1); size_t out_stride = (size_t)(vid->screen->pitch >> 1); /* Manipulate offsets so that padding/crop * are applied correctly */ uint16_t *in_ptr = src + vid->frame_crop_x + vid->frame_crop_y * in_stride; uint16_t *out_ptr = (uint16_t*)(vid->screen->pixels) + vid->frame_padding_x + out_stride * vid->frame_padding_y; size_t y = vid->frame_height; /* TODO/FIXME: Optimize this loop */ do { memcpy(out_ptr, in_ptr, vid->frame_width * sizeof(uint16_t)); in_ptr += in_stride; out_ptr += out_stride; } while (--y); } static void sdl_rs90_scale_frame32_integer(sdl_rs90_video_t *vid, uint32_t *src, unsigned width, unsigned height, unsigned src_pitch) { /* 32 bit - divide pitch by 4 */ size_t in_stride = (size_t)(src_pitch >> 2); size_t out_stride = (size_t)(vid->screen->pitch >> 2); /* Manipulate offsets so that padding/crop * are applied correctly */ uint32_t *in_ptr = src + vid->frame_crop_x + vid->frame_crop_y * in_stride; uint32_t *out_ptr = (uint32_t*)(vid->screen->pixels) + vid->frame_padding_x + out_stride * vid->frame_padding_y; size_t y = vid->frame_height; /* TODO/FIXME: Optimize this loop */ do { memcpy(out_ptr, in_ptr, vid->frame_width * sizeof(uint32_t)); in_ptr += in_stride; out_ptr += out_stride; } while (--y); } /* Approximate nearest-neighbour scaling using * bitshifts and integer math */ static void sdl_rs90_scale_frame16_point(sdl_rs90_video_t *vid, uint16_t *src, unsigned width, unsigned height, unsigned src_pitch) { uint32_t x_step = (((uint32_t)(width) << 16) + 1) / vid->frame_width; uint32_t y_step = (((uint32_t)(height) << 16) + 1) / vid->frame_height; /* 16 bit - divide pitch by 2 */ size_t in_stride = (size_t)(src_pitch >> 1); size_t out_stride = (size_t)(vid->screen->pitch >> 1); /* Apply x/y padding offset */ uint16_t *top_corner = (uint16_t*)(vid->screen->pixels) + vid->frame_padding_x + out_stride * vid->frame_padding_y; /* Temporary pointers */ uint16_t *in_ptr = NULL; uint16_t *out_ptr = NULL; uint32_t y = 0; size_t row; /* TODO/FIXME: Optimize these loops further. * Consider saving these computations in an array * and indexing over them. * Would likely be slower due to cache (non-)locality, * but it's worth a shot. * Tons of -> operations. */ for (row = 0; row < vid->frame_height; row++) { size_t col = vid->frame_width; uint32_t x = 0; out_ptr = top_corner + out_stride * row; in_ptr = src + (y >> 16) * in_stride; do { *(out_ptr++) = in_ptr[x >> 16]; x += x_step; } while (--col); y += y_step; } } static void sdl_rs90_scale_frame32_point(sdl_rs90_video_t *vid, uint32_t *src, unsigned width, unsigned height, unsigned src_pitch) { uint32_t x_step = (((uint32_t)(width) << 16) + 1) / vid->frame_width; uint32_t y_step = (((uint32_t)(height) << 16) + 1) / vid->frame_height; /* 32 bit - divide pitch by 4 */ size_t in_stride = (size_t)(src_pitch >> 2); size_t out_stride = (size_t)(vid->screen->pitch >> 2); /* Apply x/y padding offset */ uint32_t *top_corner = (uint32_t*)(vid->screen->pixels) + vid->frame_padding_x + out_stride * vid->frame_padding_y; /* Temporary pointers */ uint32_t *in_ptr = NULL; uint32_t *out_ptr = NULL; uint32_t y = 0; size_t row; /* TODO/FIXME: Optimize these loops further. * Consider saving these computations in an array * and indexing over them. * Would likely be slower due to cache (non-)locality, * but it's worth a shot. * Tons of -> operations. */ for (row = 0; row < vid->frame_height; row++) { size_t col = vid->frame_width; uint32_t x = 0; out_ptr = top_corner + out_stride * row; in_ptr = src + (y >> 16) * in_stride; do { *(out_ptr++) = in_ptr[x >> 16]; x += x_step; } while (--col); y += y_step; } } /* Produces a 50:50 mix of pixels a and b * > c.f. "Mixing Packed RGB Pixels Efficiently" * http://blargg.8bitalley.com/info/rgb_mixing.html */ #define SDL_RS90_PIXEL_AVERAGE_16(a, b) (((a) + (b) + (((a) ^ (b)) & 0x821)) >> 1) #define SDL_RS90_PIXEL_AVERAGE_32(a, b) (((a) + (b) + (((a) ^ (b)) & 0x10101)) >> 1) /* Scales a single horizontal line using approximate * linear scaling * > c.f. "Image Scaling with Bresenham" * https://www.drdobbs.com/image-scaling-with-bresenham/184405045 */ static void sdl_rs90_scale_line_bresenham16(uint16_t *target, uint16_t *src, unsigned src_width, unsigned target_width, unsigned int_part, unsigned fract_part, unsigned midpoint) { unsigned num_pixels = target_width; unsigned E = 0; /* TODO/FIXME: Determine better variable name - 'error'? */ /* If source and target have the same width, * can perform a fast copy of raw pixel data */ if (src_width == target_width) { memcpy(target, src, target_width * sizeof(uint16_t)); return; } else if (target_width > src_width) num_pixels--; do { *(target++) = (E >= midpoint) ? SDL_RS90_PIXEL_AVERAGE_16(*src, *(src + 1)) : *src; src += int_part; E += fract_part; if (E >= target_width) { E -= target_width; src++; } } while (--num_pixels); if (target_width > src_width) *target = *src; } static void sdl_rs90_scale_frame16_bresenham_horz(sdl_rs90_video_t *vid, uint16_t *src, unsigned width, unsigned height, unsigned src_pitch) { /* 16 bit - divide pitch by 2 */ size_t in_stride = (size_t)(src_pitch >> 1); size_t out_stride = (size_t)(vid->screen->pitch >> 1); uint16_t *prev_src = NULL; /* Account for x/y padding */ uint16_t *target = (uint16_t*)(vid->screen->pixels) + vid->frame_padding_x + (out_stride * vid->frame_padding_y); unsigned target_width = vid->frame_width; unsigned target_height = vid->frame_height; unsigned num_lines = target_height; unsigned int_part = (height / target_height) * in_stride; unsigned fract_part = height % target_height; unsigned E = 0; /* TODO/FIXME: Determine better variable name - 'error'? */ unsigned col_int_part = width / target_width; unsigned col_fract_part = width % target_width; /* Enhance midpoint selection as described in * https://www.compuphase.com/graphic/scale1errata.htm */ int col_midpoint = (target_width > width) ? ((int)target_width - 3 * ((int)target_width - (int)width)) >> 1 : (int)(target_width << 1) - (int)width; /* Clamp lower bound to (target_width / 2) */ if (col_midpoint < (int)(target_width >> 1)) col_midpoint = (int)(target_width >> 1); while (num_lines--) { /* If line is supposed to be identical to * previous line, just copy it */ if (src == prev_src) memcpy(target, target - out_stride, target_width * sizeof(uint16_t)); else { sdl_rs90_scale_line_bresenham16(target, src, width, target_width, col_int_part, col_fract_part, (unsigned)col_midpoint); prev_src = src; } target += out_stride; src += int_part; E += fract_part; if (E >= target_height) { E -= target_height; src += in_stride; } } } static void sdl_rs90_scale_line_bresenham32(uint32_t *target, uint32_t *src, unsigned src_width, unsigned target_width, unsigned int_part, unsigned fract_part, unsigned midpoint) { unsigned num_pixels = target_width; unsigned E = 0; /* TODO/FIXME: Determine better variable name - 'error'? */ /* If source and target have the same width, * can perform a fast copy of raw pixel data */ if (src_width == target_width) { memcpy(target, src, target_width * sizeof(uint32_t)); return; } else if (target_width > src_width) num_pixels--; do { *(target++) = (E >= midpoint) ? SDL_RS90_PIXEL_AVERAGE_32(*src, *(src + 1)) : *src; src += int_part; E += fract_part; if (E >= target_width) { E -= target_width; src++; } } while (--num_pixels); if (target_width > src_width) *target = *src; } static void sdl_rs90_scale_frame32_bresenham_horz(sdl_rs90_video_t *vid, uint32_t *src, unsigned width, unsigned height, unsigned src_pitch) { /* 32 bit - divide pitch by 4 */ size_t in_stride = (size_t)(src_pitch >> 2); size_t out_stride = (size_t)(vid->screen->pitch >> 2); uint32_t *prev_src = NULL; /* Account for x/y padding */ uint32_t *target = (uint32_t*)(vid->screen->pixels) + vid->frame_padding_x + (out_stride * vid->frame_padding_y); unsigned target_width = vid->frame_width; unsigned target_height = vid->frame_height; unsigned num_lines = target_height; unsigned int_part = (height / target_height) * in_stride; unsigned fract_part = height % target_height; unsigned E = 0; /* TODO/FIXME: Determine better variable name - 'error'? */ unsigned col_int_part = width / target_width; unsigned col_fract_part = width % target_width; /* Enhance midpoint selection as described in * https://www.compuphase.com/graphic/scale1errata.htm */ int col_midpoint = (target_width > width) ? ((int)target_width - 3 * ((int)target_width - (int)width)) >> 1 : (int)(target_width << 1) - (int)width; /* Clamp lower bound to (target_width / 2) */ if (col_midpoint < (int)(target_width >> 1)) col_midpoint = (int)(target_width >> 1); while (num_lines--) { /* If line is supposed to be identical to * previous line, just copy it */ if (src == prev_src) memcpy(target, target - out_stride, target_width * sizeof(uint32_t)); else { sdl_rs90_scale_line_bresenham32(target, src, width, target_width, col_int_part, col_fract_part, (unsigned)col_midpoint); prev_src = src; } target += out_stride; src += int_part; E += fract_part; if (E >= target_height) { E -= target_height; src += in_stride; } } } static void sdl_rs90_set_scale_frame_functions(sdl_rs90_video_t *vid) { /* Set integer scaling by default */ vid->scale_frame16 = sdl_rs90_scale_frame16_integer; vid->scale_frame32 = sdl_rs90_scale_frame32_integer; if (!vid->scale_integer) { switch (vid->softfilter_type) { case DINGUX_RS90_SOFTFILTER_BRESENHAM_HORZ: vid->scale_frame16 = sdl_rs90_scale_frame16_bresenham_horz; vid->scale_frame32 = sdl_rs90_scale_frame32_bresenham_horz; break; case DINGUX_RS90_SOFTFILTER_POINT: default: vid->scale_frame16 = sdl_rs90_scale_frame16_point; vid->scale_frame32 = sdl_rs90_scale_frame32_point; break; } } } /* Image interpolation END */ static void sdl_rs90_init_font_color(sdl_rs90_video_t *vid) { settings_t *settings = config_get_ptr(); uint32_t red = 0xFF; uint32_t green = 0xFF; uint32_t blue = 0xFF; if (settings) { red = (uint32_t)((settings->floats.video_msg_color_r * 255.0f) + 0.5f) & 0xFF; green = (uint32_t)((settings->floats.video_msg_color_g * 255.0f) + 0.5f) & 0xFF; blue = (uint32_t)((settings->floats.video_msg_color_b * 255.0f) + 0.5f) & 0xFF; } /* Convert to XRGB8888 */ vid->font_colour32 = (red << 16) | (green << 8) | blue; /* Convert to RGB565 */ red = red >> 3; green = green >> 3; blue = blue >> 3; vid->font_colour16 = (red << 11) | (green << 6) | blue; } static void sdl_rs90_blit_text16( sdl_rs90_video_t *vid, unsigned x, unsigned y, const char *str) { /* Note: Cannot draw text in padding region * (padding region is never cleared, so * any text pixels would remain as garbage) */ uint16_t *screen_buf = (uint16_t*)vid->screen->pixels; bool **font_lut = vid->osd_font->lut; /* 16 bit - divide pitch by 2 */ uint16_t screen_stride = (uint16_t)(vid->screen->pitch >> 1); uint16_t screen_width = vid->screen->w; uint16_t screen_height = vid->screen->h; unsigned x_pos = x + vid->frame_padding_x; unsigned y_pos = (y > (screen_height >> 1)) ? (y - vid->frame_padding_y) : (y + vid->frame_padding_y); uint16_t shadow_color_buf[2] = {0}; uint16_t color_buf[2]; color_buf[0] = vid->font_colour16; color_buf[1] = 0; /* Check for out of bounds y coordinates */ if (y_pos + FONT_HEIGHT + 1 >= screen_height - vid->frame_padding_y) return; while (!string_is_empty(str)) { /* Check for out of bounds x coordinates */ if (x_pos + FONT_WIDTH_STRIDE + 1 >= screen_width - vid->frame_padding_x) return; /* Deal with spaces first, for efficiency */ if (*str == ' ') str++; else { uint16_t i, j; bool *symbol_lut; uint32_t symbol = utf8_walk(&str); /* Stupid hack: 'oe' ligatures are not really * standard extended ASCII, so we have to waste * CPU cycles performing a conversion from the * unicode values... */ if (symbol == 339) /* Latin small ligature oe */ symbol = 156; if (symbol == 338) /* Latin capital ligature oe */ symbol = 140; if (symbol >= SDL_RS90_NUM_FONT_GLYPHS) continue; symbol_lut = font_lut[symbol]; for (j = 0; j < FONT_HEIGHT; j++) { uint32_t buff_offset = ((y_pos + j) * screen_stride) + x_pos; for (i = 0; i < FONT_WIDTH; i++) { if (*(symbol_lut + i + (j * FONT_WIDTH))) { uint16_t *screen_buf_ptr = screen_buf + buff_offset + i; /* Text pixel + right shadow */ memcpy(screen_buf_ptr, color_buf, sizeof(uint16_t)); /* Bottom shadow */ screen_buf_ptr += screen_stride; memcpy(screen_buf_ptr, shadow_color_buf, sizeof(uint16_t)); } } } } x_pos += FONT_WIDTH_STRIDE; } } static void sdl_rs90_blit_text32( sdl_rs90_video_t *vid, unsigned x, unsigned y, const char *str) { /* Note: Cannot draw text in padding region * (padding region is never cleared, so * any text pixels would remain as garbage) */ uint32_t *screen_buf = (uint32_t*)vid->screen->pixels; bool **font_lut = vid->osd_font->lut; /* 32 bit - divide pitch by 4 */ uint32_t screen_stride = (uint32_t)(vid->screen->pitch >> 2); uint32_t screen_width = vid->screen->w; uint32_t screen_height = vid->screen->h; unsigned x_pos = x + vid->frame_padding_x; unsigned y_pos = (y > (screen_height >> 1)) ? (y - vid->frame_padding_y) : (y + vid->frame_padding_y); uint32_t shadow_color_buf[2] = {0}; uint32_t color_buf[2]; color_buf[0] = vid->font_colour32; color_buf[1] = 0; /* Check for out of bounds y coordinates */ if (y_pos + FONT_HEIGHT + 1 >= screen_height - vid->frame_padding_y) return; while (!string_is_empty(str)) { /* Check for out of bounds x coordinates */ if (x_pos + FONT_WIDTH_STRIDE + 1 >= screen_width - vid->frame_padding_x) return; /* Deal with spaces first, for efficiency */ if (*str == ' ') str++; else { uint32_t i, j; bool *symbol_lut; uint32_t symbol = utf8_walk(&str); /* Stupid hack: 'oe' ligatures are not really * standard extended ASCII, so we have to waste * CPU cycles performing a conversion from the * unicode values... */ if (symbol == 339) /* Latin small ligature oe */ symbol = 156; if (symbol == 338) /* Latin capital ligature oe */ symbol = 140; if (symbol >= SDL_RS90_NUM_FONT_GLYPHS) continue; symbol_lut = font_lut[symbol]; for (j = 0; j < FONT_HEIGHT; j++) { uint32_t buff_offset = ((y_pos + j) * screen_stride) + x_pos; for (i = 0; i < FONT_WIDTH; i++) { if (*(symbol_lut + i + (j * FONT_WIDTH))) { uint32_t *screen_buf_ptr = screen_buf + buff_offset + i; /* Text pixel + right shadow */ memcpy(screen_buf_ptr, color_buf, sizeof(uint32_t)); /* Bottom shadow */ screen_buf_ptr += screen_stride; memcpy(screen_buf_ptr, shadow_color_buf, sizeof(uint32_t)); } } } } x_pos += FONT_WIDTH_STRIDE; } } static void sdl_rs90_blit_video_mode_error_msg(sdl_rs90_video_t *vid) { const char *error_msg = msg_hash_to_str(MSG_UNSUPPORTED_VIDEO_MODE); char display_mode[64]; display_mode[0] = '\0'; /* Zero out pixel buffer */ memset(vid->screen->pixels, 0, vid->screen->pitch * vid->screen->h); /* Generate display mode string */ snprintf(display_mode, sizeof(display_mode), "> %ux%u, %s", vid->frame_width, vid->frame_height, vid->rgb32 ? "XRGB8888" : "RGB565"); /* Print error message */ if (vid->rgb32) { sdl_rs90_blit_text32(vid, FONT_WIDTH_STRIDE, FONT_WIDTH_STRIDE, error_msg); sdl_rs90_blit_text32(vid, FONT_WIDTH_STRIDE, FONT_WIDTH_STRIDE + FONT_HEIGHT_STRIDE, display_mode); } else { sdl_rs90_blit_text16(vid, FONT_WIDTH_STRIDE, FONT_WIDTH_STRIDE, error_msg); sdl_rs90_blit_text16(vid, FONT_WIDTH_STRIDE, FONT_WIDTH_STRIDE + FONT_HEIGHT_STRIDE, display_mode); } } static void sdl_rs90_gfx_free(void *data) { sdl_rs90_video_t *vid = (sdl_rs90_video_t*)data; if (!vid) return; if (vid->osd_font) bitmapfont_free_lut(vid->osd_font); free(vid); } static void sdl_rs90_input_driver_init( const char *input_drv_name, const char *joypad_drv_name, input_driver_t **input, void **input_data) { /* Sanity check */ if (!input || !input_data) return; *input = NULL; *input_data = NULL; /* If input driver name is empty, cannot * initialise anything... */ if (string_is_empty(input_drv_name)) return; if (string_is_equal(input_drv_name, "sdl_dingux")) { *input_data = input_driver_init_wrap(&input_sdl_dingux, joypad_drv_name); if (*input_data) *input = &input_sdl_dingux; return; } #if defined(HAVE_SDL) || defined(HAVE_SDL2) if (string_is_equal(input_drv_name, "sdl")) { *input_data = input_driver_init_wrap(&input_sdl, joypad_drv_name); if (*input_data) *input = &input_sdl; return; } #endif #if defined(HAVE_UDEV) if (string_is_equal(input_drv_name, "udev")) { *input_data = input_driver_init_wrap(&input_udev, joypad_drv_name); if (*input_data) *input = &input_udev; return; } #endif #if defined(__linux__) if (string_is_equal(input_drv_name, "linuxraw")) { *input_data = input_driver_init_wrap(&input_linuxraw, joypad_drv_name); if (*input_data) *input = &input_linuxraw; return; } #endif } static void *sdl_rs90_gfx_init(const video_info_t *video, input_driver_t **input, void **input_data) { sdl_rs90_video_t *vid = NULL; uint32_t sdl_subsystem_flags = SDL_WasInit(0); settings_t *settings = config_get_ptr(); #if defined(DINGUX_BETA) enum dingux_refresh_rate current_refresh_rate = DINGUX_REFRESH_RATE_60HZ; enum dingux_refresh_rate target_refresh_rate = (enum dingux_refresh_rate) settings->uints.video_dingux_refresh_rate; bool refresh_rate_valid = false; float hw_refresh_rate = 0.0f; #endif const char *input_drv_name = settings->arrays.input_driver; const char *joypad_drv_name = settings->arrays.input_joypad_driver; uint32_t surface_flags = (video->vsync) ? SDL_RS90_SURFACE_FLAGS_VSYNC_ON : SDL_RS90_SURFACE_FLAGS_VSYNC_OFF; /* Initialise graphics subsystem, if required */ if (sdl_subsystem_flags == 0) { if (SDL_Init(SDL_INIT_VIDEO) < 0) return NULL; } else if ((sdl_subsystem_flags & SDL_INIT_VIDEO) == 0) { if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) return NULL; } vid = (sdl_rs90_video_t*)calloc(1, sizeof(*vid)); if (!vid) return NULL; #if defined(DINGUX_BETA) /* Get current refresh rate */ refresh_rate_valid = dingux_get_video_refresh_rate(¤t_refresh_rate); /* Check if refresh rate needs to be updated */ if (!refresh_rate_valid || (current_refresh_rate != target_refresh_rate)) hw_refresh_rate = dingux_set_video_refresh_rate(target_refresh_rate); else { /* Correct refresh rate is already set, * just convert to float */ switch (current_refresh_rate) { case DINGUX_REFRESH_RATE_50HZ: hw_refresh_rate = 50.0f; break; default: hw_refresh_rate = 60.0f; break; } } if (hw_refresh_rate == 0.0f) { RARCH_ERR("[SDL1]: Failed to set video refresh rate\n"); goto error; } vid->refresh_rate = target_refresh_rate; switch (target_refresh_rate) { case DINGUX_REFRESH_RATE_50HZ: vid->ff_frame_time_min = 20000; break; default: vid->ff_frame_time_min = 16667; break; } driver_ctl(RARCH_DRIVER_CTL_SET_REFRESH_RATE, &hw_refresh_rate); #else vid->ff_frame_time_min = 16667; #endif vid->screen = SDL_SetVideoMode( SDL_RS90_WIDTH, SDL_RS90_HEIGHT, video->rgb32 ? 32 : 16, surface_flags); if (!vid->screen) { RARCH_ERR("[SDL1]: Failed to init SDL surface: %s\n", SDL_GetError()); goto error; } vid->content_width = SDL_RS90_WIDTH; vid->content_height = SDL_RS90_HEIGHT; vid->frame_width = SDL_RS90_WIDTH; vid->frame_height = SDL_RS90_HEIGHT; vid->rgb32 = video->rgb32; vid->vsync = video->vsync; vid->keep_aspect = settings->bools.video_dingux_ipu_keep_aspect; vid->scale_integer = settings->bools.video_scale_integer; vid->softfilter_type = (enum dingux_rs90_softfilter_type) settings->uints.video_dingux_rs90_softfilter_type; vid->menu_active = false; vid->was_in_menu = false; vid->quitting = false; vid->mode_valid = true; vid->last_frame_time = 0; SDL_ShowCursor(SDL_DISABLE); sdl_rs90_input_driver_init(input_drv_name, joypad_drv_name, input, input_data); /* Initialise OSD font */ sdl_rs90_init_font_color(vid); vid->osd_font = bitmapfont_get_lut(); if (!vid->osd_font || vid->osd_font->glyph_max < (SDL_RS90_NUM_FONT_GLYPHS - 1)) { RARCH_ERR("[SDL1]: Failed to init OSD font\n"); goto error; } /* Assign frame scaling function pointers */ sdl_rs90_set_scale_frame_functions(vid); return vid; error: sdl_rs90_gfx_free(vid); return NULL; } static void sdl_rs90_set_output( sdl_rs90_video_t* vid, unsigned width, unsigned height, bool rgb32) { uint32_t surface_flags = (vid->vsync) ? SDL_RS90_SURFACE_FLAGS_VSYNC_ON : SDL_RS90_SURFACE_FLAGS_VSYNC_OFF; vid->content_width = width; vid->content_height = height; /* Technically, "scale_integer" here just means "do not scale" * If the content is larger, we crop, otherwise we just centre * it in the frame. * If we want to support a core with an absolutely tiny screen * (i.e. less than 120x80), we should do actual integer scaling * (PokeMini @ 96x64 and VeMUlator @ 48x32 are probably the * only cores where this is an issue, but PokeMini at least * offers internal upscaling...) */ if (vid->scale_integer) { if (width > SDL_RS90_WIDTH) { vid->frame_width = SDL_RS90_WIDTH; vid->frame_crop_x = (width - SDL_RS90_WIDTH) >> 1; vid->frame_padding_x = 0; } else { vid->frame_width = width; vid->frame_crop_x = 0; vid->frame_padding_x = (SDL_RS90_WIDTH - width) >> 1; } if (height > SDL_RS90_HEIGHT) { vid->frame_height = SDL_RS90_HEIGHT; vid->frame_crop_y = (height - SDL_RS90_HEIGHT) >> 1; vid->frame_padding_y = 0; } else { vid->frame_height = height; vid->frame_crop_y = 0; vid->frame_padding_y = (SDL_RS90_HEIGHT - height) >> 1; } } else { /* Normal scaling */ if (vid->keep_aspect) { if (height * SDL_RS90_WIDTH > width * SDL_RS90_HEIGHT) { /* Integer math is fine */ vid->frame_width = (width * SDL_RS90_HEIGHT) / height; vid->frame_height = SDL_RS90_HEIGHT; } else { /* Integer math is fine */ vid->frame_width = SDL_RS90_WIDTH; vid->frame_height = (height * SDL_RS90_WIDTH) / width; } } else { vid->frame_width = SDL_RS90_WIDTH; vid->frame_height = SDL_RS90_HEIGHT; } vid->frame_crop_x = 0; vid->frame_padding_x = (SDL_RS90_WIDTH - vid->frame_width) >> 1; vid->frame_crop_y = 0; vid->frame_padding_y = (SDL_RS90_HEIGHT - vid->frame_height) >> 1; } /* Attempt to change video mode */ vid->screen = SDL_SetVideoMode( SDL_RS90_WIDTH, SDL_RS90_HEIGHT, rgb32 ? 32 : 16, surface_flags); /* Check whether selected display mode is valid */ if (unlikely(!vid->screen)) { RARCH_ERR("[SDL1]: Failed to init SDL surface: %s\n", SDL_GetError()); vid->mode_valid = false; } else { /* Determine whether frame padding is required */ if ((vid->frame_padding_x > 0) || (vid->frame_padding_y > 0)) { /* To prevent garbage pixels in the padding * region, must zero out pixel buffer */ if (SDL_MUSTLOCK(vid->screen)) SDL_LockSurface(vid->screen); memset(vid->screen->pixels, 0, vid->screen->pitch * vid->screen->h); if (SDL_MUSTLOCK(vid->screen)) SDL_UnlockSurface(vid->screen); } vid->mode_valid = true; } } static void sdl_rs90_blit_frame16(sdl_rs90_video_t *vid, uint16_t* src, unsigned width, unsigned height, unsigned src_pitch) { /* If source and destination buffers have the * same pitch, perform fast copy of raw pixel data */ /* TODO/FIXME: Make sure this code path is used for * GBA content */ if (src_pitch == vid->screen->pitch && height == SDL_RS90_HEIGHT) memcpy(vid->screen->pixels, src, src_pitch * SDL_RS90_HEIGHT); else vid->scale_frame16(vid, src, width, height, src_pitch); } static void sdl_rs90_blit_frame32(sdl_rs90_video_t *vid, uint32_t* src, unsigned width, unsigned height, unsigned src_pitch) { /* If source and destination buffers have the * same pitch, perform fast copy of raw pixel data */ /* TODO/FIXME: Make sure this code path is used for * GBA content */ if ((src_pitch == vid->screen->pitch) && (height == SDL_RS90_HEIGHT)) memcpy(vid->screen->pixels, src, src_pitch * SDL_RS90_HEIGHT); else vid->scale_frame32(vid, src, width, height, src_pitch); } static bool sdl_rs90_gfx_frame(void *data, const void *frame, unsigned width, unsigned height, uint64_t frame_count, unsigned pitch, const char *msg, video_frame_info_t *video_info) { sdl_rs90_video_t* vid = (sdl_rs90_video_t*)data; /* Return early if: * - Input sdl_rs90_video_t struct is NULL * (cannot realistically happen) * - Menu is inactive and input 'content' frame * data is NULL (may happen when e.g. a running * core skips a frame) */ if (unlikely(!vid)) return true; /* If fast forward is currently active, we may * push frames at an 'unlimited' rate. Since the * display has a fixed refresh rate of 60 Hz (or * potentially 50 Hz on OpenDingux Beta), this * represents wasted effort. We therefore drop any * 'excess' frames in this case. * (Note that we *only* do this when fast forwarding. * Attempting this trick while running content normally * will cause bad frame pacing) */ if (unlikely(video_info->input_driver_nonblock_state)) { retro_time_t current_time = cpu_features_get_time_usec(); if ((current_time - vid->last_frame_time) < vid->ff_frame_time_min) return true; vid->last_frame_time = current_time; } #ifdef HAVE_MENU menu_driver_frame(video_info->menu_is_alive, video_info); #endif if (likely(!vid->menu_active)) { /* Update video mode if we were in the menu on * the previous frame, or width/height have changed */ if (unlikely( vid->was_in_menu || (vid->content_width != width) || (vid->content_height != height))) sdl_rs90_set_output(vid, width, height, vid->rgb32); /* Must always lock SDL surface before * manipulating raw pixel buffer */ if (SDL_MUSTLOCK(vid->screen)) SDL_LockSurface(vid->screen); if (likely(vid->mode_valid)) { if (likely(frame)) { /* Blit frame to SDL surface */ if (vid->rgb32) sdl_rs90_blit_frame32(vid, (uint32_t*)frame, width, height, pitch); else sdl_rs90_blit_frame16(vid, (uint16_t*)frame, width, height, pitch); } } /* If current display mode is invalid, * just display an error message */ else sdl_rs90_blit_video_mode_error_msg(vid); vid->was_in_menu = false; } else { /* If this is the first frame that the menu * is active, update video mode */ if (!vid->was_in_menu) { sdl_rs90_set_output(vid, SDL_RS90_WIDTH, SDL_RS90_HEIGHT, false); vid->was_in_menu = true; } if (SDL_MUSTLOCK(vid->screen)) SDL_LockSurface(vid->screen); /* Blit menu texture to SDL surface */ sdl_rs90_blit_frame16(vid, vid->menu_texture, SDL_RS90_WIDTH, SDL_RS90_HEIGHT, SDL_RS90_WIDTH * sizeof(uint16_t)); } /* Print OSD text, if required */ if (msg) { /* If menu is active, colour depth is overridden * to 16 bit */ if (vid->rgb32 && !vid->menu_active) sdl_rs90_blit_text32(vid, FONT_WIDTH_STRIDE, vid->screen->h - (FONT_HEIGHT + FONT_WIDTH_STRIDE), msg); else sdl_rs90_blit_text16(vid, FONT_WIDTH_STRIDE, vid->screen->h - (FONT_HEIGHT + FONT_WIDTH_STRIDE), msg); } /* Pixel manipulation complete - unlock * SDL surface */ if (SDL_MUSTLOCK(vid->screen)) SDL_UnlockSurface(vid->screen); SDL_Flip(vid->screen); return true; } static void sdl_rs90_set_texture_enable(void *data, bool state, bool full_screen) { sdl_rs90_video_t *vid = (sdl_rs90_video_t*)data; if (unlikely(!vid)) return; vid->menu_active = state; } static void sdl_rs90_set_texture_frame(void *data, const void *frame, bool rgb32, unsigned width, unsigned height, float alpha) { sdl_rs90_video_t *vid = (sdl_rs90_video_t*)data; if (unlikely( !vid || rgb32 || (width > SDL_RS90_WIDTH) || (height > SDL_RS90_HEIGHT))) return; memcpy(vid->menu_texture, frame, width * height * sizeof(uint16_t)); } static void sdl_rs90_gfx_set_nonblock_state(void *data, bool toggle, bool adaptive_vsync_enabled, unsigned swap_interval) { sdl_rs90_video_t *vid = (sdl_rs90_video_t*)data; bool vsync = !toggle; if (unlikely(!vid)) return; /* Check whether vsync status has changed */ if (vid->vsync != vsync) { unsigned current_width = vid->content_width; unsigned current_height = vid->content_height; vid->vsync = vsync; /* Update video mode */ /* TODO/FIXME: The following workaround is required * on GCW0 devices; check whether it is required on * the RS-90, and remove if not */ /* Note that a tedious workaround is required... * - Calling SDL_SetVideoMode() with the currently * set width, height and pixel format can randomly * become a noop even if the surface flags change. * - Since all we are doing here is changing the VSYNC * parameter (which just modifies surface flags), this * means the VSYNC toggle may not be registered... * - This is a huge problem when enabling fast forward, * because VSYNC ON effectively limits maximum frame * rate - if we push frames too rapidly, the OS chokes * and the display freezes. * We have to ensure that the VSYNC state change is * applied in all cases. We can only do this by forcing * a 'real' video mode update, which means adjusting the * video resolution. We therefore end up calling * sdl_rs90_set_output() *twice*, setting the dimensions * to an arbitrary value before restoring the actual * desired width/height */ sdl_rs90_set_output(vid, current_width, (current_height > 4) ? (current_height - 2) : 16, vid->rgb32); sdl_rs90_set_output(vid, current_width, current_height, vid->rgb32); } } static void sdl_rs90_gfx_check_window(sdl_rs90_video_t *vid) { SDL_Event event; SDL_PumpEvents(); while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_QUITMASK)) { if (event.type != SDL_QUIT) continue; vid->quitting = true; break; } } static bool sdl_rs90_gfx_alive(void *data) { sdl_rs90_video_t *vid = (sdl_rs90_video_t*)data; if (unlikely(!vid)) return false; sdl_rs90_gfx_check_window(vid); return !vid->quitting; } static bool sdl_rs90_gfx_focus(void *data) { return true; } static bool sdl_rs90_gfx_suppress_screensaver(void *data, bool enable) { return false; } static bool sdl_rs90_gfx_has_windowed(void *data) { return false; } static void sdl_rs90_gfx_viewport_info(void *data, struct video_viewport *vp) { sdl_rs90_video_t *vid = (sdl_rs90_video_t*)data; if (unlikely(!vid)) return; vp->x = 0; vp->y = 0; vp->width = vp->full_width = vid->frame_width; vp->height = vp->full_height = vid->frame_height; } static float sdl_rs90_get_refresh_rate(void *data) { #if defined(DINGUX_BETA) sdl_rs90_video_t *vid = (sdl_rs90_video_t*)data; if (!vid) return 0.0f; switch (vid->refresh_rate) { case DINGUX_REFRESH_RATE_50HZ: return 50.0f; default: break; } #endif return 60.0f; } static void sdl_rs90_set_filtering(void *data, unsigned index, bool smooth, bool ctx_scaling) { sdl_rs90_video_t *vid = (sdl_rs90_video_t*)data; settings_t *settings = config_get_ptr(); enum dingux_rs90_softfilter_type softfilter_type = (settings) ? (enum dingux_rs90_softfilter_type)settings->uints.video_dingux_rs90_softfilter_type : DINGUX_RS90_SOFTFILTER_POINT; if (!vid || !settings) return; /* Update software filter setting, if required */ if (vid->softfilter_type != softfilter_type) { vid->softfilter_type = softfilter_type; sdl_rs90_set_scale_frame_functions(vid); } } static void sdl_rs90_apply_state_changes(void *data) { sdl_rs90_video_t *vid = (sdl_rs90_video_t*)data; settings_t *settings = config_get_ptr(); bool keep_aspect = (settings) ? settings->bools.video_dingux_ipu_keep_aspect : true; bool integer_scaling = (settings) ? settings->bools.video_scale_integer : false; if (!vid || !settings) return; if ((vid->keep_aspect != keep_aspect) || (vid->scale_integer != integer_scaling)) { vid->keep_aspect = keep_aspect; vid->scale_integer = integer_scaling; /* Reassign frame scaling function pointers */ sdl_rs90_set_scale_frame_functions(vid); /* Aspect/scaling changes require all frame * dimension/padding/cropping parameters to * be recalculated. Easiest method is to just * (re-)set the current output video mode * Note: If menu is active, colour depth is * overridden to 16 bit */ sdl_rs90_set_output(vid, vid->content_width, vid->content_height, vid->menu_active ? false : vid->rgb32); } } static uint32_t sdl_rs90_get_flags(void *data) { return 0; } static const video_poke_interface_t sdl_rs90_poke_interface = { sdl_rs90_get_flags, NULL, NULL, NULL, sdl_rs90_get_refresh_rate, sdl_rs90_set_filtering, NULL, /* get_video_output_size */ NULL, /* get_video_output_prev */ NULL, /* get_video_output_next */ NULL, /* get_current_framebuffer */ NULL, /* get_proc_address */ NULL, sdl_rs90_apply_state_changes, sdl_rs90_set_texture_frame, sdl_rs90_set_texture_enable, NULL, NULL, /* sdl_show_mouse */ NULL, /* sdl_grab_mouse_toggle */ NULL, /* get_current_shader */ NULL, /* get_current_software_framebuffer */ NULL, /* get_hw_render_interface */ NULL, /* set_hdr_max_nits */ NULL, /* set_hdr_paper_white_nits */ NULL, /* set_hdr_contrast */ NULL /* set_hdr_expand_gamut */ }; static void sdl_rs90_get_poke_interface(void *data, const video_poke_interface_t **iface) { *iface = &sdl_rs90_poke_interface; } static bool sdl_rs90_gfx_set_shader(void *data, enum rarch_shader_type type, const char *path) { return false; } video_driver_t video_sdl_rs90 = { sdl_rs90_gfx_init, sdl_rs90_gfx_frame, sdl_rs90_gfx_set_nonblock_state, sdl_rs90_gfx_alive, sdl_rs90_gfx_focus, sdl_rs90_gfx_suppress_screensaver, sdl_rs90_gfx_has_windowed, sdl_rs90_gfx_set_shader, sdl_rs90_gfx_free, "sdl_rs90", NULL, NULL, /* set_rotation */ sdl_rs90_gfx_viewport_info, NULL, /* read_viewport */ NULL, /* read_frame_raw */ #ifdef HAVE_OVERLAY NULL, #endif #ifdef HAVE_VIDEO_LAYOUT NULL, #endif sdl_rs90_get_poke_interface };