all repos — mgba @ 456dbc482f9ee76e8b632e296ff4162ac38f18a1

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