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 void _setup(struct mGUIRunner* runner) {
165 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_A, GBA_KEY_A);
166 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_B, GBA_KEY_B);
167 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_PLUS, GBA_KEY_START);
168 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_MINUS, GBA_KEY_SELECT);
169 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DUP, GBA_KEY_UP);
170 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DDOWN, GBA_KEY_DOWN);
171 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DLEFT, GBA_KEY_LEFT);
172 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DRIGHT, GBA_KEY_RIGHT);
173 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_L, GBA_KEY_L);
174 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_R, GBA_KEY_R);
175
176 runner->core->setVideoBuffer(runner->core, frameBuffer, 256);
177 runner->core->setAVStream(runner->core, &stream);
178}
179
180static void _gameLoaded(struct mGUIRunner* runner) {
181 u32 samplerate = audoutGetSampleRate();
182
183 double ratio = GBAAudioCalculateRatio(1, 60.0, 1);
184 blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), samplerate * ratio);
185 blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), samplerate * ratio);
186}
187
188static void _drawTex(struct mGUIRunner* runner, unsigned width, unsigned height, bool faded) {
189 glBindBuffer(GL_ARRAY_BUFFER, vbo);
190
191 glEnable(GL_BLEND);
192 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
193
194 glUseProgram(program);
195 float aspectX = width / (float) runner->params.width;
196 float aspectY = height / (float) runner->params.height;
197 float max;
198 if (aspectX > aspectY) {
199 max = floor(1.0 / aspectX);
200 } else {
201 max = floor(1.0 / aspectY);
202 }
203
204 aspectX *= max;
205 aspectY *= max;
206
207 glUniform1i(texLocation, 0);
208 glUniform2f(dimsLocation, aspectX, aspectY);
209 glUniform2f(insizeLocation, width, height);
210 if (!faded) {
211 glUniform4f(colorLocation, 1.0f, 1.0f, 1.0f, 1.0f);
212 } else {
213 glUniform4f(colorLocation, 0.8f, 0.8f, 0.8f, 0.8f);
214 }
215
216 glVertexAttribPointer(offsetLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
217 glEnableVertexAttribArray(offsetLocation);
218
219 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
220
221 glDisableVertexAttribArray(offsetLocation);
222 glBindBuffer(GL_ARRAY_BUFFER, 0);
223 glUseProgram(0);
224}
225
226static void _drawFrame(struct mGUIRunner* runner, bool faded) {
227 glActiveTexture(GL_TEXTURE0);
228 glBindTexture(GL_TEXTURE_2D, tex);
229 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, frameBuffer);
230
231 unsigned width, height;
232 runner->core->desiredVideoDimensions(runner->core, &width, &height);
233 _drawTex(runner, width, height, faded);
234
235 ++framecount;
236}
237
238static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsigned width, unsigned height, bool faded) {
239 glActiveTexture(GL_TEXTURE0);
240 glBindTexture(GL_TEXTURE_2D, tex);
241 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
242
243 _drawTex(runner, width, height, faded);
244}
245
246static uint16_t _pollGameInput(struct mGUIRunner* runner) {
247 int keys = 0;
248 hidScanInput();
249 u32 padkeys = hidKeysHeld(CONTROLLER_P1_AUTO);
250 keys |= mInputMapKeyBits(&runner->core->inputMap, AUTO_INPUT, padkeys, 0);
251 return keys;
252}
253
254static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
255 UNUSED(runner);
256 frameLimiter = limit;
257}
258
259static bool _running(struct mGUIRunner* runner) {
260 UNUSED(runner);
261 return appletMainLoop();
262}
263
264static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
265 UNUSED(stream);
266 static AudioOutBuffer* releasedBuffers;
267 u32 audoutNReleasedBuffers;
268 audoutGetReleasedAudioOutBuffer(&releasedBuffers, &audoutNReleasedBuffers);
269 enqueuedBuffers -= audoutNReleasedBuffers;
270 if (!frameLimiter && enqueuedBuffers == N_BUFFERS) {
271 blip_clear(left);
272 blip_clear(right);
273 return;
274 }
275
276 struct GBAStereoSample* samples = audioBuffer[audioBufferActive];
277 blip_read_samples(left, &samples[0].left, SAMPLES, true);
278 blip_read_samples(right, &samples[0].right, SAMPLES, true);
279 audoutAppendAudioOutBuffer(&audoutBuffer[audioBufferActive]);
280 audioBufferActive += 1;
281 audioBufferActive %= N_BUFFERS;
282 ++enqueuedBuffers;
283}
284
285int main(int argc, char* argv[]) {
286 socketInitializeDefault();
287 nxlinkStdio();
288 initEgl();
289 romfsInit();
290 audoutInitialize();
291
292 struct GUIFont* font = GUIFontCreate();
293
294 u32 width = 1280;
295 u32 height = 720;
296
297 glViewport(0, 0, width, height);
298 glClearColor(0.f, 0.f, 0.f, 1.f);
299
300 glGenTextures(1, &tex);
301 glActiveTexture(GL_TEXTURE0);
302 glBindTexture(GL_TEXTURE_2D, tex);
303 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
304 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
305 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
306 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
307
308 program = glCreateProgram();
309 GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
310 GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
311 const GLchar* shaderBuffer[2];
312
313 shaderBuffer[0] = _gles2Header;
314
315 shaderBuffer[1] = _vertexShader;
316 glShaderSource(vertexShader, 2, shaderBuffer, NULL);
317
318 shaderBuffer[1] = _fragmentShader;
319 glShaderSource(fragmentShader, 2, shaderBuffer, NULL);
320
321 glAttachShader(program, vertexShader);
322 glAttachShader(program, fragmentShader);
323
324 glCompileShader(fragmentShader);
325
326 GLint success;
327 glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
328 if (!success) {
329 GLchar msg[512];
330 glGetShaderInfoLog(fragmentShader, sizeof(msg), NULL, msg);
331 puts(msg);
332 }
333
334 glCompileShader(vertexShader);
335
336 glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
337 if (!success) {
338 GLchar msg[512];
339 glGetShaderInfoLog(vertexShader, sizeof(msg), NULL, msg);
340 puts(msg);
341 }
342 glLinkProgram(program);
343
344 glDeleteShader(vertexShader);
345 glDeleteShader(fragmentShader);
346
347 texLocation = glGetUniformLocation(program, "tex");
348 colorLocation = glGetUniformLocation(program, "color");
349 dimsLocation = glGetUniformLocation(program, "dims");
350 insizeLocation = glGetUniformLocation(program, "insize");
351 offsetLocation = glGetAttribLocation(program, "offset");
352
353 glGenBuffers(1, &vbo);
354 glBindBuffer(GL_ARRAY_BUFFER, vbo);
355 glBufferData(GL_ARRAY_BUFFER, sizeof(_offsets), _offsets, GL_STATIC_DRAW);
356 glBindBuffer(GL_ARRAY_BUFFER, 0);
357
358 stream.videoDimensionsChanged = NULL;
359 stream.postVideoFrame = NULL;
360 stream.postAudioFrame = NULL;
361 stream.postAudioBuffer = _postAudioBuffer;
362
363 memset(audioBuffer, 0, sizeof(audioBuffer));
364 audioBufferActive = 0;
365 enqueuedBuffers = 0;
366 size_t i;
367 for (i = 0; i < N_BUFFERS; ++i) {
368 audoutBuffer[i].next = NULL;
369 audoutBuffer[i].buffer = audioBuffer[i];
370 audoutBuffer[i].buffer_size = BUFFER_SIZE;
371 audoutBuffer[i].data_size = BUFFER_SIZE;
372 audoutBuffer[i].data_offset = 0;
373 }
374
375 struct mGUIRunner runner = {
376 .params = {
377 width, height,
378 font, "/",
379 _drawStart, _drawEnd,
380 _pollInput, NULL,
381 NULL,
382 NULL, NULL,
383 },
384 .keySources = (struct GUIInputKeys[]) {
385 {
386 .name = "Controller Input",
387 .id = AUTO_INPUT,
388 .keyNames = (const char*[]) {
389 "A",
390 "B",
391 "X",
392 "Y",
393 "L Stick",
394 "R Stick",
395 "L",
396 "R",
397 "ZL",
398 "ZR",
399 "+",
400 "-",
401 "Left",
402 "Up",
403 "Right",
404 "Down",
405 "L Left",
406 "L Up",
407 "L Right",
408 "L Down",
409 "R Left",
410 "R Up",
411 "R Right",
412 "R Down",
413 "SL",
414 "SR"
415 },
416 .nKeys = 26
417 },
418 { .id = 0 }
419 },
420 .nConfigExtra = 0,
421 .setup = _setup,
422 .teardown = NULL,
423 .gameLoaded = _gameLoaded,
424 .gameUnloaded = NULL,
425 .prepareForFrame = NULL,
426 .drawFrame = _drawFrame,
427 .drawScreenshot = _drawScreenshot,
428 .paused = NULL,
429 .unpaused = _gameLoaded,
430 .incrementScreenMode = NULL,
431 .setFrameLimiter = _setFrameLimiter,
432 .pollGameInput = _pollGameInput,
433 .running = _running
434 };
435 mGUIInit(&runner, "switch");
436
437 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_A, GUI_INPUT_SELECT);
438 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_B, GUI_INPUT_BACK);
439 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_X, GUI_INPUT_CANCEL);
440 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DUP, GUI_INPUT_UP);
441 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DDOWN, GUI_INPUT_DOWN);
442 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DLEFT, GUI_INPUT_LEFT);
443 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DRIGHT, GUI_INPUT_RIGHT);
444
445 audoutStartAudioOut();
446 mGUIRunloop(&runner);
447
448 audoutExit();
449 deinitEgl();
450 socketExit();
451 return 0;
452}