all repos — mgba @ 59f78a05e4e0da2be407e41e6845434dace902df

mGBA Game Boy Advance Emulator

src/gba/gui/gui-runner.c (view raw)

  1/* Copyright (c) 2013-2015 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 "gui-runner.h"
  7
  8#include "gba/gui/gui-config.h"
  9#include "gba/interface.h"
 10#include "gba/serialize.h"
 11#include "util/gui/file-select.h"
 12#include "util/gui/font.h"
 13#include "util/gui/menu.h"
 14#include "util/memory.h"
 15#include "util/png-io.h"
 16#include "util/vfs.h"
 17
 18#ifdef _3DS
 19#include <3ds.h>
 20#endif
 21
 22#include <sys/time.h>
 23
 24#define FPS_GRANULARITY 120
 25#define FPS_BUFFER_SIZE 3
 26
 27enum {
 28	RUNNER_CONTINUE = 1,
 29	RUNNER_EXIT,
 30	RUNNER_SAVE_STATE,
 31	RUNNER_LOAD_STATE,
 32	RUNNER_SCREENSHOT,
 33	RUNNER_CONFIG,
 34	RUNNER_RESET,
 35	RUNNER_COMMAND_MASK = 0xFFFF,
 36
 37	RUNNER_STATE_1 = 0x10000,
 38	RUNNER_STATE_2 = 0x20000,
 39	RUNNER_STATE_3 = 0x30000,
 40	RUNNER_STATE_4 = 0x40000,
 41	RUNNER_STATE_5 = 0x50000,
 42	RUNNER_STATE_6 = 0x60000,
 43	RUNNER_STATE_7 = 0x70000,
 44	RUNNER_STATE_8 = 0x80000,
 45	RUNNER_STATE_9 = 0x90000,
 46};
 47
 48static void _drawBackground(struct GUIBackground* background, void* context) {
 49	UNUSED(context);
 50	struct GBAGUIBackground* gbaBackground = (struct GBAGUIBackground*) background;
 51	if (gbaBackground->p->drawFrame) {
 52		gbaBackground->p->drawFrame(gbaBackground->p, true);
 53	}
 54}
 55
 56static void _drawState(struct GUIBackground* background, void* id) {
 57	struct GBAGUIBackground* gbaBackground = (struct GBAGUIBackground*) background;
 58	int stateId = ((int) id) >> 16;
 59	if (gbaBackground->p->drawScreenshot) {
 60		if (gbaBackground->screenshot && gbaBackground->screenshotId == (int) id) {
 61			gbaBackground->p->drawScreenshot(gbaBackground->p, gbaBackground->screenshot, true);
 62			return;
 63		}
 64		struct VFile* vf = GBAGetState(gbaBackground->p->context.gba, gbaBackground->p->context.dirs.state, stateId, false);
 65		uint32_t* pixels = gbaBackground->screenshot;
 66		if (!pixels) {
 67			pixels = anonymousMemoryMap(VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4);
 68			gbaBackground->screenshot = pixels;
 69		}
 70		bool success = false;
 71		if (vf && isPNG(vf) && pixels) {
 72			png_structp png = PNGReadOpen(vf, PNG_HEADER_BYTES);
 73			png_infop info = png_create_info_struct(png);
 74			png_infop end = png_create_info_struct(png);
 75			if (png && info && end) {
 76				success = PNGReadHeader(png, info);
 77				success = success && PNGReadPixels(png, info, pixels, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, VIDEO_HORIZONTAL_PIXELS);
 78				success = success && PNGReadFooter(png, end);
 79			}
 80			PNGReadClose(png, info, end);
 81		}
 82		if (vf) {
 83			vf->close(vf);
 84		}
 85		if (success) {
 86			gbaBackground->p->drawScreenshot(gbaBackground->p, pixels, true);
 87			gbaBackground->screenshotId = (int) id;
 88		} else if (gbaBackground->p->drawFrame) {
 89			gbaBackground->p->drawFrame(gbaBackground->p, true);
 90		}
 91	}
 92}
 93
 94static void _updateLux(struct GBALuminanceSource* lux) {
 95	UNUSED(lux);
 96}
 97
 98static uint8_t _readLux(struct GBALuminanceSource* lux) {
 99	struct GBAGUIRunnerLux* runnerLux = (struct GBAGUIRunnerLux*) lux;
100	int value = 0x16;
101	if (runnerLux->luxLevel > 0) {
102		value += GBA_LUX_LEVELS[runnerLux->luxLevel - 1];
103	}
104	return 0xFF - value;
105}
106
107void GBAGUIInit(struct GBAGUIRunner* runner, const char* port) {
108	GUIInit(&runner->params);
109	GBAContextInit(&runner->context, port);
110	runner->luminanceSource.d.readLuminance = _readLux;
111	runner->luminanceSource.d.sample = _updateLux;
112	runner->luminanceSource.luxLevel = 0;
113	runner->context.gba->luminanceSource = &runner->luminanceSource.d;
114	runner->background.d.draw = _drawBackground;
115	runner->background.p = runner;
116	runner->fps = 0;
117	runner->lastFpsCheck = 0;
118	runner->totalDelta = 0;
119	CircleBufferInit(&runner->fpsBuffer, FPS_BUFFER_SIZE * sizeof(uint32_t));
120	if (runner->setup) {
121		runner->setup(runner);
122	}
123
124	if (runner->context.config.port && runner->keySources) {
125		size_t i;
126		for (i = 0; runner->keySources[i].id; ++i) {
127			mInputMapLoad(&runner->context.inputMap, runner->keySources[i].id, mCoreConfigGetInput(&runner->context.config));
128		}
129	}
130}
131
132void GBAGUIDeinit(struct GBAGUIRunner* runner) {
133	if (runner->teardown) {
134		runner->teardown(runner);
135	}
136	if (runner->context.config.port) {
137		if (runner->keySources) {
138			size_t i;
139			for (i = 0; runner->keySources[i].id; ++i) {
140				mInputMapSave(&runner->context.inputMap, runner->keySources[i].id, mCoreConfigGetInput(&runner->context.config));
141			}
142		}
143		mCoreConfigSave(&runner->context.config);
144	}
145	CircleBufferDeinit(&runner->fpsBuffer);
146	GBAContextDeinit(&runner->context);
147}
148
149void GBAGUIRun(struct GBAGUIRunner* runner, const char* path) {
150	struct GBAGUIBackground drawState = {
151		.d = {
152			.draw = _drawState
153		},
154		.p = runner,
155		.screenshot = 0,
156		.screenshotId = 0
157	};
158	struct GUIMenu pauseMenu = {
159		.title = "Game Paused",
160		.index = 0,
161		.background = &runner->background.d
162	};
163	struct GUIMenu stateSaveMenu = {
164		.title = "Save state",
165		.index = 0,
166		.background = &drawState.d
167	};
168	struct GUIMenu stateLoadMenu = {
169		.title = "Load state",
170		.index = 0,
171		.background = &drawState.d
172	};
173	GUIMenuItemListInit(&pauseMenu.items, 0);
174	GUIMenuItemListInit(&stateSaveMenu.items, 9);
175	GUIMenuItemListInit(&stateLoadMenu.items, 9);
176	*GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Unpause", .data = (void*) RUNNER_CONTINUE };
177	*GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Save state", .submenu = &stateSaveMenu };
178	*GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Load state", .submenu = &stateLoadMenu };
179
180	*GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 1", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_1) };
181	*GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 2", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_2) };
182	*GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 3", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_3) };
183	*GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 4", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_4) };
184	*GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 5", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_5) };
185	*GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 6", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_6) };
186	*GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 7", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_7) };
187	*GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 8", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_8) };
188	*GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 9", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_9) };
189
190	*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 1", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_1) };
191	*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 2", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_2) };
192	*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 3", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_3) };
193	*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 4", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_4) };
194	*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 5", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_5) };
195	*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 6", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_6) };
196	*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 7", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_7) };
197	*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 8", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_8) };
198	*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 9", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_9) };
199
200	*GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Take screenshot", .data = (void*) RUNNER_SCREENSHOT };
201	*GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Configure", .data = (void*) RUNNER_CONFIG };
202	*GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Reset game", .data = (void*) RUNNER_RESET };
203	*GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Exit game", .data = (void*) RUNNER_EXIT };
204
205	// TODO: Message box API
206	runner->params.drawStart();
207	if (runner->params.guiPrepare) {
208		runner->params.guiPrepare();
209	}
210	GUIFontPrint(runner->params.font, runner->params.width / 2, (GUIFontHeight(runner->params.font) + runner->params.height) / 2, GUI_ALIGN_HCENTER, 0xFFFFFFFF, "Loading...");
211	if (runner->params.guiFinish) {
212		runner->params.guiFinish();
213	}
214	runner->params.drawEnd();
215
216	if (!GBAContextLoadROM(&runner->context, path, true)) {
217		int i;
218		for (i = 0; i < 300; ++i) {
219			runner->params.drawStart();
220			if (runner->params.guiPrepare) {
221				runner->params.guiPrepare();
222			}
223			GUIFontPrint(runner->params.font, runner->params.width / 2, (GUIFontHeight(runner->params.font) + runner->params.height) / 2, GUI_ALIGN_HCENTER, 0xFFFFFFFF, "Load failed!");
224			if (runner->params.guiFinish) {
225				runner->params.guiFinish();
226			}
227			runner->params.drawEnd();
228		}
229		return;
230	}
231	bool running = GBAContextStart(&runner->context);
232	if (runner->gameLoaded) {
233		runner->gameLoaded(runner);
234	}
235	while (running) {
236		CircleBufferClear(&runner->fpsBuffer);
237		runner->totalDelta = 0;
238		runner->fps = 0;
239		struct timeval tv;
240		gettimeofday(&tv, 0);
241		runner->lastFpsCheck = 1000000LL * tv.tv_sec + tv.tv_usec;
242
243		while (true) {
244#ifdef _3DS
245			running = aptMainLoop();
246			if (!running) {
247				break;
248			}
249#endif
250			uint32_t guiKeys;
251			GUIPollInput(&runner->params, &guiKeys, 0);
252			if (guiKeys & (1 << GUI_INPUT_CANCEL)) {
253				break;
254			}
255			if (guiKeys & (1 << GBA_GUI_INPUT_INCREASE_BRIGHTNESS)) {
256				if (runner->luminanceSource.luxLevel < 10) {
257					++runner->luminanceSource.luxLevel;
258				}
259			}
260			if (guiKeys & (1 << GBA_GUI_INPUT_DECREASE_BRIGHTNESS)) {
261				if (runner->luminanceSource.luxLevel > 0) {
262					--runner->luminanceSource.luxLevel;
263				}
264			}
265			if (guiKeys & (1 << GBA_GUI_INPUT_SCREEN_MODE) && runner->incrementScreenMode) {
266				runner->incrementScreenMode(runner);
267			}
268			uint16_t keys = runner->pollGameInput(runner);
269			if (runner->prepareForFrame) {
270				runner->prepareForFrame(runner);
271			}
272			GBAContextFrame(&runner->context, keys);
273			if (runner->drawFrame) {
274				int drawFps = false;
275				mCoreConfigGetIntValue(&runner->context.config, "fpsCounter", &drawFps);
276
277				runner->params.drawStart();
278				runner->drawFrame(runner, false);
279				if (drawFps) {
280					if (runner->params.guiPrepare) {
281						runner->params.guiPrepare();
282					}
283					GUIFontPrintf(runner->params.font, 0, GUIFontHeight(runner->params.font), GUI_ALIGN_LEFT, 0x7FFFFFFF, "%.2f fps", runner->fps);
284					if (runner->params.guiFinish) {
285						runner->params.guiFinish();
286					}
287				}
288				runner->params.drawEnd();
289
290				if (runner->context.gba->video.frameCounter % FPS_GRANULARITY == 0) {
291					if (drawFps) {
292						struct timeval tv;
293						gettimeofday(&tv, 0);
294						uint64_t t = 1000000LL * tv.tv_sec + tv.tv_usec;
295						uint64_t delta = t - runner->lastFpsCheck;
296						runner->lastFpsCheck = t;
297						if (delta > 0x7FFFFFFFLL) {
298							CircleBufferClear(&runner->fpsBuffer);
299							runner->fps = 0;
300						}
301						if (CircleBufferSize(&runner->fpsBuffer) == CircleBufferCapacity(&runner->fpsBuffer)) {
302							int32_t last;
303							CircleBufferRead32(&runner->fpsBuffer, &last);
304							runner->totalDelta -= last;
305						}
306						CircleBufferWrite32(&runner->fpsBuffer, delta);
307						runner->totalDelta += delta;
308						runner->fps = (CircleBufferSize(&runner->fpsBuffer) * FPS_GRANULARITY * 1000000.0f) / (runner->totalDelta * sizeof(uint32_t));
309					}
310				}
311			}
312		}
313
314		if (runner->paused) {
315			runner->paused(runner);
316		}
317		GUIInvalidateKeys(&runner->params);
318		uint32_t keys = 0xFFFFFFFF; // Huge hack to avoid an extra variable!
319		struct GUIMenuItem* item;
320		enum GUIMenuExitReason reason = GUIShowMenu(&runner->params, &pauseMenu, &item);
321		if (reason == GUI_MENU_EXIT_ACCEPT) {
322			struct VFile* vf;
323			switch (((int) item->data) & RUNNER_COMMAND_MASK) {
324			case RUNNER_EXIT:
325				running = false;
326				keys = 0;
327				break;
328			case RUNNER_RESET:
329				GBAContextReset(&runner->context);
330				break;
331			case RUNNER_SAVE_STATE:
332				vf = GBAGetState(runner->context.gba, runner->context.dirs.state, ((int) item->data) >> 16, true);
333				if (vf) {
334					GBASaveStateNamed(runner->context.gba, vf, SAVESTATE_SCREENSHOT);
335					vf->close(vf);
336				}
337				break;
338			case RUNNER_LOAD_STATE:
339				vf = GBAGetState(runner->context.gba, runner->context.dirs.state, ((int) item->data) >> 16, false);
340				if (vf) {
341					GBALoadStateNamed(runner->context.gba, vf, SAVESTATE_SCREENSHOT);
342					vf->close(vf);
343				}
344				break;
345			case RUNNER_SCREENSHOT:
346				GBATakeScreenshot(runner->context.gba, runner->context.dirs.screenshot);
347				break;
348			case RUNNER_CONFIG:
349				GBAGUIShowConfig(runner, runner->configExtra, runner->nConfigExtra);
350				mCoreConfigGetIntValue(&runner->context.config, "frameskip", &runner->context.gba->video.frameskip);
351				break;
352			case RUNNER_CONTINUE:
353				break;
354			}
355		}
356		int frames = 0;
357		GUIPollInput(&runner->params, 0, &keys);
358		while (keys && frames < 30) {
359			++frames;
360			runner->params.drawStart();
361			runner->drawFrame(runner, true);
362			runner->params.drawEnd();
363			GUIPollInput(&runner->params, 0, &keys);
364		}
365		if (runner->unpaused) {
366			runner->unpaused(runner);
367		}
368	}
369	GBAContextStop(&runner->context);
370	if (runner->gameUnloaded) {
371		runner->gameUnloaded(runner);
372	}
373	GBAContextUnloadROM(&runner->context);
374	drawState.screenshotId = 0;
375	if (drawState.screenshot) {
376		mappedMemoryFree(drawState.screenshot, VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4);
377	}
378	GUIMenuItemListDeinit(&pauseMenu.items);
379	GUIMenuItemListDeinit(&stateSaveMenu.items);
380	GUIMenuItemListDeinit(&stateLoadMenu.items);
381}
382
383void GBAGUIRunloop(struct GBAGUIRunner* runner) {
384	while (true) {
385		char path[PATH_MAX];
386		if (!GUISelectFile(&runner->params, path, sizeof(path), 0)) {
387			break;
388		}
389		GBAGUIRun(runner, path);
390	}
391}