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