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 mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool);
293 interframeBlending = fakeBool;
294
295 rumble.up = 0;
296 rumble.down = 0;
297}
298
299static void _gameUnloaded(struct mGUIRunner* runner) {
300 HidVibrationValue values[4];
301 memcpy(&values[0], &vibrationStop, sizeof(rumble.value));
302 memcpy(&values[1], &vibrationStop, sizeof(rumble.value));
303 memcpy(&values[2], &vibrationStop, sizeof(rumble.value));
304 memcpy(&values[3], &vibrationStop, sizeof(rumble.value));
305 hidSendVibrationValues(vibrationDeviceHandles, values, 4);
306}
307
308static void _drawTex(struct mGUIRunner* runner, unsigned width, unsigned height, bool faded, bool blendTop) {
309 glViewport(0, 1080 - vheight, vwidth, vheight);
310 glEnable(GL_BLEND);
311 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
312
313 glUseProgram(program);
314 glBindVertexArray(vao);
315 float aspectX = width / (float) vwidth;
316 float aspectY = height / (float) vheight;
317 float max = 1.f;
318 switch (screenMode) {
319 case SM_PA:
320 if (aspectX > aspectY) {
321 max = floor(1.0 / aspectX);
322 } else {
323 max = floor(1.0 / aspectY);
324 }
325 if (max >= 1.0) {
326 break;
327 }
328 // Fall through
329 case SM_AF:
330 if (aspectX > aspectY) {
331 max = 1.0 / aspectX;
332 } else {
333 max = 1.0 / aspectY;
334 }
335 break;
336 case SM_SF:
337 aspectX = 1.0;
338 aspectY = 1.0;
339 break;
340 }
341
342 aspectX *= max;
343 aspectY *= max;
344
345 glUniform1i(texLocation, 0);
346 glUniform2f(dimsLocation, aspectX, aspectY);
347 if (usePbo) {
348 glUniform2f(insizeLocation, width / 256.f, height / 256.f);
349 } else {
350 glUniform2f(insizeLocation, 1, 1);
351 }
352 if (!faded) {
353 glUniform4f(colorLocation, 1.0f, 1.0f, 1.0f, blendTop ? 0.5f : 1.0f);
354 } else {
355 glUniform4f(colorLocation, 0.8f, 0.8f, 0.8f, blendTop ? 0.4f : 0.8f);
356 }
357
358 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
359
360 glBindVertexArray(0);
361 glUseProgram(0);
362 glDisable(GL_BLEND);
363 glViewport(0, 1080 - runner->params.height, runner->params.width, runner->params.height);
364}
365
366static void _prepareForFrame(struct mGUIRunner* runner) {
367 if (interframeBlending) {
368 glBindFramebuffer(GL_READ_FRAMEBUFFER, copyFbo);
369 glReadBuffer(GL_COLOR_ATTACHMENT0);
370 int width, height;
371 int format;
372 glBindTexture(GL_TEXTURE_2D, tex);
373 glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
374 glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);
375 glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &format);
376 glBindTexture(GL_TEXTURE_2D, oldTex);
377 glCopyTexImage2D(GL_TEXTURE_2D, 0, format, 0, 0, width, height, 0);
378 glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
379 }
380
381 if (usePbo) {
382 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
383 frameBuffer = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, 256 * 256 * 4, GL_MAP_WRITE_BIT);
384 if (frameBuffer) {
385 runner->core->setVideoBuffer(runner->core, frameBuffer, 256);
386 }
387 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
388 }
389}
390
391static void _drawFrame(struct mGUIRunner* runner, bool faded) {
392 ++framecount;
393 if (!frameLimiter && framecount < framecap) {
394 return;
395 }
396
397 unsigned width, height;
398 runner->core->desiredVideoDimensions(runner->core, &width, &height);
399
400 glActiveTexture(GL_TEXTURE0);
401 if (usePbo) {
402 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
403 glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
404
405 glBindTexture(GL_TEXTURE_2D, tex);
406 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, height, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
407 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
408 } else if (!interframeBlending) {
409 glBindTexture(GL_TEXTURE_2D, tex);
410 }
411
412 if (interframeBlending) {
413 glBindTexture(GL_TEXTURE_2D, oldTex);
414 _drawTex(runner, width, height, faded, false);
415 glBindTexture(GL_TEXTURE_2D, tex);
416 _drawTex(runner, width, height, faded, true);
417 } else {
418 _drawTex(runner, width, height, faded, false);
419 }
420
421
422 HidVibrationValue values[4];
423 if (rumble.up) {
424 rumble.value.amp_low = rumble.up / (float) (rumble.up + rumble.down);
425 rumble.value.amp_high = rumble.up / (float) (rumble.up + rumble.down);
426 memcpy(&values[0], &rumble.value, sizeof(rumble.value));
427 memcpy(&values[1], &rumble.value, sizeof(rumble.value));
428 memcpy(&values[2], &rumble.value, sizeof(rumble.value));
429 memcpy(&values[3], &rumble.value, sizeof(rumble.value));
430 } else {
431 memcpy(&values[0], &vibrationStop, sizeof(rumble.value));
432 memcpy(&values[1], &vibrationStop, sizeof(rumble.value));
433 memcpy(&values[2], &vibrationStop, sizeof(rumble.value));
434 memcpy(&values[3], &vibrationStop, sizeof(rumble.value));
435 }
436 hidSendVibrationValues(vibrationDeviceHandles, values, 4);
437 rumble.up = 0;
438 rumble.down = 0;
439}
440
441static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsigned width, unsigned height, bool faded) {
442 glActiveTexture(GL_TEXTURE0);
443 glBindTexture(GL_TEXTURE_2D, tex);
444 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
445
446 _drawTex(runner, width, height, faded, false);
447}
448
449static uint16_t _pollGameInput(struct mGUIRunner* runner) {
450 return _pollInput(&runner->core->inputMap);
451}
452
453static void _incrementScreenMode(struct mGUIRunner* runner) {
454 UNUSED(runner);
455 screenMode = (screenMode + 1) % SM_MAX;
456 mCoreConfigSetUIntValue(&runner->config, "screenMode", screenMode);
457}
458
459static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
460 UNUSED(runner);
461 if (!frameLimiter && limit) {
462 while (enqueuedBuffers > 1) {
463 AudioOutBuffer* releasedBuffers;
464 u32 audoutNReleasedBuffers;
465 audoutWaitPlayFinish(&releasedBuffers, &audoutNReleasedBuffers, 100000000);
466 enqueuedBuffers -= audoutNReleasedBuffers;
467 }
468 }
469 frameLimiter = limit;
470 eglSwapInterval(s_surface, limit);
471}
472
473static bool _running(struct mGUIRunner* runner) {
474 UNUSED(runner);
475 u8 newMode = appletGetOperationMode();
476 if (newMode != vmode) {
477 if (newMode == AppletOperationMode_Docked) {
478 vwidth = 1920;
479 vheight = 1080;
480 } else {
481 vwidth = 1280;
482 vheight = 720;
483 }
484 nwindowSetCrop(nwindowGetDefault(), 0, 0, vwidth, vheight);
485 vmode = newMode;
486 }
487
488 return appletMainLoop();
489}
490
491static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
492 UNUSED(stream);
493 AudioOutBuffer* releasedBuffers;
494 u32 audoutNReleasedBuffers;
495 audoutGetReleasedAudioOutBuffer(&releasedBuffers, &audoutNReleasedBuffers);
496 enqueuedBuffers -= audoutNReleasedBuffers;
497 if (!frameLimiter && enqueuedBuffers >= N_BUFFERS) {
498 blip_clear(left);
499 blip_clear(right);
500 return;
501 }
502 if (enqueuedBuffers >= N_BUFFERS - 1 && R_SUCCEEDED(audoutWaitPlayFinish(&releasedBuffers, &audoutNReleasedBuffers, 10000000))) {
503 enqueuedBuffers -= audoutNReleasedBuffers;
504 }
505
506 struct GBAStereoSample* samples = audioBuffer[audioBufferActive];
507 blip_read_samples(left, &samples[0].left, SAMPLES, true);
508 blip_read_samples(right, &samples[0].right, SAMPLES, true);
509 audoutAppendAudioOutBuffer(&audoutBuffer[audioBufferActive]);
510 audioBufferActive += 1;
511 audioBufferActive %= N_BUFFERS;
512 ++enqueuedBuffers;
513}
514
515void _setRumble(struct mRumble* rumble, int enable) {
516 struct mSwitchRumble* sr = (struct mSwitchRumble*) rumble;
517 if (enable) {
518 ++sr->up;
519 } else {
520 ++sr->down;
521 }
522}
523
524int32_t _readTiltX(struct mRotationSource* source) {
525 UNUSED(source);
526 SixAxisSensorValues sixaxis;
527 hidSixAxisSensorValuesRead(&sixaxis, CONTROLLER_P1_AUTO, 1);
528 return sixaxis.accelerometer.x * 3e8f;
529}
530
531int32_t _readTiltY(struct mRotationSource* source) {
532 UNUSED(source);
533 SixAxisSensorValues sixaxis;
534 hidSixAxisSensorValuesRead(&sixaxis, CONTROLLER_P1_AUTO, 1);
535 return sixaxis.accelerometer.y * -3e8f;
536}
537
538int32_t _readGyroZ(struct mRotationSource* source) {
539 UNUSED(source);
540 SixAxisSensorValues sixaxis;
541 hidSixAxisSensorValuesRead(&sixaxis, CONTROLLER_P1_AUTO, 1);
542 return sixaxis.gyroscope.z * -1.1e9f;
543}
544
545static int _batteryState(void) {
546 u32 charge;
547 int state = 0;
548 if (R_SUCCEEDED(psmGetBatteryChargePercentage(&charge))) {
549 state = (charge + 12) / 25;
550 } else {
551 return BATTERY_NOT_PRESENT;
552 }
553 ChargerType type;
554 if (R_SUCCEEDED(psmGetChargerType(&type)) && type) {
555 state |= BATTERY_CHARGING;
556 }
557 return state;
558}
559
560static void _guiPrepare(void) {
561 glViewport(0, 1080 - vheight, vwidth, vheight);
562}
563
564int main(int argc, char* argv[]) {
565 NWindow* window = nwindowGetDefault();
566 nwindowSetDimensions(window, 1920, 1080);
567
568 socketInitializeDefault();
569 nxlinkStdio();
570 initEgl();
571 romfsInit();
572 audoutInitialize();
573 psmInitialize();
574
575 struct GUIFont* font = GUIFontCreate();
576
577 vmode = appletGetOperationMode();
578 if (vmode == AppletOperationMode_Docked) {
579 vwidth = 1920;
580 vheight = 1080;
581 } else {
582 vwidth = 1280;
583 vheight = 720;
584 }
585 nwindowSetCrop(window, 0, 0, vwidth, vheight);
586
587 glViewport(0, 1080 - vheight, vwidth, vheight);
588 glClearColor(0.f, 0.f, 0.f, 1.f);
589
590 glActiveTexture(GL_TEXTURE0);
591 glGenTextures(1, &tex);
592 glBindTexture(GL_TEXTURE_2D, tex);
593 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
594 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
595 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
596 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
597
598 glGenTextures(1, &oldTex);
599 glBindTexture(GL_TEXTURE_2D, oldTex);
600 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
601 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
602 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
603 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
604
605 glGenBuffers(1, &pbo);
606 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
607 glBufferData(GL_PIXEL_UNPACK_BUFFER, 256 * 256 * 4, NULL, GL_STREAM_DRAW);
608 frameBuffer = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, 256 * 256 * 4, GL_MAP_WRITE_BIT);
609 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
610
611 glGenFramebuffers(1, ©Fbo);
612 glBindTexture(GL_TEXTURE_2D, tex);
613 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
614 glBindTexture(GL_TEXTURE_2D, oldTex);
615 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
616 glBindFramebuffer(GL_READ_FRAMEBUFFER, copyFbo);
617 glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
618 glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
619
620 program = glCreateProgram();
621 GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
622 GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
623 const GLchar* shaderBuffer[2];
624
625 shaderBuffer[0] = _gles2Header;
626
627 shaderBuffer[1] = _vertexShader;
628 glShaderSource(vertexShader, 2, shaderBuffer, NULL);
629
630 shaderBuffer[1] = _fragmentShader;
631 glShaderSource(fragmentShader, 2, shaderBuffer, NULL);
632
633 glAttachShader(program, vertexShader);
634 glAttachShader(program, fragmentShader);
635
636 glCompileShader(fragmentShader);
637
638 GLint success;
639 glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
640 if (!success) {
641 GLchar msg[512];
642 glGetShaderInfoLog(fragmentShader, sizeof(msg), NULL, msg);
643 puts(msg);
644 }
645
646 glCompileShader(vertexShader);
647
648 glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
649 if (!success) {
650 GLchar msg[512];
651 glGetShaderInfoLog(vertexShader, sizeof(msg), NULL, msg);
652 puts(msg);
653 }
654 glLinkProgram(program);
655
656 glDeleteShader(vertexShader);
657 glDeleteShader(fragmentShader);
658
659 texLocation = glGetUniformLocation(program, "tex");
660 colorLocation = glGetUniformLocation(program, "color");
661 dimsLocation = glGetUniformLocation(program, "dims");
662 insizeLocation = glGetUniformLocation(program, "insize");
663 GLuint offsetLocation = glGetAttribLocation(program, "offset");
664
665 glGenBuffers(1, &vbo);
666 glGenVertexArrays(1, &vao);
667 glBindVertexArray(vao);
668 glBindBuffer(GL_ARRAY_BUFFER, vbo);
669 glBufferData(GL_ARRAY_BUFFER, sizeof(_offsets), _offsets, GL_STATIC_DRAW);
670 glVertexAttribPointer(offsetLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
671 glEnableVertexAttribArray(offsetLocation);
672 glBindVertexArray(0);
673
674 rumble.d.setRumble = _setRumble;
675 rumble.value.freq_low = 120.0;
676 rumble.value.freq_high = 180.0;
677 hidInitializeVibrationDevices(&vibrationDeviceHandles[0], 2, CONTROLLER_HANDHELD, TYPE_HANDHELD | TYPE_JOYCON_PAIR);
678 hidInitializeVibrationDevices(&vibrationDeviceHandles[2], 2, CONTROLLER_PLAYER_1, TYPE_HANDHELD | TYPE_JOYCON_PAIR);
679
680 u32 handles[4];
681 hidGetSixAxisSensorHandles(&handles[0], 2, CONTROLLER_PLAYER_1, TYPE_JOYCON_PAIR);
682 hidGetSixAxisSensorHandles(&handles[2], 1, CONTROLLER_PLAYER_1, TYPE_PROCONTROLLER);
683 hidGetSixAxisSensorHandles(&handles[3], 1, CONTROLLER_HANDHELD, TYPE_HANDHELD);
684 hidStartSixAxisSensor(handles[0]);
685 hidStartSixAxisSensor(handles[1]);
686 hidStartSixAxisSensor(handles[2]);
687 hidStartSixAxisSensor(handles[3]);
688 rotation.readTiltX = _readTiltX;
689 rotation.readTiltY = _readTiltY;
690 rotation.readGyroZ = _readGyroZ;
691
692 stream.videoDimensionsChanged = NULL;
693 stream.postVideoFrame = NULL;
694 stream.postAudioFrame = NULL;
695 stream.postAudioBuffer = _postAudioBuffer;
696
697 memset(audioBuffer, 0, sizeof(audioBuffer));
698 audioBufferActive = 0;
699 enqueuedBuffers = 0;
700 size_t i;
701 for (i = 0; i < N_BUFFERS; ++i) {
702 audoutBuffer[i].next = NULL;
703 audoutBuffer[i].buffer = audioBuffer[i];
704 audoutBuffer[i].buffer_size = BUFFER_SIZE;
705 audoutBuffer[i].data_size = BUFFER_SIZE;
706 audoutBuffer[i].data_offset = 0;
707 }
708
709 struct mGUIRunner runner = {
710 .params = {
711 1280, 720,
712 font, "/",
713 _drawStart, _drawEnd,
714 _pollInput, _pollCursor,
715 _batteryState,
716 _guiPrepare, NULL,
717 },
718 .keySources = (struct GUIInputKeys[]) {
719 {
720 .name = "Controller Input",
721 .id = AUTO_INPUT,
722 .keyNames = (const char*[]) {
723 "A",
724 "B",
725 "X",
726 "Y",
727 "L Stick",
728 "R Stick",
729 "L",
730 "R",
731 "ZL",
732 "ZR",
733 "+",
734 "-",
735 "Left",
736 "Up",
737 "Right",
738 "Down",
739 "L Left",
740 "L Up",
741 "L Right",
742 "L Down",
743 "R Left",
744 "R Up",
745 "R Right",
746 "R Down",
747 "SL",
748 "SR"
749 },
750 .nKeys = 26
751 },
752 { .id = 0 }
753 },
754 .configExtra = (struct GUIMenuItem[]) {
755 {
756 .title = "Screen mode",
757 .data = "screenMode",
758 .submenu = 0,
759 .state = SM_PA,
760 .validStates = (const char*[]) {
761 "Pixel-Accurate",
762 "Aspect-Ratio Fit",
763 "Stretched",
764 },
765 .nStates = 3
766 },
767 {
768 .title = "Fast forward cap",
769 .data = "fastForwardCap",
770 .submenu = 0,
771 .state = 7,
772 .validStates = (const char*[]) {
773 "2", "3", "4", "5", "6", "7", "8", "9",
774 "10", "11", "12", "13", "14", "15",
775 "20", "30"
776 },
777 .stateMappings = (const struct GUIVariant[]) {
778 GUI_V_U(2),
779 GUI_V_U(3),
780 GUI_V_U(4),
781 GUI_V_U(5),
782 GUI_V_U(6),
783 GUI_V_U(7),
784 GUI_V_U(8),
785 GUI_V_U(9),
786 GUI_V_U(10),
787 GUI_V_U(11),
788 GUI_V_U(12),
789 GUI_V_U(13),
790 GUI_V_U(14),
791 GUI_V_U(15),
792 GUI_V_U(20),
793 GUI_V_U(30),
794 },
795 .nStates = 16
796 },
797 {
798 .title = "GPU-accelerated renderer (experimental, requires game reload)",
799 .data = "hwaccelVideo",
800 .submenu = 0,
801 .state = 0,
802 .validStates = (const char*[]) {
803 "Off",
804 "On",
805 },
806 .nStates = 2
807 },
808 {
809 .title = "Hi-res scaling (requires GPU rendering)",
810 .data = "videoScale",
811 .submenu = 0,
812 .state = 0,
813 .validStates = (const char*[]) {
814 "1x",
815 "2x",
816 "3x",
817 "4x",
818 "5x",
819 "6x",
820 },
821 .stateMappings = (const struct GUIVariant[]) {
822 GUI_V_U(1),
823 GUI_V_U(2),
824 GUI_V_U(3),
825 GUI_V_U(4),
826 GUI_V_U(5),
827 GUI_V_U(6),
828 },
829 .nStates = 6
830 },
831 },
832 .nConfigExtra = 4,
833 .setup = _setup,
834 .teardown = NULL,
835 .gameLoaded = _gameLoaded,
836 .gameUnloaded = _gameUnloaded,
837 .prepareForFrame = _prepareForFrame,
838 .drawFrame = _drawFrame,
839 .drawScreenshot = _drawScreenshot,
840 .paused = _gameUnloaded,
841 .unpaused = _gameLoaded,
842 .incrementScreenMode = _incrementScreenMode,
843 .setFrameLimiter = _setFrameLimiter,
844 .pollGameInput = _pollGameInput,
845 .running = _running
846 };
847 mGUIInit(&runner, "switch");
848
849 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_A, GUI_INPUT_SELECT);
850 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_B, GUI_INPUT_BACK);
851 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_X, GUI_INPUT_CANCEL);
852 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DUP, GUI_INPUT_UP);
853 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DDOWN, GUI_INPUT_DOWN);
854 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DLEFT, GUI_INPUT_LEFT);
855 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DRIGHT, GUI_INPUT_RIGHT);
856
857 audoutStartAudioOut();
858
859 if (argc > 1) {
860 size_t i;
861 for (i = 0; runner.keySources[i].id; ++i) {
862 mInputMapLoad(&runner.params.keyMap, runner.keySources[i].id, mCoreConfigGetInput(&runner.config));
863 }
864 mGUIRun(&runner, argv[1]);
865 } else {
866 mGUIRunloop(&runner);
867 }
868
869 mGUIDeinit(&runner);
870
871 audoutStopAudioOut();
872 GUIFontDestroy(font);
873
874 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
875 glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
876 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
877 glDeleteBuffers(1, &pbo);
878
879 glDeleteFramebuffers(1, ©Fbo);
880 glDeleteTextures(1, &tex);
881 glDeleteTextures(1, &oldTex);
882 glDeleteBuffers(1, &vbo);
883 glDeleteProgram(program);
884 glDeleteVertexArrays(1, &vao);
885
886 hidStopSixAxisSensor(handles[0]);
887 hidStopSixAxisSensor(handles[1]);
888 hidStopSixAxisSensor(handles[2]);
889 hidStopSixAxisSensor(handles[3]);
890
891 psmExit();
892 audoutExit();
893 romfsExit();
894 deinitEgl();
895 socketExit();
896 return 0;
897}