mirror of https://github.com/mgba-emu/mgba.git
Core: Add mCoreThread
This commit is contained in:
parent
cfd031f140
commit
192f85259a
|
@ -19,6 +19,7 @@ typedef uint32_t color_t;
|
||||||
#define BYTES_PER_PIXEL 4
|
#define BYTES_PER_PIXEL 4
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
struct mCoreSync;
|
||||||
struct mCore {
|
struct mCore {
|
||||||
void* cpu;
|
void* cpu;
|
||||||
void* board;
|
void* board;
|
||||||
|
@ -26,6 +27,8 @@ struct mCore {
|
||||||
bool (*init)(struct mCore*);
|
bool (*init)(struct mCore*);
|
||||||
void (*deinit)(struct mCore*);
|
void (*deinit)(struct mCore*);
|
||||||
|
|
||||||
|
void (*setSync)(struct mCore*, struct mCoreSync*);
|
||||||
|
|
||||||
void (*desiredVideoDimensions)(struct mCore*, unsigned* width, unsigned* height);
|
void (*desiredVideoDimensions)(struct mCore*, unsigned* width, unsigned* height);
|
||||||
void (*setVideoBuffer)(struct mCore*, color_t* buffer, size_t stride);
|
void (*setVideoBuffer)(struct mCore*, color_t* buffer, size_t stride);
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,15 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
|
#include "core/thread.h"
|
||||||
|
|
||||||
#define MAX_CATEGORY 64
|
#define MAX_CATEGORY 64
|
||||||
|
|
||||||
struct mLogger* mLogGetContext(void) {
|
struct mLogger* mLogGetContext(void) {
|
||||||
|
struct mLogger* logger = mCoreThreadLogger();
|
||||||
|
if (logger) {
|
||||||
|
return logger;
|
||||||
|
}
|
||||||
return NULL; // TODO
|
return NULL; // TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,7 @@ enum mLogLevel {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct mLogger {
|
struct mLogger {
|
||||||
ATTRIBUTE_FORMAT(printf, 4, 5)
|
void (*log)(struct mLogger*, int category, enum mLogLevel level, const char* format, va_list args);
|
||||||
void (*log)(struct mLogger*, int category, enum mLogLevel level, const char* format, ...);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct mLogger* mLogGetContext(void);
|
struct mLogger* mLogGetContext(void);
|
||||||
|
|
|
@ -0,0 +1,392 @@
|
||||||
|
/* Copyright (c) 2013-2016 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 "thread.h"
|
||||||
|
|
||||||
|
#include "core/core.h"
|
||||||
|
#include "util/patch.h"
|
||||||
|
#include "util/vfs.h"
|
||||||
|
|
||||||
|
#include "platform/commandline.h"
|
||||||
|
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
#ifndef DISABLE_THREADING
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
#elif _WIN32
|
||||||
|
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 mCoreThread* threadContext, enum mCoreThreadState newState, bool broadcast) {
|
||||||
|
MutexLock(&threadContext->stateMutex);
|
||||||
|
threadContext->state = newState;
|
||||||
|
if (broadcast) {
|
||||||
|
ConditionWake(&threadContext->stateCond);
|
||||||
|
}
|
||||||
|
MutexUnlock(&threadContext->stateMutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _waitOnInterrupt(struct mCoreThread* threadContext) {
|
||||||
|
while (threadContext->state == THREAD_INTERRUPTED) {
|
||||||
|
ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _waitUntilNotState(struct mCoreThread* threadContext, enum mCoreThreadState oldState) {
|
||||||
|
while (threadContext->state == oldState) {
|
||||||
|
MutexUnlock(&threadContext->stateMutex);
|
||||||
|
|
||||||
|
MutexLock(&threadContext->stateMutex);
|
||||||
|
ConditionWake(&threadContext->stateCond);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _pauseThread(struct mCoreThread* threadContext, bool onThread) {
|
||||||
|
threadContext->state = THREAD_PAUSING;
|
||||||
|
if (!onThread) {
|
||||||
|
_waitUntilNotState(threadContext, THREAD_PAUSING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static THREAD_ENTRY _mCoreThreadRun(void* context) {
|
||||||
|
struct mCoreThread* threadContext = context;
|
||||||
|
#ifdef USE_PTHREADS
|
||||||
|
pthread_once(&_contextOnce, _createTLS);
|
||||||
|
pthread_setspecific(_contextKey, threadContext);
|
||||||
|
#elif _WIN32
|
||||||
|
InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
|
||||||
|
TlsSetValue(_contextKey, threadContext);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ThreadSetName("CPU Thread");
|
||||||
|
|
||||||
|
#if !defined(_WIN32) && defined(USE_PTHREADS)
|
||||||
|
sigset_t signals;
|
||||||
|
sigemptyset(&signals);
|
||||||
|
pthread_sigmask(SIG_SETMASK, &signals, 0);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct mCore* core = threadContext->core;
|
||||||
|
core->setSync(core, &threadContext->sync);
|
||||||
|
core->reset(core);
|
||||||
|
|
||||||
|
if (threadContext->startCallback) {
|
||||||
|
threadContext->startCallback(threadContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
_changeState(threadContext, THREAD_RUNNING, true);
|
||||||
|
|
||||||
|
while (threadContext->state < THREAD_EXITING) {
|
||||||
|
core->runLoop(core);
|
||||||
|
|
||||||
|
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_RUN_ON) {
|
||||||
|
if (threadContext->run) {
|
||||||
|
threadContext->run(threadContext);
|
||||||
|
}
|
||||||
|
threadContext->state = threadContext->savedState;
|
||||||
|
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) {
|
||||||
|
core->reset(core);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (threadContext->state < THREAD_SHUTDOWN) {
|
||||||
|
_changeState(threadContext, THREAD_SHUTDOWN, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (threadContext->cleanCallback) {
|
||||||
|
threadContext->cleanCallback(threadContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mCoreThreadStart(struct mCoreThread* threadContext) {
|
||||||
|
threadContext->state = THREAD_INITIALIZED;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
#ifdef USE_PTHREADS
|
||||||
|
sigset_t signals;
|
||||||
|
sigemptyset(&signals);
|
||||||
|
sigaddset(&signals, SIGINT);
|
||||||
|
sigaddset(&signals, SIGTRAP);
|
||||||
|
pthread_sigmask(SIG_BLOCK, &signals, 0);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
MutexLock(&threadContext->stateMutex);
|
||||||
|
ThreadCreate(&threadContext->thread, _mCoreThreadRun, threadContext);
|
||||||
|
while (threadContext->state < THREAD_RUNNING) {
|
||||||
|
ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
|
||||||
|
}
|
||||||
|
MutexUnlock(&threadContext->stateMutex);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mCoreThreadHasStarted(struct mCoreThread* threadContext) {
|
||||||
|
bool hasStarted;
|
||||||
|
MutexLock(&threadContext->stateMutex);
|
||||||
|
hasStarted = threadContext->state > THREAD_INITIALIZED;
|
||||||
|
MutexUnlock(&threadContext->stateMutex);
|
||||||
|
return hasStarted;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mCoreThreadHasExited(struct mCoreThread* threadContext) {
|
||||||
|
bool hasExited;
|
||||||
|
MutexLock(&threadContext->stateMutex);
|
||||||
|
hasExited = threadContext->state > THREAD_EXITING;
|
||||||
|
MutexUnlock(&threadContext->stateMutex);
|
||||||
|
return hasExited;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mCoreThreadHasCrashed(struct mCoreThread* threadContext) {
|
||||||
|
bool hasExited;
|
||||||
|
MutexLock(&threadContext->stateMutex);
|
||||||
|
hasExited = threadContext->state == THREAD_CRASHED;
|
||||||
|
MutexUnlock(&threadContext->stateMutex);
|
||||||
|
return hasExited;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mCoreThreadEnd(struct mCoreThread* threadContext) {
|
||||||
|
MutexLock(&threadContext->stateMutex);
|
||||||
|
_waitOnInterrupt(threadContext);
|
||||||
|
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);
|
||||||
|
|
||||||
|
MutexLock(&threadContext->sync.videoFrameMutex);
|
||||||
|
threadContext->sync.videoFrameWait = false;
|
||||||
|
threadContext->sync.videoFrameOn = false;
|
||||||
|
ConditionWake(&threadContext->sync.videoFrameRequiredCond);
|
||||||
|
ConditionWake(&threadContext->sync.videoFrameAvailableCond);
|
||||||
|
MutexUnlock(&threadContext->sync.videoFrameMutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mCoreThreadReset(struct mCoreThread* threadContext) {
|
||||||
|
MutexLock(&threadContext->stateMutex);
|
||||||
|
_waitOnInterrupt(threadContext);
|
||||||
|
threadContext->state = THREAD_RESETING;
|
||||||
|
ConditionWake(&threadContext->stateCond);
|
||||||
|
MutexUnlock(&threadContext->stateMutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mCoreThreadJoin(struct mCoreThread* threadContext) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mCoreThreadIsActive(struct mCoreThread* threadContext) {
|
||||||
|
return threadContext->state >= THREAD_RUNNING && threadContext->state < THREAD_EXITING;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mCoreThreadInterrupt(struct mCoreThread* threadContext) {
|
||||||
|
if (!threadContext) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MutexLock(&threadContext->stateMutex);
|
||||||
|
++threadContext->interruptDepth;
|
||||||
|
if (threadContext->interruptDepth > 1 || !mCoreThreadIsActive(threadContext)) {
|
||||||
|
MutexUnlock(&threadContext->stateMutex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
threadContext->savedState = threadContext->state;
|
||||||
|
_waitOnInterrupt(threadContext);
|
||||||
|
threadContext->state = THREAD_INTERRUPTING;
|
||||||
|
ConditionWake(&threadContext->stateCond);
|
||||||
|
_waitUntilNotState(threadContext, THREAD_INTERRUPTING);
|
||||||
|
MutexUnlock(&threadContext->stateMutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mCoreThreadContinue(struct mCoreThread* threadContext) {
|
||||||
|
if (!threadContext) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MutexLock(&threadContext->stateMutex);
|
||||||
|
--threadContext->interruptDepth;
|
||||||
|
if (threadContext->interruptDepth < 1 && mCoreThreadIsActive(threadContext)) {
|
||||||
|
threadContext->state = threadContext->savedState;
|
||||||
|
ConditionWake(&threadContext->stateCond);
|
||||||
|
}
|
||||||
|
MutexUnlock(&threadContext->stateMutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mCoreThreadRunFunction(struct mCoreThread* threadContext, void (*run)(struct mCoreThread*)) {
|
||||||
|
MutexLock(&threadContext->stateMutex);
|
||||||
|
threadContext->run = run;
|
||||||
|
_waitOnInterrupt(threadContext);
|
||||||
|
threadContext->savedState = threadContext->state;
|
||||||
|
threadContext->state = THREAD_RUN_ON;
|
||||||
|
ConditionWake(&threadContext->stateCond);
|
||||||
|
_waitUntilNotState(threadContext, THREAD_RUN_ON);
|
||||||
|
MutexUnlock(&threadContext->stateMutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mCoreThreadPause(struct mCoreThread* threadContext) {
|
||||||
|
bool frameOn = threadContext->sync.videoFrameOn;
|
||||||
|
MutexLock(&threadContext->stateMutex);
|
||||||
|
_waitOnInterrupt(threadContext);
|
||||||
|
if (threadContext->state == THREAD_RUNNING) {
|
||||||
|
_pauseThread(threadContext, false);
|
||||||
|
threadContext->frameWasOn = frameOn;
|
||||||
|
frameOn = false;
|
||||||
|
}
|
||||||
|
MutexUnlock(&threadContext->stateMutex);
|
||||||
|
|
||||||
|
mCoreSyncSetVideoSync(&threadContext->sync, frameOn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mCoreThreadUnpause(struct mCoreThread* threadContext) {
|
||||||
|
bool frameOn = threadContext->sync.videoFrameOn;
|
||||||
|
MutexLock(&threadContext->stateMutex);
|
||||||
|
_waitOnInterrupt(threadContext);
|
||||||
|
if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
|
||||||
|
threadContext->state = THREAD_RUNNING;
|
||||||
|
ConditionWake(&threadContext->stateCond);
|
||||||
|
frameOn = threadContext->frameWasOn;
|
||||||
|
}
|
||||||
|
MutexUnlock(&threadContext->stateMutex);
|
||||||
|
|
||||||
|
mCoreSyncSetVideoSync(&threadContext->sync, frameOn);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mCoreThreadIsPaused(struct mCoreThread* threadContext) {
|
||||||
|
bool isPaused;
|
||||||
|
MutexLock(&threadContext->stateMutex);
|
||||||
|
_waitOnInterrupt(threadContext);
|
||||||
|
isPaused = threadContext->state == THREAD_PAUSED;
|
||||||
|
MutexUnlock(&threadContext->stateMutex);
|
||||||
|
return isPaused;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mCoreThreadTogglePause(struct mCoreThread* threadContext) {
|
||||||
|
bool frameOn = threadContext->sync.videoFrameOn;
|
||||||
|
MutexLock(&threadContext->stateMutex);
|
||||||
|
_waitOnInterrupt(threadContext);
|
||||||
|
if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
|
||||||
|
threadContext->state = THREAD_RUNNING;
|
||||||
|
ConditionWake(&threadContext->stateCond);
|
||||||
|
frameOn = threadContext->frameWasOn;
|
||||||
|
} else if (threadContext->state == THREAD_RUNNING) {
|
||||||
|
_pauseThread(threadContext, false);
|
||||||
|
threadContext->frameWasOn = frameOn;
|
||||||
|
frameOn = false;
|
||||||
|
}
|
||||||
|
MutexUnlock(&threadContext->stateMutex);
|
||||||
|
|
||||||
|
mCoreSyncSetVideoSync(&threadContext->sync, frameOn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mCoreThreadPauseFromThread(struct mCoreThread* threadContext) {
|
||||||
|
bool frameOn = true;
|
||||||
|
MutexLock(&threadContext->stateMutex);
|
||||||
|
_waitOnInterrupt(threadContext);
|
||||||
|
if (threadContext->state == THREAD_RUNNING) {
|
||||||
|
_pauseThread(threadContext, true);
|
||||||
|
frameOn = false;
|
||||||
|
}
|
||||||
|
MutexUnlock(&threadContext->stateMutex);
|
||||||
|
|
||||||
|
mCoreSyncSetVideoSync(&threadContext->sync, frameOn);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_PTHREADS
|
||||||
|
struct mCoreThread* mCoreThreadGet(void) {
|
||||||
|
pthread_once(&_contextOnce, _createTLS);
|
||||||
|
return pthread_getspecific(_contextKey);
|
||||||
|
}
|
||||||
|
#elif _WIN32
|
||||||
|
struct mCoreThread* mCoreThreadGet(void) {
|
||||||
|
InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
|
||||||
|
return TlsGetValue(_contextKey);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#else
|
||||||
|
struct mCoreThread* mCoreThreadGet(void) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void _mCoreLog(struct mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args) {
|
||||||
|
printf("%s: ", mLogCategoryName(category));
|
||||||
|
vprintf(format, args);
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
struct mLogger* mCoreThreadLogger(void) {
|
||||||
|
struct mCoreThread* thread = mCoreThreadGet();
|
||||||
|
if (thread) {
|
||||||
|
if (!thread->logger.log) {
|
||||||
|
thread->logger.log = _mCoreLog;
|
||||||
|
}
|
||||||
|
return &thread->logger;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
/* Copyright (c) 2013-2016 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/. */
|
||||||
|
#ifndef M_CORE_THREAD_H
|
||||||
|
#define M_CORE_THREAD_H
|
||||||
|
|
||||||
|
#include "util/common.h"
|
||||||
|
|
||||||
|
#include "core/log.h"
|
||||||
|
#include "core/sync.h"
|
||||||
|
#include "util/threading.h"
|
||||||
|
|
||||||
|
struct mCoreThread;
|
||||||
|
struct mCore;
|
||||||
|
|
||||||
|
typedef void (*ThreadCallback)(struct mCoreThread* threadContext);
|
||||||
|
|
||||||
|
enum mCoreThreadState {
|
||||||
|
THREAD_INITIALIZED = -1,
|
||||||
|
THREAD_RUNNING = 0,
|
||||||
|
THREAD_INTERRUPTED,
|
||||||
|
THREAD_INTERRUPTING,
|
||||||
|
THREAD_PAUSED,
|
||||||
|
THREAD_PAUSING,
|
||||||
|
THREAD_RUN_ON,
|
||||||
|
THREAD_RESETING,
|
||||||
|
THREAD_EXITING,
|
||||||
|
THREAD_SHUTDOWN,
|
||||||
|
THREAD_CRASHED
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mCoreThread {
|
||||||
|
// Input
|
||||||
|
struct mCore* core;
|
||||||
|
|
||||||
|
// Threading state
|
||||||
|
Thread thread;
|
||||||
|
enum mCoreThreadState state;
|
||||||
|
|
||||||
|
Mutex stateMutex;
|
||||||
|
Condition stateCond;
|
||||||
|
enum mCoreThreadState savedState;
|
||||||
|
int interruptDepth;
|
||||||
|
bool frameWasOn;
|
||||||
|
|
||||||
|
struct mLogger logger;
|
||||||
|
enum mLogLevel logLevel;
|
||||||
|
ThreadCallback startCallback;
|
||||||
|
ThreadCallback cleanCallback;
|
||||||
|
ThreadCallback frameCallback;
|
||||||
|
void* userData;
|
||||||
|
void (*run)(struct mCoreThread*);
|
||||||
|
|
||||||
|
struct mCoreSync sync;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool mCoreThreadStart(struct mCoreThread* threadContext);
|
||||||
|
bool mCoreThreadHasStarted(struct mCoreThread* threadContext);
|
||||||
|
bool mCoreThreadHasExited(struct mCoreThread* threadContext);
|
||||||
|
bool mCoreThreadHasCrashed(struct mCoreThread* threadContext);
|
||||||
|
void mCoreThreadEnd(struct mCoreThread* threadContext);
|
||||||
|
void mCoreThreadReset(struct mCoreThread* threadContext);
|
||||||
|
void mCoreThreadJoin(struct mCoreThread* threadContext);
|
||||||
|
|
||||||
|
bool mCoreThreadIsActive(struct mCoreThread* threadContext);
|
||||||
|
void mCoreThreadInterrupt(struct mCoreThread* threadContext);
|
||||||
|
void mCoreThreadContinue(struct mCoreThread* threadContext);
|
||||||
|
|
||||||
|
void mCoreThreadRunFunction(struct mCoreThread* threadContext, void (*run)(struct mCoreThread*));
|
||||||
|
|
||||||
|
void mCoreThreadPause(struct mCoreThread* threadContext);
|
||||||
|
void mCoreThreadUnpause(struct mCoreThread* threadContext);
|
||||||
|
bool mCoreThreadIsPaused(struct mCoreThread* threadContext);
|
||||||
|
void mCoreThreadTogglePause(struct mCoreThread* threadContext);
|
||||||
|
void mCoreThreadPauseFromThread(struct mCoreThread* threadContext);
|
||||||
|
|
||||||
|
struct mCoreThread* mCoreThreadGet(void);
|
||||||
|
struct mLogger* mCoreThreadLogger(void);
|
||||||
|
|
||||||
|
#endif
|
|
@ -48,6 +48,11 @@ static void _GBCoreDeinit(struct mCore* core) {
|
||||||
mappedMemoryFree(core->board, sizeof(struct GB));
|
mappedMemoryFree(core->board, sizeof(struct GB));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void _GBCoreSetSync(struct mCore* core, struct mCoreSync* sync) {
|
||||||
|
struct GB* gb = core->board;
|
||||||
|
gb->sync = sync;
|
||||||
|
}
|
||||||
|
|
||||||
static void _GBCoreDesiredVideoDimensions(struct mCore* core, unsigned* width, unsigned* height) {
|
static void _GBCoreDesiredVideoDimensions(struct mCore* core, unsigned* width, unsigned* height) {
|
||||||
UNUSED(core);
|
UNUSED(core);
|
||||||
*width = GB_VIDEO_HORIZONTAL_PIXELS;
|
*width = GB_VIDEO_HORIZONTAL_PIXELS;
|
||||||
|
@ -136,6 +141,7 @@ struct mCore* GBCoreCreate(void) {
|
||||||
core->board = 0;
|
core->board = 0;
|
||||||
core->init = _GBCoreInit;
|
core->init = _GBCoreInit;
|
||||||
core->deinit = _GBCoreDeinit;
|
core->deinit = _GBCoreDeinit;
|
||||||
|
core->setSync = _GBCoreSetSync;
|
||||||
core->desiredVideoDimensions = _GBCoreDesiredVideoDimensions;
|
core->desiredVideoDimensions = _GBCoreDesiredVideoDimensions;
|
||||||
core->setVideoBuffer = _GBCoreSetVideoBuffer;
|
core->setVideoBuffer = _GBCoreSetVideoBuffer;
|
||||||
core->isROM = _GBCoreIsROM;
|
core->isROM = _GBCoreIsROM;
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
#include "video.h"
|
#include "video.h"
|
||||||
|
|
||||||
|
#include "core/sync.h"
|
||||||
#include "gb/gb.h"
|
#include "gb/gb.h"
|
||||||
#include "gb/io.h"
|
#include "gb/io.h"
|
||||||
|
|
||||||
|
@ -112,6 +113,7 @@ int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) {
|
||||||
video->mode = 1;
|
video->mode = 1;
|
||||||
++video->frameCounter;
|
++video->frameCounter;
|
||||||
video->renderer->finishFrame(video->renderer);
|
video->renderer->finishFrame(video->renderer);
|
||||||
|
mCoreSyncPostFrame(video->p->sync);
|
||||||
if (GBRegisterSTATIsVblankIRQ(video->stat) || GBRegisterSTATIsOAMIRQ(video->stat)) {
|
if (GBRegisterSTATIsVblankIRQ(video->stat) || GBRegisterSTATIsOAMIRQ(video->stat)) {
|
||||||
video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT);
|
video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT);
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,7 @@ static BOOL CALLBACK _createTLS(PINIT_ONCE once, PVOID param, PVOID* context) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static void _changeState(struct GBAThread* threadContext, enum ThreadState newState, bool broadcast) {
|
static void _changeState(struct GBAThread* threadContext, enum mCoreThreadState newState, bool broadcast) {
|
||||||
MutexLock(&threadContext->stateMutex);
|
MutexLock(&threadContext->stateMutex);
|
||||||
threadContext->state = newState;
|
threadContext->state = newState;
|
||||||
if (broadcast) {
|
if (broadcast) {
|
||||||
|
@ -92,7 +92,7 @@ static void _waitOnInterrupt(struct GBAThread* threadContext) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _waitUntilNotState(struct GBAThread* threadContext, enum ThreadState oldState) {
|
static void _waitUntilNotState(struct GBAThread* threadContext, enum mCoreThreadState oldState) {
|
||||||
MutexLock(&threadContext->sync.videoFrameMutex);
|
MutexLock(&threadContext->sync.videoFrameMutex);
|
||||||
bool videoFrameWait = threadContext->sync.videoFrameWait;
|
bool videoFrameWait = threadContext->sync.videoFrameWait;
|
||||||
threadContext->sync.videoFrameWait = false;
|
threadContext->sync.videoFrameWait = false;
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
#include "core/directories.h"
|
#include "core/directories.h"
|
||||||
#include "core/sync.h"
|
#include "core/sync.h"
|
||||||
|
#include "core/thread.h"
|
||||||
#include "gba/gba.h"
|
#include "gba/gba.h"
|
||||||
#include "gba/input.h"
|
#include "gba/input.h"
|
||||||
#include "gba/context/overrides.h"
|
#include "gba/context/overrides.h"
|
||||||
|
@ -21,26 +22,12 @@ struct GBAArguments;
|
||||||
struct GBACheatSet;
|
struct GBACheatSet;
|
||||||
struct GBAOptions;
|
struct GBAOptions;
|
||||||
|
|
||||||
typedef void (*ThreadCallback)(struct GBAThread* threadContext);
|
typedef void (*GBAThreadCallback)(struct GBAThread* threadContext);
|
||||||
typedef bool (*ThreadStopCallback)(struct GBAThread* threadContext);
|
typedef bool (*ThreadStopCallback)(struct GBAThread* threadContext);
|
||||||
|
|
||||||
enum ThreadState {
|
|
||||||
THREAD_INITIALIZED = -1,
|
|
||||||
THREAD_RUNNING = 0,
|
|
||||||
THREAD_INTERRUPTED,
|
|
||||||
THREAD_INTERRUPTING,
|
|
||||||
THREAD_PAUSED,
|
|
||||||
THREAD_PAUSING,
|
|
||||||
THREAD_RUN_ON,
|
|
||||||
THREAD_RESETING,
|
|
||||||
THREAD_EXITING,
|
|
||||||
THREAD_SHUTDOWN,
|
|
||||||
THREAD_CRASHED
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GBAThread {
|
struct GBAThread {
|
||||||
// Output
|
// Output
|
||||||
enum ThreadState state;
|
enum mCoreThreadState state;
|
||||||
struct GBA* gba;
|
struct GBA* gba;
|
||||||
struct ARMCore* cpu;
|
struct ARMCore* cpu;
|
||||||
|
|
||||||
|
@ -80,15 +67,15 @@ struct GBAThread {
|
||||||
|
|
||||||
Mutex stateMutex;
|
Mutex stateMutex;
|
||||||
Condition stateCond;
|
Condition stateCond;
|
||||||
enum ThreadState savedState;
|
enum mCoreThreadState savedState;
|
||||||
int interruptDepth;
|
int interruptDepth;
|
||||||
bool frameWasOn;
|
bool frameWasOn;
|
||||||
|
|
||||||
GBALogHandler logHandler;
|
GBALogHandler logHandler;
|
||||||
int logLevel;
|
int logLevel;
|
||||||
ThreadCallback startCallback;
|
GBAThreadCallback startCallback;
|
||||||
ThreadCallback cleanCallback;
|
GBAThreadCallback cleanCallback;
|
||||||
ThreadCallback frameCallback;
|
GBAThreadCallback frameCallback;
|
||||||
ThreadStopCallback stopCallback;
|
ThreadStopCallback stopCallback;
|
||||||
void* userData;
|
void* userData;
|
||||||
void (*run)(struct GBAThread*);
|
void (*run)(struct GBAThread*);
|
||||||
|
|
|
@ -129,18 +129,14 @@ bool mSDLGLInitGB(struct mSDLRenderer* renderer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void mSDLGLRunloopGB(struct mSDLRenderer* renderer, void* user) {
|
void mSDLGLRunloopGB(struct mSDLRenderer* renderer, void* user) {
|
||||||
UNUSED(user);
|
struct mCoreThread* context = user;
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
struct VideoBackend* v = &renderer->gl.d;
|
struct VideoBackend* v = &renderer->gl.d;
|
||||||
renderer->audio.psg = &((struct GB*) renderer->core->board)->audio;
|
renderer->audio.psg = &((struct GB*) renderer->core->board)->audio;
|
||||||
|
|
||||||
while (true) {
|
while (context->state < THREAD_EXITING) {
|
||||||
renderer->core->runFrame(renderer->core);
|
|
||||||
while (SDL_PollEvent(&event)) {
|
while (SDL_PollEvent(&event)) {
|
||||||
mSDLHandleEvent(renderer->core, &renderer->player, &event);
|
mSDLHandleEvent(context, &renderer->player, &event);
|
||||||
if (event.type == SDL_QUIT) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||||
// Event handling can change the size of the screen
|
// Event handling can change the size of the screen
|
||||||
if (renderer->player.windowUpdated) {
|
if (renderer->player.windowUpdated) {
|
||||||
|
@ -151,7 +147,10 @@ void mSDLGLRunloopGB(struct mSDLRenderer* renderer, void* user) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
v->postFrame(v, renderer->outputBuffer);
|
if (mCoreSyncWaitFrameStart(&context->sync)) {
|
||||||
|
v->postFrame(v, renderer->outputBuffer);
|
||||||
|
}
|
||||||
|
mCoreSyncWaitFrameEnd(&context->sync);
|
||||||
v->drawFrame(v);
|
v->drawFrame(v);
|
||||||
v->swap(v);
|
v->swap(v);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/config.h"
|
#include "core/config.h"
|
||||||
|
#include "core/thread.h"
|
||||||
#ifdef M_CORE_GBA
|
#ifdef M_CORE_GBA
|
||||||
#include "gba/gba.h"
|
#include "gba/gba.h"
|
||||||
#include "gba/supervisor/thread.h"
|
#include "gba/supervisor/thread.h"
|
||||||
|
@ -277,6 +278,12 @@ int mSDLRunGBA(struct mSDLRenderer* renderer, struct GBAArguments* args, struct
|
||||||
|
|
||||||
#ifdef M_CORE_GB
|
#ifdef M_CORE_GB
|
||||||
int mSDLRunGB(struct mSDLRenderer* renderer, struct GBAArguments* args) {
|
int mSDLRunGB(struct mSDLRenderer* renderer, struct GBAArguments* args) {
|
||||||
|
struct mCoreThread thread = {
|
||||||
|
.core = renderer->core,
|
||||||
|
.sync = {
|
||||||
|
.audioWait = true
|
||||||
|
}
|
||||||
|
};
|
||||||
struct VFile* vf = VFileOpen(args->fname, O_RDONLY);
|
struct VFile* vf = VFileOpen(args->fname, O_RDONLY);
|
||||||
struct VFile* savVf = 0;
|
struct VFile* savVf = 0;
|
||||||
|
|
||||||
|
@ -284,6 +291,7 @@ int mSDLRunGB(struct mSDLRenderer* renderer, struct GBAArguments* args) {
|
||||||
renderer->audio.sampleRate = 44100;
|
renderer->audio.sampleRate = 44100;
|
||||||
|
|
||||||
GBSDLInitAudio(&renderer->audio, 0);
|
GBSDLInitAudio(&renderer->audio, 0);
|
||||||
|
renderer->audio.sync = &thread.sync;
|
||||||
|
|
||||||
{
|
{
|
||||||
char savepath[PATH_MAX];
|
char savepath[PATH_MAX];
|
||||||
|
@ -295,10 +303,10 @@ int mSDLRunGB(struct mSDLRenderer* renderer, struct GBAArguments* args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer->core->loadROM(renderer->core, vf, savVf, args->fname);
|
renderer->core->loadROM(renderer->core, vf, savVf, args->fname);
|
||||||
renderer->core->reset(renderer->core);
|
mCoreThreadStart(&thread);
|
||||||
renderer->audio.psg = 0;
|
renderer->audio.psg = 0;
|
||||||
GBSDLResumeAudio(&renderer->audio);
|
GBSDLResumeAudio(&renderer->audio);
|
||||||
renderer->runloop(renderer, NULL);
|
renderer->runloop(renderer, &thread);
|
||||||
renderer->core->unloadROM(renderer->core);
|
renderer->core->unloadROM(renderer->core);
|
||||||
vf->close(vf);
|
vf->close(vf);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -47,6 +47,7 @@ bool GBSDLInitAudio(struct GBSDLAudio* context, struct GBAThread* threadContext)
|
||||||
if (context->samples > threadContext->audioBuffers) {
|
if (context->samples > threadContext->audioBuffers) {
|
||||||
threadContext->audioBuffers = context->samples * 2;
|
threadContext->audioBuffers = context->samples * 2;
|
||||||
}
|
}
|
||||||
|
context->sync = &threadContext->sync;
|
||||||
|
|
||||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||||
SDL_PauseAudioDevice(context->deviceId, 0);
|
SDL_PauseAudioDevice(context->deviceId, 0);
|
||||||
|
@ -116,8 +117,8 @@ static void _GBSDLAudioCallback(void* context, Uint8* data, int len) {
|
||||||
blip_read_samples(psg->right, ((short*) data) + 1, available, 1);
|
blip_read_samples(psg->right, ((short*) data) + 1, available, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audioContext->thread) {
|
if (audioContext->sync) {
|
||||||
mCoreSyncConsumeAudio(&audioContext->thread->sync);
|
mCoreSyncConsumeAudio(audioContext->sync);
|
||||||
}
|
}
|
||||||
if (available < len) {
|
if (available < len) {
|
||||||
memset(((short*) data) + audioContext->obtainedSpec.channels * available, 0, (len - available) * audioContext->obtainedSpec.channels * sizeof(short));
|
memset(((short*) data) + audioContext->obtainedSpec.channels * available, 0, (len - available) * audioContext->obtainedSpec.channels * sizeof(short));
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "sdl-events.h"
|
#include "sdl-events.h"
|
||||||
|
|
||||||
#include "core/input.h"
|
#include "core/input.h"
|
||||||
|
#include "core/thread.h"
|
||||||
#include "debugger/debugger.h"
|
#include "debugger/debugger.h"
|
||||||
#include "gba/input.h"
|
#include "gba/input.h"
|
||||||
#include "gba/io.h"
|
#include "gba/io.h"
|
||||||
|
@ -542,7 +543,7 @@ static void _mSDLHandleJoyAxisGBA(struct GBAThread* context, struct mSDLPlayer*
|
||||||
context->activeKeys = keys;
|
context->activeKeys = keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _mSDLHandleKeypress(struct mCore* core, struct mSDLPlayer* sdlContext, const struct SDL_KeyboardEvent* event) {
|
static void _mSDLHandleKeypress(struct mCoreThread* context, struct mSDLPlayer* sdlContext, const struct SDL_KeyboardEvent* event) {
|
||||||
int key = -1;
|
int key = -1;
|
||||||
if (!event->keysym.mod) {
|
if (!event->keysym.mod) {
|
||||||
#if !defined(BUILD_PANDORA) && SDL_VERSION_ATLEAST(2, 0, 0)
|
#if !defined(BUILD_PANDORA) && SDL_VERSION_ATLEAST(2, 0, 0)
|
||||||
|
@ -553,12 +554,16 @@ static void _mSDLHandleKeypress(struct mCore* core, struct mSDLPlayer* sdlContex
|
||||||
}
|
}
|
||||||
if (key != -1) {
|
if (key != -1) {
|
||||||
if (event->type == SDL_KEYDOWN) {
|
if (event->type == SDL_KEYDOWN) {
|
||||||
core->addKeys(core, 1 << key);
|
context->core->addKeys(context->core, 1 << key);
|
||||||
} else {
|
} else {
|
||||||
core->clearKeys(core, 1 << key);
|
context->core->clearKeys(context->core, 1 << key);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (event->keysym.sym == SDLK_TAB) {
|
||||||
|
context->sync.audioWait = event->type != SDL_KEYDOWN;
|
||||||
|
return;
|
||||||
|
}
|
||||||
// TODO: Put back events
|
// TODO: Put back events
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -626,10 +631,10 @@ void mSDLHandleEventGBA(struct GBAThread* context, struct mSDLPlayer* sdlContext
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void mSDLHandleEvent(struct mCore* core, struct mSDLPlayer* sdlContext, const union SDL_Event* event) {
|
void mSDLHandleEvent(struct mCoreThread* context, struct mSDLPlayer* sdlContext, const union SDL_Event* event) {
|
||||||
switch (event->type) {
|
switch (event->type) {
|
||||||
case SDL_QUIT:
|
case SDL_QUIT:
|
||||||
// TODO
|
mCoreThreadEnd(context);
|
||||||
break;
|
break;
|
||||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||||
case SDL_WINDOWEVENT:
|
case SDL_WINDOWEVENT:
|
||||||
|
@ -638,17 +643,17 @@ void mSDLHandleEvent(struct mCore* core, struct mSDLPlayer* sdlContext, const un
|
||||||
#endif
|
#endif
|
||||||
case SDL_KEYDOWN:
|
case SDL_KEYDOWN:
|
||||||
case SDL_KEYUP:
|
case SDL_KEYUP:
|
||||||
_mSDLHandleKeypress(core, sdlContext, &event->key);
|
_mSDLHandleKeypress(context, sdlContext, &event->key);
|
||||||
break;
|
break;
|
||||||
case SDL_JOYBUTTONDOWN:
|
case SDL_JOYBUTTONDOWN:
|
||||||
case SDL_JOYBUTTONUP:
|
case SDL_JOYBUTTONUP:
|
||||||
_mSDLHandleJoyButton(core, sdlContext, &event->jbutton);
|
_mSDLHandleJoyButton(context->core, sdlContext, &event->jbutton);
|
||||||
break;
|
break;
|
||||||
case SDL_JOYHATMOTION:
|
case SDL_JOYHATMOTION:
|
||||||
// TODO
|
// TODO
|
||||||
break;
|
break;
|
||||||
case SDL_JOYAXISMOTION:
|
case SDL_JOYAXISMOTION:
|
||||||
_mSDLHandleJoyAxis(core, sdlContext, &event->jaxis);
|
_mSDLHandleJoyAxis(context->core, sdlContext, &event->jaxis);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,8 +97,8 @@ struct GBAThread;
|
||||||
void mSDLInitBindingsGBA(struct mInputMap* inputMap);
|
void mSDLInitBindingsGBA(struct mInputMap* inputMap);
|
||||||
void mSDLHandleEventGBA(struct GBAThread* context, struct mSDLPlayer* sdlContext, const union SDL_Event* event);
|
void mSDLHandleEventGBA(struct GBAThread* context, struct mSDLPlayer* sdlContext, const union SDL_Event* event);
|
||||||
|
|
||||||
struct mCore;
|
struct mCoreThread;
|
||||||
void mSDLHandleEvent(struct mCore* core, struct mSDLPlayer* sdlContext, const union SDL_Event* event);
|
void mSDLHandleEvent(struct mCoreThread* context, struct mSDLPlayer* sdlContext, const union SDL_Event* event);
|
||||||
|
|
||||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||||
void mSDLSuspendScreensaver(struct mSDLEvents*);
|
void mSDLSuspendScreensaver(struct mSDLEvents*);
|
||||||
|
|
Loading…
Reference in New Issue