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