all repos — mgba @ b192330166b2b25f5e71c2b4e1eb5c78a9871e99

mGBA Game Boy Advance Emulator

src/platform/switch/main.c (view raw)

  1/* Copyright (c) 2013-2018 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 "feature/gui/gui-runner.h"
  7#include <mgba/core/blip_buf.h>
  8#include <mgba/core/core.h>
  9#include <mgba/internal/gba/audio.h>
 10#include <mgba/internal/gba/input.h>
 11#include <mgba-util/gui.h>
 12#include <mgba-util/gui/font.h>
 13#include <mgba-util/gui/menu.h>
 14
 15#include <switch.h>
 16#include <EGL/egl.h>
 17#include <GLES3/gl3.h>
 18
 19#define AUTO_INPUT 0x4E585031
 20#define SAMPLES 0x400
 21#define BUFFER_SIZE 0x1000
 22#define N_BUFFERS 4
 23#define ANALOG_DEADZONE 0x4000
 24
 25TimeType __nx_time_type = TimeType_UserSystemClock;
 26
 27static EGLDisplay s_display;
 28static EGLContext s_context;
 29static EGLSurface s_surface;
 30
 31static const GLfloat _offsets[] = {
 32	0.f, 0.f,
 33	1.f, 0.f,
 34	1.f, 1.f,
 35	0.f, 1.f,
 36};
 37
 38static const GLchar* const _gles2Header =
 39	"#version 100\n"
 40	"precision mediump float;\n";
 41
 42static const char* const _vertexShader =
 43	"attribute vec2 offset;\n"
 44	"uniform vec2 dims;\n"
 45	"uniform vec2 insize;\n"
 46	"varying vec2 texCoord;\n"
 47
 48	"void main() {\n"
 49	"	vec2 ratio = insize / 256.0;\n"
 50	"	vec2 scaledOffset = offset * dims;\n"
 51	"	gl_Position = vec4(scaledOffset.x * 2.0 - dims.x, scaledOffset.y * -2.0 + dims.y, 0.0, 1.0);\n"
 52	"	texCoord = offset * ratio;\n"
 53	"}";
 54
 55static const char* const _fragmentShader =
 56	"varying vec2 texCoord;\n"
 57	"uniform sampler2D tex;\n"
 58	"uniform vec4 color;\n"
 59
 60	"void main() {\n"
 61	"	vec4 texColor = vec4(texture2D(tex, texCoord).rgb, 1.0);\n"
 62	"	texColor *= color;\n"
 63	"	gl_FragColor = texColor;\n"
 64	"}";
 65
 66static GLuint program;
 67static GLuint vbo;
 68static GLuint vao;
 69static GLuint pbo;
 70static GLuint texLocation;
 71static GLuint dimsLocation;
 72static GLuint insizeLocation;
 73static GLuint colorLocation;
 74static GLuint tex;
 75
 76static color_t* frameBuffer;
 77static struct mAVStream stream;
 78static struct mSwitchRumble {
 79	struct mRumble d;
 80	int up;
 81	int down;
 82	HidVibrationValue value;
 83} rumble;
 84static int audioBufferActive;
 85static struct GBAStereoSample audioBuffer[N_BUFFERS][SAMPLES] __attribute__((__aligned__(0x1000)));
 86static AudioOutBuffer audoutBuffer[N_BUFFERS];
 87static int enqueuedBuffers;
 88static bool frameLimiter = true;
 89static unsigned framecount = 0;
 90static unsigned framecap = 10;
 91static u32 vibrationDeviceHandles[4];
 92static HidVibrationValue vibrationStop = { .freq_low = 160.f, .freq_high = 320.f };
 93
 94static bool initEgl() {
 95    s_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
 96    if (!s_display) {
 97        goto _fail0;
 98    }
 99
100    eglInitialize(s_display, NULL, NULL);
101
102    EGLConfig config;
103    EGLint numConfigs;
104    static const EGLint attributeList[] = {
105        EGL_RED_SIZE, 1,
106        EGL_GREEN_SIZE, 1,
107        EGL_BLUE_SIZE, 1,
108        EGL_NONE
109    };
110    eglChooseConfig(s_display, attributeList, &config, 1, &numConfigs);
111    if (!numConfigs) {
112        goto _fail1;
113    }
114
115    s_surface = eglCreateWindowSurface(s_display, config, "", NULL);
116    if (!s_surface) {
117        goto _fail1;
118    }
119
120	EGLint contextAttributeList[] = {
121		EGL_CONTEXT_CLIENT_VERSION, 3,
122		EGL_NONE
123	};
124    s_context = eglCreateContext(s_display, config, EGL_NO_CONTEXT, contextAttributeList);
125    if (!s_context) {
126        goto _fail2;
127    }
128
129    eglMakeCurrent(s_display, s_surface, s_surface, s_context);
130    return true;
131
132_fail2:
133    eglDestroySurface(s_display, s_surface);
134    s_surface = NULL;
135_fail1:
136    eglTerminate(s_display);
137    s_display = NULL;
138_fail0:
139    return false;
140}
141
142static void deinitEgl() {
143    if (s_display) {
144        if (s_context) {
145            eglDestroyContext(s_display, s_context);
146        }
147        if (s_surface) {
148            eglDestroySurface(s_display, s_surface);
149        }
150        eglTerminate(s_display);
151    }
152}
153
154static void _mapKey(struct mInputMap* map, uint32_t binding, int nativeKey, enum GBAKey key) {
155	mInputBindKey(map, binding, __builtin_ctz(nativeKey), key);
156}
157
158static void _drawStart(void) {
159	glClear(GL_COLOR_BUFFER_BIT);
160}
161
162static void _drawEnd(void) {
163	if (frameLimiter || framecount >= framecap) {
164		eglSwapBuffers(s_display, s_surface);
165		framecount = 0;
166	}
167}
168
169static uint32_t _pollInput(const struct mInputMap* map) {
170	int keys = 0;
171	hidScanInput();
172	u32 padkeys = hidKeysHeld(CONTROLLER_P1_AUTO);
173	keys |= mInputMapKeyBits(map, AUTO_INPUT, padkeys, 0);
174
175	JoystickPosition jspos;
176	hidJoystickRead(&jspos, CONTROLLER_P1_AUTO, JOYSTICK_LEFT);
177
178	int l = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_LSTICK_LEFT));
179	int r = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_LSTICK_RIGHT));
180	int u = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_LSTICK_UP));
181	int d = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_LSTICK_DOWN));
182
183	if (l == -1) {
184		l = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_DLEFT));
185	}
186	if (r == -1) {
187		r = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_DRIGHT));
188	}
189	if (u == -1) {
190		u = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_DUP));
191	}
192	if (d == -1) {
193		d = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_DDOWN));
194	}
195
196	if (jspos.dx < -ANALOG_DEADZONE && l != -1) {
197		keys |= 1 << l;
198	}
199	if (jspos.dx > ANALOG_DEADZONE && r != -1) {
200		keys |= 1 << r;
201	}
202	if (jspos.dy < -ANALOG_DEADZONE && d != -1) {
203		keys |= 1 << d;
204	}
205	if (jspos.dy > ANALOG_DEADZONE && u != -1) {
206		keys |= 1 << u;
207	}
208	return keys;
209}
210
211static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
212	hidScanInput();
213	if (hidTouchCount() < 1) {
214		return GUI_CURSOR_NOT_PRESENT;
215	}
216	touchPosition touch;
217	hidTouchRead(&touch, 0);
218	*x = touch.px;
219	*y = touch.py;
220	return GUI_CURSOR_DOWN;
221}
222
223
224static void _setup(struct mGUIRunner* runner) {
225	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_A, GBA_KEY_A);
226	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_B, GBA_KEY_B);
227	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_PLUS, GBA_KEY_START);
228	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_MINUS, GBA_KEY_SELECT);
229	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DUP, GBA_KEY_UP);
230	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DDOWN, GBA_KEY_DOWN);
231	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DLEFT, GBA_KEY_LEFT);
232	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DRIGHT, GBA_KEY_RIGHT);
233	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_L, GBA_KEY_L);
234	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_R, GBA_KEY_R);
235
236	runner->core->setVideoBuffer(runner->core, frameBuffer, 256);
237	runner->core->setPeripheral(runner->core, mPERIPH_RUMBLE, &rumble.d);
238	runner->core->setAVStream(runner->core, &stream);
239}
240
241static void _gameLoaded(struct mGUIRunner* runner) {
242	u32 samplerate = audoutGetSampleRate();
243
244	double ratio = GBAAudioCalculateRatio(1, 60.0, 1);
245	blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), samplerate * ratio);
246	blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), samplerate * ratio);
247
248	mCoreConfigGetUIntValue(&runner->config, "fastForwardCap", &framecap);
249
250	rumble.up = 0;
251	rumble.down = 0;
252}
253
254static void _gameUnloaded(struct mGUIRunner* runner) {
255	HidVibrationValue values[4];
256	memcpy(&values[0], &vibrationStop, sizeof(rumble.value));
257	memcpy(&values[1], &vibrationStop, sizeof(rumble.value));
258	memcpy(&values[2], &vibrationStop, sizeof(rumble.value));
259	memcpy(&values[3], &vibrationStop, sizeof(rumble.value));
260	hidSendVibrationValues(vibrationDeviceHandles, values, 4);
261}
262
263static void _drawTex(struct mGUIRunner* runner, unsigned width, unsigned height, bool faded) {
264	glEnable(GL_BLEND);
265	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
266
267	glUseProgram(program);
268	glBindVertexArray(vao);
269	float aspectX = width / (float) runner->params.width;
270	float aspectY = height / (float) runner->params.height;
271	float max;
272	if (aspectX > aspectY) {
273		max = floor(1.0 / aspectX);
274	} else {
275		max = floor(1.0 / aspectY);
276	}
277
278	aspectX *= max;
279	aspectY *= max;
280
281	glUniform1i(texLocation, 0);
282	glUniform2f(dimsLocation, aspectX, aspectY);
283	glUniform2f(insizeLocation, width, height);
284	if (!faded) {
285		glUniform4f(colorLocation, 1.0f, 1.0f, 1.0f, 1.0f);
286	} else {
287		glUniform4f(colorLocation, 0.8f, 0.8f, 0.8f, 0.8f);		
288	}
289
290	glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
291
292	glBindVertexArray(0);
293	glUseProgram(0);
294}
295
296static void _prepareForFrame(struct mGUIRunner* runner) {
297	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
298	frameBuffer = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, 256 * 256 * 4, GL_MAP_WRITE_BIT);
299	if (frameBuffer) {
300		runner->core->setVideoBuffer(runner->core, frameBuffer, 256);
301	}
302	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
303}
304
305static void _drawFrame(struct mGUIRunner* runner, bool faded) {
306	++framecount;
307	if (!frameLimiter && framecount < framecap) {
308		return;
309	}
310
311	unsigned width, height;
312	runner->core->desiredVideoDimensions(runner->core, &width, &height);
313
314	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
315	glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
316
317	glActiveTexture(GL_TEXTURE0);
318	glBindTexture(GL_TEXTURE_2D, tex);
319	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, height, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
320	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
321
322	_drawTex(runner, width, height, faded);
323
324	HidVibrationValue values[4];
325	if (rumble.up) {
326		rumble.value.amp_low = rumble.up / (float) (rumble.up + rumble.down);
327		rumble.value.amp_high = rumble.up / (float) (rumble.up + rumble.down);
328		memcpy(&values[0], &rumble.value, sizeof(rumble.value));
329		memcpy(&values[1], &rumble.value, sizeof(rumble.value));
330		memcpy(&values[2], &rumble.value, sizeof(rumble.value));
331		memcpy(&values[3], &rumble.value, sizeof(rumble.value));
332	} else {
333		memcpy(&values[0], &vibrationStop, sizeof(rumble.value));
334		memcpy(&values[1], &vibrationStop, sizeof(rumble.value));
335		memcpy(&values[2], &vibrationStop, sizeof(rumble.value));
336		memcpy(&values[3], &vibrationStop, sizeof(rumble.value));
337	}
338	hidSendVibrationValues(vibrationDeviceHandles, values, 4);
339	rumble.up = 0;
340	rumble.down = 0;
341}
342
343static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsigned width, unsigned height, bool faded) {
344	glActiveTexture(GL_TEXTURE0);
345	glBindTexture(GL_TEXTURE_2D, tex);
346	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
347
348	_drawTex(runner, width, height, faded);
349}
350
351static uint16_t _pollGameInput(struct mGUIRunner* runner) {
352	return _pollInput(&runner->core->inputMap);
353}
354
355static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
356	UNUSED(runner);
357	if (!frameLimiter && limit) {
358		while (enqueuedBuffers > 1) {
359			AudioOutBuffer* releasedBuffers;
360			u32 audoutNReleasedBuffers;
361			audoutWaitPlayFinish(&releasedBuffers, &audoutNReleasedBuffers, 100000000);
362			enqueuedBuffers -= audoutNReleasedBuffers;
363		}
364	}
365	frameLimiter = limit;
366}
367
368static bool _running(struct mGUIRunner* runner) {
369	UNUSED(runner);
370	return appletMainLoop();
371}
372
373static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
374	UNUSED(stream);
375	AudioOutBuffer* releasedBuffers;
376	u32 audoutNReleasedBuffers;
377	audoutGetReleasedAudioOutBuffer(&releasedBuffers, &audoutNReleasedBuffers);
378	enqueuedBuffers -= audoutNReleasedBuffers;
379	if (!frameLimiter && enqueuedBuffers >= N_BUFFERS) {
380		blip_clear(left);
381		blip_clear(right);
382		return;
383	}
384	if (enqueuedBuffers >= N_BUFFERS - 1 && R_SUCCEEDED(audoutWaitPlayFinish(&releasedBuffers, &audoutNReleasedBuffers, 10000000))) {
385		enqueuedBuffers -= audoutNReleasedBuffers;
386	}
387
388	struct GBAStereoSample* samples = audioBuffer[audioBufferActive];
389	blip_read_samples(left, &samples[0].left, SAMPLES, true);
390	blip_read_samples(right, &samples[0].right, SAMPLES, true);
391	audoutAppendAudioOutBuffer(&audoutBuffer[audioBufferActive]);
392	audioBufferActive += 1;
393	audioBufferActive %= N_BUFFERS;
394	++enqueuedBuffers;
395}
396
397void _setRumble(struct mRumble* rumble, int enable) {
398	struct mSwitchRumble* sr = (struct mSwitchRumble*) rumble;
399	if (enable) {
400		++sr->up;
401	} else {
402		++sr->down;
403	}
404}
405
406static int _batteryState(void) {
407	u32 charge;
408	int state = 0;
409	if (R_SUCCEEDED(psmGetBatteryChargePercentage(&charge))) {
410		state = (charge + 12) / 25;
411	} else {
412		return BATTERY_NOT_PRESENT;
413	}
414	ChargerType type;
415	if (R_SUCCEEDED(psmGetChargerType(&type)) && type) {
416		state |= BATTERY_CHARGING;
417	}
418	return state;
419}
420
421int main(int argc, char* argv[]) {
422	socketInitializeDefault();
423	nxlinkStdio();
424	initEgl();
425	romfsInit();
426	audoutInitialize();
427	psmInitialize();
428
429	struct GUIFont* font = GUIFontCreate();
430
431	u32 width = 1280;
432	u32 height = 720;
433
434	glViewport(0, 0, width, height);
435	glClearColor(0.f, 0.f, 0.f, 1.f);
436
437	glGenTextures(1, &tex);
438	glActiveTexture(GL_TEXTURE0);
439	glBindTexture(GL_TEXTURE_2D, tex);
440	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
441	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
442	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
443	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
444	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
445
446	glGenBuffers(1, &pbo);
447	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
448	glBufferData(GL_PIXEL_UNPACK_BUFFER, 256 * 256 * 4, NULL, GL_STREAM_DRAW);
449	frameBuffer = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, 256 * 256 * 4, GL_MAP_WRITE_BIT);
450	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
451
452	program = glCreateProgram();
453	GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
454	GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
455	const GLchar* shaderBuffer[2];
456
457	shaderBuffer[0] = _gles2Header;
458
459	shaderBuffer[1] = _vertexShader;
460	glShaderSource(vertexShader, 2, shaderBuffer, NULL);
461
462	shaderBuffer[1] = _fragmentShader;
463	glShaderSource(fragmentShader, 2, shaderBuffer, NULL);
464
465	glAttachShader(program, vertexShader);
466	glAttachShader(program, fragmentShader);
467
468	glCompileShader(fragmentShader);
469
470	GLint success;
471	glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
472	if (!success) {
473		GLchar msg[512];
474		glGetShaderInfoLog(fragmentShader, sizeof(msg), NULL, msg);
475		puts(msg);
476	}
477
478	glCompileShader(vertexShader);
479
480	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
481	if (!success) {
482		GLchar msg[512];
483		glGetShaderInfoLog(vertexShader, sizeof(msg), NULL, msg);
484		puts(msg);
485	}
486	glLinkProgram(program);
487
488	glDeleteShader(vertexShader);
489	glDeleteShader(fragmentShader);
490
491	texLocation = glGetUniformLocation(program, "tex");
492	colorLocation = glGetUniformLocation(program, "color");
493	dimsLocation = glGetUniformLocation(program, "dims");
494	insizeLocation = glGetUniformLocation(program, "insize");
495	GLuint offsetLocation = glGetAttribLocation(program, "offset");
496
497	glGenBuffers(1, &vbo);
498	glGenVertexArrays(1, &vao);
499	glBindVertexArray(vao);
500	glBindBuffer(GL_ARRAY_BUFFER, vbo);
501	glBufferData(GL_ARRAY_BUFFER, sizeof(_offsets), _offsets, GL_STATIC_DRAW);
502	glVertexAttribPointer(offsetLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
503	glEnableVertexAttribArray(offsetLocation);
504	glBindVertexArray(0);
505
506	rumble.d.setRumble = _setRumble;
507	rumble.value.freq_low = 120.0;
508	rumble.value.freq_high = 180.0;
509	hidInitializeVibrationDevices(&vibrationDeviceHandles[0], 2, CONTROLLER_HANDHELD, TYPE_HANDHELD | TYPE_JOYCON_PAIR);
510	hidInitializeVibrationDevices(&vibrationDeviceHandles[2], 2, CONTROLLER_PLAYER_1, TYPE_HANDHELD | TYPE_JOYCON_PAIR);
511
512	stream.videoDimensionsChanged = NULL;
513	stream.postVideoFrame = NULL;
514	stream.postAudioFrame = NULL;
515	stream.postAudioBuffer = _postAudioBuffer;
516
517	memset(audioBuffer, 0, sizeof(audioBuffer));
518	audioBufferActive = 0;
519	enqueuedBuffers = 0;
520	size_t i;
521	for (i = 0; i < N_BUFFERS; ++i) {
522		audoutBuffer[i].next = NULL;
523		audoutBuffer[i].buffer = audioBuffer[i];
524		audoutBuffer[i].buffer_size = BUFFER_SIZE;
525		audoutBuffer[i].data_size = BUFFER_SIZE;
526		audoutBuffer[i].data_offset = 0;
527	}
528
529	struct mGUIRunner runner = {
530		.params = {
531			width, height,
532			font, "/",
533			_drawStart, _drawEnd,
534			_pollInput, _pollCursor,
535			_batteryState,
536			NULL, NULL,
537		},
538		.keySources = (struct GUIInputKeys[]) {
539			{
540				.name = "Controller Input",
541				.id = AUTO_INPUT,
542				.keyNames = (const char*[]) {
543					"A",
544					"B",
545					"X",
546					"Y",
547					"L Stick",
548					"R Stick",
549					"L",
550					"R",
551					"ZL",
552					"ZR",
553					"+",
554					"-",
555					"Left",
556					"Up",
557					"Right",
558					"Down",
559					"L Left",
560					"L Up",
561					"L Right",
562					"L Down",
563					"R Left",
564					"R Up",
565					"R Right",
566					"R Down",
567					"SL",
568					"SR"
569				},
570				.nKeys = 26
571			},
572			{ .id = 0 }
573		},
574		.configExtra = (struct GUIMenuItem[]) {
575			{
576				.title = "Fast forward cap",
577				.data = "fastForwardCap",
578				.submenu = 0,
579				.state = 7,
580				.validStates = (const char*[]) {
581					"3", "4", "5", "6", "7", "8", "9",
582					"10", "11", "12", "13", "14", "15",
583					"20", "30"
584				},
585				.stateMappings = (const struct GUIVariant[]) {
586					GUI_V_U(3),
587					GUI_V_U(4),
588					GUI_V_U(5),
589					GUI_V_U(6),
590					GUI_V_U(7),
591					GUI_V_U(8),
592					GUI_V_U(9),
593					GUI_V_U(10),
594					GUI_V_U(11),
595					GUI_V_U(12),
596					GUI_V_U(13),
597					GUI_V_U(14),
598					GUI_V_U(15),
599					GUI_V_U(20),
600					GUI_V_U(30),
601				},
602				.nStates = 15
603			},
604		},
605		.nConfigExtra = 1,
606		.setup = _setup,
607		.teardown = NULL,
608		.gameLoaded = _gameLoaded,
609		.gameUnloaded = _gameUnloaded,
610		.prepareForFrame = _prepareForFrame,
611		.drawFrame = _drawFrame,
612		.drawScreenshot = _drawScreenshot,
613		.paused = _gameUnloaded,
614		.unpaused = _gameLoaded,
615		.incrementScreenMode = NULL,
616		.setFrameLimiter = _setFrameLimiter,
617		.pollGameInput = _pollGameInput,
618		.running = _running
619	};
620	mGUIInit(&runner, "switch");
621
622	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_A, GUI_INPUT_SELECT);
623	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_B, GUI_INPUT_BACK);
624	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_X, GUI_INPUT_CANCEL);
625	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DUP, GUI_INPUT_UP);
626	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DDOWN, GUI_INPUT_DOWN);
627	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DLEFT, GUI_INPUT_LEFT);
628	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DRIGHT, GUI_INPUT_RIGHT);
629
630	audoutStartAudioOut();
631
632	if (argc > 1) {
633		size_t i;
634		for (i = 0; runner.keySources[i].id; ++i) {
635			mInputMapLoad(&runner.params.keyMap, runner.keySources[i].id, mCoreConfigGetInput(&runner.config));
636		}
637		mGUIRun(&runner, argv[1]);
638	} else {
639		mGUIRunloop(&runner);
640	}
641
642	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
643	glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
644	glDeleteBuffers(1, &pbo);
645
646	glDeleteTextures(1, &tex);
647	glDeleteBuffers(1, &vbo);
648	glDeleteProgram(program);
649	glDeleteVertexArrays(1, &vao);
650
651	psmExit();
652	audoutExit();
653	deinitEgl();
654	socketExit();
655	return 0;
656}