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