all repos — mgba @ 927f8b0d8859fbfb8fe3f75e8ff8460ff74adf26

mGBA Game Boy Advance Emulator

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
 19#define AUTO_INPUT 0x4E585031
 20#define SAMPLES 0x400
 21#define BUFFER_SIZE 0x1000
 22#define N_BUFFERS 4
 23#define ANALOG_DEADZONE 0x4000
 24
 25TimeType __nx_time_type = TimeType_UserSystemClock;
 26
 27static EGLDisplay s_display;
 28static EGLContext s_context;
 29static EGLSurface s_surface;
 30
 31static const GLfloat _offsets[] = {
 32	0.f, 0.f,
 33	1.f, 0.f,
 34	1.f, 1.f,
 35	0.f, 1.f,
 36};
 37
 38static const GLchar* const _gles2Header =
 39	"#version 100\n"
 40	"precision mediump float;\n";
 41
 42static const char* const _vertexShader =
 43	"attribute vec2 offset;\n"
 44	"uniform vec2 dims;\n"
 45	"uniform vec2 insize;\n"
 46	"varying vec2 texCoord;\n"
 47
 48	"void main() {\n"
 49	"	vec2 ratio = insize / 256.0;\n"
 50	"	vec2 scaledOffset = offset * dims;\n"
 51	"	gl_Position = vec4(scaledOffset.x * 2.0 - dims.x, scaledOffset.y * -2.0 + dims.y, 0.0, 1.0);\n"
 52	"	texCoord = offset * ratio;\n"
 53	"}";
 54
 55static const char* const _fragmentShader =
 56	"varying vec2 texCoord;\n"
 57	"uniform sampler2D tex;\n"
 58	"uniform vec4 color;\n"
 59
 60	"void main() {\n"
 61	"	vec4 texColor = vec4(texture2D(tex, texCoord).rgb, 1.0);\n"
 62	"	texColor *= color;\n"
 63	"	gl_FragColor = texColor;\n"
 64	"}";
 65
 66static GLuint program;
 67static GLuint vbo;
 68static GLuint vao;
 69static GLuint pbo;
 70static GLuint texLocation;
 71static GLuint dimsLocation;
 72static GLuint insizeLocation;
 73static GLuint colorLocation;
 74static GLuint tex;
 75
 76static color_t* frameBuffer;
 77static struct mAVStream stream;
 78static int audioBufferActive;
 79static struct GBAStereoSample audioBuffer[N_BUFFERS][SAMPLES] __attribute__((__aligned__(0x1000)));
 80static AudioOutBuffer audoutBuffer[N_BUFFERS];
 81static int enqueuedBuffers;
 82static bool frameLimiter = true;
 83static unsigned framecount = 0;
 84static unsigned framecap = 10;
 85
 86static bool initEgl() {
 87    s_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
 88    if (!s_display) {
 89        goto _fail0;
 90    }
 91
 92    eglInitialize(s_display, NULL, NULL);
 93
 94    EGLConfig config;
 95    EGLint numConfigs;
 96    static const EGLint attributeList[] = {
 97        EGL_RED_SIZE, 1,
 98        EGL_GREEN_SIZE, 1,
 99        EGL_BLUE_SIZE, 1,
100        EGL_NONE
101    };
102    eglChooseConfig(s_display, attributeList, &config, 1, &numConfigs);
103    if (!numConfigs) {
104        goto _fail1;
105    }
106
107    s_surface = eglCreateWindowSurface(s_display, config, "", NULL);
108    if (!s_surface) {
109        goto _fail1;
110    }
111
112	EGLint contextAttributeList[] = {
113		EGL_CONTEXT_CLIENT_VERSION, 3,
114		EGL_NONE
115	};
116    s_context = eglCreateContext(s_display, config, EGL_NO_CONTEXT, contextAttributeList);
117    if (!s_context) {
118        goto _fail2;
119    }
120
121    eglMakeCurrent(s_display, s_surface, s_surface, s_context);
122    return true;
123
124_fail2:
125    eglDestroySurface(s_display, s_surface);
126    s_surface = NULL;
127_fail1:
128    eglTerminate(s_display);
129    s_display = NULL;
130_fail0:
131    return false;
132}
133
134static void deinitEgl() {
135    if (s_display) {
136        if (s_context) {
137            eglDestroyContext(s_display, s_context);
138        }
139        if (s_surface) {
140            eglDestroySurface(s_display, s_surface);
141        }
142        eglTerminate(s_display);
143    }
144}
145
146static void _mapKey(struct mInputMap* map, uint32_t binding, int nativeKey, enum GBAKey key) {
147	mInputBindKey(map, binding, __builtin_ctz(nativeKey), key);
148}
149
150static void _drawStart(void) {
151	glClear(GL_COLOR_BUFFER_BIT);
152}
153
154static void _drawEnd(void) {
155	if (frameLimiter || framecount >= framecap) {
156		eglSwapBuffers(s_display, s_surface);
157		framecount = 0;
158	}
159}
160
161static uint32_t _pollInput(const struct mInputMap* map) {
162	int keys = 0;
163	hidScanInput();
164	u32 padkeys = hidKeysHeld(CONTROLLER_P1_AUTO);
165	keys |= mInputMapKeyBits(map, AUTO_INPUT, padkeys, 0);
166
167	JoystickPosition jspos;
168	hidJoystickRead(&jspos, CONTROLLER_P1_AUTO, JOYSTICK_LEFT);
169
170	int l = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_LSTICK_LEFT));
171	int r = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_LSTICK_RIGHT));
172	int u = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_LSTICK_UP));
173	int d = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_LSTICK_DOWN));
174
175	if (l == -1) {
176		l = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_DLEFT));
177	}
178	if (r == -1) {
179		r = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_DRIGHT));
180	}
181	if (u == -1) {
182		u = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_DUP));
183	}
184	if (d == -1) {
185		d = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_DDOWN));
186	}
187
188	if (jspos.dx < -ANALOG_DEADZONE && l != -1) {
189		keys |= 1 << l;
190	}
191	if (jspos.dx > ANALOG_DEADZONE && r != -1) {
192		keys |= 1 << r;
193	}
194	if (jspos.dy < -ANALOG_DEADZONE && d != -1) {
195		keys |= 1 << d;
196	}
197	if (jspos.dy > ANALOG_DEADZONE && u != -1) {
198		keys |= 1 << u;
199	}
200	return keys;
201}
202
203static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
204	hidScanInput();
205	if (hidTouchCount() < 1) {
206		return GUI_CURSOR_NOT_PRESENT;
207	}
208	touchPosition touch;
209	hidTouchRead(&touch, 0);
210	*x = touch.px;
211	*y = touch.py;
212	return GUI_CURSOR_DOWN;
213}
214
215
216static void _setup(struct mGUIRunner* runner) {
217	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_A, GBA_KEY_A);
218	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_B, GBA_KEY_B);
219	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_PLUS, GBA_KEY_START);
220	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_MINUS, GBA_KEY_SELECT);
221	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DUP, GBA_KEY_UP);
222	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DDOWN, GBA_KEY_DOWN);
223	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DLEFT, GBA_KEY_LEFT);
224	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DRIGHT, GBA_KEY_RIGHT);
225	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_L, GBA_KEY_L);
226	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_R, GBA_KEY_R);
227
228	runner->core->setVideoBuffer(runner->core, frameBuffer, 256);
229	runner->core->setAVStream(runner->core, &stream);
230}
231
232static void _gameLoaded(struct mGUIRunner* runner) {
233	u32 samplerate = audoutGetSampleRate();
234
235	double ratio = GBAAudioCalculateRatio(1, 60.0, 1);
236	blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), samplerate * ratio);
237	blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), samplerate * ratio);
238
239	mCoreConfigGetUIntValue(&runner->config, "fastForwardCap", &framecap);
240}
241
242static void _drawTex(struct mGUIRunner* runner, unsigned width, unsigned height, bool faded) {
243	glEnable(GL_BLEND);
244	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
245
246	glUseProgram(program);
247	glBindVertexArray(vao);
248	float aspectX = width / (float) runner->params.width;
249	float aspectY = height / (float) runner->params.height;
250	float max;
251	if (aspectX > aspectY) {
252		max = floor(1.0 / aspectX);
253	} else {
254		max = floor(1.0 / aspectY);
255	}
256
257	aspectX *= max;
258	aspectY *= max;
259
260	glUniform1i(texLocation, 0);
261	glUniform2f(dimsLocation, aspectX, aspectY);
262	glUniform2f(insizeLocation, width, height);
263	if (!faded) {
264		glUniform4f(colorLocation, 1.0f, 1.0f, 1.0f, 1.0f);
265	} else {
266		glUniform4f(colorLocation, 0.8f, 0.8f, 0.8f, 0.8f);		
267	}
268
269	glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
270
271	glBindVertexArray(0);
272	glUseProgram(0);
273}
274
275static void _prepareForFrame(struct mGUIRunner* runner) {
276	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
277	frameBuffer = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, 256 * 256 * 4, GL_MAP_WRITE_BIT);
278	if (frameBuffer) {
279		runner->core->setVideoBuffer(runner->core, frameBuffer, 256);
280	}
281	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
282}
283
284static void _drawFrame(struct mGUIRunner* runner, bool faded) {
285	++framecount;
286	if (!frameLimiter && framecount < framecap) {
287		return;
288	}
289
290	unsigned width, height;
291	runner->core->desiredVideoDimensions(runner->core, &width, &height);
292
293	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
294	glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
295
296	glActiveTexture(GL_TEXTURE0);
297	glBindTexture(GL_TEXTURE_2D, tex);
298	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, height, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
299	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
300
301	_drawTex(runner, width, height, faded);
302}
303
304static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsigned width, unsigned height, bool faded) {
305	glActiveTexture(GL_TEXTURE0);
306	glBindTexture(GL_TEXTURE_2D, tex);
307	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
308
309	_drawTex(runner, width, height, faded);
310}
311
312static uint16_t _pollGameInput(struct mGUIRunner* runner) {
313	return _pollInput(&runner->core->inputMap);
314}
315
316static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
317	UNUSED(runner);
318	if (!frameLimiter && limit) {
319		while (enqueuedBuffers > 1) {
320			AudioOutBuffer* releasedBuffers;
321			u32 audoutNReleasedBuffers;
322			audoutWaitPlayFinish(&releasedBuffers, &audoutNReleasedBuffers, 100000000);
323			enqueuedBuffers -= audoutNReleasedBuffers;
324		}
325	}
326	frameLimiter = limit;
327}
328
329static bool _running(struct mGUIRunner* runner) {
330	UNUSED(runner);
331	return appletMainLoop();
332}
333
334static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
335	UNUSED(stream);
336	AudioOutBuffer* releasedBuffers;
337	u32 audoutNReleasedBuffers;
338	audoutGetReleasedAudioOutBuffer(&releasedBuffers, &audoutNReleasedBuffers);
339	enqueuedBuffers -= audoutNReleasedBuffers;
340	if (!frameLimiter && enqueuedBuffers >= N_BUFFERS) {
341		blip_clear(left);
342		blip_clear(right);
343		return;
344	}
345	if (enqueuedBuffers >= N_BUFFERS - 1 && R_SUCCEEDED(audoutWaitPlayFinish(&releasedBuffers, &audoutNReleasedBuffers, 10000000))) {
346		enqueuedBuffers -= audoutNReleasedBuffers;
347	}
348
349	struct GBAStereoSample* samples = audioBuffer[audioBufferActive];
350	blip_read_samples(left, &samples[0].left, SAMPLES, true);
351	blip_read_samples(right, &samples[0].right, SAMPLES, true);
352	audoutAppendAudioOutBuffer(&audoutBuffer[audioBufferActive]);
353	audioBufferActive += 1;
354	audioBufferActive %= N_BUFFERS;
355	++enqueuedBuffers;
356}
357
358static int _batteryState(void) {
359	u32 charge;
360	int state = 0;
361	if (R_SUCCEEDED(psmGetBatteryChargePercentage(&charge))) {
362		state = (charge + 12) / 25;
363	} else {
364		return BATTERY_NOT_PRESENT;
365	}
366	ChargerType type;
367	if (R_SUCCEEDED(psmGetChargerType(&type)) && type) {
368		state |= BATTERY_CHARGING;
369	}
370	return state;
371}
372
373int main(int argc, char* argv[]) {
374	socketInitializeDefault();
375	nxlinkStdio();
376	initEgl();
377	romfsInit();
378	audoutInitialize();
379	psmInitialize();
380
381	struct GUIFont* font = GUIFontCreate();
382
383	u32 width = 1280;
384	u32 height = 720;
385
386	glViewport(0, 0, width, height);
387	glClearColor(0.f, 0.f, 0.f, 1.f);
388
389	glGenTextures(1, &tex);
390	glActiveTexture(GL_TEXTURE0);
391	glBindTexture(GL_TEXTURE_2D, tex);
392	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
393	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
394	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
395	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
396	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
397
398	glGenBuffers(1, &pbo);
399	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
400	glBufferData(GL_PIXEL_UNPACK_BUFFER, 256 * 256 * 4, NULL, GL_STREAM_DRAW);
401	frameBuffer = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, 256 * 256 * 4, GL_MAP_WRITE_BIT);
402	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
403
404	program = glCreateProgram();
405	GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
406	GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
407	const GLchar* shaderBuffer[2];
408
409	shaderBuffer[0] = _gles2Header;
410
411	shaderBuffer[1] = _vertexShader;
412	glShaderSource(vertexShader, 2, shaderBuffer, NULL);
413
414	shaderBuffer[1] = _fragmentShader;
415	glShaderSource(fragmentShader, 2, shaderBuffer, NULL);
416
417	glAttachShader(program, vertexShader);
418	glAttachShader(program, fragmentShader);
419
420	glCompileShader(fragmentShader);
421
422	GLint success;
423	glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
424	if (!success) {
425		GLchar msg[512];
426		glGetShaderInfoLog(fragmentShader, sizeof(msg), NULL, msg);
427		puts(msg);
428	}
429
430	glCompileShader(vertexShader);
431
432	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
433	if (!success) {
434		GLchar msg[512];
435		glGetShaderInfoLog(vertexShader, sizeof(msg), NULL, msg);
436		puts(msg);
437	}
438	glLinkProgram(program);
439
440	glDeleteShader(vertexShader);
441	glDeleteShader(fragmentShader);
442
443	texLocation = glGetUniformLocation(program, "tex");
444	colorLocation = glGetUniformLocation(program, "color");
445	dimsLocation = glGetUniformLocation(program, "dims");
446	insizeLocation = glGetUniformLocation(program, "insize");
447	GLuint offsetLocation = glGetAttribLocation(program, "offset");
448
449	glGenBuffers(1, &vbo);
450	glGenVertexArrays(1, &vao);
451	glBindVertexArray(vao);
452	glBindBuffer(GL_ARRAY_BUFFER, vbo);
453	glBufferData(GL_ARRAY_BUFFER, sizeof(_offsets), _offsets, GL_STATIC_DRAW);
454	glVertexAttribPointer(offsetLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
455	glEnableVertexAttribArray(offsetLocation);
456	glBindVertexArray(0);
457
458	stream.videoDimensionsChanged = NULL;
459	stream.postVideoFrame = NULL;
460	stream.postAudioFrame = NULL;
461	stream.postAudioBuffer = _postAudioBuffer;
462
463	memset(audioBuffer, 0, sizeof(audioBuffer));
464	audioBufferActive = 0;
465	enqueuedBuffers = 0;
466	size_t i;
467	for (i = 0; i < N_BUFFERS; ++i) {
468		audoutBuffer[i].next = NULL;
469		audoutBuffer[i].buffer = audioBuffer[i];
470		audoutBuffer[i].buffer_size = BUFFER_SIZE;
471		audoutBuffer[i].data_size = BUFFER_SIZE;
472		audoutBuffer[i].data_offset = 0;
473	}
474
475	struct mGUIRunner runner = {
476		.params = {
477			width, height,
478			font, "/",
479			_drawStart, _drawEnd,
480			_pollInput, _pollCursor,
481			_batteryState,
482			NULL, NULL,
483		},
484		.keySources = (struct GUIInputKeys[]) {
485			{
486				.name = "Controller Input",
487				.id = AUTO_INPUT,
488				.keyNames = (const char*[]) {
489					"A",
490					"B",
491					"X",
492					"Y",
493					"L Stick",
494					"R Stick",
495					"L",
496					"R",
497					"ZL",
498					"ZR",
499					"+",
500					"-",
501					"Left",
502					"Up",
503					"Right",
504					"Down",
505					"L Left",
506					"L Up",
507					"L Right",
508					"L Down",
509					"R Left",
510					"R Up",
511					"R Right",
512					"R Down",
513					"SL",
514					"SR"
515				},
516				.nKeys = 26
517			},
518			{ .id = 0 }
519		},
520		.configExtra = (struct GUIMenuItem[]) {
521			{
522				.title = "Fast forward cap",
523				.data = "fastForwardCap",
524				.submenu = 0,
525				.state = 7,
526				.validStates = (const char*[]) {
527					"3", "4", "5", "6", "7", "8", "9",
528					"10", "11", "12", "13", "14", "15",
529					"20", "30"
530				},
531				.nStates = 15
532			},
533		},
534		.nConfigExtra = 1,
535		.setup = _setup,
536		.teardown = NULL,
537		.gameLoaded = _gameLoaded,
538		.gameUnloaded = NULL,
539		.prepareForFrame = _prepareForFrame,
540		.drawFrame = _drawFrame,
541		.drawScreenshot = _drawScreenshot,
542		.paused = NULL,
543		.unpaused = _gameLoaded,
544		.incrementScreenMode = NULL,
545		.setFrameLimiter = _setFrameLimiter,
546		.pollGameInput = _pollGameInput,
547		.running = _running
548	};
549	mGUIInit(&runner, "switch");
550
551	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_A, GUI_INPUT_SELECT);
552	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_B, GUI_INPUT_BACK);
553	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_X, GUI_INPUT_CANCEL);
554	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DUP, GUI_INPUT_UP);
555	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DDOWN, GUI_INPUT_DOWN);
556	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DLEFT, GUI_INPUT_LEFT);
557	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DRIGHT, GUI_INPUT_RIGHT);
558
559	audoutStartAudioOut();
560
561	if (argc > 1) {
562		size_t i;
563		for (i = 0; runner.keySources[i].id; ++i) {
564			mInputMapLoad(&runner.params.keyMap, runner.keySources[i].id, mCoreConfigGetInput(&runner.config));
565		}
566		mGUIRun(&runner, argv[1]);
567	} else {
568		mGUIRunloop(&runner);
569	}
570
571	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
572	glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
573	glDeleteBuffers(1, &pbo);
574
575	glDeleteTextures(1, &tex);
576	glDeleteBuffers(1, &vbo);
577	glDeleteProgram(program);
578	glDeleteVertexArrays(1, &vao);
579
580	psmExit();
581	audoutExit();
582	deinitEgl();
583	socketExit();
584	return 0;
585}