all repos — mgba @ 3ce0472963ea747edb1e7233e2fdfe9b1d8db4b8

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