From 9fd89c3073296525f8b1b4f3ae1a55987f5b615d Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Fri, 8 Mar 2024 11:53:48 -0400 Subject: [PATCH 01/34] add Switch port --- .github/workflows/build_switch.yml | 39 ++ .gitignore | 1 + Makefile | 2 + misc/switch/Makefile | 226 ++++++++++++ misc/switch/icon.jpg | Bin 0 -> 31604 bytes src/Audio.c | 162 +++++++++ src/Core.h | 10 +- src/Platform_Switch.c | 565 +++++++++++++++++++++++++++++ src/Window_Switch.c | 380 +++++++++++++++++++ 9 files changed, 1384 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build_switch.yml create mode 100644 misc/switch/Makefile create mode 100644 misc/switch/icon.jpg create mode 100644 src/Platform_Switch.c create mode 100644 src/Window_Switch.c diff --git a/.github/workflows/build_switch.yml b/.github/workflows/build_switch.yml new file mode 100644 index 000000000..5285a2e2e --- /dev/null +++ b/.github/workflows/build_switch.yml @@ -0,0 +1,39 @@ +name: Build latest (Switch) +on: [push] + +concurrency: + group: ${{ github.ref }}-switch + cancel-in-progress: true + +jobs: + build-switch: + if: github.ref_name == github.event.repository.default_branch + runs-on: ubuntu-latest + container: + image: devkitpro/devkita64:latest + steps: + - uses: actions/checkout@v4 + - name: Compile Switch build + id: compile + run: | + make switch + + + - uses: ./.github/actions/notify_failure + if: ${{ always() && steps.compile.outcome == 'failure' }} + with: + NOTIFY_MESSAGE: 'Failed to compile Switch build' + WEBHOOK_URL: '${{ secrets.WEBHOOK_URL }}' + + + - uses: ./.github/actions/upload_build + if: ${{ always() && steps.compile.outcome == 'success' }} + with: + SOURCE_FILE: 'ClassiCube-switch.nro' + DEST_NAME: 'ClassiCube-switch.nro' + + - uses: ./.github/actions/upload_build + if: ${{ always() && steps.compile.outcome == 'success' }} + with: + SOURCE_FILE: 'ClassiCube-switch.elf' + DEST_NAME: 'ClassiCube-switch.elf' \ No newline at end of file diff --git a/.gitignore b/.gitignore index eb9e6f85c..a538094e7 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ build-ps2/ build-ps3/ build-vita/ build-wii/ +build-switch/ # Build results [Dd]ebug/ diff --git a/Makefile b/Makefile index 8f12bd5b2..09ad0bed6 100644 --- a/Makefile +++ b/Makefile @@ -151,6 +151,8 @@ gamecube: $(MAKE) -f misc/gc/Makefile PLAT=gamecube wiiu: $(MAKE) -f misc/wiiu/Makefile PLAT=wiiu +switch: + $(MAKE) -f misc/switch/Makefile PLAT=switch clean: $(DEL) $(OBJECTS) diff --git a/misc/switch/Makefile b/misc/switch/Makefile new file mode 100644 index 000000000..68f788575 --- /dev/null +++ b/misc/switch/Makefile @@ -0,0 +1,226 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/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 +# 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): +# - .jpg +# - icon.jpg +# - /default_icon.jpg +# +# CONFIG_JSON is the filename of the NPDM config file (.json), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - .json +# - config.json +# If a JSON file is provided or autodetected, an ExeFS PFS0 (.nsp) is built instead +# of a homebrew executable (.nro). This is intended to be used for sysmodules. +# NACP building is skipped as well. +#--------------------------------------------------------------------------------- +TARGET := ClassiCube-switch +BUILD := build-switch +SOURCES := src misc/switch third_party/bearssl/src +DATA := data +INCLUDES := third_party/bearssl/inc +#ROMFS := romfs + +APP_TITLE := ClassiCube +APP_AUTHOR := UnknownShadow200 +ICON := misc/switch/icon.jpg + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE + +CFLAGS := -g -Wall -O2 -ffunction-sections \ + $(ARCH) $(DEFINES) + +CFLAGS += $(INCLUDE) -D__SWITCH__ + +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -lGLESv2 -lglad -lEGL -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, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +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) + +ifeq ($(strip $(CONFIG_JSON)),) + jsons := $(wildcard *.json) + ifneq (,$(findstring $(TARGET).json,$(jsons))) + export APP_JSON := $(TOPDIR)/$(TARGET).json + else + ifneq (,$(findstring config.json,$(jsons))) + export APP_JSON := $(TOPDIR)/config.json + endif + endif +else + export APP_JSON := $(TOPDIR)/$(CONFIG_JSON) +endif + +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)/misc/switch/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... +ifeq ($(strip $(APP_JSON)),) + @rm -fr $(BUILD) $(TARGET).nro $(TARGET).nacp $(TARGET).elf +else + @rm -fr $(BUILD) $(TARGET).nsp $(TARGET).nso $(TARGET).npdm $(TARGET).elf +endif + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +ifeq ($(strip $(APP_JSON)),) + +all : $(OUTPUT).nro + +ifeq ($(strip $(NO_NACP)),) +$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp +else +$(OUTPUT).nro : $(OUTPUT).elf +endif + +else + +all : $(OUTPUT).nsp + +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm + +$(OUTPUT).nso : $(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 +#--------------------------------------------------------------------------------------- diff --git a/misc/switch/icon.jpg b/misc/switch/icon.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a8a2b59565b083c931701bc59cf15d6be5c49c08 GIT binary patch literal 31604 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 LDGFKpYvz9dW;VWb literal 0 HcmV?d00001 diff --git a/src/Audio.c b/src/Audio.c index 509210c84..e8fb6a895 100644 --- a/src/Audio.c +++ b/src/Audio.c @@ -919,6 +919,168 @@ void Audio_AllocChunks(cc_uint32 size, void** chunks, int numChunks) { void Audio_FreeChunks(void** chunks, int numChunks) { linearFree(chunks[0]); } +#elif defined CC_BUILD_SWITCH +/*########################################################################################################################* +*-----------------------------------------------------Switch backend------------------------------------------------------* +*#########################################################################################################################*/ +#include +struct AudioContext { + AudioDriverWaveBuf bufs[AUDIO_MAX_BUFFERS]; + int count, channels, sampleRate; + void* _tmpData; int _tmpSize; +}; +//static int channelIDs; +static AudioDriver drv; + +static cc_bool AudioBackend_Init(void) { + static const AudioRendererConfig arConfig = + { + .output_rate = AudioRendererOutputRate_48kHz, + .num_voices = 24, + .num_effects = 0, + .num_sinks = 1, + .num_mix_objs = 1, + .num_mix_buffers = 2, + }; + Result res; + + res = audrenInitialize(&arConfig); + Platform_Log1("audrenInitialize: %i", &res); + + res = audrvCreate(&drv, &arConfig, 2); + Platform_Log1("audrvCreate: %i", &res); + + return R_SUCCEEDED(res); +} +static void AudioBackend_Free(void) { + audrvClose(&drv); + audrenExit(); +} +#define AUDIO_HAS_BACKEND + +void Audio_Init(struct AudioContext* ctx, int buffers) { + /*int chanID = -1; + + for (int i = 0; i < 24; i++) + { + // channel in use + if (channelIDs & (1 << i)) continue; + + chanID = i; break; + } + if (chanID == -1) return; + + channelIDs |= (1 << chanID); + ctx->count = buffers; + ctx->chanID = chanID; + ctx->used = true; + + ndspChnSetInterp(ctx->chanID, NDSP_INTERP_LINEAR);*/ +} + +void Audio_Close(struct AudioContext* ctx) { + /*if (ctx->used) { + ndspChnWaveBufClear(ctx->chanID); + ctx->channels &= ~(1 << ctx->chanID); + channelIDs &= ~(1 << ctx->chanID); + } + + ctx->used = false;*/ + AudioBase_Clear(ctx); +} + +cc_result Audio_SetFormat(struct AudioContext* ctx, int channels, int sampleRate) { + /*ctx->stereo = (channels == 2); + int fmt = ctx->stereo ? NDSP_FORMAT_STEREO_PCM16 : NDSP_FORMAT_MONO_PCM16; + + ndspChnSetFormat(ctx->chanID, fmt); + ndspChnSetRate(ctx->chanID, sampleRate);*/ + return 0; +} + +cc_result Audio_QueueChunk(struct AudioContext* ctx, void* chunk, cc_uint32 dataSize) { + /*ndspWaveBuf* buf; + + // DSP audio buffers must be aligned to a multiple of 0x80, according to the example code I could find. + if (((uintptr_t)chunk & 0x7F) != 0) { + Platform_Log1("Audio_QueueData: tried to queue buffer with non-aligned audio buffer 0x%x\n", &chunk); + } + if ((dataSize & 0x7F) != 0) { + Platform_Log1("Audio_QueueData: unaligned audio data size 0x%x\n", &dataSize); + } + + for (int i = 0; i < ctx->count; i++) + { + buf = &ctx->bufs[i]; + //Platform_Log2("QUEUE_CHUNK: %i = %i", &ctx->chanID, &buf->status); + if (buf->status == NDSP_WBUF_QUEUED || buf->status == NDSP_WBUF_PLAYING) + continue; + + buf->data_pcm16 = chunk; + buf->nsamples = dataSize / (sizeof(cc_int16) * (ctx->stereo ? 2 : 1)); + //Platform_Log1("PLAYING ON: %i", &ctx->chanID); + DSP_FlushDataCache(buf->data_pcm16, dataSize); + ndspChnWaveBufAdd(ctx->chanID, buf); + return 0; + } + // tried to queue data without polling for free buffers first + return ERR_INVALID_ARGUMENT;*/ + return 0; +} + +cc_result Audio_Play(struct AudioContext* ctx) { return 0; } + +cc_result Audio_Poll(struct AudioContext* ctx, int* inUse) { + /*ndspWaveBuf* buf; + int count = 0; + + for (int i = 0; i < ctx->count; i++) + { + buf = &ctx->bufs[i]; + //Platform_Log2("CHECK_CHUNK: %i = %i", &ctx->chanID, &buf->status); + if (buf->status == NDSP_WBUF_QUEUED || buf->status == NDSP_WBUF_PLAYING) { + count++; continue; + } + } + + *inUse = count; */ + return 0; +} + + +cc_bool Audio_FastPlay(struct AudioContext* ctx, struct AudioData* data) { + return true; +} + +cc_result Audio_PlayData(struct AudioContext* ctx, struct AudioData* data) { + /*float mix[12] = { 0 }; + mix[0] = data->volume / 100.0f; + mix[1] = data->volume / 100.0f; + + ndspChnSetMix(ctx->chanID, mix); + data->sampleRate = Audio_AdjustSampleRate(data); + cc_result res; + + if ((res = Audio_SetFormat(ctx, data->channels, data->sampleRate))) return res; + if ((res = Audio_QueueChunk(ctx, data->data, data->size))) return res;*/ + return 0; +} + +cc_bool Audio_DescribeError(cc_result res, cc_string* dst) { + return false; +} + +void Audio_AllocChunks(cc_uint32 size, void** chunks, int numChunks) { + /*size = (size + 0x7F) & ~0x7F; // round up to nearest multiple of 0x80 + cc_uint8* dst = linearAlloc(size * numChunks); + for (int i = 0; i < numChunks; i++) { + chunks[i] = dst ? (dst + size * i) : NULL; + }*/ +} + +void Audio_FreeChunks(void** chunks, int numChunks) { + //linearFree(chunks[0]); +} #elif defined CC_BUILD_DREAMCAST /*########################################################################################################################* *----------------------------------------------------Dreamcast backend----------------------------------------------------* diff --git a/src/Core.h b/src/Core.h index 206ec054d..7e4835f24 100644 --- a/src/Core.h +++ b/src/Core.h @@ -359,9 +359,17 @@ typedef cc_uint8 cc_bool; #define CC_BUILD_LOWMEM #define CC_BUILD_BEARSSL #define CC_BUILD_CONSOLE +#elif defined __SWITCH__ + #define CC_BUILD_SWITCH + #define CC_BUILD_HTTPCLIENT + #define CC_BUILD_BEARSSL + #define CC_BUILD_CONSOLE + #define CC_BUILD_TOUCH + #define CC_BUILD_GL + #define CC_BUILD_GLMODERN + #define CC_BUILD_GLES #undef CC_BUILD_FREETYPE #endif -#endif #ifndef CC_BUILD_LOWMEM diff --git a/src/Platform_Switch.c b/src/Platform_Switch.c new file mode 100644 index 000000000..f46e8c635 --- /dev/null +++ b/src/Platform_Switch.c @@ -0,0 +1,565 @@ +#include "Core.h" +#if defined CC_BUILD_SWITCH +#include "_PlatformBase.h" +#include "Stream.h" +#include "ExtMath.h" +#include "Funcs.h" +#include "Window.h" +#include "Utils.h" +#include "Errors.h" +#include "Options.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "_PlatformConsole.h" + + + +const cc_result ReturnCode_FileShareViolation = 1000000000; // not used +const cc_result ReturnCode_FileNotFound = ENOENT; +const cc_result ReturnCode_SocketInProgess = EINPROGRESS; +const cc_result ReturnCode_SocketWouldBlock = EWOULDBLOCK; +const cc_result ReturnCode_DirectoryExists = EEXIST; +const char* Platform_AppNameSuffix = " Switch"; + + +alignas(16) u8 __nx_exception_stack[0x1000]; +u64 __nx_exception_stack_size = sizeof(__nx_exception_stack); + +void __libnx_exception_handler(ThreadExceptionDump *ctx) +{ + int i; + FILE *f = fopen("sdmc:/exception_dump", "w"); + if(f==NULL)return; + + fprintf(f, "error_desc: 0x%x\n", ctx->error_desc);//You can also parse this with ThreadExceptionDesc. + //This assumes AArch64, however you can also use threadExceptionIsAArch64(). + for(i=0; i<29; i++)fprintf(f, "[X%d]: 0x%lx\n", i, ctx->cpu_gprs[i].x); + fprintf(f, "fp: 0x%lx\n", ctx->fp.x); + fprintf(f, "lr: 0x%lx\n", ctx->lr.x); + fprintf(f, "sp: 0x%lx\n", ctx->sp.x); + fprintf(f, "pc: 0x%lx\n", ctx->pc.x); + + //You could print fpu_gprs if you want. + + fprintf(f, "pstate: 0x%x\n", ctx->pstate); + fprintf(f, "afsr0: 0x%x\n", ctx->afsr0); + fprintf(f, "afsr1: 0x%x\n", ctx->afsr1); + fprintf(f, "esr: 0x%x\n", ctx->esr); + + fprintf(f, "far: 0x%lx\n", ctx->far.x); + + fclose(f); +} + + +/*########################################################################################################################* +*------------------------------------------------------Logging/Time-------------------------------------------------------* +*#########################################################################################################################*/ +cc_uint64 Stopwatch_ElapsedMicroseconds(cc_uint64 beg, cc_uint64 end) { + if (end < beg) return 0; + + return (end - beg) / 1000; +} + +cc_uint64 Stopwatch_Measure(void) { + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + return (cc_uint64)t.tv_sec * 1e9 + t.tv_nsec; +} + +void Platform_Log(const char* msg, int len) { + char buffer[256]; + cc_string str; + String_InitArray(str, buffer); + + String_AppendAll(&str, msg, len); + buffer[str.length] = '\0'; + + printf("%s\n", buffer); +} + +#define UnixTime_TotalMS(time) ((cc_uint64)time.tv_sec * 1000 + UNIX_EPOCH + (time.tv_usec / 1000)) +TimeMS DateTime_CurrentUTC_MS(void) { + struct timeval cur; + gettimeofday(&cur, NULL); + return UnixTime_TotalMS(cur); +} + +void DateTime_CurrentLocal(struct DateTime* t) { + struct timeval cur; + struct tm loc_time; + gettimeofday(&cur, NULL); + localtime_r(&cur.tv_sec, &loc_time); + + t->year = loc_time.tm_year + 1900; + t->month = loc_time.tm_mon + 1; + t->day = loc_time.tm_mday; + t->hour = loc_time.tm_hour; + t->minute = loc_time.tm_min; + t->second = loc_time.tm_sec; +} + + +/*########################################################################################################################* +*-----------------------------------------------------Directory/File------------------------------------------------------* +*#########################################################################################################################*/ +static const cc_string root_path = String_FromConst("sdmc:/switch/ClassiCube/"); + +static void GetNativePath(char* str, const cc_string* path) { + Mem_Copy(str, root_path.buffer, root_path.length); + str += root_path.length; + String_EncodeUtf8(str, path); +} + +cc_result Directory_Create(const cc_string* path) { + char str[NATIVE_STR_LEN]; + GetNativePath(str, path); + return mkdir(str, 0) == -1 ? errno : 0; +} + +int File_Exists(const cc_string* path) { + char str[NATIVE_STR_LEN]; + struct stat sb; + GetNativePath(str, path); + return stat(str, &sb) == 0 && S_ISREG(sb.st_mode); +} + +cc_result Directory_Enum(const cc_string* dirPath, void* obj, Directory_EnumCallback callback) { + cc_string path; char pathBuffer[FILENAME_SIZE]; + char str[NATIVE_STR_LEN]; + struct dirent* entry; + int res; + + GetNativePath(str, dirPath); + DIR* dirPtr = opendir(str); + if (!dirPtr) return errno; + + // POSIX docs: "When the end of the directory is encountered, a null pointer is returned and errno is not changed." + // errno is sometimes leftover from previous calls, so always reset it before readdir gets called + errno = 0; + String_InitArray(path, pathBuffer); + + while ((entry = readdir(dirPtr))) { + path.length = 0; + String_Format1(&path, "%s/", dirPath); + + // ignore . and .. entry + char* src = entry->d_name; + if (src[0] == '.' && src[1] == '\0') continue; + if (src[0] == '.' && src[1] == '.' && src[2] == '\0') continue; + + int len = String_Length(src); + String_AppendUtf8(&path, src, len); + int is_dir = entry->d_type == DT_DIR; + // TODO: fallback to stat when this fails + + if (is_dir) { + res = Directory_Enum(&path, obj, callback); + if (res) { closedir(dirPtr); return res; } + } else { + callback(&path, obj); + } + errno = 0; + } + + res = errno; // return code from readdir + closedir(dirPtr); + return res; +} + +static cc_result File_Do(cc_file* file, const cc_string* path, int mode) { + char str[NATIVE_STR_LEN]; + GetNativePath(str, path); + *file = open(str, mode, 0); + return *file == -1 ? errno : 0; +} + +cc_result File_Open(cc_file* file, const cc_string* path) { + return File_Do(file, path, O_RDONLY); +} +cc_result File_Create(cc_file* file, const cc_string* path) { + return File_Do(file, path, O_RDWR | O_CREAT | O_TRUNC); +} +cc_result File_OpenOrCreate(cc_file* file, const cc_string* path) { + return File_Do(file, path, O_RDWR | O_CREAT); +} + +cc_result File_Read(cc_file file, void* data, cc_uint32 count, cc_uint32* bytesRead) { + *bytesRead = read(file, data, count); + return *bytesRead == -1 ? errno : 0; +} + +cc_result File_Write(cc_file file, const void* data, cc_uint32 count, cc_uint32* bytesWrote) { + *bytesWrote = write(file, data, count); + return *bytesWrote == -1 ? errno : 0; +} + +cc_result File_Close(cc_file file) { + return close(file) == -1 ? errno : 0; +} + +cc_result File_Seek(cc_file file, int offset, int seekType) { + static cc_uint8 modes[3] = { SEEK_SET, SEEK_CUR, SEEK_END }; + return lseek(file, offset, modes[seekType]) == -1 ? errno : 0; +} + +cc_result File_Position(cc_file file, cc_uint32* pos) { + *pos = lseek(file, 0, SEEK_CUR); + return *pos == -1 ? errno : 0; +} + +cc_result File_Length(cc_file file, cc_uint32* len) { + struct stat st; + if (fstat(file, &st) == -1) { *len = -1; return errno; } + *len = st.st_size; return 0; +} + + +/*########################################################################################################################* +*--------------------------------------------------------Threading--------------------------------------------------------* +*#########################################################################################################################*/ +void Thread_Sleep(cc_uint32 milliseconds) { usleep(milliseconds * 1000); } + +static void* ExecThread(void* param) { + ((Thread_StartFunc)param)(); + return NULL; +} + +void* Thread_Create(Thread_StartFunc func) { + return Mem_Alloc(1, sizeof(pthread_t), "thread"); +} + +void Thread_Start2(void* handle, Thread_StartFunc func) { + pthread_t* ptr = (pthread_t*)handle; + int res = pthread_create(ptr, NULL, ExecThread, (void*)func); + if (res) Logger_Abort2(res, "Creating thread"); +} + +void Thread_Detach(void* handle) { + pthread_t* ptr = (pthread_t*)handle; + int res = pthread_detach(*ptr); + if (res) Logger_Abort2(res, "Detaching thread"); + Mem_Free(ptr); +} + +void Thread_Join(void* handle) { + pthread_t* ptr = (pthread_t*)handle; + int res = pthread_join(*ptr, NULL); + if (res) Logger_Abort2(res, "Joining thread"); + Mem_Free(ptr); +} + +void* Mutex_Create(void) { + pthread_mutex_t* ptr = (pthread_mutex_t*)Mem_Alloc(1, sizeof(pthread_mutex_t), "mutex"); + int res = pthread_mutex_init(ptr, NULL); + if (res) Logger_Abort2(res, "Creating mutex"); + return ptr; +} + +void Mutex_Free(void* handle) { + int res = pthread_mutex_destroy((pthread_mutex_t*)handle); + if (res) Logger_Abort2(res, "Destroying mutex"); + Mem_Free(handle); +} + +void Mutex_Lock(void* handle) { + int res = pthread_mutex_lock((pthread_mutex_t*)handle); + if (res) Logger_Abort2(res, "Locking mutex"); +} + +void Mutex_Unlock(void* handle) { + int res = pthread_mutex_unlock((pthread_mutex_t*)handle); + if (res) Logger_Abort2(res, "Unlocking mutex"); +} + +struct WaitData { + pthread_cond_t cond; + pthread_mutex_t mutex; + int signalled; /* For when Waitable_Signal is called before Waitable_Wait */ +}; + +void* Waitable_Create(void) { + struct WaitData* ptr = (struct WaitData*)Mem_Alloc(1, sizeof(struct WaitData), "waitable"); + int res; + + res = pthread_cond_init(&ptr->cond, NULL); + if (res) Logger_Abort2(res, "Creating waitable"); + res = pthread_mutex_init(&ptr->mutex, NULL); + if (res) Logger_Abort2(res, "Creating waitable mutex"); + + ptr->signalled = false; + return ptr; +} + +void Waitable_Free(void* handle) { + struct WaitData* ptr = (struct WaitData*)handle; + int res; + + res = pthread_cond_destroy(&ptr->cond); + if (res) Logger_Abort2(res, "Destroying waitable"); + res = pthread_mutex_destroy(&ptr->mutex); + if (res) Logger_Abort2(res, "Destroying waitable mutex"); + Mem_Free(handle); +} + +void Waitable_Signal(void* handle) { + struct WaitData* ptr = (struct WaitData*)handle; + int res; + + Mutex_Lock(&ptr->mutex); + ptr->signalled = true; + Mutex_Unlock(&ptr->mutex); + + res = pthread_cond_signal(&ptr->cond); + if (res) Logger_Abort2(res, "Signalling event"); +} + +void Waitable_Wait(void* handle) { + struct WaitData* ptr = (struct WaitData*)handle; + int res; + + Mutex_Lock(&ptr->mutex); + if (!ptr->signalled) { + res = pthread_cond_wait(&ptr->cond, &ptr->mutex); + if (res) Logger_Abort2(res, "Waitable wait"); + } + ptr->signalled = false; + Mutex_Unlock(&ptr->mutex); +} + +void Waitable_WaitFor(void* handle, cc_uint32 milliseconds) { + struct WaitData* ptr = (struct WaitData*)handle; + struct timeval tv; + struct timespec ts; + int res; + gettimeofday(&tv, NULL); + + /* absolute time for some silly reason */ + ts.tv_sec = tv.tv_sec + milliseconds / 1000; + ts.tv_nsec = 1000 * (tv.tv_usec + 1000 * (milliseconds % 1000)); + + /* statement above might exceed max nsec, so adjust for that */ + while (ts.tv_nsec >= 1e9) { + ts.tv_sec++; + ts.tv_nsec -= 1e9; + } + + Mutex_Lock(&ptr->mutex); + if (!ptr->signalled) { + res = pthread_cond_timedwait(&ptr->cond, &ptr->mutex, &ts); + if (res && res != ETIMEDOUT) Logger_Abort2(res, "Waitable wait for"); + } + ptr->signalled = false; + Mutex_Unlock(&ptr->mutex); +} + + +/*########################################################################################################################* +*---------------------------------------------------------Socket----------------------------------------------------------* +*#########################################################################################################################*/ +union SocketAddress { + struct sockaddr raw; + struct sockaddr_in v4; + #ifdef AF_INET6 + struct sockaddr_in6 v6; + struct sockaddr_storage total; + #endif +}; + +static cc_result ParseHost(const char* host, int port, cc_sockaddr* addrs, int* numValidAddrs) { + char portRaw[32]; cc_string portStr; + struct addrinfo hints = { 0 }; + struct addrinfo* result; + struct addrinfo* cur; + int res, i = 0; + + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + String_InitArray(portStr, portRaw); + String_AppendInt(&portStr, port); + portRaw[portStr.length] = '\0'; + + res = getaddrinfo(host, portRaw, &hints, &result); + if (res == EAI_AGAIN) return SOCK_ERR_UNKNOWN_HOST; + if (res) return res; + + /* Prefer IPv4 addresses first */ + for (cur = result; cur && i < SOCKET_MAX_ADDRS; cur = cur->ai_next) + { + if (cur->ai_family != AF_INET) continue; + Mem_Copy(addrs[i].data, cur->ai_addr, cur->ai_addrlen); + addrs[i].size = cur->ai_addrlen; i++; + } + + for (cur = result; cur && i < SOCKET_MAX_ADDRS; cur = cur->ai_next) + { + if (cur->ai_family == AF_INET) continue; + Mem_Copy(addrs[i].data, cur->ai_addr, cur->ai_addrlen); + addrs[i].size = cur->ai_addrlen; i++; + } + + freeaddrinfo(result); + *numValidAddrs = i; + return i == 0 ? ERR_INVALID_ARGUMENT : 0; +} + +cc_result Socket_ParseAddress(const cc_string* address, int port, cc_sockaddr* addrs, int* numValidAddrs) { + union SocketAddress* addr = (union SocketAddress*)addrs[0].data; + char str[NATIVE_STR_LEN]; + + String_EncodeUtf8(str, address); + *numValidAddrs = 0; + + if (inet_pton(AF_INET, str, &addr->v4.sin_addr) > 0) { + addr->v4.sin_family = AF_INET; + addr->v4.sin_port = htons(port); + + addrs[0].size = sizeof(addr->v4); + *numValidAddrs = 1; + return 0; + } + + #ifdef AF_INET6 + if (inet_pton(AF_INET6, str, &addr->v6.sin6_addr) > 0) { + addr->v6.sin6_family = AF_INET6; + addr->v6.sin6_port = htons(port); + + addrs[0].size = sizeof(addr->v6); + *numValidAddrs = 1; + return 0; + } + #endif + + return ParseHost(str, port, addrs, numValidAddrs); +} + +cc_result Socket_Connect(cc_socket* s, cc_sockaddr* addr, cc_bool nonblocking) { + struct sockaddr* raw = (struct sockaddr*)addr->data; + cc_result res; + + *s = socket(raw->sa_family, SOCK_STREAM, IPPROTO_TCP); + if (*s == -1) return errno; + + if (nonblocking) { + int blocking_raw = -1; /* non-blocking mode */ + ioctl(*s, FIONBIO, &blocking_raw); + } + + res = connect(*s, raw, addr->size); + return res == -1 ? errno : 0; +} + +cc_result Socket_Read(cc_socket s, cc_uint8* data, cc_uint32 count, cc_uint32* modified) { + int recvCount = recv(s, data, count, 0); + if (recvCount != -1) { *modified = recvCount; return 0; } + *modified = 0; return errno; +} + +cc_result Socket_Write(cc_socket s, const cc_uint8* data, cc_uint32 count, cc_uint32* modified) { + int sentCount = send(s, data, count, 0); + if (sentCount != -1) { *modified = sentCount; return 0; } + *modified = 0; return errno; +} + +void Socket_Close(cc_socket s) { + shutdown(s, SHUT_RDWR); + close(s); +} + +static cc_result Socket_Poll(cc_socket s, int mode, cc_bool* success) { + struct pollfd pfd; + int flags; + + pfd.fd = s; + pfd.events = mode == SOCKET_POLL_READ ? POLLIN : POLLOUT; + if (poll(&pfd, 1, 0) == -1) { *success = false; return errno; } + + /* to match select, closed socket still counts as readable */ + flags = mode == SOCKET_POLL_READ ? (POLLIN | POLLHUP) : POLLOUT; + *success = (pfd.revents & flags) != 0; + return 0; +} + +cc_result Socket_CheckReadable(cc_socket s, cc_bool* readable) { + return Socket_Poll(s, SOCKET_POLL_READ, readable); +} + +cc_result Socket_CheckWritable(cc_socket s, cc_bool* writable) { + socklen_t resultSize = sizeof(socklen_t); + cc_result res = Socket_Poll(s, SOCKET_POLL_WRITE, writable); + if (res || *writable) return res; + + /* https://stackoverflow.com/questions/29479953/so-error-value-after-successful-socket-operation */ + getsockopt(s, SOL_SOCKET, SO_ERROR, &res, &resultSize); + return res; +} + + +/*########################################################################################################################* +*--------------------------------------------------------Platform---------------------------------------------------------* +*#########################################################################################################################*/ +static void CreateRootDirectory(void) { + int res = mkdir(root_path.buffer, 0); + int err = res == -1 ? errno : 0; + Platform_Log1("Created root directory: %i", &err); +} + +void Platform_Init(void) { + // TODO: Redesign Drawer2D to better handle this + //Options_SetBool(OPT_USE_CHAT_FONT, true); + + CreateRootDirectory(); + + socketInitializeDefault(); + + // Configure our supported input layout: a single player with standard controller styles + padConfigureInput(1, HidNpadStyleSet_NpadStandard); + hidInitializeTouchScreen(); +} +void Platform_Free(void) { + socketExit(); +} + +cc_bool Platform_DescribeError(cc_result res, cc_string* dst) { + char chars[NATIVE_STR_LEN]; + int len; + + /* For unrecognised error codes, strerror_r might return messages */ + /* such as 'No error information', which is not very useful */ + /* (could check errno here but quicker just to skip entirely) */ + if (res >= 1000) return false; + + len = strerror_r(res, chars, NATIVE_STR_LEN); + if (len == -1) return false; + + len = String_CalcLen(chars, NATIVE_STR_LEN); + String_AppendUtf8(dst, chars, len); + return true; +} + + +/*########################################################################################################################* +*-------------------------------------------------------Encryption--------------------------------------------------------* +*#########################################################################################################################*/ +static cc_result GetMachineID(cc_uint32* key) { + return ERR_NOT_SUPPORTED; +} +#endif diff --git a/src/Window_Switch.c b/src/Window_Switch.c new file mode 100644 index 000000000..5a537d516 --- /dev/null +++ b/src/Window_Switch.c @@ -0,0 +1,380 @@ +#include "Core.h" +#if defined CC_BUILD_SWITCH +#include "_WindowBase.h" +#include "Window.h" +#include "Platform.h" +#include "Input.h" +#include "Event.h" +#include "Graphics.h" +#include "String.h" +#include "Funcs.h" +#include "Bitmap.h" +#include "Errors.h" +#include "ExtMath.h" +#include "Input.h" +#include + +#include // EGL library +#include // EGL extensions +#include // glad library (OpenGL loader) + + +static EGLDisplay s_display; +static EGLContext s_context; +static EGLSurface s_surface; + +static cc_bool launcherMode; +static Framebuffer fb; +static PadState pad; + +struct _DisplayData DisplayInfo; +struct _WindowData WindowInfo; + +void Window_Init(void) { + // Initialize the default gamepad (which reads handheld mode inputs as well as the first connected controller) + padInitializeDefault(&pad); + + DisplayInfo.Width = 1280; + DisplayInfo.Height = 720; + DisplayInfo.Depth = 4; // 32 bit + DisplayInfo.ScaleX = 1; + DisplayInfo.ScaleY = 1; + + Window_Main.Width = DisplayInfo.Width; + Window_Main.Height = DisplayInfo.Height; + Window_Main.Focused = true; + Window_Main.Exists = true; + + Input_SetTouchMode(true); + Input.Sources = INPUT_SOURCE_GAMEPAD; +} + +void Window_Free(void) { + if (launcherMode) + framebufferClose(&fb); +} + +void Window_Create2D(int width, int height) { + framebufferCreate(&fb, nwindowGetDefault(), 1280, 720, PIXEL_FORMAT_BGRA_8888, 2); + framebufferMakeLinear(&fb); + launcherMode = true; +} +void Window_Create3D(int width, int height) { + framebufferClose(&fb); + launcherMode = false; +} + +void Window_SetTitle(const cc_string* title) { } +void Clipboard_GetText(cc_string* value) { } +void Clipboard_SetText(const cc_string* value) { } + +int Window_GetWindowState(void) { return WINDOW_STATE_FULLSCREEN; } +cc_result Window_EnterFullscreen(void) { return 0; } +cc_result Window_ExitFullscreen(void) { return 0; } +int Window_IsObscured(void) { return 0; } + +void Window_Show(void) { } +void Window_SetSize(int width, int height) { } + +void Window_RequestClose(void) { + Event_RaiseVoid(&WindowEvents.Closing); +} + + +/*########################################################################################################################* +*----------------------------------------------------Input processing-----------------------------------------------------* +*#########################################################################################################################*/ +static void HandleButtons(u64 mods) { + Input_SetNonRepeatable(CCPAD_L, mods & HidNpadButton_L); + Input_SetNonRepeatable(CCPAD_R, mods & HidNpadButton_R); + + Input_SetNonRepeatable(CCPAD_A, mods & HidNpadButton_A); + Input_SetNonRepeatable(CCPAD_B, mods & HidNpadButton_B); + Input_SetNonRepeatable(CCPAD_X, mods & HidNpadButton_X); + Input_SetNonRepeatable(CCPAD_Y, mods & HidNpadButton_Y); + + Input_SetNonRepeatable(CCPAD_START, mods & HidNpadButton_Plus); + Input_SetNonRepeatable(CCPAD_SELECT, mods & HidNpadButton_Minus); + + Input_SetNonRepeatable(CCPAD_LEFT, mods & HidNpadButton_Left); + Input_SetNonRepeatable(CCPAD_RIGHT, mods & HidNpadButton_Right); + Input_SetNonRepeatable(CCPAD_UP, mods & HidNpadButton_Up); + Input_SetNonRepeatable(CCPAD_DOWN, mods & HidNpadButton_Down); +} + +static void ProcessJoystickInput(HidAnalogStickState* pos) { + // May not be exactly 0 on actual hardware + if (Math_AbsI(pos->x) <= 16) pos->x = 0; + if (Math_AbsI(pos->y) <= 16) pos->y = 0; + + Event_RaiseRawMove(&ControllerEvents.RawMoved, pos->x / 512.f, -pos->y / 512.f); +} + +static void ProcessTouchInput(void) { + static int currX, currY, prev_touchcount=0; + HidTouchScreenState state={0}; + hidGetTouchScreenStates(&state, 1); + + if (state.count && !prev_touchcount) { // stylus went down + currX = state.touches[0].x; + currY = state.touches[0].y; + Input_AddTouch(0, currX, currY); + } + else if (state.count) { // stylus is down + currX = state.touches[0].x; + currY = state.touches[0].y; + Input_UpdateTouch(0, currX, currY); + } + else if (!state.count && prev_touchcount) { // stylus was lifted + Input_RemoveTouch(0, currX, currY); + } + + prev_touchcount = state.count; +} + +void Window_ProcessEvents(double delta) { + // Scan the gamepad. This should be done once for each frame + padUpdate(&pad); + + if (!appletMainLoop()) { + Window_Main.Exists = false; + Window_RequestClose(); + return; + } + + u64 keys = padGetButtons(&pad); + HandleButtons(keys); + + // Read the sticks' position + HidAnalogStickState analog_stick_l = padGetStickPos(&pad, 0); + HidAnalogStickState analog_stick_r = padGetStickPos(&pad, 1); + ProcessJoystickInput(&analog_stick_l); + ProcessJoystickInput(&analog_stick_r); + + ProcessTouchInput(); +} + +void Cursor_SetPosition(int x, int y) { } // Makes no sense for PSP +void Window_EnableRawMouse(void) { Input.RawMode = true; } +void Window_DisableRawMouse(void) { Input.RawMode = false; } + +void Window_UpdateRawMouse(void) { } + + +/*########################################################################################################################* +*------------------------------------------------------Framebuffer--------------------------------------------------------* +*#########################################################################################################################*/ +void Window_AllocFramebuffer(struct Bitmap* bmp) { + bmp->scan0 = (BitmapCol*)Mem_Alloc(bmp->width * bmp->height, 4, "window pixels"); +} + +void Window_DrawFramebuffer(Rect2D r, struct Bitmap* bmp) { + // Retrieve the framebuffer + cc_uint32 stride; + cc_uint32* framebuf = (cc_uint32*) framebufferBegin(&fb, &stride); + + // flip upside down + for (cc_uint32 y=r.y; yscan0[pos]; + } + } + + // We're done rendering, so we end the frame here. + framebufferEnd(&fb); +} + +void Window_FreeFramebuffer(struct Bitmap* bmp) { + Mem_Free(bmp->scan0); +} + +/*########################################################################################################################* +*-----------------------------------------------------OpenGL context------------------------------------------------------* +*#########################################################################################################################*/ +void GLContext_Create(void) { + EGLint err; + + // Retrieve the default window + NWindow* win = nwindowGetDefault(); + + // Connect to the EGL default display + s_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (!s_display) + { + err = eglGetError(); + Platform_Log1("Could not connect to display! error: %d", &err); + return; + } + + // Initialize the EGL display connection + eglInitialize(s_display, NULL, NULL); + + // Select OpenGL ES as the desired graphics API + if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) + { + err = eglGetError(); + Platform_Log1("Could not set API! error: %d", &err); + eglTerminate(s_display); + s_display = NULL; + return; + } + + // Get an appropriate EGL framebuffer configuration + EGLConfig config; + EGLint numConfigs; + static const EGLint framebufferAttributeList[] = + { + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, 0, + EGL_GREEN_SIZE, 0, + EGL_BLUE_SIZE, 0, + EGL_ALPHA_SIZE, 0, + EGL_DEPTH_SIZE, GLCONTEXT_DEFAULT_DEPTH, + EGL_STENCIL_SIZE, 0, + EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_NONE + }; + eglChooseConfig(s_display, framebufferAttributeList, &config, 1, &numConfigs); + if (numConfigs == 0) + { + err = eglGetError(); + Platform_Log1("No config found! error: %d", &err); + eglTerminate(s_display); + s_display = NULL; + return; + } + + // Create an EGL window surface + s_surface = eglCreateWindowSurface(s_display, config, win, NULL); + if (!s_surface) + { + err = eglGetError(); + Platform_Log1("Surface creation failed! error: %d", &err); + eglTerminate(s_display); + s_display = NULL; + return; + } + + // Create an EGL rendering context + static const EGLint contextAttributeList[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; + s_context = eglCreateContext(s_display, config, EGL_NO_CONTEXT, contextAttributeList); + if (!s_context) + { + err = eglGetError(); + Platform_Log1("Context creation failed! error: %d", &err); + eglDestroySurface(s_display, s_surface); + s_surface = NULL; + } + + // Connect the context to the surface + eglMakeCurrent(s_display, s_surface, s_surface, s_context); + + gladLoadGL(); +} + +void GLContext_Update(void) { } + +cc_bool GLContext_TryRestore(void) { + return true; +} + +void GLContext_Free(void) { + if (s_display) + { + eglMakeCurrent(s_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if (s_context) + { + eglDestroyContext(s_display, s_context); + s_context = NULL; + } + if (s_surface) + { + eglDestroySurface(s_display, s_surface); + s_surface = NULL; + } + eglTerminate(s_display); + s_display = NULL; + } +} + +void* GLContext_GetAddress(const char* function) { + return (void*)eglGetProcAddress(function); +} + +cc_bool GLContext_SwapBuffers(void) { + eglSwapBuffers(s_display, s_surface); + return true; +} + +void GLContext_SetFpsLimit(cc_bool vsync, float minFrameMs) { } + +void GLContext_GetApiInfo(cc_string* info) { } + + +/*########################################################################################################################* +*------------------------------------------------------Soft keyboard------------------------------------------------------* +*#########################################################################################################################*/ +static void OnscreenTextChanged(const char* text) { + char tmpBuffer[NATIVE_STR_LEN]; + cc_string tmp = String_FromArray(tmpBuffer); + String_AppendUtf8(&tmp, text, String_Length(text)); + + Event_RaiseString(&InputEvents.TextChanged, &tmp); +} + +void Window_OpenKeyboard(struct OpenKeyboardArgs* args) { + const char* btnText = args->type & KEYBOARD_FLAG_SEND ? "Send" : "Enter"; + char input[NATIVE_STR_LEN] = { 0 }; + char output[NATIVE_STR_LEN] = { 0 }; + String_EncodeUtf8(input, args->text); + + int mode = args->type & 0xFF; + SwkbdType type = (mode == KEYBOARD_TYPE_NUMBER || mode == KEYBOARD_TYPE_INTEGER) ? SwkbdType_NumPad : SwkbdType_Normal; + + SwkbdConfig kbd; + swkbdCreate(&kbd, 0); + + if (mode == KEYBOARD_TYPE_PASSWORD) + swkbdConfigMakePresetPassword(&kbd); + else + { + swkbdConfigMakePresetDefault(&kbd); + swkbdConfigSetType(&kbd, type); + } + + swkbdConfigSetInitialText(&kbd, input); + swkbdConfigSetGuideText(&kbd, args->placeholder); + swkbdConfigSetOkButtonText(&kbd, btnText); + + Result rc = swkbdShow(&kbd, output, sizeof(output)); + if (R_SUCCEEDED(rc)) + OnscreenTextChanged(output); + + swkbdClose(&kbd); +} +void Window_SetKeyboardText(const cc_string* text) { } +void Window_CloseKeyboard(void) { /* TODO implement */ } + + +/*########################################################################################################################* +*-------------------------------------------------------Misc/Other--------------------------------------------------------* +*#########################################################################################################################*/ +//void Window_ShowDialog(const char* title, const char* msg) { +static void ShowDialogCore(const char* title, const char* msg) { + ErrorApplicationConfig c; + errorApplicationCreate(&c, title, msg); + errorApplicationShow(&c); +} + +cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* args) { + return ERR_NOT_SUPPORTED; +} + +cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args) { + return ERR_NOT_SUPPORTED; +} +#endif From 7ee08a5c162510f1b91e5d635db9ad0090513c79 Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Fri, 8 Mar 2024 18:23:23 -0400 Subject: [PATCH 02/34] add -lpthread just in case --- misc/switch/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/switch/Makefile b/misc/switch/Makefile index 68f788575..007e678d9 100644 --- a/misc/switch/Makefile +++ b/misc/switch/Makefile @@ -63,7 +63,7 @@ CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions ASFLAGS := -g $(ARCH) LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) -LIBS := -lGLESv2 -lglad -lEGL -lglapi -ldrm_nouveau -lnx -lm +LIBS := -lGLESv2 -lglad -lEGL -lglapi -ldrm_nouveau -lnx -lm -lpthread #--------------------------------------------------------------------------------- # list of directories containing libraries, this must be the top level containing From 05a7e82582408f97a02b7444c53f19bebe888ed7 Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Fri, 8 Mar 2024 18:23:43 -0400 Subject: [PATCH 03/34] comment out Thread_Detach code --- src/Platform_Switch.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Platform_Switch.c b/src/Platform_Switch.c index f46e8c635..ce3c95fa3 100644 --- a/src/Platform_Switch.c +++ b/src/Platform_Switch.c @@ -253,10 +253,11 @@ void Thread_Start2(void* handle, Thread_StartFunc func) { } void Thread_Detach(void* handle) { - pthread_t* ptr = (pthread_t*)handle; + // crashes when loading singleplayer. pthread_detach isn't implemented, apparently? + /*pthread_t* ptr = (pthread_t*)handle; int res = pthread_detach(*ptr); if (res) Logger_Abort2(res, "Detaching thread"); - Mem_Free(ptr); + Mem_Free(ptr);*/ } void Thread_Join(void* handle) { From 198c6e18cc4f05d6dd509a1e79a7d6bec567b915 Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Sat, 9 Mar 2024 13:35:41 -0400 Subject: [PATCH 04/34] implement switch audren driver temporarily calls audrvUpdate() on Window_ProcessEvents. need an audio update function --- src/Audio.c | 140 +++++++++++++++++++++++++++++++++++++++----- src/Window_Switch.c | 5 ++ 2 files changed, 129 insertions(+), 16 deletions(-) diff --git a/src/Audio.c b/src/Audio.c index e8fb6a895..c44141e3c 100644 --- a/src/Audio.c +++ b/src/Audio.c @@ -925,14 +925,28 @@ void Audio_FreeChunks(void** chunks, int numChunks) { *#########################################################################################################################*/ #include struct AudioContext { + int chanID, used; AudioDriverWaveBuf bufs[AUDIO_MAX_BUFFERS]; int count, channels, sampleRate; void* _tmpData; int _tmpSize; + cc_bool stereo; }; -//static int channelIDs; -static AudioDriver drv; +struct AudioMemPools { + void* chunk; + int mpid; +}; + +static int channelIDs; +static struct AudioMemPools audioPools[64]; +AudioDriver drv; +bool switchAudio = false; static cc_bool AudioBackend_Init(void) { + if (switchAudio) return true; + switchAudio = true; + + Mem_Set(audioPools, 0, sizeof(audioPools)); + static const AudioRendererConfig arConfig = { .output_rate = AudioRendererOutputRate_48kHz, @@ -945,21 +959,33 @@ static cc_bool AudioBackend_Init(void) { Result res; res = audrenInitialize(&arConfig); - Platform_Log1("audrenInitialize: %i", &res); + //Platform_Log1("audrenInitialize: %" PRIx32 "\n", &res); res = audrvCreate(&drv, &arConfig, 2); - Platform_Log1("audrvCreate: %i", &res); + //Platform_Log1("audrvCreate: %" PRIx32 "\n", &res); + + static const u8 sink_channels[] = { 0, 1 }; + /*int sink =*/ audrvDeviceSinkAdd(&drv, AUDREN_DEFAULT_DEVICE_NAME, 2, sink_channels); + + res = audrvUpdate(&drv); + //Platform_Log1("audrvUpdate: %" PRIx32 "\n", &res); + + res = audrenStartAudioRenderer(); + //Platform_Log1("audrenStartAudioRenderer: %" PRIx32 "\n", &res); return R_SUCCEEDED(res); } static void AudioBackend_Free(void) { + if (!switchAudio) return; + switchAudio = false; + audrvClose(&drv); audrenExit(); } #define AUDIO_HAS_BACKEND void Audio_Init(struct AudioContext* ctx, int buffers) { - /*int chanID = -1; + int chanID = -1; for (int i = 0; i < 24; i++) { @@ -974,31 +1000,63 @@ void Audio_Init(struct AudioContext* ctx, int buffers) { ctx->count = buffers; ctx->chanID = chanID; ctx->used = true; - - ndspChnSetInterp(ctx->chanID, NDSP_INTERP_LINEAR);*/ } void Audio_Close(struct AudioContext* ctx) { - /*if (ctx->used) { - ndspChnWaveBufClear(ctx->chanID); + if (ctx->used) { + audrvVoiceStop(&drv, ctx->chanID); ctx->channels &= ~(1 << ctx->chanID); channelIDs &= ~(1 << ctx->chanID); } - ctx->used = false;*/ + ctx->used = false; AudioBase_Clear(ctx); } cc_result Audio_SetFormat(struct AudioContext* ctx, int channels, int sampleRate) { - /*ctx->stereo = (channels == 2); + /* int fmt = ctx->stereo ? NDSP_FORMAT_STEREO_PCM16 : NDSP_FORMAT_MONO_PCM16; ndspChnSetFormat(ctx->chanID, fmt); ndspChnSetRate(ctx->chanID, sampleRate);*/ + ctx->stereo = (channels == 2); + ctx->channels = channels; + ctx->sampleRate = sampleRate; return 0; } cc_result Audio_QueueChunk(struct AudioContext* ctx, void* chunk, cc_uint32 dataSize) { + AudioDriverWaveBuf* buf; + + // Audio buffers must be aligned to a multiple of 0x1000, according to libnx example code + if (((uintptr_t)chunk & 0xFFF) != 0) { + Platform_Log1("Audio_QueueData: tried to queue buffer with non-aligned audio buffer 0x%x\n", &chunk); + } + if ((dataSize & 0xFFF) != 0) { + Platform_Log1("Audio_QueueData: unaligned audio data size 0x%x\n", &dataSize); + } + + for (int i = 0; i < ctx->count; i++) + { + buf = &ctx->bufs[i]; + //Platform_Log2("QUEUE_CHUNK: %i = %i", &ctx->chanID, &buf->status); + if (buf->state == AudioDriverWaveBufState_Queued || buf->state == AudioDriverWaveBufState_Playing) + continue; + + buf->data_pcm16 = chunk; + buf->size = dataSize / (sizeof(cc_int16) * (ctx->stereo ? 2 : 1)); + buf->start_sample_offset = 0; + buf->end_sample_offset = dataSize/2; + //Platform_Log1("PLAYING ON: %i", &ctx->chanID); + armDCacheFlush(buf->data_pcm16, dataSize); + audrvVoiceInit(&drv, ctx->chanID, 1, PcmFormat_Int16, ctx->sampleRate); + audrvVoiceSetDestinationMix(&drv, ctx->chanID, AUDREN_FINAL_MIX_ID); + audrvVoiceSetMixFactor(&drv, ctx->chanID, 1.0f, 0, 0); + audrvVoiceSetMixFactor(&drv, ctx->chanID, 1.0f, 0, 1); + audrvVoiceAddWaveBuf(&drv, ctx->chanID, buf); + return 0; + } + /*ndspWaveBuf* buf; // DSP audio buffers must be aligned to a multiple of 0x80, according to the example code I could find. @@ -1022,13 +1080,15 @@ cc_result Audio_QueueChunk(struct AudioContext* ctx, void* chunk, cc_uint32 data DSP_FlushDataCache(buf->data_pcm16, dataSize); ndspChnWaveBufAdd(ctx->chanID, buf); return 0; - } + }*/ // tried to queue data without polling for free buffers first - return ERR_INVALID_ARGUMENT;*/ - return 0; + return ERR_INVALID_ARGUMENT; } -cc_result Audio_Play(struct AudioContext* ctx) { return 0; } +cc_result Audio_Play(struct AudioContext* ctx) { + audrvVoiceStart(&drv, ctx->chanID); + return 0; +} cc_result Audio_Poll(struct AudioContext* ctx, int* inUse) { /*ndspWaveBuf* buf; @@ -1044,6 +1104,20 @@ cc_result Audio_Poll(struct AudioContext* ctx, int* inUse) { } *inUse = count; */ + + AudioDriverWaveBuf* buf; + int count = 0; + + for (int i = 0; i < ctx->count; i++) + { + buf = &ctx->bufs[i]; + //Platform_Log2("CHECK_CHUNK: %i = %i", &ctx->chanID, &buf->status); + if (buf->state == AudioDriverWaveBufState_Queued || buf->state == AudioDriverWaveBufState_Playing) { + count++; continue; + } + } + + *inUse = count; return 0; } @@ -1063,6 +1137,13 @@ cc_result Audio_PlayData(struct AudioContext* ctx, struct AudioData* data) { if ((res = Audio_SetFormat(ctx, data->channels, data->sampleRate))) return res; if ((res = Audio_QueueChunk(ctx, data->data, data->size))) return res;*/ + + data->sampleRate = Audio_AdjustSampleRate(data); + cc_result res; + + if ((res = Audio_SetFormat(ctx, data->channels, data->sampleRate))) return res; + if ((res = Audio_QueueChunk(ctx, data->data, data->size))) return res; + if ((res = Audio_Play(ctx))) return res; return 0; } @@ -1076,10 +1157,37 @@ void Audio_AllocChunks(cc_uint32 size, void** chunks, int numChunks) { for (int i = 0; i < numChunks; i++) { chunks[i] = dst ? (dst + size * i) : NULL; }*/ + size = (size + 0xFFF) & ~0xFFF; // round up to nearest multiple of 0x1000 + cc_uint8* dst = (cc_uint8*)memalign(0x1000, size * numChunks); + armDCacheFlush(dst, size * numChunks); + + for (int i = 0; i < numChunks; i++) { + int mpid = audrvMemPoolAdd(&drv, dst + size * i, size); + audrvMemPoolAttach(&drv, mpid); + + for (int j = 0; j < 64; j++) { + if (audioPools[j].chunk != NULL) continue; + audioPools[j].chunk = chunks[0]; + audioPools[j].mpid = mpid; + break; + } + + chunks[i] = dst ? (dst + size * i) : NULL; + } } void Audio_FreeChunks(void** chunks, int numChunks) { - //linearFree(chunks[0]); + // remove memory pool from audren + for (int i=0; i<64; i++) { + if (audioPools[i].chunk == chunks[0]) { + audrvMemPoolDetach(&drv, audioPools[i].mpid); + audrvMemPoolRemove(&drv, audioPools[i].mpid); + Mem_Set(&audioPools[i], 0, sizeof(struct AudioMemPools)); + break; + } + } + + Mem_Free(chunks[0]); } #elif defined CC_BUILD_DREAMCAST /*########################################################################################################################* diff --git a/src/Window_Switch.c b/src/Window_Switch.c index 5a537d516..9930a02cd 100644 --- a/src/Window_Switch.c +++ b/src/Window_Switch.c @@ -22,6 +22,8 @@ static EGLDisplay s_display; static EGLContext s_context; static EGLSurface s_surface; +extern AudioDriver drv; // temporary +extern bool switchAudio; // temporary static cc_bool launcherMode; static Framebuffer fb; @@ -136,6 +138,9 @@ void Window_ProcessEvents(double delta) { // Scan the gamepad. This should be done once for each frame padUpdate(&pad); + if (switchAudio) + audrvUpdate(&drv); + if (!appletMainLoop()) { Window_Main.Exists = false; Window_RequestClose(); From ec4959c57eb21cab3fe26ff3a320b26ab7c1d01b Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Sat, 9 Mar 2024 14:41:58 -0400 Subject: [PATCH 05/34] fix some mistakes on switch audren --- src/Audio.c | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/Audio.c b/src/Audio.c index c44141e3c..19305f505 100644 --- a/src/Audio.c +++ b/src/Audio.c @@ -1044,15 +1044,16 @@ cc_result Audio_QueueChunk(struct AudioContext* ctx, void* chunk, cc_uint32 data continue; buf->data_pcm16 = chunk; - buf->size = dataSize / (sizeof(cc_int16) * (ctx->stereo ? 2 : 1)); + buf->size = dataSize; buf->start_sample_offset = 0; buf->end_sample_offset = dataSize/2; //Platform_Log1("PLAYING ON: %i", &ctx->chanID); armDCacheFlush(buf->data_pcm16, dataSize); - audrvVoiceInit(&drv, ctx->chanID, 1, PcmFormat_Int16, ctx->sampleRate); + audrvVoiceStop(&drv, ctx->chanID); + audrvVoiceInit(&drv, ctx->chanID, ctx->channels, PcmFormat_Int16, ctx->sampleRate); audrvVoiceSetDestinationMix(&drv, ctx->chanID, AUDREN_FINAL_MIX_ID); - audrvVoiceSetMixFactor(&drv, ctx->chanID, 1.0f, 0, 0); - audrvVoiceSetMixFactor(&drv, ctx->chanID, 1.0f, 0, 1); + //audrvVoiceSetMixFactor(&drv, ctx->chanID, 1.0f, 0, 0); + //audrvVoiceSetMixFactor(&drv, ctx->chanID, 1.0f, 0, 1); audrvVoiceAddWaveBuf(&drv, ctx->chanID, buf); return 0; } @@ -1159,31 +1160,33 @@ void Audio_AllocChunks(cc_uint32 size, void** chunks, int numChunks) { }*/ size = (size + 0xFFF) & ~0xFFF; // round up to nearest multiple of 0x1000 cc_uint8* dst = (cc_uint8*)memalign(0x1000, size * numChunks); - armDCacheFlush(dst, size * numChunks); + //armDCacheFlush(dst, size * numChunks); for (int i = 0; i < numChunks; i++) { int mpid = audrvMemPoolAdd(&drv, dst + size * i, size); audrvMemPoolAttach(&drv, mpid); + chunks[i] = dst ? (dst + size * i) : NULL; + for (int j = 0; j < 64; j++) { if (audioPools[j].chunk != NULL) continue; - audioPools[j].chunk = chunks[0]; + audioPools[j].chunk = chunks[i]; audioPools[j].mpid = mpid; break; } - - chunks[i] = dst ? (dst + size * i) : NULL; } } void Audio_FreeChunks(void** chunks, int numChunks) { // remove memory pool from audren - for (int i=0; i<64; i++) { - if (audioPools[i].chunk == chunks[0]) { - audrvMemPoolDetach(&drv, audioPools[i].mpid); - audrvMemPoolRemove(&drv, audioPools[i].mpid); - Mem_Set(&audioPools[i], 0, sizeof(struct AudioMemPools)); - break; + for (int i=0; i Date: Sat, 9 Mar 2024 19:46:37 -0400 Subject: [PATCH 06/34] replace pthread with libnx threads --- misc/switch/Makefile | 2 +- src/Platform_Switch.c | 85 ++++++++++++++----------------------------- 2 files changed, 29 insertions(+), 58 deletions(-) diff --git a/misc/switch/Makefile b/misc/switch/Makefile index 007e678d9..68f788575 100644 --- a/misc/switch/Makefile +++ b/misc/switch/Makefile @@ -63,7 +63,7 @@ CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions ASFLAGS := -g $(ARCH) LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) -LIBS := -lGLESv2 -lglad -lEGL -lglapi -ldrm_nouveau -lnx -lm -lpthread +LIBS := -lGLESv2 -lglad -lEGL -lglapi -ldrm_nouveau -lnx -lm #--------------------------------------------------------------------------------- # list of directories containing libraries, this must be the top level containing diff --git a/src/Platform_Switch.c b/src/Platform_Switch.c index ce3c95fa3..1f926994c 100644 --- a/src/Platform_Switch.c +++ b/src/Platform_Switch.c @@ -235,21 +235,23 @@ cc_result File_Length(cc_file file, cc_uint32* len) { /*########################################################################################################################* *--------------------------------------------------------Threading--------------------------------------------------------* *#########################################################################################################################*/ -void Thread_Sleep(cc_uint32 milliseconds) { usleep(milliseconds * 1000); } +void Thread_Sleep(cc_uint32 milliseconds) { + cc_uint64 timeout_ns = milliseconds * (1000 * 1000); // to nanoseconds + svcSleepThread(timeout_ns); +} -static void* ExecThread(void* param) { +static void ExecSwitchThread(void* param) { ((Thread_StartFunc)param)(); - return NULL; } void* Thread_Create(Thread_StartFunc func) { - return Mem_Alloc(1, sizeof(pthread_t), "thread"); + Thread* thread = (Thread*)Mem_Alloc(1, sizeof(Thread), "thread"); + threadCreate(thread, ExecSwitchThread, (void*)func, NULL, 0x10000, 0x2C, -2); + return thread; } void Thread_Start2(void* handle, Thread_StartFunc func) { - pthread_t* ptr = (pthread_t*)handle; - int res = pthread_create(ptr, NULL, ExecThread, (void*)func); - if (res) Logger_Abort2(res, "Creating thread"); + threadStart((Thread*)handle); } void Thread_Detach(void* handle) { @@ -261,49 +263,41 @@ void Thread_Detach(void* handle) { } void Thread_Join(void* handle) { - pthread_t* ptr = (pthread_t*)handle; - int res = pthread_join(*ptr, NULL); - if (res) Logger_Abort2(res, "Joining thread"); - Mem_Free(ptr); + Thread* thread = (Thread*)handle; + threadWaitForExit(thread); + threadClose(thread); + Mem_Free(thread); } void* Mutex_Create(void) { - pthread_mutex_t* ptr = (pthread_mutex_t*)Mem_Alloc(1, sizeof(pthread_mutex_t), "mutex"); - int res = pthread_mutex_init(ptr, NULL); - if (res) Logger_Abort2(res, "Creating mutex"); - return ptr; + Mutex* mutex = (Mutex*)Mem_Alloc(1, sizeof(Mutex), "mutex"); + mutexInit(mutex); + return mutex; } void Mutex_Free(void* handle) { - int res = pthread_mutex_destroy((pthread_mutex_t*)handle); - if (res) Logger_Abort2(res, "Destroying mutex"); Mem_Free(handle); } void Mutex_Lock(void* handle) { - int res = pthread_mutex_lock((pthread_mutex_t*)handle); - if (res) Logger_Abort2(res, "Locking mutex"); + mutexLock((Mutex*)handle); } void Mutex_Unlock(void* handle) { - int res = pthread_mutex_unlock((pthread_mutex_t*)handle); - if (res) Logger_Abort2(res, "Unlocking mutex"); + mutexUnlock((Mutex*)handle); } struct WaitData { - pthread_cond_t cond; - pthread_mutex_t mutex; + CondVar cond; + Mutex mutex; int signalled; /* For when Waitable_Signal is called before Waitable_Wait */ }; void* Waitable_Create(void) { struct WaitData* ptr = (struct WaitData*)Mem_Alloc(1, sizeof(struct WaitData), "waitable"); - int res; - res = pthread_cond_init(&ptr->cond, NULL); - if (res) Logger_Abort2(res, "Creating waitable"); - res = pthread_mutex_init(&ptr->mutex, NULL); - if (res) Logger_Abort2(res, "Creating waitable mutex"); + mutexInit(&ptr->mutex); + condvarInit(&ptr->cond); ptr->signalled = false; return ptr; @@ -311,35 +305,25 @@ void* Waitable_Create(void) { void Waitable_Free(void* handle) { struct WaitData* ptr = (struct WaitData*)handle; - int res; - - res = pthread_cond_destroy(&ptr->cond); - if (res) Logger_Abort2(res, "Destroying waitable"); - res = pthread_mutex_destroy(&ptr->mutex); - if (res) Logger_Abort2(res, "Destroying waitable mutex"); - Mem_Free(handle); + Mem_Free(ptr); } void Waitable_Signal(void* handle) { struct WaitData* ptr = (struct WaitData*)handle; - int res; Mutex_Lock(&ptr->mutex); - ptr->signalled = true; + condvarWakeOne(&ptr->cond); Mutex_Unlock(&ptr->mutex); - res = pthread_cond_signal(&ptr->cond); - if (res) Logger_Abort2(res, "Signalling event"); + ptr->signalled = true; } void Waitable_Wait(void* handle) { struct WaitData* ptr = (struct WaitData*)handle; - int res; Mutex_Lock(&ptr->mutex); if (!ptr->signalled) { - res = pthread_cond_wait(&ptr->cond, &ptr->mutex); - if (res) Logger_Abort2(res, "Waitable wait"); + condvarWait(&ptr->cond, &ptr->mutex); } ptr->signalled = false; Mutex_Unlock(&ptr->mutex); @@ -347,25 +331,12 @@ void Waitable_Wait(void* handle) { void Waitable_WaitFor(void* handle, cc_uint32 milliseconds) { struct WaitData* ptr = (struct WaitData*)handle; - struct timeval tv; - struct timespec ts; - int res; - gettimeofday(&tv, NULL); - /* absolute time for some silly reason */ - ts.tv_sec = tv.tv_sec + milliseconds / 1000; - ts.tv_nsec = 1000 * (tv.tv_usec + 1000 * (milliseconds % 1000)); - - /* statement above might exceed max nsec, so adjust for that */ - while (ts.tv_nsec >= 1e9) { - ts.tv_sec++; - ts.tv_nsec -= 1e9; - } + cc_uint64 timeout_ns = milliseconds * (1000 * 1000); // to nanoseconds Mutex_Lock(&ptr->mutex); if (!ptr->signalled) { - res = pthread_cond_timedwait(&ptr->cond, &ptr->mutex, &ts); - if (res && res != ETIMEDOUT) Logger_Abort2(res, "Waitable wait for"); + condvarWaitTimeout(&ptr->cond, &ptr->mutex, timeout_ns); } ptr->signalled = false; Mutex_Unlock(&ptr->mutex); From f14e3a55f5462850bf2f4892eab02d18c7939034 Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Sat, 9 Mar 2024 21:06:05 -0400 Subject: [PATCH 07/34] add Thread_Detach for switch --- src/Platform_Switch.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Platform_Switch.c b/src/Platform_Switch.c index 1f926994c..e596f9df2 100644 --- a/src/Platform_Switch.c +++ b/src/Platform_Switch.c @@ -255,11 +255,9 @@ void Thread_Start2(void* handle, Thread_StartFunc func) { } void Thread_Detach(void* handle) { - // crashes when loading singleplayer. pthread_detach isn't implemented, apparently? - /*pthread_t* ptr = (pthread_t*)handle; - int res = pthread_detach(*ptr); - if (res) Logger_Abort2(res, "Detaching thread"); - Mem_Free(ptr);*/ + Thread* thread = (Thread*)handle; + threadClose(thread); + Mem_Free(thread); } void Thread_Join(void* handle) { From 8566ac96319f8756fc7ad32ef3f9d84b716c6454 Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Sat, 9 Mar 2024 21:38:58 -0400 Subject: [PATCH 08/34] use LEvent (light events) for waitable also left Thread_Detach() empty --- src/Platform_Switch.c | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/src/Platform_Switch.c b/src/Platform_Switch.c index e596f9df2..d8f52821f 100644 --- a/src/Platform_Switch.c +++ b/src/Platform_Switch.c @@ -254,11 +254,7 @@ void Thread_Start2(void* handle, Thread_StartFunc func) { threadStart((Thread*)handle); } -void Thread_Detach(void* handle) { - Thread* thread = (Thread*)handle; - threadClose(thread); - Mem_Free(thread); -} +void Thread_Detach(void* handle) { } void Thread_Join(void* handle) { Thread* thread = (Thread*)handle; @@ -285,10 +281,11 @@ void Mutex_Unlock(void* handle) { mutexUnlock((Mutex*)handle); } +/* struct WaitData { CondVar cond; Mutex mutex; - int signalled; /* For when Waitable_Signal is called before Waitable_Wait */ + int signalled; /& For when Waitable_Signal is called before Waitable_Wait }; void* Waitable_Create(void) { @@ -339,6 +336,32 @@ void Waitable_WaitFor(void* handle, cc_uint32 milliseconds) { ptr->signalled = false; Mutex_Unlock(&ptr->mutex); } +*/ + +void* Waitable_Create(void) { + LEvent* ptr = (LEvent*)Mem_Alloc(1, sizeof(LEvent), "waitable"); + leventInit(ptr, false, true); + return ptr; +} + +void Waitable_Free(void* handle) { + LEvent* ptr = (LEvent*)handle; + leventClear(ptr); + Mem_Free(ptr); +} + +void Waitable_Signal(void* handle) { + //leventSignal((LEvent*)handle); +} + +void Waitable_Wait(void* handle) { + leventWait((LEvent*)handle, UINT64_MAX); +} + +void Waitable_WaitFor(void* handle, cc_uint32 milliseconds) { + cc_uint64 timeout_ns = milliseconds * (1000 * 1000); // to nanoseconds + leventWait((LEvent*)handle, timeout_ns); +} /*########################################################################################################################* From ee012dcd36eb6f7b6087c1e51ca64dff566c7c0e Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Sat, 9 Mar 2024 22:12:27 -0400 Subject: [PATCH 09/34] use 0x20000 stack size for threads --- src/Platform_Switch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Platform_Switch.c b/src/Platform_Switch.c index d8f52821f..f0bb61542 100644 --- a/src/Platform_Switch.c +++ b/src/Platform_Switch.c @@ -246,7 +246,7 @@ static void ExecSwitchThread(void* param) { void* Thread_Create(Thread_StartFunc func) { Thread* thread = (Thread*)Mem_Alloc(1, sizeof(Thread), "thread"); - threadCreate(thread, ExecSwitchThread, (void*)func, NULL, 0x10000, 0x2C, -2); + threadCreate(thread, ExecSwitchThread, (void*)func, NULL, 0x20000, 0x2C, -2); return thread; } From f37b3aea77696c788b9cd36225665636202aaed4 Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Sat, 9 Mar 2024 22:59:14 -0400 Subject: [PATCH 10/34] revert back to mutex/condvar waitables fixes sign in --- src/Platform_Switch.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Platform_Switch.c b/src/Platform_Switch.c index f0bb61542..4ddffe024 100644 --- a/src/Platform_Switch.c +++ b/src/Platform_Switch.c @@ -281,11 +281,11 @@ void Mutex_Unlock(void* handle) { mutexUnlock((Mutex*)handle); } -/* + struct WaitData { CondVar cond; Mutex mutex; - int signalled; /& For when Waitable_Signal is called before Waitable_Wait + int signalled; // For when Waitable_Signal is called before Waitable_Wait }; void* Waitable_Create(void) { @@ -336,7 +336,7 @@ void Waitable_WaitFor(void* handle, cc_uint32 milliseconds) { ptr->signalled = false; Mutex_Unlock(&ptr->mutex); } -*/ +/* void* Waitable_Create(void) { LEvent* ptr = (LEvent*)Mem_Alloc(1, sizeof(LEvent), "waitable"); @@ -362,7 +362,7 @@ void Waitable_WaitFor(void* handle, cc_uint32 milliseconds) { cc_uint64 timeout_ns = milliseconds * (1000 * 1000); // to nanoseconds leventWait((LEvent*)handle, timeout_ns); } - +*/ /*########################################################################################################################* *---------------------------------------------------------Socket----------------------------------------------------------* From 2d8949661da2b58e8744aa7593f9a25e01199916 Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Sat, 9 Mar 2024 22:59:40 -0400 Subject: [PATCH 11/34] create sdmc:/switch if it doesn't exist, it fails to create sdmc:/switch/ClassiCube and does not download resources --- src/Platform_Switch.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Platform_Switch.c b/src/Platform_Switch.c index 4ddffe024..f2707d792 100644 --- a/src/Platform_Switch.c +++ b/src/Platform_Switch.c @@ -510,6 +510,7 @@ cc_result Socket_CheckWritable(cc_socket s, cc_bool* writable) { *--------------------------------------------------------Platform---------------------------------------------------------* *#########################################################################################################################*/ static void CreateRootDirectory(void) { + mkdir("sdmc:/switch", 0); int res = mkdir(root_path.buffer, 0); int err = res == -1 ? errno : 0; Platform_Log1("Created root directory: %i", &err); From 36b937e1c8229fe2905be2b151817229c614901d Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Sun, 10 Mar 2024 18:28:35 -0400 Subject: [PATCH 12/34] increase thread stack size to 0x100000 --- src/Platform_Switch.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Platform_Switch.c b/src/Platform_Switch.c index f2707d792..e5db54c6d 100644 --- a/src/Platform_Switch.c +++ b/src/Platform_Switch.c @@ -26,7 +26,6 @@ #include #include #include -#include #include "_PlatformConsole.h" @@ -246,7 +245,7 @@ static void ExecSwitchThread(void* param) { void* Thread_Create(Thread_StartFunc func) { Thread* thread = (Thread*)Mem_Alloc(1, sizeof(Thread), "thread"); - threadCreate(thread, ExecSwitchThread, (void*)func, NULL, 0x20000, 0x2C, -2); + threadCreate(thread, ExecSwitchThread, (void*)func, NULL, 0x100000, 0x2C, -2); return thread; } From 5fdad5031671ecf4767792255458281aa1c2ceae Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Sun, 10 Mar 2024 22:22:54 -0400 Subject: [PATCH 13/34] sounds are finally working on Switch music is audible as well but it's broken. WARNING: if you decide to play with music, watch out for garbage data earrape --- src/Audio.c | 161 +++++++++++++++++++++++++--------------------------- 1 file changed, 78 insertions(+), 83 deletions(-) diff --git a/src/Audio.c b/src/Audio.c index 19305f505..11b4e9657 100644 --- a/src/Audio.c +++ b/src/Audio.c @@ -924,12 +924,13 @@ void Audio_FreeChunks(void** chunks, int numChunks) { *-----------------------------------------------------Switch backend------------------------------------------------------* *#########################################################################################################################*/ #include +#include +#include struct AudioContext { int chanID, used; AudioDriverWaveBuf bufs[AUDIO_MAX_BUFFERS]; int count, channels, sampleRate; void* _tmpData; int _tmpSize; - cc_bool stereo; }; struct AudioMemPools { void* chunk; @@ -941,6 +942,62 @@ static struct AudioMemPools audioPools[64]; AudioDriver drv; bool switchAudio = false; + +// implementations of malloc_aligned and free_aligned +// https://stackoverflow.com/questions/6563120/what-does-posix-memalign-memalign-do +static void *malloc_aligned(size_t alignment, size_t bytes) +{ + // we need to allocate enough storage for the requested bytes, some + // book-keeping (to store the location returned by malloc) and some extra + // padding to allow us to find an aligned byte. im not entirely sure if + // 2 * alignment is enough here, its just a guess. + const size_t total_size = bytes + (2 * alignment) + sizeof(size_t); + + char *data = malloc(sizeof(char) * total_size); + + if (data) + { + // store the original start of the malloc'd data. + const void * const data_start = data; + + // dedicate enough space to the book-keeping. + data += sizeof(size_t); + + // find a memory location with correct alignment. the alignment minus + // the remainder of this mod operation is how many bytes forward we need + // to move to find an aligned byte. + const size_t offset = alignment - (((size_t)data) % alignment); + + // set data to the aligned memory. + data += offset; + + // write the book-keeping. + size_t *book_keeping = (size_t*)(data - sizeof(size_t)); + *book_keeping = (size_t)data_start; + } + + return data; +} + +static void free_aligned(void *raw_data) +{ + if (raw_data) + { + char *data = raw_data; + + // we have to assume this memory was allocated with malloc_aligned. + // this means the sizeof(size_t) bytes before data are the book-keeping + // which points to the location we need to pass to free. + data -= sizeof(size_t); + + // set data to the location stored in book-keeping. + data = (char*)(*((size_t*)data)); + + // free the memory. + free(data); + } +} + static cc_bool AudioBackend_Init(void) { if (switchAudio) return true; switchAudio = true; @@ -956,22 +1013,16 @@ static cc_bool AudioBackend_Init(void) { .num_mix_objs = 1, .num_mix_buffers = 2, }; - Result res; - res = audrenInitialize(&arConfig); - //Platform_Log1("audrenInitialize: %" PRIx32 "\n", &res); - - res = audrvCreate(&drv, &arConfig, 2); - //Platform_Log1("audrvCreate: %" PRIx32 "\n", &res); + audrenInitialize(&arConfig); + audrvCreate(&drv, &arConfig, 2); static const u8 sink_channels[] = { 0, 1 }; /*int sink =*/ audrvDeviceSinkAdd(&drv, AUDREN_DEFAULT_DEVICE_NAME, 2, sink_channels); - res = audrvUpdate(&drv); - //Platform_Log1("audrvUpdate: %" PRIx32 "\n", &res); + audrvUpdate(&drv); - res = audrenStartAudioRenderer(); - //Platform_Log1("audrenStartAudioRenderer: %" PRIx32 "\n", &res); + Result res = audrenStartAudioRenderer(); return R_SUCCEEDED(res); } @@ -1005,7 +1056,6 @@ void Audio_Init(struct AudioContext* ctx, int buffers) { void Audio_Close(struct AudioContext* ctx) { if (ctx->used) { audrvVoiceStop(&drv, ctx->chanID); - ctx->channels &= ~(1 << ctx->chanID); channelIDs &= ~(1 << ctx->chanID); } @@ -1014,14 +1064,15 @@ void Audio_Close(struct AudioContext* ctx) { } cc_result Audio_SetFormat(struct AudioContext* ctx, int channels, int sampleRate) { - /* - int fmt = ctx->stereo ? NDSP_FORMAT_STEREO_PCM16 : NDSP_FORMAT_MONO_PCM16; - - ndspChnSetFormat(ctx->chanID, fmt); - ndspChnSetRate(ctx->chanID, sampleRate);*/ - ctx->stereo = (channels == 2); ctx->channels = channels; ctx->sampleRate = sampleRate; + + audrvVoiceStop(&drv, ctx->chanID); + audrvVoiceInit(&drv, ctx->chanID, ctx->channels, PcmFormat_Int16, ctx->sampleRate); + audrvVoiceSetDestinationMix(&drv, ctx->chanID, AUDREN_FINAL_MIX_ID); + audrvVoiceSetMixFactor(&drv, ctx->chanID, 1.0f, 0, 0); + audrvVoiceSetMixFactor(&drv, ctx->chanID, 1.0f, 0, 1); + return 0; } @@ -1048,40 +1099,14 @@ cc_result Audio_QueueChunk(struct AudioContext* ctx, void* chunk, cc_uint32 data buf->start_sample_offset = 0; buf->end_sample_offset = dataSize/2; //Platform_Log1("PLAYING ON: %i", &ctx->chanID); - armDCacheFlush(buf->data_pcm16, dataSize); - audrvVoiceStop(&drv, ctx->chanID); - audrvVoiceInit(&drv, ctx->chanID, ctx->channels, PcmFormat_Int16, ctx->sampleRate); - audrvVoiceSetDestinationMix(&drv, ctx->chanID, AUDREN_FINAL_MIX_ID); - //audrvVoiceSetMixFactor(&drv, ctx->chanID, 1.0f, 0, 0); - //audrvVoiceSetMixFactor(&drv, ctx->chanID, 1.0f, 0, 1); + + audrvVoiceSetPaused(&drv, ctx->chanID, true); audrvVoiceAddWaveBuf(&drv, ctx->chanID, buf); + audrvVoiceSetPaused(&drv, ctx->chanID, false); + return 0; } - /*ndspWaveBuf* buf; - - // DSP audio buffers must be aligned to a multiple of 0x80, according to the example code I could find. - if (((uintptr_t)chunk & 0x7F) != 0) { - Platform_Log1("Audio_QueueData: tried to queue buffer with non-aligned audio buffer 0x%x\n", &chunk); - } - if ((dataSize & 0x7F) != 0) { - Platform_Log1("Audio_QueueData: unaligned audio data size 0x%x\n", &dataSize); - } - - for (int i = 0; i < ctx->count; i++) - { - buf = &ctx->bufs[i]; - //Platform_Log2("QUEUE_CHUNK: %i = %i", &ctx->chanID, &buf->status); - if (buf->status == NDSP_WBUF_QUEUED || buf->status == NDSP_WBUF_PLAYING) - continue; - - buf->data_pcm16 = chunk; - buf->nsamples = dataSize / (sizeof(cc_int16) * (ctx->stereo ? 2 : 1)); - //Platform_Log1("PLAYING ON: %i", &ctx->chanID); - DSP_FlushDataCache(buf->data_pcm16, dataSize); - ndspChnWaveBufAdd(ctx->chanID, buf); - return 0; - }*/ // tried to queue data without polling for free buffers first return ERR_INVALID_ARGUMENT; } @@ -1092,20 +1117,6 @@ cc_result Audio_Play(struct AudioContext* ctx) { } cc_result Audio_Poll(struct AudioContext* ctx, int* inUse) { - /*ndspWaveBuf* buf; - int count = 0; - - for (int i = 0; i < ctx->count; i++) - { - buf = &ctx->bufs[i]; - //Platform_Log2("CHECK_CHUNK: %i = %i", &ctx->chanID, &buf->status); - if (buf->status == NDSP_WBUF_QUEUED || buf->status == NDSP_WBUF_PLAYING) { - count++; continue; - } - } - - *inUse = count; */ - AudioDriverWaveBuf* buf; int count = 0; @@ -1128,17 +1139,6 @@ cc_bool Audio_FastPlay(struct AudioContext* ctx, struct AudioData* data) { } cc_result Audio_PlayData(struct AudioContext* ctx, struct AudioData* data) { - /*float mix[12] = { 0 }; - mix[0] = data->volume / 100.0f; - mix[1] = data->volume / 100.0f; - - ndspChnSetMix(ctx->chanID, mix); - data->sampleRate = Audio_AdjustSampleRate(data); - cc_result res; - - if ((res = Audio_SetFormat(ctx, data->channels, data->sampleRate))) return res; - if ((res = Audio_QueueChunk(ctx, data->data, data->size))) return res;*/ - data->sampleRate = Audio_AdjustSampleRate(data); cc_result res; @@ -1153,21 +1153,16 @@ cc_bool Audio_DescribeError(cc_result res, cc_string* dst) { } void Audio_AllocChunks(cc_uint32 size, void** chunks, int numChunks) { - /*size = (size + 0x7F) & ~0x7F; // round up to nearest multiple of 0x80 - cc_uint8* dst = linearAlloc(size * numChunks); - for (int i = 0; i < numChunks; i++) { - chunks[i] = dst ? (dst + size * i) : NULL; - }*/ size = (size + 0xFFF) & ~0xFFF; // round up to nearest multiple of 0x1000 - cc_uint8* dst = (cc_uint8*)memalign(0x1000, size * numChunks); - //armDCacheFlush(dst, size * numChunks); + void* dst = malloc_aligned(0x1000, size * numChunks); + armDCacheFlush(dst, size * numChunks); for (int i = 0; i < numChunks; i++) { + chunks[i] = dst + size * i; + int mpid = audrvMemPoolAdd(&drv, dst + size * i, size); audrvMemPoolAttach(&drv, mpid); - chunks[i] = dst ? (dst + size * i) : NULL; - for (int j = 0; j < 64; j++) { if (audioPools[j].chunk != NULL) continue; audioPools[j].chunk = chunks[i]; @@ -1190,7 +1185,7 @@ void Audio_FreeChunks(void** chunks, int numChunks) { } } - Mem_Free(chunks[0]); + free_aligned(chunks[0]); } #elif defined CC_BUILD_DREAMCAST /*########################################################################################################################* From ed3c0ff23c705507b473dbea21fa04ed92ec8cb1 Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Mon, 11 Mar 2024 11:08:30 -0400 Subject: [PATCH 14/34] use libnx code for aligned alloc --- src/Audio.c | 60 ++--------------------------------------------------- 1 file changed, 2 insertions(+), 58 deletions(-) diff --git a/src/Audio.c b/src/Audio.c index 11b4e9657..7d9fa5fb4 100644 --- a/src/Audio.c +++ b/src/Audio.c @@ -942,62 +942,6 @@ static struct AudioMemPools audioPools[64]; AudioDriver drv; bool switchAudio = false; - -// implementations of malloc_aligned and free_aligned -// https://stackoverflow.com/questions/6563120/what-does-posix-memalign-memalign-do -static void *malloc_aligned(size_t alignment, size_t bytes) -{ - // we need to allocate enough storage for the requested bytes, some - // book-keeping (to store the location returned by malloc) and some extra - // padding to allow us to find an aligned byte. im not entirely sure if - // 2 * alignment is enough here, its just a guess. - const size_t total_size = bytes + (2 * alignment) + sizeof(size_t); - - char *data = malloc(sizeof(char) * total_size); - - if (data) - { - // store the original start of the malloc'd data. - const void * const data_start = data; - - // dedicate enough space to the book-keeping. - data += sizeof(size_t); - - // find a memory location with correct alignment. the alignment minus - // the remainder of this mod operation is how many bytes forward we need - // to move to find an aligned byte. - const size_t offset = alignment - (((size_t)data) % alignment); - - // set data to the aligned memory. - data += offset; - - // write the book-keeping. - size_t *book_keeping = (size_t*)(data - sizeof(size_t)); - *book_keeping = (size_t)data_start; - } - - return data; -} - -static void free_aligned(void *raw_data) -{ - if (raw_data) - { - char *data = raw_data; - - // we have to assume this memory was allocated with malloc_aligned. - // this means the sizeof(size_t) bytes before data are the book-keeping - // which points to the location we need to pass to free. - data -= sizeof(size_t); - - // set data to the location stored in book-keeping. - data = (char*)(*((size_t*)data)); - - // free the memory. - free(data); - } -} - static cc_bool AudioBackend_Init(void) { if (switchAudio) return true; switchAudio = true; @@ -1154,7 +1098,7 @@ cc_bool Audio_DescribeError(cc_result res, cc_string* dst) { void Audio_AllocChunks(cc_uint32 size, void** chunks, int numChunks) { size = (size + 0xFFF) & ~0xFFF; // round up to nearest multiple of 0x1000 - void* dst = malloc_aligned(0x1000, size * numChunks); + void* dst = aligned_alloc(0x1000, size * numChunks); armDCacheFlush(dst, size * numChunks); for (int i = 0; i < numChunks; i++) { @@ -1185,7 +1129,7 @@ void Audio_FreeChunks(void** chunks, int numChunks) { } } - free_aligned(chunks[0]); + free(chunks[0]); } #elif defined CC_BUILD_DREAMCAST /*########################################################################################################################* From aa23b5d444e2a1a2710b05fe9e9ac45451381730 Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Wed, 13 Mar 2024 01:09:02 -0400 Subject: [PATCH 15/34] download sound resources on Switch --- src/Resources.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Resources.c b/src/Resources.c index 69c974d6b..0bfec991d 100644 --- a/src/Resources.c +++ b/src/Resources.c @@ -1055,7 +1055,7 @@ static const struct AssetSet* const asset_sets[] = { &ccTexsAssetSet, &mccTexsAssetSet, &mccMusicAssetSet, -#ifndef CC_BUILD_CONSOLE +#if !defined(CC_BUILD_CONSOLE) || defined(CC_BUILD_SWITCH) &mccSoundAssetSet #endif /* TODO: Vorbis decoding */ }; From 076f8dcb73b4511203b1dc691356ba115b80deec Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Wed, 13 Mar 2024 19:54:57 -0400 Subject: [PATCH 16/34] download sound asset on all platfoms removed if defined CC_BUILD_CONSOLE preprocessor --- src/Resources.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Resources.c b/src/Resources.c index 0bfec991d..9f091f3c3 100644 --- a/src/Resources.c +++ b/src/Resources.c @@ -1055,9 +1055,7 @@ static const struct AssetSet* const asset_sets[] = { &ccTexsAssetSet, &mccTexsAssetSet, &mccMusicAssetSet, -#if !defined(CC_BUILD_CONSOLE) || defined(CC_BUILD_SWITCH) &mccSoundAssetSet -#endif /* TODO: Vorbis decoding */ }; void Resources_CheckExistence(void) { From 5f9f8d15bc3a452204728167bb4361d258eb6b9a Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Wed, 13 Mar 2024 19:55:10 -0400 Subject: [PATCH 17/34] some more progress on switch audio backend * if channels == 2 (stereo), use audrvVoiceSetMixFactor() calls from hwopus-decoder libnx example * issue where music stops after playing for a few seconds --- src/Audio.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/Audio.c b/src/Audio.c index 7d9fa5fb4..fbcb955a8 100644 --- a/src/Audio.c +++ b/src/Audio.c @@ -1014,8 +1014,19 @@ cc_result Audio_SetFormat(struct AudioContext* ctx, int channels, int sampleRate audrvVoiceStop(&drv, ctx->chanID); audrvVoiceInit(&drv, ctx->chanID, ctx->channels, PcmFormat_Int16, ctx->sampleRate); audrvVoiceSetDestinationMix(&drv, ctx->chanID, AUDREN_FINAL_MIX_ID); - audrvVoiceSetMixFactor(&drv, ctx->chanID, 1.0f, 0, 0); - audrvVoiceSetMixFactor(&drv, ctx->chanID, 1.0f, 0, 1); + + if (channels == 1) { + // mono + audrvVoiceSetMixFactor(&drv, ctx->chanID, 1.0f, 0, 0); + audrvVoiceSetMixFactor(&drv, ctx->chanID, 1.0f, 0, 1); + } + else { + // stereo + audrvVoiceSetMixFactor(&drv, ctx->chanID, 1.0f, 0, 0); + audrvVoiceSetMixFactor(&drv, ctx->chanID, 0.0f, 0, 1); + audrvVoiceSetMixFactor(&drv, ctx->chanID, 0.0f, 1, 0); + audrvVoiceSetMixFactor(&drv, ctx->chanID, 1.0f, 1, 1); + } return 0; } @@ -1031,6 +1042,7 @@ cc_result Audio_QueueChunk(struct AudioContext* ctx, void* chunk, cc_uint32 data Platform_Log1("Audio_QueueData: unaligned audio data size 0x%x\n", &dataSize); } + for (int i = 0; i < ctx->count; i++) { buf = &ctx->bufs[i]; @@ -1041,12 +1053,11 @@ cc_result Audio_QueueChunk(struct AudioContext* ctx, void* chunk, cc_uint32 data buf->data_pcm16 = chunk; buf->size = dataSize; buf->start_sample_offset = 0; - buf->end_sample_offset = dataSize/2; + buf->end_sample_offset = dataSize / (sizeof(cc_int16) * ((ctx->channels == 2) ? 2 : 1));; //Platform_Log1("PLAYING ON: %i", &ctx->chanID); - audrvVoiceSetPaused(&drv, ctx->chanID, true); audrvVoiceAddWaveBuf(&drv, ctx->chanID, buf); - audrvVoiceSetPaused(&drv, ctx->chanID, false); + audrvVoiceStart(&drv, ctx->chanID); return 0; } @@ -1068,7 +1079,7 @@ cc_result Audio_Poll(struct AudioContext* ctx, int* inUse) { { buf = &ctx->bufs[i]; //Platform_Log2("CHECK_CHUNK: %i = %i", &ctx->chanID, &buf->status); - if (buf->state == AudioDriverWaveBufState_Queued || buf->state == AudioDriverWaveBufState_Playing) { + if (buf->state == AudioDriverWaveBufState_Queued || buf->state == AudioDriverWaveBufState_Playing || buf->state == AudioDriverWaveBufState_Waiting) { count++; continue; } } @@ -1098,8 +1109,7 @@ cc_bool Audio_DescribeError(cc_result res, cc_string* dst) { void Audio_AllocChunks(cc_uint32 size, void** chunks, int numChunks) { size = (size + 0xFFF) & ~0xFFF; // round up to nearest multiple of 0x1000 - void* dst = aligned_alloc(0x1000, size * numChunks); - armDCacheFlush(dst, size * numChunks); + void* dst = aligned_alloc(0x1000, size*numChunks); for (int i = 0; i < numChunks; i++) { chunks[i] = dst + size * i; From e0487ef70d1da885c921c2ff7b6aab96620beb46 Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Wed, 13 Mar 2024 20:45:09 -0400 Subject: [PATCH 18/34] waiting --- src/Audio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Audio.c b/src/Audio.c index fbcb955a8..d1baa9c5f 100644 --- a/src/Audio.c +++ b/src/Audio.c @@ -1047,7 +1047,7 @@ cc_result Audio_QueueChunk(struct AudioContext* ctx, void* chunk, cc_uint32 data { buf = &ctx->bufs[i]; //Platform_Log2("QUEUE_CHUNK: %i = %i", &ctx->chanID, &buf->status); - if (buf->state == AudioDriverWaveBufState_Queued || buf->state == AudioDriverWaveBufState_Playing) + if (buf->state == AudioDriverWaveBufState_Queued || buf->state == AudioDriverWaveBufState_Playing || buf->state == AudioDriverWaveBufState_Waiting) continue; buf->data_pcm16 = chunk; From 0bf5bb776282644107c79571d11aedfb2eb124c6 Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Thu, 14 Mar 2024 13:11:43 -0400 Subject: [PATCH 19/34] log using svcOutputDebugString() --- src/Platform_Switch.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/Platform_Switch.c b/src/Platform_Switch.c index e5db54c6d..d5859f312 100644 --- a/src/Platform_Switch.c +++ b/src/Platform_Switch.c @@ -84,14 +84,7 @@ cc_uint64 Stopwatch_Measure(void) { } void Platform_Log(const char* msg, int len) { - char buffer[256]; - cc_string str; - String_InitArray(str, buffer); - - String_AppendAll(&str, msg, len); - buffer[str.length] = '\0'; - - printf("%s\n", buffer); + svcOutputDebugString(msg, len); } #define UnixTime_TotalMS(time) ((cc_uint64)time.tv_sec * 1000 + UNIX_EPOCH + (time.tv_usec / 1000)) From 413fa5e8de49212810933b43cff7d57b19620d98 Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Thu, 14 Mar 2024 19:46:28 -0400 Subject: [PATCH 20/34] why didn't this sync?? --- src/Core.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Core.h b/src/Core.h index 7e4835f24..c0148f7dc 100644 --- a/src/Core.h +++ b/src/Core.h @@ -370,6 +370,7 @@ typedef cc_uint8 cc_bool; #define CC_BUILD_GLES #undef CC_BUILD_FREETYPE #endif +#endif #ifndef CC_BUILD_LOWMEM From c590975cdf4ce9024d031ab03095ff1bd6b94360 Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Thu, 14 Mar 2024 21:33:02 -0400 Subject: [PATCH 21/34] fix music --- src/Audio.c | 24 +++++++++++++++++++----- src/Platform_Switch.c | 5 +++++ src/Window_Switch.c | 7 +++++-- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/Audio.c b/src/Audio.c index d1baa9c5f..242311476 100644 --- a/src/Audio.c +++ b/src/Audio.c @@ -941,6 +941,7 @@ static int channelIDs; static struct AudioMemPools audioPools[64]; AudioDriver drv; bool switchAudio = false; +extern void* audrv_mutex; static cc_bool AudioBackend_Init(void) { if (switchAudio) return true; @@ -1046,18 +1047,23 @@ cc_result Audio_QueueChunk(struct AudioContext* ctx, void* chunk, cc_uint32 data for (int i = 0; i < ctx->count; i++) { buf = &ctx->bufs[i]; - //Platform_Log2("QUEUE_CHUNK: %i = %i", &ctx->chanID, &buf->status); + int state = buf->state; + cc_uint32 size = dataSize; + cc_uint32 endOffset = dataSize / (sizeof(cc_int16) * ((ctx->channels == 2) ? 2 : 1)); + + //Platform_Log3("QUEUE_CHUNK %i: %i = %i", &ctx->chanID, &i, &state); if (buf->state == AudioDriverWaveBufState_Queued || buf->state == AudioDriverWaveBufState_Playing || buf->state == AudioDriverWaveBufState_Waiting) continue; buf->data_pcm16 = chunk; - buf->size = dataSize; + buf->size = size; buf->start_sample_offset = 0; - buf->end_sample_offset = dataSize / (sizeof(cc_int16) * ((ctx->channels == 2) ? 2 : 1));; - //Platform_Log1("PLAYING ON: %i", &ctx->chanID); + buf->end_sample_offset = endOffset; + //Platform_Log3("PLAY %i: %i = %i", &ctx->chanID, &i, &state); + Mutex_Lock(audrv_mutex); audrvVoiceAddWaveBuf(&drv, ctx->chanID, buf); - audrvVoiceStart(&drv, ctx->chanID); + Mutex_Unlock(audrv_mutex); return 0; } @@ -1075,9 +1081,11 @@ cc_result Audio_Poll(struct AudioContext* ctx, int* inUse) { AudioDriverWaveBuf* buf; int count = 0; + //int states[4]; for (int i = 0; i < ctx->count; i++) { buf = &ctx->bufs[i]; + //states[i] = buf->state; //Platform_Log2("CHECK_CHUNK: %i = %i", &ctx->chanID, &buf->status); if (buf->state == AudioDriverWaveBufState_Queued || buf->state == AudioDriverWaveBufState_Playing || buf->state == AudioDriverWaveBufState_Waiting) { count++; continue; @@ -1085,6 +1093,12 @@ cc_result Audio_Poll(struct AudioContext* ctx, int* inUse) { } *inUse = count; + + /* + char abuf[64]; + sprintf(abuf, "%d %d %d %d", states[0], states[1], states[2], states[3]); + Platform_Log2("%i inUse, %c", inUse, abuf); + */ return 0; } diff --git a/src/Platform_Switch.c b/src/Platform_Switch.c index d5859f312..2592334d3 100644 --- a/src/Platform_Switch.c +++ b/src/Platform_Switch.c @@ -37,6 +37,8 @@ const cc_result ReturnCode_SocketWouldBlock = EWOULDBLOCK; const cc_result ReturnCode_DirectoryExists = EEXIST; const char* Platform_AppNameSuffix = " Switch"; +void* audrv_mutex; + alignas(16) u8 __nx_exception_stack[0x1000]; u64 __nx_exception_stack_size = sizeof(__nx_exception_stack); @@ -519,9 +521,12 @@ void Platform_Init(void) { // Configure our supported input layout: a single player with standard controller styles padConfigureInput(1, HidNpadStyleSet_NpadStandard); hidInitializeTouchScreen(); + + audrv_mutex = Mutex_Create(); } void Platform_Free(void) { socketExit(); + Mutex_Free(audrv_mutex); } cc_bool Platform_DescribeError(cc_result res, cc_string* dst) { diff --git a/src/Window_Switch.c b/src/Window_Switch.c index 9930a02cd..a2b9e30e7 100644 --- a/src/Window_Switch.c +++ b/src/Window_Switch.c @@ -22,8 +22,9 @@ static EGLDisplay s_display; static EGLContext s_context; static EGLSurface s_surface; -extern AudioDriver drv; // temporary -extern bool switchAudio; // temporary +extern AudioDriver drv; +extern bool switchAudio; +extern void* audrv_mutex; static cc_bool launcherMode; static Framebuffer fb; @@ -138,8 +139,10 @@ void Window_ProcessEvents(double delta) { // Scan the gamepad. This should be done once for each frame padUpdate(&pad); + Mutex_Lock(audrv_mutex); if (switchAudio) audrvUpdate(&drv); + Mutex_Unlock(audrv_mutex); if (!appletMainLoop()) { Window_Main.Exists = false; From 2df6854dcfb0cea7ac94e431c8997f46c977e4ef Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Thu, 14 Mar 2024 21:44:06 -0400 Subject: [PATCH 22/34] fix waitable milliseconds overflow fixes music immediately starting again after it ends --- src/Platform_Switch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Platform_Switch.c b/src/Platform_Switch.c index 2592334d3..57e7f6f09 100644 --- a/src/Platform_Switch.c +++ b/src/Platform_Switch.c @@ -321,7 +321,7 @@ void Waitable_Wait(void* handle) { void Waitable_WaitFor(void* handle, cc_uint32 milliseconds) { struct WaitData* ptr = (struct WaitData*)handle; - cc_uint64 timeout_ns = milliseconds * (1000 * 1000); // to nanoseconds + cc_uint64 timeout_ns = (cc_uint64)milliseconds * (1000 * 1000); // to nanoseconds Mutex_Lock(&ptr->mutex); if (!ptr->signalled) { From 1383d84daf93c959bf52dea57b949e3254b326f4 Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Thu, 14 Mar 2024 21:55:40 -0400 Subject: [PATCH 23/34] change sound volume --- src/Audio.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Audio.c b/src/Audio.c index 242311476..3e074e8b2 100644 --- a/src/Audio.c +++ b/src/Audio.c @@ -1052,7 +1052,7 @@ cc_result Audio_QueueChunk(struct AudioContext* ctx, void* chunk, cc_uint32 data cc_uint32 endOffset = dataSize / (sizeof(cc_int16) * ((ctx->channels == 2) ? 2 : 1)); //Platform_Log3("QUEUE_CHUNK %i: %i = %i", &ctx->chanID, &i, &state); - if (buf->state == AudioDriverWaveBufState_Queued || buf->state == AudioDriverWaveBufState_Playing || buf->state == AudioDriverWaveBufState_Waiting) + if (state == AudioDriverWaveBufState_Queued || state == AudioDriverWaveBufState_Playing || state == AudioDriverWaveBufState_Waiting) continue; buf->data_pcm16 = chunk; @@ -1113,7 +1113,10 @@ cc_result Audio_PlayData(struct AudioContext* ctx, struct AudioData* data) { if ((res = Audio_SetFormat(ctx, data->channels, data->sampleRate))) return res; if ((res = Audio_QueueChunk(ctx, data->data, data->size))) return res; + + audrvVoiceSetVolume(&drv, ctx->chanID, data->volume/100.f); if ((res = Audio_Play(ctx))) return res; + return 0; } From 5c136aea57d94a81176ec913fdb7209258c932c6 Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Thu, 14 Mar 2024 22:18:14 -0400 Subject: [PATCH 24/34] AudioBackend_Tick!! --- src/Audio.c | 12 +++++++++++- src/Platform_Switch.c | 5 ----- src/Window_Switch.c | 8 -------- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/Audio.c b/src/Audio.c index 7c4bc9419..0bc17d23b 100644 --- a/src/Audio.c +++ b/src/Audio.c @@ -950,12 +950,14 @@ static int channelIDs; static struct AudioMemPools audioPools[64]; AudioDriver drv; bool switchAudio = false; -extern void* audrv_mutex; +void* audrv_mutex; static cc_bool AudioBackend_Init(void) { if (switchAudio) return true; switchAudio = true; + audrv_mutex = Mutex_Create(); + Mem_Set(audioPools, 0, sizeof(audioPools)); static const AudioRendererConfig arConfig = @@ -980,9 +982,17 @@ static cc_bool AudioBackend_Init(void) { return R_SUCCEEDED(res); } + +void AudioBackend_Tick(void) { + Mutex_Lock(audrv_mutex); + if (switchAudio) audrvUpdate(&drv); + Mutex_Unlock(audrv_mutex); +} + static void AudioBackend_Free(void) { if (!switchAudio) return; switchAudio = false; + Mutex_Free(audrv_mutex); audrvClose(&drv); audrenExit(); diff --git a/src/Platform_Switch.c b/src/Platform_Switch.c index 57e7f6f09..124c7aef6 100644 --- a/src/Platform_Switch.c +++ b/src/Platform_Switch.c @@ -37,8 +37,6 @@ const cc_result ReturnCode_SocketWouldBlock = EWOULDBLOCK; const cc_result ReturnCode_DirectoryExists = EEXIST; const char* Platform_AppNameSuffix = " Switch"; -void* audrv_mutex; - alignas(16) u8 __nx_exception_stack[0x1000]; u64 __nx_exception_stack_size = sizeof(__nx_exception_stack); @@ -521,12 +519,9 @@ void Platform_Init(void) { // Configure our supported input layout: a single player with standard controller styles padConfigureInput(1, HidNpadStyleSet_NpadStandard); hidInitializeTouchScreen(); - - audrv_mutex = Mutex_Create(); } void Platform_Free(void) { socketExit(); - Mutex_Free(audrv_mutex); } cc_bool Platform_DescribeError(cc_result res, cc_string* dst) { diff --git a/src/Window_Switch.c b/src/Window_Switch.c index a2b9e30e7..5a537d516 100644 --- a/src/Window_Switch.c +++ b/src/Window_Switch.c @@ -22,9 +22,6 @@ static EGLDisplay s_display; static EGLContext s_context; static EGLSurface s_surface; -extern AudioDriver drv; -extern bool switchAudio; -extern void* audrv_mutex; static cc_bool launcherMode; static Framebuffer fb; @@ -139,11 +136,6 @@ void Window_ProcessEvents(double delta) { // Scan the gamepad. This should be done once for each frame padUpdate(&pad); - Mutex_Lock(audrv_mutex); - if (switchAudio) - audrvUpdate(&drv); - Mutex_Unlock(audrv_mutex); - if (!appletMainLoop()) { Window_Main.Exists = false; Window_RequestClose(); From 8d4adefb0e30fd1b481198b56b428e258e40cef9 Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Fri, 15 Mar 2024 12:44:12 -0400 Subject: [PATCH 25/34] don't de-init audren on AudioBackend_Free() fixes sounds not playing when returning to launcher and starting the game again --- src/Audio.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/Audio.c b/src/Audio.c index 0bc17d23b..a00ca5a56 100644 --- a/src/Audio.c +++ b/src/Audio.c @@ -956,7 +956,7 @@ static cc_bool AudioBackend_Init(void) { if (switchAudio) return true; switchAudio = true; - audrv_mutex = Mutex_Create(); + if (!audrv_mutex) audrv_mutex = Mutex_Create(); Mem_Set(audioPools, 0, sizeof(audioPools)); @@ -990,12 +990,9 @@ void AudioBackend_Tick(void) { } static void AudioBackend_Free(void) { - if (!switchAudio) return; - switchAudio = false; - Mutex_Free(audrv_mutex); - - audrvClose(&drv); - audrenExit(); + for (int i = 0; i < 24; i++) { + audrvVoiceStop(&drv, i); + } } #define AUDIO_HAS_BACKEND @@ -1155,7 +1152,7 @@ void Audio_AllocChunks(cc_uint32 size, void** chunks, int numChunks) { for (int j = 0; j < 64; j++) { if (audioPools[j].chunk != NULL) continue; - audioPools[j].chunk = chunks[i]; + audioPools[j].chunk = dst; audioPools[j].mpid = mpid; break; } @@ -1166,7 +1163,7 @@ void Audio_FreeChunks(void** chunks, int numChunks) { // remove memory pool from audren for (int i=0; i Date: Fri, 15 Mar 2024 19:12:53 -0400 Subject: [PATCH 26/34] audrvUpdate after freeing audio backend fixes garbage data playing on yuzu after returning to launcher --- src/Audio.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Audio.c b/src/Audio.c index a00ca5a56..bc2440883 100644 --- a/src/Audio.c +++ b/src/Audio.c @@ -993,6 +993,7 @@ static void AudioBackend_Free(void) { for (int i = 0; i < 24; i++) { audrvVoiceStop(&drv, i); } + audrvUpdate(&drv); } #define AUDIO_HAS_BACKEND From 102a5367d1b1feb50a1648632e673a3c4fd99787 Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Fri, 15 Mar 2024 19:19:27 -0400 Subject: [PATCH 27/34] change resolution on handheld/docked mode --- src/Window_Switch.c | 47 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/src/Window_Switch.c b/src/Window_Switch.c index 5a537d516..247fddd41 100644 --- a/src/Window_Switch.c +++ b/src/Window_Switch.c @@ -26,22 +26,54 @@ static EGLSurface s_surface; static cc_bool launcherMode; static Framebuffer fb; static PadState pad; +static AppletHookCookie cookie; struct _DisplayData DisplayInfo; struct _WindowData WindowInfo; +static void Set_Resolution(void) { + // check whether the Switch is docked + // use 720p for handheld, 1080p for docked + AppletOperationMode opMode = appletGetOperationMode(); + int w = 1280; + int h = 720; + if (opMode == AppletOperationMode_Console) { + w = 1920; + h = 1080; + } + + DisplayInfo.Width = w; + DisplayInfo.Height = h; + + Window_Main.Width = DisplayInfo.Width; + Window_Main.Height = DisplayInfo.Height; +} + +static void Applet_Event(AppletHookType type, void* param) { + if (type == AppletHookType_OnOperationMode) { + Set_Resolution(); + + if (launcherMode) { + framebufferClose(&fb); + framebufferCreate(&fb, nwindowGetDefault(), DisplayInfo.Width, DisplayInfo.Height, PIXEL_FORMAT_BGRA_8888, 2); + framebufferMakeLinear(&fb); + } + + Event_RaiseVoid(&WindowEvents.Resized); + } +} + void Window_Init(void) { // Initialize the default gamepad (which reads handheld mode inputs as well as the first connected controller) padInitializeDefault(&pad); - DisplayInfo.Width = 1280; - DisplayInfo.Height = 720; + appletHook(&cookie, Applet_Event, NULL); + Set_Resolution(); + DisplayInfo.Depth = 4; // 32 bit DisplayInfo.ScaleX = 1; DisplayInfo.ScaleY = 1; - - Window_Main.Width = DisplayInfo.Width; - Window_Main.Height = DisplayInfo.Height; + Window_Main.Focused = true; Window_Main.Exists = true; @@ -52,11 +84,12 @@ void Window_Init(void) { void Window_Free(void) { if (launcherMode) framebufferClose(&fb); + appletUnhook(&cookie); } void Window_Create2D(int width, int height) { - framebufferCreate(&fb, nwindowGetDefault(), 1280, 720, PIXEL_FORMAT_BGRA_8888, 2); - framebufferMakeLinear(&fb); + framebufferCreate(&fb, nwindowGetDefault(), DisplayInfo.Width, DisplayInfo.Height, PIXEL_FORMAT_BGRA_8888, 2); + framebufferMakeLinear(&fb); launcherMode = true; } void Window_Create3D(int width, int height) { From cedc2ca8e005ea764df4eb537c686dbe95f3b1cc Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Fri, 15 Mar 2024 20:06:08 -0400 Subject: [PATCH 28/34] use classicube's built in EGL implementation i didn't realize i forgot to set Window_Main.Handle so it didn't crash --- src/Core.h | 1 + src/Window_Switch.c | 134 +------------------------------------------- 2 files changed, 2 insertions(+), 133 deletions(-) diff --git a/src/Core.h b/src/Core.h index c0148f7dc..dec05a45f 100644 --- a/src/Core.h +++ b/src/Core.h @@ -368,6 +368,7 @@ typedef cc_uint8 cc_bool; #define CC_BUILD_GL #define CC_BUILD_GLMODERN #define CC_BUILD_GLES + #define CC_BUILD_EGL #undef CC_BUILD_FREETYPE #endif #endif diff --git a/src/Window_Switch.c b/src/Window_Switch.c index 247fddd41..4fb3dcbfe 100644 --- a/src/Window_Switch.c +++ b/src/Window_Switch.c @@ -14,15 +14,6 @@ #include "Input.h" #include -#include // EGL library -#include // EGL extensions -#include // glad library (OpenGL loader) - - -static EGLDisplay s_display; -static EGLContext s_context; -static EGLSurface s_surface; - static cc_bool launcherMode; static Framebuffer fb; static PadState pad; @@ -76,6 +67,7 @@ void Window_Init(void) { Window_Main.Focused = true; Window_Main.Exists = true; + Window_Main.Handle = nwindowGetDefault(); Input_SetTouchMode(true); Input.Sources = INPUT_SOURCE_GAMEPAD; @@ -224,130 +216,6 @@ void Window_FreeFramebuffer(struct Bitmap* bmp) { Mem_Free(bmp->scan0); } -/*########################################################################################################################* -*-----------------------------------------------------OpenGL context------------------------------------------------------* -*#########################################################################################################################*/ -void GLContext_Create(void) { - EGLint err; - - // Retrieve the default window - NWindow* win = nwindowGetDefault(); - - // Connect to the EGL default display - s_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); - if (!s_display) - { - err = eglGetError(); - Platform_Log1("Could not connect to display! error: %d", &err); - return; - } - - // Initialize the EGL display connection - eglInitialize(s_display, NULL, NULL); - - // Select OpenGL ES as the desired graphics API - if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) - { - err = eglGetError(); - Platform_Log1("Could not set API! error: %d", &err); - eglTerminate(s_display); - s_display = NULL; - return; - } - - // Get an appropriate EGL framebuffer configuration - EGLConfig config; - EGLint numConfigs; - static const EGLint framebufferAttributeList[] = - { - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL_RED_SIZE, 0, - EGL_GREEN_SIZE, 0, - EGL_BLUE_SIZE, 0, - EGL_ALPHA_SIZE, 0, - EGL_DEPTH_SIZE, GLCONTEXT_DEFAULT_DEPTH, - EGL_STENCIL_SIZE, 0, - EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER, - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - EGL_NONE - }; - eglChooseConfig(s_display, framebufferAttributeList, &config, 1, &numConfigs); - if (numConfigs == 0) - { - err = eglGetError(); - Platform_Log1("No config found! error: %d", &err); - eglTerminate(s_display); - s_display = NULL; - return; - } - - // Create an EGL window surface - s_surface = eglCreateWindowSurface(s_display, config, win, NULL); - if (!s_surface) - { - err = eglGetError(); - Platform_Log1("Surface creation failed! error: %d", &err); - eglTerminate(s_display); - s_display = NULL; - return; - } - - // Create an EGL rendering context - static const EGLint contextAttributeList[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; - s_context = eglCreateContext(s_display, config, EGL_NO_CONTEXT, contextAttributeList); - if (!s_context) - { - err = eglGetError(); - Platform_Log1("Context creation failed! error: %d", &err); - eglDestroySurface(s_display, s_surface); - s_surface = NULL; - } - - // Connect the context to the surface - eglMakeCurrent(s_display, s_surface, s_surface, s_context); - - gladLoadGL(); -} - -void GLContext_Update(void) { } - -cc_bool GLContext_TryRestore(void) { - return true; -} - -void GLContext_Free(void) { - if (s_display) - { - eglMakeCurrent(s_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - if (s_context) - { - eglDestroyContext(s_display, s_context); - s_context = NULL; - } - if (s_surface) - { - eglDestroySurface(s_display, s_surface); - s_surface = NULL; - } - eglTerminate(s_display); - s_display = NULL; - } -} - -void* GLContext_GetAddress(const char* function) { - return (void*)eglGetProcAddress(function); -} - -cc_bool GLContext_SwapBuffers(void) { - eglSwapBuffers(s_display, s_surface); - return true; -} - -void GLContext_SetFpsLimit(cc_bool vsync, float minFrameMs) { } - -void GLContext_GetApiInfo(cc_string* info) { } - - /*########################################################################################################################* *------------------------------------------------------Soft keyboard------------------------------------------------------* *#########################################################################################################################*/ From 7978719237d2cdbb927dc27f4e15433a191e65ca Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Fri, 15 Mar 2024 21:08:37 -0400 Subject: [PATCH 29/34] attempts to fix resolution --- src/Window_Switch.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Window_Switch.c b/src/Window_Switch.c index 4fb3dcbfe..4cc0348b2 100644 --- a/src/Window_Switch.c +++ b/src/Window_Switch.c @@ -36,8 +36,12 @@ static void Set_Resolution(void) { DisplayInfo.Width = w; DisplayInfo.Height = h; - Window_Main.Width = DisplayInfo.Width; - Window_Main.Height = DisplayInfo.Height; + Window_Main.Width = w; + Window_Main.Height = h; + + nwindowSetDimensions(Window_Main.Handle, w, h); + nwindowSetCrop(Window_Main.Handle, 0, 0, w, h); + glViewport(0, 0, w, h); } static void Applet_Event(AppletHookType type, void* param) { @@ -58,9 +62,6 @@ void Window_Init(void) { // Initialize the default gamepad (which reads handheld mode inputs as well as the first connected controller) padInitializeDefault(&pad); - appletHook(&cookie, Applet_Event, NULL); - Set_Resolution(); - DisplayInfo.Depth = 4; // 32 bit DisplayInfo.ScaleX = 1; DisplayInfo.ScaleY = 1; @@ -71,6 +72,9 @@ void Window_Init(void) { Input_SetTouchMode(true); Input.Sources = INPUT_SOURCE_GAMEPAD; + + appletHook(&cookie, Applet_Event, NULL); + Set_Resolution(); } void Window_Free(void) { From 8fd0e83c6e91f80aa2694340824b83d422cc9a66 Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Sat, 16 Mar 2024 13:20:52 -0400 Subject: [PATCH 30/34] fix resolution change on handheld/docked thx unknownshadow200 --- src/Window_Switch.c | 35 +++++++++++++++++++++++++++++++---- src/_WindowBase.h | 4 ++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/Window_Switch.c b/src/Window_Switch.c index 4cc0348b2..6f48b58d8 100644 --- a/src/Window_Switch.c +++ b/src/Window_Switch.c @@ -38,10 +38,6 @@ static void Set_Resolution(void) { Window_Main.Width = w; Window_Main.Height = h; - - nwindowSetDimensions(Window_Main.Handle, w, h); - nwindowSetCrop(Window_Main.Handle, 0, 0, w, h); - glViewport(0, 0, w, h); } static void Applet_Event(AppletHookType type, void* param) { @@ -54,7 +50,14 @@ static void Applet_Event(AppletHookType type, void* param) { framebufferMakeLinear(&fb); } + NWindow* win = (NWindow*)Window_Main.Handle; + int real_width = win->width; win->width = Window_Main.Width; + int real_height = win->height; win->height = Window_Main.Height; + Event_RaiseVoid(&WindowEvents.Resized); + + win->width = real_width; + win->height = real_height; } } @@ -73,6 +76,8 @@ void Window_Init(void) { Input_SetTouchMode(true); Input.Sources = INPUT_SOURCE_GAMEPAD; + nwindowSetDimensions(Window_Main.Handle, 1920, 1080); + appletHook(&cookie, Applet_Event, NULL); Set_Resolution(); } @@ -220,6 +225,28 @@ void Window_FreeFramebuffer(struct Bitmap* bmp) { Mem_Free(bmp->scan0); } +/*########################################################################################################################* +*-----------------------------------------------------OpenGL context------------------------------------------------------* +*#########################################################################################################################*/ +static void GLContext_InitSurface(void) { + NWindow* window = (NWindow*)Window_Main.Handle; + if (!window) return; /* window not created or lost */ + + // terrible, but fixes 720p/1080p resolution change on handheld/docked modes + int real_w = window->width; + int real_h = window->height; + window->width = Window_Main.Width; + window->height = Window_Main.Height; + + ctx_surface = eglCreateWindowSurface(ctx_display, ctx_config, window, NULL); + if (!ctx_surface) return; + + window->width = real_w; + window->height = real_h; + + eglMakeCurrent(ctx_display, ctx_surface, ctx_surface, ctx_context); +} + /*########################################################################################################################* *------------------------------------------------------Soft keyboard------------------------------------------------------* *#########################################################################################################################*/ diff --git a/src/_WindowBase.h b/src/_WindowBase.h index f5f56c45b..a6baf5505 100644 --- a/src/_WindowBase.h +++ b/src/_WindowBase.h @@ -107,6 +107,9 @@ static EGLSurface ctx_surface; static EGLConfig ctx_config; static EGLint ctx_numConfig; +#ifdef CC_BUILD_SWITCH +static void GLContext_InitSurface(void); // replacement in Window_Switch.c for handheld/docked resolution fix +#else static void GLContext_InitSurface(void) { void* window = Window_Main.Handle; if (!window) return; /* window not created or lost */ @@ -115,6 +118,7 @@ static void GLContext_InitSurface(void) { if (!ctx_surface) return; eglMakeCurrent(ctx_display, ctx_surface, ctx_surface, ctx_context); } +#endif static void GLContext_FreeSurface(void) { if (!ctx_surface) return; From 3231cb7b06f64a46f48e22aaf60f4ee815b18d75 Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Sat, 16 Mar 2024 15:50:18 -0400 Subject: [PATCH 31/34] add Switch to readme --- readme.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/readme.md b/readme.md index 1bb4e7f6e..94634866a 100644 --- a/readme.md +++ b/readme.md @@ -82,6 +82,7 @@ And also runs on: * PS3 - unfinished, rendering issues (can [download from here](https://www.classicube.net/download/ps3), **usually outdated**) * Nintendo 64 - unfinished, moderate rendering issues (if you have a GitHub account, can [download from here](https://github.com/ClassiCube/ClassiCube/actions/workflows/build_n64.yml)) * PS2 - unfinished, major rendering and **stability issues** (if you have a GitHub account, can [download from here](https://github.com/ClassiCube/ClassiCube/actions/workflows/build_ps2.yml)) +* Switch (if you have a GitHub account, can [download from here](https://github.com/ClassiCube/ClassiCube/actions/workflows/build_switch.yml)) # Compiling @@ -359,6 +360,8 @@ Further information (e.g. style) for ClassiCube's source code can be found in th * [ares](https://github.com/ares-emulator/ares) - Emulator used to test Nintendo 64 port * [ps2sdk](https://github.com/ps2dev/ps2sdk) - Backend for PS2 * [PCSX2](https://github.com/PCSX2/pcsx2) - Emulator used to test PS2 port +* [libnx](https://github.com/switchbrew/libnx) - Backend for Nintendo Switch +* [Ryujinx](https://github.com/Ryujinx/Ryujinx) - Emulator used to test Nintendo Switch port ## Sound Credits From 1ce265fe89fa7bcbcede1d2c42543a6dc146bd48 Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Sat, 16 Mar 2024 16:00:12 -0400 Subject: [PATCH 32/34] remove stray code from Applet_Event this is already in GLContext_InitSurface --- src/Window_Switch.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Window_Switch.c b/src/Window_Switch.c index 6f48b58d8..0c975695f 100644 --- a/src/Window_Switch.c +++ b/src/Window_Switch.c @@ -50,14 +50,7 @@ static void Applet_Event(AppletHookType type, void* param) { framebufferMakeLinear(&fb); } - NWindow* win = (NWindow*)Window_Main.Handle; - int real_width = win->width; win->width = Window_Main.Width; - int real_height = win->height; win->height = Window_Main.Height; - Event_RaiseVoid(&WindowEvents.Resized); - - win->width = real_width; - win->height = real_height; } } From 46da4fb095f636812143db84c38e851a4afbfbee Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Sat, 16 Mar 2024 16:25:37 -0400 Subject: [PATCH 33/34] adapt thread API redesign for Switch --- src/Platform_Switch.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Platform_Switch.c b/src/Platform_Switch.c index 124c7aef6..7f80dfae9 100644 --- a/src/Platform_Switch.c +++ b/src/Platform_Switch.c @@ -236,14 +236,12 @@ static void ExecSwitchThread(void* param) { ((Thread_StartFunc)param)(); } -void* Thread_Create(Thread_StartFunc func) { - Thread* thread = (Thread*)Mem_Alloc(1, sizeof(Thread), "thread"); - threadCreate(thread, ExecSwitchThread, (void*)func, NULL, 0x100000, 0x2C, -2); - return thread; -} +void Thread_Run(void** handle, Thread_StartFunc func, int stackSize, const char* name) { + Thread* thread = (Thread*)Mem_Alloc(1, sizeof(Thread), name); + *handle = thread; -void Thread_Start2(void* handle, Thread_StartFunc func) { - threadStart((Thread*)handle); + threadCreate(thread, ExecSwitchThread, (void*)func, NULL, stackSize, 0x2C, -2); + threadStart(thread); } void Thread_Detach(void* handle) { } From dec5e41ab95b2219a262620c0ca7c9307f17b397 Mon Sep 17 00:00:00 2001 From: headshot2017 <> Date: Sat, 16 Mar 2024 16:34:16 -0400 Subject: [PATCH 34/34] Revert "add Switch to readme" This reverts commit 3231cb7b06f64a46f48e22aaf60f4ee815b18d75. --- readme.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/readme.md b/readme.md index 94634866a..1bb4e7f6e 100644 --- a/readme.md +++ b/readme.md @@ -82,7 +82,6 @@ And also runs on: * PS3 - unfinished, rendering issues (can [download from here](https://www.classicube.net/download/ps3), **usually outdated**) * Nintendo 64 - unfinished, moderate rendering issues (if you have a GitHub account, can [download from here](https://github.com/ClassiCube/ClassiCube/actions/workflows/build_n64.yml)) * PS2 - unfinished, major rendering and **stability issues** (if you have a GitHub account, can [download from here](https://github.com/ClassiCube/ClassiCube/actions/workflows/build_ps2.yml)) -* Switch (if you have a GitHub account, can [download from here](https://github.com/ClassiCube/ClassiCube/actions/workflows/build_switch.yml)) # Compiling @@ -360,8 +359,6 @@ Further information (e.g. style) for ClassiCube's source code can be found in th * [ares](https://github.com/ares-emulator/ares) - Emulator used to test Nintendo 64 port * [ps2sdk](https://github.com/ps2dev/ps2sdk) - Backend for PS2 * [PCSX2](https://github.com/PCSX2/pcsx2) - Emulator used to test PS2 port -* [libnx](https://github.com/switchbrew/libnx) - Backend for Nintendo Switch -* [Ryujinx](https://github.com/Ryujinx/Ryujinx) - Emulator used to test Nintendo Switch port ## Sound Credits