all repos — mgba @ 1247dec1ba725ff21307429a3a9f417685ef1218

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