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 3
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 frameLimiter = limit;
270}
271
272static bool _running(struct mGUIRunner* runner) {
273 UNUSED(runner);
274 return appletMainLoop();
275}
276
277static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
278 UNUSED(stream);
279 static AudioOutBuffer* releasedBuffers;
280 u32 audoutNReleasedBuffers;
281 audoutGetReleasedAudioOutBuffer(&releasedBuffers, &audoutNReleasedBuffers);
282 enqueuedBuffers -= audoutNReleasedBuffers;
283 if (!frameLimiter && enqueuedBuffers == N_BUFFERS) {
284 blip_clear(left);
285 blip_clear(right);
286 return;
287 }
288
289 struct GBAStereoSample* samples = audioBuffer[audioBufferActive];
290 blip_read_samples(left, &samples[0].left, SAMPLES, true);
291 blip_read_samples(right, &samples[0].right, SAMPLES, true);
292 audoutAppendAudioOutBuffer(&audoutBuffer[audioBufferActive]);
293 audioBufferActive += 1;
294 audioBufferActive %= N_BUFFERS;
295 ++enqueuedBuffers;
296}
297
298static int _batteryState(void) {
299 u32 charge;
300 int state = 0;
301 if (R_SUCCEEDED(psmGetBatteryChargePercentage(&charge))) {
302 state = charge / 25;
303 }
304 return state;
305}
306
307int main(int argc, char* argv[]) {
308 socketInitializeDefault();
309 nxlinkStdio();
310 initEgl();
311 romfsInit();
312 audoutInitialize();
313 psmInitialize();
314
315 struct GUIFont* font = GUIFontCreate();
316
317 u32 width = 1280;
318 u32 height = 720;
319
320 glViewport(0, 0, width, height);
321 glClearColor(0.f, 0.f, 0.f, 1.f);
322
323 glGenTextures(1, &tex);
324 glActiveTexture(GL_TEXTURE0);
325 glBindTexture(GL_TEXTURE_2D, tex);
326 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
327 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
328 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
329 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
330
331 program = glCreateProgram();
332 GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
333 GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
334 const GLchar* shaderBuffer[2];
335
336 shaderBuffer[0] = _gles2Header;
337
338 shaderBuffer[1] = _vertexShader;
339 glShaderSource(vertexShader, 2, shaderBuffer, NULL);
340
341 shaderBuffer[1] = _fragmentShader;
342 glShaderSource(fragmentShader, 2, shaderBuffer, NULL);
343
344 glAttachShader(program, vertexShader);
345 glAttachShader(program, fragmentShader);
346
347 glCompileShader(fragmentShader);
348
349 GLint success;
350 glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
351 if (!success) {
352 GLchar msg[512];
353 glGetShaderInfoLog(fragmentShader, sizeof(msg), NULL, msg);
354 puts(msg);
355 }
356
357 glCompileShader(vertexShader);
358
359 glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
360 if (!success) {
361 GLchar msg[512];
362 glGetShaderInfoLog(vertexShader, sizeof(msg), NULL, msg);
363 puts(msg);
364 }
365 glLinkProgram(program);
366
367 glDeleteShader(vertexShader);
368 glDeleteShader(fragmentShader);
369
370 texLocation = glGetUniformLocation(program, "tex");
371 colorLocation = glGetUniformLocation(program, "color");
372 dimsLocation = glGetUniformLocation(program, "dims");
373 insizeLocation = glGetUniformLocation(program, "insize");
374 offsetLocation = glGetAttribLocation(program, "offset");
375
376 glGenBuffers(1, &vbo);
377 glBindBuffer(GL_ARRAY_BUFFER, vbo);
378 glBufferData(GL_ARRAY_BUFFER, sizeof(_offsets), _offsets, GL_STATIC_DRAW);
379 glBindBuffer(GL_ARRAY_BUFFER, 0);
380
381 stream.videoDimensionsChanged = NULL;
382 stream.postVideoFrame = NULL;
383 stream.postAudioFrame = NULL;
384 stream.postAudioBuffer = _postAudioBuffer;
385
386 memset(audioBuffer, 0, sizeof(audioBuffer));
387 audioBufferActive = 0;
388 enqueuedBuffers = 0;
389 size_t i;
390 for (i = 0; i < N_BUFFERS; ++i) {
391 audoutBuffer[i].next = NULL;
392 audoutBuffer[i].buffer = audioBuffer[i];
393 audoutBuffer[i].buffer_size = BUFFER_SIZE;
394 audoutBuffer[i].data_size = BUFFER_SIZE;
395 audoutBuffer[i].data_offset = 0;
396 }
397
398 struct mGUIRunner runner = {
399 .params = {
400 width, height,
401 font, "/",
402 _drawStart, _drawEnd,
403 _pollInput, _pollCursor,
404 _batteryState,
405 NULL, NULL,
406 },
407 .keySources = (struct GUIInputKeys[]) {
408 {
409 .name = "Controller Input",
410 .id = AUTO_INPUT,
411 .keyNames = (const char*[]) {
412 "A",
413 "B",
414 "X",
415 "Y",
416 "L Stick",
417 "R Stick",
418 "L",
419 "R",
420 "ZL",
421 "ZR",
422 "+",
423 "-",
424 "Left",
425 "Up",
426 "Right",
427 "Down",
428 "L Left",
429 "L Up",
430 "L Right",
431 "L Down",
432 "R Left",
433 "R Up",
434 "R Right",
435 "R Down",
436 "SL",
437 "SR"
438 },
439 .nKeys = 26
440 },
441 { .id = 0 }
442 },
443 .nConfigExtra = 0,
444 .setup = _setup,
445 .teardown = NULL,
446 .gameLoaded = _gameLoaded,
447 .gameUnloaded = NULL,
448 .prepareForFrame = NULL,
449 .drawFrame = _drawFrame,
450 .drawScreenshot = _drawScreenshot,
451 .paused = NULL,
452 .unpaused = _gameLoaded,
453 .incrementScreenMode = NULL,
454 .setFrameLimiter = _setFrameLimiter,
455 .pollGameInput = _pollGameInput,
456 .running = _running
457 };
458 mGUIInit(&runner, "switch");
459
460 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_A, GUI_INPUT_SELECT);
461 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_B, GUI_INPUT_BACK);
462 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_X, GUI_INPUT_CANCEL);
463 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DUP, GUI_INPUT_UP);
464 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DDOWN, GUI_INPUT_DOWN);
465 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DLEFT, GUI_INPUT_LEFT);
466 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DRIGHT, GUI_INPUT_RIGHT);
467
468 audoutStartAudioOut();
469 mGUIRunloop(&runner);
470
471 psmExit();
472 audoutExit();
473 deinitEgl();
474 socketExit();
475 return 0;
476}