all repos — mgba @ 7bbc607a4c57ab8b64be823b2d389c850a6f0ae7

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 void _setup(struct mGUIRunner* runner) {
165	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_A, GBA_KEY_A);
166	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_B, GBA_KEY_B);
167	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_PLUS, GBA_KEY_START);
168	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_MINUS, GBA_KEY_SELECT);
169	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DUP, GBA_KEY_UP);
170	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DDOWN, GBA_KEY_DOWN);
171	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DLEFT, GBA_KEY_LEFT);
172	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DRIGHT, GBA_KEY_RIGHT);
173	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_L, GBA_KEY_L);
174	_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_R, GBA_KEY_R);
175
176	runner->core->setVideoBuffer(runner->core, frameBuffer, 256);
177	runner->core->setAVStream(runner->core, &stream);
178}
179
180static void _gameLoaded(struct mGUIRunner* runner) {
181	u32 samplerate = audoutGetSampleRate();
182
183	double ratio = GBAAudioCalculateRatio(1, 60.0, 1);
184	blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), samplerate * ratio);
185	blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), samplerate * ratio);
186}
187
188static void _drawTex(struct mGUIRunner* runner, unsigned width, unsigned height, bool faded) {
189	glBindBuffer(GL_ARRAY_BUFFER, vbo);
190
191	glEnable(GL_BLEND);
192	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
193
194	glUseProgram(program);
195	float aspectX = width / (float) runner->params.width;
196	float aspectY = height / (float) runner->params.height;
197	float max;
198	if (aspectX > aspectY) {
199		max = floor(1.0 / aspectX);
200	} else {
201		max = floor(1.0 / aspectY);
202	}
203
204	aspectX *= max;
205	aspectY *= max;
206
207	glUniform1i(texLocation, 0);
208	glUniform2f(dimsLocation, aspectX, aspectY);
209	glUniform2f(insizeLocation, width, height);
210	if (!faded) {
211		glUniform4f(colorLocation, 1.0f, 1.0f, 1.0f, 1.0f);
212	} else {
213		glUniform4f(colorLocation, 0.8f, 0.8f, 0.8f, 0.8f);		
214	}
215
216	glVertexAttribPointer(offsetLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
217	glEnableVertexAttribArray(offsetLocation);
218
219	glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
220
221	glDisableVertexAttribArray(offsetLocation);
222	glBindBuffer(GL_ARRAY_BUFFER, 0);
223	glUseProgram(0);
224}
225
226static void _drawFrame(struct mGUIRunner* runner, bool faded) {
227	glActiveTexture(GL_TEXTURE0);
228	glBindTexture(GL_TEXTURE_2D, tex);
229	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, frameBuffer);
230
231	unsigned width, height;
232	runner->core->desiredVideoDimensions(runner->core, &width, &height);
233	_drawTex(runner, width, height, faded);
234
235	++framecount;
236}
237
238static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsigned width, unsigned height, bool faded) {
239	glActiveTexture(GL_TEXTURE0);
240	glBindTexture(GL_TEXTURE_2D, tex);
241	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
242
243	_drawTex(runner, width, height, faded);
244}
245
246static uint16_t _pollGameInput(struct mGUIRunner* runner) {
247	int keys = 0;
248	hidScanInput();
249	u32 padkeys = hidKeysHeld(CONTROLLER_P1_AUTO);
250	keys |= mInputMapKeyBits(&runner->core->inputMap, AUTO_INPUT, padkeys, 0);
251	return keys;
252}
253
254static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
255	UNUSED(runner);
256	frameLimiter = limit;
257}
258
259static bool _running(struct mGUIRunner* runner) {
260	UNUSED(runner);
261	return appletMainLoop();
262}
263
264static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
265	UNUSED(stream);
266	static AudioOutBuffer* releasedBuffers;
267	u32 audoutNReleasedBuffers;
268	audoutGetReleasedAudioOutBuffer(&releasedBuffers, &audoutNReleasedBuffers);
269	enqueuedBuffers -= audoutNReleasedBuffers;
270	if (!frameLimiter && enqueuedBuffers == N_BUFFERS) {
271		blip_clear(left);
272		blip_clear(right);
273		return;
274	}
275
276	struct GBAStereoSample* samples = audioBuffer[audioBufferActive];
277	blip_read_samples(left, &samples[0].left, SAMPLES, true);
278	blip_read_samples(right, &samples[0].right, SAMPLES, true);
279	audoutAppendAudioOutBuffer(&audoutBuffer[audioBufferActive]);
280	audioBufferActive += 1;
281	audioBufferActive %= N_BUFFERS;
282	++enqueuedBuffers;
283}
284
285int main(int argc, char* argv[]) {
286	socketInitializeDefault();
287	nxlinkStdio();
288	initEgl();
289	romfsInit();
290	audoutInitialize();
291
292	struct GUIFont* font = GUIFontCreate();
293
294	u32 width = 1280;
295	u32 height = 720;
296
297	glViewport(0, 0, width, height);
298	glClearColor(0.f, 0.f, 0.f, 1.f);
299
300	glGenTextures(1, &tex);
301	glActiveTexture(GL_TEXTURE0);
302	glBindTexture(GL_TEXTURE_2D, tex);
303	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
304	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
305	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
306	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
307
308	program = glCreateProgram();
309	GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
310	GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
311	const GLchar* shaderBuffer[2];
312
313	shaderBuffer[0] = _gles2Header;
314
315	shaderBuffer[1] = _vertexShader;
316	glShaderSource(vertexShader, 2, shaderBuffer, NULL);
317
318	shaderBuffer[1] = _fragmentShader;
319	glShaderSource(fragmentShader, 2, shaderBuffer, NULL);
320
321	glAttachShader(program, vertexShader);
322	glAttachShader(program, fragmentShader);
323
324	glCompileShader(fragmentShader);
325
326	GLint success;
327	glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
328	if (!success) {
329		GLchar msg[512];
330		glGetShaderInfoLog(fragmentShader, sizeof(msg), NULL, msg);
331		puts(msg);
332	}
333
334	glCompileShader(vertexShader);
335
336	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
337	if (!success) {
338		GLchar msg[512];
339		glGetShaderInfoLog(vertexShader, sizeof(msg), NULL, msg);
340		puts(msg);
341	}
342	glLinkProgram(program);
343
344	glDeleteShader(vertexShader);
345	glDeleteShader(fragmentShader);
346
347	texLocation = glGetUniformLocation(program, "tex");
348	colorLocation = glGetUniformLocation(program, "color");
349	dimsLocation = glGetUniformLocation(program, "dims");
350	insizeLocation = glGetUniformLocation(program, "insize");
351	offsetLocation = glGetAttribLocation(program, "offset");
352
353	glGenBuffers(1, &vbo);
354	glBindBuffer(GL_ARRAY_BUFFER, vbo);
355	glBufferData(GL_ARRAY_BUFFER, sizeof(_offsets), _offsets, GL_STATIC_DRAW);
356	glBindBuffer(GL_ARRAY_BUFFER, 0);
357
358	stream.videoDimensionsChanged = NULL;
359	stream.postVideoFrame = NULL;
360	stream.postAudioFrame = NULL;
361	stream.postAudioBuffer = _postAudioBuffer;
362
363	memset(audioBuffer, 0, sizeof(audioBuffer));
364	audioBufferActive = 0;
365	enqueuedBuffers = 0;
366	size_t i;
367	for (i = 0; i < N_BUFFERS; ++i) {
368		audoutBuffer[i].next = NULL;
369		audoutBuffer[i].buffer = audioBuffer[i];
370		audoutBuffer[i].buffer_size = BUFFER_SIZE;
371		audoutBuffer[i].data_size = BUFFER_SIZE;
372		audoutBuffer[i].data_offset = 0;
373	}
374
375	struct mGUIRunner runner = {
376		.params = {
377			width, height,
378			font, "/",
379			_drawStart, _drawEnd,
380			_pollInput, NULL,
381			NULL,
382			NULL, NULL,
383		},
384		.keySources = (struct GUIInputKeys[]) {
385			{
386				.name = "Controller Input",
387				.id = AUTO_INPUT,
388				.keyNames = (const char*[]) {
389					"A",
390					"B",
391					"X",
392					"Y",
393					"L Stick",
394					"R Stick",
395					"L",
396					"R",
397					"ZL",
398					"ZR",
399					"+",
400					"-",
401					"Left",
402					"Up",
403					"Right",
404					"Down",
405					"L Left",
406					"L Up",
407					"L Right",
408					"L Down",
409					"R Left",
410					"R Up",
411					"R Right",
412					"R Down",
413					"SL",
414					"SR"
415				},
416				.nKeys = 26
417			},
418			{ .id = 0 }
419		},
420		.nConfigExtra = 0,
421		.setup = _setup,
422		.teardown = NULL,
423		.gameLoaded = _gameLoaded,
424		.gameUnloaded = NULL,
425		.prepareForFrame = NULL,
426		.drawFrame = _drawFrame,
427		.drawScreenshot = _drawScreenshot,
428		.paused = NULL,
429		.unpaused = _gameLoaded,
430		.incrementScreenMode = NULL,
431		.setFrameLimiter = _setFrameLimiter,
432		.pollGameInput = _pollGameInput,
433		.running = _running
434	};
435	mGUIInit(&runner, "switch");
436
437	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_A, GUI_INPUT_SELECT);
438	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_B, GUI_INPUT_BACK);
439	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_X, GUI_INPUT_CANCEL);
440	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DUP, GUI_INPUT_UP);
441	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DDOWN, GUI_INPUT_DOWN);
442	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DLEFT, GUI_INPUT_LEFT);
443	_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DRIGHT, GUI_INPUT_RIGHT);
444
445	audoutStartAudioOut();
446	mGUIRunloop(&runner);
447
448	audoutExit();
449	deinitEgl();
450	socketExit();
451	return 0;
452}