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, ©Fbo);
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, ©Fbo);
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}