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, "", 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}
407
408static bool _running(struct mGUIRunner* runner) {
409 UNUSED(runner);
410 return appletMainLoop();
411}
412
413static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
414 UNUSED(stream);
415 AudioOutBuffer* releasedBuffers;
416 u32 audoutNReleasedBuffers;
417 audoutGetReleasedAudioOutBuffer(&releasedBuffers, &audoutNReleasedBuffers);
418 enqueuedBuffers -= audoutNReleasedBuffers;
419 if (!frameLimiter && enqueuedBuffers >= N_BUFFERS) {
420 blip_clear(left);
421 blip_clear(right);
422 return;
423 }
424 if (enqueuedBuffers >= N_BUFFERS - 1 && R_SUCCEEDED(audoutWaitPlayFinish(&releasedBuffers, &audoutNReleasedBuffers, 10000000))) {
425 enqueuedBuffers -= audoutNReleasedBuffers;
426 }
427
428 struct GBAStereoSample* samples = audioBuffer[audioBufferActive];
429 blip_read_samples(left, &samples[0].left, SAMPLES, true);
430 blip_read_samples(right, &samples[0].right, SAMPLES, true);
431 audoutAppendAudioOutBuffer(&audoutBuffer[audioBufferActive]);
432 audioBufferActive += 1;
433 audioBufferActive %= N_BUFFERS;
434 ++enqueuedBuffers;
435}
436
437void _setRumble(struct mRumble* rumble, int enable) {
438 struct mSwitchRumble* sr = (struct mSwitchRumble*) rumble;
439 if (enable) {
440 ++sr->up;
441 } else {
442 ++sr->down;
443 }
444}
445
446int32_t _readTiltX(struct mRotationSource* source) {
447 UNUSED(source);
448 SixAxisSensorValues sixaxis;
449 hidSixAxisSensorValuesRead(&sixaxis, CONTROLLER_P1_AUTO, 1);
450 return sixaxis.accelerometer.x * 3e8f;
451}
452
453int32_t _readTiltY(struct mRotationSource* source) {
454 UNUSED(source);
455 SixAxisSensorValues sixaxis;
456 hidSixAxisSensorValuesRead(&sixaxis, CONTROLLER_P1_AUTO, 1);
457 return sixaxis.accelerometer.y * -3e8f;
458}
459
460int32_t _readGyroZ(struct mRotationSource* source) {
461 UNUSED(source);
462 SixAxisSensorValues sixaxis;
463 hidSixAxisSensorValuesRead(&sixaxis, CONTROLLER_P1_AUTO, 1);
464 return sixaxis.gyroscope.z * 1.1e9f;
465}
466
467static int _batteryState(void) {
468 u32 charge;
469 int state = 0;
470 if (R_SUCCEEDED(psmGetBatteryChargePercentage(&charge))) {
471 state = (charge + 12) / 25;
472 } else {
473 return BATTERY_NOT_PRESENT;
474 }
475 ChargerType type;
476 if (R_SUCCEEDED(psmGetChargerType(&type)) && type) {
477 state |= BATTERY_CHARGING;
478 }
479 return state;
480}
481
482int main(int argc, char* argv[]) {
483 socketInitializeDefault();
484 nxlinkStdio();
485 initEgl();
486 romfsInit();
487 audoutInitialize();
488 psmInitialize();
489
490 struct GUIFont* font = GUIFontCreate();
491
492 u32 width = 1280;
493 u32 height = 720;
494
495 glViewport(0, 0, width, height);
496 glClearColor(0.f, 0.f, 0.f, 1.f);
497
498 glGenTextures(1, &tex);
499 glActiveTexture(GL_TEXTURE0);
500 glBindTexture(GL_TEXTURE_2D, tex);
501 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
502 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
503 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
504 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
505 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
506
507 glGenBuffers(1, &pbo);
508 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
509 glBufferData(GL_PIXEL_UNPACK_BUFFER, 256 * 256 * 4, NULL, GL_STREAM_DRAW);
510 frameBuffer = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, 256 * 256 * 4, GL_MAP_WRITE_BIT);
511 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
512
513 program = glCreateProgram();
514 GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
515 GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
516 const GLchar* shaderBuffer[2];
517
518 shaderBuffer[0] = _gles2Header;
519
520 shaderBuffer[1] = _vertexShader;
521 glShaderSource(vertexShader, 2, shaderBuffer, NULL);
522
523 shaderBuffer[1] = _fragmentShader;
524 glShaderSource(fragmentShader, 2, shaderBuffer, NULL);
525
526 glAttachShader(program, vertexShader);
527 glAttachShader(program, fragmentShader);
528
529 glCompileShader(fragmentShader);
530
531 GLint success;
532 glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
533 if (!success) {
534 GLchar msg[512];
535 glGetShaderInfoLog(fragmentShader, sizeof(msg), NULL, msg);
536 puts(msg);
537 }
538
539 glCompileShader(vertexShader);
540
541 glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
542 if (!success) {
543 GLchar msg[512];
544 glGetShaderInfoLog(vertexShader, sizeof(msg), NULL, msg);
545 puts(msg);
546 }
547 glLinkProgram(program);
548
549 glDeleteShader(vertexShader);
550 glDeleteShader(fragmentShader);
551
552 texLocation = glGetUniformLocation(program, "tex");
553 colorLocation = glGetUniformLocation(program, "color");
554 dimsLocation = glGetUniformLocation(program, "dims");
555 insizeLocation = glGetUniformLocation(program, "insize");
556 GLuint offsetLocation = glGetAttribLocation(program, "offset");
557
558 glGenBuffers(1, &vbo);
559 glGenVertexArrays(1, &vao);
560 glBindVertexArray(vao);
561 glBindBuffer(GL_ARRAY_BUFFER, vbo);
562 glBufferData(GL_ARRAY_BUFFER, sizeof(_offsets), _offsets, GL_STATIC_DRAW);
563 glVertexAttribPointer(offsetLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
564 glEnableVertexAttribArray(offsetLocation);
565 glBindVertexArray(0);
566
567 rumble.d.setRumble = _setRumble;
568 rumble.value.freq_low = 120.0;
569 rumble.value.freq_high = 180.0;
570 hidInitializeVibrationDevices(&vibrationDeviceHandles[0], 2, CONTROLLER_HANDHELD, TYPE_HANDHELD | TYPE_JOYCON_PAIR);
571 hidInitializeVibrationDevices(&vibrationDeviceHandles[2], 2, CONTROLLER_PLAYER_1, TYPE_HANDHELD | TYPE_JOYCON_PAIR);
572
573 u32 handles[4];
574 hidGetSixAxisSensorHandles(&handles[0], 2, CONTROLLER_PLAYER_1, TYPE_JOYCON_PAIR);
575 hidGetSixAxisSensorHandles(&handles[2], 1, CONTROLLER_PLAYER_1, TYPE_PROCONTROLLER);
576 hidGetSixAxisSensorHandles(&handles[3], 1, CONTROLLER_HANDHELD, TYPE_HANDHELD);
577 hidStartSixAxisSensor(handles[0]);
578 hidStartSixAxisSensor(handles[1]);
579 hidStartSixAxisSensor(handles[2]);
580 hidStartSixAxisSensor(handles[3]);
581 rotation.readTiltX = _readTiltX;
582 rotation.readTiltY = _readTiltY;
583 rotation.readGyroZ = _readGyroZ;
584
585 stream.videoDimensionsChanged = NULL;
586 stream.postVideoFrame = NULL;
587 stream.postAudioFrame = NULL;
588 stream.postAudioBuffer = _postAudioBuffer;
589
590 memset(audioBuffer, 0, sizeof(audioBuffer));
591 audioBufferActive = 0;
592 enqueuedBuffers = 0;
593 size_t i;
594 for (i = 0; i < N_BUFFERS; ++i) {
595 audoutBuffer[i].next = NULL;
596 audoutBuffer[i].buffer = audioBuffer[i];
597 audoutBuffer[i].buffer_size = BUFFER_SIZE;
598 audoutBuffer[i].data_size = BUFFER_SIZE;
599 audoutBuffer[i].data_offset = 0;
600 }
601
602 struct mGUIRunner runner = {
603 .params = {
604 width, height,
605 font, "/",
606 _drawStart, _drawEnd,
607 _pollInput, _pollCursor,
608 _batteryState,
609 NULL, NULL,
610 },
611 .keySources = (struct GUIInputKeys[]) {
612 {
613 .name = "Controller Input",
614 .id = AUTO_INPUT,
615 .keyNames = (const char*[]) {
616 "A",
617 "B",
618 "X",
619 "Y",
620 "L Stick",
621 "R Stick",
622 "L",
623 "R",
624 "ZL",
625 "ZR",
626 "+",
627 "-",
628 "Left",
629 "Up",
630 "Right",
631 "Down",
632 "L Left",
633 "L Up",
634 "L Right",
635 "L Down",
636 "R Left",
637 "R Up",
638 "R Right",
639 "R Down",
640 "SL",
641 "SR"
642 },
643 .nKeys = 26
644 },
645 { .id = 0 }
646 },
647 .configExtra = (struct GUIMenuItem[]) {
648 {
649 .title = "Screen mode",
650 .data = "screenMode",
651 .submenu = 0,
652 .state = SM_PA,
653 .validStates = (const char*[]) {
654 "Pixel-Accurate",
655 "Aspect-Ratio Fit",
656 "Stretched",
657 },
658 .nStates = 3
659 },
660 {
661 .title = "Fast forward cap",
662 .data = "fastForwardCap",
663 .submenu = 0,
664 .state = 7,
665 .validStates = (const char*[]) {
666 "3", "4", "5", "6", "7", "8", "9",
667 "10", "11", "12", "13", "14", "15",
668 "20", "30"
669 },
670 .stateMappings = (const struct GUIVariant[]) {
671 GUI_V_U(3),
672 GUI_V_U(4),
673 GUI_V_U(5),
674 GUI_V_U(6),
675 GUI_V_U(7),
676 GUI_V_U(8),
677 GUI_V_U(9),
678 GUI_V_U(10),
679 GUI_V_U(11),
680 GUI_V_U(12),
681 GUI_V_U(13),
682 GUI_V_U(14),
683 GUI_V_U(15),
684 GUI_V_U(20),
685 GUI_V_U(30),
686 },
687 .nStates = 15
688 },
689 },
690 .nConfigExtra = 2,
691 .setup = _setup,
692 .teardown = NULL,
693 .gameLoaded = _gameLoaded,
694 .gameUnloaded = _gameUnloaded,
695 .prepareForFrame = _prepareForFrame,
696 .drawFrame = _drawFrame,
697 .drawScreenshot = _drawScreenshot,
698 .paused = _gameUnloaded,
699 .unpaused = _gameLoaded,
700 .incrementScreenMode = _incrementScreenMode,
701 .setFrameLimiter = _setFrameLimiter,
702 .pollGameInput = _pollGameInput,
703 .running = _running
704 };
705 mGUIInit(&runner, "switch");
706
707 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_A, GUI_INPUT_SELECT);
708 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_B, GUI_INPUT_BACK);
709 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_X, GUI_INPUT_CANCEL);
710 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DUP, GUI_INPUT_UP);
711 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DDOWN, GUI_INPUT_DOWN);
712 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DLEFT, GUI_INPUT_LEFT);
713 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DRIGHT, GUI_INPUT_RIGHT);
714
715 audoutStartAudioOut();
716
717 if (argc > 1) {
718 size_t i;
719 for (i = 0; runner.keySources[i].id; ++i) {
720 mInputMapLoad(&runner.params.keyMap, runner.keySources[i].id, mCoreConfigGetInput(&runner.config));
721 }
722 mGUIRun(&runner, argv[1]);
723 } else {
724 mGUIRunloop(&runner);
725 }
726
727 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
728 glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
729 glDeleteBuffers(1, &pbo);
730
731 glDeleteTextures(1, &tex);
732 glDeleteBuffers(1, &vbo);
733 glDeleteProgram(program);
734 glDeleteVertexArrays(1, &vao);
735
736 hidStopSixAxisSensor(handles[0]);
737 hidStopSixAxisSensor(handles[1]);
738 hidStopSixAxisSensor(handles[2]);
739 hidStopSixAxisSensor(handles[3]);
740
741 psmExit();
742 audoutExit();
743 deinitEgl();
744 socketExit();
745 return 0;
746}