diff --git a/meson.build b/meson.build index dcf6e441..21c67b17 100644 --- a/meson.build +++ b/meson.build @@ -117,7 +117,7 @@ if sm_check.stderr() != '' warning('Submodule check completed with errors:\n@0@'.format(sm_check.stderr())) endif -static = get_option('static') or (host_machine.system() == 'emscripten') +static = get_option('static') or ['emscripten', 'nx'].contains(host_machine.system()) dep_freetype = dependency('freetype2', required : true, static : static, fallback : ['freetype', 'freetype_dep']) dep_opusfile = dependency('opusfile', required : false, static : static, fallback : ['opusfile', 'opusfile_dep']) @@ -197,6 +197,7 @@ prefer_relpath_systems = [ force_relpath_systems = [ 'emscripten', + 'nx' ] if macos_app_bundle @@ -268,7 +269,7 @@ config.set('TAISEI_BUILDCONF_LOG_FATAL_MSGBOX', ( )) config.set('TAISEI_BUILDCONF_DEBUG_OPENGL', get_option('debug_opengl')) -install_docs = get_option('docs') and host_machine.system() != 'emscripten' +install_docs = get_option('docs') and not ['emscripten', 'nx'].contains(host_machine.system()) if host_machine.system() == 'windows' if install_docs @@ -310,6 +311,7 @@ bindist_deps = [] subdir('misc') subdir('emscripten') +subdir('switch') subdir('external') subdir('resources') subdir('doc') @@ -363,7 +365,7 @@ Summary: ', '.join(enabled_audio_backends), get_option('a_default'), ', '.join(enabled_renderers), - get_option('r_default'), + default_renderer, get_option('shader_transpiler'), enable_zip, package_data, diff --git a/resources/00-taisei.pkgdir/shader/meson.build b/resources/00-taisei.pkgdir/shader/meson.build index f4235b93..80abe613 100644 --- a/resources/00-taisei.pkgdir/shader/meson.build +++ b/resources/00-taisei.pkgdir/shader/meson.build @@ -140,7 +140,7 @@ spvc_vert_args = [ '--stage', 'vert', ] -if host_machine.system() == 'emscripten' +if ['emscripten', 'nx'].contains(host_machine.system()) validate_glsl = 'true' transpile_glsl = true else diff --git a/resources/meson.build b/resources/meson.build index f03e0712..97e9919b 100644 --- a/resources/meson.build +++ b/resources/meson.build @@ -81,6 +81,7 @@ foreach pkg : packages pkg_path, '@OUTPUT@', '--depfile', '@DEPFILE@', + '--exclude', '**/meson.build', ], output : pkg_zip, depfile : '@0@.d'.format(pkg_zip), @@ -94,6 +95,31 @@ foreach pkg : packages endif endforeach +if host_machine.system() == 'nx' + # Package shaders that were transpiled + shader_pkg_zip = '01-es-shaders.zip' + shader_pkg_path = join_paths(shaders_build_dir, '..') + if package_data + bindist_deps += custom_target(shader_pkg_zip, + command : [pack_command, + shader_pkg_path, + '@OUTPUT@', + '--depfile', '@DEPFILE@', + '--exclude', '**/*.spv', + '--exclude', '**/meson.build', + ], + output : shader_pkg_zip, + depfile : '@0@.d'.format(shader_pkg_zip), + install : true, + install_dir : data_path, + ) + else + glob_result = run_command(glob_command, shaders_build_dir, '**/*.spv', '**/meson.build') + assert(glob_result.returncode() == 0, 'Glob script failed') + install_subdir(shaders_build_dir, install_dir : data_path, exclude_files : glob_result.stdout().split('\n')) + endif +endif + if host_machine.system() == 'emscripten' # First add some stuff that isn't sourced from resources/ diff --git a/scripts/pack.py b/scripts/pack.py index 5175f34c..b6236a16 100755 --- a/scripts/pack.py +++ b/scripts/pack.py @@ -24,7 +24,6 @@ from taiseilib.common import ( write_depfile, ) - def pack(args): nocompress_file = args.directory / '.nocompress' @@ -40,7 +39,7 @@ def pack(args): with ZipFile(str(args.output), 'w', ZIP_DEFLATED, **zkwargs) as zf: for path in sorted(args.directory.glob('**/*')): - if path.name[0] == '.' or path.name == 'meson.build': + if path.name[0] == '.' or any(path.match(x) for x in args.exclude): continue relpath = path.relative_to(args.directory) @@ -82,6 +81,12 @@ def main(args): help='the output archive path' ) + parser.add_argument('--exclude', + action='append', + default=[], + help='file exclusion pattern' + ) + add_common_args(parser, depfile=True) args = parser.parse_args(args[1:]) diff --git a/src/arch_switch.c b/src/arch_switch.c new file mode 100644 index 00000000..8ef5ce7a --- /dev/null +++ b/src/arch_switch.c @@ -0,0 +1,107 @@ +/* + * This software is licensed under the terms of the MIT-License + * See COPYING for further information. + * --- + * Copyright (c) 2019, p-sam . + */ + +#include "taisei.h" + +#include "arch_switch.h" + +#include +#include +#include +#include + +#define NX_LOG_FMT(fmt, ...) tsfprintf(stdout, "[NX] " fmt "\n", ##__VA_ARGS__) +#define NX_LOG(str) NX_LOG_FMT("%s", str) +#define NX_SETENV(name, val) NX_LOG_FMT("Setting env var %s to %s", name, val);env_set_string(name, val, true) + +static nxAtExitFn g_nxAtExitFn = NULL; +static char g_programDir[FS_MAX_PATH] = {0}; +static AppletHookCookie g_hookCookie; + +static void onAppletHook(AppletHookType hook, void *param) { + switch (hook) { + case AppletHookType_OnExitRequest: + NX_LOG("Got AppletHook OnExitRequest, exiting.\n"); + taisei_quit(); + break; + + default: + break; + } +} + +attr_used +void userAppInit(void) { + socketInitializeDefault(); + appletLockExit(); + appletHook(&g_hookCookie, onAppletHook, NULL); + +#ifdef DEBUG + dup2(1, 2); + NX_LOG("stderr -> stdout"); + nxlinkStdio(); + NX_LOG("nxlink enabled"); + NX_SETENV("TAISEI_NOASYNC", "1"); +#endif + + appletInitializeGamePlayRecording(); + appletSetGamePlayRecordingState(1); + + getcwd(g_programDir, FS_MAX_PATH); + +#if defined(DEBUG) && defined(TAISEI_BUILDCONF_DEBUG_OPENGL) + // enable Mesa logging: + NX_SETENV("EGL_LOG_LEVEL", "debug"); + NX_SETENV("MESA_VERBOSE", "all"); + NX_SETENV("MESA_DEBUG", "1"); + NX_SETENV("NOUVEAU_MESA_DEBUG", "1"); + + // enable shader debugging in Nouveau: + NX_SETENV("NV50_PROG_OPTIMIZE", "0"); + NX_SETENV("NV50_PROG_DEBUG", "1"); + NX_SETENV("NV50_PROG_CHIPSET", "0x120"); +#else + // disable error checking and save CPU time + NX_SETENV("MESA_NO_ERROR", "1"); +#endif +} + +attr_used +void userAppExit(void) { + if(g_nxAtExitFn != NULL) { + NX_LOG("calling exit callback"); + g_nxAtExitFn(); + g_nxAtExitFn = NULL; + } + socketExit(); + appletUnlockExit(); +} + +int nxAtExit(nxAtExitFn fn) { + if(g_nxAtExitFn == NULL) { + NX_LOG("got exit callback"); + g_nxAtExitFn = fn; + return 0; + } + return -1; +} + +void __attribute__((weak)) noreturn __libnx_exit(int rc); + +void noreturn nxExit(int rc) { + __libnx_exit(rc); +} + +void noreturn nxAbort(void) { + /* Using abort would not give us correct offsets in crash reports, + * nor code region name, so we use __builtin_trap instead */ + __builtin_trap(); +} + +const char* nxGetProgramDir(void) { + return g_programDir; +} diff --git a/src/arch_switch.h b/src/arch_switch.h new file mode 100644 index 00000000..ca1235b5 --- /dev/null +++ b/src/arch_switch.h @@ -0,0 +1,22 @@ +/* + * This software is licensed under the terms of the MIT-License + * See COPYING for further information. + * --- + * Copyright (c) 2019, p-sam . + */ + +#ifndef IGUARD_arch_switch_h +#define IGUARD_arch_switch_h + +#include "taisei.h" + +typedef void (*nxAtExitFn)(void); + +void userAppInit(void); +void userAppExit(void); +int nxAtExit(nxAtExitFn fn); +void noreturn nxExit(int rc); +void noreturn nxAbort(void); +const char* nxGetProgramDir(void); + +#endif // IGUARD_arch_switch_h diff --git a/src/config.c b/src/config.c index 474fb26a..33a2c4f8 100644 --- a/src/config.c +++ b/src/config.c @@ -476,4 +476,9 @@ void config_load(void) { // set config version to the latest config_set_int(CONFIG_VERSION, sizeof(config_upgrades) / sizeof(ConfigUpgradeFunc)); + +#ifdef __SWITCH__ + config_set_int(CONFIG_GAMEPAD_ENABLED, true); + config_set_str(CONFIG_GAMEPAD_DEVICE, "any"); +#endif } diff --git a/src/gamepad.h b/src/gamepad.h index fd483077..f0b53243 100644 --- a/src/gamepad.h +++ b/src/gamepad.h @@ -44,10 +44,17 @@ typedef enum GamepadEmulatedButton { typedef enum GamepadButton { // must match SDL_GameControllerButton GAMEPAD_BUTTON_INVALID = -1, +#ifdef __SWITCH__ + GAMEPAD_BUTTON_B, + GAMEPAD_BUTTON_A, + GAMEPAD_BUTTON_Y, + GAMEPAD_BUTTON_X, +#else GAMEPAD_BUTTON_A, GAMEPAD_BUTTON_B, GAMEPAD_BUTTON_X, GAMEPAD_BUTTON_Y, +#endif GAMEPAD_BUTTON_BACK, GAMEPAD_BUTTON_GUIDE, GAMEPAD_BUTTON_START, diff --git a/src/global.h b/src/global.h index 70fcaa6e..d12536e1 100644 --- a/src/global.h +++ b/src/global.h @@ -50,8 +50,13 @@ enum { // defaults +#ifdef __SWITCH__ + RESX = 1280, + RESY = 720, +#else RESX = 800, RESY = 600, +#endif VIEWPORT_X = 40, VIEWPORT_Y = 20, diff --git a/src/log.c b/src/log.c index 667e19a9..90f260a9 100644 --- a/src/log.c +++ b/src/log.c @@ -91,8 +91,9 @@ noreturn static void log_abort(const char *msg) { } #endif - // abort() doesn't clean up, but it lets us get a backtrace, which is more useful log_shutdown(); + + // abort() doesn't clean up, but it lets us get a backtrace, which is more useful abort(); } diff --git a/src/menu/options.c b/src/menu/options.c index 49a0d58d..371df990 100644 --- a/src/menu/options.c +++ b/src/menu/options.c @@ -164,6 +164,7 @@ static int bind_gpdev_set(OptionBinding *b, int v) { return b->selected; } +#ifndef __SWITCH__ // BT_GamepadDevice: dynamic device list static OptionBinding* bind_gpdevice(int cfgentry) { OptionBinding *bind = bind_new(); @@ -191,6 +192,7 @@ static OptionBinding* bind_stroption(ConfigIndex cfgentry) { return bind; } +#endif // BT_Resolution: super-special binding type for the resolution setting static void bind_resolution_update(OptionBinding *bind) { @@ -590,9 +592,11 @@ static void bind_setvaluerange_fancy(OptionBinding *b, int ma) { } } +#ifndef __SWITCH__ static bool gamepad_enabled_depencence(void) { return config_get_int(CONFIG_GAMEPAD_ENABLED); } +#endif static MenuData* create_options_menu_gamepad_controls(MenuData *parent) { MenuData *m = create_options_menu_base("Gamepad Controls"); @@ -662,6 +666,7 @@ static MenuData* create_options_menu_gamepad(MenuData *parent) { OptionBinding *b; +#ifndef __SWITCH__ add_menu_entry(m, "Enable Gamepad/Joystick support", do_nothing, b = bind_option(CONFIG_GAMEPAD_ENABLED, bind_common_onoff_get, bind_common_onoff_set) ); bind_onoff(b); @@ -669,6 +674,7 @@ static MenuData* create_options_menu_gamepad(MenuData *parent) { add_menu_entry(m, "Device", do_nothing, b = bind_gpdevice(CONFIG_GAMEPAD_DEVICE) ); b->dependence = gamepad_enabled_depencence; +#endif add_menu_separator(m); add_menu_entry(m, "Customize controls…", enter_options_menu_gamepad_controls, NULL); @@ -811,11 +817,13 @@ MenuData* create_options_menu(void) { MenuData *m = create_options_menu_base("Options"); OptionBinding *b; +#ifndef __SWITCH__ add_menu_entry(m, "Player name", do_nothing, b = bind_stroption(CONFIG_PLAYERNAME) ); add_menu_separator(m); +#endif add_menu_entry(m, "Save replays", do_nothing, b = bind_option(CONFIG_SAVE_RPY, bind_common_onoffplus_get, bind_common_onoffplus_set) diff --git a/src/meson.build b/src/meson.build index 47799b08..a7ab3dc7 100644 --- a/src/meson.build +++ b/src/meson.build @@ -97,6 +97,12 @@ else ) endif +if host_machine.system() == 'nx' + taisei_src += files( + 'arch_switch.c', + ) +endif + sse42_src = [] subdir('audio') @@ -242,6 +248,53 @@ if host_machine.system() == 'emscripten' install : true, install_dir : bindir, ) +elif host_machine.system() == 'nx' + taisei_elf_name = '@0@.elf'.format(taisei_basename) + taisei_elf = executable(taisei_elf_name, taisei_src, version_deps, + dependencies : taisei_deps, + c_args : taisei_c_args, + c_pch : 'pch/taisei_pch.h', + install : true, + install_dir : bindir, + override_options: ['strip=false'], + ) + bindist_deps += taisei_elf + + taisei_nacp_name = '@0@.nacp'.format(taisei_basename) + taisei_nacp = custom_target(taisei_nacp_name, + command : [ + find_program('nacptool'), + '--create', + nx_app_title, + nx_app_author, + taisei_version_string, + '@OUTPUT@', + ], + build_by_default : true, + install : false, + output : taisei_nacp_name, + ) + + taisei_nro_name = '@0@.nro'.format(taisei_basename) + taisei_nro = custom_target(taisei_nro_name, + # NOTE: Unfortunately we can't just put 'taisei_elf' directly into the command array. + # Meson then makes an invalid assumption that we are going to execute it ("use as a generator"), + # and aborts because there's no exe wrapper in the cross file (which wouldn't make sense to have). + + command : [ + find_program('elf2nro'), + taisei_elf.full_path(), # workaround for the above issue + '@OUTPUT@', + '--nacp=@0@'.format(taisei_nacp.full_path()), # if we could pass the path in a standalone argument, we could have meson generate an implicit dependency here... + '--icon=@0@'.format(nx_icon_path), + ], + build_by_default : true, + depends : [taisei_elf, taisei_nacp], + install : true, + install_dir : bindir, + output : taisei_nro_name, + ) + bindist_deps += taisei_nro else taisei = executable(taisei_basename, taisei_src, version_deps, dependencies : taisei_deps, diff --git a/src/renderer/meson.build b/src/renderer/meson.build index 6e0cf05c..4a5e284c 100644 --- a/src/renderer/meson.build +++ b/src/renderer/meson.build @@ -1,8 +1,22 @@ +modules = [ + 'gl33', + 'gles20', + 'gles30', + 'null', +] -default_backend = get_option('r_default') +if host_machine.system() == 'nx' + has_forced_renderer = true + forced_renderer = 'gles30' +else + has_forced_renderer = false + forced_renderer = '' +endif -if not get_option('r_@0@'.format(default_backend)) - error('Default renderer \'@0@\' is not enabled. Enable it with -Dr_@0@=true, or set r_default to something else.'.format(default_backend)) +default_renderer = has_forced_renderer ? forced_renderer : get_option('r_default') + +if not has_forced_renderer and not get_option('r_@0@'.format(default_renderer)) + error('Default renderer \'@0@\' is not enabled. Enable it with -Dr_@0@=true, or set r_default to something else.'.format(default_renderer)) endif renderer_src = files( @@ -21,19 +35,13 @@ subdir('glescommon') subdir('gles20') subdir('gles30') -modules = [ - 'gl33', - 'gles20', - 'gles30', - 'null', -] - included_deps = [] needed_deps = ['common'] r_macro = [] foreach m : modules - if get_option('r_@0@'.format(m)) + should_include = has_forced_renderer ? m == forced_renderer : get_option('r_@0@'.format(m)) + if should_include renderer_src += get_variable('r_@0@_src'.format(m)) r_macro += ['R(@0@)'.format(m)] enabled_renderers += [m] @@ -53,4 +61,4 @@ endforeach r_macro = ' '.join(r_macro) config.set('TAISEI_BUILDCONF_RENDERER_BACKENDS', r_macro) -config.set_quoted('TAISEI_BUILDCONF_RENDERER_DEFAULT', default_backend) +config.set_quoted('TAISEI_BUILDCONF_RENDERER_DEFAULT', default_renderer) diff --git a/src/util/compat.h b/src/util/compat.h index de64dde1..56cc1b28 100644 --- a/src/util/compat.h +++ b/src/util/compat.h @@ -278,4 +278,11 @@ typedef complex max_align_t; #define CASTPTR_ASSUME_ALIGNED(expr, type) ((type*)ASSUME_ALIGNED((expr), alignof(type))) +#ifdef __SWITCH__ + #include "../arch_switch.h" + #define atexit nxAtExit + #define exit nxExit + #define abort nxAbort +#endif + #endif // IGUARD_util_compat_h diff --git a/src/vfs/meson.build b/src/vfs/meson.build index 948a70f3..462d815b 100644 --- a/src/vfs/meson.build +++ b/src/vfs/meson.build @@ -37,6 +37,11 @@ if host_machine.system() == 'emscripten' 'setup_emscripten.c', 'sync_emscripten.c', ) +elif host_machine.system() == 'nx' + vfs_src += files( + 'setup_switch.c', + 'sync_noop.c', + ) else vfs_src += files( 'setup_generic.c', diff --git a/src/vfs/setup.h b/src/vfs/setup.h index 6fdb6f86..bd73dbcb 100644 --- a/src/vfs/setup.h +++ b/src/vfs/setup.h @@ -4,6 +4,7 @@ * --- * Copyright (c) 2011-2019, Lukas Weber . * Copyright (c) 2012-2019, Andrei Alexeyev . + * Copyright (c) 2019, p-sam . */ #ifndef IGUARD_vfs_setup_h @@ -12,7 +13,50 @@ #include "taisei.h" #include "public.h" +#include "loadpacks.h" + +typedef struct VfsSetupFixedPaths { + const char* res_path; + const char* storage_path; + const char* cache_path; +} VfsSetupFixedPaths; void vfs_setup(CallChain onready); +static inline void vfs_setup_fixedpaths_onsync(CallChainResult ccr, VfsSetupFixedPaths* paths) { + assume(paths != NULL); + assume(paths->res_path != NULL); + assume(paths->storage_path != NULL); + assume(paths->cache_path != NULL); + + log_info("Resource path: %s", paths->res_path); + log_info("Storage path: %s", paths->storage_path); + log_info("Cache path: %s", paths->cache_path); + + vfs_create_union_mountpoint("/res"); + + if(!vfs_mount_syspath("/res-dir", paths->res_path, VFS_SYSPATH_MOUNT_READONLY)) { + log_fatal("Failed to mount '%s': %s", paths->res_path, vfs_get_error()); + } + + if(!vfs_mount_syspath("/storage", paths->storage_path, VFS_SYSPATH_MOUNT_MKDIR)) { + log_fatal("Failed to mount '%s': %s", paths->storage_path, vfs_get_error()); + } + + if(!vfs_mount_syspath("/cache", paths->cache_path, VFS_SYSPATH_MOUNT_MKDIR)) { + log_fatal("Failed to mount '%s': %s", paths->cache_path, vfs_get_error()); + } + + vfs_load_packages("/res-dir", "/res"); + vfs_mount_alias("/res", "/res-dir"); + vfs_unmount("/res-dir"); + + vfs_mkdir_required("storage/replays"); + vfs_mkdir_required("storage/screenshots"); + + CallChain *next = ccr.ctx; + run_call_chain(next, NULL); + free(next); +} + #endif // IGUARD_vfs_setup_h diff --git a/src/vfs/setup_emscripten.c b/src/vfs/setup_emscripten.c index 41d96246..f09d3aac 100644 --- a/src/vfs/setup_emscripten.c +++ b/src/vfs/setup_emscripten.c @@ -4,6 +4,7 @@ * --- * Copyright (c) 2011-2019, Lukas Weber . * Copyright (c) 2012-2019, Andrei Alexeyev . + * Copyright (c) 2019, p-sam . */ #include "taisei.h" @@ -13,32 +14,13 @@ #include "util.h" static void vfs_setup_onsync(CallChainResult ccr) { - const char *res_path = "/" TAISEI_BUILDCONF_DATA_PATH; - const char *storage_path = "/persistent/storage"; - const char *cache_path = "/persistent/cache"; + VfsSetupFixedPaths paths = { + .res_path = "/" TAISEI_BUILDCONF_DATA_PATH, + .storage_path = "/persistent/storage", + .cache_path ="/persistent/cache", + }; - log_info("Resource path: %s", res_path); - log_info("Storage path: %s", storage_path); - log_info("Cache path: %s", cache_path); - - if(!vfs_mount_syspath("/res", res_path, VFS_SYSPATH_MOUNT_READONLY)) { - log_fatal("Failed to mount '%s': %s", res_path, vfs_get_error()); - } - - if(!vfs_mount_syspath("/storage", storage_path, VFS_SYSPATH_MOUNT_MKDIR)) { - log_fatal("Failed to mount '%s': %s", storage_path, vfs_get_error()); - } - - if(!vfs_mount_syspath("/cache", cache_path, VFS_SYSPATH_MOUNT_MKDIR)) { - log_fatal("Failed to mount '%s': %s", cache_path, vfs_get_error()); - } - - vfs_mkdir_required("storage/replays"); - vfs_mkdir_required("storage/screenshots"); - - CallChain *next = ccr.ctx; - run_call_chain(next, NULL); - free(next); + vfs_setup_fixedpaths_onsync(ccr, &paths); } void vfs_setup(CallChain next) { diff --git a/src/vfs/setup_switch.c b/src/vfs/setup_switch.c new file mode 100644 index 00000000..e47596d1 --- /dev/null +++ b/src/vfs/setup_switch.c @@ -0,0 +1,39 @@ +/* + * This software is licensed under the terms of the MIT-License + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . + * Copyright (c) 2019, p-sam . + */ + +#include "taisei.h" + +#include "public.h" +#include "setup.h" +#include "util.h" + +static void vfs_setup_onsync(CallChainResult ccr) { + const char *program_dir = nxGetProgramDir(); + char *res_path = strfmt("%s/%s", program_dir, TAISEI_BUILDCONF_DATA_PATH); + char *storage_path = strfmt("%s/storage", program_dir); + char *cache_path = strfmt("%s/cache", program_dir); + + VfsSetupFixedPaths paths = { + .res_path = res_path, + .storage_path = storage_path, + .cache_path = cache_path, + }; + + vfs_setup_fixedpaths_onsync(ccr, &paths); + + free(res_path); + free(storage_path); + free(cache_path); +} + +void vfs_setup(CallChain next) { + vfs_init(); + CallChain *cc = memdup(&next, sizeof(next)); + vfs_sync(VFS_SYNC_LOAD, CALLCHAIN(vfs_setup_onsync, cc)); +} diff --git a/src/video.c b/src/video.c index 2b8eb286..984e5e0e 100644 --- a/src/video.c +++ b/src/video.c @@ -66,6 +66,7 @@ static VideoCapabilityState video_query_capability_alwaysfullscreen(VideoCapabil UNREACHABLE; } +#ifndef __SWITCH__ static VideoCapabilityState video_query_capability_webcanvas(VideoCapability cap) { switch(cap) { case VIDEO_CAP_EXTERNAL_RESIZE: @@ -78,6 +79,7 @@ static VideoCapabilityState video_query_capability_webcanvas(VideoCapability cap return video_query_capability_generic(cap); } } +#endif static void video_add_mode(int width, int height) { if(video.modes) { @@ -622,6 +624,10 @@ void video_init(void) { const char *driver = SDL_GetCurrentVideoDriver(); log_info("Using driver '%s'", driver); +#ifdef __SWITCH__ + video.backend = VIDEO_BACKEND_SWITCH; + video_query_capability = video_query_capability_alwaysfullscreen; +#else video_query_capability = video_query_capability_generic; if(!strcmp(driver, "x11")) { @@ -642,11 +648,12 @@ void video_init(void) { } else { video.backend = VIDEO_BACKEND_OTHER; } +#endif r_init(); // Register all resolutions that are available in fullscreen - +#ifndef __SWITCH__ for(int s = 0; s < video_num_displays(); ++s) { log_info("Found display #%i: %s", s, video_display_name(s)); for(int i = 0; i < SDL_GetNumDisplayModes(s); ++i) { @@ -660,6 +667,7 @@ void video_init(void) { } } } +#endif if(!fullscreen_available) { log_warn("No available fullscreen modes"); @@ -669,6 +677,11 @@ void video_init(void) { // Then, add some common 4:3 modes for the windowed mode if they are not there yet. // This is required for some multihead setups. VideoMode common_modes[] = { +#ifdef __SWITCH__ + {640, 480}, + {1280, 720}, + {1920, 1080}, +#else {RESX, RESY}, {SCREEN_W, SCREEN_H}, @@ -679,7 +692,7 @@ void video_init(void) { {1152, 864}, {1400, 1050}, {1440, 1080}, - +#endif {0, 0}, }; diff --git a/src/video.h b/src/video.h index 696661af..bf0f3d3f 100644 --- a/src/video.h +++ b/src/video.h @@ -41,6 +41,7 @@ typedef enum VideoBackend { VIDEO_BACKEND_EMSCRIPTEN, VIDEO_BACKEND_KMSDRM, VIDEO_BACKEND_RPI, + VIDEO_BACKEND_SWITCH, } VideoBackend; typedef struct { diff --git a/switch/README.md b/switch/README.md new file mode 100644 index 00000000..d465b6ce --- /dev/null +++ b/switch/README.md @@ -0,0 +1,43 @@ +Taisei Switch Port +================== + +

+ +## Installation + +### Grabbing Binaries + +Download the latest release, +and extract the archive in the `/switch` folder on your SD Card. +Then, run the game from the [hbmenu](https://github.com/switchbrew/nx-hbmenu) +using [hbl](https://github.com/switchbrew/nx-hbloader). + +**WARNING:** This will crash if executed from an applet such as the Photo/Library applet, +be sure to launch it from hbmenu on top of the game of your choice, +which can be done by holding R over any installed title on latest Atmosphère, with default settings. + +### Build dependencies + +For building, you need the devkitA64 from devkitPro setup, along with switch portlibs and libnx. +Documentation to setup that can be found [here](https://switchbrew.org/wiki/Setting_up_Development_Environment). + +Other dependencies common to the main targets include: + + * meson >= 0.45.0 (build system; >=0.48.0 recommended) + * Python >= 3.5 + * ninja + * glslc + * spirv-cross + +### Compiling from source + +Run one of the following commands from the project root: + +``` +mkdir -p ./build/nx +./switch/crossfile.sh > ./build/nx/crossfile.txt +meson --cross-file="./build/nx/crossfile.txt" . ./build/nx +ninja -C ./build/nx +``` + +**Note:** You can optionally set a custom prefix and `ninja install` NRO and assets into that folder. \ No newline at end of file diff --git a/switch/crossfile.sh b/switch/crossfile.sh new file mode 100755 index 00000000..338c62e5 --- /dev/null +++ b/switch/crossfile.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +set -eo pipefail + +source "$DEVKITPRO/switchvars.sh" + +function meson_arg_list() { + while (( "$#" )); do + echo -n "'$1'"; + if [ $# -gt 1 ]; then + echo -n ","; + fi + shift + done +} + +function bin_path() { + which "${TOOL_PREFIX}$1" +} + +ADDITIONAL_LINK_FLAGS="-specs=$DEVKITPRO/libnx/switch.specs" + +cat <