RetroArch/frontend/drivers/platform_wiiu.c
Nikos Chantziaras 430baf7c21
Add Linux GameMode support (#13339)
This can fix a lot of performance issues, like audio crackling and frame
time spikes. This requires the GameMode package to be installed. See:

https://github.com/FeralInteractive/gamemode

This commit adds a "Game Mode" bool option to the "Power
Management" and "Latency" settings sections, and it can be toggled
on/off without restarting RA.

The actual toggling of game mode happens in a new frontend platform
interface function. Perhaps this will become useful for other platforms
that provide some equivalent of Linux GameMode.

Since the GameMode ABI is fixed, and the API comes as a single,
header-only file with no actual deps, we simply bundle the header
(deps/feralgamemode/gamemode_client.h.) That way, all Linux builds will
have support for GameMode regardless of whether the GameMode development
package is installed or not.
2021-12-14 14:07:42 +01:00

647 lines
17 KiB
C

/* RetroArch - A frontend for libretro.
* Copyright (C) 2014-2016 - Ali Bouhlel
* Copyright (C) 2011-2017 - 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 <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/iosupport.h>
#include <net/net_compat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <wiiu/types.h>
#include <wiiu/ac.h>
#include <file/file_path.h>
#ifndef IS_SALAMANDER
#include <lists/file_list.h>
#endif
#include <string/stdstring.h>
#include <wiiu/gx2.h>
#include <wiiu/kpad.h>
#include <wiiu/ios.h>
#include <wiiu/os.h>
#include <wiiu/procui.h>
#include <wiiu/sysapp.h>
#include "file_path_special.h"
#include "../frontend.h"
#include "../frontend_driver.h"
#include "../../defaults.h"
#include "../../paths.h"
#include "../../retroarch.h"
#include "../../verbosity.h"
#include "hbl.h"
#include "wiiu_dbg.h"
#include "system/exception_handler.h"
#include "tasks/tasks_internal.h"
#ifndef IS_SALAMANDER
#ifdef HAVE_MENU
#include "../../menu/menu_driver.h"
#endif
#endif
#define WIIU_SD_PATH "sd:/"
#define WIIU_USB_PATH "usb:/"
#define WIIU_STORAGE_USB_PATH "storage_usb:/"
/**
* The Wii U frontend driver, along with the main() method.
*/
#ifndef IS_SALAMANDER
static enum frontend_fork wiiu_fork_mode = FRONTEND_FORK_NONE;
#endif
static const char *elf_path_cst = WIIU_SD_PATH "retroarch/retroarch.elf";
static bool exists(char *path)
{
struct stat stat_buf = {0};
if (!path)
return false;
return (stat(path, &stat_buf) == 0);
}
static void fix_asset_directory(void)
{
char src_path_buf[PATH_MAX_LENGTH] = {0};
char dst_path_buf[PATH_MAX_LENGTH] = {0};
fill_pathname_join(src_path_buf, g_defaults.dirs[DEFAULT_DIR_PORT], "media", sizeof(g_defaults.dirs[DEFAULT_DIR_PORT]));
fill_pathname_join(dst_path_buf, g_defaults.dirs[DEFAULT_DIR_PORT], "assets", sizeof(g_defaults.dirs[DEFAULT_DIR_PORT]));
if (exists(dst_path_buf) || !exists(src_path_buf))
return;
rename(src_path_buf, dst_path_buf);
}
static void frontend_wiiu_get_env_settings(int *argc, char *argv[],
void *args, void *params_data)
{
fill_pathname_basedir(g_defaults.dirs[DEFAULT_DIR_PORT], elf_path_cst, sizeof(g_defaults.dirs[DEFAULT_DIR_PORT]));
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CORE_ASSETS], g_defaults.dirs[DEFAULT_DIR_PORT],
"downloads", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE_ASSETS]));
fix_asset_directory();
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_ASSETS], g_defaults.dirs[DEFAULT_DIR_PORT],
"assets", sizeof(g_defaults.dirs[DEFAULT_DIR_ASSETS]));
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CORE], g_defaults.dirs[DEFAULT_DIR_PORT],
"cores", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE]));
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CORE_INFO], g_defaults.dirs[DEFAULT_DIR_CORE],
"info", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE_INFO]));
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_SAVESTATE], g_defaults.dirs[DEFAULT_DIR_CORE],
"savestates", sizeof(g_defaults.dirs[DEFAULT_DIR_SAVESTATE]));
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_SRAM], g_defaults.dirs[DEFAULT_DIR_CORE],
"savefiles", sizeof(g_defaults.dirs[DEFAULT_DIR_SRAM]));
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_SYSTEM], g_defaults.dirs[DEFAULT_DIR_CORE],
"system", sizeof(g_defaults.dirs[DEFAULT_DIR_SYSTEM]));
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_PLAYLIST], g_defaults.dirs[DEFAULT_DIR_CORE],
"playlists", sizeof(g_defaults.dirs[DEFAULT_DIR_PLAYLIST]));
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_MENU_CONFIG], g_defaults.dirs[DEFAULT_DIR_PORT],
"config", sizeof(g_defaults.dirs[DEFAULT_DIR_MENU_CONFIG]));
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_REMAP], g_defaults.dirs[DEFAULT_DIR_PORT],
"config/remaps", sizeof(g_defaults.dirs[DEFAULT_DIR_REMAP]));
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_VIDEO_FILTER], g_defaults.dirs[DEFAULT_DIR_PORT],
"filters", sizeof(g_defaults.dirs[DEFAULT_DIR_VIDEO_FILTER]));
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_DATABASE], g_defaults.dirs[DEFAULT_DIR_PORT],
"database/rdb", sizeof(g_defaults.dirs[DEFAULT_DIR_DATABASE]));
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CURSOR], g_defaults.dirs[DEFAULT_DIR_PORT],
"database/cursors", sizeof(g_defaults.dirs[DEFAULT_DIR_CURSOR]));
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_LOGS], g_defaults.dirs[DEFAULT_DIR_CORE],
"logs", sizeof(g_defaults.dirs[DEFAULT_DIR_LOGS]));
fill_pathname_join(g_defaults.path_config, g_defaults.dirs[DEFAULT_DIR_PORT],
FILE_PATH_MAIN_CONFIG, sizeof(g_defaults.path_config));
#ifndef IS_SALAMANDER
dir_check_defaults("custom.ini");
#endif
}
static void frontend_wiiu_deinit(void *data)
{
(void)data;
}
static void frontend_wiiu_shutdown(bool unused)
{
(void)unused;
}
static void frontend_wiiu_init(void *data)
{
(void)data;
DEBUG_LINE();
verbosity_enable();
DEBUG_LINE();
}
static int frontend_wiiu_get_rating(void) { return 10; }
enum frontend_architecture frontend_wiiu_get_arch(void)
{
return FRONTEND_ARCH_PPC;
}
static int frontend_wiiu_parse_drive_list(void *data, bool load_content)
{
#ifndef IS_SALAMANDER
file_list_t *list = (file_list_t *)data;
enum msg_hash_enums enum_idx = load_content ?
MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR :
MENU_ENUM_LABEL_FILE_BROWSER_DIRECTORY;
if (!list)
return -1;
menu_entries_append_enum(list, WIIU_SD_PATH,
msg_hash_to_str(MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR),
enum_idx,
FILE_TYPE_DIRECTORY, 0, 0);
menu_entries_append_enum(list, WIIU_USB_PATH,
msg_hash_to_str(MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR),
enum_idx,
FILE_TYPE_DIRECTORY, 0, 0);
menu_entries_append_enum(list, WIIU_STORAGE_USB_PATH,
msg_hash_to_str(MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR),
enum_idx,
FILE_TYPE_DIRECTORY, 0, 0);
#endif
return 0;
}
static void frontend_wiiu_exec(const char *path, bool should_load_content)
{
struct
{
u32 magic;
u32 argc;
char * argv[3];
char args[];
}*param = getApplicationEndAddr();
int len = 0;
param->argc = 0;
if (!path || !*path)
{
RARCH_LOG("No executable path provided, cannot Restart\n");
}
DEBUG_STR(path);
strcpy(param->args + len, elf_path_cst);
param->argv[param->argc] = param->args + len;
len += strlen(param->args + len) + 1;
param->argc++;
RARCH_LOG("Attempt to load core: [%s].\n", path);
#ifndef IS_SALAMANDER
if (should_load_content && !path_is_empty(RARCH_PATH_CONTENT))
{
strcpy(param->args + len, path_get(RARCH_PATH_CONTENT));
param->argv[param->argc] = param->args + len;
len += strlen(param->args + len) + 1;
param->argc++;
RARCH_LOG("content path: [%s].\n", path_get(RARCH_PATH_CONTENT));
}
#endif
param->argv[param->argc] = NULL;
{
if (HBL_loadToMemory(path, (u32)param->args - (u32)param + len) < 0)
RARCH_LOG("Failed to load core\n");
else
{
param->magic = ARGV_MAGIC;
ARGV_PTR = param;
DEBUG_VAR(param->argc);
DEBUG_VAR(param->argv);
}
}
}
#ifndef IS_SALAMANDER
static bool frontend_wiiu_set_fork(enum frontend_fork fork_mode)
{
switch (fork_mode)
{
case FRONTEND_FORK_CORE:
RARCH_LOG("FRONTEND_FORK_CORE\n");
wiiu_fork_mode = fork_mode;
break;
case FRONTEND_FORK_CORE_WITH_ARGS:
RARCH_LOG("FRONTEND_FORK_CORE_WITH_ARGS\n");
wiiu_fork_mode = fork_mode;
break;
case FRONTEND_FORK_RESTART:
RARCH_LOG("FRONTEND_FORK_RESTART\n");
/* NOTE: We don't implement Salamander, so just turn
* this into FRONTEND_FORK_CORE. */
wiiu_fork_mode = FRONTEND_FORK_CORE;
break;
case FRONTEND_FORK_NONE:
default:
return false;
}
return true;
}
#endif
static void frontend_wiiu_exitspawn(char *s, size_t len, char *args)
{
bool should_load_content = false;
#ifndef IS_SALAMANDER
if (wiiu_fork_mode == FRONTEND_FORK_NONE)
return;
switch (wiiu_fork_mode)
{
case FRONTEND_FORK_CORE_WITH_ARGS:
should_load_content = true;
break;
default:
break;
}
#endif
frontend_wiiu_exec(s, should_load_content);
}
frontend_ctx_driver_t frontend_ctx_wiiu =
{
frontend_wiiu_get_env_settings,
frontend_wiiu_init,
frontend_wiiu_deinit,
frontend_wiiu_exitspawn,
NULL, /* process_args */
frontend_wiiu_exec,
#ifdef IS_SALAMANDER
NULL, /* set_fork */
#else
frontend_wiiu_set_fork,
#endif
frontend_wiiu_shutdown,
NULL, /* get_name */
NULL, /* get_os */
frontend_wiiu_get_rating,
NULL, /* content_loaded */
frontend_wiiu_get_arch, /* get_architecture */
NULL, /* get_powerstate */
frontend_wiiu_parse_drive_list,
NULL, /* get_total_mem */
NULL, /* get_free_mem */
NULL, /* install_signal_handler */
NULL, /* get_signal_handler_state */
NULL, /* set_signal_handler_state */
NULL, /* destroy_signal_handler_state */
NULL, /* attach_console */
NULL, /* detach_console */
NULL, /* get_lakka_version */
NULL, /* set_screen_brightness */
NULL, /* watch_path_for_changes */
NULL, /* check_for_path_changes */
NULL, /* set_sustained_performance_mode */
NULL, /* get_cpu_model_name */
NULL, /* get_user_language */
NULL, /* is_narrator_running */
NULL, /* accessibility_speak */
NULL, /* set_gamemode */
"wiiu", /* ident */
NULL /* get_video_driver */
};
/* main() and its supporting functions */
static void main_setup(void);
static void get_arguments(int *argc, char ***argv);
#ifndef IS_SALAMANDER
static void main_loop(void);
#endif
static void main_teardown(void);
static void init_network(void);
static void deinit_network(void);
static void init_logging(void);
static void deinit_logging(void);
static void wiiu_log_init(int port);
static void wiiu_log_deinit(void);
static ssize_t wiiu_log_write(struct _reent *r, void *fd, const char *ptr, size_t len);
static void init_pad_libraries(void);
static void deinit_pad_libraries(void);
static void SaveCallback(void);
static struct sockaddr_in broadcast;
static int wiiu_log_socket = -1;
static volatile int wiiu_log_lock = 0;
#if !defined(PC_DEVELOPMENT_TCP_PORT)
#define PC_DEVELOPMENT_TCP_PORT 4405
#endif
static devoptab_t dotab_stdout =
{
"stdout_net", /* device name */
0, /* size of file structure */
NULL, /* device open */
NULL, /* device close */
wiiu_log_write, /* device write */
NULL, /* ... */
};
int main(int argc, char **argv)
{
main_setup();
get_arguments(&argc, &argv);
#ifdef IS_SALAMANDER
int salamander_main(int argc, char **argv);
salamander_main(argc, argv);
#else
rarch_main(argc, argv, NULL);
main_loop();
main_exit(NULL);
#endif /* IS_SALAMANDER */
main_teardown();
/* We always return 0 because if we don't, it can prevent loading a
* different RPX/ELF in HBL. */
return 0;
}
static void get_arguments(int *argc, char ***argv)
{
DEBUG_VAR(ARGV_PTR);
if (ARGV_PTR && ((u32)ARGV_PTR < 0x01000000))
{
struct
{
u32 magic;
u32 argc;
char *argv[3];
} *param = ARGV_PTR;
if (param->magic == ARGV_MAGIC)
{
*argc = param->argc;
*argv = param->argv;
}
ARGV_PTR = NULL;
}
DEBUG_VAR(argc);
DEBUG_VAR(argv[0]);
DEBUG_VAR(argv[1]);
fflush(stdout);
}
static void main_setup(void)
{
setup_os_exceptions();
ProcUIInit(&SaveCallback);
init_network();
init_logging();
init_pad_libraries();
verbosity_enable();
fflush(stdout);
}
static void main_teardown(void)
{
deinit_pad_libraries();
ProcUIShutdown();
deinit_logging();
deinit_network();
}
#ifndef IS_SALAMANDER
static bool swap_is_pending(void *start_time)
{
uint32_t swap_count, flip_count;
OSTime last_flip, last_vsync;
GX2GetSwapStatus(&swap_count, &flip_count, &last_flip, &last_vsync);
return last_vsync < *(OSTime *)start_time;
}
static void main_loop(void)
{
OSTime start_time;
int status;
for (;;)
{
if (video_driver_get_ptr())
{
start_time = OSGetSystemTime();
task_queue_wait(swap_is_pending, &start_time);
}
else
task_queue_wait(NULL, NULL);
status = runloop_iterate();
if (status == -1)
break;
}
}
#endif
static void SaveCallback(void)
{
OSSavesDone_ReadyToRelease();
}
static void init_network(void)
{
ACInitialize();
ACConnect();
#ifdef IS_SALAMANDER
socket_lib_init();
#else
network_init();
#endif /* IS_SALAMANDER */
}
static void deinit_network(void)
{
ACClose();
ACFinalize();
}
int getBroadcastAddress(ACIpAddress *broadcast)
{
ACIpAddress myIp, mySubnet;
ACResult result;
if (!broadcast)
return -1;
result = ACGetAssignedAddress(&myIp);
if (result < 0)
return -1;
result = ACGetAssignedSubnet(&mySubnet);
if (result < 0)
return -1;
*broadcast = myIp | (~mySubnet);
return 0;
}
static void init_logging(void)
{
wiiu_log_init(PC_DEVELOPMENT_TCP_PORT);
devoptab_list[STD_OUT] = &dotab_stdout;
devoptab_list[STD_ERR] = &dotab_stdout;
}
static void deinit_logging(void)
{
fflush(stdout);
fflush(stderr);
wiiu_log_deinit();
}
static int broadcast_init(int port)
{
ACIpAddress broadcast_ip;
if (getBroadcastAddress(&broadcast_ip) < 0)
return -1;
memset(&broadcast, 0, sizeof(broadcast));
broadcast.sin_family = AF_INET;
broadcast.sin_port = htons(port);
broadcast.sin_addr.s_addr = htonl(broadcast_ip);
return 0;
}
static void wiiu_log_init(int port)
{
wiiu_log_lock = 0;
if (wiiu_log_socket >= 0)
return;
if (broadcast_init(port) < 0)
return;
wiiu_log_socket = socket(AF_INET, SOCK_DGRAM, 0);
if (wiiu_log_socket < 0)
return;
struct sockaddr_in connect_addr;
memset(&connect_addr, 0, sizeof(connect_addr));
connect_addr.sin_family = AF_INET;
connect_addr.sin_port = 0;
connect_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if ( bind(wiiu_log_socket, (struct sockaddr *)&connect_addr, sizeof(connect_addr)) < 0)
{
socketclose(wiiu_log_socket);
wiiu_log_socket = -1;
return;
}
}
static void wiiu_log_deinit(void)
{
if (wiiu_log_socket >= 0)
{
socketclose(wiiu_log_socket);
wiiu_log_socket = -1;
}
}
static void init_pad_libraries(void)
{
#ifndef IS_SALAMANDER
KPADInit();
WPADEnableURCC(true);
WPADEnableWiiRemote(true);
#endif /* IS_SALAMANDER */
}
static void deinit_pad_libraries(void)
{
#ifndef IS_SALAMANDER
KPADShutdown();
#endif /* IS_SALAMANDER */
}
/* logging routines */
void net_print(const char *str)
{
wiiu_log_write(NULL, 0, str, strlen(str));
}
void net_print_exp(const char *str)
{
sendto(wiiu_log_socket, str, strlen(str), 0, (struct sockaddr *)&broadcast, sizeof(broadcast));
}
/* RFC 791 specifies that any IP host must be able
* to receive a datagram of 576 bytes.
* Since we're generally never logging more than a
* line or two's worth of data (~100 bytes)
* this is a reasonable size for our use. */
#define DGRAM_SIZE 576
static ssize_t wiiu_log_write(struct _reent *r,
void *fd, const char *ptr, size_t len)
{
int remaining;
if (wiiu_log_socket < 0)
return len;
while (wiiu_log_lock)
OSSleepTicks(((248625000 / 4)) / 1000);
wiiu_log_lock = 1;
remaining = len;
while (remaining > 0)
{
int block = remaining < DGRAM_SIZE ? remaining : DGRAM_SIZE;
int sent = sendto(wiiu_log_socket, ptr, block, 0,
(struct sockaddr *)&broadcast, sizeof(broadcast));
if (sent < 0)
break;
remaining -= sent;
ptr += sent;
}
wiiu_log_lock = 0;
return len;
}