all repos — mgba @ 0b40fb0704300fadd40ff798295711b1c14f3c57

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