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