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 runner->core->setVideoBuffer(runner->core, frameBuffer, 256);
256 usePbo = true;
257 }
258
259 runner->core->setPeripheral(runner->core, mPERIPH_RUMBLE, &rumble.d);
260 runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation);
261 runner->core->setAVStream(runner->core, &stream);
262
263 unsigned mode;
264 if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
265 screenMode = mode;
266 }
267}
268
269static void _gameLoaded(struct mGUIRunner* runner) {
270 u32 samplerate = audoutGetSampleRate();
271
272 double ratio = GBAAudioCalculateRatio(1, 60.0, 1);
273 blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), samplerate * ratio);
274 blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), samplerate * ratio);
275
276 mCoreConfigGetUIntValue(&runner->config, "fastForwardCap", &framecap);
277
278 unsigned mode;
279 if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
280 screenMode = mode;
281 }
282
283 rumble.up = 0;
284 rumble.down = 0;
285}
286
287static void _gameUnloaded(struct mGUIRunner* runner) {
288 HidVibrationValue values[4];
289 memcpy(&values[0], &vibrationStop, sizeof(rumble.value));
290 memcpy(&values[1], &vibrationStop, sizeof(rumble.value));
291 memcpy(&values[2], &vibrationStop, sizeof(rumble.value));
292 memcpy(&values[3], &vibrationStop, sizeof(rumble.value));
293 hidSendVibrationValues(vibrationDeviceHandles, values, 4);
294}
295
296static void _drawTex(struct mGUIRunner* runner, unsigned width, unsigned height, bool faded) {
297 glViewport(0, 1080 - vheight, vwidth, vheight);
298 glEnable(GL_BLEND);
299 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
300
301 glUseProgram(program);
302 glBindVertexArray(vao);
303 float aspectX = width / (float) vwidth;
304 float aspectY = height / (float) vheight;
305 float max = 1.f;
306 switch (screenMode) {
307 case SM_PA:
308 if (aspectX > aspectY) {
309 max = floor(1.0 / aspectX);
310 } else {
311 max = floor(1.0 / aspectY);
312 }
313 break;
314 case SM_AF:
315 if (aspectX > aspectY) {
316 max = 1.0 / aspectX;
317 } else {
318 max = 1.0 / aspectY;
319 }
320 break;
321 case SM_SF:
322 aspectX = 1.0;
323 aspectY = 1.0;
324 break;
325 }
326
327 aspectX *= max;
328 aspectY *= max;
329
330 glUniform1i(texLocation, 0);
331 glUniform2f(dimsLocation, aspectX, aspectY);
332 if (usePbo) {
333 glUniform2f(insizeLocation, width / 256.f, height / 256.f);
334 } else {
335 glUniform2f(insizeLocation, 1, 1);
336 }
337 if (!faded) {
338 glUniform4f(colorLocation, 1.0f, 1.0f, 1.0f, 1.0f);
339 } else {
340 glUniform4f(colorLocation, 0.8f, 0.8f, 0.8f, 0.8f);
341 }
342
343 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
344
345 glBindVertexArray(0);
346 glUseProgram(0);
347 glDisable(GL_BLEND);
348 glViewport(0, 1080 - runner->params.height, runner->params.width, runner->params.height);
349}
350
351static void _prepareForFrame(struct mGUIRunner* runner) {
352 if (usePbo) {
353 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
354 frameBuffer = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, 256 * 256 * 4, GL_MAP_WRITE_BIT);
355 if (frameBuffer) {
356 runner->core->setVideoBuffer(runner->core, frameBuffer, 256);
357 }
358 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
359 }
360}
361
362static void _drawFrame(struct mGUIRunner* runner, bool faded) {
363 ++framecount;
364 if (!frameLimiter && framecount < framecap) {
365 return;
366 }
367
368 unsigned width, height;
369 runner->core->desiredVideoDimensions(runner->core, &width, &height);
370
371 glActiveTexture(GL_TEXTURE0);
372 if (usePbo) {
373 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
374 glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
375
376 glBindTexture(GL_TEXTURE_2D, tex);
377 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, height, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
378 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
379 } else {
380 glBindTexture(GL_TEXTURE_2D, tex);
381 }
382
383 _drawTex(runner, width, height, faded);
384
385 HidVibrationValue values[4];
386 if (rumble.up) {
387 rumble.value.amp_low = rumble.up / (float) (rumble.up + rumble.down);
388 rumble.value.amp_high = rumble.up / (float) (rumble.up + rumble.down);
389 memcpy(&values[0], &rumble.value, sizeof(rumble.value));
390 memcpy(&values[1], &rumble.value, sizeof(rumble.value));
391 memcpy(&values[2], &rumble.value, sizeof(rumble.value));
392 memcpy(&values[3], &rumble.value, sizeof(rumble.value));
393 } else {
394 memcpy(&values[0], &vibrationStop, sizeof(rumble.value));
395 memcpy(&values[1], &vibrationStop, sizeof(rumble.value));
396 memcpy(&values[2], &vibrationStop, sizeof(rumble.value));
397 memcpy(&values[3], &vibrationStop, sizeof(rumble.value));
398 }
399 hidSendVibrationValues(vibrationDeviceHandles, values, 4);
400 rumble.up = 0;
401 rumble.down = 0;
402}
403
404static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsigned width, unsigned height, bool faded) {
405 glActiveTexture(GL_TEXTURE0);
406 glBindTexture(GL_TEXTURE_2D, tex);
407 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
408
409 _drawTex(runner, width, height, faded);
410}
411
412static uint16_t _pollGameInput(struct mGUIRunner* runner) {
413 return _pollInput(&runner->core->inputMap);
414}
415
416static void _incrementScreenMode(struct mGUIRunner* runner) {
417 UNUSED(runner);
418 screenMode = (screenMode + 1) % SM_MAX;
419 mCoreConfigSetUIntValue(&runner->config, "screenMode", screenMode);
420}
421
422static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
423 UNUSED(runner);
424 if (!frameLimiter && limit) {
425 while (enqueuedBuffers > 1) {
426 AudioOutBuffer* releasedBuffers;
427 u32 audoutNReleasedBuffers;
428 audoutWaitPlayFinish(&releasedBuffers, &audoutNReleasedBuffers, 100000000);
429 enqueuedBuffers -= audoutNReleasedBuffers;
430 }
431 }
432 frameLimiter = limit;
433 eglSwapInterval(s_surface, limit);
434}
435
436static bool _running(struct mGUIRunner* runner) {
437 UNUSED(runner);
438 u8 newMode = appletGetOperationMode();
439 if (newMode != vmode) {
440 if (newMode == AppletOperationMode_Docked) {
441 vwidth = 1920;
442 vheight = 1080;
443 } else {
444 vwidth = 1280;
445 vheight = 720;
446 }
447 nwindowSetCrop(nwindowGetDefault(), 0, 0, vwidth, vheight);
448 vmode = newMode;
449 }
450
451 return appletMainLoop();
452}
453
454static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
455 UNUSED(stream);
456 AudioOutBuffer* releasedBuffers;
457 u32 audoutNReleasedBuffers;
458 audoutGetReleasedAudioOutBuffer(&releasedBuffers, &audoutNReleasedBuffers);
459 enqueuedBuffers -= audoutNReleasedBuffers;
460 if (!frameLimiter && enqueuedBuffers >= N_BUFFERS) {
461 blip_clear(left);
462 blip_clear(right);
463 return;
464 }
465 if (enqueuedBuffers >= N_BUFFERS - 1 && R_SUCCEEDED(audoutWaitPlayFinish(&releasedBuffers, &audoutNReleasedBuffers, 10000000))) {
466 enqueuedBuffers -= audoutNReleasedBuffers;
467 }
468
469 struct GBAStereoSample* samples = audioBuffer[audioBufferActive];
470 blip_read_samples(left, &samples[0].left, SAMPLES, true);
471 blip_read_samples(right, &samples[0].right, SAMPLES, true);
472 audoutAppendAudioOutBuffer(&audoutBuffer[audioBufferActive]);
473 audioBufferActive += 1;
474 audioBufferActive %= N_BUFFERS;
475 ++enqueuedBuffers;
476}
477
478void _setRumble(struct mRumble* rumble, int enable) {
479 struct mSwitchRumble* sr = (struct mSwitchRumble*) rumble;
480 if (enable) {
481 ++sr->up;
482 } else {
483 ++sr->down;
484 }
485}
486
487int32_t _readTiltX(struct mRotationSource* source) {
488 UNUSED(source);
489 SixAxisSensorValues sixaxis;
490 hidSixAxisSensorValuesRead(&sixaxis, CONTROLLER_P1_AUTO, 1);
491 return sixaxis.accelerometer.x * 3e8f;
492}
493
494int32_t _readTiltY(struct mRotationSource* source) {
495 UNUSED(source);
496 SixAxisSensorValues sixaxis;
497 hidSixAxisSensorValuesRead(&sixaxis, CONTROLLER_P1_AUTO, 1);
498 return sixaxis.accelerometer.y * -3e8f;
499}
500
501int32_t _readGyroZ(struct mRotationSource* source) {
502 UNUSED(source);
503 SixAxisSensorValues sixaxis;
504 hidSixAxisSensorValuesRead(&sixaxis, CONTROLLER_P1_AUTO, 1);
505 return sixaxis.gyroscope.z * -1.1e9f;
506}
507
508static int _batteryState(void) {
509 u32 charge;
510 int state = 0;
511 if (R_SUCCEEDED(psmGetBatteryChargePercentage(&charge))) {
512 state = (charge + 12) / 25;
513 } else {
514 return BATTERY_NOT_PRESENT;
515 }
516 ChargerType type;
517 if (R_SUCCEEDED(psmGetChargerType(&type)) && type) {
518 state |= BATTERY_CHARGING;
519 }
520 return state;
521}
522
523static void _guiPrepare(void) {
524 glViewport(0, 1080 - vheight, vwidth, vheight);
525}
526
527int main(int argc, char* argv[]) {
528 NWindow* window = nwindowGetDefault();
529 nwindowSetDimensions(window, 1920, 1080);
530
531 socketInitializeDefault();
532 nxlinkStdio();
533 initEgl();
534 romfsInit();
535 audoutInitialize();
536 psmInitialize();
537
538 struct GUIFont* font = GUIFontCreate();
539
540 vmode = appletGetOperationMode();
541 if (vmode == AppletOperationMode_Docked) {
542 vwidth = 1920;
543 vheight = 1080;
544 } else {
545 vwidth = 1280;
546 vheight = 720;
547 }
548 nwindowSetCrop(window, 0, 0, vwidth, vheight);
549
550 glViewport(0, 1080 - vheight, vwidth, vheight);
551 glClearColor(0.f, 0.f, 0.f, 1.f);
552
553 glGenTextures(1, &tex);
554 glActiveTexture(GL_TEXTURE0);
555 glBindTexture(GL_TEXTURE_2D, tex);
556 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
557 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
558 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
559 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
560 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
561
562 glGenBuffers(1, &pbo);
563 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
564 glBufferData(GL_PIXEL_UNPACK_BUFFER, 256 * 256 * 4, NULL, GL_STREAM_DRAW);
565 frameBuffer = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, 256 * 256 * 4, GL_MAP_WRITE_BIT);
566 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
567
568 program = glCreateProgram();
569 GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
570 GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
571 const GLchar* shaderBuffer[2];
572
573 shaderBuffer[0] = _gles2Header;
574
575 shaderBuffer[1] = _vertexShader;
576 glShaderSource(vertexShader, 2, shaderBuffer, NULL);
577
578 shaderBuffer[1] = _fragmentShader;
579 glShaderSource(fragmentShader, 2, shaderBuffer, NULL);
580
581 glAttachShader(program, vertexShader);
582 glAttachShader(program, fragmentShader);
583
584 glCompileShader(fragmentShader);
585
586 GLint success;
587 glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
588 if (!success) {
589 GLchar msg[512];
590 glGetShaderInfoLog(fragmentShader, sizeof(msg), NULL, msg);
591 puts(msg);
592 }
593
594 glCompileShader(vertexShader);
595
596 glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
597 if (!success) {
598 GLchar msg[512];
599 glGetShaderInfoLog(vertexShader, sizeof(msg), NULL, msg);
600 puts(msg);
601 }
602 glLinkProgram(program);
603
604 glDeleteShader(vertexShader);
605 glDeleteShader(fragmentShader);
606
607 texLocation = glGetUniformLocation(program, "tex");
608 colorLocation = glGetUniformLocation(program, "color");
609 dimsLocation = glGetUniformLocation(program, "dims");
610 insizeLocation = glGetUniformLocation(program, "insize");
611 GLuint offsetLocation = glGetAttribLocation(program, "offset");
612
613 glGenBuffers(1, &vbo);
614 glGenVertexArrays(1, &vao);
615 glBindVertexArray(vao);
616 glBindBuffer(GL_ARRAY_BUFFER, vbo);
617 glBufferData(GL_ARRAY_BUFFER, sizeof(_offsets), _offsets, GL_STATIC_DRAW);
618 glVertexAttribPointer(offsetLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
619 glEnableVertexAttribArray(offsetLocation);
620 glBindVertexArray(0);
621
622 rumble.d.setRumble = _setRumble;
623 rumble.value.freq_low = 120.0;
624 rumble.value.freq_high = 180.0;
625 hidInitializeVibrationDevices(&vibrationDeviceHandles[0], 2, CONTROLLER_HANDHELD, TYPE_HANDHELD | TYPE_JOYCON_PAIR);
626 hidInitializeVibrationDevices(&vibrationDeviceHandles[2], 2, CONTROLLER_PLAYER_1, TYPE_HANDHELD | TYPE_JOYCON_PAIR);
627
628 u32 handles[4];
629 hidGetSixAxisSensorHandles(&handles[0], 2, CONTROLLER_PLAYER_1, TYPE_JOYCON_PAIR);
630 hidGetSixAxisSensorHandles(&handles[2], 1, CONTROLLER_PLAYER_1, TYPE_PROCONTROLLER);
631 hidGetSixAxisSensorHandles(&handles[3], 1, CONTROLLER_HANDHELD, TYPE_HANDHELD);
632 hidStartSixAxisSensor(handles[0]);
633 hidStartSixAxisSensor(handles[1]);
634 hidStartSixAxisSensor(handles[2]);
635 hidStartSixAxisSensor(handles[3]);
636 rotation.readTiltX = _readTiltX;
637 rotation.readTiltY = _readTiltY;
638 rotation.readGyroZ = _readGyroZ;
639
640 stream.videoDimensionsChanged = NULL;
641 stream.postVideoFrame = NULL;
642 stream.postAudioFrame = NULL;
643 stream.postAudioBuffer = _postAudioBuffer;
644
645 memset(audioBuffer, 0, sizeof(audioBuffer));
646 audioBufferActive = 0;
647 enqueuedBuffers = 0;
648 size_t i;
649 for (i = 0; i < N_BUFFERS; ++i) {
650 audoutBuffer[i].next = NULL;
651 audoutBuffer[i].buffer = audioBuffer[i];
652 audoutBuffer[i].buffer_size = BUFFER_SIZE;
653 audoutBuffer[i].data_size = BUFFER_SIZE;
654 audoutBuffer[i].data_offset = 0;
655 }
656
657 struct mGUIRunner runner = {
658 .params = {
659 1280, 720,
660 font, "/",
661 _drawStart, _drawEnd,
662 _pollInput, _pollCursor,
663 _batteryState,
664 _guiPrepare, NULL,
665 },
666 .keySources = (struct GUIInputKeys[]) {
667 {
668 .name = "Controller Input",
669 .id = AUTO_INPUT,
670 .keyNames = (const char*[]) {
671 "A",
672 "B",
673 "X",
674 "Y",
675 "L Stick",
676 "R Stick",
677 "L",
678 "R",
679 "ZL",
680 "ZR",
681 "+",
682 "-",
683 "Left",
684 "Up",
685 "Right",
686 "Down",
687 "L Left",
688 "L Up",
689 "L Right",
690 "L Down",
691 "R Left",
692 "R Up",
693 "R Right",
694 "R Down",
695 "SL",
696 "SR"
697 },
698 .nKeys = 26
699 },
700 { .id = 0 }
701 },
702 .configExtra = (struct GUIMenuItem[]) {
703 {
704 .title = "Screen mode",
705 .data = "screenMode",
706 .submenu = 0,
707 .state = SM_PA,
708 .validStates = (const char*[]) {
709 "Pixel-Accurate",
710 "Aspect-Ratio Fit",
711 "Stretched",
712 },
713 .nStates = 3
714 },
715 {
716 .title = "Fast forward cap",
717 .data = "fastForwardCap",
718 .submenu = 0,
719 .state = 7,
720 .validStates = (const char*[]) {
721 "2", "3", "4", "5", "6", "7", "8", "9",
722 "10", "11", "12", "13", "14", "15",
723 "20", "30"
724 },
725 .stateMappings = (const struct GUIVariant[]) {
726 GUI_V_U(2),
727 GUI_V_U(3),
728 GUI_V_U(4),
729 GUI_V_U(5),
730 GUI_V_U(6),
731 GUI_V_U(7),
732 GUI_V_U(8),
733 GUI_V_U(9),
734 GUI_V_U(10),
735 GUI_V_U(11),
736 GUI_V_U(12),
737 GUI_V_U(13),
738 GUI_V_U(14),
739 GUI_V_U(15),
740 GUI_V_U(20),
741 GUI_V_U(30),
742 },
743 .nStates = 16
744 },
745 {
746 .title = "GPU-accelerated renderer (experimental, requires game reload)",
747 .data = "hwaccelVideo",
748 .submenu = 0,
749 .state = 0,
750 .validStates = (const char*[]) {
751 "Off",
752 "On",
753 },
754 .nStates = 2
755 },
756 {
757 .title = "Hi-res scaling (requires GPU rendering)",
758 .data = "videoScale",
759 .submenu = 0,
760 .state = 0,
761 .validStates = (const char*[]) {
762 "1x",
763 "2x",
764 "3x",
765 "4x",
766 "5x",
767 "6x",
768 },
769 .stateMappings = (const struct GUIVariant[]) {
770 GUI_V_U(1),
771 GUI_V_U(2),
772 GUI_V_U(3),
773 GUI_V_U(4),
774 GUI_V_U(5),
775 GUI_V_U(6),
776 },
777 .nStates = 6
778 },
779 },
780 .nConfigExtra = 4,
781 .setup = _setup,
782 .teardown = NULL,
783 .gameLoaded = _gameLoaded,
784 .gameUnloaded = _gameUnloaded,
785 .prepareForFrame = _prepareForFrame,
786 .drawFrame = _drawFrame,
787 .drawScreenshot = _drawScreenshot,
788 .paused = _gameUnloaded,
789 .unpaused = _gameLoaded,
790 .incrementScreenMode = _incrementScreenMode,
791 .setFrameLimiter = _setFrameLimiter,
792 .pollGameInput = _pollGameInput,
793 .running = _running
794 };
795 mGUIInit(&runner, "switch");
796
797 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_A, GUI_INPUT_SELECT);
798 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_B, GUI_INPUT_BACK);
799 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_X, GUI_INPUT_CANCEL);
800 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DUP, GUI_INPUT_UP);
801 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DDOWN, GUI_INPUT_DOWN);
802 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DLEFT, GUI_INPUT_LEFT);
803 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DRIGHT, GUI_INPUT_RIGHT);
804
805 audoutStartAudioOut();
806
807 if (argc > 1) {
808 size_t i;
809 for (i = 0; runner.keySources[i].id; ++i) {
810 mInputMapLoad(&runner.params.keyMap, runner.keySources[i].id, mCoreConfigGetInput(&runner.config));
811 }
812 mGUIRun(&runner, argv[1]);
813 } else {
814 mGUIRunloop(&runner);
815 }
816
817 mGUIDeinit(&runner);
818
819 audoutStopAudioOut();
820 GUIFontDestroy(font);
821
822 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
823 glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
824 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
825 glDeleteBuffers(1, &pbo);
826
827 glDeleteTextures(1, &tex);
828 glDeleteBuffers(1, &vbo);
829 glDeleteProgram(program);
830 glDeleteVertexArrays(1, &vao);
831
832 hidStopSixAxisSensor(handles[0]);
833 hidStopSixAxisSensor(handles[1]);
834 hidStopSixAxisSensor(handles[2]);
835 hidStopSixAxisSensor(handles[3]);
836
837 psmExit();
838 audoutExit();
839 romfsExit();
840 deinitEgl();
841 socketExit();
842 return 0;
843}