all repos — mgba @ 5039d17a3048224b57fef74d468133a16c4bda04

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