all repos — mgba @ 9b0e4af7b430ee1cd4bbc9b7a37768a820c2b236

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