mgba-ps3/src/gba/gba-thread.c
2014-12-03 00:39:06 -08:00

653 lines
19 KiB
C

/* Copyright (c) 2013-2014 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "gba-thread.h"
#include "arm.h"
#include "gba.h"
#include "gba-config.h"
#include "gba-serialize.h"
#include "debugger/debugger.h"
#include "util/patch.h"
#include "util/png-io.h"
#include "util/vfs.h"
#include "platform/commandline.h"
#include <signal.h>
static const float _defaultFPSTarget = 60.f;
#ifdef USE_PTHREADS
static pthread_key_t _contextKey;
static pthread_once_t _contextOnce = PTHREAD_ONCE_INIT;
static void _createTLS(void) {
pthread_key_create(&_contextKey, 0);
}
#else
static DWORD _contextKey;
static INIT_ONCE _contextOnce = INIT_ONCE_STATIC_INIT;
static BOOL CALLBACK _createTLS(PINIT_ONCE once, PVOID param, PVOID* context) {
UNUSED(once);
UNUSED(param);
UNUSED(context);
_contextKey = TlsAlloc();
return TRUE;
}
#endif
static void _changeState(struct GBAThread* threadContext, enum ThreadState newState, bool broadcast) {
MutexLock(&threadContext->stateMutex);
threadContext->state = newState;
if (broadcast) {
ConditionWake(&threadContext->stateCond);
}
MutexUnlock(&threadContext->stateMutex);
}
static void _waitOnInterrupt(struct GBAThread* threadContext) {
while (threadContext->state == THREAD_INTERRUPTED) {
ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
}
}
static void _waitUntilNotState(struct GBAThread* threadContext, enum ThreadState oldState) {
while (threadContext->state == oldState) {
MutexUnlock(&threadContext->stateMutex);
MutexLock(&threadContext->sync.videoFrameMutex);
ConditionWake(&threadContext->sync.videoFrameRequiredCond);
MutexUnlock(&threadContext->sync.videoFrameMutex);
MutexLock(&threadContext->sync.audioBufferMutex);
ConditionWake(&threadContext->sync.audioRequiredCond);
MutexUnlock(&threadContext->sync.audioBufferMutex);
MutexLock(&threadContext->stateMutex);
ConditionWake(&threadContext->stateCond);
}
}
static void _pauseThread(struct GBAThread* threadContext, bool onThread) {
if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
threadContext->debugger->state = DEBUGGER_EXITING;
}
threadContext->state = THREAD_PAUSING;
if (!onThread) {
_waitUntilNotState(threadContext, THREAD_PAUSING);
}
}
static void _changeVideoSync(struct GBASync* sync, bool frameOn) {
// Make sure the video thread can process events while the GBA thread is paused
MutexLock(&sync->videoFrameMutex);
if (frameOn != sync->videoFrameOn) {
sync->videoFrameOn = frameOn;
ConditionWake(&sync->videoFrameAvailableCond);
}
MutexUnlock(&sync->videoFrameMutex);
}
static THREAD_ENTRY _GBAThreadRun(void* context) {
#ifdef USE_PTHREADS
pthread_once(&_contextOnce, _createTLS);
#else
InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
#endif
struct GBA gba;
struct ARMCore cpu;
struct Patch patch;
struct GBAThread* threadContext = context;
struct ARMComponent* components[1] = {};
int numComponents = 0;
if (threadContext->debugger) {
components[numComponents] = &threadContext->debugger->d;
++numComponents;
}
#if !defined(_WIN32) && defined(USE_PTHREADS)
sigset_t signals;
sigemptyset(&signals);
pthread_sigmask(SIG_SETMASK, &signals, 0);
#endif
GBACreate(&gba);
ARMSetComponents(&cpu, &gba.d, numComponents, components);
ARMInit(&cpu);
gba.sync = &threadContext->sync;
threadContext->gba = &gba;
gba.logLevel = threadContext->logLevel;
#ifdef USE_PTHREADS
pthread_setspecific(_contextKey, threadContext);
#else
TlsSetValue(_contextKey, threadContext);
#endif
if (threadContext->audioBuffers) {
GBAAudioResizeBuffer(&gba.audio, threadContext->audioBuffers);
} else {
threadContext->audioBuffers = GBA_AUDIO_SAMPLES;
}
if (threadContext->renderer) {
GBAVideoAssociateRenderer(&gba.video, threadContext->renderer);
}
if (threadContext->rom) {
GBALoadROM(&gba, threadContext->rom, threadContext->save, threadContext->fname);
if (threadContext->bios) {
GBALoadBIOS(&gba, threadContext->bios);
}
if (threadContext->patch && loadPatch(threadContext->patch, &patch)) {
GBAApplyPatch(&gba, &patch);
}
}
ARMReset(&cpu);
if (threadContext->debugger) {
threadContext->debugger->log = GBADebuggerLogShim;
GBAAttachDebugger(&gba, threadContext->debugger);
ARMDebuggerEnter(threadContext->debugger, DEBUGGER_ENTER_ATTACHED);
}
GBASIOSetDriverSet(&gba.sio, &threadContext->sioDrivers);
gba.keySource = &threadContext->activeKeys;
if (threadContext->startCallback) {
threadContext->startCallback(threadContext);
}
_changeState(threadContext, THREAD_RUNNING, true);
while (threadContext->state < THREAD_EXITING) {
if (threadContext->debugger) {
struct ARMDebugger* debugger = threadContext->debugger;
ARMDebuggerRun(debugger);
if (debugger->state == DEBUGGER_SHUTDOWN) {
_changeState(threadContext, THREAD_EXITING, false);
}
} else {
while (threadContext->state == THREAD_RUNNING) {
ARMRunLoop(&cpu);
}
}
int resetScheduled = 0;
MutexLock(&threadContext->stateMutex);
while (threadContext->state > THREAD_RUNNING && threadContext->state < THREAD_EXITING) {
if (threadContext->state == THREAD_PAUSING) {
threadContext->state = THREAD_PAUSED;
ConditionWake(&threadContext->stateCond);
}
if (threadContext->state == THREAD_INTERRUPTING) {
threadContext->state = THREAD_INTERRUPTED;
ConditionWake(&threadContext->stateCond);
}
if (threadContext->state == THREAD_RESETING) {
threadContext->state = THREAD_RUNNING;
resetScheduled = 1;
}
while (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_INTERRUPTED) {
ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
}
}
MutexUnlock(&threadContext->stateMutex);
if (resetScheduled) {
ARMReset(&cpu);
}
}
while (threadContext->state != THREAD_SHUTDOWN) {
_changeState(threadContext, THREAD_SHUTDOWN, false);
}
if (threadContext->cleanCallback) {
threadContext->cleanCallback(threadContext);
}
threadContext->gba = 0;
ARMDeinit(&cpu);
GBADestroy(&gba);
threadContext->sync.videoFrameOn = false;
ConditionWake(&threadContext->sync.videoFrameAvailableCond);
ConditionWake(&threadContext->sync.audioRequiredCond);
return 0;
}
void GBAMapOptionsToContext(const struct GBAOptions* opts, struct GBAThread* threadContext) {
threadContext->bios = VFileOpen(opts->bios, O_RDONLY);
threadContext->frameskip = opts->frameskip;
threadContext->logLevel = opts->logLevel;
threadContext->rewindBufferCapacity = opts->rewindBufferCapacity;
threadContext->rewindBufferInterval = opts->rewindBufferInterval;
threadContext->sync.audioWait = opts->audioSync;
threadContext->sync.videoFrameWait = opts->videoSync;
if (opts->fpsTarget) {
threadContext->fpsTarget = opts->fpsTarget;
}
if (opts->audioBuffers) {
threadContext->audioBuffers = opts->audioBuffers;
}
}
void GBAMapArgumentsToContext(const struct GBAArguments* args, struct GBAThread* threadContext) {
if (args->dirmode) {
threadContext->gameDir = VDirOpen(args->fname);
threadContext->stateDir = threadContext->gameDir;
} else {
threadContext->rom = VFileOpen(args->fname, O_RDONLY);
#if ENABLE_LIBZIP
threadContext->gameDir = VDirOpenZip(args->fname, 0);
#endif
}
threadContext->fname = args->fname;
threadContext->patch = VFileOpen(args->patch, O_RDONLY);
}
bool GBAThreadStart(struct GBAThread* threadContext) {
// TODO: error check
threadContext->activeKeys = 0;
threadContext->state = THREAD_INITIALIZED;
threadContext->sync.videoFrameOn = true;
threadContext->sync.videoFrameSkip = 0;
threadContext->rewindBufferNext = threadContext->rewindBufferInterval;
threadContext->rewindBufferSize = 0;
if (threadContext->rewindBufferCapacity) {
threadContext->rewindBuffer = calloc(threadContext->rewindBufferCapacity, sizeof(void*));
} else {
threadContext->rewindBuffer = 0;
}
if (!threadContext->fpsTarget) {
threadContext->fpsTarget = _defaultFPSTarget;
}
if (threadContext->rom && !GBAIsROM(threadContext->rom)) {
threadContext->rom->close(threadContext->rom);
threadContext->rom = 0;
}
if (threadContext->gameDir) {
threadContext->gameDir->rewind(threadContext->gameDir);
struct VDirEntry* dirent = threadContext->gameDir->listNext(threadContext->gameDir);
while (dirent) {
struct Patch patchTemp;
struct VFile* vf = threadContext->gameDir->openFile(threadContext->gameDir, dirent->name(dirent), O_RDONLY);
if (!vf) {
continue;
}
if (!threadContext->rom && GBAIsROM(vf)) {
threadContext->rom = vf;
} else if (!threadContext->patch && loadPatch(vf, &patchTemp)) {
threadContext->patch = vf;
} else {
vf->close(vf);
}
dirent = threadContext->gameDir->listNext(threadContext->gameDir);
}
}
if (!threadContext->rom) {
threadContext->state = THREAD_SHUTDOWN;
return false;
}
threadContext->save = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "sram", ".sav", O_CREAT | O_RDWR);
MutexInit(&threadContext->stateMutex);
ConditionInit(&threadContext->stateCond);
MutexInit(&threadContext->sync.videoFrameMutex);
ConditionInit(&threadContext->sync.videoFrameAvailableCond);
ConditionInit(&threadContext->sync.videoFrameRequiredCond);
MutexInit(&threadContext->sync.audioBufferMutex);
ConditionInit(&threadContext->sync.audioRequiredCond);
threadContext->interruptDepth = 0;
#ifndef _WIN32
sigset_t signals;
sigemptyset(&signals);
sigaddset(&signals, SIGINT);
sigaddset(&signals, SIGTRAP);
pthread_sigmask(SIG_BLOCK, &signals, 0);
#endif
MutexLock(&threadContext->stateMutex);
ThreadCreate(&threadContext->thread, _GBAThreadRun, threadContext);
while (threadContext->state < THREAD_RUNNING) {
ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
}
MutexUnlock(&threadContext->stateMutex);
return true;
}
bool GBAThreadHasStarted(struct GBAThread* threadContext) {
bool hasStarted;
MutexLock(&threadContext->stateMutex);
hasStarted = threadContext->state > THREAD_INITIALIZED;
MutexUnlock(&threadContext->stateMutex);
return hasStarted;
}
void GBAThreadEnd(struct GBAThread* threadContext) {
MutexLock(&threadContext->stateMutex);
if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
threadContext->debugger->state = DEBUGGER_EXITING;
}
threadContext->state = THREAD_EXITING;
ConditionWake(&threadContext->stateCond);
MutexUnlock(&threadContext->stateMutex);
MutexLock(&threadContext->sync.audioBufferMutex);
threadContext->sync.audioWait = 0;
ConditionWake(&threadContext->sync.audioRequiredCond);
MutexUnlock(&threadContext->sync.audioBufferMutex);
}
void GBAThreadReset(struct GBAThread* threadContext) {
MutexLock(&threadContext->stateMutex);
_waitOnInterrupt(threadContext);
threadContext->state = THREAD_RESETING;
ConditionWake(&threadContext->stateCond);
MutexUnlock(&threadContext->stateMutex);
}
void GBAThreadJoin(struct GBAThread* threadContext) {
MutexLock(&threadContext->sync.videoFrameMutex);
threadContext->sync.videoFrameWait = 0;
ConditionWake(&threadContext->sync.videoFrameRequiredCond);
MutexUnlock(&threadContext->sync.videoFrameMutex);
ThreadJoin(threadContext->thread);
MutexDeinit(&threadContext->stateMutex);
ConditionDeinit(&threadContext->stateCond);
MutexDeinit(&threadContext->sync.videoFrameMutex);
ConditionWake(&threadContext->sync.videoFrameAvailableCond);
ConditionDeinit(&threadContext->sync.videoFrameAvailableCond);
ConditionWake(&threadContext->sync.videoFrameRequiredCond);
ConditionDeinit(&threadContext->sync.videoFrameRequiredCond);
ConditionWake(&threadContext->sync.audioRequiredCond);
ConditionDeinit(&threadContext->sync.audioRequiredCond);
MutexDeinit(&threadContext->sync.audioBufferMutex);
int i;
for (i = 0; i < threadContext->rewindBufferCapacity; ++i) {
if (threadContext->rewindBuffer[i]) {
GBADeallocateState(threadContext->rewindBuffer[i]);
}
}
free(threadContext->rewindBuffer);
if (threadContext->rom) {
threadContext->rom->close(threadContext->rom);
threadContext->rom = 0;
}
if (threadContext->save) {
threadContext->save->close(threadContext->save);
threadContext->save = 0;
}
if (threadContext->bios) {
threadContext->bios->close(threadContext->bios);
threadContext->bios = 0;
}
if (threadContext->patch) {
threadContext->patch->close(threadContext->patch);
threadContext->patch = 0;
}
if (threadContext->gameDir) {
if (threadContext->stateDir == threadContext->gameDir) {
threadContext->stateDir = 0;
}
threadContext->gameDir->close(threadContext->gameDir);
threadContext->gameDir = 0;
}
if (threadContext->stateDir) {
threadContext->stateDir->close(threadContext->stateDir);
threadContext->stateDir = 0;
}
}
bool GBAThreadIsActive(struct GBAThread* threadContext) {
return threadContext->state >= THREAD_RUNNING && threadContext->state < THREAD_EXITING;
}
void GBAThreadInterrupt(struct GBAThread* threadContext) {
MutexLock(&threadContext->stateMutex);
++threadContext->interruptDepth;
if (threadContext->interruptDepth > 1 || !GBAThreadIsActive(threadContext)) {
MutexUnlock(&threadContext->stateMutex);
return;
}
threadContext->savedState = threadContext->state;
_waitOnInterrupt(threadContext);
threadContext->state = THREAD_INTERRUPTING;
if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
threadContext->debugger->state = DEBUGGER_EXITING;
}
ConditionWake(&threadContext->stateCond);
_waitUntilNotState(threadContext, THREAD_INTERRUPTING);
MutexUnlock(&threadContext->stateMutex);
}
void GBAThreadContinue(struct GBAThread* threadContext) {
MutexLock(&threadContext->stateMutex);
--threadContext->interruptDepth;
if (threadContext->interruptDepth < 1 && GBAThreadIsActive(threadContext)) {
threadContext->state = threadContext->savedState;
ConditionWake(&threadContext->stateCond);
}
MutexUnlock(&threadContext->stateMutex);
}
void GBAThreadPause(struct GBAThread* threadContext) {
bool frameOn = true;
MutexLock(&threadContext->stateMutex);
_waitOnInterrupt(threadContext);
if (threadContext->state == THREAD_RUNNING) {
_pauseThread(threadContext, false);
frameOn = false;
}
MutexUnlock(&threadContext->stateMutex);
_changeVideoSync(&threadContext->sync, frameOn);
}
void GBAThreadUnpause(struct GBAThread* threadContext) {
MutexLock(&threadContext->stateMutex);
_waitOnInterrupt(threadContext);
if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
threadContext->state = THREAD_RUNNING;
ConditionWake(&threadContext->stateCond);
}
MutexUnlock(&threadContext->stateMutex);
_changeVideoSync(&threadContext->sync, true);
}
bool GBAThreadIsPaused(struct GBAThread* threadContext) {
bool isPaused;
MutexLock(&threadContext->stateMutex);
_waitOnInterrupt(threadContext);
isPaused = threadContext->state == THREAD_PAUSED;
MutexUnlock(&threadContext->stateMutex);
return isPaused;
}
void GBAThreadTogglePause(struct GBAThread* threadContext) {
bool frameOn = true;
MutexLock(&threadContext->stateMutex);
_waitOnInterrupt(threadContext);
if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
threadContext->state = THREAD_RUNNING;
ConditionWake(&threadContext->stateCond);
} else if (threadContext->state == THREAD_RUNNING) {
_pauseThread(threadContext, false);
frameOn = false;
}
MutexUnlock(&threadContext->stateMutex);
_changeVideoSync(&threadContext->sync, frameOn);
}
void GBAThreadPauseFromThread(struct GBAThread* threadContext) {
bool frameOn = true;
MutexLock(&threadContext->stateMutex);
_waitOnInterrupt(threadContext);
if (threadContext->state == THREAD_RUNNING) {
_pauseThread(threadContext, true);
frameOn = false;
}
MutexUnlock(&threadContext->stateMutex);
_changeVideoSync(&threadContext->sync, frameOn);
}
#ifdef USE_PTHREADS
struct GBAThread* GBAThreadGetContext(void) {
pthread_once(&_contextOnce, _createTLS);
return pthread_getspecific(_contextKey);
}
#else
struct GBAThread* GBAThreadGetContext(void) {
InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
return TlsGetValue(_contextKey);
}
#endif
#ifdef USE_PNG
void GBAThreadTakeScreenshot(struct GBAThread* threadContext) {
unsigned stride;
void* pixels = 0;
struct VFile* vf = VDirOptionalOpenIncrementFile(threadContext->stateDir, threadContext->gba->activeFile, "screenshot", "-", ".png", O_CREAT | O_TRUNC | O_WRONLY);
threadContext->gba->video.renderer->getPixels(threadContext->gba->video.renderer, &stride, &pixels);
png_structp png = PNGWriteOpen(vf);
png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels);
PNGWriteClose(png, info);
vf->close(vf);
}
#endif
void GBASyncPostFrame(struct GBASync* sync) {
if (!sync) {
return;
}
MutexLock(&sync->videoFrameMutex);
++sync->videoFramePending;
--sync->videoFrameSkip;
if (sync->videoFrameSkip < 0) {
do {
ConditionWake(&sync->videoFrameAvailableCond);
if (sync->videoFrameWait) {
ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex);
}
} while (sync->videoFrameWait && sync->videoFramePending);
}
MutexUnlock(&sync->videoFrameMutex);
struct GBAThread* thread = GBAThreadGetContext();
if (!thread) {
return;
}
if (thread->rewindBuffer) {
--thread->rewindBufferNext;
if (thread->rewindBufferNext <= 0) {
thread->rewindBufferNext = thread->rewindBufferInterval;
GBARecordFrame(thread);
}
}
if (thread->stream) {
thread->stream->postVideoFrame(thread->stream, thread->renderer);
}
if (thread->frameCallback) {
thread->frameCallback(thread);
}
}
bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) {
if (!sync) {
return true;
}
MutexLock(&sync->videoFrameMutex);
ConditionWake(&sync->videoFrameRequiredCond);
if (!sync->videoFrameOn && !sync->videoFramePending) {
return false;
}
if (sync->videoFrameOn && !sync->videoFramePending) {
ConditionWait(&sync->videoFrameAvailableCond, &sync->videoFrameMutex);
}
sync->videoFramePending = 0;
sync->videoFrameSkip = frameskip;
return true;
}
void GBASyncWaitFrameEnd(struct GBASync* sync) {
if (!sync) {
return;
}
MutexUnlock(&sync->videoFrameMutex);
}
bool GBASyncDrawingFrame(struct GBASync* sync) {
return sync->videoFrameSkip <= 0;
}
void GBASyncSuspendDrawing(struct GBASync* sync) {
_changeVideoSync(sync, false);
}
void GBASyncResumeDrawing(struct GBASync* sync) {
_changeVideoSync(sync, true);
}
void GBASyncProduceAudio(struct GBASync* sync, bool wait) {
if (sync->audioWait && wait) {
// TODO loop properly in event of spurious wakeups
ConditionWait(&sync->audioRequiredCond, &sync->audioBufferMutex);
}
MutexUnlock(&sync->audioBufferMutex);
}
void GBASyncLockAudio(struct GBASync* sync) {
MutexLock(&sync->audioBufferMutex);
}
void GBASyncUnlockAudio(struct GBASync* sync) {
MutexUnlock(&sync->audioBufferMutex);
}
void GBASyncConsumeAudio(struct GBASync* sync) {
ConditionWake(&sync->audioRequiredCond);
MutexUnlock(&sync->audioBufferMutex);
}