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