all repos — mgba @ d025dd57041479d54dfec5238573a1cda877b866

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