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