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/core.h>
8#include <mgba/internal/gba/input.h>
9#include <mgba-util/gui.h>
10#include <mgba-util/gui/font.h>
11
12#include <switch.h>
13#include <EGL/egl.h>
14#include <GLES2/gl2.h>
15
16#define AUTO_INPUT 0x4E585031
17
18static EGLDisplay s_display;
19static EGLContext s_context;
20static EGLSurface s_surface;
21
22static const GLfloat _offsets[] = {
23 0.f, 0.f,
24 1.f, 0.f,
25 1.f, 1.f,
26 0.f, 1.f,
27};
28
29static const GLchar* const _gles2Header =
30 "#version 100\n"
31 "precision mediump float;\n";
32
33static const char* const _vertexShader =
34 "attribute vec2 offset;\n"
35 "uniform vec2 dims;\n"
36 "uniform vec2 insize;\n"
37 "varying vec2 texCoord;\n"
38
39 "void main() {\n"
40 " vec2 ratio = insize / 256.0;\n"
41 " vec2 scaledOffset = offset * dims;\n"
42 " gl_Position = vec4(scaledOffset.x * 2.0 - dims.x, scaledOffset.y * -2.0 + dims.y, 0.0, 1.0);\n"
43 " texCoord = offset * ratio;\n"
44 "}";
45
46static const char* const _fragmentShader =
47 "varying vec2 texCoord;\n"
48 "uniform sampler2D tex;\n"
49 "uniform vec4 color;\n"
50
51 "void main() {\n"
52 " vec4 texColor = vec4(texture2D(tex, texCoord).rgb, 1.0);\n"
53 " texColor *= color;\n"
54 " gl_FragColor = texColor;\n"
55 "}";
56
57static GLuint program;
58static GLuint vbo;
59static GLuint offsetLocation;
60static GLuint texLocation;
61static GLuint dimsLocation;
62static GLuint insizeLocation;
63static GLuint colorLocation;
64static GLuint tex;
65
66static color_t frameBuffer[256 * 256];
67
68static bool initEgl() {
69 s_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
70 if (!s_display) {
71 goto _fail0;
72 }
73
74 eglInitialize(s_display, NULL, NULL);
75
76 EGLConfig config;
77 EGLint numConfigs;
78 static const EGLint attributeList[] = {
79 EGL_RED_SIZE, 1,
80 EGL_GREEN_SIZE, 1,
81 EGL_BLUE_SIZE, 1,
82 EGL_NONE
83 };
84 eglChooseConfig(s_display, attributeList, &config, 1, &numConfigs);
85 if (!numConfigs) {
86 goto _fail1;
87 }
88
89 s_surface = eglCreateWindowSurface(s_display, config, "", NULL);
90 if (!s_surface) {
91 goto _fail1;
92 }
93
94 s_context = eglCreateContext(s_display, config, EGL_NO_CONTEXT, NULL);
95 if (!s_context) {
96 goto _fail2;
97 }
98
99 eglMakeCurrent(s_display, s_surface, s_surface, s_context);
100 return true;
101
102_fail2:
103 eglDestroySurface(s_display, s_surface);
104 s_surface = NULL;
105_fail1:
106 eglTerminate(s_display);
107 s_display = NULL;
108_fail0:
109 return false;
110}
111
112static void deinitEgl() {
113 if (s_display) {
114 if (s_context) {
115 eglDestroyContext(s_display, s_context);
116 }
117 if (s_surface) {
118 eglDestroySurface(s_display, s_surface);
119 }
120 eglTerminate(s_display);
121 }
122}
123
124static void _mapKey(struct mInputMap* map, uint32_t binding, int nativeKey, enum GBAKey key) {
125 mInputBindKey(map, binding, __builtin_ctz(nativeKey), key);
126}
127
128static void _drawStart(void) {
129 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
130}
131
132static void _drawEnd(void) {
133 eglSwapBuffers(s_display, s_surface);
134}
135
136static uint32_t _pollInput(const struct mInputMap* map) {
137 int keys = 0;
138 hidScanInput();
139 u32 padkeys = hidKeysHeld(CONTROLLER_P1_AUTO);
140 keys |= mInputMapKeyBits(map, AUTO_INPUT, padkeys, 0);
141 return keys;
142}
143
144static void _setup(struct mGUIRunner* runner) {
145 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_A, GBA_KEY_A);
146 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_B, GBA_KEY_B);
147 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_PLUS, GBA_KEY_START);
148 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_MINUS, GBA_KEY_SELECT);
149 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DUP, GBA_KEY_UP);
150 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DDOWN, GBA_KEY_DOWN);
151 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DLEFT, GBA_KEY_LEFT);
152 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DRIGHT, GBA_KEY_RIGHT);
153 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_L, GBA_KEY_L);
154 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_R, GBA_KEY_R);
155
156 runner->core->setVideoBuffer(runner->core, frameBuffer, 256);
157}
158
159static void _drawTex(struct mGUIRunner* runner, unsigned width, unsigned height, bool faded) {
160 glBindBuffer(GL_ARRAY_BUFFER, vbo);
161
162 glEnable(GL_BLEND);
163 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
164
165 glUseProgram(program);
166 float aspectX = width / (float) runner->params.width;
167 float aspectY = height / (float) runner->params.height;
168 float max;
169 if (aspectX > aspectY) {
170 max = floor(1.0 / aspectX);
171 } else {
172 max = floor(1.0 / aspectY);
173 }
174
175 aspectX *= max;
176 aspectY *= max;
177
178 glUniform1i(texLocation, 0);
179 glUniform2f(dimsLocation, aspectX, aspectY);
180 glUniform2f(insizeLocation, width, height);
181 if (!faded) {
182 glUniform4f(colorLocation, 1.0f, 1.0f, 1.0f, 1.0f);
183 } else {
184 glUniform4f(colorLocation, 0.8f, 0.8f, 0.8f, 0.8f);
185 }
186
187 glVertexAttribPointer(offsetLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
188 glEnableVertexAttribArray(offsetLocation);
189
190 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
191
192 glDisableVertexAttribArray(offsetLocation);
193 glBindBuffer(GL_ARRAY_BUFFER, 0);
194 glUseProgram(0);
195}
196
197static void _drawFrame(struct mGUIRunner* runner, bool faded) {
198 glActiveTexture(GL_TEXTURE0);
199 glBindTexture(GL_TEXTURE_2D, tex);
200 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, frameBuffer);
201
202 unsigned width, height;
203 runner->core->desiredVideoDimensions(runner->core, &width, &height);
204 _drawTex(runner, width, height, faded);
205}
206
207static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsigned width, unsigned height, bool faded) {
208 glActiveTexture(GL_TEXTURE0);
209 glBindTexture(GL_TEXTURE_2D, tex);
210 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
211
212 _drawTex(runner, width, height, faded);
213}
214
215static uint16_t _pollGameInput(struct mGUIRunner* runner) {
216 int keys = 0;
217 hidScanInput();
218 u32 padkeys = hidKeysHeld(CONTROLLER_P1_AUTO);
219 keys |= mInputMapKeyBits(&runner->core->inputMap, AUTO_INPUT, padkeys, 0);
220 return keys;
221}
222
223static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
224 UNUSED(runner);
225}
226
227static bool _running(struct mGUIRunner* runner) {
228 UNUSED(runner);
229 return appletMainLoop();
230}
231
232int main(int argc, char* argv[]) {
233 socketInitializeDefault();
234 nxlinkStdio();
235 initEgl();
236 romfsInit();
237
238 struct GUIFont* font = GUIFontCreate();
239
240 u32 width = 1280;
241 u32 height = 720;
242
243 glViewport(0, 0, width, height);
244 glClearColor(0.f, 0.f, 0.f, 1.f);
245
246 glGenTextures(1, &tex);
247 glActiveTexture(GL_TEXTURE0);
248 glBindTexture(GL_TEXTURE_2D, tex);
249 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
250 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
251 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
252 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
253
254 program = glCreateProgram();
255 GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
256 GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
257 const GLchar* shaderBuffer[2];
258
259 shaderBuffer[0] = _gles2Header;
260
261 shaderBuffer[1] = _vertexShader;
262 glShaderSource(vertexShader, 2, shaderBuffer, NULL);
263
264 shaderBuffer[1] = _fragmentShader;
265 glShaderSource(fragmentShader, 2, shaderBuffer, NULL);
266
267 glAttachShader(program, vertexShader);
268 glAttachShader(program, fragmentShader);
269
270 glCompileShader(fragmentShader);
271
272 GLint success;
273 glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
274 if (!success) {
275 GLchar msg[512];
276 glGetShaderInfoLog(fragmentShader, sizeof(msg), NULL, msg);
277 puts(msg);
278 }
279
280 glCompileShader(vertexShader);
281
282 glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
283 if (!success) {
284 GLchar msg[512];
285 glGetShaderInfoLog(vertexShader, sizeof(msg), NULL, msg);
286 puts(msg);
287 }
288 glLinkProgram(program);
289
290 glDeleteShader(vertexShader);
291 glDeleteShader(fragmentShader);
292
293 texLocation = glGetUniformLocation(program, "tex");
294 colorLocation = glGetUniformLocation(program, "color");
295 dimsLocation = glGetUniformLocation(program, "dims");
296 insizeLocation = glGetUniformLocation(program, "insize");
297 offsetLocation = glGetAttribLocation(program, "offset");
298
299 glGenBuffers(1, &vbo);
300 glBindBuffer(GL_ARRAY_BUFFER, vbo);
301 glBufferData(GL_ARRAY_BUFFER, sizeof(_offsets), _offsets, GL_STATIC_DRAW);
302 glBindBuffer(GL_ARRAY_BUFFER, 0);
303
304 struct mGUIRunner runner = {
305 .params = {
306 width, height,
307 font, "/",
308 _drawStart, _drawEnd,
309 _pollInput, NULL,
310 NULL,
311 NULL, NULL,
312 },
313 .nConfigExtra = 0,
314 .setup = _setup,
315 .teardown = NULL,
316 .gameLoaded = NULL,
317 .gameUnloaded = NULL,
318 .prepareForFrame = NULL,
319 .drawFrame = _drawFrame,
320 .drawScreenshot = _drawScreenshot,
321 .paused = NULL,
322 .unpaused = NULL,
323 .incrementScreenMode = NULL,
324 .setFrameLimiter = _setFrameLimiter,
325 .pollGameInput = _pollGameInput,
326 .running = _running
327 };
328 mGUIInit(&runner, "switch");
329
330 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_A, GUI_INPUT_SELECT);
331 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_B, GUI_INPUT_BACK);
332 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_X, GUI_INPUT_CANCEL);
333 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DUP, GUI_INPUT_UP);
334 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DDOWN, GUI_INPUT_DOWN);
335 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DLEFT, GUI_INPUT_LEFT);
336 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DRIGHT, GUI_INPUT_RIGHT);
337
338 mGUIRunloop(&runner);
339
340 deinitEgl();
341 socketExit();
342 return 0;
343}