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