all repos — mgba @ 3fb46a2a8832c383429fd65c00ba3e642c750bcf

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