all repos — mgba @ d746cb16d6350c587a4bca70e80be6aaae8c659b

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