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