mirror of
https://github.com/XProger/OpenLara.git
synced 2025-08-08 06:06:51 +02:00
№15 Nintendo Switch support
This commit is contained in:
@@ -1070,6 +1070,10 @@ namespace GAPI {
|
||||
support.texHalf = support.texHalfLinear || extSupport(ext, "_texture_half_float");
|
||||
support.clipDist = false; // TODO
|
||||
|
||||
#ifdef _OS_NX
|
||||
support.shaderBinary = false; // TODO: check GPU crash for current switchbrew mesa libs
|
||||
#endif
|
||||
|
||||
#ifdef PROFILE
|
||||
support.profMarker = extSupport(ext, "_KHR_debug");
|
||||
support.profTiming = extSupport(ext, "_timer_query");
|
||||
|
184
src/platform/nx/Makefile
Normal file
184
src/platform/nx/Makefile
Normal file
@@ -0,0 +1,184 @@
|
||||
#---------------------------------------------------------------------------------
|
||||
.SUFFIXES:
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
ifeq ($(strip $(DEVKITPRO)),)
|
||||
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro")
|
||||
endif
|
||||
|
||||
TOPDIR ?= $(CURDIR)
|
||||
include $(DEVKITPRO)/libnx/switch_rules
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# TARGET is the name of the output
|
||||
# BUILD is the directory where object files & intermediate files will be placed
|
||||
# SOURCES is a list of directories containing source code
|
||||
# DATA is a list of directories containing data files
|
||||
# INCLUDES is a list of directories containing header files
|
||||
# EXEFS_SRC is the optional input directory containing data copied into exefs, if anything this normally should only contain "main.npdm".
|
||||
# ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional)
|
||||
#
|
||||
# NO_ICON: if set to anything, do not use icon.
|
||||
# NO_NACP: if set to anything, no .nacp file is generated.
|
||||
# APP_TITLE is the name of the app stored in the .nacp file (Optional)
|
||||
# APP_AUTHOR is the author of the app stored in the .nacp file (Optional)
|
||||
# APP_VERSION is the version of the app stored in the .nacp file (Optional)
|
||||
# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional)
|
||||
# ICON is the filename of the icon (.jpg), relative to the project folder.
|
||||
# If not set, it attempts to use one of the following (in this order):
|
||||
# - <Project name>.jpg
|
||||
# - icon.jpg
|
||||
# - <libnx folder>/default_icon.jpg
|
||||
#---------------------------------------------------------------------------------
|
||||
TARGET := OpenLara
|
||||
BUILD := build
|
||||
SOURCES := ./ ./../../libs/stb_vorbis/ ./../../libs/tinf/ ./../../libs/minimp3/
|
||||
DATA := data
|
||||
INCLUDES := ../../
|
||||
EXEFS_SRC := exefs_src
|
||||
#ROMFS := romfs
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# options for code generation
|
||||
#---------------------------------------------------------------------------------
|
||||
ARCH := -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE -mcpu=cortex-a57+crc+fp+simd
|
||||
|
||||
CFLAGS := -g -O -ffast-math -fno-strict-aliasing -fomit-frame-pointer -ffunction-sections \
|
||||
$(ARCH) $(DEFINES)
|
||||
|
||||
CFLAGS += $(INCLUDE) -D__SWITCH__
|
||||
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11
|
||||
|
||||
ASFLAGS := -g $(ARCH)
|
||||
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
||||
|
||||
LIBS := -lEGL -lGLESv2 -lglapi -ldrm_nouveau -lnx -lm
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# list of directories containing libraries, this must be the top level containing
|
||||
# include and lib
|
||||
#---------------------------------------------------------------------------------
|
||||
LIBDIRS := $(PORTLIBS) $(LIBNX)
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# no real need to edit anything past this point unless you need to add additional
|
||||
# rules for different file extensions
|
||||
#---------------------------------------------------------------------------------
|
||||
ifneq ($(BUILD),$(notdir $(CURDIR)))
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
export OUTPUT := $(CURDIR)/$(TARGET)
|
||||
export TOPDIR := $(CURDIR)
|
||||
|
||||
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
|
||||
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
|
||||
|
||||
export DEPSDIR := $(CURDIR)/$(BUILD)
|
||||
|
||||
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
|
||||
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
|
||||
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
|
||||
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# use CXX for linking C++ projects
|
||||
#---------------------------------------------------------------------------------
|
||||
export LD := $(CXX)
|
||||
|
||||
export OFILES_BIN := $(addsuffix .o,$(BINFILES))
|
||||
export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
|
||||
export OFILES := $(OFILES_BIN) $(OFILES_SRC)
|
||||
export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES)))
|
||||
|
||||
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
|
||||
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
|
||||
-I$(CURDIR)/$(BUILD)
|
||||
|
||||
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
|
||||
|
||||
export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC)
|
||||
|
||||
ifeq ($(strip $(ICON)),)
|
||||
icons := $(wildcard *.jpg)
|
||||
ifneq (,$(findstring $(TARGET).jpg,$(icons)))
|
||||
export APP_ICON := $(TOPDIR)/$(TARGET).jpg
|
||||
else
|
||||
ifneq (,$(findstring icon.jpg,$(icons)))
|
||||
export APP_ICON := $(TOPDIR)/icon.jpg
|
||||
endif
|
||||
endif
|
||||
else
|
||||
export APP_ICON := $(TOPDIR)/$(ICON)
|
||||
endif
|
||||
|
||||
ifeq ($(strip $(NO_ICON)),)
|
||||
export NROFLAGS += --icon=$(APP_ICON)
|
||||
endif
|
||||
|
||||
ifeq ($(strip $(NO_NACP)),)
|
||||
export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp
|
||||
endif
|
||||
|
||||
ifneq ($(APP_TITLEID),)
|
||||
export NACPFLAGS += --titleid=$(APP_TITLEID)
|
||||
endif
|
||||
|
||||
ifneq ($(ROMFS),)
|
||||
export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS)
|
||||
endif
|
||||
|
||||
.PHONY: $(BUILD) clean all
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
all: $(BUILD)
|
||||
|
||||
$(BUILD):
|
||||
@[ -d $@ ] || mkdir -p $@
|
||||
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
clean:
|
||||
@echo clean ...
|
||||
@rm -fr $(BUILD) $(TARGET).pfs0 $(TARGET).nso $(TARGET).nro $(TARGET).nacp $(TARGET).elf
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
else
|
||||
.PHONY: all
|
||||
|
||||
DEPENDS := $(OFILES:.o=.d)
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# main targets
|
||||
#---------------------------------------------------------------------------------
|
||||
all : $(OUTPUT).pfs0 $(OUTPUT).nro
|
||||
|
||||
$(OUTPUT).pfs0 : $(OUTPUT).nso
|
||||
|
||||
$(OUTPUT).nso : $(OUTPUT).elf
|
||||
|
||||
ifeq ($(strip $(NO_NACP)),)
|
||||
$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp
|
||||
else
|
||||
$(OUTPUT).nro : $(OUTPUT).elf
|
||||
endif
|
||||
|
||||
$(OUTPUT).elf : $(OFILES)
|
||||
|
||||
$(OFILES_SRC) : $(HFILES_BIN)
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# you need a rule like this for each extension you use as binary data
|
||||
#---------------------------------------------------------------------------------
|
||||
%.bin.o %_bin.h : %.bin
|
||||
#---------------------------------------------------------------------------------
|
||||
@echo $(notdir $<)
|
||||
@$(bin2o)
|
||||
|
||||
-include $(DEPENDS)
|
||||
|
||||
#---------------------------------------------------------------------------------------
|
||||
endif
|
||||
#---------------------------------------------------------------------------------------
|
1
src/platform/nx/deploy.bat
Normal file
1
src/platform/nx/deploy.bat
Normal file
@@ -0,0 +1 @@
|
||||
C:\devkitPro\tools\bin\nxlink.exe -a 192.168.1.46 OpenLara.nro
|
306
src/platform/nx/main.cpp
Normal file
306
src/platform/nx/main.cpp
Normal file
@@ -0,0 +1,306 @@
|
||||
#include <string.h>
|
||||
#include <cstdlib>
|
||||
#include <malloc.h>
|
||||
|
||||
#include <switch.h>
|
||||
#include "game.h"
|
||||
|
||||
// multi-threading
|
||||
void* osMutexInit() {
|
||||
RMutex *mutex = new RMutex();
|
||||
rmutexInit(mutex);
|
||||
return mutex;
|
||||
}
|
||||
|
||||
void osMutexFree(void *obj) {
|
||||
delete ((RMutex*)obj);
|
||||
}
|
||||
|
||||
void osMutexLock(void *obj) {
|
||||
rmutexLock((RMutex*)obj);
|
||||
}
|
||||
|
||||
void osMutexUnlock(void *obj) {
|
||||
rmutexUnlock((RMutex*)obj);
|
||||
}
|
||||
|
||||
// timing
|
||||
unsigned int startTime;
|
||||
|
||||
int osGetTime() {
|
||||
u64 tick = armGetSystemTick();
|
||||
return (tick * 625 / 12) / 1000000;
|
||||
}
|
||||
|
||||
// sound
|
||||
#define SND_RESAMPLE (44100.0f / 48000.0f)
|
||||
#define SND_FRAMES_FILL 2352
|
||||
#define SND_FRAMES_OUT int(SND_FRAMES_FILL / SND_RESAMPLE)
|
||||
|
||||
Thread sndThread;
|
||||
AudioOutBuffer sndOutBuffer[2];
|
||||
Sound::Frame *sndFrames;
|
||||
|
||||
void sndFill(void *args) {
|
||||
Sound::Frame *frames = new Sound::Frame[SND_FRAMES_FILL]; // some decoders
|
||||
|
||||
while (!Core::isQuit) {
|
||||
AudioOutBuffer *chunk;
|
||||
audoutWaitPlayFinish(&chunk, NULL, U64_MAX);
|
||||
|
||||
Sound::fill(frames, SND_FRAMES_FILL);
|
||||
|
||||
for (int j = 0; j < SND_FRAMES_OUT; j++) { // resample 44100 -> 48000
|
||||
((Sound::Frame*)chunk->buffer)[j] = frames[int(j * SND_RESAMPLE)];
|
||||
}
|
||||
|
||||
audoutAppendAudioOutBuffer(chunk);
|
||||
}
|
||||
|
||||
delete[] frames;
|
||||
}
|
||||
|
||||
bool sndInit() {
|
||||
audoutInitialize();
|
||||
|
||||
memset(sndOutBuffer, 0, sizeof(sndOutBuffer));
|
||||
|
||||
for (int i = 0; i < COUNT(sndOutBuffer); i++) {
|
||||
AudioOutBuffer &chunk = sndOutBuffer[i];
|
||||
chunk.data_size = SND_FRAMES_OUT * sizeof(Sound::Frame);
|
||||
chunk.buffer_size = (chunk.data_size + 0xFFF) & ~0xFFF;
|
||||
chunk.buffer = memalign(0x1000, chunk.buffer_size);
|
||||
memset(chunk.buffer, 0, chunk.buffer_size);
|
||||
audoutAppendAudioOutBuffer(&chunk);
|
||||
}
|
||||
|
||||
audoutStartAudioOut();
|
||||
|
||||
threadCreate(&sndThread, sndFill, NULL, 0x4000, 0x2B, 2);
|
||||
threadStart(&sndThread);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void sndFree() {
|
||||
threadWaitForExit(&sndThread);
|
||||
threadClose(&sndThread);
|
||||
audoutStopAudioOut();
|
||||
audoutExit();
|
||||
}
|
||||
|
||||
// Window
|
||||
EGLDisplay display;
|
||||
EGLSurface surface;
|
||||
EGLContext context;
|
||||
|
||||
bool eglInit() {
|
||||
display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
if (display == EGL_NO_DISPLAY)
|
||||
return false;
|
||||
|
||||
if (eglInitialize(display, NULL, NULL) == EGL_FALSE)
|
||||
return false;
|
||||
|
||||
static const EGLint eglAttr[] = {
|
||||
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
|
||||
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
|
||||
EGL_RED_SIZE, 8,
|
||||
EGL_GREEN_SIZE, 8,
|
||||
EGL_BLUE_SIZE, 8,
|
||||
EGL_ALPHA_SIZE, 8,
|
||||
EGL_DEPTH_SIZE, 24,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
static const EGLint ctxAttr[] = {
|
||||
EGL_CONTEXT_MAJOR_VERSION, 2,
|
||||
EGL_CONTEXT_MINOR_VERSION, 0,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
EGLConfig config;
|
||||
EGLint configCount = 0;
|
||||
|
||||
if (eglChooseConfig(display, eglAttr, &config, 1, &configCount) == EGL_FALSE)
|
||||
return false;
|
||||
|
||||
surface = eglCreateWindowSurface(display, config, (char*)"", NULL);
|
||||
|
||||
if (surface == EGL_NO_SURFACE)
|
||||
return false;
|
||||
|
||||
if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE)
|
||||
return false;
|
||||
|
||||
context = eglCreateContext(display, config, EGL_NO_CONTEXT, ctxAttr);
|
||||
if (context == EGL_NO_CONTEXT)
|
||||
return false;
|
||||
|
||||
if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void eglFree() {
|
||||
eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
eglDestroySurface(display, surface);
|
||||
eglDestroyContext(display, context);
|
||||
eglTerminate(display);
|
||||
eglReleaseThread();
|
||||
}
|
||||
|
||||
// Input
|
||||
bool osJoyReady(int index) {
|
||||
return index == 0;
|
||||
}
|
||||
|
||||
void osJoyVibrate(int index, float L, float R) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
bool joyIsSplit;
|
||||
int joySplitTime;
|
||||
|
||||
void joySplit(bool split) {
|
||||
joyIsSplit = split;
|
||||
joySplitTime = osGetTime();
|
||||
if (split) {
|
||||
hidSetNpadJoyHoldType(HidJoyHoldType_Horizontal);
|
||||
hidSetNpadJoyAssignmentModeSingleByDefault(CONTROLLER_PLAYER_1);
|
||||
hidSetNpadJoyAssignmentModeSingleByDefault(CONTROLLER_PLAYER_2);
|
||||
} else {
|
||||
hidSetNpadJoyHoldType(HidJoyHoldType_Default);
|
||||
hidSetNpadJoyAssignmentModeDual(CONTROLLER_PLAYER_1);
|
||||
hidSetNpadJoyAssignmentModeDual(CONTROLLER_PLAYER_2);
|
||||
hidMergeSingleJoyAsDualJoy(CONTROLLER_PLAYER_1, CONTROLLER_PLAYER_2);
|
||||
|
||||
if (Game::level && Game::level.players[1]) {
|
||||
Game::level->addPlayer(1); // add existing player == remove player
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void joyInit() {
|
||||
joySplit(false);
|
||||
}
|
||||
|
||||
void joyUpdate() {
|
||||
const static u64 keys[jkMAX] = { 0,
|
||||
KEY_B, KEY_A, KEY_Y, KEY_X, KEY_L, KEY_R, KEY_PLUS, KEY_MINUS,
|
||||
0, 0, KEY_ZL, KEY_ZR,
|
||||
KEY_DLEFT, KEY_DRIGHT, KEY_DUP, KEY_DDOWN,
|
||||
};
|
||||
|
||||
if (hidGetHandheldMode() && joyIsSplit) {
|
||||
joySplit(false);
|
||||
}
|
||||
|
||||
hidScanInput();
|
||||
|
||||
u64 mDown = 0;
|
||||
|
||||
for (int i = 0; i < (joyIsSplit ? 2 : 1); i++) {
|
||||
HidControllerID ctrl = (i == 0 ? CONTROLLER_P1_AUTO : CONTROLLER_PLAYER_2);
|
||||
|
||||
u64 mask = hidKeysDown(ctrl) | hidKeysHeld(ctrl);
|
||||
mDown |= mask;
|
||||
|
||||
for (int j = 1; j < jkMAX; j++) {
|
||||
if (j != jkSelect && j != jkStart) {
|
||||
Input::setJoyDown(i, JoyKey(jkNone + j), (mask & keys[j]) != 0);
|
||||
}
|
||||
}
|
||||
|
||||
Input::setJoyDown(i, jkSelect, (mask & (KEY_MINUS | KEY_PLUS)) != 0);
|
||||
Input::setJoyDown(i, jkStart, (mask & (KEY_LSTICK | KEY_RSTICK)) != 0);
|
||||
|
||||
JoystickPosition sL, sR;
|
||||
|
||||
hidJoystickRead(&sL, ctrl, JOYSTICK_LEFT);
|
||||
hidJoystickRead(&sR, ctrl, JOYSTICK_RIGHT);
|
||||
Input::setJoyPos(i, jkL, vec2(float(sL.dx), float(-sL.dy)) / 32767.0f);
|
||||
Input::setJoyPos(i, jkR, vec2(float(sR.dx), float(-sR.dy)) / 32767.0f);
|
||||
}
|
||||
|
||||
if ((mDown & (KEY_L | KEY_R)) == (KEY_L | KEY_R)) { // hold L and R to split/merge joy-con's
|
||||
if (joySplitTime + 1000 < osGetTime()) { // 1 sec timer
|
||||
joySplit(!joyIsSplit);
|
||||
}
|
||||
} else {
|
||||
joySplitTime = osGetTime();
|
||||
}
|
||||
}
|
||||
|
||||
void touchUpdate() {
|
||||
int touchesCount = hidTouchCount();
|
||||
|
||||
bool touchState[COUNT(Input::touch)];
|
||||
|
||||
for (int i = 0; i < COUNT(Input::touch); i++) {
|
||||
touchState[i] = Input::down[ikTouchA + i];
|
||||
}
|
||||
|
||||
for (int i = 0; i < touchesCount; i++) {
|
||||
touchPosition touch;
|
||||
hidTouchRead(&touch, i);
|
||||
|
||||
InputKey key = Input::getTouch(i /*touch.id*/); // require libnx with https://github.com/switchbrew/libnx/pull/228
|
||||
if (key == ikNone) continue;
|
||||
|
||||
Input::setPos(key, vec2(float(touch.px), float(touch.py)));
|
||||
Input::setDown(key, true);
|
||||
|
||||
touchState[key - ikTouchA] = false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < COUNT(Input::touch); i++) {
|
||||
if (touchState[i]) {
|
||||
Input::setDown(InputKey(ikTouchA + i), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
Core::width = 1280;
|
||||
Core::height = 720;
|
||||
|
||||
if (!eglInit()) {
|
||||
LOG("! can't initialize EGL context\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
cacheDir[0] = saveDir[0] = contentDir[0] = 0;
|
||||
|
||||
strcat(contentDir, "/switch/OpenLara/");
|
||||
strcat(cacheDir, "/switch/OpenLara/cache/");
|
||||
strcat(saveDir, "/switch/OpenLara/");
|
||||
|
||||
//mkdir("/switch/OpenLara/cache/");
|
||||
|
||||
startTime = osGetTime();
|
||||
|
||||
sndInit();
|
||||
joyInit();
|
||||
|
||||
Game::init();
|
||||
|
||||
while (appletMainLoop() && !Core::isQuit) {
|
||||
joyUpdate();
|
||||
touchUpdate();
|
||||
|
||||
if (Game::update()) {
|
||||
Game::render();
|
||||
Core::waitVBlank();
|
||||
eglSwapBuffers(display, surface);
|
||||
}
|
||||
};
|
||||
|
||||
sndFree();
|
||||
Game::deinit();
|
||||
|
||||
eglFree();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
@@ -24,7 +24,7 @@
|
||||
#else
|
||||
//#define ASSERT(expr) if (expr) {} else { LOG("ASSERT:\n %s:%d\n %s => %s\n", __FILE__, __LINE__, __FUNCTION__, #expr); }
|
||||
#define ASSERT(expr)
|
||||
#define ASSERTV(expr) (expr) ? 0 : 1
|
||||
#define ASSERTV(expr) (expr)
|
||||
|
||||
#ifdef _OS_LINUX
|
||||
#define LOG(...) printf(__VA_ARGS__); fflush(stdout)
|
||||
|
Reference in New Issue
Block a user