all repos — mgba @ 9564a1ab606ffcf28af258cafcb8f7ceff614720

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