src/gba/gba-thread.c (view raw)
1#include "gba-thread.h"
2
3#include "arm.h"
4#include "gba.h"
5#include "gba-serialize.h"
6
7#include "debugger/debugger.h"
8
9#include "util/patch.h"
10#include "util/png-io.h"
11#include "util/vfs.h"
12
13#include <signal.h>
14
15static const float _defaultFPSTarget = 60.f;
16
17#ifdef USE_PTHREADS
18static pthread_key_t _contextKey;
19static pthread_once_t _contextOnce = PTHREAD_ONCE_INIT;
20
21static void _createTLS(void) {
22 pthread_key_create(&_contextKey, 0);
23}
24#else
25static DWORD _contextKey;
26static INIT_ONCE _contextOnce = INIT_ONCE_STATIC_INIT;
27
28static BOOL CALLBACK _createTLS(PINIT_ONCE once, PVOID param, PVOID* context) {
29 UNUSED(once);
30 UNUSED(param);
31 UNUSED(context);
32 _contextKey = TlsAlloc();
33 return TRUE;
34}
35#endif
36
37static void _changeState(struct GBAThread* threadContext, enum ThreadState newState, bool broadcast) {
38 MutexLock(&threadContext->stateMutex);
39 threadContext->state = newState;
40 if (broadcast) {
41 ConditionWake(&threadContext->stateCond);
42 }
43 MutexUnlock(&threadContext->stateMutex);
44}
45
46static void _waitOnInterrupt(struct GBAThread* threadContext) {
47 while (threadContext->state == THREAD_INTERRUPTED) {
48 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
49 }
50}
51
52static void _waitUntilNotState(struct GBAThread* threadContext, enum ThreadState oldState) {
53 while (threadContext->state == oldState) {
54 MutexUnlock(&threadContext->stateMutex);
55
56 MutexLock(&threadContext->sync.videoFrameMutex);
57 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
58 MutexUnlock(&threadContext->sync.videoFrameMutex);
59
60 MutexLock(&threadContext->sync.audioBufferMutex);
61 ConditionWake(&threadContext->sync.audioRequiredCond);
62 MutexUnlock(&threadContext->sync.audioBufferMutex);
63
64 MutexLock(&threadContext->stateMutex);
65 ConditionWake(&threadContext->stateCond);
66 }
67}
68
69static void _pauseThread(struct GBAThread* threadContext, bool onThread) {
70 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
71 threadContext->debugger->state = DEBUGGER_EXITING;
72 }
73 threadContext->state = THREAD_PAUSING;
74 if (!onThread) {
75 _waitUntilNotState(threadContext, THREAD_PAUSING);
76 }
77}
78
79static void _changeVideoSync(struct GBAThread* threadContext, bool frameOn) {
80 // Make sure the video thread can process events while the GBA thread is paused
81 MutexLock(&threadContext->sync.videoFrameMutex);
82 if (frameOn != threadContext->sync.videoFrameOn) {
83 threadContext->sync.videoFrameOn = frameOn;
84 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
85 }
86 MutexUnlock(&threadContext->sync.videoFrameMutex);
87}
88
89static THREAD_ENTRY _GBAThreadRun(void* context) {
90#ifdef USE_PTHREADS
91 pthread_once(&_contextOnce, _createTLS);
92#else
93 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
94#endif
95
96 struct GBA gba;
97 struct ARMCore cpu;
98 struct Patch patch;
99 struct GBAThread* threadContext = context;
100 struct ARMComponent* components[1] = {};
101 int numComponents = 0;
102
103 if (threadContext->debugger) {
104 components[numComponents] = &threadContext->debugger->d;
105 ++numComponents;
106 }
107
108#if !defined(_WIN32) && defined(USE_PTHREADS)
109 sigset_t signals;
110 sigemptyset(&signals);
111 pthread_sigmask(SIG_SETMASK, &signals, 0);
112#endif
113
114 GBACreate(&gba);
115 ARMSetComponents(&cpu, &gba.d, numComponents, components);
116 ARMInit(&cpu);
117 ARMReset(&cpu);
118 threadContext->gba = &gba;
119 gba.sync = &threadContext->sync;
120 gba.logLevel = threadContext->logLevel;
121#ifdef USE_PTHREADS
122 pthread_setspecific(_contextKey, threadContext);
123#else
124 TlsSetValue(_contextKey, threadContext);
125#endif
126
127 if (threadContext->audioBuffers) {
128 GBAAudioResizeBuffer(&gba.audio, threadContext->audioBuffers);
129 } else {
130 threadContext->audioBuffers = GBA_AUDIO_SAMPLES;
131 }
132
133 if (threadContext->renderer) {
134 GBAVideoAssociateRenderer(&gba.video, threadContext->renderer);
135 }
136
137 if (threadContext->rom) {
138 GBALoadROM(&gba, threadContext->rom, threadContext->save, threadContext->fname);
139 if (threadContext->bios) {
140 GBALoadBIOS(&gba, threadContext->bios);
141 }
142
143 if (threadContext->patch && loadPatch(threadContext->patch, &patch)) {
144 GBAApplyPatch(&gba, &patch);
145 }
146 }
147
148 if (threadContext->debugger) {
149 threadContext->debugger->log = GBADebuggerLogShim;
150 GBAAttachDebugger(&gba, threadContext->debugger);
151 ARMDebuggerEnter(threadContext->debugger, DEBUGGER_ENTER_ATTACHED);
152 }
153
154 GBASIOSetDriverSet(&gba.sio, &threadContext->sioDrivers);
155
156 gba.keySource = &threadContext->activeKeys;
157
158 if (threadContext->startCallback) {
159 threadContext->startCallback(threadContext);
160 }
161
162 _changeState(threadContext, THREAD_RUNNING, true);
163
164 while (threadContext->state < THREAD_EXITING) {
165 if (threadContext->debugger) {
166 struct ARMDebugger* debugger = threadContext->debugger;
167 ARMDebuggerRun(debugger);
168 if (debugger->state == DEBUGGER_SHUTDOWN) {
169 _changeState(threadContext, THREAD_EXITING, false);
170 }
171 } else {
172 while (threadContext->state == THREAD_RUNNING) {
173 ARMRunLoop(&cpu);
174 }
175 }
176
177 int resetScheduled = 0;
178 MutexLock(&threadContext->stateMutex);
179 if (threadContext->state == THREAD_PAUSING) {
180 threadContext->state = THREAD_PAUSED;
181 ConditionWake(&threadContext->stateCond);
182 }
183 if (threadContext->state == THREAD_INTERRUPTING) {
184 threadContext->state = THREAD_INTERRUPTED;
185 ConditionWake(&threadContext->stateCond);
186 }
187 if (threadContext->state == THREAD_RESETING) {
188 threadContext->state = THREAD_RUNNING;
189 resetScheduled = 1;
190 }
191 while (threadContext->state == THREAD_PAUSED) {
192 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
193 }
194 MutexUnlock(&threadContext->stateMutex);
195 if (resetScheduled) {
196 ARMReset(&cpu);
197 }
198 }
199
200 while (threadContext->state != THREAD_SHUTDOWN) {
201 _changeState(threadContext, THREAD_SHUTDOWN, false);
202 }
203
204 if (threadContext->cleanCallback) {
205 threadContext->cleanCallback(threadContext);
206 }
207
208 threadContext->gba = 0;
209 ARMDeinit(&cpu);
210 GBADestroy(&gba);
211
212 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
213 ConditionWake(&threadContext->sync.audioRequiredCond);
214
215 return 0;
216}
217
218void GBAMapOptionsToContext(struct StartupOptions* opts, struct GBAThread* threadContext) {
219 if (opts->dirmode) {
220 threadContext->gameDir = VDirOpen(opts->fname);
221 threadContext->stateDir = threadContext->gameDir;
222 } else {
223 threadContext->rom = VFileOpen(opts->fname, O_RDONLY);
224#if ENABLE_LIBZIP
225 threadContext->gameDir = VDirOpenZip(opts->fname, 0);
226#endif
227 }
228 threadContext->fname = opts->fname;
229 threadContext->bios = VFileOpen(opts->bios, O_RDONLY);
230 threadContext->patch = VFileOpen(opts->patch, O_RDONLY);
231 threadContext->frameskip = opts->frameskip;
232 threadContext->logLevel = opts->logLevel;
233 threadContext->rewindBufferCapacity = opts->rewindBufferCapacity;
234 threadContext->rewindBufferInterval = opts->rewindBufferInterval;
235}
236
237bool GBAThreadStart(struct GBAThread* threadContext) {
238 // TODO: error check
239 threadContext->activeKeys = 0;
240 threadContext->state = THREAD_INITIALIZED;
241 threadContext->sync.videoFrameOn = true;
242 threadContext->sync.videoFrameSkip = 0;
243
244 threadContext->rewindBufferNext = threadContext->rewindBufferInterval;
245 threadContext->rewindBufferSize = 0;
246 if (threadContext->rewindBufferCapacity) {
247 threadContext->rewindBuffer = calloc(threadContext->rewindBufferCapacity, sizeof(void*));
248 } else {
249 threadContext->rewindBuffer = 0;
250 }
251
252 if (!threadContext->fpsTarget) {
253 threadContext->fpsTarget = _defaultFPSTarget;
254 }
255
256 if (threadContext->rom && !GBAIsROM(threadContext->rom)) {
257 threadContext->rom->close(threadContext->rom);
258 threadContext->rom = 0;
259 }
260
261 if (threadContext->gameDir) {
262 threadContext->gameDir->rewind(threadContext->gameDir);
263 struct VDirEntry* dirent = threadContext->gameDir->listNext(threadContext->gameDir);
264 while (dirent) {
265 struct Patch patchTemp;
266 struct VFile* vf = threadContext->gameDir->openFile(threadContext->gameDir, dirent->name(dirent), O_RDONLY);
267 if (!vf) {
268 continue;
269 }
270 if (!threadContext->rom && GBAIsROM(vf)) {
271 threadContext->rom = vf;
272 } else if (!threadContext->patch && loadPatch(vf, &patchTemp)) {
273 threadContext->patch = vf;
274 } else {
275 vf->close(vf);
276 }
277 dirent = threadContext->gameDir->listNext(threadContext->gameDir);
278 }
279
280 }
281
282 if (!threadContext->rom) {
283 return false;
284 }
285
286 threadContext->save = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "sram", ".sav", O_CREAT | O_RDWR);
287
288 MutexInit(&threadContext->stateMutex);
289 ConditionInit(&threadContext->stateCond);
290
291 MutexInit(&threadContext->sync.videoFrameMutex);
292 ConditionInit(&threadContext->sync.videoFrameAvailableCond);
293 ConditionInit(&threadContext->sync.videoFrameRequiredCond);
294 MutexInit(&threadContext->sync.audioBufferMutex);
295 ConditionInit(&threadContext->sync.audioRequiredCond);
296
297#ifndef _WIN32
298 sigset_t signals;
299 sigemptyset(&signals);
300 sigaddset(&signals, SIGINT);
301 sigaddset(&signals, SIGTRAP);
302 pthread_sigmask(SIG_BLOCK, &signals, 0);
303#endif
304
305 MutexLock(&threadContext->stateMutex);
306 ThreadCreate(&threadContext->thread, _GBAThreadRun, threadContext);
307 while (threadContext->state < THREAD_RUNNING) {
308 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
309 }
310 MutexUnlock(&threadContext->stateMutex);
311
312 return true;
313}
314
315bool GBAThreadHasStarted(struct GBAThread* threadContext) {
316 bool hasStarted;
317 MutexLock(&threadContext->stateMutex);
318 hasStarted = threadContext->state > THREAD_INITIALIZED;
319 MutexUnlock(&threadContext->stateMutex);
320 return hasStarted;
321}
322
323void GBAThreadEnd(struct GBAThread* threadContext) {
324 MutexLock(&threadContext->stateMutex);
325 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
326 threadContext->debugger->state = DEBUGGER_EXITING;
327 }
328 threadContext->state = THREAD_EXITING;
329 ConditionWake(&threadContext->stateCond);
330 MutexUnlock(&threadContext->stateMutex);
331 MutexLock(&threadContext->sync.audioBufferMutex);
332 threadContext->sync.audioWait = 0;
333 ConditionWake(&threadContext->sync.audioRequiredCond);
334 MutexUnlock(&threadContext->sync.audioBufferMutex);
335}
336
337void GBAThreadReset(struct GBAThread* threadContext) {
338 MutexLock(&threadContext->stateMutex);
339 _waitOnInterrupt(threadContext);
340 threadContext->state = THREAD_RESETING;
341 ConditionWake(&threadContext->stateCond);
342 MutexUnlock(&threadContext->stateMutex);
343}
344
345void GBAThreadJoin(struct GBAThread* threadContext) {
346 MutexLock(&threadContext->sync.videoFrameMutex);
347 threadContext->sync.videoFrameWait = 0;
348 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
349 MutexUnlock(&threadContext->sync.videoFrameMutex);
350
351 ThreadJoin(threadContext->thread);
352
353 MutexDeinit(&threadContext->stateMutex);
354 ConditionDeinit(&threadContext->stateCond);
355
356 MutexDeinit(&threadContext->sync.videoFrameMutex);
357 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
358 ConditionDeinit(&threadContext->sync.videoFrameAvailableCond);
359 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
360 ConditionDeinit(&threadContext->sync.videoFrameRequiredCond);
361
362 ConditionWake(&threadContext->sync.audioRequiredCond);
363 ConditionDeinit(&threadContext->sync.audioRequiredCond);
364 MutexDeinit(&threadContext->sync.audioBufferMutex);
365
366 int i;
367 for (i = 0; i < threadContext->rewindBufferCapacity; ++i) {
368 if (threadContext->rewindBuffer[i]) {
369 GBADeallocateState(threadContext->rewindBuffer[i]);
370 }
371 }
372 free(threadContext->rewindBuffer);
373
374 GBAInputMapDeinit(&threadContext->inputMap);
375
376 if (threadContext->rom) {
377 threadContext->rom->close(threadContext->rom);
378 threadContext->rom = 0;
379 }
380
381 if (threadContext->save) {
382 threadContext->save->close(threadContext->save);
383 threadContext->save = 0;
384 }
385
386 if (threadContext->bios) {
387 threadContext->bios->close(threadContext->bios);
388 threadContext->bios = 0;
389 }
390
391 if (threadContext->patch) {
392 threadContext->patch->close(threadContext->patch);
393 threadContext->patch = 0;
394 }
395
396 if (threadContext->gameDir) {
397 if (threadContext->stateDir == threadContext->gameDir) {
398 threadContext->stateDir = 0;
399 }
400 threadContext->gameDir->close(threadContext->gameDir);
401 threadContext->gameDir = 0;
402 }
403
404 if (threadContext->stateDir) {
405 threadContext->stateDir->close(threadContext->stateDir);
406 threadContext->stateDir = 0;
407 }
408}
409
410void GBAThreadInterrupt(struct GBAThread* threadContext) {
411 MutexLock(&threadContext->stateMutex);
412 threadContext->savedState = threadContext->state;
413 _waitOnInterrupt(threadContext);
414 threadContext->state = THREAD_INTERRUPTING;
415 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
416 threadContext->debugger->state = DEBUGGER_EXITING;
417 }
418 ConditionWake(&threadContext->stateCond);
419 _waitUntilNotState(threadContext, THREAD_INTERRUPTING);
420 MutexUnlock(&threadContext->stateMutex);
421}
422
423void GBAThreadContinue(struct GBAThread* threadContext) {
424 _changeState(threadContext, threadContext->savedState, 1);
425}
426
427void GBAThreadPause(struct GBAThread* threadContext) {
428 bool frameOn = true;
429 MutexLock(&threadContext->stateMutex);
430 _waitOnInterrupt(threadContext);
431 if (threadContext->state == THREAD_RUNNING) {
432 _pauseThread(threadContext, false);
433 frameOn = false;
434 }
435 MutexUnlock(&threadContext->stateMutex);
436
437 _changeVideoSync(threadContext, frameOn);
438}
439
440void GBAThreadUnpause(struct GBAThread* threadContext) {
441 MutexLock(&threadContext->stateMutex);
442 _waitOnInterrupt(threadContext);
443 if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
444 threadContext->state = THREAD_RUNNING;
445 ConditionWake(&threadContext->stateCond);
446 }
447 MutexUnlock(&threadContext->stateMutex);
448
449 _changeVideoSync(threadContext, true);
450}
451
452bool GBAThreadIsPaused(struct GBAThread* threadContext) {
453 bool isPaused;
454 MutexLock(&threadContext->stateMutex);
455 _waitOnInterrupt(threadContext);
456 isPaused = threadContext->state == THREAD_PAUSED;
457 MutexUnlock(&threadContext->stateMutex);
458 return isPaused;
459}
460
461void GBAThreadTogglePause(struct GBAThread* threadContext) {
462 bool frameOn = true;
463 MutexLock(&threadContext->stateMutex);
464 _waitOnInterrupt(threadContext);
465 if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
466 threadContext->state = THREAD_RUNNING;
467 ConditionWake(&threadContext->stateCond);
468 } else if (threadContext->state == THREAD_RUNNING) {
469 _pauseThread(threadContext, false);
470 frameOn = false;
471 }
472 MutexUnlock(&threadContext->stateMutex);
473
474 _changeVideoSync(threadContext, frameOn);
475}
476
477void GBAThreadPauseFromThread(struct GBAThread* threadContext) {
478 bool frameOn = true;
479 MutexLock(&threadContext->stateMutex);
480 _waitOnInterrupt(threadContext);
481 if (threadContext->state == THREAD_RUNNING) {
482 _pauseThread(threadContext, true);
483 frameOn = false;
484 }
485 MutexUnlock(&threadContext->stateMutex);
486
487 _changeVideoSync(threadContext, frameOn);
488}
489
490#ifdef USE_PTHREADS
491struct GBAThread* GBAThreadGetContext(void) {
492 pthread_once(&_contextOnce, _createTLS);
493 return pthread_getspecific(_contextKey);
494}
495#else
496struct GBAThread* GBAThreadGetContext(void) {
497 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
498 return TlsGetValue(_contextKey);
499}
500#endif
501
502#ifdef USE_PNG
503void GBAThreadTakeScreenshot(struct GBAThread* threadContext) {
504 unsigned stride;
505 void* pixels = 0;
506 struct VFile* vf = VDirOptionalOpenFile(threadContext->stateDir, threadContext->gba->activeFile, "screenshot", ".png", O_CREAT | O_TRUNC | O_WRONLY);
507 threadContext->gba->video.renderer->getPixels(threadContext->gba->video.renderer, &stride, &pixels);
508 png_structp png = PNGWriteOpen(vf);
509 png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
510 PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels);
511 PNGWriteClose(png, info);
512 vf->close(vf);
513}
514#endif
515
516void GBASyncPostFrame(struct GBASync* sync) {
517 if (!sync) {
518 return;
519 }
520
521 MutexLock(&sync->videoFrameMutex);
522 ++sync->videoFramePending;
523 --sync->videoFrameSkip;
524 if (sync->videoFrameSkip < 0) {
525 do {
526 ConditionWake(&sync->videoFrameAvailableCond);
527 if (sync->videoFrameWait) {
528 ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex);
529 }
530 } while (sync->videoFrameWait && sync->videoFramePending);
531 }
532 MutexUnlock(&sync->videoFrameMutex);
533
534 struct GBAThread* thread = GBAThreadGetContext();
535 if (!thread) {
536 return;
537 }
538
539 if (thread->rewindBuffer) {
540 --thread->rewindBufferNext;
541 if (thread->rewindBufferNext <= 0) {
542 thread->rewindBufferNext = thread->rewindBufferInterval;
543 GBARecordFrame(thread);
544 }
545 }
546 if (thread->stream) {
547 thread->stream->postVideoFrame(thread->stream, thread->renderer);
548 }
549 if (thread->frameCallback) {
550 thread->frameCallback(thread);
551 }
552}
553
554bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) {
555 if (!sync) {
556 return true;
557 }
558
559 MutexLock(&sync->videoFrameMutex);
560 ConditionWake(&sync->videoFrameRequiredCond);
561 if (!sync->videoFrameOn && !sync->videoFramePending) {
562 return false;
563 }
564 ConditionWait(&sync->videoFrameAvailableCond, &sync->videoFrameMutex);
565 sync->videoFramePending = 0;
566 sync->videoFrameSkip = frameskip;
567 return true;
568}
569
570void GBASyncWaitFrameEnd(struct GBASync* sync) {
571 if (!sync) {
572 return;
573 }
574
575 MutexUnlock(&sync->videoFrameMutex);
576}
577
578bool GBASyncDrawingFrame(struct GBASync* sync) {
579 return sync->videoFrameSkip <= 0;
580}
581
582void GBASyncProduceAudio(struct GBASync* sync, bool wait) {
583 if (sync->audioWait && wait) {
584 // TODO loop properly in event of spurious wakeups
585 ConditionWait(&sync->audioRequiredCond, &sync->audioBufferMutex);
586 }
587 MutexUnlock(&sync->audioBufferMutex);
588}
589
590void GBASyncLockAudio(struct GBASync* sync) {
591 MutexLock(&sync->audioBufferMutex);
592}
593
594void GBASyncUnlockAudio(struct GBASync* sync) {
595 MutexUnlock(&sync->audioBufferMutex);
596}
597
598void GBASyncConsumeAudio(struct GBASync* sync) {
599 ConditionWake(&sync->audioRequiredCond);
600 MutexUnlock(&sync->audioBufferMutex);
601}