all repos — mgba @ ff8f03ab74c20ce387dbbd20a57c317dfd225ab5

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