all repos — mgba @ 67c3f386a4106d5d3017aea882f64dc0ae94a375

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