all repos — mgba @ a64c38d3140d8a4015650096c0f61b7d4a9ee38f

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