all repos — mgba @ da5d109d694386df5b7c1aa247b156b7cd3f438e

mGBA Game Boy Advance Emulator

src/platform/qt/GameController.cpp (view raw)

  1/* Copyright (c) 2013-2014 Jeffrey Pfau
  2 *
  3 * This Source Code Form is subject to the terms of the Mozilla Public
  4 * License, v. 2.0. If a copy of the MPL was not distributed with this
  5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  6#include "GameController.h"
  7
  8#include "AudioProcessor.h"
  9#include "InputController.h"
 10
 11#include <QDateTime>
 12#include <QThread>
 13
 14extern "C" {
 15#include "gba.h"
 16#include "gba-audio.h"
 17#include "gba-serialize.h"
 18#include "renderers/video-software.h"
 19#include "util/vfs.h"
 20}
 21
 22using namespace QGBA;
 23
 24GameController::GameController(QObject* parent)
 25	: QObject(parent)
 26	, m_drawContext(new uint32_t[256 * 256])
 27	, m_threadContext()
 28	, m_activeKeys(0)
 29	, m_logLevels(0)
 30	, m_gameOpen(false)
 31	, m_audioThread(new QThread(this))
 32	, m_audioProcessor(AudioProcessor::create())
 33	, m_videoSync(VIDEO_SYNC)
 34	, m_audioSync(AUDIO_SYNC)
 35	, m_turbo(false)
 36	, m_turboForced(false)
 37	, m_inputController(nullptr)
 38{
 39	m_renderer = new GBAVideoSoftwareRenderer;
 40	GBAVideoSoftwareRendererCreate(m_renderer);
 41	m_renderer->outputBuffer = (color_t*) m_drawContext;
 42	m_renderer->outputBufferStride = 256;
 43	m_threadContext.state = THREAD_INITIALIZED;
 44	m_threadContext.debugger = 0;
 45	m_threadContext.frameskip = 0;
 46	m_threadContext.bios = 0;
 47	m_threadContext.renderer = &m_renderer->d;
 48	m_threadContext.userData = this;
 49	m_threadContext.rewindBufferCapacity = 0;
 50	m_threadContext.logLevel = -1;
 51
 52	m_lux.p = this;
 53	m_lux.sample = [] (GBALuminanceSource* context) {
 54		GameControllerLux* lux = static_cast<GameControllerLux*>(context);
 55		lux->value = 0xFF - lux->p->m_luxValue;
 56	};
 57
 58	m_lux.readLuminance = [] (GBALuminanceSource* context) {
 59		GameControllerLux* lux = static_cast<GameControllerLux*>(context);
 60		return lux->value;
 61	};
 62
 63	m_rtc.p = this;
 64	m_rtc.override = GameControllerRTC::NO_OVERRIDE;
 65	m_rtc.sample = [] (GBARTCSource* context) { };
 66	m_rtc.unixTime = [] (GBARTCSource* context) -> time_t {
 67		GameControllerRTC* rtc = static_cast<GameControllerRTC*>(context);
 68		switch (rtc->override) {
 69		case GameControllerRTC::NO_OVERRIDE:
 70		default:
 71			return time(nullptr);
 72		case GameControllerRTC::FIXED:
 73			return rtc->value;
 74		case GameControllerRTC::FAKE_EPOCH:
 75			return rtc->value + rtc->p->m_threadContext.gba->video.frameCounter * VIDEO_TOTAL_LENGTH / GBA_ARM7TDMI_FREQUENCY;
 76		}
 77	};
 78
 79	m_threadContext.startCallback = [] (GBAThread* context) {
 80		GameController* controller = static_cast<GameController*>(context->userData);
 81		controller->m_audioProcessor->setInput(context);
 82		context->gba->luminanceSource = &controller->m_lux;
 83		context->gba->rtcSource = &controller->m_rtc;
 84		controller->gameStarted(context);
 85	};
 86
 87	m_threadContext.cleanCallback = [] (GBAThread* context) {
 88		GameController* controller = static_cast<GameController*>(context->userData);
 89		controller->gameStopped(context);
 90	};
 91
 92	m_threadContext.frameCallback = [] (GBAThread* context) {
 93		GameController* controller = static_cast<GameController*>(context->userData);
 94		controller->m_pauseMutex.lock();
 95		if (controller->m_pauseAfterFrame) {
 96			GBAThreadPauseFromThread(context);
 97			controller->m_pauseAfterFrame = false;
 98			controller->gamePaused(&controller->m_threadContext);
 99		}
100		controller->m_pauseMutex.unlock();
101		controller->frameAvailable(controller->m_drawContext);
102	};
103
104	m_threadContext.logHandler = [] (GBAThread* context, enum GBALogLevel level, const char* format, va_list args) {
105		GameController* controller = static_cast<GameController*>(context->userData);
106		if (level == GBA_LOG_FATAL) {
107			MutexLock(&controller->m_threadContext.stateMutex);
108			controller->m_threadContext.state = THREAD_EXITING;
109			MutexUnlock(&controller->m_threadContext.stateMutex);
110			QMetaObject::invokeMethod(controller, "crashGame", Q_ARG(const QString&, QString().vsprintf(format, args)));
111		} else if (!(controller->m_logLevels & level)) {
112			return;
113		}
114		controller->postLog(level, QString().vsprintf(format, args));
115	};
116
117	m_audioThread->start(QThread::TimeCriticalPriority);
118	m_audioProcessor->moveToThread(m_audioThread);
119	connect(this, SIGNAL(gameStarted(GBAThread*)), m_audioProcessor, SLOT(start()));
120	connect(this, SIGNAL(gameStopped(GBAThread*)), m_audioProcessor, SLOT(pause()));
121	connect(this, SIGNAL(gamePaused(GBAThread*)), m_audioProcessor, SLOT(pause()));
122	connect(this, SIGNAL(gameUnpaused(GBAThread*)), m_audioProcessor, SLOT(start()));
123
124#ifdef BUILD_SDL
125	connect(this, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(testSDLEvents()));
126#endif
127}
128
129GameController::~GameController() {
130	m_audioThread->quit();
131	m_audioThread->wait();
132	disconnect();
133	closeGame();
134	delete m_renderer;
135	delete[] m_drawContext;
136}
137
138#ifdef USE_GDB_STUB
139ARMDebugger* GameController::debugger() {
140	return m_threadContext.debugger;
141}
142
143void GameController::setDebugger(ARMDebugger* debugger) {
144	bool wasPaused = isPaused();
145	setPaused(true);
146	if (m_threadContext.debugger && GBAThreadHasStarted(&m_threadContext)) {
147		GBADetachDebugger(m_threadContext.gba);
148	}
149	m_threadContext.debugger = debugger;
150	if (m_threadContext.debugger && GBAThreadHasStarted(&m_threadContext)) {
151		GBAAttachDebugger(m_threadContext.gba, m_threadContext.debugger);
152	}
153	setPaused(wasPaused);
154}
155#endif
156
157void GameController::loadGame(const QString& path, bool dirmode) {
158	closeGame();
159	if (!dirmode) {
160		QFile file(path);
161		if (!file.open(QIODevice::ReadOnly)) {
162			return;
163		}
164		file.close();
165	}
166
167	m_fname = path;
168	m_dirmode = dirmode;
169	openGame();
170}
171
172void GameController::openGame() {
173	m_gameOpen = true;
174
175	m_pauseAfterFrame = false;
176
177	if (m_turbo) {
178		m_threadContext.sync.videoFrameWait = false;
179		m_threadContext.sync.audioWait = false;
180	} else {
181		m_threadContext.sync.videoFrameWait = m_videoSync;
182		m_threadContext.sync.audioWait = m_audioSync;
183	}
184
185	m_threadContext.fname = strdup(m_fname.toLocal8Bit().constData());
186	if (m_dirmode) {
187		m_threadContext.gameDir = VDirOpen(m_threadContext.fname);
188		m_threadContext.stateDir = m_threadContext.gameDir;
189	} else {
190		m_threadContext.rom = VFileOpen(m_threadContext.fname, O_RDONLY);
191#if ENABLE_LIBZIP
192		m_threadContext.gameDir = VDirOpenZip(m_threadContext.fname, 0);
193#endif
194	}
195
196	if (!m_bios.isNull()) {
197		m_threadContext.bios = VFileOpen(m_bios.toLocal8Bit().constData(), O_RDONLY);
198	}
199
200	if (!m_patch.isNull()) {
201		m_threadContext.patch = VFileOpen(m_patch.toLocal8Bit().constData(), O_RDONLY);
202	}
203
204	if (!GBAThreadStart(&m_threadContext)) {
205		m_gameOpen = false;
206	}
207}
208
209void GameController::loadBIOS(const QString& path) {
210	if (m_bios == path) {
211		return;
212	}
213	m_bios = path;
214	if (m_gameOpen) {
215		closeGame();
216		openGame();
217	}
218}
219
220void GameController::loadPatch(const QString& path) {
221	m_patch = path;
222	if (m_gameOpen) {
223		closeGame();
224		openGame();
225	}
226}
227
228void GameController::closeGame() {
229	if (!m_gameOpen) {
230		return;
231	}
232	if (GBAThreadIsPaused(&m_threadContext)) {
233		GBAThreadUnpause(&m_threadContext);
234	}
235	GBAThreadEnd(&m_threadContext);
236	GBAThreadJoin(&m_threadContext);
237	if (m_threadContext.fname) {
238		free(const_cast<char*>(m_threadContext.fname));
239		m_threadContext.fname = nullptr;
240	}
241
242	m_patch = QString();
243
244	m_gameOpen = false;
245	emit gameStopped(&m_threadContext);
246}
247
248void GameController::crashGame(const QString& crashMessage) {
249	closeGame();
250	emit gameCrashed(crashMessage);
251}
252
253bool GameController::isPaused() {
254	if (!m_gameOpen) {
255		return false;
256	}
257	return GBAThreadIsPaused(&m_threadContext);
258}
259
260void GameController::setPaused(bool paused) {
261	if (paused == GBAThreadIsPaused(&m_threadContext)) {
262		return;
263	}
264	if (paused) {
265		GBAThreadPause(&m_threadContext);
266		emit gamePaused(&m_threadContext);
267	} else {
268		GBAThreadUnpause(&m_threadContext);
269		emit gameUnpaused(&m_threadContext);
270	}
271}
272
273void GameController::reset() {
274	GBAThreadReset(&m_threadContext);
275}
276
277void GameController::threadInterrupt() {
278	if (m_gameOpen) {
279		GBAThreadInterrupt(&m_threadContext);
280	}
281}
282
283void GameController::threadContinue() {
284	if (m_gameOpen) {
285		GBAThreadContinue(&m_threadContext);
286	}
287}
288
289void GameController::frameAdvance() {
290	m_pauseMutex.lock();
291	m_pauseAfterFrame = true;
292	setPaused(false);
293	m_pauseMutex.unlock();
294}
295
296void GameController::keyPressed(int key) {
297	int mappedKey = 1 << key;
298	m_activeKeys |= mappedKey;
299	updateKeys();
300}
301
302void GameController::keyReleased(int key) {
303	int mappedKey = 1 << key;
304	m_activeKeys &= ~mappedKey;
305	updateKeys();
306}
307
308void GameController::setAudioBufferSamples(int samples) {
309	threadInterrupt();
310	redoSamples(samples);
311	threadContinue();
312	QMetaObject::invokeMethod(m_audioProcessor, "setBufferSamples", Q_ARG(int, samples));
313}
314
315void GameController::setFPSTarget(float fps) {
316	threadInterrupt();
317	m_threadContext.fpsTarget = fps;
318	redoSamples(m_audioProcessor->getBufferSamples());
319	threadContinue();
320	QMetaObject::invokeMethod(m_audioProcessor, "inputParametersChanged");
321}
322
323void GameController::setSkipBIOS(bool set) {
324	threadInterrupt();
325	m_threadContext.skipBios = set;
326	threadContinue();
327}
328
329void GameController::loadState(int slot) {
330	threadInterrupt();
331	GBALoadState(m_threadContext.gba, m_threadContext.stateDir, slot);
332	threadContinue();
333	emit stateLoaded(&m_threadContext);
334	emit frameAvailable(m_drawContext);
335}
336
337void GameController::saveState(int slot) {
338	threadInterrupt();
339	GBASaveState(m_threadContext.gba, m_threadContext.stateDir, slot, true);
340	threadContinue();
341}
342
343void GameController::setVideoSync(bool set) {
344	m_videoSync = set;
345	if (!m_turbo) {
346		threadInterrupt();
347		m_threadContext.sync.videoFrameWait = set;
348		threadContinue();
349	}
350}
351
352void GameController::setAudioSync(bool set) {
353	m_audioSync = set;
354	if (!m_turbo) {
355		threadInterrupt();
356		m_threadContext.sync.audioWait = set;
357		threadContinue();
358	}
359}
360
361void GameController::setFrameskip(int skip) {
362	m_threadContext.frameskip = skip;
363}
364
365void GameController::setTurbo(bool set, bool forced) {
366	if (m_turboForced && !forced) {
367		return;
368	}
369	m_turbo = set;
370	if (set) {
371		m_turboForced = forced;
372	} else {
373		m_turboForced = false;
374	}
375	threadInterrupt();
376	m_threadContext.sync.audioWait = set ? false : m_audioSync;
377	m_threadContext.sync.videoFrameWait = set ? false : m_videoSync;
378	threadContinue();
379}
380
381void GameController::setAVStream(GBAAVStream* stream) {
382	threadInterrupt();
383	m_threadContext.stream = stream;
384	threadContinue();
385}
386
387void GameController::clearAVStream() {
388	threadInterrupt();
389	m_threadContext.stream = nullptr;
390	threadContinue();
391}
392
393void GameController::setRealTime() {
394	m_rtc.override = GameControllerRTC::NO_OVERRIDE;
395}
396
397void GameController::setFixedTime(const QDateTime& time) {
398	m_rtc.override = GameControllerRTC::FIXED;
399	m_rtc.value = time.toMSecsSinceEpoch() / 1000;
400}
401
402void GameController::setFakeEpoch(const QDateTime& time) {
403	m_rtc.override = GameControllerRTC::FAKE_EPOCH;
404	m_rtc.value = time.toMSecsSinceEpoch() / 1000;
405}
406
407void GameController::updateKeys() {
408	int activeKeys = m_activeKeys;
409#ifdef BUILD_SDL
410	activeKeys |= m_activeButtons;
411#endif
412	m_threadContext.activeKeys = activeKeys;
413}
414
415void GameController::redoSamples(int samples) {
416#if RESAMPLE_LIBRARY != RESAMPLE_BLIP_BUF
417	float sampleRate = 0x8000;
418	float ratio;
419	if (m_threadContext.gba) {
420		sampleRate = m_threadContext.gba->audio.sampleRate;
421	}
422	ratio = GBAAudioCalculateRatio(sampleRate, m_threadContext.fpsTarget, 44100);
423	m_threadContext.audioBuffers = ceil(samples / ratio);
424#else
425	m_threadContext.audioBuffers = samples;
426#endif
427	if (m_threadContext.gba) {
428		GBAAudioResizeBuffer(&m_threadContext.gba->audio, m_threadContext.audioBuffers);
429	}
430}
431
432void GameController::setLogLevel(int levels) {
433	threadInterrupt();
434	m_logLevels = levels;
435	threadContinue();
436}
437
438void GameController::enableLogLevel(int levels) {
439	threadInterrupt();
440	m_logLevels |= levels;
441	threadContinue();
442}
443
444void GameController::disableLogLevel(int levels) {
445	threadInterrupt();
446	m_logLevels &= ~levels;
447	threadContinue();
448}
449
450#ifdef BUILD_SDL
451void GameController::testSDLEvents() {
452	if (!m_inputController) {
453		return;
454	}
455
456	m_activeButtons = m_inputController->testSDLEvents();
457	updateKeys();
458}
459#endif