diff --git a/file.c b/file.c index c57f6c043b..91cdafeee5 100644 --- a/file.c +++ b/file.c @@ -1,685 +1,682 @@ -/* RetroArch - A frontend for libretro. - * Copyright (C) 2010-2012 - Hans-Kristian Arntzen - * - * 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 "file.h" -#include "general.h" -#include -#include "boolean.h" -#include "libretro.h" -#include -#include -#include "dynamic.h" -#include "movie.h" -#include "patch.h" -#include "compat/strl.h" -#include "hash.h" - -#if defined(_WIN32) && !defined(_XBOX) -#include -#include -#include -#elif defined(_XBOX) -#include -#define setmode _setmode -#define INVALID_FILE_ATTRIBUTES -1 -#endif - -// Generic file loader. -ssize_t read_file(const char *path, void **buf) -{ - void *rom_buf = NULL; - FILE *file = fopen(path, "rb"); - ssize_t rc = 0; - size_t len = 0; - if (!file) - goto error; - - fseek(file, 0, SEEK_END); - len = ftell(file); - rewind(file); - rom_buf = malloc(len + 1); - if (!rom_buf) - { - RARCH_ERR("Couldn't allocate memory.\n"); - goto error; - } - - if ((rc = fread(rom_buf, 1, len, file)) < (ssize_t)len) - RARCH_WARN("Didn't read whole file.\n"); - - *buf = rom_buf; - // Allow for easy reading of strings to be safe. - // Will only work with sane character formatting (Unix). - ((char*)rom_buf)[len] = '\0'; - fclose(file); - return rc; - -error: - if (file) - fclose(file); - free(rom_buf); - *buf = NULL; - return -1; -} - -// Reads file content as one string. -bool read_file_string(const char *path, char **buf) -{ - *buf = NULL; - FILE *file = fopen(path, "r"); - size_t len = 0; - char *ptr = NULL; - - if (!file) - goto error; - - fseek(file, 0, SEEK_END); - len = ftell(file) + 2; // Takes account of being able to read in EOF and '\0' at end. - rewind(file); - - *buf = (char*)calloc(len, sizeof(char)); - if (!*buf) - goto error; - - ptr = *buf; - - while (ptr && !feof(file)) - { - size_t bufsize = (size_t)(((ptrdiff_t)*buf + (ptrdiff_t)len) - (ptrdiff_t)ptr); - fgets(ptr, bufsize, file); - - ptr += strlen(ptr); - } - - ptr = strchr(ptr, EOF); - if (ptr) - *ptr = '\0'; - - fclose(file); - return true; - -error: - if (file) - fclose(file); - if (*buf) - free(*buf); - return false; -} - -static void patch_rom(uint8_t **buf, ssize_t *size) -{ - uint8_t *ret_buf = *buf; - ssize_t ret_size = *size; - - const char *patch_desc = NULL; - const char *patch_path = NULL; - patch_error_t err = PATCH_UNKNOWN; - patch_func_t func = NULL; - - ssize_t patch_size = 0; - void *patch_data = NULL; - bool success = false; - - if (g_extern.ups_pref + g_extern.bps_pref + g_extern.ips_pref > 1) - { - RARCH_WARN("Several patches are explicitly defined, ignoring all ...\n"); - return; - } - - bool allow_bps = !g_extern.ups_pref && !g_extern.ips_pref; - bool allow_ups = !g_extern.bps_pref && !g_extern.ips_pref; - bool allow_ips = !g_extern.ups_pref && !g_extern.bps_pref; - - if (allow_ups && *g_extern.ups_name && (patch_size = read_file(g_extern.ups_name, &patch_data)) >= 0) - { - patch_desc = "UPS"; - patch_path = g_extern.ups_name; - func = ups_apply_patch; - } - else if (allow_bps && *g_extern.bps_name && (patch_size = read_file(g_extern.bps_name, &patch_data)) >= 0) - { - patch_desc = "BPS"; - patch_path = g_extern.bps_name; - func = bps_apply_patch; - } - else if (allow_ips && *g_extern.ips_name && (patch_size = read_file(g_extern.ips_name, &patch_data)) >= 0) - { - patch_desc = "IPS"; - patch_path = g_extern.ips_name; - func = ips_apply_patch; - } - else - { - RARCH_LOG("Did not find a valid ROM patch.\n"); - return; - } - - RARCH_LOG("Found %s file in \"%s\", attempting to patch ...\n", patch_desc, patch_path); - - size_t target_size = ret_size * 4; // Just to be sure ... - uint8_t *patched_rom = (uint8_t*)malloc(target_size); - if (!patched_rom) - { - RARCH_ERR("Failed to allocate memory for patched ROM ...\n"); - goto error; - } - - err = func((const uint8_t*)patch_data, patch_size, ret_buf, ret_size, patched_rom, &target_size); - if (err == PATCH_SUCCESS) - { - RARCH_LOG("ROM patched successfully (%s).\n", patch_desc); - success = true; - } - else - RARCH_ERR("Failed to patch %s: Error #%u\n", patch_desc, (unsigned)err); - - if (success) - { - free(ret_buf); - *buf = patched_rom; - *size = target_size; - } - - if (patch_data) - free(patch_data); - - return; - -error: - *buf = ret_buf; - *size = ret_size; - if (patch_data) - free(patch_data); -} - -// Load SNES rom only. Applies a hack for headered ROMs. -static ssize_t read_rom_file(FILE *file, void **buf) -{ - ssize_t ret = 0; - uint8_t *ret_buf = NULL; - - if (file == NULL) // stdin - { -#if defined(_WIN32) && !defined(_XBOX) -//TODO: Warning on MSVC 2012 - warning C4996: 'setmode': -//The POSIX name for this item is deprecated. Instead, -//use the ISO C++ conformant name: _setmode - setmode(0, O_BINARY); -#endif - - RARCH_LOG("Reading ROM from stdin ...\n"); - size_t buf_size = 0xFFFFF; // Some initial guesstimate. - size_t buf_ptr = 0; - uint8_t *rom_buf = (uint8_t*)malloc(buf_size); - if (rom_buf == NULL) - { - RARCH_ERR("Couldn't allocate memory.\n"); - return -1; - } - - for (;;) - { - size_t ret = fread(rom_buf + buf_ptr, 1, buf_size - buf_ptr, stdin); - buf_ptr += ret; - - // We've reached the end - if (buf_ptr < buf_size) - break; - - rom_buf = (uint8_t*)realloc(rom_buf, buf_size * 2); - if (rom_buf == NULL) - { - RARCH_ERR("Couldn't allocate memory.\n"); - return -1; - } - - buf_size *= 2; - } - - ret_buf = rom_buf; - ret = buf_ptr; - } - else - { - fseek(file, 0, SEEK_END); - ret = ftell(file); - rewind(file); - - void *rom_buf = malloc(ret); - if (rom_buf == NULL) - { - RARCH_ERR("Couldn't allocate memory.\n"); - return -1; - } - - if (fread(rom_buf, 1, ret, file) < (size_t)ret) - { - RARCH_ERR("Didn't read whole file.\n"); - free(rom_buf); - return -1; - } - - ret_buf = (uint8_t*)rom_buf; - } - - if (!g_extern.block_patch) - { - // Attempt to apply a patch. - patch_rom(&ret_buf, &ret); - } - - // Remove copier header if present (512 first bytes). - if ((ret & 0x7fff) == 512) - { - memmove(ret_buf, ret_buf + 512, ret - 512); - ret -= 512; - } - - g_extern.cart_crc = crc32_calculate(ret_buf, ret); -#ifdef HAVE_XML - sha256_hash(g_extern.sha256, ret_buf, ret); - RARCH_LOG("SHA256 sum: %s\n", g_extern.sha256); -#endif - *buf = ret_buf; - return ret; -} - - -// Dump stuff to file. -static bool dump_to_file(const char *path, const void *data, size_t size) -{ - FILE *file = fopen(path, "wb"); - if (!file) - return false; - else - { - bool ret = fwrite(data, 1, size, file) == size; - fclose(file); - return ret; - } -} - -static const char *ramtype2str(int type) -{ - switch (type) - { - case RETRO_MEMORY_SAVE_RAM: - case RETRO_MEMORY_SNES_GAME_BOY_RAM: - case RETRO_MEMORY_SNES_BSX_RAM: - return ".srm"; - - case RETRO_MEMORY_RTC: - case RETRO_MEMORY_SNES_GAME_BOY_RTC: - return ".rtc"; - - case RETRO_MEMORY_SNES_BSX_PRAM: - return ".pram"; - - case RETRO_MEMORY_SNES_SUFAMI_TURBO_A_RAM: - return ".aram"; - case RETRO_MEMORY_SNES_SUFAMI_TURBO_B_RAM: - return ".bram"; - - default: - return ""; - } -} - -// Attempt to save valuable RAM data somewhere ... -static void dump_to_file_desperate(const void *data, size_t size, int type) -{ -#if defined(_WIN32) && !defined(_XBOX) - const char *base = getenv("APPDATA"); -#elif defined(__CELLOS_LV2__) || defined(_XBOX) - const char *base = NULL; -#else - const char *base = getenv("HOME"); -#endif - - if (!base) - goto error; - - char path[PATH_MAX]; - snprintf(path, sizeof(path), "%s/RetroArch-recovery-", base); - char timebuf[PATH_MAX]; - - time_t time_; - time(&time_); - strftime(timebuf, sizeof(timebuf), "%Y-%m-%d-%H-%M-%S", localtime(&time_)); - strlcat(path, timebuf, sizeof(path)); - strlcat(path, ramtype2str(type), sizeof(path)); - - if (dump_to_file(path, data, size)) - RARCH_WARN("Succeeded in saving RAM data to \"%s\".\n", path); - else - goto error; - - return; - -error: - RARCH_WARN("Failed ... Cannot recover save file.\n"); -} - -bool save_state(const char *path) -{ - RARCH_LOG("Saving state: \"%s\".\n", path); - size_t size = pretro_serialize_size(); - if (size == 0) - return false; - - void *data = malloc(size); - if (!data) - { - RARCH_ERR("Failed to allocate memory for save state buffer.\n"); - return false; - } - - RARCH_LOG("State size: %d bytes.\n", (int)size); - bool ret = pretro_serialize(data, size); - if (ret) - ret = dump_to_file(path, data, size); - - if (!ret) - RARCH_ERR("Failed to save state to \"%s\".\n", path); - - free(data); - return ret; -} - -bool load_state(const char *path) -{ - RARCH_LOG("Loading state: \"%s\".\n", path); - void *buf = NULL; - ssize_t size = read_file(path, &buf); - - if (size < 0) - { - RARCH_ERR("Failed to load state from \"%s\".\n", path); - return false; - } - - bool ret = true; - RARCH_LOG("State size: %u bytes.\n", (unsigned)size); - - void *block_buf[2] = {NULL, NULL}; - int block_type[2] = {-1, -1}; - size_t block_size[2] = {0}; - - if (g_settings.block_sram_overwrite) - { - RARCH_LOG("Blocking SRAM overwrite.\n"); - switch (g_extern.game_type) - { - case RARCH_CART_NORMAL: - block_type[0] = RETRO_MEMORY_SAVE_RAM; - block_type[1] = RETRO_MEMORY_RTC; - break; - - case RARCH_CART_BSX: - case RARCH_CART_BSX_SLOTTED: - block_type[0] = RETRO_MEMORY_SNES_BSX_RAM; - block_type[1] = RETRO_MEMORY_SNES_BSX_PRAM; - break; - - case RARCH_CART_SUFAMI: - block_type[0] = RETRO_MEMORY_SNES_SUFAMI_TURBO_A_RAM; - block_type[1] = RETRO_MEMORY_SNES_SUFAMI_TURBO_B_RAM; - break; - - case RARCH_CART_SGB: - block_type[0] = RETRO_MEMORY_SNES_GAME_BOY_RAM; - block_type[1] = RETRO_MEMORY_SNES_GAME_BOY_RTC; - break; - } - } - - for (unsigned i = 0; i < 2; i++) - if (block_type[i] != -1) - block_size[i] = pretro_get_memory_size(block_type[i]); - - for (unsigned i = 0; i < 2; i++) - if (block_size[i]) - block_buf[i] = malloc(block_size[i]); - - // Backup current SRAM which is overwritten by unserialize. - for (unsigned i = 0; i < 2; i++) - { - if (block_buf[i]) - { - const void *ptr = pretro_get_memory_data(block_type[i]); - if (ptr) - memcpy(block_buf[i], ptr, block_size[i]); - } - } - - ret = pretro_unserialize(buf, size); - - // Flush back :D - for (unsigned i = 0; i < 2 && ret; i++) - { - if (block_buf[i]) - { - void *ptr = pretro_get_memory_data(block_type[i]); - if (ptr) - memcpy(ptr, block_buf[i], block_size[i]); - } - } - - for (unsigned i = 0; i < 2; i++) - if (block_buf[i]) - free(block_buf[i]); - - free(buf); - return ret; -} - -void load_ram_file(const char *path, int type) -{ - size_t size = pretro_get_memory_size(type); - void *data = pretro_get_memory_data(type); - - if (size == 0 || !data) - return; - - void *buf = NULL; - ssize_t rc = read_file(path, &buf); - if (rc > 0 && rc <= (ssize_t)size) - memcpy(data, buf, rc); - - free(buf); -} - -void save_ram_file(const char *path, int type) -{ - size_t size = pretro_get_memory_size(type); - void *data = pretro_get_memory_data(type); - - if (data && size > 0) - { - if (!dump_to_file(path, data, size)) - { - RARCH_ERR("Failed to save SRAM.\n"); - RARCH_WARN("Attempting to recover ...\n"); - dump_to_file_desperate(data, size, type); - } - } -} - -static char *load_xml_map(const char *path) -{ - char *xml_buf = NULL; - if (*path) - { - if (read_file_string(path, &xml_buf)) - RARCH_LOG("Found XML memory map in \"%s\"\n", path); - } - - return xml_buf; -} - -#define MAX_ROMS 4 - -static bool load_roms(unsigned rom_type, const char **rom_paths, size_t roms) -{ - bool ret = true; - - if (roms == 0) - return false; - - if (roms > MAX_ROMS) - return false; - - void *rom_buf[MAX_ROMS] = {NULL}; - ssize_t rom_len[MAX_ROMS] = {0}; - struct retro_game_info info[MAX_ROMS] = {{NULL}}; - - if (!g_extern.system.info.need_fullpath) - { - if ((rom_len[0] = read_rom_file(g_extern.rom_file, &rom_buf[0])) == -1) - { - RARCH_ERR("Could not read ROM file.\n"); - return false; - } - - if (g_extern.rom_file) - fclose(g_extern.rom_file); - - RARCH_LOG("ROM size: %u bytes.\n", (unsigned)rom_len[0]); - } - else - { - if (!g_extern.rom_file) - { - RARCH_ERR("Implementation requires a full path to be set, cannot load ROM from stdin. Aborting ...\n"); - return false; - } - - fclose(g_extern.rom_file); - RARCH_LOG("ROM loading skipped. Implementation will load it on its own.\n"); - } - - char *xml_buf = load_xml_map(g_extern.xml_name); - - info[0].path = rom_paths[0]; - info[0].data = rom_buf[0]; - info[0].size = rom_len[0]; - info[0].meta = xml_buf; - - for (size_t i = 1; i < roms; i++) - { - if (rom_paths[i] && - !g_extern.system.info.need_fullpath && - (rom_len[i] = read_file(rom_paths[i], &rom_buf[i])) == -1) - { - RARCH_ERR("Could not read ROM file: \"%s\".\n", rom_paths[i]); - ret = false; - goto end; - } - - info[i].path = rom_paths[i]; - info[i].data = rom_buf[i]; - info[i].size = rom_len[i]; - } - - if (rom_type == 0) - ret = pretro_load_game(&info[0]); - else - ret = pretro_load_game_special(rom_type, info, roms); - - if (!ret) - RARCH_ERR("Failed to load game.\n"); - -end: - for (unsigned i = 0; i < MAX_ROMS; i++) - free(rom_buf[i]); - free(xml_buf); - - return ret; -} - -static bool load_normal_rom(void) -{ - const char *path = *g_extern.fullpath ? g_extern.fullpath : NULL; - return load_roms(0, &path, 1); -} - -static bool load_sgb_rom(void) -{ - const char *path[2] = { - *g_extern.fullpath ? g_extern.fullpath : NULL, - g_extern.gb_rom_path - }; - - return load_roms(RETRO_GAME_TYPE_SUPER_GAME_BOY, path, 2); -} - -static bool load_bsx_rom(bool slotted) -{ - const char *path[2] = { - *g_extern.fullpath ? g_extern.fullpath : NULL, - g_extern.bsx_rom_path - }; - - return load_roms(slotted ? RETRO_GAME_TYPE_BSX_SLOTTED : RETRO_GAME_TYPE_BSX, path, 2); -} - -static bool load_sufami_rom(void) -{ - const char *path[3] = { - *g_extern.fullpath ? g_extern.fullpath : NULL, - *g_extern.sufami_rom_path[0] ? g_extern.sufami_rom_path[0] : NULL, - *g_extern.sufami_rom_path[1] ? g_extern.sufami_rom_path[1] : NULL, - }; - - return load_roms(RETRO_GAME_TYPE_SUFAMI_TURBO, path, 3); -} - -bool init_rom_file(enum rarch_game_type type) -{ - switch (type) - { - case RARCH_CART_SGB: - if (!load_sgb_rom()) - return false; - break; - - case RARCH_CART_NORMAL: - if (!load_normal_rom()) - return false; - break; - - case RARCH_CART_BSX: - if (!load_bsx_rom(false)) - return false; - break; - - case RARCH_CART_BSX_SLOTTED: - if (!load_bsx_rom(true)) - return false; - break; - - case RARCH_CART_SUFAMI: - if (!load_sufami_rom()) - return false; - break; - - default: - RARCH_ERR("Invalid ROM type.\n"); - return false; - } - - return true; -} - +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2012 - Hans-Kristian Arntzen + * + * 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 "file.h" +#include "general.h" +#include +#include "boolean.h" +#include "libretro.h" +#include +#include +#include "dynamic.h" +#include "movie.h" +#include "patch.h" +#include "compat/strl.h" +#include "hash.h" + +#if defined(_WIN32) && !defined(_XBOX) +#include +#include +#include +#elif defined(_XBOX) +#include +#define setmode _setmode +#define INVALID_FILE_ATTRIBUTES -1 +#endif + +// Generic file loader. +ssize_t read_file(const char *path, void **buf) +{ + void *rom_buf = NULL; + FILE *file = fopen(path, "rb"); + ssize_t rc = 0; + size_t len = 0; + if (!file) + goto error; + + fseek(file, 0, SEEK_END); + len = ftell(file); + rewind(file); + rom_buf = malloc(len + 1); + if (!rom_buf) + { + RARCH_ERR("Couldn't allocate memory.\n"); + goto error; + } + + if ((rc = fread(rom_buf, 1, len, file)) < (ssize_t)len) + RARCH_WARN("Didn't read whole file.\n"); + + *buf = rom_buf; + // Allow for easy reading of strings to be safe. + // Will only work with sane character formatting (Unix). + ((char*)rom_buf)[len] = '\0'; + fclose(file); + return rc; + +error: + if (file) + fclose(file); + free(rom_buf); + *buf = NULL; + return -1; +} + +// Reads file content as one string. +bool read_file_string(const char *path, char **buf) +{ + *buf = NULL; + FILE *file = fopen(path, "r"); + size_t len = 0; + char *ptr = NULL; + + if (!file) + goto error; + + fseek(file, 0, SEEK_END); + len = ftell(file) + 2; // Takes account of being able to read in EOF and '\0' at end. + rewind(file); + + *buf = (char*)calloc(len, sizeof(char)); + if (!*buf) + goto error; + + ptr = *buf; + + while (ptr && !feof(file)) + { + size_t bufsize = (size_t)(((ptrdiff_t)*buf + (ptrdiff_t)len) - (ptrdiff_t)ptr); + fgets(ptr, bufsize, file); + + ptr += strlen(ptr); + } + + ptr = strchr(ptr, EOF); + if (ptr) + *ptr = '\0'; + + fclose(file); + return true; + +error: + if (file) + fclose(file); + if (*buf) + free(*buf); + return false; +} + +static void patch_rom(uint8_t **buf, ssize_t *size) +{ + uint8_t *ret_buf = *buf; + ssize_t ret_size = *size; + + const char *patch_desc = NULL; + const char *patch_path = NULL; + patch_error_t err = PATCH_UNKNOWN; + patch_func_t func = NULL; + + ssize_t patch_size = 0; + void *patch_data = NULL; + bool success = false; + + if (g_extern.ups_pref + g_extern.bps_pref + g_extern.ips_pref > 1) + { + RARCH_WARN("Several patches are explicitly defined, ignoring all ...\n"); + return; + } + + bool allow_bps = !g_extern.ups_pref && !g_extern.ips_pref; + bool allow_ups = !g_extern.bps_pref && !g_extern.ips_pref; + bool allow_ips = !g_extern.ups_pref && !g_extern.bps_pref; + + if (allow_ups && *g_extern.ups_name && (patch_size = read_file(g_extern.ups_name, &patch_data)) >= 0) + { + patch_desc = "UPS"; + patch_path = g_extern.ups_name; + func = ups_apply_patch; + } + else if (allow_bps && *g_extern.bps_name && (patch_size = read_file(g_extern.bps_name, &patch_data)) >= 0) + { + patch_desc = "BPS"; + patch_path = g_extern.bps_name; + func = bps_apply_patch; + } + else if (allow_ips && *g_extern.ips_name && (patch_size = read_file(g_extern.ips_name, &patch_data)) >= 0) + { + patch_desc = "IPS"; + patch_path = g_extern.ips_name; + func = ips_apply_patch; + } + else + { + RARCH_LOG("Did not find a valid ROM patch.\n"); + return; + } + + RARCH_LOG("Found %s file in \"%s\", attempting to patch ...\n", patch_desc, patch_path); + + size_t target_size = ret_size * 4; // Just to be sure ... + uint8_t *patched_rom = (uint8_t*)malloc(target_size); + if (!patched_rom) + { + RARCH_ERR("Failed to allocate memory for patched ROM ...\n"); + goto error; + } + + err = func((const uint8_t*)patch_data, patch_size, ret_buf, ret_size, patched_rom, &target_size); + if (err == PATCH_SUCCESS) + { + RARCH_LOG("ROM patched successfully (%s).\n", patch_desc); + success = true; + } + else + RARCH_ERR("Failed to patch %s: Error #%u\n", patch_desc, (unsigned)err); + + if (success) + { + free(ret_buf); + *buf = patched_rom; + *size = target_size; + } + + if (patch_data) + free(patch_data); + + return; + +error: + *buf = ret_buf; + *size = ret_size; + if (patch_data) + free(patch_data); +} + +// Load SNES rom only. Applies a hack for headered ROMs. +static ssize_t read_rom_file(FILE *file, void **buf) +{ + ssize_t ret = 0; + uint8_t *ret_buf = NULL; + + if (file == NULL) // stdin + { +#if defined(_WIN32) && !defined(_XBOX) + _setmode(0, O_BINARY); +#endif + + RARCH_LOG("Reading ROM from stdin ...\n"); + size_t buf_size = 0xFFFFF; // Some initial guesstimate. + size_t buf_ptr = 0; + uint8_t *rom_buf = (uint8_t*)malloc(buf_size); + if (rom_buf == NULL) + { + RARCH_ERR("Couldn't allocate memory.\n"); + return -1; + } + + for (;;) + { + size_t ret = fread(rom_buf + buf_ptr, 1, buf_size - buf_ptr, stdin); + buf_ptr += ret; + + // We've reached the end + if (buf_ptr < buf_size) + break; + + rom_buf = (uint8_t*)realloc(rom_buf, buf_size * 2); + if (rom_buf == NULL) + { + RARCH_ERR("Couldn't allocate memory.\n"); + return -1; + } + + buf_size *= 2; + } + + ret_buf = rom_buf; + ret = buf_ptr; + } + else + { + fseek(file, 0, SEEK_END); + ret = ftell(file); + rewind(file); + + void *rom_buf = malloc(ret); + if (rom_buf == NULL) + { + RARCH_ERR("Couldn't allocate memory.\n"); + return -1; + } + + if (fread(rom_buf, 1, ret, file) < (size_t)ret) + { + RARCH_ERR("Didn't read whole file.\n"); + free(rom_buf); + return -1; + } + + ret_buf = (uint8_t*)rom_buf; + } + + if (!g_extern.block_patch) + { + // Attempt to apply a patch. + patch_rom(&ret_buf, &ret); + } + + // Remove copier header if present (512 first bytes). + if ((ret & 0x7fff) == 512) + { + memmove(ret_buf, ret_buf + 512, ret - 512); + ret -= 512; + } + + g_extern.cart_crc = crc32_calculate(ret_buf, ret); +#ifdef HAVE_XML + sha256_hash(g_extern.sha256, ret_buf, ret); + RARCH_LOG("SHA256 sum: %s\n", g_extern.sha256); +#endif + *buf = ret_buf; + return ret; +} + + +// Dump stuff to file. +static bool dump_to_file(const char *path, const void *data, size_t size) +{ + FILE *file = fopen(path, "wb"); + if (!file) + return false; + else + { + bool ret = fwrite(data, 1, size, file) == size; + fclose(file); + return ret; + } +} + +static const char *ramtype2str(int type) +{ + switch (type) + { + case RETRO_MEMORY_SAVE_RAM: + case RETRO_MEMORY_SNES_GAME_BOY_RAM: + case RETRO_MEMORY_SNES_BSX_RAM: + return ".srm"; + + case RETRO_MEMORY_RTC: + case RETRO_MEMORY_SNES_GAME_BOY_RTC: + return ".rtc"; + + case RETRO_MEMORY_SNES_BSX_PRAM: + return ".pram"; + + case RETRO_MEMORY_SNES_SUFAMI_TURBO_A_RAM: + return ".aram"; + case RETRO_MEMORY_SNES_SUFAMI_TURBO_B_RAM: + return ".bram"; + + default: + return ""; + } +} + +// Attempt to save valuable RAM data somewhere ... +static void dump_to_file_desperate(const void *data, size_t size, int type) +{ +#if defined(_WIN32) && !defined(_XBOX) + const char *base = getenv("APPDATA"); +#elif defined(__CELLOS_LV2__) || defined(_XBOX) + const char *base = NULL; +#else + const char *base = getenv("HOME"); +#endif + + if (!base) + goto error; + + char path[PATH_MAX]; + snprintf(path, sizeof(path), "%s/RetroArch-recovery-", base); + char timebuf[PATH_MAX]; + + time_t time_; + time(&time_); + strftime(timebuf, sizeof(timebuf), "%Y-%m-%d-%H-%M-%S", localtime(&time_)); + strlcat(path, timebuf, sizeof(path)); + strlcat(path, ramtype2str(type), sizeof(path)); + + if (dump_to_file(path, data, size)) + RARCH_WARN("Succeeded in saving RAM data to \"%s\".\n", path); + else + goto error; + + return; + +error: + RARCH_WARN("Failed ... Cannot recover save file.\n"); +} + +bool save_state(const char *path) +{ + RARCH_LOG("Saving state: \"%s\".\n", path); + size_t size = pretro_serialize_size(); + if (size == 0) + return false; + + void *data = malloc(size); + if (!data) + { + RARCH_ERR("Failed to allocate memory for save state buffer.\n"); + return false; + } + + RARCH_LOG("State size: %d bytes.\n", (int)size); + bool ret = pretro_serialize(data, size); + if (ret) + ret = dump_to_file(path, data, size); + + if (!ret) + RARCH_ERR("Failed to save state to \"%s\".\n", path); + + free(data); + return ret; +} + +bool load_state(const char *path) +{ + RARCH_LOG("Loading state: \"%s\".\n", path); + void *buf = NULL; + ssize_t size = read_file(path, &buf); + + if (size < 0) + { + RARCH_ERR("Failed to load state from \"%s\".\n", path); + return false; + } + + bool ret = true; + RARCH_LOG("State size: %u bytes.\n", (unsigned)size); + + void *block_buf[2] = {NULL, NULL}; + int block_type[2] = {-1, -1}; + size_t block_size[2] = {0}; + + if (g_settings.block_sram_overwrite) + { + RARCH_LOG("Blocking SRAM overwrite.\n"); + switch (g_extern.game_type) + { + case RARCH_CART_NORMAL: + block_type[0] = RETRO_MEMORY_SAVE_RAM; + block_type[1] = RETRO_MEMORY_RTC; + break; + + case RARCH_CART_BSX: + case RARCH_CART_BSX_SLOTTED: + block_type[0] = RETRO_MEMORY_SNES_BSX_RAM; + block_type[1] = RETRO_MEMORY_SNES_BSX_PRAM; + break; + + case RARCH_CART_SUFAMI: + block_type[0] = RETRO_MEMORY_SNES_SUFAMI_TURBO_A_RAM; + block_type[1] = RETRO_MEMORY_SNES_SUFAMI_TURBO_B_RAM; + break; + + case RARCH_CART_SGB: + block_type[0] = RETRO_MEMORY_SNES_GAME_BOY_RAM; + block_type[1] = RETRO_MEMORY_SNES_GAME_BOY_RTC; + break; + } + } + + for (unsigned i = 0; i < 2; i++) + if (block_type[i] != -1) + block_size[i] = pretro_get_memory_size(block_type[i]); + + for (unsigned i = 0; i < 2; i++) + if (block_size[i]) + block_buf[i] = malloc(block_size[i]); + + // Backup current SRAM which is overwritten by unserialize. + for (unsigned i = 0; i < 2; i++) + { + if (block_buf[i]) + { + const void *ptr = pretro_get_memory_data(block_type[i]); + if (ptr) + memcpy(block_buf[i], ptr, block_size[i]); + } + } + + ret = pretro_unserialize(buf, size); + + // Flush back :D + for (unsigned i = 0; i < 2 && ret; i++) + { + if (block_buf[i]) + { + void *ptr = pretro_get_memory_data(block_type[i]); + if (ptr) + memcpy(ptr, block_buf[i], block_size[i]); + } + } + + for (unsigned i = 0; i < 2; i++) + if (block_buf[i]) + free(block_buf[i]); + + free(buf); + return ret; +} + +void load_ram_file(const char *path, int type) +{ + size_t size = pretro_get_memory_size(type); + void *data = pretro_get_memory_data(type); + + if (size == 0 || !data) + return; + + void *buf = NULL; + ssize_t rc = read_file(path, &buf); + if (rc > 0 && rc <= (ssize_t)size) + memcpy(data, buf, rc); + + free(buf); +} + +void save_ram_file(const char *path, int type) +{ + size_t size = pretro_get_memory_size(type); + void *data = pretro_get_memory_data(type); + + if (data && size > 0) + { + if (!dump_to_file(path, data, size)) + { + RARCH_ERR("Failed to save SRAM.\n"); + RARCH_WARN("Attempting to recover ...\n"); + dump_to_file_desperate(data, size, type); + } + } +} + +static char *load_xml_map(const char *path) +{ + char *xml_buf = NULL; + if (*path) + { + if (read_file_string(path, &xml_buf)) + RARCH_LOG("Found XML memory map in \"%s\"\n", path); + } + + return xml_buf; +} + +#define MAX_ROMS 4 + +static bool load_roms(unsigned rom_type, const char **rom_paths, size_t roms) +{ + bool ret = true; + + if (roms == 0) + return false; + + if (roms > MAX_ROMS) + return false; + + void *rom_buf[MAX_ROMS] = {NULL}; + ssize_t rom_len[MAX_ROMS] = {0}; + struct retro_game_info info[MAX_ROMS] = {{NULL}}; + + if (!g_extern.system.info.need_fullpath) + { + if ((rom_len[0] = read_rom_file(g_extern.rom_file, &rom_buf[0])) == -1) + { + RARCH_ERR("Could not read ROM file.\n"); + return false; + } + + if (g_extern.rom_file) + fclose(g_extern.rom_file); + + RARCH_LOG("ROM size: %u bytes.\n", (unsigned)rom_len[0]); + } + else + { + if (!g_extern.rom_file) + { + RARCH_ERR("Implementation requires a full path to be set, cannot load ROM from stdin. Aborting ...\n"); + return false; + } + + fclose(g_extern.rom_file); + RARCH_LOG("ROM loading skipped. Implementation will load it on its own.\n"); + } + + char *xml_buf = load_xml_map(g_extern.xml_name); + + info[0].path = rom_paths[0]; + info[0].data = rom_buf[0]; + info[0].size = rom_len[0]; + info[0].meta = xml_buf; + + for (size_t i = 1; i < roms; i++) + { + if (rom_paths[i] && + !g_extern.system.info.need_fullpath && + (rom_len[i] = read_file(rom_paths[i], &rom_buf[i])) == -1) + { + RARCH_ERR("Could not read ROM file: \"%s\".\n", rom_paths[i]); + ret = false; + goto end; + } + + info[i].path = rom_paths[i]; + info[i].data = rom_buf[i]; + info[i].size = rom_len[i]; + } + + if (rom_type == 0) + ret = pretro_load_game(&info[0]); + else + ret = pretro_load_game_special(rom_type, info, roms); + + if (!ret) + RARCH_ERR("Failed to load game.\n"); + +end: + for (unsigned i = 0; i < MAX_ROMS; i++) + free(rom_buf[i]); + free(xml_buf); + + return ret; +} + +static bool load_normal_rom(void) +{ + const char *path = *g_extern.fullpath ? g_extern.fullpath : NULL; + return load_roms(0, &path, 1); +} + +static bool load_sgb_rom(void) +{ + const char *path[2] = { + *g_extern.fullpath ? g_extern.fullpath : NULL, + g_extern.gb_rom_path + }; + + return load_roms(RETRO_GAME_TYPE_SUPER_GAME_BOY, path, 2); +} + +static bool load_bsx_rom(bool slotted) +{ + const char *path[2] = { + *g_extern.fullpath ? g_extern.fullpath : NULL, + g_extern.bsx_rom_path + }; + + return load_roms(slotted ? RETRO_GAME_TYPE_BSX_SLOTTED : RETRO_GAME_TYPE_BSX, path, 2); +} + +static bool load_sufami_rom(void) +{ + const char *path[3] = { + *g_extern.fullpath ? g_extern.fullpath : NULL, + *g_extern.sufami_rom_path[0] ? g_extern.sufami_rom_path[0] : NULL, + *g_extern.sufami_rom_path[1] ? g_extern.sufami_rom_path[1] : NULL, + }; + + return load_roms(RETRO_GAME_TYPE_SUFAMI_TURBO, path, 3); +} + +bool init_rom_file(enum rarch_game_type type) +{ + switch (type) + { + case RARCH_CART_SGB: + if (!load_sgb_rom()) + return false; + break; + + case RARCH_CART_NORMAL: + if (!load_normal_rom()) + return false; + break; + + case RARCH_CART_BSX: + if (!load_bsx_rom(false)) + return false; + break; + + case RARCH_CART_BSX_SLOTTED: + if (!load_bsx_rom(true)) + return false; + break; + + case RARCH_CART_SUFAMI: + if (!load_sufami_rom()) + return false; + break; + + default: + RARCH_ERR("Invalid ROM type.\n"); + return false; + } + + return true; +} + diff --git a/gfx/context/wgl_ctx.c b/gfx/context/wgl_ctx.c index 0914555278..e21b3b4316 100644 --- a/gfx/context/wgl_ctx.c +++ b/gfx/context/wgl_ctx.c @@ -1,456 +1,449 @@ -/* RetroArch - A frontend for libretro. - * Copyright (C) 2010-2012 - Hans-Kristian Arntzen - * Copyright (C) 2011-2012 - Daniel De Matteis - * - * 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 . - */ - -// Win32/WGL context. - -// TODO: Rewrite initializer lists - not supported on MSVC 2010/2012 - -#include "../../driver.h" -#include "../gfx_context.h" -#include "../gl_common.h" -#include "../gfx_common.h" -#include - -#define IDI_ICON 1 -#define MAX_MONITORS 9 - -static HWND g_hwnd; -static HGLRC g_hrc; -static HDC g_hdc; -static HMONITOR g_last_hm; -static HMONITOR g_all_hms[MAX_MONITORS]; -static unsigned g_num_mons; - -static bool g_quit; -static bool g_inited; -static unsigned g_interval; - -static unsigned g_resize_width; -static unsigned g_resize_height; -static bool g_resized; - -static bool g_restore_desktop; - -static void gfx_ctx_get_video_size(unsigned *width, unsigned *height); -static void gfx_ctx_destroy(void); - -static BOOL (APIENTRY *p_swap_interval)(int); - -static void setup_pixel_format(HDC hdc) -{ - PIXELFORMATDESCRIPTOR pfd = {0}; - pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); - pfd.nVersion = 1; - pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; - pfd.iPixelType = PFD_TYPE_RGBA; - pfd.cColorBits = 32; - pfd.cDepthBits = 0; - pfd.cStencilBits = 0; - pfd.iLayerType = PFD_MAIN_PLANE; - - SetPixelFormat(hdc, ChoosePixelFormat(hdc, &pfd), &pfd); -} - -static void create_gl_context(HWND hwnd) -{ - g_hdc = GetDC(hwnd); - setup_pixel_format(g_hdc); - - g_hrc = wglCreateContext(g_hdc); - if (g_hrc) - { - if (wglMakeCurrent(g_hdc, g_hrc)) - g_inited = true; - else - g_quit = true; - } - else - g_quit = true; -} - -static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, - WPARAM wparam, LPARAM lparam) -{ - switch (message) - { - case WM_SYSCOMMAND: - // Prevent screensavers, etc, while running. - switch (wparam) - { - case SC_SCREENSAVE: - case SC_MONITORPOWER: - return 0; - } - break; - - case WM_SYSKEYDOWN: - switch (wparam) - { - case VK_F10: - case VK_MENU: - case VK_RSHIFT: - return 0; - } - break; - - case WM_CREATE: - create_gl_context(hwnd); - return 0; - - case WM_CLOSE: - case WM_DESTROY: - case WM_QUIT: - g_quit = true; - return 0; - - case WM_SIZE: - // Do not send resize message if we minimize. - if (wparam != SIZE_MAXHIDE && wparam != SIZE_MINIMIZED) - { - g_resize_width = LOWORD(lparam); - g_resize_height = HIWORD(lparam); - g_resized = true; - } - return 0; - } - - return DefWindowProc(hwnd, message, wparam, lparam); -} - -static void gfx_ctx_swap_interval(unsigned interval) -{ - g_interval = interval; - - if (g_hrc && p_swap_interval) - { - RARCH_LOG("[WGL]: wglSwapInterval(%u)\n", g_interval); - if (!p_swap_interval(g_interval)) - RARCH_WARN("[WGL]: wglSwapInterval() failed.\n"); - } -} - -static void gfx_ctx_check_window(bool *quit, - bool *resize, unsigned *width, unsigned *height, unsigned frame_count) -{ - (void)frame_count; - - MSG msg; - while (PeekMessage(&msg, g_hwnd, 0, 0, PM_REMOVE)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - - *quit = g_quit; - if (g_resized) - { - *resize = true; - *width = g_resize_width; - *height = g_resize_height; - g_resized = false; - } -} - -static void gfx_ctx_swap_buffers(void) -{ - SwapBuffers(g_hdc); -} - -static void gfx_ctx_set_resize(unsigned width, unsigned height) -{ - (void)width; - (void)height; -} - -static void gfx_ctx_update_window_title(bool reset) -{ - if (reset) - gfx_window_title_reset(); - - char buf[128]; - if (gfx_window_title(buf, sizeof(buf))) - SetWindowText(g_hwnd, buf); -} - -static void gfx_ctx_get_video_size(unsigned *width, unsigned *height) -{ - if (!g_hwnd) - { - RECT screen_rect; - GetClientRect(GetDesktopWindow(), &screen_rect); - *width = screen_rect.right - screen_rect.left; - *height = screen_rect.bottom - screen_rect.top; - } - else - { - *width = g_resize_width; - *height = g_resize_height; - } -} - -BOOL CALLBACK monitor_enum_proc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) -{ - g_all_hms[g_num_mons++] = hMonitor; - return TRUE; -} - -static bool gfx_ctx_init(void) -{ - if (g_inited) - return false; - - g_quit = false; - g_restore_desktop = false; - - g_num_mons = 0; - EnumDisplayMonitors(NULL, NULL, monitor_enum_proc, 0); - - WNDCLASSEX wndclass = {0}; - wndclass.cbSize = sizeof(wndclass); - wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; - wndclass.lpfnWndProc = WndProc; - wndclass.hInstance = GetModuleHandle(NULL); - wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); - wndclass.lpszClassName = "RetroArch"; - wndclass.hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON)); - wndclass.hIconSm = (HICON)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0); - - if (!RegisterClassEx(&wndclass)) - return false; - - return true; -} - -static bool set_fullscreen(unsigned width, unsigned height, char *dev_name) -{ - DEVMODE devmode; - memset(&devmode, 0, sizeof(devmode)); - devmode.dmSize = sizeof(DEVMODE); - devmode.dmPelsWidth = width; - devmode.dmPelsHeight = height; - devmode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT; - - RARCH_LOG("[WGL]: Setting fullscreen to %ux%u on device %s.\n", width, height, dev_name); - return ChangeDisplaySettingsEx(dev_name, &devmode, NULL, CDS_FULLSCREEN, NULL) == DISP_CHANGE_SUCCESSFUL; -} - -static void show_cursor(bool show) -{ - if (show) - while (ShowCursor(TRUE) < 0); - else - while (ShowCursor(FALSE) >= 0); -} - -static bool gfx_ctx_set_video_mode( - unsigned width, unsigned height, - unsigned bits, bool fullscreen) -{ - (void)bits; - - DWORD style; -#if defined(_WIN32) - MONITORINFOEX current_mon; -#else - MONITORINFOEX current_mon = {{0}}; -#endif - current_mon.cbSize = sizeof(MONITORINFOEX); - if (!g_last_hm) - g_last_hm = MonitorFromWindow(GetDesktopWindow(), MONITOR_DEFAULTTONEAREST); - HMONITOR hm_to_use = g_last_hm; - - unsigned fs_monitor = g_settings.video.monitor_index; - if (fs_monitor && fs_monitor <= g_num_mons && g_all_hms[fs_monitor - 1]) - hm_to_use = g_all_hms[fs_monitor - 1]; - - GetMonitorInfo(hm_to_use, (MONITORINFO*)¤t_mon); - - g_resize_width = width; - g_resize_height = height; - - bool windowed_full = g_settings.video.windowed_fullscreen; - if (fullscreen) - { - if (windowed_full) - { - style = WS_EX_TOPMOST | WS_POPUP; - g_resize_width = width = current_mon.rcMonitor.right - current_mon.rcMonitor.left; - g_resize_height = height = current_mon.rcMonitor.bottom - current_mon.rcMonitor.top; - } - else - { - style = WS_POPUP | WS_VISIBLE; - - if (!set_fullscreen(width, height, current_mon.szDevice)) - goto error; - - // display settings might have changed, get new coordinates - GetMonitorInfo(hm_to_use, (MONITORINFO*)¤t_mon); - g_restore_desktop = true; - } - } - else - { - style = WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN; - RECT rect = {0}; - rect.right = width; - rect.bottom = height; - AdjustWindowRect(&rect, style, FALSE); - width = rect.right - rect.left; - height = rect.bottom - rect.top; - } - - g_hwnd = CreateWindowEx(0, "RetroArch", "RetroArch", style, - fullscreen ? current_mon.rcMonitor.left : CW_USEDEFAULT, - fullscreen ? current_mon.rcMonitor.top : CW_USEDEFAULT, - width, height, - NULL, NULL, NULL, NULL); - - if (!g_hwnd) - goto error; - - gfx_ctx_update_window_title(true); - - if (!fullscreen || windowed_full) - { - ShowWindow(g_hwnd, SW_RESTORE); - UpdateWindow(g_hwnd); - SetForegroundWindow(g_hwnd); - SetFocus(g_hwnd); - } - - show_cursor(!fullscreen); - - // Wait until GL context is created (or failed to do so ...) - MSG msg; - while (!g_inited && !g_quit && GetMessage(&msg, g_hwnd, 0, 0)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - - if (g_quit) - goto error; - - p_swap_interval = (BOOL (APIENTRY *)(int))wglGetProcAddress("wglSwapIntervalEXT"); - - gfx_ctx_swap_interval(g_interval); - - driver.display_type = RARCH_DISPLAY_WIN32; - driver.video_display = 0; - driver.video_window = (uintptr_t)g_hwnd; - - return true; - -error: - gfx_ctx_destroy(); - return false; -} - -static void gfx_ctx_destroy(void) -{ - if (g_hrc) - { - wglMakeCurrent(NULL, NULL); - wglDeleteContext(g_hrc); - g_hrc = NULL; - } - - if (g_hwnd && g_hdc) - { - ReleaseDC(g_hwnd, g_hdc); - g_hdc = NULL; - } - - if (g_hwnd) - { - g_last_hm = MonitorFromWindow(g_hwnd, MONITOR_DEFAULTTONEAREST); - DestroyWindow(g_hwnd); - UnregisterClass("RetroArch", GetModuleHandle(NULL)); - g_hwnd = NULL; - } - - if (g_restore_desktop) - { -#if defined(_WIN32) - MONITORINFOEX current_mon; -#else - MONITORINFOEX current_mon = {{0}}; -#endif - current_mon.cbSize = sizeof(MONITORINFOEX); - GetMonitorInfo(g_last_hm, (MONITORINFO*)¤t_mon); - ChangeDisplaySettingsEx(current_mon.szDevice, NULL, NULL, 0, NULL); - g_restore_desktop = false; - } - - g_inited = false; -} - -static void gfx_ctx_input_driver(const input_driver_t **input, void **input_data) -{ - void *dinput = input_dinput.init(); - *input = dinput ? &input_dinput : NULL; - *input_data = dinput; -} - -static bool gfx_ctx_has_focus(void) -{ - if (!g_inited) - return false; - - return GetFocus() == g_hwnd; -} - -static gfx_ctx_proc_t gfx_ctx_get_proc_address(const char *symbol) -{ - return (gfx_ctx_proc_t)wglGetProcAddress(symbol); -} - -static bool gfx_ctx_bind_api(enum gfx_ctx_api api) -{ - return api == GFX_CTX_OPENGL_API; -} - -static bool gfx_ctx_init_egl_image_buffer(const video_info_t *video) -{ - return false; -} - -static bool gfx_ctx_write_egl_image(const void *frame, unsigned width, unsigned height, unsigned pitch, bool rgb32, unsigned index, void **image_handle) -{ - return false; -} - -const gfx_ctx_driver_t gfx_ctx_wgl = { - gfx_ctx_init, - gfx_ctx_destroy, - gfx_ctx_bind_api, - gfx_ctx_swap_interval, - gfx_ctx_set_video_mode, - gfx_ctx_get_video_size, - NULL, - gfx_ctx_update_window_title, - gfx_ctx_check_window, - gfx_ctx_set_resize, - gfx_ctx_has_focus, - gfx_ctx_swap_buffers, - gfx_ctx_input_driver, - gfx_ctx_get_proc_address, - gfx_ctx_init_egl_image_buffer, - gfx_ctx_write_egl_image, - "wgl", -}; - +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2012 - Hans-Kristian Arntzen + * Copyright (C) 2011-2012 - Daniel De Matteis + * + * 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 . + */ + +// Win32/WGL context. + +#include "../../driver.h" +#include "../gfx_context.h" +#include "../gl_common.h" +#include "../gfx_common.h" +#include +#include + +#define IDI_ICON 1 +#define MAX_MONITORS 9 + +static HWND g_hwnd; +static HGLRC g_hrc; +static HDC g_hdc; +static HMONITOR g_last_hm; +static HMONITOR g_all_hms[MAX_MONITORS]; +static unsigned g_num_mons; + +static bool g_quit; +static bool g_inited; +static unsigned g_interval; + +static unsigned g_resize_width; +static unsigned g_resize_height; +static bool g_resized; + +static bool g_restore_desktop; + +static void gfx_ctx_get_video_size(unsigned *width, unsigned *height); +static void gfx_ctx_destroy(void); + +static BOOL (APIENTRY *p_swap_interval)(int); + +static void setup_pixel_format(HDC hdc) +{ + PIXELFORMATDESCRIPTOR pfd = {0}; + pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cColorBits = 32; + pfd.cDepthBits = 0; + pfd.cStencilBits = 0; + pfd.iLayerType = PFD_MAIN_PLANE; + + SetPixelFormat(hdc, ChoosePixelFormat(hdc, &pfd), &pfd); +} + +static void create_gl_context(HWND hwnd) +{ + g_hdc = GetDC(hwnd); + setup_pixel_format(g_hdc); + + g_hrc = wglCreateContext(g_hdc); + if (g_hrc) + { + if (wglMakeCurrent(g_hdc, g_hrc)) + g_inited = true; + else + g_quit = true; + } + else + g_quit = true; +} + +static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, + WPARAM wparam, LPARAM lparam) +{ + switch (message) + { + case WM_SYSCOMMAND: + // Prevent screensavers, etc, while running. + switch (wparam) + { + case SC_SCREENSAVE: + case SC_MONITORPOWER: + return 0; + } + break; + + case WM_SYSKEYDOWN: + switch (wparam) + { + case VK_F10: + case VK_MENU: + case VK_RSHIFT: + return 0; + } + break; + + case WM_CREATE: + create_gl_context(hwnd); + return 0; + + case WM_CLOSE: + case WM_DESTROY: + case WM_QUIT: + g_quit = true; + return 0; + + case WM_SIZE: + // Do not send resize message if we minimize. + if (wparam != SIZE_MAXHIDE && wparam != SIZE_MINIMIZED) + { + g_resize_width = LOWORD(lparam); + g_resize_height = HIWORD(lparam); + g_resized = true; + } + return 0; + } + + return DefWindowProc(hwnd, message, wparam, lparam); +} + +static void gfx_ctx_swap_interval(unsigned interval) +{ + g_interval = interval; + + if (g_hrc && p_swap_interval) + { + RARCH_LOG("[WGL]: wglSwapInterval(%u)\n", g_interval); + if (!p_swap_interval(g_interval)) + RARCH_WARN("[WGL]: wglSwapInterval() failed.\n"); + } +} + +static void gfx_ctx_check_window(bool *quit, + bool *resize, unsigned *width, unsigned *height, unsigned frame_count) +{ + (void)frame_count; + + MSG msg; + while (PeekMessage(&msg, g_hwnd, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + *quit = g_quit; + if (g_resized) + { + *resize = true; + *width = g_resize_width; + *height = g_resize_height; + g_resized = false; + } +} + +static void gfx_ctx_swap_buffers(void) +{ + SwapBuffers(g_hdc); +} + +static void gfx_ctx_set_resize(unsigned width, unsigned height) +{ + (void)width; + (void)height; +} + +static void gfx_ctx_update_window_title(bool reset) +{ + if (reset) + gfx_window_title_reset(); + + char buf[128]; + if (gfx_window_title(buf, sizeof(buf))) + SetWindowText(g_hwnd, buf); +} + +static void gfx_ctx_get_video_size(unsigned *width, unsigned *height) +{ + if (!g_hwnd) + { + RECT screen_rect; + GetClientRect(GetDesktopWindow(), &screen_rect); + *width = screen_rect.right - screen_rect.left; + *height = screen_rect.bottom - screen_rect.top; + } + else + { + *width = g_resize_width; + *height = g_resize_height; + } +} + +BOOL CALLBACK monitor_enum_proc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) +{ + g_all_hms[g_num_mons++] = hMonitor; + return TRUE; +} + +static bool gfx_ctx_init(void) +{ + if (g_inited) + return false; + + g_quit = false; + g_restore_desktop = false; + + g_num_mons = 0; + EnumDisplayMonitors(NULL, NULL, monitor_enum_proc, 0); + + WNDCLASSEX wndclass = {0}; + wndclass.cbSize = sizeof(wndclass); + wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wndclass.lpfnWndProc = WndProc; + wndclass.hInstance = GetModuleHandle(NULL); + wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); + wndclass.lpszClassName = "RetroArch"; + wndclass.hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON)); + wndclass.hIconSm = (HICON)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0); + + if (!RegisterClassEx(&wndclass)) + return false; + + return true; +} + +static bool set_fullscreen(unsigned width, unsigned height, char *dev_name) +{ + DEVMODE devmode; + memset(&devmode, 0, sizeof(devmode)); + devmode.dmSize = sizeof(DEVMODE); + devmode.dmPelsWidth = width; + devmode.dmPelsHeight = height; + devmode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT; + + RARCH_LOG("[WGL]: Setting fullscreen to %ux%u on device %s.\n", width, height, dev_name); + return ChangeDisplaySettingsEx(dev_name, &devmode, NULL, CDS_FULLSCREEN, NULL) == DISP_CHANGE_SUCCESSFUL; +} + +static void show_cursor(bool show) +{ + if (show) + while (ShowCursor(TRUE) < 0); + else + while (ShowCursor(FALSE) >= 0); +} + +static bool gfx_ctx_set_video_mode( + unsigned width, unsigned height, + unsigned bits, bool fullscreen) +{ + (void)bits; + + DWORD style; + MONITORINFOEX current_mon; + memset(¤t_mon, 0, sizeof(current_mon)); + current_mon.cbSize = sizeof(MONITORINFOEX); + if (!g_last_hm) + g_last_hm = MonitorFromWindow(GetDesktopWindow(), MONITOR_DEFAULTTONEAREST); + HMONITOR hm_to_use = g_last_hm; + + unsigned fs_monitor = g_settings.video.monitor_index; + if (fs_monitor && fs_monitor <= g_num_mons && g_all_hms[fs_monitor - 1]) + hm_to_use = g_all_hms[fs_monitor - 1]; + + GetMonitorInfo(hm_to_use, (MONITORINFO*)¤t_mon); + + g_resize_width = width; + g_resize_height = height; + + bool windowed_full = g_settings.video.windowed_fullscreen; + if (fullscreen) + { + if (windowed_full) + { + style = WS_EX_TOPMOST | WS_POPUP; + g_resize_width = width = current_mon.rcMonitor.right - current_mon.rcMonitor.left; + g_resize_height = height = current_mon.rcMonitor.bottom - current_mon.rcMonitor.top; + } + else + { + style = WS_POPUP | WS_VISIBLE; + + if (!set_fullscreen(width, height, current_mon.szDevice)) + goto error; + + // display settings might have changed, get new coordinates + GetMonitorInfo(hm_to_use, (MONITORINFO*)¤t_mon); + g_restore_desktop = true; + } + } + else + { + style = WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN; + RECT rect = {0}; + rect.right = width; + rect.bottom = height; + AdjustWindowRect(&rect, style, FALSE); + width = rect.right - rect.left; + height = rect.bottom - rect.top; + } + + g_hwnd = CreateWindowEx(0, "RetroArch", "RetroArch", style, + fullscreen ? current_mon.rcMonitor.left : CW_USEDEFAULT, + fullscreen ? current_mon.rcMonitor.top : CW_USEDEFAULT, + width, height, + NULL, NULL, NULL, NULL); + + if (!g_hwnd) + goto error; + + gfx_ctx_update_window_title(true); + + if (!fullscreen || windowed_full) + { + ShowWindow(g_hwnd, SW_RESTORE); + UpdateWindow(g_hwnd); + SetForegroundWindow(g_hwnd); + SetFocus(g_hwnd); + } + + show_cursor(!fullscreen); + + // Wait until GL context is created (or failed to do so ...) + MSG msg; + while (!g_inited && !g_quit && GetMessage(&msg, g_hwnd, 0, 0)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + if (g_quit) + goto error; + + p_swap_interval = (BOOL (APIENTRY *)(int))wglGetProcAddress("wglSwapIntervalEXT"); + + gfx_ctx_swap_interval(g_interval); + + driver.display_type = RARCH_DISPLAY_WIN32; + driver.video_display = 0; + driver.video_window = (uintptr_t)g_hwnd; + + return true; + +error: + gfx_ctx_destroy(); + return false; +} + +static void gfx_ctx_destroy(void) +{ + if (g_hrc) + { + wglMakeCurrent(NULL, NULL); + wglDeleteContext(g_hrc); + g_hrc = NULL; + } + + if (g_hwnd && g_hdc) + { + ReleaseDC(g_hwnd, g_hdc); + g_hdc = NULL; + } + + if (g_hwnd) + { + g_last_hm = MonitorFromWindow(g_hwnd, MONITOR_DEFAULTTONEAREST); + DestroyWindow(g_hwnd); + UnregisterClass("RetroArch", GetModuleHandle(NULL)); + g_hwnd = NULL; + } + + if (g_restore_desktop) + { + MONITORINFOEX current_mon; + memset(¤t_mon, 0, sizeof(current_mon)); + current_mon.cbSize = sizeof(MONITORINFOEX); + GetMonitorInfo(g_last_hm, (MONITORINFO*)¤t_mon); + ChangeDisplaySettingsEx(current_mon.szDevice, NULL, NULL, 0, NULL); + g_restore_desktop = false; + } + + g_inited = false; +} + +static void gfx_ctx_input_driver(const input_driver_t **input, void **input_data) +{ + void *dinput = input_dinput.init(); + *input = dinput ? &input_dinput : NULL; + *input_data = dinput; +} + +static bool gfx_ctx_has_focus(void) +{ + if (!g_inited) + return false; + + return GetFocus() == g_hwnd; +} + +static gfx_ctx_proc_t gfx_ctx_get_proc_address(const char *symbol) +{ + return (gfx_ctx_proc_t)wglGetProcAddress(symbol); +} + +static bool gfx_ctx_bind_api(enum gfx_ctx_api api) +{ + return api == GFX_CTX_OPENGL_API; +} + +static bool gfx_ctx_init_egl_image_buffer(const video_info_t *video) +{ + return false; +} + +static bool gfx_ctx_write_egl_image(const void *frame, unsigned width, unsigned height, unsigned pitch, bool rgb32, unsigned index, void **image_handle) +{ + return false; +} + +const gfx_ctx_driver_t gfx_ctx_wgl = { + gfx_ctx_init, + gfx_ctx_destroy, + gfx_ctx_bind_api, + gfx_ctx_swap_interval, + gfx_ctx_set_video_mode, + gfx_ctx_get_video_size, + NULL, + gfx_ctx_update_window_title, + gfx_ctx_check_window, + gfx_ctx_set_resize, + gfx_ctx_has_focus, + gfx_ctx_swap_buffers, + gfx_ctx_input_driver, + gfx_ctx_get_proc_address, + gfx_ctx_init_egl_image_buffer, + gfx_ctx_write_egl_image, + "wgl", +}; + diff --git a/gfx/scaler/scaler_int.c b/gfx/scaler/scaler_int.c index 1faa0b8e17..4b3e6c7959 100644 --- a/gfx/scaler/scaler_int.c +++ b/gfx/scaler/scaler_int.c @@ -1,275 +1,275 @@ -/* RetroArch - A frontend for libretro. - * Copyright (C) 2010-2012 - Hans-Kristian Arntzen - * - * 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 "scaler_int.h" - -#ifdef SCALER_NO_SIMD -#undef __SSE2__ -#endif - -#if defined(__SSE2__) -#include -#endif - -static inline uint64_t build_argb64(uint16_t a, uint16_t r, uint16_t g, uint16_t b) -{ - return ((uint64_t)a << 48) | ((uint64_t)r << 32) | ((uint64_t)g << 16) | ((uint64_t)b << 0); -} - -static inline uint8_t clamp_8bit(int16_t col) -{ - if (col > 255) - return 255; - else if (col < 0) - return 0; - else - return (uint8_t)col; -} - -// ARGB8888 scaler is split in two: -// -// First, horizontal scaler is applied. -// Here, all 8-bit channels are expanded to 16-bit. Values are then shifted 7 to left to occupy 15 bits. -// The sign bit is kept empty as we have to do signed multiplication for the filter. -// A mulhi [(a * b) >> 16] is applied which loses some precision, but is very efficient for SIMD. -// It is accurate enough for 8-bit purposes. -// -// The fixed point 1.0 for filter is (1 << 14). After horizontal scale, the output is kept -// with 16-bit channels, and will now have 13 bits of precision as [(a * (1 << 14)) >> 16] is effectively a right shift by 2. -// -// Vertical scaler takes the 13 bit channels, and performs the same mulhi steps. -// Another 2 bits of precision is lost, which ends up as 11 bits. -// Scaling is now complete. Channels are shifted right by 3, and saturated into 8-bit values. -// -// The C version of scalers perform the exact same operations as the SIMD code for testing purposes. - -#if defined(__SSE2__) -void scaler_argb8888_vert(const struct scaler_ctx *ctx, void *output_, int stride) -{ - const uint64_t *input = ctx->scaled.frame; - uint32_t *output = (uint32_t*)output_; - - const int16_t *filter_vert = ctx->vert.filter; - - for (int h = 0; h < ctx->out_height; h++, filter_vert += ctx->vert.filter_stride, output += stride >> 2) - { - const uint64_t *input_base = input + ctx->vert.filter_pos[h] * (ctx->scaled.stride >> 3); - - for (int w = 0; w < ctx->out_width; w++) - { - __m128i res = _mm_setzero_si128(); - - const uint64_t *input_base_y = input_base + w; - - size_t y; - for (y = 0; (y + 1) < ctx->vert.filter_len; y += 2, input_base_y += (ctx->scaled.stride >> 2)) - { - __m128i coeff = _mm_set_epi64x(filter_vert[y + 1] * 0x0001000100010001ll, filter_vert[y + 0] * 0x0001000100010001ll); - __m128i col = _mm_set_epi64x(input_base_y[ctx->scaled.stride >> 3], input_base_y[0]); - - res = _mm_adds_epi16(_mm_mulhi_epi16(col, coeff), res); - } - - for (; y < ctx->vert.filter_len; y++, input_base_y += (ctx->scaled.stride >> 3)) - { - __m128i coeff = _mm_set_epi64x(0, filter_vert[y] * 0x0001000100010001ll); - __m128i col = _mm_set_epi64x(0, input_base_y[0]); - - res = _mm_adds_epi16(_mm_mulhi_epi16(col, coeff), res); - } - - res = _mm_adds_epi16(_mm_srli_si128(res, 8), res); - res = _mm_srai_epi16(res, (7 - 2 - 2)); - - __m128i final = _mm_packus_epi16(res, res); - - output[w] = _mm_cvtsi128_si32(final); - } - } -} -#else -void scaler_argb8888_vert(const struct scaler_ctx *ctx, void *output_, int stride) -{ - const uint64_t *input = ctx->scaled.frame; - uint32_t *output = (uint32_t*)output_; - - const int16_t *filter_vert = ctx->vert.filter; - - for (int h = 0; h < ctx->out_height; h++, filter_vert += ctx->vert.filter_stride, output += stride >> 2) - { - const uint64_t *input_base = input + ctx->vert.filter_pos[h] * (ctx->scaled.stride >> 3); - - for (int w = 0; w < ctx->out_width; w++) - { - int16_t res_a = 0; - int16_t res_r = 0; - int16_t res_g = 0; - int16_t res_b = 0; - - const uint64_t *input_base_y = input_base + w; - for (size_t y = 0; y < ctx->vert.filter_len; y++, input_base_y += (ctx->scaled.stride >> 3)) - { - uint64_t col = *input_base_y; - - int16_t a = (col >> 48) & 0xffff; - int16_t r = (col >> 32) & 0xffff; - int16_t g = (col >> 16) & 0xffff; - int16_t b = (col >> 0) & 0xffff; - - int16_t coeff = filter_vert[y]; - - res_a += (a * coeff) >> 16; - res_r += (r * coeff) >> 16; - res_g += (g * coeff) >> 16; - res_b += (b * coeff) >> 16; - } - - res_a >>= (7 - 2 - 2); - res_r >>= (7 - 2 - 2); - res_g >>= (7 - 2 - 2); - res_b >>= (7 - 2 - 2); - - output[w] = (clamp_8bit(res_a) << 24) | (clamp_8bit(res_r) << 16) | (clamp_8bit(res_g) << 8) | (clamp_8bit(res_b) << 0); - } - } -} -#endif - -#if defined(__SSE2__) -void scaler_argb8888_horiz(const struct scaler_ctx *ctx, const void *input_, int stride) -{ - const uint32_t *input = (const uint32_t*)input_; - uint64_t *output = ctx->scaled.frame; - - for (int h = 0; h < ctx->scaled.height; h++, input += stride >> 2, output += ctx->scaled.stride >> 3) - { - const int16_t *filter_horiz = ctx->horiz.filter; - - for (int w = 0; w < ctx->scaled.width; w++, filter_horiz += ctx->horiz.filter_stride) - { - __m128i res = _mm_setzero_si128(); - - const uint32_t *input_base_x = input + ctx->horiz.filter_pos[w]; - - size_t x; - for (x = 0; (x + 1) < ctx->horiz.filter_len; x += 2) - { - __m128i coeff = _mm_set_epi64x(filter_horiz[x + 1] * 0x0001000100010001ll, filter_horiz[x + 0] * 0x0001000100010001ll); - - __m128i col = _mm_unpacklo_epi8(_mm_set_epi64x(0, - ((uint64_t)input_base_x[x + 1] << 32) | input_base_x[x + 0]), _mm_setzero_si128()); - - col = _mm_slli_epi16(col, 7); - res = _mm_adds_epi16(_mm_mulhi_epi16(col, coeff), res); - } - - for (; x < ctx->horiz.filter_len; x++) - { - __m128i coeff = _mm_set_epi64x(0, filter_horiz[x] * 0x0001000100010001ll); - __m128i col = _mm_unpacklo_epi8(_mm_set_epi32(0, 0, 0, input_base_x[x]), _mm_setzero_si128()); - - col = _mm_slli_epi16(col, 7); - res = _mm_adds_epi16(_mm_mulhi_epi16(col, coeff), res); - } - - res = _mm_adds_epi16(_mm_srli_si128(res, 8), res); - -#ifdef __x86_64__ - output[w] = _mm_cvtsi128_si64(res); -#else // 32-bit doesn't have si64. Do it in two steps. - union - { - uint32_t *u32; - uint64_t *u64; - } u; - u.u64 = output + w; - u.u32[0] = _mm_cvtsi128_si32(res); - u.u32[1] = _mm_cvtsi128_si32(_mm_srli_si128(res, 4)); -#endif - } - } -} -#else -void scaler_argb8888_horiz(const struct scaler_ctx *ctx, const void *input_, int stride) -{ - const uint32_t *input = (uint32_t*)input_; - uint64_t *output = ctx->scaled.frame; - - for (int h = 0; h < ctx->scaled.height; h++, input += stride >> 2, output += ctx->scaled.stride >> 3) - { - const int16_t *filter_horiz = ctx->horiz.filter; - - for (int w = 0; w < ctx->scaled.width; w++, filter_horiz += ctx->horiz.filter_stride) - { - const uint32_t *input_base_x = input + ctx->horiz.filter_pos[w]; - - int16_t res_a = 0; - int16_t res_r = 0; - int16_t res_g = 0; - int16_t res_b = 0; - - for (size_t x = 0; x < ctx->horiz.filter_len; x++) - { - uint32_t col = input_base_x[x]; - - int16_t a = (col >> (24 - 7)) & (0xff << 7); - int16_t r = (col >> (16 - 7)) & (0xff << 7); - int16_t g = (col >> ( 8 - 7)) & (0xff << 7); - int16_t b = (col << ( 0 + 7)) & (0xff << 7); - - int16_t coeff = filter_horiz[x]; - - res_a += (a * coeff) >> 16; - res_r += (r * coeff) >> 16; - res_g += (g * coeff) >> 16; - res_b += (b * coeff) >> 16; - } - - output[w] = build_argb64(res_a, res_r, res_g, res_b); - } - } -} -#endif - -void scaler_argb8888_point_special(const struct scaler_ctx *ctx, - void *output_, const void *input_, - int out_width, int out_height, - int in_width, int in_height, - int out_stride, int in_stride) -{ - (void)ctx; - int x_pos = (1 << 15) * in_width / out_width - (1 << 15); - int x_step = (1 << 16) * in_width / out_width; - int y_pos = (1 << 15) * in_height / out_height - (1 << 15); - int y_step = (1 << 16) * in_height / out_height; - - if (x_pos < 0) - x_pos = 0; - if (y_pos < 0) - y_pos = 0; - - const uint32_t *input = (const uint32_t*)input_; - uint32_t *output = (uint32_t*)output_; - - for (int h = 0; h < out_height; h++, y_pos += y_step, output += out_stride >> 2) - { - int x = x_pos; - const uint32_t *inp = input + (y_pos >> 16) * (in_stride >> 2); - - for (int w = 0; w < out_width; w++, x += x_step) - output[w] = inp[x >> 16]; - } -} - +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2012 - Hans-Kristian Arntzen + * + * 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 "scaler_int.h" + +#ifdef SCALER_NO_SIMD +#undef __SSE2__ +#endif + +#if defined(__SSE2__) +#include +#endif + +static inline uint64_t build_argb64(uint16_t a, uint16_t r, uint16_t g, uint16_t b) +{ + return ((uint64_t)a << 48) | ((uint64_t)r << 32) | ((uint64_t)g << 16) | ((uint64_t)b << 0); +} + +static inline uint8_t clamp_8bit(int16_t col) +{ + if (col > 255) + return 255; + else if (col < 0) + return 0; + else + return (uint8_t)col; +} + +// ARGB8888 scaler is split in two: +// +// First, horizontal scaler is applied. +// Here, all 8-bit channels are expanded to 16-bit. Values are then shifted 7 to left to occupy 15 bits. +// The sign bit is kept empty as we have to do signed multiplication for the filter. +// A mulhi [(a * b) >> 16] is applied which loses some precision, but is very efficient for SIMD. +// It is accurate enough for 8-bit purposes. +// +// The fixed point 1.0 for filter is (1 << 14). After horizontal scale, the output is kept +// with 16-bit channels, and will now have 13 bits of precision as [(a * (1 << 14)) >> 16] is effectively a right shift by 2. +// +// Vertical scaler takes the 13 bit channels, and performs the same mulhi steps. +// Another 2 bits of precision is lost, which ends up as 11 bits. +// Scaling is now complete. Channels are shifted right by 3, and saturated into 8-bit values. +// +// The C version of scalers perform the exact same operations as the SIMD code for testing purposes. + +#if defined(__SSE2__) +void scaler_argb8888_vert(const struct scaler_ctx *ctx, void *output_, int stride) +{ + const uint64_t *input = ctx->scaled.frame; + uint32_t *output = (uint32_t*)output_; + + const int16_t *filter_vert = ctx->vert.filter; + + for (int h = 0; h < ctx->out_height; h++, filter_vert += ctx->vert.filter_stride, output += stride >> 2) + { + const uint64_t *input_base = input + ctx->vert.filter_pos[h] * (ctx->scaled.stride >> 3); + + for (int w = 0; w < ctx->out_width; w++) + { + __m128i res = _mm_setzero_si128(); + + const uint64_t *input_base_y = input_base + w; + + size_t y; + for (y = 0; (y + 1) < ctx->vert.filter_len; y += 2, input_base_y += (ctx->scaled.stride >> 2)) + { + __m128i coeff = _mm_set_epi64x(filter_vert[y + 1] * 0x0001000100010001ll, filter_vert[y + 0] * 0x0001000100010001ll); + __m128i col = _mm_set_epi64x(input_base_y[ctx->scaled.stride >> 3], input_base_y[0]); + + res = _mm_adds_epi16(_mm_mulhi_epi16(col, coeff), res); + } + + for (; y < ctx->vert.filter_len; y++, input_base_y += (ctx->scaled.stride >> 3)) + { + __m128i coeff = _mm_set_epi64x(0, filter_vert[y] * 0x0001000100010001ll); + __m128i col = _mm_set_epi64x(0, input_base_y[0]); + + res = _mm_adds_epi16(_mm_mulhi_epi16(col, coeff), res); + } + + res = _mm_adds_epi16(_mm_srli_si128(res, 8), res); + res = _mm_srai_epi16(res, (7 - 2 - 2)); + + __m128i final = _mm_packus_epi16(res, res); + + output[w] = _mm_cvtsi128_si32(final); + } + } +} +#else +void scaler_argb8888_vert(const struct scaler_ctx *ctx, void *output_, int stride) +{ + const uint64_t *input = ctx->scaled.frame; + uint32_t *output = (uint32_t*)output_; + + const int16_t *filter_vert = ctx->vert.filter; + + for (int h = 0; h < ctx->out_height; h++, filter_vert += ctx->vert.filter_stride, output += stride >> 2) + { + const uint64_t *input_base = input + ctx->vert.filter_pos[h] * (ctx->scaled.stride >> 3); + + for (int w = 0; w < ctx->out_width; w++) + { + int16_t res_a = 0; + int16_t res_r = 0; + int16_t res_g = 0; + int16_t res_b = 0; + + const uint64_t *input_base_y = input_base + w; + for (size_t y = 0; y < ctx->vert.filter_len; y++, input_base_y += (ctx->scaled.stride >> 3)) + { + uint64_t col = *input_base_y; + + int16_t a = (col >> 48) & 0xffff; + int16_t r = (col >> 32) & 0xffff; + int16_t g = (col >> 16) & 0xffff; + int16_t b = (col >> 0) & 0xffff; + + int16_t coeff = filter_vert[y]; + + res_a += (a * coeff) >> 16; + res_r += (r * coeff) >> 16; + res_g += (g * coeff) >> 16; + res_b += (b * coeff) >> 16; + } + + res_a >>= (7 - 2 - 2); + res_r >>= (7 - 2 - 2); + res_g >>= (7 - 2 - 2); + res_b >>= (7 - 2 - 2); + + output[w] = (clamp_8bit(res_a) << 24) | (clamp_8bit(res_r) << 16) | (clamp_8bit(res_g) << 8) | (clamp_8bit(res_b) << 0); + } + } +} +#endif + +#if defined(__SSE2__) +void scaler_argb8888_horiz(const struct scaler_ctx *ctx, const void *input_, int stride) +{ + const uint32_t *input = (const uint32_t*)input_; + uint64_t *output = ctx->scaled.frame; + + for (int h = 0; h < ctx->scaled.height; h++, input += stride >> 2, output += ctx->scaled.stride >> 3) + { + const int16_t *filter_horiz = ctx->horiz.filter; + + for (int w = 0; w < ctx->scaled.width; w++, filter_horiz += ctx->horiz.filter_stride) + { + __m128i res = _mm_setzero_si128(); + + const uint32_t *input_base_x = input + ctx->horiz.filter_pos[w]; + + size_t x; + for (x = 0; (x + 1) < ctx->horiz.filter_len; x += 2) + { + __m128i coeff = _mm_set_epi64x(filter_horiz[x + 1] * 0x0001000100010001ll, filter_horiz[x + 0] * 0x0001000100010001ll); + + __m128i col = _mm_unpacklo_epi8(_mm_set_epi64x(0, + ((uint64_t)input_base_x[x + 1] << 32) | input_base_x[x + 0]), _mm_setzero_si128()); + + col = _mm_slli_epi16(col, 7); + res = _mm_adds_epi16(_mm_mulhi_epi16(col, coeff), res); + } + + for (; x < ctx->horiz.filter_len; x++) + { + __m128i coeff = _mm_set_epi64x(0, filter_horiz[x] * 0x0001000100010001ll); + __m128i col = _mm_unpacklo_epi8(_mm_set_epi32(0, 0, 0, input_base_x[x]), _mm_setzero_si128()); + + col = _mm_slli_epi16(col, 7); + res = _mm_adds_epi16(_mm_mulhi_epi16(col, coeff), res); + } + + res = _mm_adds_epi16(_mm_srli_si128(res, 8), res); + +#ifdef __x86_64__ + output[w] = _mm_cvtsi128_si64(res); +#else // 32-bit doesn't have si64. Do it in two steps. + union + { + uint32_t *u32; + uint64_t *u64; + } u; + u.u64 = output + w; + u.u32[0] = _mm_cvtsi128_si32(res); + u.u32[1] = _mm_cvtsi128_si32(_mm_srli_si128(res, 4)); +#endif + } + } +} +#else +void scaler_argb8888_horiz(const struct scaler_ctx *ctx, const void *input_, int stride) +{ + const uint32_t *input = (uint32_t*)input_; + uint64_t *output = ctx->scaled.frame; + + for (int h = 0; h < ctx->scaled.height; h++, input += stride >> 2, output += ctx->scaled.stride >> 3) + { + const int16_t *filter_horiz = ctx->horiz.filter; + + for (int w = 0; w < ctx->scaled.width; w++, filter_horiz += ctx->horiz.filter_stride) + { + const uint32_t *input_base_x = input + ctx->horiz.filter_pos[w]; + + int16_t res_a = 0; + int16_t res_r = 0; + int16_t res_g = 0; + int16_t res_b = 0; + + for (size_t x = 0; x < ctx->horiz.filter_len; x++) + { + uint32_t col = input_base_x[x]; + + int16_t a = (col >> (24 - 7)) & (0xff << 7); + int16_t r = (col >> (16 - 7)) & (0xff << 7); + int16_t g = (col >> ( 8 - 7)) & (0xff << 7); + int16_t b = (col << ( 0 + 7)) & (0xff << 7); + + int16_t coeff = filter_horiz[x]; + + res_a += (a * coeff) >> 16; + res_r += (r * coeff) >> 16; + res_g += (g * coeff) >> 16; + res_b += (b * coeff) >> 16; + } + + output[w] = build_argb64(res_a, res_r, res_g, res_b); + } + } +} +#endif + +void scaler_argb8888_point_special(const struct scaler_ctx *ctx, + void *output_, const void *input_, + int out_width, int out_height, + int in_width, int in_height, + int out_stride, int in_stride) +{ + (void)ctx; + int x_pos = (1 << 15) * in_width / out_width - (1 << 15); + int x_step = (1 << 16) * in_width / out_width; + int y_pos = (1 << 15) * in_height / out_height - (1 << 15); + int y_step = (1 << 16) * in_height / out_height; + + if (x_pos < 0) + x_pos = 0; + if (y_pos < 0) + y_pos = 0; + + const uint32_t *input = (const uint32_t*)input_; + uint32_t *output = (uint32_t*)output_; + + for (int h = 0; h < out_height; h++, y_pos += y_step, output += out_stride >> 2) + { + int x = x_pos; + const uint32_t *inp = input + (y_pos >> 16) * (in_stride >> 2); + + for (int w = 0; w < out_width; w++, x += x_step) + output[w] = inp[x >> 16]; + } +} +