all repos — mgba @ 101d80dca32cf2cab879600db2c6c959ea57c442

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