diff --git a/Makefile.port b/Makefile.port index 278835001..2b2b2cb26 100644 --- a/Makefile.port +++ b/Makefile.port @@ -150,7 +150,7 @@ B_DIR := build/$(ROMID)-port G_DIR := src/generated/$(ROMID) ifneq (,$(findstring windows,$(TARGET_PLATFORM))) - TARGET_CFLAGS := $(shell pkg-config sdl2 --cflags-only-I) + TARGET_CFLAGS := $(shell pkg-config sdl2 --cflags-only-I) -pthread TARGET_LDFLAGS := $(shell pkg-config sdl2 --libs) -lz -lopengl32 -ldbghelp # on windows/mingw we need this to be built with a 32-bit compiler so it finds the correct libs TOOLCHAIN ?= i686-w64-mingw32- @@ -161,7 +161,7 @@ ifneq (,$(findstring windows,$(TARGET_PLATFORM))) TARGET_LDFLAGS += -lstdc++ endif else # assume *nix - TARGET_CFLAGS := $(shell pkg-config sdl2 --cflags-only-I) + TARGET_CFLAGS := $(shell pkg-config sdl2 --cflags-only-I) -pthread TARGET_LDFLAGS := $(shell pkg-config sdl2 --libs) -lGL -lz -ldl ifneq (,$(findstring arm,$(TARGET_PLATFORM))$(findstring aarch,$(TARGET_PLATFORM))) # linux on arm or aarch64 diff --git a/port/fast3d/gfx_sdl2.cpp b/port/fast3d/gfx_sdl2.cpp index 181038d26..537b4c52d 100644 --- a/port/fast3d/gfx_sdl2.cpp +++ b/port/fast3d/gfx_sdl2.cpp @@ -261,7 +261,7 @@ static inline void sync_framerate_with_timer(void) { // We want to exit a bit early, so we can busy-wait the rest to never miss the deadline left -= 15000UL; if (left > 0) { - sysSleep(left); + sysFrameLimiterSleep(left); } do { diff --git a/port/include/audio.h b/port/include/audio.h index 32fc16cbe..8aa5a453c 100644 --- a/port/include/audio.h +++ b/port/include/audio.h @@ -6,7 +6,6 @@ s32 audioInit(void); s32 audioGetBytesBuffered(void); s32 audioGetSamplesBuffered(void); -void audioSetNextBuffer(const s16 *buf, u32 len); -void audioEndFrame(void); +void audioSubmitBuffer(const s16 *buf, u32 size); #endif diff --git a/port/include/system.h b/port/include/system.h index 84d568382..b65690b47 100644 --- a/port/include/system.h +++ b/port/include/system.h @@ -35,7 +35,11 @@ void *sysMemZeroAlloc(const u32 size); void sysMemFree(void *ptr); // hns is specified in 100ns units -void sysSleep(const s64 hns); +// do not use sysFrameLimiterSleep on any thread other than the main one +void sysFrameLimiterSleep(const s64 hns); + +// us is specified in microseconds +void sysSleep(const s64 us); // yield CPU if supported (e.g. during a busy loop) void sysCpuRelax(void); diff --git a/port/include/thread.h b/port/include/thread.h new file mode 100644 index 000000000..7a9f6383f --- /dev/null +++ b/port/include/thread.h @@ -0,0 +1,27 @@ +#ifndef _IN_THREAD_H +#define _IN_THREAD_H + +#include +#include "platform.h" + +// TODO: backends +#ifndef PLATFORM_USE_PTHREAD +#error "No available thread backend." +#endif + +#include + +#define SYSMUTEX_STATIC_INIT PTHREAD_MUTEX_INITIALIZER + +typedef pthread_t systhread; +typedef pthread_mutex_t sysmutex; + +typedef void *(*systhreadfn)(void *); + +s32 sysThreadCreate(systhread *thread, systhreadfn func, void *arg); +s32 sysThreadJoin(systhread *thread, void **result); + +void sysMutexLock(sysmutex *mtx); +void sysMutexUnlock(sysmutex *mtx); + +#endif // _IN_THREAD_H diff --git a/port/src/audio.c b/port/src/audio.c index c66cf5912..f0f172de5 100644 --- a/port/src/audio.c +++ b/port/src/audio.c @@ -7,12 +7,13 @@ #include "system.h" static SDL_AudioDeviceID dev; -static const s16 *nextBuf; -static u32 nextSize = 0; static s32 bufferSize = 512; static s32 queueLimit = 8192; +static f32 offsetFrames = 0.5f; +static u32 offsetBytes; + s32 audioInit(void) { if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) { @@ -28,7 +29,8 @@ s32 audioInit(void) want.samples = bufferSize; want.callback = NULL; - nextBuf = NULL; + const f32 spf = (f32)want.freq / (PAL ? 50.f : 60.f); + offsetBytes = offsetFrames * spf * (f32)sizeof(int16_t); dev = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0); if (dev == 0) { @@ -43,33 +45,39 @@ s32 audioInit(void) s32 audioGetBytesBuffered(void) { - return SDL_GetQueuedAudioSize(dev); + u32 count = SDL_GetQueuedAudioSize(dev); + + // pd's audio code decides how many samples of audio to produce based on the amount of audio + // currently buffered, so we adjust the reported count to be some number of frames in the future + // to counteract lag; idea shamelessly stolen from mm recomp + if (offsetBytes) { + if (count > offsetBytes) { + count -= offsetBytes; + } else { + count = 0; + } + } + + return count; } s32 audioGetSamplesBuffered(void) { - return audioGetBytesBuffered() / 4; + return audioGetBytesBuffered() / (2 * sizeof(s16)); } -void audioSetNextBuffer(const s16 *buf, u32 len) +void audioSubmitBuffer(const s16 *buf, u32 size) { - nextBuf = buf; - nextSize = len; -} - -void audioEndFrame(void) -{ - if (nextBuf && nextSize) { + if (buf && size) { if (audioGetSamplesBuffered() < queueLimit) { - SDL_QueueAudio(dev, nextBuf, nextSize); + SDL_QueueAudio(dev, buf, size); } - nextBuf = NULL; - nextSize = 0; } } PD_CONSTRUCTOR static void audioConfigInit(void) { configRegisterInt("Audio.BufferSize", &bufferSize, 0, 1 * 1024 * 1024); + configRegisterFloat("Audio.OffsetFrames", &offsetFrames, 0.f, 5.f); configRegisterInt("Audio.QueueLimit", &queueLimit, 0, 1 * 1024 * 1024); } diff --git a/port/src/libultra.c b/port/src/libultra.c index 74edd9782..3d749d211 100644 --- a/port/src/libultra.c +++ b/port/src/libultra.c @@ -166,7 +166,7 @@ u32 osAiGetLength(void) s32 osAiSetNextBuffer(void *bufPtr, u32 size) { - audioSetNextBuffer(bufPtr, size); + audioSubmitBuffer(bufPtr, size); return 0; } diff --git a/port/src/pdmain.c b/port/src/pdmain.c index aeeb6186d..97281ed96 100644 --- a/port/src/pdmain.c +++ b/port/src/pdmain.c @@ -502,7 +502,7 @@ void mainLoop(void) schedEndFrame(&g_Sched); } if (g_TickExtraSleep) { - sysSleep(EXTRA_SLEEP_TIME); + sysFrameLimiterSleep(EXTRA_SLEEP_TIME); } } diff --git a/port/src/pdsched.c b/port/src/pdsched.c index c62f8b424..ed0e433da 100644 --- a/port/src/pdsched.c +++ b/port/src/pdsched.c @@ -244,18 +244,6 @@ void schedStartFrame(OSSched *sc) videoStartFrame(); } -void schedAudioFrame(OSSched *sc) -{ - s32 i; - - if (!g_SndDisabled) { - for (i = 0; i < g_Vars.diffframe60; i++) { - amgrFrame(); - audioEndFrame(); - } - } -} - /** * Handle a retrace (vsync) event. * @@ -274,18 +262,6 @@ void schedEndFrame(OSSched *sc) { sc->frameCount++; -#if PAL - if (!g_Resetting && (sc->frameCount & 1)) { - // osStopTimer(&g_SchedRspTimer); - // osSetTimer(&g_SchedRspTimer, 280000, 0, amgrGetFrameMesgQueue(), &g_SchedRspMsg); - } -#else - if (!g_Resetting && ((sc->frameCount & 1) || IS4MB())) { - // osStopTimer(&g_SchedRspTimer); - // osSetTimer(&g_SchedRspTimer, 280000, 0, amgrGetFrameMesgQueue(), &g_SchedRspMsg); - } -#endif - if (!g_Resetting) { viHandleRetrace(); } @@ -299,7 +275,6 @@ void schedEndFrame(OSSched *sc) joy00014238(); sndHandleRetrace(); - schedAudioFrame(sc); schedRenderCrashPeriodically(sc->frameCount); debuggerEndFrame(); diff --git a/port/src/system.c b/port/src/system.c index 16b689c47..8e06f8ed8 100644 --- a/port/src/system.c +++ b/port/src/system.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include "platform.h" @@ -32,8 +33,6 @@ __attribute__((dllexport)) u32 AmdPowerXpressRequestHighPerformance = 1; #else -#include - // figure out how to yield #if defined(PLATFORM_X86) || defined(PLATFORM_X86_64) // this should work even if the code is not built with SSE enabled, at least on gcc and clang, @@ -293,7 +292,7 @@ void sysMemFree(void *ptr) free(ptr); } -void sysSleep(const s64 hns) +void sysFrameLimiterSleep(const s64 hns) { #ifdef PLATFORM_WIN32 static LARGE_INTEGER li; @@ -306,6 +305,12 @@ void sysSleep(const s64 hns) #endif } +void sysSleep(const s64 us) +{ + const struct timespec spec = { 0, us * 1000 }; + nanosleep(&spec, NULL); +} + void sysCpuRelax(void) { DO_YIELD(); diff --git a/port/src/thread.c b/port/src/thread.c new file mode 100644 index 000000000..232c104ac --- /dev/null +++ b/port/src/thread.c @@ -0,0 +1,27 @@ +#include "thread.h" + +#ifndef PLATFORM_USE_PTHREAD +#error "No available thread backend." +#endif + +#include + +s32 sysThreadCreate(systhread *thread, systhreadfn func, void *arg) +{ + return pthread_create(thread, NULL, func, arg); +} + +s32 sysThreadJoin(systhread *thread, void **result) +{ + return pthread_join(*thread, result); +} + +void sysMutexLock(sysmutex *mtx) +{ + pthread_mutex_lock(mtx); +} + +void sysMutexUnlock(sysmutex *mtx) +{ + pthread_mutex_unlock(mtx); +} diff --git a/src/game/chraicommands.c b/src/game/chraicommands.c index 9a5517348..b25d433ed 100644 --- a/src/game/chraicommands.c +++ b/src/game/chraicommands.c @@ -9840,20 +9840,14 @@ bool aiIfMusicEventQueueIsEmpty(void) { f32 value = (u64)osGetCount() * 64 / 3000; -#ifdef PLATFORM_N64 // will hang forever until the audio thread wakes it up if (g_MusicEventQueueLength) { -#else // HACK: will wait 1 frame and get on with it - static bool waited = false; - if (g_MusicEventQueueLength && !waited) { - waited = true; +#ifndef PLATFORM_N64 + musicTickEvents(); #endif g_Vars.aioffset += 4; } else { u8 *cmd = g_Vars.ailist + g_Vars.aioffset; g_Vars.aioffset = chraiGoToLabel(g_Vars.ailist, g_Vars.aioffset, cmd[3]); -#ifndef PLATFORM_N64 - waited = false; -#endif } return false; diff --git a/src/game/timing.c b/src/game/timing.c index 747c7ed24..7bf96eb75 100644 --- a/src/game/timing.c +++ b/src/game/timing.c @@ -47,7 +47,7 @@ void frametimeCalculate(void) #ifndef PLATFORM_N64 if (g_TickExtraSleep) { - sysSleep(EXTRA_SLEEP_TIME); + sysFrameLimiterSleep(EXTRA_SLEEP_TIME); } #endif } while (g_Vars.mininc60 && diffframe60 < g_Vars.mininc60); diff --git a/src/include/lib/audiomgr.h b/src/include/lib/audiomgr.h index ec1d6ba0a..4e6f777bb 100644 --- a/src/include/lib/audiomgr.h +++ b/src/include/lib/audiomgr.h @@ -17,7 +17,11 @@ OSMesgQueue *amgrGetFrameMesgQueue(void); void amgrStopThread(void); #ifndef PLATFORM_N64 -void amgrFrame(void); +void amgrLock(void); +void amgrUnlock(void); +#else +#define amgrLock() do {} while(0) +#define amgrUnlock() do {} while(0) #endif #endif diff --git a/src/include/platform.h b/src/include/platform.h index 30615f750..5fc0ceca1 100644 --- a/src/include/platform.h +++ b/src/include/platform.h @@ -34,6 +34,9 @@ #error "64-bit platforms are currently not supported." #endif +// threading backend +#define PLATFORM_USE_PTHREAD 1 + // detect endianness #if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ diff --git a/src/lib/audiomgr.c b/src/lib/audiomgr.c index 7791798de..6d9250a01 100644 --- a/src/lib/audiomgr.c +++ b/src/lib/audiomgr.c @@ -10,6 +10,13 @@ #include "lib/libc/ll.h" #include "data.h" #include "types.h" +#ifndef PLATFORM_N64 +#include "lib/audiomgr.h" +#include "thread.h" +#include "audio.h" +#include "system.h" +#define AMGR_THREAD_SLEEP_US 2000 +#endif u32 var80091560; u32 var80091564; @@ -43,6 +50,14 @@ void *g_AudioSp; u32 var8005cf90 = 0x00000000; u8 var8005cf94 = 1; +#ifndef PLATFORM_N64 +systhread g_AmgrThread; +sysmutex g_AmgrMutex = SYSMUTEX_STATIC_INIT; +u32 g_AmgrFrame; +u32 g_AmgrFrameTarget; +void *amgrThread(void *arg); +#endif + void amgrHandleDoneMsg(AudioInfo *info); void amgrHandleFrameMsg(AudioInfo *info, AudioInfo *previnfo); void amgrMain(void *arg); @@ -155,6 +170,9 @@ void amgrStartThread(void) { osStartThread(&g_AudioManager.thread); g_AudioIsThreadRunning = true; +#ifndef PLATFORM_N64 + sysThreadCreate(&g_AmgrThread, &amgrThread, NULL); +#endif } OSMesgQueue *amgrGetFrameMesgQueue(void) @@ -171,6 +189,12 @@ void amgrStopThread(void) { if (g_AudioIsThreadRunning) { osStopThread(&g_AudioManager.thread); +#ifndef PLATFORM_N64 + amgrLock(); + g_AudioIsThreadRunning = false; + amgrUnlock(); + sysThreadJoin(&g_AmgrThread, NULL); +#endif } } @@ -321,11 +345,14 @@ void amgrHandleDoneMsg(AudioInfo *info) } #ifndef PLATFORM_N64 + void amgrFrame(void) { static AudioInfo *previnfo = NULL; static s32 count = 0; + amgrLock(); + var80091588 = osGetTime(); AudioInfo *info = g_AudioManager.audioInfo[g_AdmaCurFrame % 3]; @@ -363,6 +390,8 @@ void amgrFrame(void) count++; + amgrUnlock(); + var80091590 = osGetTime(); var80091570 = var80091590 - var80091588; @@ -377,4 +406,42 @@ void amgrFrame(void) var80091568 = var80091590 - var80091588; } } + +void amgrLock(void) +{ + sysMutexLock(&g_AmgrMutex); +} + +void amgrUnlock(void) +{ + sysMutexUnlock(&g_AmgrMutex); +} + +void *amgrThread(void *arg) +{ + const f64 frametime = 1000000.0 / (PAL ? 50.0 : 60.0); + bool running = g_AudioIsThreadRunning; + f64 nexttick = sysGetMicroseconds(); + f64 tick; + + while (running) + { + tick = sysGetMicroseconds() + 0.01; + + while (nexttick < tick) { + amgrFrame(); + g_AmgrFrame++; + nexttick += frametime; + } + + sysSleep(AMGR_THREAD_SLEEP_US); + + amgrLock(); + running = g_AudioIsThreadRunning; + amgrUnlock(); + } + + return NULL; +} + #endif