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