all repos — mgba @ 35d2e0eee9a1f5be82b223b712f03a517b4ef6a4

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