all repos — mgba @ e073986ea83e4b272a1a92bb8d4d9e2e938fec1b

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