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