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 / 256.0;\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 int audioBufferActive;
85static struct GBAStereoSample audioBuffer[N_BUFFERS][SAMPLES] __attribute__((__aligned__(0x1000)));
86static AudioOutBuffer audoutBuffer[N_BUFFERS];
87static int enqueuedBuffers;
88static bool frameLimiter = true;
89static unsigned framecount = 0;
90static unsigned framecap = 10;
91static u32 vibrationDeviceHandles[4];
92static HidVibrationValue vibrationStop = { .freq_low = 160.f, .freq_high = 320.f };
93
94static bool initEgl() {
95 s_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
96 if (!s_display) {
97 goto _fail0;
98 }
99
100 eglInitialize(s_display, NULL, NULL);
101
102 EGLConfig config;
103 EGLint numConfigs;
104 static const EGLint attributeList[] = {
105 EGL_RED_SIZE, 1,
106 EGL_GREEN_SIZE, 1,
107 EGL_BLUE_SIZE, 1,
108 EGL_NONE
109 };
110 eglChooseConfig(s_display, attributeList, &config, 1, &numConfigs);
111 if (!numConfigs) {
112 goto _fail1;
113 }
114
115 s_surface = eglCreateWindowSurface(s_display, config, "", NULL);
116 if (!s_surface) {
117 goto _fail1;
118 }
119
120 EGLint contextAttributeList[] = {
121 EGL_CONTEXT_CLIENT_VERSION, 3,
122 EGL_NONE
123 };
124 s_context = eglCreateContext(s_display, config, EGL_NO_CONTEXT, contextAttributeList);
125 if (!s_context) {
126 goto _fail2;
127 }
128
129 eglMakeCurrent(s_display, s_surface, s_surface, s_context);
130 return true;
131
132_fail2:
133 eglDestroySurface(s_display, s_surface);
134 s_surface = NULL;
135_fail1:
136 eglTerminate(s_display);
137 s_display = NULL;
138_fail0:
139 return false;
140}
141
142static void deinitEgl() {
143 if (s_display) {
144 if (s_context) {
145 eglDestroyContext(s_display, s_context);
146 }
147 if (s_surface) {
148 eglDestroySurface(s_display, s_surface);
149 }
150 eglTerminate(s_display);
151 }
152}
153
154static void _mapKey(struct mInputMap* map, uint32_t binding, int nativeKey, enum GBAKey key) {
155 mInputBindKey(map, binding, __builtin_ctz(nativeKey), key);
156}
157
158static void _drawStart(void) {
159 glClear(GL_COLOR_BUFFER_BIT);
160}
161
162static void _drawEnd(void) {
163 if (frameLimiter || framecount >= framecap) {
164 eglSwapBuffers(s_display, s_surface);
165 framecount = 0;
166 }
167}
168
169static uint32_t _pollInput(const struct mInputMap* map) {
170 int keys = 0;
171 hidScanInput();
172 u32 padkeys = hidKeysHeld(CONTROLLER_P1_AUTO);
173 keys |= mInputMapKeyBits(map, AUTO_INPUT, padkeys, 0);
174
175 JoystickPosition jspos;
176 hidJoystickRead(&jspos, CONTROLLER_P1_AUTO, JOYSTICK_LEFT);
177
178 int l = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_LSTICK_LEFT));
179 int r = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_LSTICK_RIGHT));
180 int u = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_LSTICK_UP));
181 int d = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_LSTICK_DOWN));
182
183 if (l == -1) {
184 l = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_DLEFT));
185 }
186 if (r == -1) {
187 r = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_DRIGHT));
188 }
189 if (u == -1) {
190 u = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_DUP));
191 }
192 if (d == -1) {
193 d = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_DDOWN));
194 }
195
196 if (jspos.dx < -ANALOG_DEADZONE && l != -1) {
197 keys |= 1 << l;
198 }
199 if (jspos.dx > ANALOG_DEADZONE && r != -1) {
200 keys |= 1 << r;
201 }
202 if (jspos.dy < -ANALOG_DEADZONE && d != -1) {
203 keys |= 1 << d;
204 }
205 if (jspos.dy > ANALOG_DEADZONE && u != -1) {
206 keys |= 1 << u;
207 }
208 return keys;
209}
210
211static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
212 hidScanInput();
213 if (hidTouchCount() < 1) {
214 return GUI_CURSOR_NOT_PRESENT;
215 }
216 touchPosition touch;
217 hidTouchRead(&touch, 0);
218 *x = touch.px;
219 *y = touch.py;
220 return GUI_CURSOR_DOWN;
221}
222
223
224static void _setup(struct mGUIRunner* runner) {
225 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_A, GBA_KEY_A);
226 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_B, GBA_KEY_B);
227 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_PLUS, GBA_KEY_START);
228 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_MINUS, GBA_KEY_SELECT);
229 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DUP, GBA_KEY_UP);
230 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DDOWN, GBA_KEY_DOWN);
231 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DLEFT, GBA_KEY_LEFT);
232 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DRIGHT, GBA_KEY_RIGHT);
233 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_L, GBA_KEY_L);
234 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_R, GBA_KEY_R);
235
236 runner->core->setVideoBuffer(runner->core, frameBuffer, 256);
237 runner->core->setPeripheral(runner->core, mPERIPH_RUMBLE, &rumble.d);
238 runner->core->setAVStream(runner->core, &stream);
239}
240
241static void _gameLoaded(struct mGUIRunner* runner) {
242 u32 samplerate = audoutGetSampleRate();
243
244 double ratio = GBAAudioCalculateRatio(1, 60.0, 1);
245 blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), samplerate * ratio);
246 blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), samplerate * ratio);
247
248 mCoreConfigGetUIntValue(&runner->config, "fastForwardCap", &framecap);
249
250 rumble.up = 0;
251 rumble.down = 0;
252}
253
254static void _gameUnloaded(struct mGUIRunner* runner) {
255 HidVibrationValue values[4];
256 memcpy(&values[0], &vibrationStop, sizeof(rumble.value));
257 memcpy(&values[1], &vibrationStop, sizeof(rumble.value));
258 memcpy(&values[2], &vibrationStop, sizeof(rumble.value));
259 memcpy(&values[3], &vibrationStop, sizeof(rumble.value));
260 hidSendVibrationValues(vibrationDeviceHandles, values, 4);
261}
262
263static void _drawTex(struct mGUIRunner* runner, unsigned width, unsigned height, bool faded) {
264 glEnable(GL_BLEND);
265 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
266
267 glUseProgram(program);
268 glBindVertexArray(vao);
269 float aspectX = width / (float) runner->params.width;
270 float aspectY = height / (float) runner->params.height;
271 float max;
272 if (aspectX > aspectY) {
273 max = floor(1.0 / aspectX);
274 } else {
275 max = floor(1.0 / aspectY);
276 }
277
278 aspectX *= max;
279 aspectY *= max;
280
281 glUniform1i(texLocation, 0);
282 glUniform2f(dimsLocation, aspectX, aspectY);
283 glUniform2f(insizeLocation, width, height);
284 if (!faded) {
285 glUniform4f(colorLocation, 1.0f, 1.0f, 1.0f, 1.0f);
286 } else {
287 glUniform4f(colorLocation, 0.8f, 0.8f, 0.8f, 0.8f);
288 }
289
290 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
291
292 glBindVertexArray(0);
293 glUseProgram(0);
294}
295
296static void _prepareForFrame(struct mGUIRunner* runner) {
297 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
298 frameBuffer = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, 256 * 256 * 4, GL_MAP_WRITE_BIT);
299 if (frameBuffer) {
300 runner->core->setVideoBuffer(runner->core, frameBuffer, 256);
301 }
302 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
303}
304
305static void _drawFrame(struct mGUIRunner* runner, bool faded) {
306 ++framecount;
307 if (!frameLimiter && framecount < framecap) {
308 return;
309 }
310
311 unsigned width, height;
312 runner->core->desiredVideoDimensions(runner->core, &width, &height);
313
314 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
315 glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
316
317 glActiveTexture(GL_TEXTURE0);
318 glBindTexture(GL_TEXTURE_2D, tex);
319 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, height, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
320 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
321
322 _drawTex(runner, width, height, faded);
323
324 HidVibrationValue values[4];
325 if (rumble.up) {
326 rumble.value.amp_low = rumble.up / (float) (rumble.up + rumble.down);
327 rumble.value.amp_high = rumble.up / (float) (rumble.up + rumble.down);
328 memcpy(&values[0], &rumble.value, sizeof(rumble.value));
329 memcpy(&values[1], &rumble.value, sizeof(rumble.value));
330 memcpy(&values[2], &rumble.value, sizeof(rumble.value));
331 memcpy(&values[3], &rumble.value, sizeof(rumble.value));
332 } else {
333 memcpy(&values[0], &vibrationStop, sizeof(rumble.value));
334 memcpy(&values[1], &vibrationStop, sizeof(rumble.value));
335 memcpy(&values[2], &vibrationStop, sizeof(rumble.value));
336 memcpy(&values[3], &vibrationStop, sizeof(rumble.value));
337 }
338 hidSendVibrationValues(vibrationDeviceHandles, values, 4);
339 rumble.up = 0;
340 rumble.down = 0;
341}
342
343static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsigned width, unsigned height, bool faded) {
344 glActiveTexture(GL_TEXTURE0);
345 glBindTexture(GL_TEXTURE_2D, tex);
346 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
347
348 _drawTex(runner, width, height, faded);
349}
350
351static uint16_t _pollGameInput(struct mGUIRunner* runner) {
352 return _pollInput(&runner->core->inputMap);
353}
354
355static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
356 UNUSED(runner);
357 if (!frameLimiter && limit) {
358 while (enqueuedBuffers > 1) {
359 AudioOutBuffer* releasedBuffers;
360 u32 audoutNReleasedBuffers;
361 audoutWaitPlayFinish(&releasedBuffers, &audoutNReleasedBuffers, 100000000);
362 enqueuedBuffers -= audoutNReleasedBuffers;
363 }
364 }
365 frameLimiter = limit;
366}
367
368static bool _running(struct mGUIRunner* runner) {
369 UNUSED(runner);
370 return appletMainLoop();
371}
372
373static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
374 UNUSED(stream);
375 AudioOutBuffer* releasedBuffers;
376 u32 audoutNReleasedBuffers;
377 audoutGetReleasedAudioOutBuffer(&releasedBuffers, &audoutNReleasedBuffers);
378 enqueuedBuffers -= audoutNReleasedBuffers;
379 if (!frameLimiter && enqueuedBuffers >= N_BUFFERS) {
380 blip_clear(left);
381 blip_clear(right);
382 return;
383 }
384 if (enqueuedBuffers >= N_BUFFERS - 1 && R_SUCCEEDED(audoutWaitPlayFinish(&releasedBuffers, &audoutNReleasedBuffers, 10000000))) {
385 enqueuedBuffers -= audoutNReleasedBuffers;
386 }
387
388 struct GBAStereoSample* samples = audioBuffer[audioBufferActive];
389 blip_read_samples(left, &samples[0].left, SAMPLES, true);
390 blip_read_samples(right, &samples[0].right, SAMPLES, true);
391 audoutAppendAudioOutBuffer(&audoutBuffer[audioBufferActive]);
392 audioBufferActive += 1;
393 audioBufferActive %= N_BUFFERS;
394 ++enqueuedBuffers;
395}
396
397void _setRumble(struct mRumble* rumble, int enable) {
398 struct mSwitchRumble* sr = (struct mSwitchRumble*) rumble;
399 if (enable) {
400 ++sr->up;
401 } else {
402 ++sr->down;
403 }
404}
405
406static int _batteryState(void) {
407 u32 charge;
408 int state = 0;
409 if (R_SUCCEEDED(psmGetBatteryChargePercentage(&charge))) {
410 state = (charge + 12) / 25;
411 } else {
412 return BATTERY_NOT_PRESENT;
413 }
414 ChargerType type;
415 if (R_SUCCEEDED(psmGetChargerType(&type)) && type) {
416 state |= BATTERY_CHARGING;
417 }
418 return state;
419}
420
421int main(int argc, char* argv[]) {
422 socketInitializeDefault();
423 nxlinkStdio();
424 initEgl();
425 romfsInit();
426 audoutInitialize();
427 psmInitialize();
428
429 struct GUIFont* font = GUIFontCreate();
430
431 u32 width = 1280;
432 u32 height = 720;
433
434 glViewport(0, 0, width, height);
435 glClearColor(0.f, 0.f, 0.f, 1.f);
436
437 glGenTextures(1, &tex);
438 glActiveTexture(GL_TEXTURE0);
439 glBindTexture(GL_TEXTURE_2D, tex);
440 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
441 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
442 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
443 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
444 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
445
446 glGenBuffers(1, &pbo);
447 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
448 glBufferData(GL_PIXEL_UNPACK_BUFFER, 256 * 256 * 4, NULL, GL_STREAM_DRAW);
449 frameBuffer = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, 256 * 256 * 4, GL_MAP_WRITE_BIT);
450 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
451
452 program = glCreateProgram();
453 GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
454 GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
455 const GLchar* shaderBuffer[2];
456
457 shaderBuffer[0] = _gles2Header;
458
459 shaderBuffer[1] = _vertexShader;
460 glShaderSource(vertexShader, 2, shaderBuffer, NULL);
461
462 shaderBuffer[1] = _fragmentShader;
463 glShaderSource(fragmentShader, 2, shaderBuffer, NULL);
464
465 glAttachShader(program, vertexShader);
466 glAttachShader(program, fragmentShader);
467
468 glCompileShader(fragmentShader);
469
470 GLint success;
471 glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
472 if (!success) {
473 GLchar msg[512];
474 glGetShaderInfoLog(fragmentShader, sizeof(msg), NULL, msg);
475 puts(msg);
476 }
477
478 glCompileShader(vertexShader);
479
480 glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
481 if (!success) {
482 GLchar msg[512];
483 glGetShaderInfoLog(vertexShader, sizeof(msg), NULL, msg);
484 puts(msg);
485 }
486 glLinkProgram(program);
487
488 glDeleteShader(vertexShader);
489 glDeleteShader(fragmentShader);
490
491 texLocation = glGetUniformLocation(program, "tex");
492 colorLocation = glGetUniformLocation(program, "color");
493 dimsLocation = glGetUniformLocation(program, "dims");
494 insizeLocation = glGetUniformLocation(program, "insize");
495 GLuint offsetLocation = glGetAttribLocation(program, "offset");
496
497 glGenBuffers(1, &vbo);
498 glGenVertexArrays(1, &vao);
499 glBindVertexArray(vao);
500 glBindBuffer(GL_ARRAY_BUFFER, vbo);
501 glBufferData(GL_ARRAY_BUFFER, sizeof(_offsets), _offsets, GL_STATIC_DRAW);
502 glVertexAttribPointer(offsetLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
503 glEnableVertexAttribArray(offsetLocation);
504 glBindVertexArray(0);
505
506 rumble.d.setRumble = _setRumble;
507 rumble.value.freq_low = 120.0;
508 rumble.value.freq_high = 180.0;
509 hidInitializeVibrationDevices(&vibrationDeviceHandles[0], 2, CONTROLLER_HANDHELD, TYPE_HANDHELD | TYPE_JOYCON_PAIR);
510 hidInitializeVibrationDevices(&vibrationDeviceHandles[2], 2, CONTROLLER_PLAYER_1, TYPE_HANDHELD | TYPE_JOYCON_PAIR);
511
512 stream.videoDimensionsChanged = NULL;
513 stream.postVideoFrame = NULL;
514 stream.postAudioFrame = NULL;
515 stream.postAudioBuffer = _postAudioBuffer;
516
517 memset(audioBuffer, 0, sizeof(audioBuffer));
518 audioBufferActive = 0;
519 enqueuedBuffers = 0;
520 size_t i;
521 for (i = 0; i < N_BUFFERS; ++i) {
522 audoutBuffer[i].next = NULL;
523 audoutBuffer[i].buffer = audioBuffer[i];
524 audoutBuffer[i].buffer_size = BUFFER_SIZE;
525 audoutBuffer[i].data_size = BUFFER_SIZE;
526 audoutBuffer[i].data_offset = 0;
527 }
528
529 struct mGUIRunner runner = {
530 .params = {
531 width, height,
532 font, "/",
533 _drawStart, _drawEnd,
534 _pollInput, _pollCursor,
535 _batteryState,
536 NULL, NULL,
537 },
538 .keySources = (struct GUIInputKeys[]) {
539 {
540 .name = "Controller Input",
541 .id = AUTO_INPUT,
542 .keyNames = (const char*[]) {
543 "A",
544 "B",
545 "X",
546 "Y",
547 "L Stick",
548 "R Stick",
549 "L",
550 "R",
551 "ZL",
552 "ZR",
553 "+",
554 "-",
555 "Left",
556 "Up",
557 "Right",
558 "Down",
559 "L Left",
560 "L Up",
561 "L Right",
562 "L Down",
563 "R Left",
564 "R Up",
565 "R Right",
566 "R Down",
567 "SL",
568 "SR"
569 },
570 .nKeys = 26
571 },
572 { .id = 0 }
573 },
574 .configExtra = (struct GUIMenuItem[]) {
575 {
576 .title = "Fast forward cap",
577 .data = "fastForwardCap",
578 .submenu = 0,
579 .state = 7,
580 .validStates = (const char*[]) {
581 "3", "4", "5", "6", "7", "8", "9",
582 "10", "11", "12", "13", "14", "15",
583 "20", "30"
584 },
585 .stateMappings = (const struct GUIVariant[]) {
586 GUI_V_U(3),
587 GUI_V_U(4),
588 GUI_V_U(5),
589 GUI_V_U(6),
590 GUI_V_U(7),
591 GUI_V_U(8),
592 GUI_V_U(9),
593 GUI_V_U(10),
594 GUI_V_U(11),
595 GUI_V_U(12),
596 GUI_V_U(13),
597 GUI_V_U(14),
598 GUI_V_U(15),
599 GUI_V_U(20),
600 GUI_V_U(30),
601 },
602 .nStates = 15
603 },
604 },
605 .nConfigExtra = 1,
606 .setup = _setup,
607 .teardown = NULL,
608 .gameLoaded = _gameLoaded,
609 .gameUnloaded = _gameUnloaded,
610 .prepareForFrame = _prepareForFrame,
611 .drawFrame = _drawFrame,
612 .drawScreenshot = _drawScreenshot,
613 .paused = _gameUnloaded,
614 .unpaused = _gameLoaded,
615 .incrementScreenMode = NULL,
616 .setFrameLimiter = _setFrameLimiter,
617 .pollGameInput = _pollGameInput,
618 .running = _running
619 };
620 mGUIInit(&runner, "switch");
621
622 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_A, GUI_INPUT_SELECT);
623 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_B, GUI_INPUT_BACK);
624 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_X, GUI_INPUT_CANCEL);
625 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DUP, GUI_INPUT_UP);
626 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DDOWN, GUI_INPUT_DOWN);
627 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DLEFT, GUI_INPUT_LEFT);
628 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DRIGHT, GUI_INPUT_RIGHT);
629
630 audoutStartAudioOut();
631
632 if (argc > 1) {
633 size_t i;
634 for (i = 0; runner.keySources[i].id; ++i) {
635 mInputMapLoad(&runner.params.keyMap, runner.keySources[i].id, mCoreConfigGetInput(&runner.config));
636 }
637 mGUIRun(&runner, argv[1]);
638 } else {
639 mGUIRunloop(&runner);
640 }
641
642 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
643 glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
644 glDeleteBuffers(1, &pbo);
645
646 glDeleteTextures(1, &tex);
647 glDeleteBuffers(1, &vbo);
648 glDeleteProgram(program);
649 glDeleteVertexArrays(1, &vao);
650
651 psmExit();
652 audoutExit();
653 deinitEgl();
654 socketExit();
655 return 0;
656}