all repos — mgba @ 9de8f084ba55460b02d300c1dd8b8e6c56f691d5

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 3
 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 & 2) == 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	frameLimiter = limit;
270}
271
272static bool _running(struct mGUIRunner* runner) {
273	UNUSED(runner);
274	return appletMainLoop();
275}
276
277static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
278	UNUSED(stream);
279	static AudioOutBuffer* releasedBuffers;
280	u32 audoutNReleasedBuffers;
281	audoutGetReleasedAudioOutBuffer(&releasedBuffers, &audoutNReleasedBuffers);
282	enqueuedBuffers -= audoutNReleasedBuffers;
283	if (!frameLimiter && enqueuedBuffers == N_BUFFERS) {
284		blip_clear(left);
285		blip_clear(right);
286		return;
287	}
288
289	struct GBAStereoSample* samples = audioBuffer[audioBufferActive];
290	blip_read_samples(left, &samples[0].left, SAMPLES, true);
291	blip_read_samples(right, &samples[0].right, SAMPLES, true);
292	audoutAppendAudioOutBuffer(&audoutBuffer[audioBufferActive]);
293	audioBufferActive += 1;
294	audioBufferActive %= N_BUFFERS;
295	++enqueuedBuffers;
296}
297
298static int _batteryState(void) {
299	u32 charge;
300	int state = 0;
301	if (R_SUCCEEDED(psmGetBatteryChargePercentage(&charge))) {
302		state = charge / 25;
303	}
304	return state;
305}
306
307int main(int argc, char* argv[]) {
308	socketInitializeDefault();
309	nxlinkStdio();
310	initEgl();
311	romfsInit();
312	audoutInitialize();
313	psmInitialize();
314
315	struct GUIFont* font = GUIFontCreate();
316
317	u32 width = 1280;
318	u32 height = 720;
319
320	glViewport(0, 0, width, height);
321	glClearColor(0.f, 0.f, 0.f, 1.f);
322
323	glGenTextures(1, &tex);
324	glActiveTexture(GL_TEXTURE0);
325	glBindTexture(GL_TEXTURE_2D, tex);
326	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
327	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
328	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
329	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
330
331	program = glCreateProgram();
332	GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
333	GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
334	const GLchar* shaderBuffer[2];
335
336	shaderBuffer[0] = _gles2Header;
337
338	shaderBuffer[1] = _vertexShader;
339	glShaderSource(vertexShader, 2, shaderBuffer, NULL);
340
341	shaderBuffer[1] = _fragmentShader;
342	glShaderSource(fragmentShader, 2, shaderBuffer, NULL);
343
344	glAttachShader(program, vertexShader);
345	glAttachShader(program, fragmentShader);
346
347	glCompileShader(fragmentShader);
348
349	GLint success;
350	glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
351	if (!success) {
352		GLchar msg[512];
353		glGetShaderInfoLog(fragmentShader, sizeof(msg), NULL, msg);
354		puts(msg);
355	}
356
357	glCompileShader(vertexShader);
358
359	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
360	if (!success) {
361		GLchar msg[512];
362		glGetShaderInfoLog(vertexShader, sizeof(msg), NULL, msg);
363		puts(msg);
364	}
365	glLinkProgram(program);
366
367	glDeleteShader(vertexShader);
368	glDeleteShader(fragmentShader);
369
370	texLocation = glGetUniformLocation(program, "tex");
371	colorLocation = glGetUniformLocation(program, "color");
372	dimsLocation = glGetUniformLocation(program, "dims");
373	insizeLocation = glGetUniformLocation(program, "insize");
374	offsetLocation = glGetAttribLocation(program, "offset");
375
376	glGenBuffers(1, &vbo);
377	glBindBuffer(GL_ARRAY_BUFFER, vbo);
378	glBufferData(GL_ARRAY_BUFFER, sizeof(_offsets), _offsets, GL_STATIC_DRAW);
379	glBindBuffer(GL_ARRAY_BUFFER, 0);
380
381	stream.videoDimensionsChanged = NULL;
382	stream.postVideoFrame = NULL;
383	stream.postAudioFrame = NULL;
384	stream.postAudioBuffer = _postAudioBuffer;
385
386	memset(audioBuffer, 0, sizeof(audioBuffer));
387	audioBufferActive = 0;
388	enqueuedBuffers = 0;
389	size_t i;
390	for (i = 0; i < N_BUFFERS; ++i) {
391		audoutBuffer[i].next = NULL;
392		audoutBuffer[i].buffer = audioBuffer[i];
393		audoutBuffer[i].buffer_size = BUFFER_SIZE;
394		audoutBuffer[i].data_size = BUFFER_SIZE;
395		audoutBuffer[i].data_offset = 0;
396	}
397
398	struct mGUIRunner runner = {
399		.params = {
400			width, height,
401			font, "/",
402			_drawStart, _drawEnd,
403			_pollInput, _pollCursor,
404			_batteryState,
405			NULL, NULL,
406		},
407		.keySources = (struct GUIInputKeys[]) {
408			{
409				.name = "Controller Input",
410				.id = AUTO_INPUT,
411				.keyNames = (const char*[]) {
412					"A",
413					"B",
414					"X",
415					"Y",
416					"L Stick",
417					"R Stick",
418					"L",
419					"R",
420					"ZL",
421					"ZR",
422					"+",
423					"-",
424					"Left",
425					"Up",
426					"Right",
427					"Down",
428					"L Left",
429					"L Up",
430					"L Right",
431					"L Down",
432					"R Left",
433					"R Up",
434					"R Right",
435					"R Down",
436					"SL",
437					"SR"
438				},
439				.nKeys = 26
440			},
441			{ .id = 0 }
442		},
443		.nConfigExtra = 0,
444		.setup = _setup,
445		.teardown = NULL,
446		.gameLoaded = _gameLoaded,
447		.gameUnloaded = NULL,
448		.prepareForFrame = NULL,
449		.drawFrame = _drawFrame,
450		.drawScreenshot = _drawScreenshot,
451		.paused = NULL,
452		.unpaused = _gameLoaded,
453		.incrementScreenMode = NULL,
454		.setFrameLimiter = _setFrameLimiter,
455		.pollGameInput = _pollGameInput,
456		.running = _running
457	};
458	mGUIInit(&runner, "switch");
459
460	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_A, GUI_INPUT_SELECT);
461	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_B, GUI_INPUT_BACK);
462	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_X, GUI_INPUT_CANCEL);
463	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DUP, GUI_INPUT_UP);
464	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DDOWN, GUI_INPUT_DOWN);
465	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DLEFT, GUI_INPUT_LEFT);
466	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DRIGHT, GUI_INPUT_RIGHT);
467
468	audoutStartAudioOut();
469	mGUIRunloop(&runner);
470
471	psmExit();
472	audoutExit();
473	deinitEgl();
474	socketExit();
475	return 0;
476}