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