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