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