all repos — mgba @ 4af5238a3be27d2caf025041c7773a491a083dfd

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