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