all repos — mgba @ f2134880898dd3ceaba7945c992c8ff34e245682

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 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	runner->core->setAudioBufferSize(runner->core, SAMPLES);
286}
287
288static void _gameLoaded(struct mGUIRunner* runner) {
289	u32 samplerate = audoutGetSampleRate();
290
291	double ratio = GBAAudioCalculateRatio(1, 60.0, 1);
292	blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), samplerate * ratio);
293	blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), samplerate * ratio);
294
295	mCoreConfigGetUIntValue(&runner->config, "fastForwardCap", &framecap);
296
297	unsigned mode;
298	if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
299		screenMode = mode;
300	}
301
302	int fakeBool;
303	if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) {
304		interframeBlending = fakeBool;
305	}
306	if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) {
307		sgbCrop = fakeBool;
308	}
309	if (mCoreConfigGetIntValue(&runner->config, "useLightSensor", &fakeBool)) {
310		if (useLightSensor != fakeBool) {
311			useLightSensor = fakeBool;
312
313			if (runner->core->platform(runner->core) == PLATFORM_GBA) {
314				if (useLightSensor) {
315					runner->core->setPeripheral(runner->core, mPERIPH_GBA_LUMINANCE, &lightSensor.d);
316				} else {
317					runner->core->setPeripheral(runner->core, mPERIPH_GBA_LUMINANCE, &runner->luminanceSource.d);
318				}
319			}
320		}
321	}
322
323	rumble.up = 0;
324	rumble.down = 0;
325}
326
327static void _gameUnloaded(struct mGUIRunner* runner) {
328	HidVibrationValue values[4];
329	memcpy(&values[0], &vibrationStop, sizeof(rumble.value));
330	memcpy(&values[1], &vibrationStop, sizeof(rumble.value));
331	memcpy(&values[2], &vibrationStop, sizeof(rumble.value));
332	memcpy(&values[3], &vibrationStop, sizeof(rumble.value));
333	hidSendVibrationValues(vibrationDeviceHandles, values, 4);
334}
335
336static void _drawTex(struct mGUIRunner* runner, unsigned width, unsigned height, bool faded, bool blendTop) {
337	glViewport(0, 1080 - vheight, vwidth, vheight);
338	glEnable(GL_BLEND);
339	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
340
341	glUseProgram(program);
342	glBindVertexArray(vao);
343	float inwidth = width;
344	float inheight = height;
345	if (sgbCrop && width == 256 && height == 224) {
346		inwidth = GB_VIDEO_HORIZONTAL_PIXELS;
347		inheight = GB_VIDEO_VERTICAL_PIXELS;
348	}
349	float aspectX = inwidth / vwidth;
350	float aspectY = inheight / vheight;
351	float max = 1.f;
352	switch (screenMode) {
353	case SM_PA:
354		if (aspectX > aspectY) {
355			max = floor(1.0 / aspectX);
356		} else {
357			max = floor(1.0 / aspectY);
358		}
359		if (max >= 1.0) {
360			break;
361		}
362		// Fall through
363	case SM_AF:
364		if (aspectX > aspectY) {
365			max = 1.0 / aspectX;
366		} else {
367			max = 1.0 / aspectY;
368		}
369		break;
370	case SM_SF:
371		aspectX = 1.0;
372		aspectY = 1.0;
373		break;
374	}
375
376	if (screenMode != SM_SF) {
377		aspectX = width / (float) vwidth;
378		aspectY = height / (float) vheight;
379	}
380
381	aspectX *= max;
382	aspectY *= max;
383
384	glUniform1i(texLocation, 0);
385	glUniform2f(dimsLocation, aspectX, aspectY);
386	if (usePbo) {
387		glUniform2f(insizeLocation, width / 256.f, height / 256.f);
388	} else {
389		glUniform2f(insizeLocation, 1, 1);
390	}
391	if (!faded) {
392		glUniform4f(colorLocation, 1.0f, 1.0f, 1.0f, blendTop ? 0.5f : 1.0f);
393	} else {
394		glUniform4f(colorLocation, 0.8f, 0.8f, 0.8f, blendTop ? 0.4f : 0.8f);
395	}
396
397	glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
398
399	glBindVertexArray(0);
400	glUseProgram(0);
401	glDisable(GL_BLEND);
402	glViewport(0, 1080 - runner->params.height, runner->params.width, runner->params.height);
403}
404
405static void _prepareForFrame(struct mGUIRunner* runner) {
406	if (interframeBlending) {
407		glBindFramebuffer(GL_READ_FRAMEBUFFER, copyFbo);
408		glReadBuffer(GL_COLOR_ATTACHMENT0);
409		int width, height;
410		int format;
411		glBindTexture(GL_TEXTURE_2D, tex);
412		glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
413		glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);
414		glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &format);
415		glBindTexture(GL_TEXTURE_2D, oldTex);
416		glCopyTexImage2D(GL_TEXTURE_2D, 0, format, 0, 0, width, height, 0);
417		glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
418	}
419
420	if (usePbo) {
421		glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
422		frameBuffer = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, 256 * 256 * 4, GL_MAP_WRITE_BIT);
423		if (frameBuffer) {
424			runner->core->setVideoBuffer(runner->core, frameBuffer, 256);
425		}
426		glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
427	}
428}
429
430static void _drawFrame(struct mGUIRunner* runner, bool faded) {
431	++framecount;
432	if (!frameLimiter && framecount < framecap) {
433		return;
434	}
435
436	unsigned width, height;
437	runner->core->desiredVideoDimensions(runner->core, &width, &height);
438
439	glActiveTexture(GL_TEXTURE0);
440	if (usePbo) {
441		glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
442		glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
443
444		glBindTexture(GL_TEXTURE_2D, tex);
445		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, height, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
446		glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
447	} else if (!interframeBlending) {
448		glBindTexture(GL_TEXTURE_2D, tex);
449	}
450
451	if (interframeBlending) {
452		glBindTexture(GL_TEXTURE_2D, oldTex);
453		_drawTex(runner, width, height, faded, false);
454		glBindTexture(GL_TEXTURE_2D, tex);
455		_drawTex(runner, width, height, faded, true);
456	} else {
457		_drawTex(runner, width, height, faded, false);
458	}
459
460
461	HidVibrationValue values[4];
462	if (rumble.up) {
463		rumble.value.amp_low = rumble.up / (float) (rumble.up + rumble.down);
464		rumble.value.amp_high = rumble.up / (float) (rumble.up + rumble.down);
465		memcpy(&values[0], &rumble.value, sizeof(rumble.value));
466		memcpy(&values[1], &rumble.value, sizeof(rumble.value));
467		memcpy(&values[2], &rumble.value, sizeof(rumble.value));
468		memcpy(&values[3], &rumble.value, sizeof(rumble.value));
469	} else {
470		memcpy(&values[0], &vibrationStop, sizeof(rumble.value));
471		memcpy(&values[1], &vibrationStop, sizeof(rumble.value));
472		memcpy(&values[2], &vibrationStop, sizeof(rumble.value));
473		memcpy(&values[3], &vibrationStop, sizeof(rumble.value));
474	}
475	hidSendVibrationValues(vibrationDeviceHandles, values, 4);
476	rumble.up = 0;
477	rumble.down = 0;
478}
479
480static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsigned width, unsigned height, bool faded) {
481	glActiveTexture(GL_TEXTURE0);
482	glBindTexture(GL_TEXTURE_2D, tex);
483	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
484
485	_drawTex(runner, width, height, faded, false);
486}
487
488static uint16_t _pollGameInput(struct mGUIRunner* runner) {
489	return _pollInput(&runner->core->inputMap);
490}
491
492static void _incrementScreenMode(struct mGUIRunner* runner) {
493	UNUSED(runner);
494	screenMode = (screenMode + 1) % SM_MAX;
495	mCoreConfigSetUIntValue(&runner->config, "screenMode", screenMode);
496}
497
498static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
499	UNUSED(runner);
500	if (!frameLimiter && limit) {
501		while (enqueuedBuffers > 1) {
502			AudioOutBuffer* releasedBuffers;
503			u32 audoutNReleasedBuffers;
504			audoutWaitPlayFinish(&releasedBuffers, &audoutNReleasedBuffers, 100000000);
505			enqueuedBuffers -= audoutNReleasedBuffers;
506		}
507	}
508	frameLimiter = limit;
509	eglSwapInterval(s_surface, limit);
510}
511
512static bool _running(struct mGUIRunner* runner) {
513	UNUSED(runner);
514	u8 newMode = appletGetOperationMode();
515	if (newMode != vmode) {
516		if (newMode == AppletOperationMode_Docked) {
517			vwidth = 1920;
518			vheight = 1080;
519		} else {
520			vwidth = 1280;
521			vheight = 720;
522		}
523		nwindowSetCrop(nwindowGetDefault(), 0, 0, vwidth, vheight);
524		vmode = newMode;
525	}
526
527	return appletMainLoop();
528}
529
530static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
531	UNUSED(stream);
532	AudioOutBuffer* releasedBuffers;
533	u32 audoutNReleasedBuffers;
534	audoutGetReleasedAudioOutBuffer(&releasedBuffers, &audoutNReleasedBuffers);
535	enqueuedBuffers -= audoutNReleasedBuffers;
536	if (!frameLimiter && enqueuedBuffers >= N_BUFFERS) {
537		blip_clear(left);
538		blip_clear(right);
539		return;
540	}
541	if (enqueuedBuffers >= N_BUFFERS - 1 && R_SUCCEEDED(audoutWaitPlayFinish(&releasedBuffers, &audoutNReleasedBuffers, 10000000))) {
542		enqueuedBuffers -= audoutNReleasedBuffers;
543	}
544	if (enqueuedBuffers >= N_BUFFERS) {
545		blip_clear(left);
546		blip_clear(right);
547		return;
548	}
549
550	struct GBAStereoSample* samples = audioBuffer[audioBufferActive];
551	blip_read_samples(left, &samples[0].left, SAMPLES, true);
552	blip_read_samples(right, &samples[0].right, SAMPLES, true);
553	audoutAppendAudioOutBuffer(&audoutBuffer[audioBufferActive]);
554	audioBufferActive += 1;
555	audioBufferActive %= N_BUFFERS;
556	++enqueuedBuffers;
557}
558
559void _setRumble(struct mRumble* rumble, int enable) {
560	struct mSwitchRumble* sr = (struct mSwitchRumble*) rumble;
561	if (enable) {
562		++sr->up;
563	} else {
564		++sr->down;
565	}
566}
567
568int32_t _readTiltX(struct mRotationSource* source) {
569	UNUSED(source);
570	SixAxisSensorValues sixaxis;
571	hidSixAxisSensorValuesRead(&sixaxis, CONTROLLER_P1_AUTO, 1);
572	return sixaxis.accelerometer.x * 3e8f;
573}
574
575int32_t _readTiltY(struct mRotationSource* source) {
576	UNUSED(source);
577	SixAxisSensorValues sixaxis;
578	hidSixAxisSensorValuesRead(&sixaxis, CONTROLLER_P1_AUTO, 1);
579	return sixaxis.accelerometer.y * -3e8f;
580}
581
582int32_t _readGyroZ(struct mRotationSource* source) {
583	UNUSED(source);
584	SixAxisSensorValues sixaxis;
585	hidSixAxisSensorValuesRead(&sixaxis, CONTROLLER_P1_AUTO, 1);
586	return sixaxis.gyroscope.z * -1.1e9f;
587}
588
589static void _lightSensorSample(struct GBALuminanceSource* lux) {
590	struct mGUIRunnerLux* runnerLux = (struct mGUIRunnerLux*) lux;
591	float luxLevel = 0;
592	appletGetCurrentIlluminance(&luxLevel);
593	runnerLux->luxLevel = cbrtf(luxLevel) * 8;
594}
595
596static uint8_t _lightSensorRead(struct GBALuminanceSource* lux) {
597	struct mGUIRunnerLux* runnerLux = (struct mGUIRunnerLux*) lux;
598	return 0xFF - runnerLux->luxLevel;
599}
600
601static int _batteryState(void) {
602	u32 charge;
603	int state = 0;
604	if (R_SUCCEEDED(psmGetBatteryChargePercentage(&charge))) {
605		state = (charge + 12) / 25;
606	} else {
607		return BATTERY_NOT_PRESENT;
608	}
609	ChargerType type;
610	if (R_SUCCEEDED(psmGetChargerType(&type)) && type) {
611		state |= BATTERY_CHARGING;
612	}
613	return state;
614}
615
616static void _guiPrepare(void) {
617	glViewport(0, 1080 - vheight, vwidth, vheight);
618}
619
620int main(int argc, char* argv[]) {
621	NWindow* window = nwindowGetDefault();
622	nwindowSetDimensions(window, 1920, 1080);
623
624	socketInitializeDefault();
625	nxlinkStdio();
626	initEgl();
627	romfsInit();
628	audoutInitialize();
629	psmInitialize();
630
631	struct GUIFont* font = GUIFontCreate();
632
633	vmode = appletGetOperationMode();
634	if (vmode == AppletOperationMode_Docked) {
635		vwidth = 1920;
636		vheight = 1080;
637	} else {
638		vwidth = 1280;
639		vheight = 720;
640	}
641	nwindowSetCrop(window, 0, 0, vwidth, vheight);
642
643	glViewport(0, 1080 - vheight, vwidth, vheight);
644	glClearColor(0.f, 0.f, 0.f, 1.f);
645
646	glActiveTexture(GL_TEXTURE0);
647	glGenTextures(1, &tex);
648	glBindTexture(GL_TEXTURE_2D, tex);
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	glGenTextures(1, &oldTex);
655	glBindTexture(GL_TEXTURE_2D, oldTex);
656	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
657	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
658	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
659	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
660
661	glGenBuffers(1, &pbo);
662	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
663	glBufferData(GL_PIXEL_UNPACK_BUFFER, 256 * 256 * 4, NULL, GL_STREAM_DRAW);
664	frameBuffer = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, 256 * 256 * 4, GL_MAP_WRITE_BIT);
665	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
666
667	glGenFramebuffers(1, &copyFbo);
668	glBindTexture(GL_TEXTURE_2D, tex);
669	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
670	glBindTexture(GL_TEXTURE_2D, oldTex);
671	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
672	glBindFramebuffer(GL_READ_FRAMEBUFFER, copyFbo);
673	glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
674	glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
675
676	program = glCreateProgram();
677	GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
678	GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
679	const GLchar* shaderBuffer[2];
680
681	shaderBuffer[0] = _gles2Header;
682
683	shaderBuffer[1] = _vertexShader;
684	glShaderSource(vertexShader, 2, shaderBuffer, NULL);
685
686	shaderBuffer[1] = _fragmentShader;
687	glShaderSource(fragmentShader, 2, shaderBuffer, NULL);
688
689	glAttachShader(program, vertexShader);
690	glAttachShader(program, fragmentShader);
691
692	glCompileShader(fragmentShader);
693
694	GLint success;
695	glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
696	if (!success) {
697		GLchar msg[512];
698		glGetShaderInfoLog(fragmentShader, sizeof(msg), NULL, msg);
699		puts(msg);
700	}
701
702	glCompileShader(vertexShader);
703
704	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
705	if (!success) {
706		GLchar msg[512];
707		glGetShaderInfoLog(vertexShader, sizeof(msg), NULL, msg);
708		puts(msg);
709	}
710	glLinkProgram(program);
711
712	glDeleteShader(vertexShader);
713	glDeleteShader(fragmentShader);
714
715	texLocation = glGetUniformLocation(program, "tex");
716	colorLocation = glGetUniformLocation(program, "color");
717	dimsLocation = glGetUniformLocation(program, "dims");
718	insizeLocation = glGetUniformLocation(program, "insize");
719	GLuint offsetLocation = glGetAttribLocation(program, "offset");
720
721	glGenBuffers(1, &vbo);
722	glGenVertexArrays(1, &vao);
723	glBindVertexArray(vao);
724	glBindBuffer(GL_ARRAY_BUFFER, vbo);
725	glBufferData(GL_ARRAY_BUFFER, sizeof(_offsets), _offsets, GL_STATIC_DRAW);
726	glVertexAttribPointer(offsetLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
727	glEnableVertexAttribArray(offsetLocation);
728	glBindVertexArray(0);
729
730	rumble.d.setRumble = _setRumble;
731	rumble.value.freq_low = 120.0;
732	rumble.value.freq_high = 180.0;
733	hidInitializeVibrationDevices(&vibrationDeviceHandles[0], 2, CONTROLLER_HANDHELD, TYPE_HANDHELD | TYPE_JOYCON_PAIR);
734	hidInitializeVibrationDevices(&vibrationDeviceHandles[2], 2, CONTROLLER_PLAYER_1, TYPE_HANDHELD | TYPE_JOYCON_PAIR);
735
736	u32 handles[4];
737	hidGetSixAxisSensorHandles(&handles[0], 2, CONTROLLER_PLAYER_1, TYPE_JOYCON_PAIR);
738	hidGetSixAxisSensorHandles(&handles[2], 1, CONTROLLER_PLAYER_1, TYPE_PROCONTROLLER);
739	hidGetSixAxisSensorHandles(&handles[3], 1, CONTROLLER_HANDHELD, TYPE_HANDHELD);
740	hidStartSixAxisSensor(handles[0]);
741	hidStartSixAxisSensor(handles[1]);
742	hidStartSixAxisSensor(handles[2]);
743	hidStartSixAxisSensor(handles[3]);
744	rotation.readTiltX = _readTiltX;
745	rotation.readTiltY = _readTiltY;
746	rotation.readGyroZ = _readGyroZ;
747
748	lightSensor.d.readLuminance = _lightSensorRead;
749	lightSensor.d.sample = _lightSensorSample;
750
751	stream.videoDimensionsChanged = NULL;
752	stream.postVideoFrame = NULL;
753	stream.postAudioFrame = NULL;
754	stream.postAudioBuffer = _postAudioBuffer;
755
756	memset(audioBuffer, 0, sizeof(audioBuffer));
757	audioBufferActive = 0;
758	enqueuedBuffers = 0;
759	size_t i;
760	for (i = 0; i < N_BUFFERS; ++i) {
761		audoutBuffer[i].next = NULL;
762		audoutBuffer[i].buffer = audioBuffer[i];
763		audoutBuffer[i].buffer_size = BUFFER_SIZE;
764		audoutBuffer[i].data_size = SAMPLES * 4;
765		audoutBuffer[i].data_offset = 0;
766	}
767
768	bool illuminanceAvailable = false;
769	appletIsIlluminanceAvailable(&illuminanceAvailable);
770
771	struct mGUIRunner runner = {
772		.params = {
773			1280, 720,
774			font, "/",
775			_drawStart, _drawEnd,
776			_pollInput, _pollCursor,
777			_batteryState,
778			_guiPrepare, NULL,
779		},
780		.keySources = (struct GUIInputKeys[]) {
781			{
782				.name = "Controller Input",
783				.id = AUTO_INPUT,
784				.keyNames = (const char*[]) {
785					"A",
786					"B",
787					"X",
788					"Y",
789					"L Stick",
790					"R Stick",
791					"L",
792					"R",
793					"ZL",
794					"ZR",
795					"+",
796					"-",
797					"Left",
798					"Up",
799					"Right",
800					"Down",
801					"L Left",
802					"L Up",
803					"L Right",
804					"L Down",
805					"R Left",
806					"R Up",
807					"R Right",
808					"R Down",
809					"SL",
810					"SR"
811				},
812				.nKeys = 26
813			},
814			{ .id = 0 }
815		},
816		.configExtra = (struct GUIMenuItem[]) {
817			{
818				.title = "Screen mode",
819				.data = "screenMode",
820				.submenu = 0,
821				.state = SM_PA,
822				.validStates = (const char*[]) {
823					"Pixel-Accurate",
824					"Aspect-Ratio Fit",
825					"Stretched",
826				},
827				.nStates = 3
828			},
829			{
830				.title = "Fast forward cap",
831				.data = "fastForwardCap",
832				.submenu = 0,
833				.state = 7,
834				.validStates = (const char*[]) {
835					"2", "3", "4", "5", "6", "7", "8", "9",
836					"10", "11", "12", "13", "14", "15",
837					"20", "30"
838				},
839				.stateMappings = (const struct GUIVariant[]) {
840					GUI_V_U(2),
841					GUI_V_U(3),
842					GUI_V_U(4),
843					GUI_V_U(5),
844					GUI_V_U(6),
845					GUI_V_U(7),
846					GUI_V_U(8),
847					GUI_V_U(9),
848					GUI_V_U(10),
849					GUI_V_U(11),
850					GUI_V_U(12),
851					GUI_V_U(13),
852					GUI_V_U(14),
853					GUI_V_U(15),
854					GUI_V_U(20),
855					GUI_V_U(30),
856				},
857				.nStates = 16
858			},
859			{
860				.title = "GPU-accelerated renderer (experimental, requires game reload)",
861				.data = "hwaccelVideo",
862				.submenu = 0,
863				.state = 0,
864				.validStates = (const char*[]) {
865					"Off",
866					"On",
867				},
868				.nStates = 2
869			},
870			{
871				.title = "Hi-res scaling (requires GPU rendering)",
872				.data = "videoScale",
873				.submenu = 0,
874				.state = 0,
875				.validStates = (const char*[]) {
876					"1x",
877					"2x",
878					"3x",
879					"4x",
880					"5x",
881					"6x",
882				},
883				.stateMappings = (const struct GUIVariant[]) {
884					GUI_V_U(1),
885					GUI_V_U(2),
886					GUI_V_U(3),
887					GUI_V_U(4),
888					GUI_V_U(5),
889					GUI_V_U(6),
890				},
891				.nStates = 6
892			},
893			{
894				.title = "Use built-in brightness sensor for Boktai",
895				.data = "useLightSensor",
896				.submenu = 0,
897				.state = illuminanceAvailable,
898				.validStates = (const char*[]) {
899					"Off",
900					"On",
901				},
902				.nStates = 2
903			},
904		},
905		.nConfigExtra = 5,
906		.setup = _setup,
907		.teardown = NULL,
908		.gameLoaded = _gameLoaded,
909		.gameUnloaded = _gameUnloaded,
910		.prepareForFrame = _prepareForFrame,
911		.drawFrame = _drawFrame,
912		.drawScreenshot = _drawScreenshot,
913		.paused = _gameUnloaded,
914		.unpaused = _gameLoaded,
915		.incrementScreenMode = _incrementScreenMode,
916		.setFrameLimiter = _setFrameLimiter,
917		.pollGameInput = _pollGameInput,
918		.running = _running
919	};
920	mGUIInit(&runner, "switch");
921
922	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_A, GUI_INPUT_SELECT);
923	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_B, GUI_INPUT_BACK);
924	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_X, GUI_INPUT_CANCEL);
925	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DUP, GUI_INPUT_UP);
926	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DDOWN, GUI_INPUT_DOWN);
927	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DLEFT, GUI_INPUT_LEFT);
928	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DRIGHT, GUI_INPUT_RIGHT);
929
930	audoutStartAudioOut();
931
932	if (argc > 0) {
933		struct VFile* vf = VFileOpen("romfs:/fileassoc.cfg.in", O_RDONLY);
934		if (vf) {
935			size_t size = vf->size(vf);
936			const char* arg0 = strchr(argv[0], '/');
937			char* buffer[2] = {
938				calloc(size + 1, 1),
939				malloc(size + strlen(arg0) + 1)
940			};
941			vf->read(vf, buffer[0], vf->size(vf));
942			vf->close(vf);
943			snprintf(buffer[1], size + strlen(arg0), buffer[0], arg0);
944			mkdir("sdmc:/config/nx-hbmenu/fileassoc", 0755);
945			vf = VFileOpen("sdmc:/config/nx-hbmenu/fileassoc/mgba.cfg", O_CREAT | O_TRUNC | O_WRONLY);
946			if (vf) {
947				vf->write(vf, buffer[1], strlen(buffer[1]));
948				vf->close(vf);
949			}
950			free(buffer[0]);
951			free(buffer[1]);
952		}
953	}
954
955	if (argc > 1) {
956		size_t i;
957		for (i = 0; runner.keySources[i].id; ++i) {
958			mInputMapLoad(&runner.params.keyMap, runner.keySources[i].id, mCoreConfigGetInput(&runner.config));
959		}
960		mGUIRun(&runner, argv[1]);
961	} else {
962		mGUIRunloop(&runner);
963	}
964
965	mGUIDeinit(&runner);
966
967	audoutStopAudioOut();
968	GUIFontDestroy(font);
969
970	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
971	glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
972	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
973	glDeleteBuffers(1, &pbo);
974
975	glDeleteFramebuffers(1, &copyFbo);
976	glDeleteTextures(1, &tex);
977	glDeleteTextures(1, &oldTex);
978	glDeleteBuffers(1, &vbo);
979	glDeleteProgram(program);
980	glDeleteVertexArrays(1, &vao);
981
982	hidStopSixAxisSensor(handles[0]);
983	hidStopSixAxisSensor(handles[1]);
984	hidStopSixAxisSensor(handles[2]);
985	hidStopSixAxisSensor(handles[3]);
986
987	psmExit();
988	audoutExit();
989	romfsExit();
990	deinitEgl();
991	socketExit();
992	return 0;
993}