all repos — mgba @ 2d303cdda314c48a9534fef04782f8ad2bf2ca6a

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        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	eglSwapInterval(s_surface, limit);
407}
408
409static bool _running(struct mGUIRunner* runner) {
410	UNUSED(runner);
411	return appletMainLoop();
412}
413
414static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
415	UNUSED(stream);
416	AudioOutBuffer* releasedBuffers;
417	u32 audoutNReleasedBuffers;
418	audoutGetReleasedAudioOutBuffer(&releasedBuffers, &audoutNReleasedBuffers);
419	enqueuedBuffers -= audoutNReleasedBuffers;
420	if (!frameLimiter && enqueuedBuffers >= N_BUFFERS) {
421		blip_clear(left);
422		blip_clear(right);
423		return;
424	}
425	if (enqueuedBuffers >= N_BUFFERS - 1 && R_SUCCEEDED(audoutWaitPlayFinish(&releasedBuffers, &audoutNReleasedBuffers, 10000000))) {
426		enqueuedBuffers -= audoutNReleasedBuffers;
427	}
428
429	struct GBAStereoSample* samples = audioBuffer[audioBufferActive];
430	blip_read_samples(left, &samples[0].left, SAMPLES, true);
431	blip_read_samples(right, &samples[0].right, SAMPLES, true);
432	audoutAppendAudioOutBuffer(&audoutBuffer[audioBufferActive]);
433	audioBufferActive += 1;
434	audioBufferActive %= N_BUFFERS;
435	++enqueuedBuffers;
436}
437
438void _setRumble(struct mRumble* rumble, int enable) {
439	struct mSwitchRumble* sr = (struct mSwitchRumble*) rumble;
440	if (enable) {
441		++sr->up;
442	} else {
443		++sr->down;
444	}
445}
446
447int32_t _readTiltX(struct mRotationSource* source) {
448	UNUSED(source);
449	SixAxisSensorValues sixaxis;
450	hidSixAxisSensorValuesRead(&sixaxis, CONTROLLER_P1_AUTO, 1);
451	return sixaxis.accelerometer.x * 3e8f;
452}
453
454int32_t _readTiltY(struct mRotationSource* source) {
455	UNUSED(source);
456	SixAxisSensorValues sixaxis;
457	hidSixAxisSensorValuesRead(&sixaxis, CONTROLLER_P1_AUTO, 1);
458	return sixaxis.accelerometer.y * -3e8f;
459}
460
461int32_t _readGyroZ(struct mRotationSource* source) {
462	UNUSED(source);
463	SixAxisSensorValues sixaxis;
464	hidSixAxisSensorValuesRead(&sixaxis, CONTROLLER_P1_AUTO, 1);
465	return sixaxis.gyroscope.z * 1.1e9f;
466}
467
468static int _batteryState(void) {
469	u32 charge;
470	int state = 0;
471	if (R_SUCCEEDED(psmGetBatteryChargePercentage(&charge))) {
472		state = (charge + 12) / 25;
473	} else {
474		return BATTERY_NOT_PRESENT;
475	}
476	ChargerType type;
477	if (R_SUCCEEDED(psmGetChargerType(&type)) && type) {
478		state |= BATTERY_CHARGING;
479	}
480	return state;
481}
482
483int main(int argc, char* argv[]) {
484	socketInitializeDefault();
485	nxlinkStdio();
486	initEgl();
487	romfsInit();
488	audoutInitialize();
489	psmInitialize();
490
491	struct GUIFont* font = GUIFontCreate();
492
493	u32 width = 1280;
494	u32 height = 720;
495
496	glViewport(0, 0, width, height);
497	glClearColor(0.f, 0.f, 0.f, 1.f);
498
499	glGenTextures(1, &tex);
500	glActiveTexture(GL_TEXTURE0);
501	glBindTexture(GL_TEXTURE_2D, tex);
502	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
503	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
504	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
505	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
506	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
507
508	glGenBuffers(1, &pbo);
509	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
510	glBufferData(GL_PIXEL_UNPACK_BUFFER, 256 * 256 * 4, NULL, GL_STREAM_DRAW);
511	frameBuffer = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, 256 * 256 * 4, GL_MAP_WRITE_BIT);
512	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
513
514	program = glCreateProgram();
515	GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
516	GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
517	const GLchar* shaderBuffer[2];
518
519	shaderBuffer[0] = _gles2Header;
520
521	shaderBuffer[1] = _vertexShader;
522	glShaderSource(vertexShader, 2, shaderBuffer, NULL);
523
524	shaderBuffer[1] = _fragmentShader;
525	glShaderSource(fragmentShader, 2, shaderBuffer, NULL);
526
527	glAttachShader(program, vertexShader);
528	glAttachShader(program, fragmentShader);
529
530	glCompileShader(fragmentShader);
531
532	GLint success;
533	glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
534	if (!success) {
535		GLchar msg[512];
536		glGetShaderInfoLog(fragmentShader, sizeof(msg), NULL, msg);
537		puts(msg);
538	}
539
540	glCompileShader(vertexShader);
541
542	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
543	if (!success) {
544		GLchar msg[512];
545		glGetShaderInfoLog(vertexShader, sizeof(msg), NULL, msg);
546		puts(msg);
547	}
548	glLinkProgram(program);
549
550	glDeleteShader(vertexShader);
551	glDeleteShader(fragmentShader);
552
553	texLocation = glGetUniformLocation(program, "tex");
554	colorLocation = glGetUniformLocation(program, "color");
555	dimsLocation = glGetUniformLocation(program, "dims");
556	insizeLocation = glGetUniformLocation(program, "insize");
557	GLuint offsetLocation = glGetAttribLocation(program, "offset");
558
559	glGenBuffers(1, &vbo);
560	glGenVertexArrays(1, &vao);
561	glBindVertexArray(vao);
562	glBindBuffer(GL_ARRAY_BUFFER, vbo);
563	glBufferData(GL_ARRAY_BUFFER, sizeof(_offsets), _offsets, GL_STATIC_DRAW);
564	glVertexAttribPointer(offsetLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
565	glEnableVertexAttribArray(offsetLocation);
566	glBindVertexArray(0);
567
568	rumble.d.setRumble = _setRumble;
569	rumble.value.freq_low = 120.0;
570	rumble.value.freq_high = 180.0;
571	hidInitializeVibrationDevices(&vibrationDeviceHandles[0], 2, CONTROLLER_HANDHELD, TYPE_HANDHELD | TYPE_JOYCON_PAIR);
572	hidInitializeVibrationDevices(&vibrationDeviceHandles[2], 2, CONTROLLER_PLAYER_1, TYPE_HANDHELD | TYPE_JOYCON_PAIR);
573
574	u32 handles[4];
575	hidGetSixAxisSensorHandles(&handles[0], 2, CONTROLLER_PLAYER_1, TYPE_JOYCON_PAIR);
576	hidGetSixAxisSensorHandles(&handles[2], 1, CONTROLLER_PLAYER_1, TYPE_PROCONTROLLER);
577	hidGetSixAxisSensorHandles(&handles[3], 1, CONTROLLER_HANDHELD, TYPE_HANDHELD);
578	hidStartSixAxisSensor(handles[0]);
579	hidStartSixAxisSensor(handles[1]);
580	hidStartSixAxisSensor(handles[2]);
581	hidStartSixAxisSensor(handles[3]);
582	rotation.readTiltX = _readTiltX;
583	rotation.readTiltY = _readTiltY;
584	rotation.readGyroZ = _readGyroZ;
585
586	stream.videoDimensionsChanged = NULL;
587	stream.postVideoFrame = NULL;
588	stream.postAudioFrame = NULL;
589	stream.postAudioBuffer = _postAudioBuffer;
590
591	memset(audioBuffer, 0, sizeof(audioBuffer));
592	audioBufferActive = 0;
593	enqueuedBuffers = 0;
594	size_t i;
595	for (i = 0; i < N_BUFFERS; ++i) {
596		audoutBuffer[i].next = NULL;
597		audoutBuffer[i].buffer = audioBuffer[i];
598		audoutBuffer[i].buffer_size = BUFFER_SIZE;
599		audoutBuffer[i].data_size = BUFFER_SIZE;
600		audoutBuffer[i].data_offset = 0;
601	}
602
603	struct mGUIRunner runner = {
604		.params = {
605			width, height,
606			font, "/",
607			_drawStart, _drawEnd,
608			_pollInput, _pollCursor,
609			_batteryState,
610			NULL, NULL,
611		},
612		.keySources = (struct GUIInputKeys[]) {
613			{
614				.name = "Controller Input",
615				.id = AUTO_INPUT,
616				.keyNames = (const char*[]) {
617					"A",
618					"B",
619					"X",
620					"Y",
621					"L Stick",
622					"R Stick",
623					"L",
624					"R",
625					"ZL",
626					"ZR",
627					"+",
628					"-",
629					"Left",
630					"Up",
631					"Right",
632					"Down",
633					"L Left",
634					"L Up",
635					"L Right",
636					"L Down",
637					"R Left",
638					"R Up",
639					"R Right",
640					"R Down",
641					"SL",
642					"SR"
643				},
644				.nKeys = 26
645			},
646			{ .id = 0 }
647		},
648		.configExtra = (struct GUIMenuItem[]) {
649			{
650				.title = "Screen mode",
651				.data = "screenMode",
652				.submenu = 0,
653				.state = SM_PA,
654				.validStates = (const char*[]) {
655					"Pixel-Accurate",
656					"Aspect-Ratio Fit",
657					"Stretched",
658				},
659				.nStates = 3
660			},
661			{
662				.title = "Fast forward cap",
663				.data = "fastForwardCap",
664				.submenu = 0,
665				.state = 7,
666				.validStates = (const char*[]) {
667					"2", "3", "4", "5", "6", "7", "8", "9",
668					"10", "11", "12", "13", "14", "15",
669					"20", "30"
670				},
671				.stateMappings = (const struct GUIVariant[]) {
672					GUI_V_U(2),
673					GUI_V_U(3),
674					GUI_V_U(4),
675					GUI_V_U(5),
676					GUI_V_U(6),
677					GUI_V_U(7),
678					GUI_V_U(8),
679					GUI_V_U(9),
680					GUI_V_U(10),
681					GUI_V_U(11),
682					GUI_V_U(12),
683					GUI_V_U(13),
684					GUI_V_U(14),
685					GUI_V_U(15),
686					GUI_V_U(20),
687					GUI_V_U(30),
688				},
689				.nStates = 16
690			},
691		},
692		.nConfigExtra = 2,
693		.setup = _setup,
694		.teardown = NULL,
695		.gameLoaded = _gameLoaded,
696		.gameUnloaded = _gameUnloaded,
697		.prepareForFrame = _prepareForFrame,
698		.drawFrame = _drawFrame,
699		.drawScreenshot = _drawScreenshot,
700		.paused = _gameUnloaded,
701		.unpaused = _gameLoaded,
702		.incrementScreenMode = _incrementScreenMode,
703		.setFrameLimiter = _setFrameLimiter,
704		.pollGameInput = _pollGameInput,
705		.running = _running
706	};
707	mGUIInit(&runner, "switch");
708
709	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_A, GUI_INPUT_SELECT);
710	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_B, GUI_INPUT_BACK);
711	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_X, GUI_INPUT_CANCEL);
712	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DUP, GUI_INPUT_UP);
713	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DDOWN, GUI_INPUT_DOWN);
714	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DLEFT, GUI_INPUT_LEFT);
715	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DRIGHT, GUI_INPUT_RIGHT);
716
717	audoutStartAudioOut();
718
719	if (argc > 1) {
720		size_t i;
721		for (i = 0; runner.keySources[i].id; ++i) {
722			mInputMapLoad(&runner.params.keyMap, runner.keySources[i].id, mCoreConfigGetInput(&runner.config));
723		}
724		mGUIRun(&runner, argv[1]);
725	} else {
726		mGUIRunloop(&runner);
727	}
728
729	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
730	glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
731	glDeleteBuffers(1, &pbo);
732
733	glDeleteTextures(1, &tex);
734	glDeleteBuffers(1, &vbo);
735	glDeleteProgram(program);
736	glDeleteVertexArrays(1, &vao);
737
738	hidStopSixAxisSensor(handles[0]);
739	hidStopSixAxisSensor(handles[1]);
740	hidStopSixAxisSensor(handles[2]);
741	hidStopSixAxisSensor(handles[3]);
742
743	psmExit();
744	audoutExit();
745	deinitEgl();
746	socketExit();
747	return 0;
748}