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