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#include <mgba-util/gui/menu.h>
14
15#include <switch.h>
16#include <EGL/egl.h>
17#include <GLES3/gl3.h>
18#include <GLES3/gl31.h>
19
20#define AUTO_INPUT 0x4E585031
21#define SAMPLES 0x400
22#define BUFFER_SIZE 0x1000
23#define N_BUFFERS 4
24#define ANALOG_DEADZONE 0x4000
25
26TimeType __nx_time_type = TimeType_UserSystemClock;
27
28static EGLDisplay s_display;
29static EGLContext s_context;
30static EGLSurface s_surface;
31
32static const GLfloat _offsets[] = {
33 0.f, 0.f,
34 1.f, 0.f,
35 1.f, 1.f,
36 0.f, 1.f,
37};
38
39static const GLchar* const _gles2Header =
40 "#version 100\n"
41 "precision mediump float;\n";
42
43static const char* const _vertexShader =
44 "attribute vec2 offset;\n"
45 "uniform vec2 dims;\n"
46 "uniform vec2 insize;\n"
47 "varying vec2 texCoord;\n"
48
49 "void main() {\n"
50 " vec2 ratio = insize;\n"
51 " vec2 scaledOffset = offset * dims;\n"
52 " gl_Position = vec4(scaledOffset.x * 2.0 - dims.x, scaledOffset.y * -2.0 + dims.y, 0.0, 1.0);\n"
53 " texCoord = offset * ratio;\n"
54 "}";
55
56static const char* const _fragmentShader =
57 "varying vec2 texCoord;\n"
58 "uniform sampler2D tex;\n"
59 "uniform vec4 color;\n"
60
61 "void main() {\n"
62 " vec4 texColor = vec4(texture2D(tex, texCoord).rgb, 1.0);\n"
63 " texColor *= color;\n"
64 " gl_FragColor = texColor;\n"
65 "}";
66
67static GLuint program;
68static GLuint vbo;
69static GLuint vao;
70static GLuint pbo;
71static GLuint copyFbo;
72static GLuint texLocation;
73static GLuint dimsLocation;
74static GLuint insizeLocation;
75static GLuint colorLocation;
76static GLuint tex;
77static GLuint oldTex;
78
79static color_t* frameBuffer;
80static struct mAVStream stream;
81static struct mSwitchRumble {
82 struct mRumble d;
83 int up;
84 int down;
85 HidVibrationValue value;
86} rumble;
87static struct mRotationSource rotation = {0};
88static int audioBufferActive;
89static struct GBAStereoSample audioBuffer[N_BUFFERS][SAMPLES] __attribute__((__aligned__(0x1000)));
90static AudioOutBuffer audoutBuffer[N_BUFFERS];
91static int enqueuedBuffers;
92static bool frameLimiter = true;
93static unsigned framecount = 0;
94static unsigned framecap = 10;
95static u32 vibrationDeviceHandles[4];
96static HidVibrationValue vibrationStop = { .freq_low = 160.f, .freq_high = 320.f };
97static bool usePbo = true;
98static u8 vmode;
99static u32 vwidth;
100static u32 vheight;
101static bool interframeBlending = false;
102
103static enum ScreenMode {
104 SM_PA,
105 SM_AF,
106 SM_SF,
107 SM_MAX
108} screenMode = SM_PA;
109
110static bool initEgl() {
111 s_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
112 if (!s_display) {
113 goto _fail0;
114 }
115
116 eglInitialize(s_display, NULL, NULL);
117
118 EGLConfig config;
119 EGLint numConfigs;
120 static const EGLint attributeList[] = {
121 EGL_RED_SIZE, 8,
122 EGL_GREEN_SIZE, 8,
123 EGL_BLUE_SIZE, 8,
124 EGL_NONE
125 };
126 eglChooseConfig(s_display, attributeList, &config, 1, &numConfigs);
127 if (!numConfigs) {
128 goto _fail1;
129 }
130
131 s_surface = eglCreateWindowSurface(s_display, config, nwindowGetDefault(), NULL);
132 if (!s_surface) {
133 goto _fail1;
134 }
135
136 EGLint contextAttributeList[] = {
137 EGL_CONTEXT_MAJOR_VERSION, 3,
138 EGL_CONTEXT_MINOR_VERSION, 1,
139 EGL_NONE
140 };
141 s_context = eglCreateContext(s_display, config, EGL_NO_CONTEXT, contextAttributeList);
142 if (!s_context) {
143 goto _fail2;
144 }
145
146 eglMakeCurrent(s_display, s_surface, s_surface, s_context);
147 return true;
148
149_fail2:
150 eglDestroySurface(s_display, s_surface);
151 s_surface = NULL;
152_fail1:
153 eglTerminate(s_display);
154 s_display = NULL;
155_fail0:
156 return false;
157}
158
159static void deinitEgl() {
160 if (s_display) {
161 eglMakeCurrent(s_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
162 if (s_context) {
163 eglDestroyContext(s_display, s_context);
164 }
165 if (s_surface) {
166 eglDestroySurface(s_display, s_surface);
167 }
168 eglTerminate(s_display);
169 }
170}
171
172static void _mapKey(struct mInputMap* map, uint32_t binding, int nativeKey, enum GBAKey key) {
173 mInputBindKey(map, binding, __builtin_ctz(nativeKey), key);
174}
175
176static void _drawStart(void) {
177 glClearColor(0.f, 0.f, 0.f, 1.f);
178 glClear(GL_COLOR_BUFFER_BIT);
179}
180
181static void _drawEnd(void) {
182 if (frameLimiter || framecount >= framecap) {
183 eglSwapBuffers(s_display, s_surface);
184 framecount = 0;
185 }
186}
187
188static uint32_t _pollInput(const struct mInputMap* map) {
189 int keys = 0;
190 hidScanInput();
191 u32 padkeys = hidKeysHeld(CONTROLLER_P1_AUTO);
192 keys |= mInputMapKeyBits(map, AUTO_INPUT, padkeys, 0);
193
194 JoystickPosition jspos;
195 hidJoystickRead(&jspos, CONTROLLER_P1_AUTO, JOYSTICK_LEFT);
196
197 int l = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_LSTICK_LEFT));
198 int r = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_LSTICK_RIGHT));
199 int u = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_LSTICK_UP));
200 int d = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_LSTICK_DOWN));
201
202 if (l == -1) {
203 l = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_DLEFT));
204 }
205 if (r == -1) {
206 r = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_DRIGHT));
207 }
208 if (u == -1) {
209 u = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_DUP));
210 }
211 if (d == -1) {
212 d = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_DDOWN));
213 }
214
215 if (jspos.dx < -ANALOG_DEADZONE && l != -1) {
216 keys |= 1 << l;
217 }
218 if (jspos.dx > ANALOG_DEADZONE && r != -1) {
219 keys |= 1 << r;
220 }
221 if (jspos.dy < -ANALOG_DEADZONE && d != -1) {
222 keys |= 1 << d;
223 }
224 if (jspos.dy > ANALOG_DEADZONE && u != -1) {
225 keys |= 1 << u;
226 }
227 return keys;
228}
229
230static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
231 hidScanInput();
232 if (hidTouchCount() < 1) {
233 return GUI_CURSOR_NOT_PRESENT;
234 }
235 touchPosition touch;
236 hidTouchRead(&touch, 0);
237 *x = touch.px;
238 *y = touch.py;
239 return GUI_CURSOR_DOWN;
240}
241
242
243static void _setup(struct mGUIRunner* runner) {
244 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_A, GBA_KEY_A);
245 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_B, GBA_KEY_B);
246 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_PLUS, GBA_KEY_START);
247 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_MINUS, GBA_KEY_SELECT);
248 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DUP, GBA_KEY_UP);
249 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DDOWN, GBA_KEY_DOWN);
250 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DLEFT, GBA_KEY_LEFT);
251 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DRIGHT, GBA_KEY_RIGHT);
252 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_L, GBA_KEY_L);
253 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_R, GBA_KEY_R);
254
255 int fakeBool;
256 if (mCoreConfigGetIntValue(&runner->config, "hwaccelVideo", &fakeBool) && fakeBool && runner->core->supportsFeature(runner->core, mCORE_FEATURE_OPENGL)) {
257 runner->core->setVideoGLTex(runner->core, tex);
258 usePbo = false;
259 } else {
260 glActiveTexture(GL_TEXTURE0);
261 glBindTexture(GL_TEXTURE_2D, tex);
262 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
263 runner->core->setVideoBuffer(runner->core, frameBuffer, 256);
264 usePbo = true;
265 }
266
267 runner->core->setPeripheral(runner->core, mPERIPH_RUMBLE, &rumble.d);
268 runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation);
269 runner->core->setAVStream(runner->core, &stream);
270
271 unsigned mode;
272 if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
273 screenMode = mode;
274 }
275}
276
277static void _gameLoaded(struct mGUIRunner* runner) {
278 u32 samplerate = audoutGetSampleRate();
279
280 double ratio = GBAAudioCalculateRatio(1, 60.0, 1);
281 blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), samplerate * ratio);
282 blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), samplerate * ratio);
283
284 mCoreConfigGetUIntValue(&runner->config, "fastForwardCap", &framecap);
285
286 unsigned mode;
287 if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
288 screenMode = mode;
289 }
290
291 int fakeBool;
292 if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) {
293 interframeBlending = fakeBool;
294 }
295
296 rumble.up = 0;
297 rumble.down = 0;
298}
299
300static void _gameUnloaded(struct mGUIRunner* runner) {
301 HidVibrationValue values[4];
302 memcpy(&values[0], &vibrationStop, sizeof(rumble.value));
303 memcpy(&values[1], &vibrationStop, sizeof(rumble.value));
304 memcpy(&values[2], &vibrationStop, sizeof(rumble.value));
305 memcpy(&values[3], &vibrationStop, sizeof(rumble.value));
306 hidSendVibrationValues(vibrationDeviceHandles, values, 4);
307}
308
309static void _drawTex(struct mGUIRunner* runner, unsigned width, unsigned height, bool faded, bool blendTop) {
310 glViewport(0, 1080 - vheight, vwidth, vheight);
311 glEnable(GL_BLEND);
312 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
313
314 glUseProgram(program);
315 glBindVertexArray(vao);
316 float aspectX = width / (float) vwidth;
317 float aspectY = height / (float) vheight;
318 float max = 1.f;
319 switch (screenMode) {
320 case SM_PA:
321 if (aspectX > aspectY) {
322 max = floor(1.0 / aspectX);
323 } else {
324 max = floor(1.0 / aspectY);
325 }
326 if (max >= 1.0) {
327 break;
328 }
329 // Fall through
330 case SM_AF:
331 if (aspectX > aspectY) {
332 max = 1.0 / aspectX;
333 } else {
334 max = 1.0 / aspectY;
335 }
336 break;
337 case SM_SF:
338 aspectX = 1.0;
339 aspectY = 1.0;
340 break;
341 }
342
343 aspectX *= max;
344 aspectY *= max;
345
346 glUniform1i(texLocation, 0);
347 glUniform2f(dimsLocation, aspectX, aspectY);
348 if (usePbo) {
349 glUniform2f(insizeLocation, width / 256.f, height / 256.f);
350 } else {
351 glUniform2f(insizeLocation, 1, 1);
352 }
353 if (!faded) {
354 glUniform4f(colorLocation, 1.0f, 1.0f, 1.0f, blendTop ? 0.5f : 1.0f);
355 } else {
356 glUniform4f(colorLocation, 0.8f, 0.8f, 0.8f, blendTop ? 0.4f : 0.8f);
357 }
358
359 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
360
361 glBindVertexArray(0);
362 glUseProgram(0);
363 glDisable(GL_BLEND);
364 glViewport(0, 1080 - runner->params.height, runner->params.width, runner->params.height);
365}
366
367static void _prepareForFrame(struct mGUIRunner* runner) {
368 if (interframeBlending) {
369 glBindFramebuffer(GL_READ_FRAMEBUFFER, copyFbo);
370 glReadBuffer(GL_COLOR_ATTACHMENT0);
371 int width, height;
372 int format;
373 glBindTexture(GL_TEXTURE_2D, tex);
374 glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
375 glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);
376 glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &format);
377 glBindTexture(GL_TEXTURE_2D, oldTex);
378 glCopyTexImage2D(GL_TEXTURE_2D, 0, format, 0, 0, width, height, 0);
379 glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
380 }
381
382 if (usePbo) {
383 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
384 frameBuffer = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, 256 * 256 * 4, GL_MAP_WRITE_BIT);
385 if (frameBuffer) {
386 runner->core->setVideoBuffer(runner->core, frameBuffer, 256);
387 }
388 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
389 }
390}
391
392static void _drawFrame(struct mGUIRunner* runner, bool faded) {
393 ++framecount;
394 if (!frameLimiter && framecount < framecap) {
395 return;
396 }
397
398 unsigned width, height;
399 runner->core->desiredVideoDimensions(runner->core, &width, &height);
400
401 glActiveTexture(GL_TEXTURE0);
402 if (usePbo) {
403 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
404 glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
405
406 glBindTexture(GL_TEXTURE_2D, tex);
407 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, height, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
408 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
409 } else if (!interframeBlending) {
410 glBindTexture(GL_TEXTURE_2D, tex);
411 }
412
413 if (interframeBlending) {
414 glBindTexture(GL_TEXTURE_2D, oldTex);
415 _drawTex(runner, width, height, faded, false);
416 glBindTexture(GL_TEXTURE_2D, tex);
417 _drawTex(runner, width, height, faded, true);
418 } else {
419 _drawTex(runner, width, height, faded, false);
420 }
421
422
423 HidVibrationValue values[4];
424 if (rumble.up) {
425 rumble.value.amp_low = rumble.up / (float) (rumble.up + rumble.down);
426 rumble.value.amp_high = rumble.up / (float) (rumble.up + rumble.down);
427 memcpy(&values[0], &rumble.value, sizeof(rumble.value));
428 memcpy(&values[1], &rumble.value, sizeof(rumble.value));
429 memcpy(&values[2], &rumble.value, sizeof(rumble.value));
430 memcpy(&values[3], &rumble.value, sizeof(rumble.value));
431 } else {
432 memcpy(&values[0], &vibrationStop, sizeof(rumble.value));
433 memcpy(&values[1], &vibrationStop, sizeof(rumble.value));
434 memcpy(&values[2], &vibrationStop, sizeof(rumble.value));
435 memcpy(&values[3], &vibrationStop, sizeof(rumble.value));
436 }
437 hidSendVibrationValues(vibrationDeviceHandles, values, 4);
438 rumble.up = 0;
439 rumble.down = 0;
440}
441
442static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsigned width, unsigned height, bool faded) {
443 glActiveTexture(GL_TEXTURE0);
444 glBindTexture(GL_TEXTURE_2D, tex);
445 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
446
447 _drawTex(runner, width, height, faded, false);
448}
449
450static uint16_t _pollGameInput(struct mGUIRunner* runner) {
451 return _pollInput(&runner->core->inputMap);
452}
453
454static void _incrementScreenMode(struct mGUIRunner* runner) {
455 UNUSED(runner);
456 screenMode = (screenMode + 1) % SM_MAX;
457 mCoreConfigSetUIntValue(&runner->config, "screenMode", screenMode);
458}
459
460static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
461 UNUSED(runner);
462 if (!frameLimiter && limit) {
463 while (enqueuedBuffers > 1) {
464 AudioOutBuffer* releasedBuffers;
465 u32 audoutNReleasedBuffers;
466 audoutWaitPlayFinish(&releasedBuffers, &audoutNReleasedBuffers, 100000000);
467 enqueuedBuffers -= audoutNReleasedBuffers;
468 }
469 }
470 frameLimiter = limit;
471 eglSwapInterval(s_surface, limit);
472}
473
474static bool _running(struct mGUIRunner* runner) {
475 UNUSED(runner);
476 u8 newMode = appletGetOperationMode();
477 if (newMode != vmode) {
478 if (newMode == AppletOperationMode_Docked) {
479 vwidth = 1920;
480 vheight = 1080;
481 } else {
482 vwidth = 1280;
483 vheight = 720;
484 }
485 nwindowSetCrop(nwindowGetDefault(), 0, 0, vwidth, vheight);
486 vmode = newMode;
487 }
488
489 return appletMainLoop();
490}
491
492static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
493 UNUSED(stream);
494 AudioOutBuffer* releasedBuffers;
495 u32 audoutNReleasedBuffers;
496 audoutGetReleasedAudioOutBuffer(&releasedBuffers, &audoutNReleasedBuffers);
497 enqueuedBuffers -= audoutNReleasedBuffers;
498 if (!frameLimiter && enqueuedBuffers >= N_BUFFERS) {
499 blip_clear(left);
500 blip_clear(right);
501 return;
502 }
503 if (enqueuedBuffers >= N_BUFFERS - 1 && R_SUCCEEDED(audoutWaitPlayFinish(&releasedBuffers, &audoutNReleasedBuffers, 10000000))) {
504 enqueuedBuffers -= audoutNReleasedBuffers;
505 }
506
507 struct GBAStereoSample* samples = audioBuffer[audioBufferActive];
508 blip_read_samples(left, &samples[0].left, SAMPLES, true);
509 blip_read_samples(right, &samples[0].right, SAMPLES, true);
510 audoutAppendAudioOutBuffer(&audoutBuffer[audioBufferActive]);
511 audioBufferActive += 1;
512 audioBufferActive %= N_BUFFERS;
513 ++enqueuedBuffers;
514}
515
516void _setRumble(struct mRumble* rumble, int enable) {
517 struct mSwitchRumble* sr = (struct mSwitchRumble*) rumble;
518 if (enable) {
519 ++sr->up;
520 } else {
521 ++sr->down;
522 }
523}
524
525int32_t _readTiltX(struct mRotationSource* source) {
526 UNUSED(source);
527 SixAxisSensorValues sixaxis;
528 hidSixAxisSensorValuesRead(&sixaxis, CONTROLLER_P1_AUTO, 1);
529 return sixaxis.accelerometer.x * 3e8f;
530}
531
532int32_t _readTiltY(struct mRotationSource* source) {
533 UNUSED(source);
534 SixAxisSensorValues sixaxis;
535 hidSixAxisSensorValuesRead(&sixaxis, CONTROLLER_P1_AUTO, 1);
536 return sixaxis.accelerometer.y * -3e8f;
537}
538
539int32_t _readGyroZ(struct mRotationSource* source) {
540 UNUSED(source);
541 SixAxisSensorValues sixaxis;
542 hidSixAxisSensorValuesRead(&sixaxis, CONTROLLER_P1_AUTO, 1);
543 return sixaxis.gyroscope.z * -1.1e9f;
544}
545
546static int _batteryState(void) {
547 u32 charge;
548 int state = 0;
549 if (R_SUCCEEDED(psmGetBatteryChargePercentage(&charge))) {
550 state = (charge + 12) / 25;
551 } else {
552 return BATTERY_NOT_PRESENT;
553 }
554 ChargerType type;
555 if (R_SUCCEEDED(psmGetChargerType(&type)) && type) {
556 state |= BATTERY_CHARGING;
557 }
558 return state;
559}
560
561static void _guiPrepare(void) {
562 glViewport(0, 1080 - vheight, vwidth, vheight);
563}
564
565int main(int argc, char* argv[]) {
566 NWindow* window = nwindowGetDefault();
567 nwindowSetDimensions(window, 1920, 1080);
568
569 socketInitializeDefault();
570 nxlinkStdio();
571 initEgl();
572 romfsInit();
573 audoutInitialize();
574 psmInitialize();
575
576 struct GUIFont* font = GUIFontCreate();
577
578 vmode = appletGetOperationMode();
579 if (vmode == AppletOperationMode_Docked) {
580 vwidth = 1920;
581 vheight = 1080;
582 } else {
583 vwidth = 1280;
584 vheight = 720;
585 }
586 nwindowSetCrop(window, 0, 0, vwidth, vheight);
587
588 glViewport(0, 1080 - vheight, vwidth, vheight);
589 glClearColor(0.f, 0.f, 0.f, 1.f);
590
591 glActiveTexture(GL_TEXTURE0);
592 glGenTextures(1, &tex);
593 glBindTexture(GL_TEXTURE_2D, tex);
594 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
595 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
596 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
597 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
598
599 glGenTextures(1, &oldTex);
600 glBindTexture(GL_TEXTURE_2D, oldTex);
601 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
602 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
603 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
604 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
605
606 glGenBuffers(1, &pbo);
607 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
608 glBufferData(GL_PIXEL_UNPACK_BUFFER, 256 * 256 * 4, NULL, GL_STREAM_DRAW);
609 frameBuffer = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, 256 * 256 * 4, GL_MAP_WRITE_BIT);
610 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
611
612 glGenFramebuffers(1, ©Fbo);
613 glBindTexture(GL_TEXTURE_2D, tex);
614 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
615 glBindTexture(GL_TEXTURE_2D, oldTex);
616 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
617 glBindFramebuffer(GL_READ_FRAMEBUFFER, copyFbo);
618 glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
619 glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
620
621 program = glCreateProgram();
622 GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
623 GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
624 const GLchar* shaderBuffer[2];
625
626 shaderBuffer[0] = _gles2Header;
627
628 shaderBuffer[1] = _vertexShader;
629 glShaderSource(vertexShader, 2, shaderBuffer, NULL);
630
631 shaderBuffer[1] = _fragmentShader;
632 glShaderSource(fragmentShader, 2, shaderBuffer, NULL);
633
634 glAttachShader(program, vertexShader);
635 glAttachShader(program, fragmentShader);
636
637 glCompileShader(fragmentShader);
638
639 GLint success;
640 glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
641 if (!success) {
642 GLchar msg[512];
643 glGetShaderInfoLog(fragmentShader, sizeof(msg), NULL, msg);
644 puts(msg);
645 }
646
647 glCompileShader(vertexShader);
648
649 glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
650 if (!success) {
651 GLchar msg[512];
652 glGetShaderInfoLog(vertexShader, sizeof(msg), NULL, msg);
653 puts(msg);
654 }
655 glLinkProgram(program);
656
657 glDeleteShader(vertexShader);
658 glDeleteShader(fragmentShader);
659
660 texLocation = glGetUniformLocation(program, "tex");
661 colorLocation = glGetUniformLocation(program, "color");
662 dimsLocation = glGetUniformLocation(program, "dims");
663 insizeLocation = glGetUniformLocation(program, "insize");
664 GLuint offsetLocation = glGetAttribLocation(program, "offset");
665
666 glGenBuffers(1, &vbo);
667 glGenVertexArrays(1, &vao);
668 glBindVertexArray(vao);
669 glBindBuffer(GL_ARRAY_BUFFER, vbo);
670 glBufferData(GL_ARRAY_BUFFER, sizeof(_offsets), _offsets, GL_STATIC_DRAW);
671 glVertexAttribPointer(offsetLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
672 glEnableVertexAttribArray(offsetLocation);
673 glBindVertexArray(0);
674
675 rumble.d.setRumble = _setRumble;
676 rumble.value.freq_low = 120.0;
677 rumble.value.freq_high = 180.0;
678 hidInitializeVibrationDevices(&vibrationDeviceHandles[0], 2, CONTROLLER_HANDHELD, TYPE_HANDHELD | TYPE_JOYCON_PAIR);
679 hidInitializeVibrationDevices(&vibrationDeviceHandles[2], 2, CONTROLLER_PLAYER_1, TYPE_HANDHELD | TYPE_JOYCON_PAIR);
680
681 u32 handles[4];
682 hidGetSixAxisSensorHandles(&handles[0], 2, CONTROLLER_PLAYER_1, TYPE_JOYCON_PAIR);
683 hidGetSixAxisSensorHandles(&handles[2], 1, CONTROLLER_PLAYER_1, TYPE_PROCONTROLLER);
684 hidGetSixAxisSensorHandles(&handles[3], 1, CONTROLLER_HANDHELD, TYPE_HANDHELD);
685 hidStartSixAxisSensor(handles[0]);
686 hidStartSixAxisSensor(handles[1]);
687 hidStartSixAxisSensor(handles[2]);
688 hidStartSixAxisSensor(handles[3]);
689 rotation.readTiltX = _readTiltX;
690 rotation.readTiltY = _readTiltY;
691 rotation.readGyroZ = _readGyroZ;
692
693 stream.videoDimensionsChanged = NULL;
694 stream.postVideoFrame = NULL;
695 stream.postAudioFrame = NULL;
696 stream.postAudioBuffer = _postAudioBuffer;
697
698 memset(audioBuffer, 0, sizeof(audioBuffer));
699 audioBufferActive = 0;
700 enqueuedBuffers = 0;
701 size_t i;
702 for (i = 0; i < N_BUFFERS; ++i) {
703 audoutBuffer[i].next = NULL;
704 audoutBuffer[i].buffer = audioBuffer[i];
705 audoutBuffer[i].buffer_size = BUFFER_SIZE;
706 audoutBuffer[i].data_size = BUFFER_SIZE;
707 audoutBuffer[i].data_offset = 0;
708 }
709
710 struct mGUIRunner runner = {
711 .params = {
712 1280, 720,
713 font, "/",
714 _drawStart, _drawEnd,
715 _pollInput, _pollCursor,
716 _batteryState,
717 _guiPrepare, NULL,
718 },
719 .keySources = (struct GUIInputKeys[]) {
720 {
721 .name = "Controller Input",
722 .id = AUTO_INPUT,
723 .keyNames = (const char*[]) {
724 "A",
725 "B",
726 "X",
727 "Y",
728 "L Stick",
729 "R Stick",
730 "L",
731 "R",
732 "ZL",
733 "ZR",
734 "+",
735 "-",
736 "Left",
737 "Up",
738 "Right",
739 "Down",
740 "L Left",
741 "L Up",
742 "L Right",
743 "L Down",
744 "R Left",
745 "R Up",
746 "R Right",
747 "R Down",
748 "SL",
749 "SR"
750 },
751 .nKeys = 26
752 },
753 { .id = 0 }
754 },
755 .configExtra = (struct GUIMenuItem[]) {
756 {
757 .title = "Screen mode",
758 .data = "screenMode",
759 .submenu = 0,
760 .state = SM_PA,
761 .validStates = (const char*[]) {
762 "Pixel-Accurate",
763 "Aspect-Ratio Fit",
764 "Stretched",
765 },
766 .nStates = 3
767 },
768 {
769 .title = "Fast forward cap",
770 .data = "fastForwardCap",
771 .submenu = 0,
772 .state = 7,
773 .validStates = (const char*[]) {
774 "2", "3", "4", "5", "6", "7", "8", "9",
775 "10", "11", "12", "13", "14", "15",
776 "20", "30"
777 },
778 .stateMappings = (const struct GUIVariant[]) {
779 GUI_V_U(2),
780 GUI_V_U(3),
781 GUI_V_U(4),
782 GUI_V_U(5),
783 GUI_V_U(6),
784 GUI_V_U(7),
785 GUI_V_U(8),
786 GUI_V_U(9),
787 GUI_V_U(10),
788 GUI_V_U(11),
789 GUI_V_U(12),
790 GUI_V_U(13),
791 GUI_V_U(14),
792 GUI_V_U(15),
793 GUI_V_U(20),
794 GUI_V_U(30),
795 },
796 .nStates = 16
797 },
798 {
799 .title = "GPU-accelerated renderer (experimental, requires game reload)",
800 .data = "hwaccelVideo",
801 .submenu = 0,
802 .state = 0,
803 .validStates = (const char*[]) {
804 "Off",
805 "On",
806 },
807 .nStates = 2
808 },
809 {
810 .title = "Hi-res scaling (requires GPU rendering)",
811 .data = "videoScale",
812 .submenu = 0,
813 .state = 0,
814 .validStates = (const char*[]) {
815 "1x",
816 "2x",
817 "3x",
818 "4x",
819 "5x",
820 "6x",
821 },
822 .stateMappings = (const struct GUIVariant[]) {
823 GUI_V_U(1),
824 GUI_V_U(2),
825 GUI_V_U(3),
826 GUI_V_U(4),
827 GUI_V_U(5),
828 GUI_V_U(6),
829 },
830 .nStates = 6
831 },
832 },
833 .nConfigExtra = 4,
834 .setup = _setup,
835 .teardown = NULL,
836 .gameLoaded = _gameLoaded,
837 .gameUnloaded = _gameUnloaded,
838 .prepareForFrame = _prepareForFrame,
839 .drawFrame = _drawFrame,
840 .drawScreenshot = _drawScreenshot,
841 .paused = _gameUnloaded,
842 .unpaused = _gameLoaded,
843 .incrementScreenMode = _incrementScreenMode,
844 .setFrameLimiter = _setFrameLimiter,
845 .pollGameInput = _pollGameInput,
846 .running = _running
847 };
848 mGUIInit(&runner, "switch");
849
850 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_A, GUI_INPUT_SELECT);
851 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_B, GUI_INPUT_BACK);
852 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_X, GUI_INPUT_CANCEL);
853 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DUP, GUI_INPUT_UP);
854 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DDOWN, GUI_INPUT_DOWN);
855 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DLEFT, GUI_INPUT_LEFT);
856 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DRIGHT, GUI_INPUT_RIGHT);
857
858 audoutStartAudioOut();
859
860 if (argc > 1) {
861 size_t i;
862 for (i = 0; runner.keySources[i].id; ++i) {
863 mInputMapLoad(&runner.params.keyMap, runner.keySources[i].id, mCoreConfigGetInput(&runner.config));
864 }
865 mGUIRun(&runner, argv[1]);
866 } else {
867 mGUIRunloop(&runner);
868 }
869
870 mGUIDeinit(&runner);
871
872 audoutStopAudioOut();
873 GUIFontDestroy(font);
874
875 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
876 glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
877 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
878 glDeleteBuffers(1, &pbo);
879
880 glDeleteFramebuffers(1, ©Fbo);
881 glDeleteTextures(1, &tex);
882 glDeleteTextures(1, &oldTex);
883 glDeleteBuffers(1, &vbo);
884 glDeleteProgram(program);
885 glDeleteVertexArrays(1, &vao);
886
887 hidStopSixAxisSensor(handles[0]);
888 hidStopSixAxisSensor(handles[1]);
889 hidStopSixAxisSensor(handles[2]);
890 hidStopSixAxisSensor(handles[3]);
891
892 psmExit();
893 audoutExit();
894 romfsExit();
895 deinitEgl();
896 socketExit();
897 return 0;
898}