1
0
mirror of https://github.com/XProger/OpenLara.git synced 2025-08-08 14:16:52 +02:00

№15 Nintendo Switch support

This commit is contained in:
XProger
2019-01-05 04:09:22 +03:00
parent 34a300faf9
commit 9d5371009c
5 changed files with 496 additions and 1 deletions

View File

@@ -1070,6 +1070,10 @@ namespace GAPI {
support.texHalf = support.texHalfLinear || extSupport(ext, "_texture_half_float"); support.texHalf = support.texHalfLinear || extSupport(ext, "_texture_half_float");
support.clipDist = false; // TODO support.clipDist = false; // TODO
#ifdef _OS_NX
support.shaderBinary = false; // TODO: check GPU crash for current switchbrew mesa libs
#endif
#ifdef PROFILE #ifdef PROFILE
support.profMarker = extSupport(ext, "_KHR_debug"); support.profMarker = extSupport(ext, "_KHR_debug");
support.profTiming = extSupport(ext, "_timer_query"); support.profTiming = extSupport(ext, "_timer_query");

184
src/platform/nx/Makefile Normal file
View 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
#---------------------------------------------------------------------------------------

View 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
View 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;
}

View File

@@ -24,7 +24,7 @@
#else #else
//#define ASSERT(expr) if (expr) {} else { LOG("ASSERT:\n %s:%d\n %s => %s\n", __FILE__, __LINE__, __FUNCTION__, #expr); } //#define ASSERT(expr) if (expr) {} else { LOG("ASSERT:\n %s:%d\n %s => %s\n", __FILE__, __LINE__, __FUNCTION__, #expr); }
#define ASSERT(expr) #define ASSERT(expr)
#define ASSERTV(expr) (expr) ? 0 : 1 #define ASSERTV(expr) (expr)
#ifdef _OS_LINUX #ifdef _OS_LINUX
#define LOG(...) printf(__VA_ARGS__); fflush(stdout) #define LOG(...) printf(__VA_ARGS__); fflush(stdout)